Vývoj aplikací ASP.NET Core MVC

Tip

Tento obsah je výňatek z eBooku, architekta moderních webových aplikací s ASP.NET Core a Azure, který je k dispozici na webu .NET Docs nebo jako bezplatný soubor PDF ke stažení, který si můžete přečíst offline.

Navrhování moderních webových aplikací pomocí úvodní miniatury ASP.NET Core a Azure eBooku

"Není důležité, abyste to poprvé získali správně. Je velmi důležité, abyste to naposledy získali správně." - Andrew Hunt a David Thomas

ASP.NET Core je multiplatformní opensourcová architektura pro vytváření moderních cloudově optimalizovaných webových aplikací. ASP.NET základní aplikace jsou odlehčené a modulární, s integrovanou podporou injektáže závislostí, což umožňuje větší testovatelnost a udržovatelnost. V kombinaci s MVC, která kromě aplikací založených na zobrazení podporuje vytváření moderních webových rozhraní API, je ASP.NET Core výkonnou architekturou, pomocí které můžete vytvářet podnikové webové aplikace.

MVC a Razor Pages

ASP.NET Core MVC nabízí mnoho funkcí, které jsou užitečné pro vytváření webových rozhraní API a aplikací. Pojem MVC je zkratka pro model-view-controller, vzor uživatelského rozhraní, který rozděluje povinnosti reagovat na požadavky uživatelů do několika částí. Kromě toho, že tento model sledujete, můžete také implementovat funkce ve svých aplikacích ASP.NET Core jako Razor Pages.

Razor Pages jsou integrované do ASP.NET Core MVC a používají stejné funkce pro směrování, vazby modelu, filtry, autorizaci atd. Místo samostatných složek a souborů pro kontrolery, modely, zobrazení atd. a použití směrování založeného na atributech se razor Pages umístí do jedné složky ("/Pages"), směruje se na základě jejich relativního umístění v této složce a zpracovává požadavky s obslužnými rutinami místo akcí kontroleru. V důsledku toho se při práci se službou Razor Pages obvykle všechny soubory a třídy, které potřebujete, obvykle společně nepřidělují, ne v celém webovém projektu.

Přečtěte si další informace o použití vzorů MVC, Razor Pages a souvisejících vzorů v ukázkové aplikaci eShopOnWeb.

Když vytváříte novou aplikaci ASP.NET Core, měli byste mít na paměti plán pro typ aplikace, kterou chcete sestavit. Při vytváření nového projektu v integrovaném vývojovém prostředí nebo pomocí příkazu rozhraní příkazového dotnet new řádku si můžete vybrat z několika šablon. Nejběžnějšími šablonami projektů jsou prázdné, webové rozhraní API, webová aplikace a webová aplikace (model-view-controller). I když toto rozhodnutí můžete provést pouze při prvním vytvoření projektu, nejedná se o neodvolatelné rozhodnutí. Projekt webového rozhraní API používá standardní kontrolery model-view-controller – ve výchozím nastavení chybí zobrazení. Stejně tak výchozí šablona webové aplikace používá Razor Pages, takže také chybí složka Zobrazení. Do těchto projektů můžete později přidat složku Zobrazení, která podporuje chování na základě zobrazení. Projekty webového rozhraní API a Modelu-View-Controller ve výchozím nastavení neobsahují složku Pages, ale můžete ji přidat později, abyste podporovali chování založené na razor Pages. Tyto tři šablony si můžete představit jako podporu tří různých druhů výchozí interakce uživatelů: data (webové rozhraní API), stránky a zobrazení. Pokud ale chcete, můžete kombinovat a shodovat všechny tyto šablony v rámci jednoho projektu.

Proč Razor Pages?

Razor Pages je výchozím přístupem pro nové webové aplikace v sadě Visual Studio. Razor Pages nabízí jednodušší způsob vytváření funkcí aplikací založených na stránkách, jako jsou formuláře mimo SPA. Použití kontrolerů a zobrazení bylo běžné, že aplikace mají velmi velké kontrolery, které pracovaly s mnoha různými závislostmi a zobrazením modelů a vrátily mnoho různých zobrazení. Výsledkem je větší složitost a často vede k tomu, že kontroloři, kteří nedodržovali zásadu jednotné odpovědnosti nebo otevřené/uzavřené principy efektivně. Razor Pages tento problém řeší zapouzdřením logiky na straně serveru pro danou logickou "stránku" ve webové aplikaci pomocí jeho kódu Razor. Stránka Razor Page, která nemá logiku na straně serveru, se může skládat pouze ze souboru Razor (například "Index.cshtml"). Většina jiných než triviálních stránek Razor Pages ale bude mít přidruženou třídu modelu stránky, která se podle konvence jmenuje stejně jako soubor Razor s příponou ".cs" (například "Index.cshtml.cs").

Model stránky Razor Page kombinuje odpovědnosti kontroleru MVC a modelu zobrazení. Místo zpracování požadavků pomocí metod akce kontroleru se spouští obslužné rutiny modelu stránky, jako je OnGet(), a ve výchozím nastavení vykreslují přidruženou stránku. Razor Pages zjednodušuje proces vytváření jednotlivých stránek v aplikaci ASP.NET Core a současně poskytuje všechny architektonické funkce ASP.NET Core MVC. Jsou dobrou výchozí volbou pro nové funkce založené na stránkách.

Kdy použít MVC

Pokud vytváříte webová rozhraní API, dává vzor MVC větší smysl než při pokusu o použití razor Pages. Pokud váš projekt zveřejní jenom koncové body webového rozhraní API, měli byste v ideálním případě začít ze šablony projektu webového rozhraní API. Jinak je snadné přidat kontrolery a přidružené koncové body rozhraní API do libovolné aplikace ASP.NET Core. Přístup MVC založený na zobrazení použijte, pokud migrujete existující aplikaci z ASP.NET MVC 5 nebo starší na ASP.NET Core MVC a chcete to provést s nejmenším úsilím. Jakmile provedete počáteční migraci, můžete vyhodnotit, jestli má smysl přijmout razor Pages pro nové funkce nebo dokonce jako velkoobchodní migraci. Další informace o portování aplikací .NET 4.x do .NET 8 najdete v tématu Porting Existing ASP.NET Apps to ASP.NET Core eBook.

Ať už se rozhodnete vytvořit webovou aplikaci pomocí razor Pages nebo zobrazení MVC, bude mít vaše aplikace podobný výkon a bude obsahovat podporu injektáže závislostí, filtrů, vazby modelu, ověřování atd.

Mapování požadavků na odpovědi

V srdci aplikace ASP.NET Core mapuje příchozí požadavky na odchozí odpovědi. Na nízké úrovni se toto mapování provádí s middlewarem a jednoduché ASP.NET aplikace a mikroslužby Core se můžou skládat výhradně z vlastního middlewaru. Při použití ASP.NET Core MVC můžete pracovat na poněkud vyšší úrovni, myslet na trasy, kontrolery a akce. Každý příchozí požadavek se porovná se směrovací tabulkou aplikace a pokud se najde odpovídající trasa, zavolá se přidružená metoda akce (patřící kontroleru) pro zpracování požadavku. Pokud se nenajde žádná odpovídající trasa, zavolá se obslužná rutina chyby (v tomto případě vrátí výsledek Nenalezeno).

ASP.NET aplikace Core MVC můžou používat konvenční trasy, trasy atributů nebo obojí. Konvenční trasy se definují v kódu a určují konvence směrování pomocí syntaxe jako v následujícím příkladu:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});

V tomto příkladu byla do směrovací tabulky přidána trasa s názvem "default". Definuje šablonu trasy se zástupnými symboly pro controller, actiona id. Zástupné controller symboly mají výchozí zadanou hodnotu (Homea Indexv uvedeném pořadí) a id zástupný symbol je nepovinný (na základě "?" použitého pro action něj). Zde definovaná konvence uvádí, že první část požadavku by měla odpovídat názvu kontroleru, druhé části akce a v případě potřeby třetí část bude představovat parametr ID. Konvenční trasy jsou obvykle definovány na jednom místě pro aplikaci, například v Program.cs , kde je nakonfigurovaný kanál middlewaru požadavku.

Trasy atributů se použijí přímo na kontrolery a akce, nikoli na globálně zadané. Tento přístup má výhodu, že je mnohem zjistitelnější, když se díváte na konkrétní metodu, ale znamená to, že informace o směrování se neuchovávají na jednom místě v aplikaci. Pomocí tras atributů můžete snadno zadat více tras pro danou akci a také kombinovat trasy mezi kontrolery a akcemi. Příklad:

[Route("Home")]
public class HomeController : Controller
{
    [Route("")] // Combines to define the route template "Home"
    [Route("Index")] // Combines to define route template "Home/Index"
    [Route("/")] // Does not combine, defines the route template ""
    public IActionResult Index() {}
}

Trasy je možné zadat pro [HttpGet] a podobné atributy, takže není nutné přidávat samostatné atributy [Route]. Trasy atributů můžou také používat tokeny ke snížení nutnosti opakovat názvy kontrolerů nebo akcí, jak je znázorněno níže:

[Route("[controller]")]
public class ProductsController : Controller
{
    [Route("")] // Matches 'Products'
    [Route("Index")] // Matches 'Products/Index'
    public IActionResult Index() {}
}

Razor Pages nepoužívá směrování atributů. V rámci direktivy @page razor Page můžete zadat další informace o šabloně trasy:

@page "{id:int}"

V předchozím příkladu by se daná stránka shodovala s trasou s celočíselnou id parametrem. Například stránka Products.cshtml umístěná v kořenovém /Pages adresáři by reagovala na požadavky podobné tomuto:

/Products/123

Jakmile se daný požadavek porovná s trasou, ale před zavoláním metody akce ASP.NET Core MVC provede v požadavku vazbu modelu a ověření modelu. Vazba modelu zodpovídá za převod příchozích dat HTTP na typy .NET zadané jako parametry metody akce, které se mají volat. Pokud například metoda akce očekává int id parametr, vazba modelu se pokusí poskytnout tento parametr z hodnoty zadané v rámci požadavku. K tomu vazba modelu hledá hodnoty v publikovaném formuláři, hodnoty v samotné trase a řetězcové hodnoty dotazu. Za předpokladu, že je nalezena id hodnota, bude převedena na celé číslo před předáním do metody akce.

Po vytvoření vazby modelu, ale před voláním metody akce dojde k ověření modelu. Ověření modelu používá volitelné atributy typu modelu a může pomoct zajistit, aby zadaný objekt modelu odpovídal určitým požadavkům na data. Určité hodnoty mohou být zadány podle potřeby nebo omezeny na určitou délku nebo číselný rozsah atd. Pokud jsou zadány ověřovací atributy, ale model nevyhovuje jejich požadavkům, vlastnost ModelState.IsValid bude false a sada chybných ověřovacích pravidel bude k dispozici k odeslání klientovi, který požadavek provádí.

Pokud používáte ověření modelu, nezapomeňte před provedením jakýchkoli příkazů pro změnu stavu vždy zkontrolovat, jestli je model platný, abyste měli jistotu, že vaše aplikace není poškozená neplatnými daty. Filtr můžete použít, abyste se vyhnuli nutnosti přidávat kód pro toto ověření v každé akci. ASP.NET filtry Core MVC nabízejí způsob zachycení skupin žádostí, aby bylo možné cíleně použít společné zásady a průřezové obavy. Filtry se dají použít na jednotlivé akce, celé kontrolery nebo globálně pro aplikaci.

U webových rozhraní API podporuje ASP.NET Core MVC vyjednávání obsahu, což umožňuje požadavkům určit, jak se mají odpovědi formátovat. Na základě hlaviček zadaných v požadavku budou akce vracející data formátovat odpověď ve formátu XML, JSON nebo jiném podporovaném formátu. Tato funkce umožňuje, aby stejné rozhraní API používalo více klientů s různými požadavky na formát dat.

Projekty webového rozhraní API by měly zvážit použití atributu [ApiController] , který lze použít na jednotlivé kontrolery, na základní třídu kontroleru nebo na celé sestavení. Tento atribut přidá automatickou kontrolu ověření modelu a jakákoli akce s neplatným modelem vrátí BadRequest s podrobnostmi o chybách ověření. Atribut také vyžaduje, aby všechny akce měly trasu atributu, a ne použití konvenční trasy, a vrací podrobnější informace o ProblémuDetails v reakci na chyby.

Udržování kontrolérů pod kontrolou

U aplikací založených na stránkách razor Pages dělají skvělou úlohu, která brání tomu, aby se kontrolery dostaly příliš velké. Každá jednotlivá stránka má své vlastní soubory a třídy vyhrazené pouze pro své obslužné rutiny. Před zavedením Razor Pages by řada aplikací orientovaných na zobrazení měla velké třídy kontroleru zodpovědné za mnoho různých akcí a zobrazení. Tyto třídy by přirozeně rostou tak, aby měly mnoho zodpovědností a závislostí, což ztěžuje jejich údržbu. Pokud zjistíte, že řadiče založené na zobrazení jsou příliš velké, zvažte refaktoring na použití razor Pages nebo zavedení vzoru, jako je mediátor.

Vzor návrhu mediátoru se používá ke snížení vazby mezi třídami a zároveň umožňuje komunikaci mezi nimi. V aplikacích ASP.NET Core MVC se tento model často používá k rozdělení kontrolerů na menší části pomocí obslužných rutin pro práci metod akcí. Oblíbený balíček NuGet MediatR se často používá k tomuto účelu. Kontrolery obvykle zahrnují mnoho různých metod akcí, z nichž každý může vyžadovat určité závislosti. Sada všech závislostí vyžadovaných jakoukoli akcí musí být předána do konstruktoru kontroleru. Při použití MediatR je jedinou závislostí, které kontroler obvykle bude mít, instancí mediátora. Každá akce pak použije mediátor instanci k odeslání zprávy, která je zpracována obslužnou rutinou. Obslužná rutina je specifická pro jednu akci, a proto potřebuje pouze závislosti vyžadované danou akcí. Tady je příklad kontroleru používajícího MediatR:

public class OrderController : Controller
{
    private readonly IMediator _mediator;

    public OrderController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet]
    public async Task<IActionResult> MyOrders()
    {
        var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
        return View(viewModel);
    }
    // other actions implemented similarly
}

MyOrders V akci je volání SendGetMyOrders zprávy zpracováno touto třídou:

public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
{
    private readonly IOrderRepository _orderRepository;
    public GetMyOrdersHandler(IOrderRepository orderRepository)
    {
        _orderRepository = orderRepository;
    }

  public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request, CancellationToken cancellationToken)
    {
        var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
        var orders = await _orderRepository.ListAsync(specification);
        return orders.Select(o => new OrderViewModel
            {
                OrderDate = o.OrderDate,
                OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
                  {
                    PictureUrl = oi.ItemOrdered.PictureUri,
                    ProductId = oi.ItemOrdered.CatalogItemId,
                    ProductName = oi.ItemOrdered.ProductName,
                    UnitPrice = oi.UnitPrice,
                    Units = oi.Units
                  }).ToList(),
                OrderNumber = o.Id,
                ShippingAddress = o.ShipToAddress,
                Total = o.Total()
        });
    }
}

Konečným výsledkem tohoto přístupu je, že kontrolery budou mnohem menší a zaměřují se především na směrování a vazbu modelu, zatímco jednotlivé obslužné rutiny zodpovídají za konkrétní úlohy potřebné daným koncovým bodem. Tento přístup lze dosáhnout i bez MediatR pomocí balíčku NuGet ApiEndpoints, který se pokusí přenést do kontrolerů rozhraní API stejné výhody, které Razor Pages přináší na kontrolery založené na zobrazení.

Odkazy – mapování požadavků na odpovědi

Práce se závislostmi

ASP.NET Core má integrovanou podporu a interně využívá techniku známou jako injektáž závislostí. Injektáž závislostí je technika, která umožňuje volné párování mezi různými částmi aplikace. Volné spojky je žádoucí, protože usnadňuje izolaci částí aplikace, což umožňuje testování nebo nahrazení. Zároveň je méně pravděpodobné, že změna v jedné části aplikace bude mít neočekávaný dopad někde jinde v aplikaci. Injektáž závislostí je založená na principu inverze závislostí a často je klíčem k dosažení otevřeného/uzavřeného principu. Při vyhodnocování toho, jak vaše aplikace funguje se závislostmi, dávejte pozor na zápach statickéhokódu a pamatujte na aforismus "nové je připevnění".

K statickému clingu dochází, když třídy provádí volání statických metod nebo přistupují ke statickým vlastnostem, které mají vedlejší účinky nebo závislosti na infrastruktuře. Pokud máte například metodu, která volá statickou metodu, která zase zapisuje do databáze, je vaše metoda úzce svázána s databází. Cokoli, co přeruší volání databáze, vaši metodu přeruší. Testování takových metod je notoricky obtížné, protože takové testy buď vyžadují komerční napodobované knihovny pro napodobování statických volání, nebo lze testovat pouze s testovací databází. Statická volání, která nemají žádnou závislost na infrastruktuře, zejména volání, která jsou zcela bezstavová, jsou v pořádku volání a nemají žádný vliv na párování nebo testovatelnost (mimo kód párování se statickým voláním).

Mnoho vývojářů rozumí rizikům statického clingu a globálního stavu, ale stále úzce propojte svůj kód s konkrétními implementacemi prostřednictvím přímé instance. "Nové je připevnění" je určeno jako připomenutí této spojky, a nikoli obecné odsuzování použití klíčového new slova. Stejně jako u volání statických metod nejsou nové instance typů, které nemají žádné externí závislosti, obvykle úzce párují kód s podrobnostmi implementace nebo ztěžují testování. Pokaždé, když se ale vytvoří instance třídy, je potřeba chvíli zvážit, jestli má smysl pevně zakódovat konkrétní instanci v daném umístění, nebo jestli by byl lepším návrhem požadovat tuto instanci jako závislost.

Deklarace závislostí

ASP.NET Core vychází z toho, že metody a třídy deklarují jejich závislosti a požadují je jako argumenty. ASP.NET aplikace jsou obvykle nastavené v Program.cs nebo ve Startup třídě.

Poznámka:

Konfigurace aplikací zcela v Program.cs je výchozím přístupem pro aplikace .NET 6 (a novější) a Visual Studio 2022. Šablony projektů byly aktualizovány, aby vám pomohly začít s tímto novým přístupem. ASP.NET základní projekty můžou v případě potřeby i nadále používat Startup třídu.

Konfigurace služeb v Program.cs

Pro velmi jednoduché aplikace můžete závislosti připojit přímo v Program.cs souboru pomocí WebApplicationBuilder. Po přidání všech potřebných služeb se tvůrce použije k vytvoření aplikace.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

Konfigurace služeb v Startup.cs

Samotná Startup.cs je nakonfigurovaná tak, aby podporovala injektáž závislostí v několika bodech. Pokud používáte Startup třídu, můžete jí dát konstruktor a může vyžadovat závislosti, například takto:

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
    }
}

Třída Startup je zajímavá v tom, že pro ni nejsou žádné explicitní požadavky na typ. Nedědí ze speciální Startup základní třídy ani neimplementuje žádné konkrétní rozhraní. Můžete mu dát konstruktor, nebo ne, a můžete zadat tolik parametrů konstruktoru, kolik chcete. Když se spustí webový hostitel, který jste pro svou aplikaci nakonfigurovali, zavolá Startup třídu (pokud jste jí řekli, že ji má použít), a použije injektáž závislostí k naplnění všech závislostí, Startup které třída vyžaduje. Pokud samozřejmě požadujete parametry, které nejsou nakonfigurované v kontejneru služeb používaném nástrojem ASP.NET Core, získáte výjimku, ale pokud se budete držet závislostí, o které kontejner ví, můžete požádat o cokoli, co chcete.

Injektáž závislostí je integrovaná do aplikací ASP.NET Core přímo od začátku při vytváření instance Po spuštění. Nezastaví se tam pro třídu Startup. V metodě můžete také požadovat závislosti Configure :

public void Configure(IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory)
{

}

Metoda ConfigureServices je výjimkou tohoto chování; musí mít pouze jeden parametr typu IServiceCollection. Ve skutečnosti nemusí podporovat injektáž závislostí, protože na jedné straně zodpovídá za přidávání objektů do kontejneru služeb a na druhé straně má přístup ke všem aktuálně nakonfigurovaným službám prostřednictvím parametru IServiceCollection . Proto můžete pracovat se závislostmi definovanými v kolekci služeb ASP.NET Core v každé části Startup třídy, a to buď vyžádáním potřebné služby jako parametru, nebo pomocí funkce IServiceCollection in ConfigureServices.

Poznámka:

Pokud potřebujete zajistit, aby byly pro vaši Startup třídu k dispozici určité služby, můžete je nakonfigurovat pomocí metody IWebHostBuilder a její ConfigureServices metody uvnitř CreateDefaultBuilder volání.

Třída Startup je model, jak byste měli strukturovat další části aplikace ASP.NET Core, od kontrolerů po middleware až po filtry do vlastních služeb. V každém případě byste měli postupovat podle principu explicitních závislostí, místo přímého vytváření závislostí a využití injektáže závislostí v celé aplikaci. Dávejte pozor na to, kde a jak přímo vytvoříte instance implementací, zejména služeb a objektů, které pracují s infrastrukturou nebo mají vedlejší účinky. Preferujte práci s abstrakcemi definovanými v jádru aplikace a předanými jako argumenty pro pevně zakódování odkazů na konkrétní typy implementace.

Strukturování aplikace

Monolitické aplikace obvykle mají jeden vstupní bod. V případě webové aplikace ASP.NET Core bude vstupním bodem webový projekt ASP.NET Core. To ale neznamená, že řešení by se mělo skládat jenom z jednoho projektu. Je užitečné rozdělit aplikaci do různých vrstev, aby bylo možné sledovat oddělení obav. Jakmile se rozdělí do vrstev, je užitečné přejít mimo složky do samostatných projektů, což může pomoct dosáhnout lepší zapouzdření. Nejlepším přístupem k dosažení těchto cílů pomocí aplikace ASP.NET Core je varianta čisté architektury, která je popsána v kapitole 5. Po tomto přístupu bude řešení aplikace obsahovat samostatné knihovny pro uživatelské rozhraní, infrastrukturu a ApplicationCore.

Kromě těchto projektů jsou zahrnuty i samostatné testovací projekty (testování je popsáno v kapitole 9).

Objektový model a rozhraní aplikace by se měly umístit do projektu ApplicationCore. Tento projekt bude mít co nejvíce závislostí (a žádný z konkrétních aspektů infrastruktury) a ostatní projekty v řešení na něj budou odkazovat. Obchodní entity, které je potřeba zachovat, jsou definovány v projektu ApplicationCore, stejně jako služby, které přímo nezávisí na infrastruktuře.

Podrobnosti implementace, například způsob provádění trvalosti nebo způsob odeslání oznámení uživateli, se uchovávají v projektu Infrastruktura. Tento projekt bude odkazovat na balíčky specifické pro implementaci, jako je Entity Framework Core, ale neměl by zveřejnit podrobnosti o těchto implementacích mimo projekt. Služby infrastruktury a úložiště by měly implementovat rozhraní definovaná v projektu ApplicationCore a jeho implementace trvalosti zodpovídají za načítání a ukládání entit definovaných v ApplicationCore.

Projekt uživatelského rozhraní ASP.NET Core zodpovídá za jakékoli obavy na úrovni uživatelského rozhraní, ale neměl by obsahovat podrobnosti o obchodní logice ani infrastruktuře. V ideálním případě by nemělo mít ani závislost na projektu Infrastruktura, což pomůže zajistit, aby nebyla náhodně zavedena žádná závislost mezi těmito dvěma projekty. Toho lze dosáhnout pomocí kontejneru DI třetí strany, jako je Autofac, který umožňuje definovat pravidla DI v třídách modulů v jednotlivých projektech.

Dalším přístupem k oddělení aplikace od podrobností implementace je mít mikroslužby volání aplikace, možná nasazené v jednotlivých kontejnerech Dockeru. To poskytuje ještě větší oddělení obav a oddělení než využití DI mezi dvěma projekty, ale má další složitost.

Organizace funkcí

Ve výchozím nastavení ASP.NET aplikace Core uspořádají strukturu složek tak, aby zahrnovaly kontrolery a zobrazení a často viewmodely. Kód na straně klienta, který podporuje tyto struktury na straně serveru, se obvykle ukládá samostatně ve složce wwwroot. Velké aplikace ale můžou narazit na problémy s touto organizací, protože práce na jakékoli dané funkci často vyžaduje přeskakování mezi těmito složkami. S rostoucím počtem souborů apod Průzkumník řešení ch Jedním z řešení tohoto problému je uspořádání kódu aplikace podle funkce místo podle typu souboru. Tento styl organizace se obvykle označuje jako složky funkcí nebo řezy funkcí (viz také: Svislé řezy).

ASP.NET Core MVC podporuje oblasti pro tento účel. Pomocí oblastí můžete v každé složce Oblasti vytvořit samostatné sady složek Kontrolery a Zobrazení (stejně jako všechny přidružené modely). Obrázek 7-1 ukazuje ukázkovou strukturu složek pomocí oblastí.

Organizace ukázkové oblasti

Obrázek 7–1 Organizace ukázkové oblasti

Při použití oblasti je nutné použít atributy k ozdobení kontrolerů názvem oblasti, do které patří:

[Area("Catalog")]
public class HomeController
{}

Ke svým trasám je také potřeba přidat podporu oblasti:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "areaRoute", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
    endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});

Kromě integrované podpory oblastí můžete místo atributů a vlastních tras použít také vlastní strukturu složek a konvence. To by vám umožnilo mít složky funkcí, které neobsahují samostatné složky pro zobrazení, kontrolery atd., udržovat hierarchii zploštělou a usnadnit zobrazení všech souvisejících souborů na jednom místě pro každou funkci. Pro rozhraní API je možné složky použít k nahrazení kontrolerů a každá složka může obsahovat všechny koncové body rozhraní API a jejich přidružené objekty DTO.

ASP.NET Core používá k řízení svého chování integrované typy konvencí. Tyto konvence můžete upravit nebo nahradit. Můžete například vytvořit konvenci, která automaticky získá název funkce pro daný kontroler na základě jeho oboru názvů (což obvykle koreluje se složkou, ve které se nachází kontroler):

public class FeatureConvention : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        controller.Properties.Add("feature",
        GetFeatureName(controller.ControllerType));
    }

    private string GetFeatureName(TypeInfo controllerType)
    {
        string[] tokens = controllerType.FullName.Split('.');
        if (!tokens.Any(t => t == "Features")) return "";
        string featureName = tokens
            .SkipWhile(t => !t.Equals("features", StringComparison.CurrentCultureIgnoreCase))
            .Skip(1)
            .Take(1)
            .FirstOrDefault();
        return featureName;
    }
}

Tuto konvenci pak zadáte jako možnost, když do aplikace ConfigureServices přidáte podporu MVC (nebo v Program.cs):

// ConfigureServices
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));

// Program.cs
builder.Services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));

ASP.NET Core MVC také používá konvenci k vyhledání zobrazení. Můžete ho přepsat vlastní konvencí, aby se zobrazení nacházela ve složkách funkcí (pomocí názvu funkce, který poskytuje FeatureConvention výše). Další informace o tomto přístupu a stažení funkční ukázky najdete v článku MSDN Magazine, feature slices for ASP.NET Core MVC.

Rozhraní API a Blazor aplikace

Pokud vaše aplikace obsahuje sadu webových rozhraní API, která musí být zabezpečená, měla by být tato rozhraní API ideálně nakonfigurovaná jako samostatný projekt od aplikace View nebo Razor Pages. Oddělení rozhraní API, zejména veřejných rozhraní API, od webové aplikace na straně serveru má řadu výhod. Tyto aplikace mají často jedinečné charakteristiky nasazení a zatížení. Jsou také velmi pravděpodobné, že přijmou různé mechanismy zabezpečení. Standardní aplikace založené na formulářích využívají ověřování založené na souborech cookie a rozhraní API s největší pravděpodobností s využitím ověřování založeného na tokenech.

Blazor Aplikace, ať už používají Blazor Server nebo BlazorWebAssembly, by měly být vytvořeny jako samostatné projekty. Aplikace mají různé charakteristiky modulu runtime a také modely zabezpečení. Pravděpodobně budou sdílet běžné typy s webovou aplikací (nebo projektem rozhraní API) na straně serveru a tyto typy by se měly definovat ve společném sdíleném projektu.

Přidání BlazorWebAssembly rozhraní pro správu eShopOnWeb vyžadovalo přidání několika nových projektů. Projekt BlazorWebAssembly sám, BlazorAdmin. V projektu je definována nová sada koncových bodů veřejného rozhraní API používaná BlazorAdmin a nakonfigurovaná pro použití ověřování na základě tokenů PublicApi . A některé sdílené typy používané oběma těmito projekty se uchovávají v novém BlazorShared projektu.

Můžete se zeptat, proč přidat samostatný BlazorShared projekt, pokud už existuje společný ApplicationCore projekt, který by se dal použít ke sdílení libovolných typů požadovaných oběma PublicApi i BlazorAdmin? Odpovědí je, že tento projekt zahrnuje veškerou obchodní logiku aplikace a je tak mnohem větší, než je nutné, a také mnohem pravděpodobnější, že bude nutné udržovat zabezpečení na serveru. Nezapomeňte, že všechny knihovny, na které BlazorAdmin odkazuje, se při načítání Blazor aplikace stáhnou do prohlížečů uživatelů.

V závislosti na tom, jestli používá model BFF (Backends-For-Frontends), nemusí rozhraní API spotřebovaná BlazorWebAssembly aplikací sdílet své typy 100 % s Blazor. Konkrétně může veřejné rozhraní API, které má využívat mnoho různých klientů, definovat vlastní typy požadavků a výsledků, nikoli je sdílet ve sdíleném projektu specifickém pro klienta. V ukázce eShopOnWeb se předpokládá, že PublicApi projekt je ve skutečnosti hostitelem veřejného rozhraní API, takže ne všechny jeho typy požadavků a odpovědí pocházejí z BlazorShared projektu.

Související aspekty

S růstem aplikací je stále důležitější zdůraznit aspekty křížového dělení, aby se eliminovala duplicita a zachovala se konzistence. Mezi příklady průřezových obav v aplikacích ASP.NET Core patří ověřování, pravidla ověření modelu, ukládání výstupu do mezipaměti a zpracování chyb, i když existuje mnoho dalších. ASP.NET filtry Core MVC umožňují spouštět kód před nebo po určitých krocích v kanálu zpracování požadavků. Filtr může například běžet před a po vazbě modelu, před a po akci nebo před a po výsledku akce. K řízení přístupu ke zbytku kanálu můžete použít také autorizační filtr. Obrázky 7–2 ukazují, jak požadavky procházejí filtry, pokud jsou nakonfigurované.

Požadavek se zpracovává prostřednictvím autorizačních filtrů, filtrů prostředků, vazby modelu, filtrů akcí, provedení akce a převodu výsledků akce, filtrů výjimek, filtrů výsledků a provádění výsledků. Na cestě je požadavek zpracován pouze filtry výsledků a filtry prostředků před tím, než se stane odpovědí odeslanou klientovi.

Obrázek 7–2 Provádění požadavků prostřednictvím filtrů a kanálu požadavků.

Filtry se obvykle implementují jako atributy, takže je můžete použít na kontrolery nebo akce (nebo dokonce globálně). Při přidání tímto způsobem filtry zadané na úrovni akce přepíší nebo vycházejí z filtrů zadaných na úrovni kontroleru, které samy přepíší globální filtry. Atribut lze například [Route] použít k sestavení tras mezi kontrolery a akcemi. Podobně lze autorizaci nakonfigurovat na úrovni kontroleru a pak přepsat jednotlivé akce, jak ukazuje následující ukázka:

[Authorize]
public class AccountController : Controller
{
    [AllowAnonymous] // overrides the Authorize attribute
    public async Task<IActionResult> Login() {}
    public async Task<IActionResult> ForgotPassword() {}
}

První metoda Login používá [AllowAnonymous] filtr (atribut) k přepsání filtru na úrovni kontroleru. Akce ForgotPassword (a jakákoli jiná akce ve třídě, která nemá atribut AllowAnonymous), bude vyžadovat ověřený požadavek.

Filtry lze použít k odstranění duplicit ve formě běžných zásad zpracování chyb pro rozhraní API. Typická zásada rozhraní API například vrací odpověď Nenalezeno na požadavky odkazující na klíče, které neexistují, a BadRequest odpověď v případě selhání ověření modelu. Následující příklad ukazuje tyto dvě zásady v akci:

[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
    if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
    {
        return NotFound(id);
    }
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    author.Id = id;
    await _authorRepository.UpdateAsync(author);
    return Ok();
}

Nepovolujte, aby se metody akcí staly nepotřebnými podmíněným kódem, jako je tento. Místo toho přetáhněte zásady do filtrů, které je možné použít podle potřeby. V tomto příkladu může být kontrola ověření modelu, která by měla nastat při odeslání příkazu do rozhraní API, nahrazena následujícím atributem:

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Do ValidateModelAttribute projektu můžete přidat jako závislost NuGet zahrnutím balíčku Ardalis.ValidateModel . Pro rozhraní API můžete pomocí atributu ApiController vynutit toto chování bez nutnosti samostatného ValidateModel filtru.

Podobně lze filtr použít ke kontrole, jestli záznam existuje, a vrátit 404 před provedením akce a eliminovat nutnost provádět tyto kontroly v akci. Jakmile vytáhnete běžné konvence a uspořádáte své řešení tak, aby oddělil kód infrastruktury a obchodní logiku od uživatelského rozhraní, měly by být metody akcí MVC velmi tenké:

[HttpPut("{id}")]
[ValidateAuthorExists]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
    await _authorRepository.UpdateAsync(author);
    return Ok();
}

Další informace o implementaci filtrů a stažení funkční ukázky najdete v článku MSDN Magazine, real-world ASP.NET Core MVC Filtry.

Pokud zjistíte, že máte řadu běžných odpovědí z rozhraní API na základě běžných scénářů, jako jsou chyby ověření (Chybný požadavek), nenalezena prostředek a chyby serveru, můžete zvážit použití abstrakce výsledku. Abstrakci výsledků by vrátily služby spotřebované koncovými body rozhraní API a akce kontroleru nebo koncový bod by k jejich překladu IActionResultspoužil filtr.

Odkazy – strukturování aplikací

Zabezpečení

Zabezpečení webových aplikací je velké téma s mnoha aspekty. Na nejzásadnější úrovni zabezpečení zahrnuje zajištění, abyste věděli, od koho daný požadavek pochází, a pak se zajistilo, že požadavek má přístup pouze k prostředkům, které by měl mít. Ověřování je proces porovnání přihlašovacích údajů zadaných s požadavkem na uživatele v důvěryhodném úložišti dat, abyste zjistili, jestli má být požadavek považován za přicházející ze známé entity. Autorizace je proces omezení přístupu k určitým prostředkům na základě identity uživatele. Třetím problémem zabezpečení je ochrana požadavků před odposloucháváním třetími stranami, pro které byste měli alespoň zajistit, aby vaše aplikace používala protokol SSL.

Identita

ASP.NET Core Identity je systém členství, který můžete použít k podpoře funkcí přihlášení pro vaši aplikaci. Má podporu pro místní uživatelské účty a také podporu externího zprostředkovatele přihlášení od poskytovatelů, jako jsou Účty Microsoft, Twitter, Facebook, Google a další. Kromě ASP.NET Core Identity může vaše aplikace používat ověřování systému Windows nebo zprostředkovatele identity třetí strany, jako je Server identit.

ASP.NET Základní identita je součástí nových šablon projektů, pokud je vybraná možnost Jednotlivé uživatelské účty. Tato šablona zahrnuje podporu registrace, přihlášení, externích přihlášení, zapomenutých hesel a dalších funkcí.

Vyberte jednotlivé uživatelské účty, které mají mít předkonfigurovanou identitu.

Obrázek 7–3 Vyberte jednotlivé uživatelské účty, aby byla identita předkonfigurovaná.

Podpora identit se konfiguruje v Program.cs nebo Startupa zahrnuje konfiguraci služeb i middleware.

Konfigurace identity v Program.cs

V Program.cs nakonfigurujete služby z WebHostBuilder instance a po vytvoření aplikace nakonfigurujete jeho middleware. Klíčovými body, které je třeba poznamenat, jsou volání AddDefaultIdentity požadovaných služeb a UseAuthenticationUseAuthorization volání, která přidávají požadovaný middleware.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
  app.UseExceptionHandler("/Error");
  // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
  app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

Konfigurace identity při spuštění aplikace

// Add framework services.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();
builder.Services.AddMvc();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

Je důležité, aby UseAuthenticationUseAuthorization se objevila před MapRazorPages. Při konfiguraci služeb identit si všimnete volání AddDefaultTokenProviders. To nemá nic společného s tokeny, které se dají použít k zabezpečení webové komunikace, ale odkazuje na poskytovatele, kteří vytvářejí výzvy, které se dají uživatelům poslat prostřednictvím SMS nebo e-mailu, aby si ověřili svou identitu.

Další informace o konfiguraci dvojúrovňového ověřování a povolení externích poskytovatelů přihlášení najdete v oficiální dokumentaci k ASP.NET Core.

Ověřování

Ověřování je proces určení, kdo přistupuje k systému. Pokud používáte ASP.NET Core Identity a metody konfigurace uvedené v předchozí části, automaticky nakonfiguruje některé výchozí hodnoty ověřování v aplikaci. Tyto výchozí hodnoty ale můžete nakonfigurovat ručně nebo je můžete přepsat pomocí AddIdentity. Pokud používáte identitu, nakonfiguruje ověřování na základě souborů cookie jako výchozí schéma.

Ve webovém ověřování je obvykle až pět akcí, které se můžou provádět v průběhu ověřování klienta systému. Jedná se o:

  • Ověřování. Pomocí informací poskytovaných klientem vytvořte identitu, kterou má použít v rámci aplikace.
  • Výzva. Tato akce se používá k vyžadování, aby se klient identifikoval sám.
  • Zakázat. Informujte klienta, že je zakázáno provádět akci.
  • Přihlaste se. Zachovat existujícího klienta nějakým způsobem.
  • Odhlašovací. Odeberte klienta z trvalosti.

Existuje řada běžných technik pro provádění ověřování ve webových aplikacích. Označují se jako schémata. Dané schéma definuje akce pro některé nebo všechny výše uvedené možnosti. Některá schémata podporují pouze podmnožinu akcí a mohou vyžadovat samostatné schéma k provedení těch, které nepodporuje. Schéma OpenId-Připojení (OIDC) například nepodporuje přihlášení nebo odhlášení, ale pro tuto trvalost se běžně konfiguruje tak, aby používalo ověřování cookie.

V aplikaci ASP.NET Core můžete nakonfigurovat DefaultAuthenticateScheme i volitelná konkrétní schémata pro každou z výše popsaných akcí. Příklad: DefaultChallengeScheme a DefaultForbidScheme. Volání AddIdentity konfiguruje řadu aspektů aplikace a přidává mnoho požadovaných služeb. Zahrnuje také toto volání konfigurace schématu ověřování:

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});

Tato schémata používají soubory cookie pro trvalost a přesměrování na přihlašovací stránky pro ověřování ve výchozím nastavení. Tato schémata jsou vhodná pro webové aplikace, které komunikují s uživateli prostřednictvím webových prohlížečů, ale nedoporučuje se pro rozhraní API. Místo toho rozhraní API obvykle používají jinou formu ověřování, například nosné tokeny JWT.

Webová rozhraní API využívají kód, například HttpClient v aplikacích .NET a ekvivalentní typy v jiných architekturách. Tito klienti očekávají použitelnou odpověď z volání rozhraní API nebo stavový kód označující, co (pokud existuje) došlo k problému. Tito klienti nepracují prostřednictvím prohlížeče a nevykreslují ani nekomuagují s žádným kódem HTML, který může rozhraní API vrátit. Proto není vhodné, aby koncové body rozhraní API přesměrovaly své klienty na přihlašovací stránky, pokud nejsou ověřeny. Vhodnější je další schéma.

Pokud chcete nakonfigurovat ověřování pro rozhraní API, můžete nastavit ověřování jako následující, které PublicApi projekt používá v referenční aplikaci eShopOnWeb:

builder.Services
    .AddAuthentication(config =>
    {
      config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(config =>
    {
        config.RequireHttpsMetadata = false;
        config.SaveToken = true;
        config.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

I když je možné nakonfigurovat více různých schémat ověřování v rámci jednoho projektu, je mnohem jednodušší nakonfigurovat jedno výchozí schéma. Z tohoto důvodu mimo jiné referenční aplikace eShopOnWeb odděluje své rozhraní API do svého vlastního projektu, PublicApiodděleně od hlavního Web projektu, který zahrnuje zobrazení aplikace a Razor Pages.

Ověřování v Blazor aplikacích

Blazor Serverové aplikace můžou využívat stejné funkce ověřování jako jakákoli jiná aplikace ASP.NET Core. BlazorWebAssembly aplikace nemohou používat integrované zprostředkovatele identity a ověřování, protože běží v prohlížeči. BlazorWebAssembly aplikace můžou ukládat stav ověřování uživatelů místně a mají přístup k deklaracím identity, aby určily, jaké akce by uživatelé měli provádět. Všechny kontroly ověřování a autorizace by se ale měly provádět na serveru bez ohledu na logiku implementovanou v BlazorWebAssembly aplikaci, protože uživatelé můžou aplikaci snadno obejít a interagovat s rozhraními API přímo.

Odkazy – ověřování

Autorizace

Nejjednodušší formou autorizace je omezení přístupu k anonymním uživatelům. Tuto funkci lze dosáhnout použitím atributu [Authorize] na určité kontrolery nebo akce. Pokud se role používají, můžete atribut dále rozšířit a omezit tak přístup uživatelům, kteří patří k určitým rolím, jak je znázorněno na obrázku:

[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{

}

V takovém případě budou mít uživatelé patřící buď k HRManager roli nebo Finance roli (nebo obojímu) přístup k Nástroji SalaryController. Pokud chcete vyžadovat, aby uživatel patřil k více rolím (ne jenom k jedné z několika), můžete atribut použít několikrát a pokaždé zadat požadovanou roli.

Určení určitých sad rolí jako řetězců v mnoha různých kontrolérech a akcích může vést k nežádoucímu opakování. Minimálně definujte konstanty pro tyto řetězcové literály a použijte konstanty všude, kde potřebujete zadat řetězec. Můžete také nakonfigurovat zásady autorizace, které zapouzdřují autorizační pravidla, a pak při použití atributu [Authorize] místo jednotlivých rolí zadat zásady:

[Authorize(Policy = "CanViewPrivateReport")]
public IActionResult ExecutiveSalaryReport()
{
    return View();
}

Pomocí zásad tímto způsobem můžete oddělit druhy akcí, které se na ně vztahují, od konkrétních rolí nebo pravidel. Pokud později vytvoříte novou roli, která potřebuje mít přístup k určitým prostředkům, stačí aktualizovat zásadu a neaktualizovat každý seznam rolí u každého [Authorize] atributu.

Žádosti

Deklarace identity jsou páry hodnot názvů, které představují vlastnosti ověřeného uživatele. Můžete například uložit číslo zaměstnance uživatele jako deklaraci identity. Deklarace identity je pak možné použít jako součást zásad autorizace. Můžete vytvořit zásadu s názvem EmployeeOnly, která vyžaduje existenci deklarace "EmployeeNumber"identity, jak je znázorněno v tomto příkladu:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
    });
}

Tuto zásadu pak můžete použít s atributem [Authorize] k ochraně jakéhokoli kontroleru nebo akce, jak je popsáno výše.

Zabezpečení webových rozhraní API

Většina webových rozhraní API by měla implementovat ověřovací systém založený na tokenech. Ověřování tokenů je bezstavové a navržené tak, aby bylo škálovatelné. V ověřovacím systému založeném na tokenech se klient musí nejprve ověřit u zprostředkovatele ověřování. V případě úspěchu se klientovi vystaví token, což je jednoduše kryptograficky smysluplný řetězec znaků. Nejběžnějším formátem tokenů je webový token JSON nebo JWT (často se vyslovuje jako "jot"). Když pak klient potřebuje vydat požadavek na rozhraní API, přidá tento token jako hlavičku požadavku. Server pak před dokončením požadavku ověří token nalezený v hlavičce požadavku. Obrázek 7–4 ukazuje tento proces.

Ověřování tokenů

Obrázek 7–4 Ověřování na základě tokenů pro webová rozhraní API

Můžete vytvořit vlastní ověřovací službu, integrovat se službou Azure AD a OAuth nebo implementovat službu pomocí opensourcového nástroje, jako je IdentityServer.

Tokeny JWT můžou vkládat deklarace identity o uživateli, které je možné číst na klientovi nebo serveru. K zobrazení obsahu tokenu JWT můžete použít nástroj, jako je jwt.io . Neukládejte citlivá data, jako jsou hesla nebo klíče v tokenech JTW, protože jejich obsah je snadno čitelný.

Při použití tokenů JWT s SPA nebo BlazorWebAssembly aplikacemi musíte token uložit někde na klientovi a pak ho přidat do každého volání rozhraní API. Tato aktivita se obvykle provádí jako hlavička, jak ukazuje následující kód:

// AuthService.cs in BlazorAdmin project of eShopOnWeb
private async Task SetAuthorizationHeader()
{
      var token = await GetToken();
      _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}

Po volání výše uvedené metody budou požadavky provedené s tímto _httpClient tokenem vloženy do hlaviček požadavku, což rozhraní API na straně serveru umožní ověření a autorizaci požadavku.

Vlastní zabezpečení

Upozornění

Obecně platí, že se vyhněte implementaci vlastních implementací zabezpečení.

Buďte obzvláště opatrní při "zavedení vlastní" implementace kryptografie, členství uživatelů nebo systému generování tokenů. Existuje mnoho komerčních a opensourcových alternativ, které budou téměř jistě mít lepší zabezpečení než vlastní implementace.

Reference – zabezpečení

Komunikace klienta

Kromě poskytování stránek a reagování na žádosti o data prostřednictvím webových rozhraní API můžou aplikace ASP.NET Core komunikovat přímo s připojenými klienty. Tato odchozí komunikace může používat řadu přenosových technologií, což je nejběžnější webSockets. ASP.NET Core SignalR je knihovna, která usnadňuje přidávání komunikačních funkcí mezi servery v reálném čase do vašich aplikací. SignalR podporuje celou řadu dopravních technologií, včetně WebSockets, a abstrahuje mnoho podrobností implementace od vývojáře.

Komunikace klienta v reálném čase bez ohledu na to, jestli používáte webSocket přímo nebo jiné techniky, jsou užitečná v různých scénářích aplikací. Mezi některé příklady patří:

  • Živé aplikace chatovacích místností

  • Monitorování aplikací

  • Aktualizace průběhu úloh

  • Notifications

  • Interaktivní aplikace formulářů

Při sestavování komunikace klienta do vašich aplikací jsou obvykle dvě komponenty:

  • Správce připojení na straně serveru (Centrum SignalR, WebSocketManager WebSocketHandler)

  • Klientská knihovna

Klienti nejsou omezeni na prohlížeče – mobilní aplikace, konzolové aplikace a další nativní aplikace můžou komunikovat také pomocí signalR/WebSockets. Následující jednoduchý program vypovídá veškerý obsah odeslaný do chatovací aplikace do konzoly jako součást ukázkové aplikace WebSocketManager:

public class Program
{
    private static Connection _connection;
    public static void Main(string[] args)
    {
        StartConnectionAsync();
        _connection.On("receiveMessage", (arguments) =>
        {
            Console.WriteLine($"{arguments[0]} said: {arguments[1]}");
        });
        Console.ReadLine();
        StopConnectionAsync();
    }

    public static async Task StartConnectionAsync()
    {
        _connection = new Connection();
        await _connection.StartConnectionAsync("ws://localhost:65110/chat");
    }

    public static async Task StopConnectionAsync()
    {
        await _connection.StopConnectionAsync();
    }
}

Zvažte způsoby, jak vaše aplikace komunikují přímo s klientskými aplikacemi, a zvažte, jestli by komunikace v reálném čase zlepšila uživatelské prostředí vaší aplikace.

Reference – komunikace klienta

Návrh řízený doménou – Měli byste ho použít?

DDD (Domain-Driven Design) je agilní přístup k vytváření softwaru, který zdůrazňuje zaměření na obchodní doménu. Klade důraz na komunikaci a interakci s odborníky na obchodní domény, kteří můžou souviset s vývojáři, jak funguje skutečný systém. Pokud například vytváříte systém, který zpracovává burzovní obchody, může být odborník na doménu zkušeným makléřem akcií. DDD je navržená tak, aby řešila velké, složité obchodní problémy a často není vhodná pro menší, jednodušší aplikace, protože investice do porozumění a modelování domény nestojí za to.

Při sestavování softwaru s využitím přístupu DDD by váš tým (včetně ne technických účastníků a přispěvatelů) měl pro problémový prostor vytvořit všudypřítomný jazyk . To znamená, že stejná terminologie by se měla použít pro modelovaný koncept reálného světa, ekvivalent softwaru a všechny struktury, které by mohly existovat pro zachování konceptu (například databázové tabulky). Proto by koncepty popsané v všudypřítomném jazyce měly tvořit základ pro váš doménový model.

Doménový model se skládá z objektů, které vzájemně komunikují, aby představovaly chování systému. Tyto objekty mohou spadat do následujících kategorií:

  • Entity, které představují objekty s vláknem identity. Entity jsou obvykle uloženy v trvalosti s klíčem, pomocí kterého je možné později načíst.

  • Agregace, které představují skupiny objektů, které by měly být trvalé jako jednotka.

  • Hodnotové objekty, které představují koncepty, které lze porovnat na základě součtu hodnot jejich vlastností. Například DateRange skládající se z počátečního a koncového data.

  • Doménové události, které představují věci v rámci systému, které jsou zajímavé pro jiné části systému.

Doménový model DDD by měl zapouzdřovat složité chování v rámci modelu. Entity by zejména neměly být pouze kolekcemi vlastností. Když doménový model nemá chování a pouze představuje stav systému, říká se, že jde o anemický model, který je nežádoucí v DDD.

Kromě těchto typů modelů DDD obvykle využívá různé vzory:

  • Úložiště pro abstrakci podrobností trvalosti.

  • Objekt pro zapouzdření komplexního vytváření objektů

  • Služby pro zapouzdření složitých chování a/nebo podrobností implementace infrastruktury.

  • Příkaz pro oddělení vydávajících příkazů a spuštění samotného příkazu.

  • Specifikace pro zapouzdření podrobností dotazu

DDD také doporučuje použití čisté architektury probírané dříve, což umožňuje volné párování, zapouzdření a kód, které lze snadno ověřit pomocí testů jednotek.

Kdy byste měli použít DDD

DDD je vhodná pro velké aplikace s významnou obchodní (nejen technickou) složitostí. Aplikace by měla vyžadovat znalosti odborníků na doménu. V samotném doménovém modelu by mělo existovat významné chování, které představuje obchodní pravidla a interakce nad rámec pouhého ukládání a načítání aktuálního stavu různých záznamů z úložišť dat.

Kdy byste neměli použít DDD

DDD zahrnuje investice do modelování, architektury a komunikace, které nemusí být zaručené pro menší aplikace nebo aplikace, které jsou v podstatě jen CRUD (vytvoření, čtení, aktualizace nebo odstranění). Pokud se rozhodnete k aplikaci přistupovat podle DDD, ale zjistíte, že vaše doména má anemický model bez chování, možná budete muset svůj přístup znovu promyslit. Buď vaše aplikace nemusí potřebovat DDD, nebo možná budete potřebovat pomoc s refaktoringem aplikace k zapouzdření obchodní logiky v doménovém modelu, a ne do databáze nebo uživatelského rozhraní.

Hybridním přístupem by bylo použít pouze DDD pro transakční nebo složitější oblasti aplikace, ale ne pro jednodušší části CRUD nebo jen pro čtení části aplikace. Pokud například dotazujete data na zobrazení sestavy nebo vizualizaci dat pro řídicí panel, nepotřebujete omezení agregace. Pro tyto požadavky je naprosto přijatelné mít samostatný a jednodušší model čtení.

Reference – návrh řízený doménou

Nasazení

Proces nasazení aplikace ASP.NET Core zahrnuje několik kroků bez ohledu na to, kde bude hostovaná. Prvním krokem je publikování aplikace, kterou je možné provést pomocí příkazu rozhraní příkazového dotnet publish řádku. Tento krok zkompiluje aplikaci a umístí všechny soubory potřebné ke spuštění aplikace do určené složky. Když nasadíte ze sady Visual Studio, provede se tento krok automaticky. Složka publikování obsahuje soubory .exe a .dll pro aplikaci a její závislosti. Samostatná aplikace bude obsahovat také verzi modulu runtime .NET. aplikace ASP.NET Core budou obsahovat také konfigurační soubory, statické klientské prostředky a zobrazení MVC.

ASP.NET aplikace Core jsou konzolové aplikace, které se musí spustit při spuštění serveru a restartování, pokud dojde k chybě aplikace (nebo serveru). Správce procesů lze použít k automatizaci tohoto procesu. Nejběžnějšími správci procesů pro ASP.NET Core jsou Nginx a Apache v Linuxu a IIS nebo službě Windows ve Windows.

Kromě správce procesů můžou aplikace ASP.NET Core používat reverzní proxy server. Reverzní proxy server přijímá požadavky HTTP z internetu a předává je do Kestrel po nějaké předběžné zpracování. Reverzní proxy servery poskytují aplikaci vrstvu zabezpečení. Kestrel také nepodporuje hostování více aplikací na stejném portu, takže techniky, jako jsou hlavičky hostitele, s ním nelze použít k povolení hostování více aplikací na stejném portu a IP adrese.

Kestrel na internet

Obrázek 7–5 ASP.NET hostované v Kestrel za reverzním proxy serverem

Dalším scénářem, ve kterém může být reverzní proxy server užitečný, je zabezpečení více aplikací pomocí SSL/HTTPS. V takovém případě musí mít nakonfigurovaný protokol SSL jenom reverzní proxy server. Komunikace mezi reverzním proxy serverem a Kestrelem může probíhat přes PROTOKOL HTTP, jak je znázorněno na obrázku 7-6.

ASP.NET hostované za reverzním proxy serverem zabezpečeným protokolem HTTPS

Obrázek 7–6 ASP.NET hostované za reverzním proxy serverem zabezpečeným protokolem HTTPS

Čím dál oblíbenější je hostování aplikace ASP.NET Core v kontejneru Dockeru, který se pak dá hostovat místně nebo nasadit do Azure pro cloudové hostování. Kontejner Dockeru může obsahovat kód aplikace spuštěný v Kestrelu a nasadí se za reverzním proxy serverem, jak je znázorněno výše.

Pokud hostujete aplikaci v Azure, můžete k poskytování několika služeb použít Microsoft Aplikace Azure Gateway jako vyhrazené virtuální zařízení. Kromě toho, že pro jednotlivé aplikace funguje jako reverzní proxy server, může služba Application Gateway nabídnout také následující funkce:

  • Vyrovnávání zatížení HTTP

  • Přesměrování zpracování SSL (pouze SSL na internet)

  • Koncové šifrování SSL

  • Směrování s více lokalitami (konsolidace až 20 lokalit v jedné službě Application Gateway)

  • Firewall webových aplikací

  • Podpora protokolu Websocket

  • Pokročilá diagnostika

Další informace o možnostech nasazení Azure najdete v kapitole 10.

Reference – nasazení