Megosztás a következőn keresztül:


ASP.NET Core MVC-alkalmazások fejlesztése

Jótanács

Ez a tartalom egy részlet az eBookból, Architect Modern Web Applications with ASP.NET Core és Azure-ból, amely elérhető a .NET Docs-on, vagy ingyenesen letölthető PDF formájában, ami offline olvasható.

Modern webalkalmazások létrehozása a ASP.NET Core és az Azure eBook borító miniatűrjével.

Nem fontos, hogy az első alkalommal jól csináljuk. Létfontosságú, hogy az utolsó alkalommal is helyes legyen." - Andrew Hunt és David Thomas

ASP.NET Core egy platformfüggetlen, nyílt forráskódú keretrendszer, amely modern felhőoptimalizált webalkalmazások létrehozásához használható. ASP.NET Core-alkalmazások egyszerűek és modulárisak, beépített támogatással a függőséginjektáláshoz, ami nagyobb tesztelhetőséget és karbantarthatóságot tesz lehetővé. Az MVC-vel kombinálva, amely a nézetalapú alkalmazások mellett modern webes API-k készítését is támogatja, a ASP.NET Core egy hatékony keretrendszer, amellyel vállalati webalkalmazásokat hozhat létre.

MVC és Razor Pages

ASP.NET Core MVC számos olyan funkciót kínál, amelyek hasznosak a webes API-k és alkalmazások létrehozásához. Az MVC kifejezés a "Model-View-Controller" kifejezést jelenti, amely egy olyan felhasználói felületi minta, amely több részre bontja a felhasználói kérelmek megválaszolásának feladatait. A minta követésén kívül a ASP.NET Core-alkalmazásokban Razor Pages-ként is implementálhat funkciókat.

A Razor Pages beépített ASP.NET Core MVC-be, és ugyanazokat a funkciókat használja az útválasztáshoz, a modellkötéshez, a szűrőkhöz, az engedélyezéshez stb. Ahelyett azonban, hogy külön mappákat és fájlokat használnak a vezérlők, modellek, nézetek stb. számára, és attribútumalapú útválasztást használnak, a Razor Pages egyetlen mappába ("/Pages") kerül, az útvonal a mappában lévő relatív helyük alapján történik, és vezérlőműveletek helyett kezelőkkel kezeli a kéréseket. Ennek eredményeképpen a Razor Pages használatakor a szükséges fájlok és osztályok általában egymás mellett vannak, és nem terjednek el a webes projektben.

További információ az MVC, a Razor Pages és a kapcsolódó minták alkalmazásáról az eShopOnWeb mintaalkalmazásban.

Amikor új ASP.NET Core-alkalmazást hoz létre, rendelkeznie kell egy tervvel a létrehozni kívánt alkalmazás típusára vonatkozóan. Új projekt létrehozásakor az IDE-ben vagy a dotnet new CLI-parancs használatával számos sablon közül választhat. A leggyakoribb projektsablonok az Empty, a Web API, a Web App és a Web App (Model-View-Controller). Bár ezt a döntést csak a projekt első létrehozásakor hozhatja meg, ez nem visszavonhatatlan döntés. A Webes API-projekt szabványos modell-View-Controller vezérlőket használ , csak alapértelmezés szerint nem rendelkezik nézetekkel. Hasonlóképpen, az alapértelmezett webalkalmazás-sablon Razor Pagest használ, így a Nézetek mappából is hiányzik. Később hozzáadhat egy Nézetek mappát ezekhez a projektekhez, hogy támogassa a nézetalapú működést. A Webes API és a Modell–View-Controller-projektek alapértelmezés szerint nem tartalmaznak Lapok mappát, de később hozzáadhat egyet a Razor Pages-alapú viselkedés támogatásához. Ezt a három sablont úgy tekintheti, hogy három különböző alapértelmezett felhasználói interakciót támogat: az adatokat (webes API), a lapalapút és a nézetalapút. Tetszés szerint azonban egyetlen projekten belül is kombinálhatja és egyeztetheti ezeket a sablonokat.

Miért a Razor Pages?

A Razor Pages a Visual Studio új webalkalmazásainak alapértelmezett megközelítése. A Razor Pages egyszerűbb módot kínál az oldalalapú alkalmazásfunkciók, például a nem SPA-űrlapok készítésére. A vezérlők és nézetek használatával gyakran előfordult, hogy az alkalmazások nagyon nagy vezérlőkkel rendelkeztek, amelyek számos különböző függőséggel működtek, és számos különböző nézetet adott vissza. Ez összetettebb volt, és gyakran olyan vezérlőket eredményezett, amelyek nem követték hatékonyan az egységes felelősség elvét vagy a nyitott/zárt alapelveket. A Razor Pages úgy oldja meg ezt a problémát, hogy egy webalkalmazásban egy adott logikai "oldal" kiszolgálóoldali logikáját beágyazza a Razor-markupba. A kiszolgálóoldali logikával nem rendelkező Razor-lapok csak Razor-fájlból állhatnak (például "Index.cshtml"). A legtöbb nem triviális Razor Pages azonban rendelkezik egy társított lapmodell-osztálysal, amely konvenció szerint ugyanaz, mint a Razor-fájl, amely ".cs" kiterjesztéssel rendelkezik (például "Index.cshtml.cs").

A Razor-oldal oldalmodellje egyesíti az MVC-vezérlő és a nézetmodell feladatait. A vezérlőműveleti módszerekkel történő kérelmek kezelése helyett a rendszer végrehajtja az oldalmodell-kezelőket, például az "OnGet()"-et, és alapértelmezés szerint rendereli a hozzájuk tartozó lapot. A Razor Pages leegyszerűsíti az egyes lapok ASP.NET Core-alkalmazásokban való létrehozásának folyamatát, miközben továbbra is biztosítja a ASP.NET Core MVC összes architekturális funkcióját. Jó alapértelmezett választást jelentenek az új lapalapú funkciókhoz.

Mikor érdemes használni az MVC-t?

Ha webes API-kat készít, az MVC-minta több értelmet ad, mint a Razor Pages használata. Ha a projekt csak webes API-végpontokat tesz elérhetővé, ideális esetben a Webes API-projektsablonból kell kiindulnia. Ellenkező esetben egyszerűen adhat hozzá vezérlőket és kapcsolódó API-végpontokat bármely ASP.NET Core-alkalmazáshoz. A nézetalapú MVC-módszert akkor használja, ha egy meglévő alkalmazást ASP.NET MVC 5-ös vagy korábbi verzióról ASP.NET Core MVC-re migrál, és ezt a legkisebb erőfeszítéssel szeretné elvégezni. Miután elvégezte a kezdeti migrálást, kiértékelheti, hogy érdemes-e a Razor Pagest új funkciókhoz vagy akár nagykereskedelmi migrálásként is alkalmazni. A .NET 4.x-alkalmazások .NET 8-ba történő portolásáról további információt a Meglévő ASP.NET-alkalmazások portolása a Core eBook ASP.NET című témakörben talál.

Függetlenül attól, hogy Razor Pages- vagy MVC-nézetekkel hozza létre a webalkalmazást, az alkalmazás hasonló teljesítménnyel fog rendelkezni, és támogatja a függőséginjektálást, a szűrőket, a modellkötést, az érvényesítést stb.

Kérelmek hozzáigazítása válaszokhoz

Középpontjában ASP.NET Core-alkalmazások a bejövő kéréseket a kimenő válaszokhoz rendelik. Alacsony szinten ez a leképezés köztes szoftverekkel történik, és az egyszerű ASP.NET Core-alkalmazások és mikroszolgáltatások kizárólag egyéni köztes szoftverből állhatnak. A ASP.NET Core MVC használatakor valamivel magasabb szinten dolgozhat, az útvonalak, a vezérlők és a műveletek tekintetében. A rendszer minden bejövő kérést összehasonlít az alkalmazás útválasztási táblájával, és ha talál egy megfelelő útvonalat, a rendszer meghívja a társított (vezérlőhöz tartozó) műveletmetódust a kérés kezeléséhez. Ha nem található egyező útvonal, a rendszer meghív egy hibakezelőt (ebben az esetben NotFound-eredményt ad vissza).

ASP.NET Alapszintű MVC-alkalmazások használhatnak hagyományos útvonalakat, attribútum útvonalakat vagy mindkettőt. A hagyományos útvonalak kódban vannak definiálva, és az útválasztási konvenciók szintaxissal vannak megadva, például az alábbi példában:

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

Ebben a példában egy "default" nevű útvonal lett hozzáadva az útválasztási táblához. Meghatároz egy útvonalsablont, amely helyőrzőkkel rendelkezik az controller, action és id. A controller és action helyőrzők alapértelmezett értékkel vannak megadva (Home és Index, illetve), és a id helyőrző opcionális (a "?" alkalmazásával). Az itt meghatározott konvenció kimondja, hogy a kérelem első részének meg kell felelnie az adatkezelő nevének, a művelet második részének, majd szükség esetén egy harmadik rész egy azonosítóparaméternek. A hagyományos útvonalak általában egy helyen vannak definiálva az alkalmazáshoz, például Program.cs , ahol a kérelem köztes szoftverfolyamat konfigurálva van.

Az attribútumútvonalakat a rendszer közvetlenül alkalmazza a vezérlőkre és a műveletekre a globálisan megadott helyett. Ez a megközelítés azzal az előnnyel jár, hogy sokkal könnyebben felderíthetővé teszi őket, ha egy adott módszert vizsgál, de ez azt jelenti, hogy az útválasztási információk nem egy helyen vannak tárolva az alkalmazásban. Az attribútumútvonalakkal egyszerűen megadhat több útvonalat egy adott művelethez, valamint egyesítheti az útvonalakat a vezérlők és a műveletek között. Például:

[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() {}
}

Az útvonalak a [HttpGet] és hasonló attribútumokon is megadhatóak, így nem szükséges külön [Route] attribútumokat hozzáadni. Az attribútumútvonalak jogkivonatokkal is csökkenthetik a vezérlő vagy a műveletnevek ismétlésének szükségességét az alábbiak szerint:

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

A Razor Pages nem használ attribútum-útválasztást. A Razor-lapokra vonatkozó további útvonalsablon-információkat a @page irányelv részeként adhatja meg.

@page "{id:int}"

Az előző példában a szóban forgó oldal egy egész szám id paraméterrel egyező útvonal lenne. Például a Products.cshtml oldal a gyökérben található, és az alábbihoz hasonló kérésekre válaszol:

/Products/123

Ha egy adott kérés megfelelt egy útvonalnak, de a műveletmetódus meghívása előtt ASP.NET Core MVC végrehajtja a modellkötést és a modellérvényesítést a kérésen. A modellkötés feladata, hogy a bejövő HTTP-adatokat a meghívni kívánt műveletmetódus paramétereiként megadott .NET-típusokká konvertálja. Ha például a műveletmetódus egy paramétert int id vár, a modellkötés megkísérli megadni ezt a paramétert a kérés részeként megadott értékből. Ehhez a modellkötés keres egy közzétett űrlapban szereplő értékeket, az útvonalban található értékeket, illetve a lekérdezési karaktersorozat értékeit. Feltételezve, hogy egy id érték megtalálható, az egész számmá lesz konvertálva, mielőtt átadná a műveletmetódusnak.

A modell kötése után, de a műveleti módszer meghívása előtt a modell érvényesítése megtörténik. A modellérvényesítés opcionális attribútumokat használ a modelltípuson, és segíthet biztosítani, hogy a megadott modellobjektum megfeleljen bizonyos adatkövetelményeknek. Bizonyos értékek megadása kötelező, vagy egy bizonyos hossz- vagy számtartományra korlátozva stb. Ha az érvényesítési attribútumok meg vannak adva, de a modell nem felel meg a követelményeknek, a ModelState.IsValid tulajdonság hamis lesz, és a sikertelen érvényesítési szabályok készlete elérhető lesz a kérést küldő ügyfélnek.

Ha modellérvényesítést használ, mindenképpen ellenőrizze, hogy a modell érvényes-e az állapotváltó parancsok végrehajtása előtt, hogy az alkalmazás ne sérüljön meg az érvénytelen adatokkal. Szűrővel elkerülheti, hogy az ellenőrzéshez minden művelethez kódot kelljen hozzáadni. ASP.NET Alapszintű MVC-szűrők lehetővé tehetik a kéréscsoportok elfogását, hogy a közös szabályzatok és a horizontális szempontok célzottan alkalmazhatók legyenek. A szűrők alkalmazhatók egyéni műveletekre, teljes vezérlőkre vagy globálisan egy alkalmazásra.

Webes API-k esetén ASP.NET Core MVC támogatja a tartalomegyeztetést, így a kérések megadják, hogyan kell formázni a válaszokat. A kérelemben megadott fejlécek alapján az adatokat visszaküldött műveletek XML, JSON vagy más támogatott formátumban formázják a választ. Ez a funkció lehetővé teszi, hogy ugyanazt az API-t több ügyfél is használja különböző adatformátum-követelményekkel.

A webes API-projekteknek érdemes megfontolni az [ApiController] attribútum használatát, amely alkalmazható az egyes vezérlőkre, egy alapvezérlőosztályra vagy a teljes szerelvényre. Ez az attribútum automatikus modellérvényesítési ellenőrzést ad hozzá, és az érvénytelen modellekkel kapcsolatos minden művelet egy BadRequest értéket ad vissza az érvényesítési hibák részleteivel. Az attribútumhoz az is szükséges, hogy minden műveletnek attribútumútvonala legyen a hagyományos útvonal használata helyett, és a hibákra válaszul részletesebb ProblemDetails-információkat ad vissza.

Vezérlők felügyeletének fenntartása

Lapalapú alkalmazások esetén a Razor Pages kiválóan alkalmas arra, hogy a vezérlők ne legyenek túl nagyok. Minden egyes oldal saját fájlokat és osztályokat kap, csak a kezelő(k) számára. A Razor Pages bevezetése előtt számos nézetközpontú alkalmazás nagy vezérlőosztályokkal rendelkezik, amelyek számos különböző műveletért és nézetért felelősek. Ezek az osztályok természetesen növekednének, így sok felelősséget és függőséget kapnának, ami nehezebbé teszi a fenntartásukat. Ha úgy találja, hogy a nézetalapú vezérlők túl nagyok, fontolja meg azok átalakítását a Razor Pages használatával, vagy vezessen be egy mintát, például egy közvetítőt.

A mediátor tervezési mintája az osztályok közötti összekapcsolás csökkentésére szolgál, miközben lehetővé teszi a kommunikációt közöttük. Az ASP.NET Core MVC-alkalmazásokban ezt a mintát gyakran alkalmazzák a vezérlők kisebb darabokra való feldarabolásához kezelők használatával a műveleti módszerek végrehajtásához. Ehhez gyakran használják a népszerű MediatR NuGet-csomagot . A vezérlők általában számos különböző műveleti módszert tartalmaznak, amelyek mindegyike bizonyos függőségeket igényelhet. A művelet által igényelt összes függőséget át kell adni a vezérlő konstruktorának. A MediatR használatakor a vezérlők általában csak a mediátor egy példányát használják. Ezután minden művelet a mediátorpéldány használatával küld egy üzenetet, amelyet egy kezelő dolgoz fel. A kezelő egyetlen műveletre vonatkozik, ezért csak az adott művelet által igényelt függőségekre van szüksége. Itt látható egy példa a MediatR-t használó vezérlőre:

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
}

Az MyOrders művelet során ennek az osztálynak kell kezelnie az Send üzenet GetMyOrders hívását.

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()
        });
    }
}

Ennek a megközelítésnek a végeredménye, hogy a vezérlők sokkal kisebbek lesznek, és elsősorban az útválasztásra és a modellkötésre összpontosítanak, míg az egyes kezelők felelősek az adott végpont által igényelt konkrét feladatokért. Ez a megközelítés MediatR nélkül is elérhető az ApiEndpoints NuGet-csomag használatával, amely az API-vezérlők számára ugyanazt az előnyt próbálja elérni, mint a Razor Pages a nézetalapú vezérlők számára.

Hivatkozások – Kérelmek leképezése válaszokhoz

Függőségek kezelése

ASP.NET Core beépített támogatást nyújt a függőséginjektálásnak nevezett technikához, és belsőleg is használható. A függőséginjektálás olyan technika, amely lehetővé teszi az alkalmazás különböző részei közötti laza összekapcsolást. A lazább összekapcsolás azért kívánatos, mert megkönnyíti az alkalmazás részeinek elkülönítését, lehetővé téve a tesztelést vagy a cserét. Emellett kevésbé valószínű, hogy az alkalmazás egy részének módosítása váratlan hatással lesz az alkalmazás más részeire. A függőséginjektálás a függőség inverziós elvén alapul, és gyakran kulcsfontosságú a nyitott/zárt elv eléréséhez. Amikor kiértékeli, hogy az alkalmazás hogyan működik a függőségeivel, óvakodjon a statikus klónozási kód szagától, és ne feledje az aforizmát, hogy "az új a ragasztó".

A statikus ragaszkodás akkor fordul elő, ha az osztályok statikus metódusokra irányuló hívásokat kezdeményeznek, vagy statikus tulajdonságokhoz férnek hozzá, amelyek mellékhatásai vagy függőségei vannak az infrastruktúrának. Ha például olyan metódussal rendelkezik, amely statikus metódust hív meg, amely viszont egy adatbázisba ír, a metódus szorosan kapcsolódik az adatbázishoz. Minden, ami megszakítja az adatbázis-hívást, megszakítja a metódust. Az ilyen módszerek tesztelése hírhedten nehéz, mivel az ilyen tesztek vagy kereskedelmi modellkódtárakat igényelnek a statikus hívások szimulálásához, vagy csak tesztadatbázissal tesztelhetők. A statikus hívások, amelyek nem függnek az infrastruktúrától, különösen azok a hívások, amelyek teljesen állapot nélküliek, jól hívhatók, és nincs hatással a csatolásra vagy a tesztelhetőségre (a statikus híváshoz tartozó kapcsolókódon túl).

Sok fejlesztő tisztában van a statikus ragaszkodás és a globális állapot kockázataival, de a közvetlen példányosítás révén még mindig szorosan összekapcsolja a kódot adott implementációkkal. "New is glue" célja, hogy emlékeztetőként szolgáljon e kapcsolásra, és ne a new kulcsszó használatának általános elítélése legyen. A statikus metódushívásokhoz hasonlóan az olyan új típusú példányok, amelyek nem rendelkeznek külső függőségekkel, általában nem párosítják szorosan a kódot a megvalósítás részleteihez, vagy megnehezítik a tesztelést. De minden alkalommal, amikor egy osztályt példányosít, szánjon egy rövid időt, hogy átgondolja, érdemes-e az adott példányt az adott helyen kódolni, vagy jobb megoldás lenne, ha függőségként kérné ezt a példányt.

Függőségek deklarálása

ASP.NET Core azon alapul, hogy a metódusok és osztályok deklarálják a függőségeiket, és argumentumként kérik őket. ASP.NET alkalmazások általában Program.cs vagy osztályban Startup vannak beállítva.

Megjegyzés:

Az alkalmazások teljes Program.cs konfigurálása a .NET 6 (és újabb) és a Visual Studio 2022-alkalmazások alapértelmezett módszere. A projektsablonok frissültek, hogy megkönnyítsék az új megközelítés használatának megkezdését. ASP.NET Alapprojektek igény szerint továbbra is használhatnak osztályt Startup .

Szolgáltatások konfigurálása a(z) Program.cs

Nagyon egyszerű alkalmazások esetén közvetlenül a Program.cs fájlban is összekapcsolhatja a függőségeket egy WebApplicationBuilder. Az összes szükséges szolgáltatás hozzáadása után a szerkesztő használatával hozza létre az alkalmazást.

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Szolgáltatások konfigurálása a Startup.cs

A Startup.cs önmagában úgy van konfigurálva, hogy több ponton is támogassa a függőséginjektálást. Ha osztályt Startup használ, konstruktort adhat neki, és függőségeket kérhet rajta keresztül, például:

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);
    }
}

Az Startup osztály érdekes abban, hogy nincs explicit típuskövetelmény. Nem örököl egy speciális Startup alaposztályt, és nem implementál semmilyen felületet. Konstruktort adhat neki, vagy nem, és tetszőleges számú paramétert adhat meg a konstruktoron. Amikor elindul az alkalmazáshoz konfigurált webes gazdagép, meghívja az Startup osztályt (ha azt mondta, hogy használjon egyet), és függőséginjektálással tölti fel az Startup osztály által igényelt függőségeket. Természetesen, ha olyan paramétereket kér, amelyek nincsenek konfigurálva a ASP.NET Core által használt szolgáltatástárolóban, kivételt kap, de amíg ragaszkodik a tároló által ismert függőségekhez, bármit kérhet.

A függőséginjektálás az indítási példány létrehozásakor már az elejétől be van építve a ASP.NET Core-alkalmazásokba. Ez még nem ér véget a Startup osztály számára. A metódusban Configure függőségeket is kérhet:

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

}

A ConfigureServices metódus kivételt képez ebből a viselkedésből; csak egy típusú IServiceCollectionparamétert kell megadnia. Nem igazán kell támogatnia a függőséginjektálást, mivel egyrészt az objektumokat adja hozzá a szolgáltatástárolóhoz, másrészt a paraméteren keresztül fér hozzá az IServiceCollection összes jelenleg konfigurált szolgáltatáshoz. Így az osztály minden részén Startup használhatja a ASP.NET Core-szolgáltatások gyűjteményében definiált függőségeket, akár a szükséges szolgáltatás paraméterként való kérésével, akár az IServiceCollection in ConfigureServiceshasználatával.

Megjegyzés:

Ha gondoskodnia kell arról, hogy bizonyos szolgáltatások elérhetők legyenek az Startup osztály számára, konfigurálhatja őket egy IWebHostBuilder és annak metódusával ConfigureServices a CreateDefaultBuilder híváson belül.

Az indítási osztály a ASP.NET Core-alkalmazás egyéb részeinek strukturálására szolgáló modell, a Vezérlőktől a Köztes szoftveren át a Szűrőkig a saját szolgáltatásaiig. Minden esetben kövesse az explicit függőségek elvét, és ne közvetlenül hozza létre őket, hanem kérje le a függőségeket, és használja ki a függőséginjektálást az alkalmazás teljes területén. Ügyeljen arra, hogy hol és hogyan hozza létre közvetlenül az implementációkat, különösen az infrastruktúrával működő vagy mellékhatásokkal járó szolgáltatásokat és objektumokat. Előnyben részesítse az alkalmazásmagban definiált absztrakciókkal való munkát, és adja át őket argumentumként ahelyett, hogy konkrét implementáció típusokra mutató hivatkozásokat keményen kódolna.

Az alkalmazás strukturálása

A monolitikus alkalmazások általában egyetlen belépési ponttal rendelkeznek. Egy ASP.NET Core-webalkalmazás esetén a belépési pont az ASP.NET Core-webprojekt lesz. Ez azonban nem jelenti azt, hogy a megoldásnak csak egyetlen projektből kell állnia. Az aggodalmak elkülönítése érdekében érdemes az alkalmazást különböző rétegekre bontani. Miután rétegekre bontottuk, hasznos, ha túllépünk a mappákon a projektek elkülönítése érdekében, ami segíthet a jobb beágyazás elérésében. A célok ASP.NET Core-alkalmazással való elérésének legjobb módszere a Tiszta architektúra 5. fejezetben tárgyalt változata. Ezt a megközelítést követve az alkalmazás megoldása külön kódtárakat fog tartalmazni a felhasználói felülethez, az infrastruktúrához és az ApplicationCore-hoz.

Ezen projektek mellett külön tesztprojektek is szerepelnek (a tesztelést a 9. fejezet tárgyalja).

Az alkalmazás objektummodelljét és felületeit az ApplicationCore projektben kell elhelyezni. Ennek a projektnek a lehető legkevesebb függősége lesz (és nincsenek konkrét infrastruktúra-problémák), és a megoldás többi projektje hivatkozni fog rá. Azokat az üzleti entitásokat, amelyeket meg kell őrizni, az ApplicationCore-projektben definiálják, valamint azokat a szolgáltatásokat, amelyek nem függnek közvetlenül az infrastruktúrától.

A megvalósítás részletei, például a megőrzés végrehajtása vagy az értesítések felhasználónak való elküldése az infrastruktúra-projektben maradnak. Ez a projekt olyan implementációspecifikus csomagokra fog hivatkozni, mint az Entity Framework Core, de nem fedheti fel a projekten kívüli implementációk részleteit. Az infrastruktúra-szolgáltatásoknak és az adattáraknak interfészeket kell implementálniuk, amelyek az ApplicationCore projektben vannak meghatározva, és a perzisztens megvalósítások felelnek az ApplicationCore-ban definiált entitások lekéréséért és tárolásáért.

A ASP.NET Core felhasználói felület projekt felelős a felhasználói felülettel kapcsolatos problémákért, de nem tartalmazhat üzleti logikát vagy infrastruktúrát. Valójában ideális esetben nem is kellene függőséget létrehoznia az infrastruktúra-projekthez, ami segít biztosítani, hogy a két projekt közötti függőség véletlenül ne legyen bevezetve. Ez egy harmadik féltől származó DI-tárolóval, például az Autofac-tal érhető el, amely lehetővé teszi a DI-szabályok meghatározását az egyes projektek modulosztályaiban.

Az alkalmazás implementáció részleteitől való függetlenítésének másik módszere, hogy az alkalmazás meghívja a mikroszolgáltatásokat, amelyek esetleg egyes Docker-tárolókban vannak üzembe helyezve. Ez nagyobb mértékű elkülönítést és leválasztást biztosít, mint amikor DI-t alkalmazunk két projekt között, de egyben bonyolultabbá is teszi.

Funkciók szervezése

Alapértelmezés szerint ASP.NET Core-alkalmazások úgy rendszerezik a mappastruktúrájukat, hogy vezérlőket és nézeteket, valamint gyakran Nézetmodelleket is tartalmazzanak. A kiszolgálóoldali struktúrákat támogató ügyféloldali kódot általában külön tárolják a wwwroot mappában. A nagy méretű alkalmazások azonban problémákba ütközhetnek ezzel a szervezettel, mivel egy adott funkció használata gyakran szükségessé teszi a mappák közötti ugrást. Ez egyre nehezebbé válik az egyes mappákban lévő fájlok és almappák számának növekedésével, ami nagy mennyiségű görgetést eredményez a Megoldáskezelőben. A probléma egyik megoldása az alkalmazáskód szolgáltatás szerinti rendszerezése fájltípus helyett. Ezt a szervezeti stílust jellemzően funkciómappáknak vagy szolgáltatásszeleteknek nevezzük (lásd még: Függőleges szeletek).

ASP.NET Core MVC erre a célra támogatja a területeket. A területek használatával külön vezérlő- és nézetmappákat (valamint a hozzájuk tartozó modelleket) hozhat létre az egyes területmappákban. A 7–1. ábra egy példamappastruktúrát mutat be a Területek használatával.

Mintaterület szervezete

7-1. ábra. Mintaterület szervezete

Területek használata esetén attribútumokkal kell díszítenie a vezérlőket annak a területnek a nevével, amelyhez tartoznak:

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

Emellett területtámogatást is hozzá kell adnia az útvonalakhoz:

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?}");
});

A Területek beépített támogatása mellett használhatja a saját mappastruktúráját is, valamint az attribútumok és az egyéni útvonalak helyett konvenciókat. Ez lehetővé tenné, hogy olyan funkciómappák legyenek, amelyek nem tartalmaztak külön mappákat a nézetekhez, vezérlőkhöz stb., így a hierarchia laposabbá válik, és egyszerűbbé válik az összes kapcsolódó fájl egyetlen helyen való megjelenítése az egyes funkciókhoz. AZ API-k esetében a mappák a vezérlők cseréjére használhatók, és minden mappa tartalmazhat minden API-végpontot és a hozzájuk tartozó DTO-kat.

ASP.NET Core beépített konvenciótípusokkal szabályozza a viselkedését. Ezeket az egyezményeket módosíthatja vagy lecserélheti. Létrehozhat például egy konvenciót, amely automatikusan lekéri egy adott vezérlő szolgáltatásnevét a névtere alapján (amely általában azzal a mappával van összefüggésben, amelyben a vezérlő található):

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;
    }
}

Ezt az egyezményt akkor adja meg lehetőségként, ha mVC-támogatást ad hozzá az alkalmazáshoz ConfigureServices (vagy 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 egy konvencióval is megkeresi a nézeteket. Felülírhatja azt egy egyéni konvenció alkalmazásával, így a nézetek a feature mappákban helyezkednek el (a fent említett FeatureConvention által megadott funkciónév használatával). Erről a megközelítésről többet is megtudhat, és letölthet egy működő mintát az MSDN Magazin cikkéből, a Feature Slices for ASP.NET Core MVC-hez.

API-k és Blazor alkalmazások

Ha az alkalmazás webes API-kat tartalmaz, amelyeket védeni kell, ezeket az API-kat ideális esetben külön projektként kell konfigurálni a Nézet vagy a Razor Pages alkalmazástól. Az API-k, különösen a nyilvános API-k kiszolgálóoldali webalkalmazástól való elkülönítése számos előnnyel jár. Ezek az alkalmazások gyakran egyedi üzembe helyezési és terhelési jellemzőkkel rendelkeznek. Emellett nagy valószínűséggel különböző biztonsági mechanizmusokat vezetnek be, mivel a szabványos, űrlapalapú alkalmazások valószínűleg cookie-alapú hitelesítést, míg az API-k jellemzően jogkivonatalapú hitelesítést használnak.

Emellett Blazor alkalmazásokat, akár Blazor kiszolgálót, akár BlazorWebAssembly kiszolgálót használnak, külön projektekként kell létrehozni. Az alkalmazások különböző futtatókörnyezeti jellemzőkkel és biztonsági modellekkel rendelkeznek. Valószínű, hogy közös típusokat osztanak meg a kiszolgálóoldali webalkalmazással (vagy API-projekttel), és ezeket a típusokat egy közös közös projektben kell definiálni.

Az eShopOnWeb felügyeleti felületének BlazorWebAssembly hozzáadása több új projekt hozzáadását igényelte. Maga a BlazorWebAssembly projekt, BlazorAdmin. A BlazorAdmin projektben van definiálva a token alapú hitelesítést használó és konfigurált PublicApi által használt nyilvános API-végpontok új készlete. A két projekt által használt egyes megosztott típusok pedig egy új BlazorShared projektben maradnak.

Felmerülhet a kérdés, hogy miért adunk hozzá külön BlazorShared projektet, ha már létezik olyan közös ApplicationCore projekt, amely használható a mindkettő PublicApi és BlazorAdmin által igényelt típusok megosztására. A válasz az, hogy ez a projekt magában foglalja az alkalmazás üzleti logikáját, és így sokkal nagyobb, mint szükséges, és sokkal nagyobb valószínűséggel kell biztonságosan tartani a kiszolgálón. Ne feledje, hogy bármely könyvtár, amelyre BlazorAdmin hivatkozik, le fog töltődni a felhasználók böngészőibe, amikor betöltik a Blazor alkalmazást.

Attól függően, hogy valaki a HáttérrendszerFor-Frontends (BFF) mintát használja-e, előfordulhat, hogy az BlazorWebAssembly alkalmazás által használt API-k nem osztják meg a 100-% típusukat Blazor. A nyilvános API-k, amelyeket számos különböző ügyfélnek szántak, saját kéréseket és eredménytípusokat határozhatnak meg, ahelyett, hogy megosztanák őket egy ügyfélspecifikus megosztott projektben. Az eShopOnWeb-mintában feltételezik, hogy a PublicApi projekt valójában egy nyilvános API-t üzemeltet, így nem minden kérés- és választípus származik a BlazorShared projektből.

Többirányú aggodalmak

Az alkalmazások növekedésének köszönhetően egyre fontosabbá válik a keresztirányú szempontok figyelembe vétele a duplikációk kiküszöbölése és a konzisztencia fenntartása érdekében. Néhány példa a ASP.NET Core-alkalmazásokra vonatkozó horizontális problémákra: hitelesítés, modellérvényesítési szabályok, kimeneti gyorsítótárazás és hibakezelés, bár sok más is létezik. ASP.NET Alapvető MVC-szűrők lehetővé teszik a kód futtatását a kérelemfeldolgozási folyamat bizonyos lépései előtt vagy után. Egy szűrő például futtatható a modellkötés előtt és után, egy művelet előtt és után, illetve egy művelet eredménye előtt és után. A folyamat többi részéhez való hozzáférést engedélyezési szűrővel is szabályozhatja. A 7–2. ábra bemutatja, hogy a kérelmek végrehajtása hogyan halad át a szűrőkön, ha konfigurálva van.

A kérelem feldolgozása engedélyezési szűrők, erőforrásszűrők, modellkötés, műveleti szűrők, műveletvégrehajtás és műveleteredmény-átalakítás, kivételszűrők, eredményszűrők és eredményvégrehajtás révén történik. A kimenő úton a kérést csak az eredményszűrők és az erőforrásszűrők dolgozzák fel, mielőtt az ügyfélnek küldött válaszsá válnak.

7-2. ábra. Kérés végrehajtása szűrőkön és a kérések folyamatán keresztül.

A szűrők általában attribútumként vannak implementálva, így alkalmazhatja őket vezérlőkre vagy műveletekre (vagy akár globálisan). Ilyen módon hozzáadva a műveleti szinten megadott szűrők felülbírálják vagy a vezérlő szintjén megadott szűrőkre épülnek, amelyek maguk felülbírálják a globális szűrőket. Az attribútum segítségével például [Route] útvonalak hozhatók létre a vezérlők és a műveletek között. Hasonlóképpen az engedélyezés konfigurálható a vezérlő szintjén, majd felül lehet bírálni az egyes műveleteket, ahogy az alábbi példa is mutatja:

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

Az első módszer, a Bejelentkezés a [AllowAnonymous] szűrő (attribútum) használatával felülbírálja az Engedélyezés szűrőkészletet a vezérlő szintjén. A ForgotPassword művelethez (és az osztály azon többi műveletéhez, amely nem rendelkezik AllowAnonymous attribútummal) hitelesített kérést igényel.

A szűrőkkel kiküszöbölhetők a duplikációk az API-k gyakori hibakezelési szabályzatai formájában. Egy tipikus API-szabályzat például egy NotFound választ ad vissza azokra a kérésekre, amelyek nem létező kulcsokra hivatkoznak, és egy BadRequest választ, ha a modell érvényesítése sikertelen. Az alábbi példa az alábbi két szabályzatot mutatja be működés közben:

[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();
}

Ne engedélyezze, hogy a műveleti metódusok ilyen feltételes kóddal legyenek zsúfoltak. Ehelyett vonja be a szabályzatokat olyan szűrőkre, amelyek szükség szerint alkalmazhatók. Ebben a példában a modellérvényesítési ellenőrzés, amelynek akkor kell történnie, amikor parancsot küldenek az API-nak, a következő attribútum váltható fel:

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

Az ValidateModelAttribute csomag bevonásával hozzáadhatja a -t a projekthez mint NuGet-függőséget. API-k esetén az attribútummal kényszerítheti ezt a ApiController viselkedést anélkül, hogy külön ValidateModel szűrőre van szükség.

Hasonlóképpen egy szűrővel ellenőrizheti, hogy létezik-e rekord, és a művelet végrehajtása előtt visszaadja a 404-et, így nincs szükség ezekre az ellenőrzésekre a műveletben. Miután kivette a gyakori konvenciókból, és rendszerezte a megoldást az infrastruktúrakód és az üzleti logika felhasználói felülettől való elkülönítésére, az MVC műveleti módszereinek rendkívül vékonynak kell lenniük:

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

A szűrők implementálásáról és egy működő minta letöltéséről az MSDN MagazinReal-World ASP.NET Alapvető MVC-szűrők című cikkéből olvashat bővebben.

Ha azt tapasztalja, hogy az API-k számos gyakori választ adnak olyan gyakori forgatókönyvek alapján, mint az érvényesítési hibák (hibás kérelem), az erőforrás nem található, és kiszolgálóhibák, érdemes lehet eredmény absztrakciót használni. Az eredmény absztrakciót az API-végpontok által használt szolgáltatások adják vissza, és a vezérlőművelet vagy végpont szűrőt használna ezek IActionResults alakítására.

Hivatkozások – Alkalmazások strukturálása

Biztonság

A webalkalmazások biztonságossá tétele nagy témakör, számos szempontot figyelembe véve. A legalapvetőbb szinten a biztonság magában foglalja annak biztosítását, hogy tudja, kitől származik egy adott kérés, majd annak biztosításával, hogy a kérés csak a szükséges erőforrásokhoz férhessen hozzá. A hitelesítés a kéréshez megadott hitelesítő adatok és a megbízható adattárakban lévők összehasonlítása annak érdekében, hogy a kérést egy ismert entitásból érkezőnek kell-e tekinteni. Az engedélyezés a felhasználói identitáson alapuló egyes erőforrásokhoz való hozzáférés korlátozásának folyamata. A harmadik biztonsági szempont a kérések harmadik felek általi lehallgatásától való védelme, amelynek érdekében legalább gondoskodnia kell arról, hogy az alkalmazás SSL-t használjon.

Személyazonosság

ASP.NET Core Identity egy tagsági rendszer, a szolgáltatásokat az alkalmazás bejelentkezési funkcióinak támogatásához használhatja. Támogatja a helyi felhasználói fiókokat, valamint külső bejelentkezési szolgáltatói támogatást olyan szolgáltatóktól, mint a Microsoft-fiók, a Twitter, a Facebook, a Google stb. Az ASP.NET Core Identity mellett az alkalmazás windowsos hitelesítést is használhat, vagy egy külső identitásszolgáltatót, például az Identity Servert.

ASP.NET Core Identity akkor szerepel az új projektsablonokban, ha az Egyéni felhasználói fiókok lehetőség van kiválasztva. Ez a sablon támogatja a regisztrációt, a bejelentkezést, a külső bejelentkezéseket, az elfelejtett jelszavakat és a további funkciókat.

Egyéni felhasználói fiókok kiválasztása az identitás előre konfigurálásához

7-3. ábra. Válassza ki az egyéni felhasználói fiókokat az identitás előre konfigurálásához.

Az identitástámogatás Program.cs vagy Startup, és magában foglalja a szolgáltatások és a köztes szoftver konfigurálását is.

Az identitás konfigurálása a Program.cs fájlban

A Program.cs fájlban először a WebHostBuilder példány szolgáltatásait konfigurálod, majd az alkalmazás létrehozása után a köztes szoftvert. AddDefaultIdentity a szükséges szolgáltatások hívásai, valamint a UseAuthentication és UseAuthorization hívások, amelyek a szükséges middleware-t hozzáadják.

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();

Identitás konfigurálása az alkalmazás indításakor

// 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();

Fontos, hogy UseAuthentication és UseAuthorization megjelenjenek MapRazorPages előtt. Az Identitásszolgáltatások konfigurálásakor észre fog venni egy hívást AddDefaultTokenProviders fele. Ennek semmi köze a webkommunikáció védelméhez használható jogkivonatokhoz, ehelyett azokra a szolgáltatókra hivatkozik, amelyek felhasználóknak küldött sms-ek vagy e-mailek útján kérik személyazonosságuk megerősítését.

További információ a kéttényezős hitelesítés konfigurálásáról és a külső bejelentkezési szolgáltatók engedélyezéséről a hivatalos ASP.NET Core-dokumentumokból.

Hitelesítés

A hitelesítés annak meghatározása, hogy ki fér hozzá a rendszerhez. Ha ASP.NET Core Identity-t és az előző szakaszban bemutatott konfigurációs módszereket használja, az automatikusan beállít néhány alapértelmezett hitelesítést az alkalmazásban. Ezeket az alapértelmezett értékeket azonban manuálisan is konfigurálhatja, vagy felülbírálhatja az AddIdentity által beállítottakat. Ha identitást használ, az alapértelmezett sémaként a cookie-alapú hitelesítést konfigurálja.

A webes hitelesítésben általában legfeljebb öt művelet végezhető el egy rendszer ügyfélének hitelesítése során. Ezek a következők:

  • Hitelesít. Az ügyfél által megadott információk használatával hozzon létre egy identitást az alkalmazáson belül.
  • Kihívás. Ez a művelet arra szolgál, hogy megkövetelje az ügyféltől, hogy azonosítsa magát.
  • Tilts Tájékoztassa az ügyfelet, hogy nem hajthatnak végre műveletet.
  • Bejelentkezés. Valamilyen módon őrizze meg a meglévő ügyfelet.
  • Kijelentkezés. Távolítsa el az ügyfelet az adatmegőrzésből.

A webalkalmazásokban számos gyakori módszer létezik a hitelesítés végrehajtására. Ezeket sémáknak nevezzük. Egy adott séma a fenti lehetőségek némelyikéhez vagy mindegyikéhez definiál műveleteket. Egyes sémák csak a műveletek egy részhalmazát támogatják, és előfordulhat, hogy külön sémát igényelnek a nem támogatott műveletek végrehajtásához. Az OpenId-Connect (OIDC) séma például nem támogatja a bejelentkezést vagy a kijelentkezést, de általában úgy van konfigurálva, hogy cookie-hitelesítést használjon ehhez az adatmegőrzéshez.

A ASP.NET Core-alkalmazásban konfigurálhat egy DefaultAuthenticateScheme és opcionális konkrét sémát a fent leírt műveletek mindegyikéhez. Például: DefaultChallengeScheme és DefaultForbidScheme. A hívás AddIdentity az alkalmazás számos aspektusát konfigurálja, és számos szükséges szolgáltatást ad hozzá. Ez a hívás a hitelesítési séma konfigurálására is vonatkozik:

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

Ezek a sémák cookie-kat használnak az adatmegőrzéshez, és alapértelmezés szerint átirányítják a bejelentkezési oldalakra hitelesítés céljából. Ezek a sémák megfelelőek olyan webalkalmazásokhoz, amelyek webböngészőken keresztül kommunikálnak a felhasználókkal, de API-khoz nem ajánlottak. Ehelyett az API-k általában más hitelesítési módot használnak, például JWT-tulajdonosi jogkivonatokat.

A webes API-kat kód által fogyasztják, például HttpClient a .NET alkalmazásokban és más keretrendszereknek megfelelő típusokban. Ezek az ügyfelek hasznos választ várnak egy API-hívástól, vagy egy állapotkódot, amely jelzi, hogy mi történt, ha van ilyen, probléma. Ezek az ügyfelek nem böngészőn keresztül kommunikálnak, és nem renderelnek és nem használnak semmilyen HTML-t, amelyet egy API visszaadhat. Ezért nem célszerű, hogy az API-végpontok átirányítsák az ügyfeleiket a bejelentkezési oldalakra, ha nincsenek hitelesítve. Egy másik séma megfelelőbb.

Az API-k hitelesítésének konfigurálásához az alábbihoz hasonló hitelesítést állíthat be, amelyet a projekt használ az PublicApi eShopOnWeb referenciaalkalmazásban:

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
        };
    });

Bár egy projekten belül több különböző hitelesítési séma is konfigurálható, sokkal egyszerűbb egyetlen alapértelmezett sémát konfigurálni. Ezért többek között az eShopOnWeb referenciaalkalmazás saját projektre bontja az API-kat, PublicApielkülönítve az alkalmazás nézeteit és a Razor Pagest tartalmazó fő Web projekttől.

Hitelesítés az alkalmazásokban Blazor

Blazor A kiszolgálóalkalmazások ugyanazokat a hitelesítési funkciókat használhatják, mint bármely más ASP.NET Core-alkalmazás. Blazor WebAssembly az alkalmazások azonban nem használhatják a beépített identitás- és hitelesítésszolgáltatókat, mivel a böngészőben futnak. Blazor WebAssembly az alkalmazások helyileg tárolhatják a felhasználói hitelesítési állapotot, és hozzáférhetnek a jogcímekhez annak meghatározásához, hogy a felhasználók milyen műveleteket hajthatnak végre. A kiszolgálón azonban minden hitelesítési és engedélyezési ellenőrzést el kell végezni, függetlenül az alkalmazásban implementált BlazorWebAssembly logikától, mivel a felhasználók egyszerűen megkerülhetik az alkalmazást, és közvetlenül kezelhetik az API-kat.

Hivatkozások – Hitelesítés

Engedélyezés

Az engedélyezés legegyszerűbb formája a névtelen felhasználók hozzáférésének korlátozása. Ez a funkció úgy érhető el, hogy az [Authorize] attribútumot bizonyos vezérlőkre vagy műveletekre alkalmazza. Szerepkörök használata esetén az attribútum tovább bővíthető, hogy korlátozza az egyes szerepkörökhöz tartozó felhasználók hozzáférését az alábbi módon:

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

}

Ebben az esetben a HRManager szerepkörökhöz ( Finance vagy mindkettőhöz) tartozó felhasználók hozzáférhetnek a SalaryControllerhez. Ha meg szeretné követelni, hogy egy felhasználó több szerepkörhöz tartozjon (nem csak egy a több közül), az attribútumot többször is alkalmazhatja, és minden alkalommal meg kell adnia egy kötelező szerepkört.

Ha bizonyos szerepköröket sztringként ad meg számos különböző vezérlőben és műveletben, az nemkívánatos ismétléshez vezethet. Legalább definiáljon állandókat ezekhez a szöveges literálokhoz, és használja az állandókat bárhol, ahol csak szükséges a sztring megadása. Az attribútum alkalmazásakor az engedélyezési szabályzatokat is konfigurálhatja, amelyek belefoglalják az engedélyezési szabályokat, majd egyéni szerepkörök helyett a [Authorize] szabályzatot adhatja meg:

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

A szabályzatok ily módon történő használatával elkülönítheti a korlátozott műveleteket az adott szerepköröktől vagy szabályoktól. Később, ha létrehoz egy új szerepkört, amelynek hozzáféréssel kell rendelkeznie bizonyos erőforrásokhoz, egyszerűen frissíthet egy szabályzatot ahelyett, hogy minden [Authorize] attribútum szerepkörlistáját frissíteni kellene.

Igénylések

A jogcímek olyan névértékpárok, amelyek egy hitelesített felhasználó tulajdonságait jelölik. Előfordulhat például, hogy a felhasználók alkalmazotti számát jogcímként tárolja. A követelések ezután az engedélyezési szabályok részeként használhatók. Létrehozhat egy "EmployeeOnly" nevű szabályzatot, amely megköveteli egy úgynevezett "EmployeeNumber"jogcím meglétét, ahogyan az ebben a példában látható:

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

Ezt a szabályzatot ezután az attribútummal együtt használhatja a [Authorize] fent leírt vezérlők és/vagy műveletek védelmére.

Webes API-k védelme

A legtöbb webes API-nak jogkivonatalapú hitelesítési rendszert kell implementálnia. A tokenes hitelesítés állapotmentes és skálázható. Jogkivonatalapú hitelesítési rendszerben az ügyfélnek először hitelesítenie kell magát a hitelesítésszolgáltatóval. Ha sikeres, az ügyfél kap egy token-t, amely egyszerűen egy kriptográfiailag értelmes karaktersorozat. Az azonosítók leggyakoribb formátuma a JSON Web Token vagy JWT (gyakran "jot" néven ejtve). Amikor az ügyfélnek ezután egy API-nak kell kérést kiadnia, a jogkivonatot fejlécként adja hozzá a kéréshez. A kiszolgáló ezután ellenőrzi a kérés fejlécében található jogkivonatot a kérés befejezése előtt. A 7-4. ábra ezt a folyamatot mutatja be.

TokenAuth

7-4. ábra. Jogkivonatalapú hitelesítés webes API-khoz.

Létrehozhatja saját hitelesítési szolgáltatását, integrálhatja az Azure AD-t és az OAuth-ot, vagy implementálhat egy szolgáltatást egy nyílt forráskódú eszköz, például az IdentityServer használatával.

A JWT-jogkivonatok jogcímeket ágyazhatnak be a felhasználóról, amelyek olvashatók az ügyfélen vagy a kiszolgálón. Egy JWT-jogkivonat tartalmának megtekintéséhez használhat olyan eszközt, mint a jwt.io . Ne tároljon bizalmas adatokat, például jelszavakat vagy kulcsokat JTW-jogkivonatokban, mivel azok tartalma könnyen olvasható.

Ha JWT-jogkivonatokat használ SPA-val vagy BlazorWebAssembly alkalmazásokkal, a jogkivonatot valahol az ügyfélen kell tárolnia, majd hozzá kell adnia minden API-híváshoz. Ez a tevékenység általában fejlécként történik, ahogy az alábbi kód is mutatja:

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

A fenti metódus meghívása után a _httpClient kérés fejléceibe beágyazott jogkivonattal rendelkező kérések lehetővé teszik a kiszolgálóoldali API számára a kérés hitelesítését és engedélyezését.

Egyénre szabott biztonsági megoldások

Figyelmeztetés

Általános szabályként kerülje a saját egyéni biztonsági implementációk implementálását.

Különösen ügyeljen arra, hogy ne saját implementációt készítsen a titkosítás, a felhasználói tagság vagy a token generációs rendszer számára. Számos kereskedelmi és nyílt forráskódú alternatíva érhető el, amelyek szinte biztosan jobb biztonságot biztosítanak, mint egy egyéni implementáció.

Hivatkozások – Biztonság

Ügyfél-kommunikáció

A lapok kiszolgálása és a webes API-kon keresztüli adatkérések megválaszolása mellett ASP.NET Core-alkalmazások közvetlenül kommunikálhatnak a csatlakoztatott ügyfelekkel. Ez a kimenő kommunikáció számos különböző szállítási technológiát használhat, amelyek közül a leggyakoribbak a WebSockets. ASP.NET Core SignalR egy olyan kódtár, amellyel egyszerűen adhat hozzá valós idejű kiszolgáló–ügyfél kommunikációs funkciókat az alkalmazásokhoz. A SignalR számos szállítási technológiát támogat, köztük a WebSocketeket is, és a implementáció számos részletét elvonja a fejlesztőtől.

A valós idejű ügyfélkommunikáció, akár a WebSockets közvetlen használata, akár más technikák használata, számos alkalmazási forgatókönyvben hasznos lehet. Néhány példa:

  • Élő csevegőszoba-alkalmazások

  • Alkalmazások monitorozása

  • Feladat előrehaladási frissítései

  • Értesítések

  • Interaktív űrlapalkalmazások

Amikor ügyfélkommunikációt épít ki az alkalmazásokba, általában két összetevőből áll:

  • Kiszolgálóoldali kapcsolatkezelő (SignalR Hub, WebSocketManager WebSocketHandler)

  • Ügyféloldali könyvtár

Az ügyfelek nem korlátozódnak a böngészőkre – mobilalkalmazások, konzolalkalmazások és más natív alkalmazások is kommunikálhatnak SignalR/WebSocket használatával. Az alábbi egyszerű program egy WebSocketManager mintaalkalmazás részeként visszhangozza a csevegőalkalmazásnak küldött összes tartalmat:

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();
    }
}

Fontolja meg, hogyan kommunikálhatnak közvetlenül az alkalmazások az ügyfélalkalmazásokkal, és fontolja meg, hogy a valós idejű kommunikáció javítaná-e az alkalmazás felhasználói élményét.

Hivatkozások – Ügyfélkommunikáció

Tartományalapú tervezés – Alkalmazza?

Domain-Driven Tervezés (DDD) a szoftverfejlesztés agilis megközelítése, amely az üzleti területre összpontosít. Nagy hangsúlyt fektet a kommunikációra és az üzleti tartomány szakértőivel való interakcióra, akik a fejlesztőkkel a valós rendszer működéséhez tudnak kapcsolódni. Ha például olyan rendszert hoz létre, amely kezeli a tőzsdei kereskedést, a tartományszakértője tapasztalt tőzsdeügynök lehet. A DDD-t nagy, összetett üzleti problémák kezelésére tervezték, és gyakran nem alkalmas kisebb, egyszerűbb alkalmazásokhoz, mivel a tartomány megértésére és modellezésére irányuló befektetés nem éri meg.

Ha DDD-megközelítést követve fejleszt szoftvereket, a csapatnak (beleértve a nem műszaki érdekelt feleket és a közreműködőket) ki kell dolgoznia egy mindenütt elérhető nyelvet a problématérhez. Vagyis ugyanazt a terminológiát kell használni a modellezett valós koncepcióhoz, a szoftver megfelelőjéhez és a koncepció megőrzéséhez esetleg létező struktúrákhoz (például adatbázistáblákhoz). Így a mindenütt használt nyelven leírt fogalmak képezik a tartománymodell alapját.

A tartománymodell olyan objektumokat tartalmaz, amelyek kommunikálnak egymással a rendszer viselkedésének megfelelően. Ezek az objektumok a következő kategóriákba sorolhatók:

  • Entitások, amelyek identitásszállal rendelkező objektumokat jelölnek. Az entitásokat általában kulcs használatával tárolják, amely által később lekérhetők.

  • Aggregátumok, amelyek olyan objektumcsoportokat jelölnek, amelyeket egységként kell őrizni.

  • Értékobjektumok, amelyek a tulajdonságértékek összege alapján összehasonlítható fogalmakat jelölik. A DateRange például egy kezdő és egy záró dátumból áll.

  • Tartományi események, amelyek a rendszeren belüli olyan eseményeket jelölnek, amelyek a rendszer más részei számára érdekesek.

A DDD-tartománymodellnek összetett viselkedést kell magában foglalnia a modellben. Az entitások nem lehetnek csupán tulajdonságok gyűjteményei. Ha a tartománymodell nem rendelkezik viselkedéssel, és csupán a rendszer állapotát képviseli, azt mondják, hogy vérszegény modell, ami nem kívánatos a DDD-ben.

Ezeken a modelltípusokon kívül a DDD általában különböző mintákat alkalmaz:

  • Adattár, a perzisztencia részleteinek absztrakciójához.

  • Factory, összetett objektumlétrehozás beágyazására.

  • Szolgáltatások az összetett viselkedés és/vagy infrastruktúra-megvalósítás részleteinek beágyazására.

  • Parancs, a parancsok kiadásának és végrehajtásának szétválasztására.

  • Specifikáció a lekérdezés részleteinek beágyazására.

A DDD a korábban tárgyalt Clean Architektúra használatát is javasolja, amely lehetővé teszi a laza csatolást, a beágyazást és a kódokat, amelyek egységtesztekkel könnyen ellenőrizhetők.

Mikor érdemes alkalmazni a DDD-t?

A DDD kiválóan alkalmas olyan nagy alkalmazásokhoz, amelyek jelentős üzleti (nem csak műszaki) összetettséggel bírnak. Az alkalmazásnak tartományszakértői ismereteket kell igényelnie. Magának a tartománymodellnek jelentős viselkedésnek kell lennie, ami üzleti szabályokat és interakciókat jelöl, nem csupán az adattárakból származó rekordok aktuális állapotának tárolását és lekérését.

Mikor ne alkalmazza a DDD-t?

A DDD magában foglalja a modellezés, az architektúra és a kommunikáció olyan befektetéseit, amelyek nem feltétlenül indokoltak olyan kisebb alkalmazások vagy alkalmazások esetében, amelyek lényegében csak CRUD-k (létrehozás/olvasás/frissítés/törlés). Ha úgy dönt, hogy a DDD megközelítést alkalmazza az alkalmazásnál, de úgy találja, hogy a domaine egy vérszegény modell, amelynél nincs viselkedés, előfordulhat, hogy újra kell gondolnia a megközelítését. Előfordulhat, hogy az alkalmazásnak nincs szüksége DDD-re, vagy segítségre lehet szüksége az alkalmazás újrabontásához az üzleti logika tartománymodellben való beágyazásához, nem pedig az adatbázisban vagy a felhasználói felületen.

A hibrid megközelítés az lenne, ha csak a DDD-t használnánk az alkalmazás tranzakciós vagy összetettebb területeihez, de az alkalmazás egyszerűbb CRUD vagy írásvédett részeihez nem. Nincs szükség például az összesítés korlátozására, ha adatokat kérdez le egy jelentés megjelenítéséhez vagy egy irányítópult adatainak megjelenítéséhez. Teljesen elfogadható, ha külön, egyszerűbb olvasási modellel rendelkezik az ilyen követelményekhez.

Hivatkozások – Domain-Driven tervezés

Telepítés

A ASP.NET Core-alkalmazás üzembe helyezésének folyamatában van néhány lépés, függetlenül attól, hogy hol lesz üzemeltetve. Az első lépés az alkalmazás közzététele, amely a dotnet publish CLI paranccsal végezhető el. Ez a lépés lefordítja az alkalmazást, és az alkalmazás futtatásához szükséges összes fájlt egy kijelölt mappába helyezi. Amikor a Visual Studióból telepíti az üzembe helyezést, a rendszer automatikusan végrehajtja ezt a lépést. A közzétételi mappa .exe és .dll fájlokat tartalmaz az alkalmazáshoz és függőségeihez. Az önálló alkalmazások a .NET-futtatókörnyezet egy verzióját is tartalmazzák. ASP.NET alapalkalmazások konfigurációs fájlokat, statikus ügyfélegységeket és MVC-nézeteket is tartalmaznak.

ASP.NET alapalkalmazások olyan konzolalkalmazások, amelyeket a kiszolgáló indításakor és újraindításakor kell elindítani, ha az alkalmazás (vagy kiszolgáló) összeomlik. A folyamat automatizálásához egy folyamatkezelő használható. A ASP.NET Core leggyakoribb folyamatkezelői az Nginx és az Apache Linuxon és IIS-en, illetve Windowson a Windows szolgáltatás.

A folyamatkezelőn kívül a ASP.NET Core-alkalmazások fordított proxykiszolgálót is használhatnak. A fordított proxykiszolgáló http-kérelmeket fogad az internetről, és bizonyos előzetes kezelés után továbbítja őket a Kestrelnek. A fordított proxykiszolgálók biztonsági réteget biztosítanak az alkalmazás számára. A Kestrel emellett nem támogatja több alkalmazás üzemeltetését ugyanazon a porton, így az olyan technikák, mint a gazdagépfejlécek, nem használhatók vele több alkalmazás ugyanazon a porton és IP-címen való üzemeltetésére.

Kestrel az internethez

7–5. ábra. ASP.NET, amelyet Kestrelben üzemeltetnek, egy fordított proxykiszolgáló mögött

Egy másik forgatókönyv, amelyben a fordított proxy hasznos lehet, ha több alkalmazást is biztonságossá tesz SSL/HTTPS használatával. Ebben az esetben csak a fordított proxynak kell SSL-t konfigurálnia. A fordított proxykiszolgáló és a Kestrel közötti kommunikáció HTTP-en keresztül történhet, ahogy a 7–6. ábrán látható.

HTTPS által védett fordított proxykiszolgáló mögött üzemeltetett ASP.NET

7-6. ábra. HTTPS által védett fordított proxykiszolgáló mögött üzemeltetett ASP.NET

Egyre népszerűbb módszer a ASP.NET Core-alkalmazás üzemeltetése egy Docker-tárolóban, amely helyileg üzemeltethető, vagy üzembe helyezhető az Azure-ban felhőalapú üzemeltetés céljából. A Docker-tároló tartalmazhatja a Kestrelen futó alkalmazáskódot, és egy fordított proxykiszolgáló mögött lenne üzembe helyezve, ahogy az fent látható.

Ha az alkalmazást az Azure-ban üzemelteti, a Microsoft Azure Application Gatewayt dedikált virtuális berendezésként használhatja több szolgáltatás biztosításához. Amellett, hogy fordított proxyként működik az egyes alkalmazásokhoz, az Application Gateway a következő funkciókat is kínálhatja:

  • HTTP-terheléselosztás

  • SSL-kiszervezés (SSL csak az internet felé)

  • Teljes körű SSL

  • Többhelyes útválasztás (egyetlen Application Gatewayen legfeljebb 20 hely összesítése)

  • Webalkalmazási tűzfal

  • Websocket-támogatás

  • Speciális diagnosztika

További információ az Azure üzembe helyezési lehetőségeiről a 10. fejezetben.

Hivatkozások – Üzembe helyezés