Jak dělám záznamy svých přednášek pro YouTube

Od určité doby nahrávám veškeré své přednášky pro HAVIT YouTube Channel. Nenahrávám stage, ale jen obrazovku a audio.

Základní setup je jednoduchý…

Short Story

Záznam obrazovky pomocí Camtasie, audio nahrávám pomocí klopového mikrofonu do iPhonu, v Camtasii to spojím v bodě tlesknutí a vyexportuju do MP4 pro YouTube.

TechSmith Camtasia pro záznam obrazovky

image

Konkrétně Camtasia Recorder:

          • v režimu Full Screen
  • se záznamem z vestavěného mikrofonu v notebooku (je dobré si dopředu vyzkoušet) – pro některé „nepohyblivé“ přednášky stačí, jinak mi poskytuje synchronizační klapku se samostatným audiozáznamem (viz dále)
  • jediná záludnost – nesmíte pustit Camtasia Recorder, dokud se nepřipojíte na projektor a neusadíte si rozlišení. Jinak se můžete dočkat nepříjemností, jako já letos na brněnském MS Festu. Jak jsem dodatečně analyzoval, zapnutý Recorder blokoval změny rozlišení, a mě se zoufale v časovém presu nedařilo s projektorem uspokojivě skamarádit.
  • licenci Camtasie mám jako MVP od TechSmith bezplatně, alternativně lze použít Windows Media Encoder (ale sám jsem nikdy nezkoušel).

Rode smartLav mikrofon + iPhone pro záznam zvuku

image

Velmi kvalitní „klopový“ mikrofon:

  • v zapojení do iPhone telefonu (pro přednášku vždy přepínám do Flight modu),
  • se záznamem do aplikace Rode Rec (ale úplně v pohodě to fungovalo i do předinstalované aplikace Diktafon)
  • nahrávám v nastavení 11025 Hz, Mono (vychází to kolem 20 MB/hod),
  • mikrofon nedávat ke krku, ale spíš někam na prsa/solar, citlivý je dost,
  • důležité je před záznamem nastavit/zkontroloval leveling záznamu, obvykle je citlivost vytočena na maximum, a pokud by se to nechalo, záznam bude téměř nepoužitelný (Jednou jsem musel použití i původní obyčejný záznam z vestavěného mikrofonu notebooku, když jsem na toto zapomněl). Je potřeba stáhnout citlivost tak, aby se při mluvení nedostával ukazatel do červené zóny.
  • výstup si přes File Sharing do formátu AAC (.m4a), stahuji z iPhone přes iTunes (starší verze Camtasie M4A neuměly, bylo potřeba to prohnat přes iTunes do MP3, nyní už to jde přímo),
  • stačil by zřejmě i mnohem obyčejnější mikrofon a jakékoliv jiné záznamové zařízení k němu, které vám umožní volný pohyb. Michal Altair Valášek třeba používá stejným způsobem diktafon SONY, který položí někam zhruba do první řady.

Sestříhání v Camtasia Studiu

Celý trik následného spojení v Camtasia Studiu spočívá v použití synchronizační klapky (stejný princip, jako u filmu), v amatérských podmínkách tedy tlesknutí/lusknutí, nebo podobném zvuku na začátku záznamu, který zobrazí na audio-křivce špičku.

image

Následně je potřeba zahodit méně kvalitní audio ze záznamu obrazovky, tj. rozpojit obraz od audia (pravé tlačítko myši na stopě + Separate video and audio), stejně tak zahodit track System Audio (pokud tam speciálně něco nemáte).

Obvykle dávám ještě:

  • Enable noise removal (Adjust sensitivity = 40-50)
  • někdy pomáhá Enable volume leveling, pokud nebyl dobře nalevelovaný mikrofon při záznamu.

Produkce MP4 pro YouTube

Dle instrukcí, které jsem našel přímo na webu na YouTube a zkombinoval s vlastními požadavky, používám obvykle nastavení produkce (ve finále koukám jiné, než YouTube doporučuje :-)):

  • Editing Dimensions (= Recording Dimensions)
  • Format = MP4
  • bez Controlleru
  • ostatní nechávám default (Frame Rate = automatic, …)

Dostanu MP4 soubor (obvykle cca 100-200MB), který si archivuji a nahraju na YouTube. Zdrojové soubory nakonec zahazuji.

Na YouTube ještě většinou nahrávám vlastní „miniaturu“. První slide uložím z PowerPointu jako PNG (File / Save As…) a doplním ho v Paint.NET na velikost 1280×720 (rozměr dle doporučení YouTube).

Azure: Nedaří se vytvořit databázi importem z *.bacpac

Při pokusu o založení nové databáze importem z *.bacpac v portálu se zobrazuje potvrzení, že přístup do Azure Storage je v pořádku a toto oznámení je následováno chybou oznamující, že nelze vložit požadavek na import databáze.

Heslo k databázovému serveru obsahovalo diakritiku, nápad zkusit heslo bez diakritiky se osvědčil –  po změně hesla funguje import bez problémů.

Na Azure mi nešla založit DB ve WestEurope, SQL server ano, jiné regiony ano (Azure Free Trial?)

Krátká empirická zkušenost.

Symptoms

Nová subscription, která vznikla tak nějak mimoděk, jako první subscription k danému organizational accountu a při zakládání této první subscription se to na nic neptalo a založilo to Trial Subscription s kreditem 150 EUR (pěkně děkuji, viz níže).

No nic, víc jsem to nezkoumal a prostě jen vypnul spending limit s tím, že se z toho stane běžná Pay-As-You-Go (

Zakládáme do subscription nové služby. WebSites v pohodě, WebJobs v pohodě, SendGrid v pohodě, při zakládání nové databáze „starý“ portal (manage.windowsazure.com) založí databázový server, ale k tomu řekne Could not create database ‚XY‘:

clip_image002

V detailech zprávy je výstižné upřesnění: Could not create database ‚XY‘.

Nejpozoruhodnější na tom je, že úplně stejná databáze v regionu East Asia se bez problémů vytvoří. Ostatní služby WestEurope OK.

Při použití nového portálu (portal.azure.com) je chování stejné, jen v detailech problému je cosi o „not supported for your subscription“ (WTF!). Na homepage všechny uzly Azure zelené, žádný výpadek není hlášen.

?Resolution?

Běžné Googlení „Could not create database“ dává diskuzní příspěvky o tom, že se něco v Azure nezpropagovalo a nezbývá než kontaktovat Azure Support. Pay-As-You-Go však má support omezený a nechce se mi čekat, než se to někam pohne. Zakládám tedy raději novou druhou subscription (tentokrát se to již ptá na subscription type a volím explicitně Pay-As-You-Go) a po chvilce usazování (napoprvé to opět nešlo, ale pro změnu nešel založit ani SQL server) vše úspěšně funguje.

?Cause?

Pokud i nyní zkusím na té prvně vytvořené subscription (která se pořád na různých místech identifikuje jako Trial a zřejmě tedy odstraněním spending limitu nedojde k přepnutí na produkční) založit databázi do WestEurope, dojde pouze k založení DB serveru, ale založení DB je zamítnuto.

Celé to zavání nějakým nedostatkem instancí v datovém centru WestEurope, což by vysvětlovalo i skutečnost, že subscription, která se označuje jako Trial, je na tom hůře, než následně založená Production. Divné jenom je, že na té Trial subscription to založí server a „jenom“ na něm nedovolí založit DB (i když i to nějakou logiku má).

Závěr

Přesnou příčinu neznám a více času s tím ztrácet nechci. Celé to do KnowledgeBase zapisuji proto, že nemusím být sám, kdo bude takovým „Free Trial“ obšťastněn.

Někdo máte podobnou/jinou zkušenost? Někdo máte lepší vysvětlení?

ASP.NET Identity 2.1 do MVC projektu s vlastní implementací UserStore (přes repositories)

Tento post popisuje jeden z mých prvních pokusů s ASP.NET Identity, které jsem chtěl zapojit do N-tier projektu s odděleným doménovým modelem, datovou vrstvou nad EntityFrameworkem, repositories, unit-of-work, atp. Webový projekt zde je abstrahován od Entity Frameworku a nemá přístup k DbContextu (nemá referenci na EntityFramework.dll).

Rychle se tedy ukázalo, že se musím vydat cestou vlastní implementace UserStore (resp. custom storage provider), který by na rozdíl od Microsoftí implementace (z balíčku Microsoft.AspNet.Identity.EntityFramework) neměl dependency na DbContext, ale na Repositories.

Relevantní články

Před zahájením implementace doporučuji zorientovat se trochu v problematice:

Pozor také, že ASP.NET Identity existuje v několika verzích a starší články se odkazují na starší verze. Verze 1.0 byla pouze základem, např, s omezením na string-klíče na IUser a IRole. Verze 2.x je aktuální podoba pro ASP.NET 4.x a mimo IUser<TKey>, IRole<TKey> interface s volbou typu klíče doplňuje i podporu zamykání účtů a spoustu dalších drobností. ASP.NET Identity 3.x je ve vývoji pro ASP.NET 5. Svět ASP.NET je nyní hodně dynamický a „kvalita“ dokumentace tomu bohužel odpovídá.

Výchozí struktura projektů

DomainClasses

  • POCO entity
  • nereferencuje skoro nic (rozhodně ne EntityFramework)
  • sem budeme umisťovat naše entity User, Role, …

DataLayer

  • persistence doménových tříd pomocí EntityFrameworku
  • mapování entit na DB, DbContext
  • referencuje DomainClasses, Entity Framework
  • sem musíme našim entitám User, Role, … nastavit mapování na DB

Repositories

  • prostě repositories :-) a související (UnitOfWork, atp.
  • referencuje DomainClasses, DataLayer
  • sem doplníme UserRepository, RoleRepository, …

Services

  • aplikační logika, WCF služby, fasády pro UI, …
  • referencuje DomainClasses, Repositories
  • sem budeme umisťovat naši implementaci UserStore s napojením na Repositories

Web

  • klasický ASP.NET MVC projekt
  • referencuje DomainClassses, Services, Repositories, nikoliv však EntityFramework
  • …a tady „to“ celé pomocí UserManageru použijeme
  • a propojíme s OWIN authentizací (cookies-based)

1. Instalace NuGet balíčků

Nejprve musíme do naší solution nainstalovat příslušné NuGet balíčky.

Microsoft.AspNet.Identity.Core

Tento NuGet balíček je základem celého ASP.NET Identity a musíme ho dostat skoro všude (bohužel!)

  • do DomainClasses – budeme potřebovat interfaces IUser, IRole. Dalo by se obejít bez těchto interfaces, ale museli bychom potom buď dodefinovat interfaces na nějakém potomkovi vytvořeném v Services pro účely propojení s ASP.NET Identity, nebo bychom museli pro ASP.NET Identity vytvořit třídy nové a na doménové třídy (entity) je mapovat (např. pomocí AutoMapperu)
  • dp DataLayer, Repositories – pořád kvůli interfaces IUser, IRole
  • do Services – budeme potřebovat pro IUserStore, IRoleStore, IUserRoleStore, …
  • do Web – sem hlavně ;-)

Balíček Microsoft.AspNet.Identity.Core naštěstí nemá žádné další závislosti, takže nám do DomainClasses, DAL, Repositories a Services nedotáhne žádný další „bordel“.

Microsoft.AspNet.Identity.Owin

OWIN implementace ASP.NET Identity do projektu Web.

Přitáhne sebou spoustu dependencies jako OWIN a další související balíčky Microsoft.Owin.Security.

Microsoft.Owin.Host.SystemWeb

Aby nám OWIN fungoval na „legacy“ System.Web. :-))´

2. Vytvoření entit User, Role + persistence do DB v DAL + Repositories

Do projektu DomainClasses vytvoříme entity User a Role.

User implementuje rozhraní IUser<TKey>, které naštěstí předepisuje jen TKey Id a string UserName. Další properties jsou na nás, později sem přidáme třeba něco na uložení hashovaného hesla, atp.

public interface IUser<out TKey>
{
    TKey Id { get; }
    string UserName { get; set; }
}

User pak může vypadat třeba takto:

public class User : IUser<int>
{
  [Required]
  public int Id { get; set; }

  [Required]
  public string UserName { get; set; }

  public ICollection<Role> Roles { get; set; }
  
  public string PasswordHash { get; set; }
}

Role implementuje obdobné rozhraní IRole<TKey>, které předpisuje properties TKey Id a string Name.

public interface IRole<out TKey>
{
    TKey Id { get; }
    string Name { get; set; }
}

Role pak může vypadat takto:

public class Role : IRole<int>
{
  [Required]
  public int Id { get; set; }

  [Required]
  public string Name { get; set; }
}

Dle vlastního způsobu implementace přidáme do projektu DataLayer persistenci nových entit (mapování EntityTypeConfiguration<>, IDbSet<> do DbContext, atp.). Stejně tak svým způsobem vytvoříme příslušné Repositories. Ani jedno není předmětem tohoto článku – pěkně v kostce viz třeba PluralSight: Entity Framework in the Enterprise (Julie Lerman).

3. Implementace UserStore, popř. RoleStore

UserStore opět není nic jiného, než naše třída, která implementuje jedno nebo více rozhraní, podle toho, co všechno od ASP.NET Identity očekáváme. Jedná se víceméně o CRUD operace, popř. několik dalších „storage“ operací, jako např. vyhledávání dle UserName, atp., vše pěkně připraveno pro async:

public interface IUserStore<TUser, in TKey> : IDisposable where TUser: class, IUser<TKey>
{
    Task CreateAsync(TUser user);
    Task DeleteAsync(TUser user);
    Task<TUser> FindByIdAsync(TKey userId);
    Task<TUser> FindByNameAsync(string userName);
    Task UpdateAsync(TUser user);
}

UserStore následně předhazujeme jako dependency do UserManageru, což je středobod ASP.NET Identity. UserManager obsahuje většinu aplikační logiky a kdo zná starší MembershipProvidery, tak si jej může představit jako novou podobu třídy Membership.

Nemá smysl, abych zde vypisoval celý UserStore, stačí malá ochutnávka, ostatní metody jsou implementovány stejně, zpravidla jako prosté volání metody příslušné Repository, nebo něco málo nad tím. Pro ukázku uvádím podobu volání vůči Repository, která nemá async podporu. Pokud máte async Repository, potom si jistě dokážete představit, že by kód byl ještě jednodušší.

public class UserStore : IUserStore<User, int>
{
  private readonly IUserRepository userRepository;
  private readonly IRoleRepository roleRepository;

  public UserStore(IUserRepository userRepository, IRoleRepository roleRepository)
  {
    this.userRepository = userRepository;
    this.roleRepository = roleRepository;
  }

  /// <summary>
  /// Insert a new user
  /// </summary>
  public Task CreateAsync(User user)
  {
    Contract.Requires<ArgumentNullException>(user != null);
    
    userRepository.AddNew(user);

    return Task.FromResult<object>(null);
  }

  

  /// <summary>
  /// Finds a user
  /// </summary>
  public Task<User> FindByIdAsync(int userId)
  {
    return Task.FromResult(userRepository.GetByID(userId));
  }

  /// <summary>
  /// Find a user by name
  /// </summary>
  public Task<User> FindByNameAsync(string userName)
  {
    Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(userName));

    return Task.FromResult(userRepository.GetByUserName(userName));
  }
  
  ...

}

Pro úplnost uvádím, že UserStore zde počítá s dependency-injection repositories přes constructor. Custom implementace zde může být jiná.

4. Nastavení OWIN autentizace ve Startup.cs

Dostáváme se pomalu k projektu Web a použití ASP.NET Identity v něm. Samotné ASP.,NET Identity authentizaci neimplementuje, stejně tak jako nebyla implementována v MembershipProviderech. ASP.NET Identity poskytuje pouze určité služby pro obsluhu uživatelů, jejich členství v rolích, ukládání hesel, vazeb na externí authentizační služby ála Google/Facebook/Twitter, atp.

Samotný authentizační mechanizmus použijeme z OWIN, konkrétně Microsoft.Owin.Security.Cookies, což je něco jako nástupce FormsAuthentication a jeho FormAuthenticationTicketu v cookie.

Do webového projektu tedy přidáme novou položku „OWIN Startup class“ (pokud ji tam už nemáme), je to klasická item-template ve Visual Studiu 2013 (tuším od Update 2 nebo 3).

[assembly: OwinStartup(typeof(Havit.HealthGuard.Web.Startup))]

namespace Havit.HealthGuard.Web
{
	public class Startup
	{
		public void Configuration(IAppBuilder app)
		{
			app.UseCookieAuthentication(new CookieAuthenticationOptions()
				{
					AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
					LoginPath = new PathString("/authentication/login")
				});
		}
	}
}

5. Nastavení autorizace stránek

Aby celé naše snažení mělo nějaký smysl, musíme samozřejmě nejenom zajistit autentizaci (ověření identity), ale i autorizaci (nastavení přístupových práv). Jelikož zde předpokládám MVC projekt (WebForms viz jeden z článků odkazovaných výše), použijeme atribut [Authorize], pro začátek třeba v podobě globálního filtru (z Global.asax):

protected void Application_Start(object sender, EventArgs e)
{
	...
	FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
	...
}

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
	...
	filters.Add(new AuthorizeAttribute());
	...
}		 

…čímž omezíme přístup na všechny controllery pouze na přihlášené uživatele

6. Přihlašovací stránka

Dostáváme se k jádru věci – přihlašovací stránce. Nebudu zde popisovat zjevnou podobu View, ani jeho ViewModel, příslušná Action vypadá takto (userManager je zde přes dependency-injection z constructoru controlleru, ale můžete si samozřejmě jeho instanci postavit prostým constructorem):

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(string returnUrl, AuthenticationLoginViewModel loginModel)
{
	if (ModelState.IsValid)
	{

		User user = userManager.Find(loginModel.UserName, loginModel.Password);
		if (user != null)
		{
			IAuthenticationManager authenticationManager = HttpContext.GetOwinContext().Authentication;
			authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

			ClaimsIdentity identity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
			AuthenticationProperties props = new AuthenticationProperties();
			props.IsPersistent = loginModel.IsPersistent;
			authenticationManager.SignIn(props, identity);
					
			if (Url.IsLocalUrl(returnUrl))
			{
				return Redirect(returnUrl);
			}
			return this.RedirectToAction<HomeController>(c => c.Index());
		}
		ModelState.AddModelError("", "Invalid username or password.");
	}

	return View(loginModel);
}

Klíčovým řádkem je zde volání userManager.Find(string userName, string password), kterážto metoda vrací naší třídu User, resp. null, pokud přihlášení není úspěšné. Zbylé řádky jsou již jen tanečky kolem OWIN AuthenticationManageru, které nejsou předmětem tohoto článku.

Pokud nyní aplikaci pustíme a přihlašování zkusíme, pak budeme dostávat hlášku „Invalid username or password.“, pokud jsme do tabulky User ještě žádného uživatele nepřidali. Pokud ho tam přidáme, pak při vyplnění správného UserName dostaneme:

System.NotSupportedException: Store does not implement IUserPasswordStore<TUser>.

7. Rozšiřování UserStore

Dostali jsme se přesně do bodu, kde se ukazuje, jak je zamýšlen způsob implementace vlastních „storage-providerů“. ASP.NET Identity přichází se smečkou rozhraní a jednotlivé funkce UserManageru fungují/nefungují podle toho, kolik z těchto rozhraní UserStore implementuje.

Chceme-li použít lokální účty s lokálními hesly, musíme na náš UserStore přidat a implementovat rozhraní IUserPasswordStore.

public interface IUserPasswordStore<TUser, in TKey> : IUserStore<TUser, TKey>, IDisposable where TUser: class, IUser<TKey>
{
    Task<string> GetPasswordHashAsync(TUser user);
    Task<bool> HasPasswordAsync(TUser user);
    Task SetPasswordHashAsync(TUser user, string passwordHash);
}

…k rozhraní IUserStore<>, které již nemusíme explicitně uvádět, přidává několik metod, které pro práci s hesly slouží. Odbočkou se dostáváme k tomu, že UserStore je zde opravdu odpovědný pouze za perzistenci hashe, nikoliv jeho samotný výpočet. Ten svojí aplikační logiku zajišťuje UserManager, a pokud ho chceme ovlivnit, můžeme vyměnit UserManager.PasswordHasher za jinou/vlastní implementaci IPasswordHasher.

Pro UserStore se nabízejí následující interfaces:

V našem případě budeme chtít implementovat IUserPasswordStore pro ukládání hesel a IUserRoleStore pro přiřazování rolí uživatelům:

public class UserStore : IUserStore<User, int>, IUserPasswordStore<User, int>, IUserRoleStore<User, int>
{

  ...

  /// <summary>
  /// Set the user password hash
  /// </summary>
  public Task SetPasswordHashAsync(User user, string passwordHash)
  {
    Contract.Requires<ArgumentNullException>(user != null);

    user.PasswordHash = passwordHash;
  
    return UpdateAsync(user);
  }

  /// <summary>
  /// Get the user password hash
  /// </summary>
  public Task<string> GetPasswordHashAsync(User user)
  {
    Contract.Requires<ArgumentNullException>(user != null);

    return Task.FromResult(user.PasswordHash);
  }

  /// <summary>
  /// Returns true if a user has a password set
  /// </summary>
  public Task<bool> HasPasswordAsync(User user)
  {
    Contract.Requires<ArgumentNullException>(user != null);
    
    return Task.FromResult(!String.IsNullOrWhiteSpace(user.PasswordHash));
  }

  /// <summary>
  /// Adds a user to a role
  /// </summary>
  public Task AddToRoleAsync(User user, string roleName)
  {
    Contract.Requires<ArgumentNullException>(user != null);
    Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(roleName));

    Role role = roleRepository.GetByName(roleName);
    if (role == null)
    {
      throw new InvalidOperationException(String.Format("Unrecognized role name: {0}", roleName));
    }

    if (!user.Roles.Contains(role))
    {
      user.Roles.Add(role);
    }

    return UpdateAsync(user);
  }

  /// <summary>
  /// Removes the role for the user
  /// </summary>
  public Task RemoveFromRoleAsync(User user, string roleName)
  {
    Contract.Requires<ArgumentNullException>(user != null);
    Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(roleName));

    user.Roles.Where(r => (r.Name == roleName)).ForEach(r => user.Roles.Remove(r));

    return UpdateAsync(user);
  }

  /// <summary>
  /// Returns the roles for this user
  /// </summary>
  public Task<IList<string>> GetRolesAsync(User user)
  {
    Contract.Requires<ArgumentNullException>(user != null);

    var roles = user.Roles.Select(r => r.Name).ToArray();

    return Task.FromResult<IList<string>>(roles);
  }

  /// <summary>
  /// Returns true if a user is in the role
  /// </summary>
  public Task<bool> IsInRoleAsync(User user, string roleName)
  {
    Contract.Requires<ArgumentNullException>(user != null);
    Contract.Requires<ArgumentException>(!String.IsNullOrWhiteSpace(roleName));

    bool result = user.Roles.Any(r => (r.Name == roleName));

    return Task.FromResult(result);
  }
}

RoleStore, RoleManager

Nakonec nutno dodat, že vedle IUserStore existuje ještě IRoleStore pro podporu perzistence rolí, tedy jejich samotných definicí, nikoliv jejich přiřazení uživatelům (což řeší výše uvedený IUserRolesStore).

K RoleStore samozřejmě existuje i RoleManager, který obsahuje aplikační logiku kolem rolí.

Závěr

Postavit vlastní UserStore pro ASP.NET Identity, který by používal Repositories, je jednoduché a velmi přímočaré. Jedinou vadou na kráse je absolutní nedostatek seriózní MSFT-dokumentace, na což si bohužel budeme muset zvykat.

Indexy (vč. unikátních) v Entity Frameworku (EF6.1)

Při studiu zdrojových kódu knihovny Microsoft.AspNet.Identity.EntityFramework.dll jsem narazil na zápis, který mi připomněl novinku v Entity Frameworku 6.1 – možnost definovat entitám indexy, včetně těch unikátních.

Principiálně jde o použití data-annotation atributu [Index(„<index name>“, [isUnique: true|false])] na příslušné property, přičemž pokud je atribut se stejným názvem použit na více properties, vznikne index přes více sloupců (vlastností Order se řídí pořadí sloupců v definici indexu).

Ve FluentAPI lze takový efekt zapsat nepřímo, pomocí doplnění anotace:

this.Property(t =&gt; t.Identifier)
	.HasColumnAnnotation(
		IndexAnnotation.AnnotationName,
			new IndexAnnotation(
				new IndexAttribute("UIDX_MonitoredApplication_Identifier") { IsUnique = true }));

UPDATE: Pokud chceme index přes více sloupců, nebo jeden sloupec zahrnout do více indexů, může kód vypadat takto:

Property(loginAccount =&gt; loginAccount.Email)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName,
        new IndexAnnotation(
            new IndexAttribute("UIDX_LoginAccount_Email_Deleted") { IsUnique = true, Order = 1 }));

Property(loginAccount =&gt; loginAccount.SingleSignOnIdentifier)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName,
        new IndexAnnotation(
            new IndexAttribute("UIDX_LoginAccount_SingleSignOnIdentifier_Deleted") { IsUnique = true, Order = 1 }));

Property(loginAccount =&gt; loginAccount.Deleted)
    .HasColumnAnnotation(
        IndexAnnotation.AnnotationName,
        new IndexAnnotation(new[]
        {
            new IndexAttribute("UIDX_LoginAccount_Email_Deleted") { IsUnique = true, Order = 2 },
            new IndexAttribute("UIDX_LoginAccount_SingleSignOnIdentifier_Deleted") { IsUnique = true, Order = 2 }
        }));

Viz též EntityFramework specs – IndexAttribute.

C# 6.0: Ostatní (extension-Add v initializers, overload resolution) [10/10]

Do C# 6.0 se dostaly ještě dvě „drobotiny“, které většině vývojářů život nijak zásadně nezmění.

Collection-initializers podporují extension-podobu Add() metody

V C# 5.0, v inicializaci kolekcí, nemohla být použita extension-podoba metody Add(), přestože VB.NET toto měl. Byl to spíše bug/opomenutí. Každopádně nyní je to napraveno.

Vylepšené vyhodnocování overloadů

Drobná vylepšení, kterých si málokdo všimne. O něco málo méně míst, kde se můžete setkat s výtkou kompilátoru ohledně nejednoznačnosti volby overloadu (např. u nullable hodnotových typů parametrů, atp.)

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: Konstruktor struct bez parametrů [9/10] – vyřazeno z RTM

Tato úprava jazyka není součástí C# 6.0 RTM, byla odstaněna.

C# 5.0 nedovoluje vytvořit na struct explicitní constructor bez parametrů, hodnoty fieldů jsou totiž automaticky inicializovány svými default(T) hodnotami. C# 6.0 toto omezení odstraňuje:

struct Person
{
    public string Name { get; }
    public int Age { get; }
   
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public Person() : this("Jane Doe", 37)
    { }
}

Důležitým omezením však je, že takový konstruktor se použije pouze v případě explicitního volání new Person(), zatímco default(Person) stále založí výchozí hodnoty fieldů, stejnětak vytvoření pole new Person[..].

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: Exception filters, await v catch/finally bloku [8/10]

C# 6.0 dohání chybějící drobnosti kolem výjimek

Exception filters

Catch-blok je vyvolán, pouze pokud je vedle typu výjimky splněna i podmínka:

try
{
    … 
}
catch (MyException ex) if (condition) // už v podmínce se lze odkázat na ex
{
    …
}

Znásilnit tuto konstrukci lze i pro neinvazivní logování výjimek, sám bych s tím však byl opatrný.

private static bool Log(Exception e) { /* log it */ ; return false; }
…
try { … } catch (Exception e) if (Log(e)) {}

Await v catch a finally blocích

C# 5.0 nepodporoval await v catch a finally blocích, protože se Microsoft domníval, že to není potřebné a implementace by byla neúměrně komplikovaná. Nyní dlužnou funkčnost doplnili:

Resource res = null;
try
{
    res = await Resource.OpenAsync(…);
}
catch(ResourceException e)
{
    await Resource.LogAsync(res, e);
}
finally
{
    if (res != null) await res.CloseAsync();
}

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: Index initializers – přehlednější inicializace slovníků [7/10]

Jednou z předchozích novinek v C# byla možnost při založení kolekce/slovníku naplnit ho rovnou i počátečními hodnotami. C# 6.0 přichází se zpřehledněním zápisu pro inicializaci slovníků (a podobných objektů s indexery):

// C# 5.0
var numbers = new Dictionary<int, string> {
    {7, "seven"},
    {9, "nine"},
    {13, "thirteen" }
};

// C# 6.0
var numbers = new Dictionary<int, string> {
    [7] = "seven",
    [9] = "nine",
    [13] = "thirteen"
};

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)

C# 6.0: nameof() expressions [6/10]

Přestože se nová FluentAPI snaží vyhnout textovým řetězcům ve prospěch „strong-type“ zápisů, stále se na mnoha a mnoha místech setkáváme s potřebou zapsat do kódu textový řetězec, který odkazuje na název nějakého prvku. Potíže s tím související jsou nasnadě – kompilátor nám takový textový řetězec nekontroluje a jakýkoliv automatický refactoring v podobě přejmenování prvku zanechá textový řetězec nedotčen. Jde tak o častý zdroj chyb.

C# 6.0 přináší pomůcku, kterou se dá těmto problémům vyhnout. Konstrukci nameof(prvek) převede kompilátor na textovou konstantu odpovídající názvu prvku:

// C# 5.0
(if x == null) throw new ArgumentNullException("x");

// C# 6.0
(if x == null) throw new ArgumentNullException(nameof(x));

Uvnitř nameof() může být prakticky cokoliv, co má jméno – proměnná, typ, metoda, property, atp. Vždy se však použije pouze poslední část jména:

Console.WriteLine(nameof(person.Address.ZipCode)); // vrátí "ZipCode"
Console.WriteLine(nameof(System.Console.WriteLine)); // vrátí "WriteLine"

Série článků o novinkách v C# 6.0:

  1. Auto-property initializers a getter-only (read-only) auto-properties
  2. Expression-bodied function members
  3. Using static
  4. Null-conditional operators (?., ?[], …)
  5. String inerpolation – zkrácený String.Format()
  6. nameof() expressions
  7. Index initializers – přehlednější inicializace slovníků
  8. Exception filters, await v catch/finally blocích
  9. Konstruktor struct bez parametrů
  10. Ostatní (extension-Add v initializers, overload resolution)