Atis.Hosting.AspNetCore 2.0.0-preview.5

This is a prerelease version of Atis.Hosting.AspNetCore.
dotnet add package Atis.Hosting.AspNetCore --version 2.0.0-preview.5
                    
NuGet\Install-Package Atis.Hosting.AspNetCore -Version 2.0.0-preview.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Atis.Hosting.AspNetCore" Version="2.0.0-preview.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Atis.Hosting.AspNetCore" Version="2.0.0-preview.5" />
                    
Directory.Packages.props
<PackageReference Include="Atis.Hosting.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Atis.Hosting.AspNetCore --version 2.0.0-preview.5
                    
#r "nuget: Atis.Hosting.AspNetCore, 2.0.0-preview.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Atis.Hosting.AspNetCore@2.0.0-preview.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Atis.Hosting.AspNetCore&version=2.0.0-preview.5&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Atis.Hosting.AspNetCore&version=2.0.0-preview.5&prerelease
                    
Install as a Cake Tool

Atis.Hosting.AspNetCore

ATIS host-larının (API və ya MVC WebUI) vahid qeydiyyat və boru xətti qurulması: Serilog/Graylog, sağlamlıq (health) uc nöqtələri, CORS, Consul, korrelyasiya id-si, ProblemDetails, forwarded headers və — yalnız API host üçün — Swagger, API versiyalama və JWT bearer.

İki giriş nöqtəsi

AddAtisApiHost / UseAtisApiHost AddAtisWebUiHost / UseAtisWebUiHost
Nə üçün Yalnız Controllers/minimal API (Razor View yoxdur) MVC + Razor View, session-lı servislər
Ortaq özəyə əlavə Swagger sənəd generasiyası, Asp.Versioning API versiyalama, (Sso bölməsi varsa) JWT bearer Session (AddDistributedMemoryCache + AddSession), AddControllersWithViews(), (aktivdirsə) Razor runtime compilation, (assembly verilibsə) FluentValidation
Autentifikasiya sxemi Yalnız Sso bölməsi mövcuddursa JWT bearer öz-özünə qurulur Heç bir sxem qurulmur — aşağıya bax

Hər ikisi ortaq özəyi (AddAtisSharedHost/UseAtisSharedHost) çağırır: CORS/health/Consul/logging accessor-ları, ProblemDetails + exception handler, audit körpüsü, ForwardedHeaders.

AddAtisWebUiHost heç bir autentifikasiya sxemi qurmur

atis-service-user-in WebUI-si OpenIddict serveridir; digər servislər Atis.Identity.Sso paketi (AddAtisSsoRelyingParty(cfg, policies) + app.MapAtisSso()) ilə OIDC relying party olur. Bu host yalnız UseAuthentication() (qeydiyyatdan keçmiş sxem provayderi varsa şərti) / UseAuthorization()-u boru xəttində düzgün yerə qoyur — sxemi (cookie + OIDC) həmin paket, ya da servisin özü qeyd edir.

ConfigureAtisSerilog ayrıca çağırılır

Serilog builder.Host (IHostBuilder) üzərində qurulur, IServiceCollection üzərində yox — ona görə IServiceCollection extension-ı (AddAtisApiHost/AddAtisWebUiHost) ola bilməz. Mütləq AddAtisApiHost/UseAtisApiHost (və ya WebUI ekvivalenti)-dan əvvəl çağırılmalıdır: hər iki UseAtisApiHost()/UseAtisWebUiHost() DI-da IDiagnosticContext (Serilog.Extensions.Hosting) tapmasa, InvalidOperationException (Azərbaycan mesajı ilə) atır. Bu qəsdəndir — unudulsa sorğu logu və Graylog-a gedən hər şey əvvəllər səssizcə itirdi.

Sink-lər: konsol və — yalnız Graylog:Enabled=true olanda — Graylog. Fayl sink-i YOXDUR (konteynerdə yanlış defolt olardı: log fayla yazılıb host-dan görünməz qalar, disk dolar). Logger qurulanda ReadFrom.Configuration(...) çağırılır — yəni servis öz Serilog:WriteTo bölməsi ilə əlavə sink qoşa bilər, ancaq həmin sink paketinə öz PackageReference-ini özü əlavə etməlidir (bu paket yalnız konsol və Graylog sink-lərini gətirir).

Program.cs — API host

using Atis.Hosting.AspNetCore.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.ConfigureAtisSerilog(); // MƏCBURİ — AddAtisApiHost/UseAtisApiHost-dan ƏVVƏL

builder.Services.AddAtisApiHost(builder.Configuration);
builder.Services.AddControllers();

// İcazə siyasəti lazımdırsa:
// builder.Services.AddAtisAuthorization(o => o.AddPermissionPolicies(StaffPermissions.All));

// DB audit sütunlarının (CreatedUserId/UpdatedUserId) avtomatik dolması üçün:
// builder.Services.AddAtisPersistence<StaffDbContext>(builder.Configuration, o => o.Schema = "staff");

var app = builder.Build();

app.UseAtisApiHost();

// DİQQƏT: UseAtisApiHost() artıq UseRouting() çağırıb — burada YENİDƏN app.UseRouting() YAZMA,
// birbaşa endpoint mapping-ə keç.
app.MapControllers();

app.Run();

Program.cs — WebUI host

using System.Reflection;
using Atis.Hosting.AspNetCore.Extensions;
using Microsoft.AspNetCore.Authentication.Cookies;

var builder = WebApplication.CreateBuilder(args);

builder.ConfigureAtisSerilog(); // MƏCBURİ — AddAtisWebUiHost/UseAtisWebUiHost-dan ƏVVƏL

// Assembly.GetExecutingAssembly() ÖZ servisinin assembly-sidir — FluentValidation validator-ları
// ordan axtarılır. null ötürülsə, FluentValidation heç qoşulmur.
builder.Services.AddAtisWebUiHost(builder.Configuration, Assembly.GetExecutingAssembly());

// Autentifikasiya sxemini bu paket qurmur — Atis.Identity.Sso qurur (cookie + OIDC code+PKCE,
// permission policy-ləri (ATIS qaydaları), ICurrentUser). Sonra app.MapAtisSso() ilə /account/login|logout gəlir:
builder.Services.AddAtisSsoRelyingParty(builder.Configuration, o => o.AddPermissionPolicies(StaffPermissions.All));

var app = builder.Build();

app.UseAtisWebUiHost();

// DİQQƏT: UseAtisWebUiHost() artıq UseRouting() çağırıb — burada YENİDƏN app.UseRouting() YAZMA,
// birbaşa endpoint mapping-ə keç.
app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

⚠ Deploy-dan ƏVVƏL

1. Konfiqurasiya bölmə adları dəyişir — bu, mövcud ~20 servisin deployment-ini sındırır

Köhnə (mövcud servislər) Yeni
ConsulConfig:Id / Name / ConsulAddress / Address Consul:ServiceId / ServiceName / ConsulAddress / ServiceAddress
ServiceUrls (massiv) Cors:AllowedOrigins (massiv) + Cors:PolicyName
Graylog C#-da hardcode Graylog:Enabled / Host / Port / Facility / TransportType
Token:Issuer / Audience / SecurityKey Sso:Authority / Sso:Audience (simmetrik açar yoxdur — OIDC discovery, Sso:RequireHttpsMetadata opsional, default true)

Servis appsettings.json-unu köçürmədən bu paketə keçsə, Consul qeydiyyatı, CORS və Graylog səssizcə söndürülmüş vəziyyətdə qalır (aşağıdakı "bölmə yoxdursa" qaydasına görə) — heç bir build və ya start-up xətası vermir, problem yalnız işlədikdə (məs. Consul-da servis görünmür) üzə çıxır.

2. Swagger artıq Production-da map olunmur

Mövcud servislərdə Swagger prod-da da açıq idi — bu, qəsdən dəyişdirilib. Swagger:Enabled DI qeydiyyatına (sənəd generasiyasına) təsir edir və mühitdən asılı deyil, amma UseSwagger()/ UseSwaggerUI() middleware-ləri yalnız !env.IsProduction() olduqda boru xəttinə qoşulur — yəni Production mühitində (ASPNETCORE_ENVIRONMENT=Production) nə /swagger/v1/swagger.json, nə də Swagger UI əlçatandır. API sənədinə istehsalatda ehtiyac varsa, ayrıca (bu paketdən kənar) həll tələb olunur.

Bölmələr — şərti vs həmişə

Bölmə Rejim Açarlar (default)
Cors Şərti — bölmə konfiqurasiyada mövcud olmasa, CORS siyasəti heç qoşulmur PolicyName ("DefaultPolicy"), AllowedOrigins (mütləq, mütləq URI-lər), AllowAnyHeader (true), AllowAnyMethod (true), AllowCredentials (false), ExposedHeaders (null)
Consul Şərti — bölmə mövcud olmasa qeydiyyat heç qoşulmur; mövcuddursa da faktiki qeydiyyat (hosted service) yalnız Consul:Enabled=true olanda işə düşür Enabled (false), ServiceId, ServiceName, ConsulAddress, ServiceAddress (Enabled=true olanda hamısı mütləq), HealthCheckPath ("/api/health"), HealthCheckInterval ("10s"), HealthCheckTimeout ("5s"), DeregisterCriticalServiceAfter ("30s"), Tags, Meta
Graylog Həmişə bind olunur (bölmə yoxdursa defolt dəyərlərlə: söndürülmüş) Enabled (false), Host, Port, Facility (Enabled=true olanda üçü də mütləq — boş Facility sink-in öz defoltunu əvəz edir və Graylog-da stream marşrutlamasını sındırır), TransportType ("Udp")
HealthEndpoints Həmişə bind olunur — bölmə olmasa da kanonik /api/health işə düşür Path ("/api/health"), ReadinessPath (null), LivenessPath (null), UsePlainTextResponse (true), PlainText ("Ok"), SuppressRequestLogging (true)
Sso Şərti, yalnız API host — bölmə mövcud olanda JWT bearer qoşulur və açarlar start-da yoxlanılır Authority (mütləq, mütləq http(s) URI), Audience (mütləq, boş ola bilməz), RequireHttpsMetadata (true)
Swagger Həmişə bind olunur, yalnız API host Enabled (true), Title ("ATIS API"), Version ("v1")
WebUi Həmişə bind olunur, yalnız WebUI host SessionTimeoutMinutes (60), RazorRuntimeCompilation (true), ErrorPath ("/Home/Error")

Bütün bölmələr ValidateOnStart() ilə qeydiyyatdan keçir, amma məzmun qaydaları hamısında yoxdur:

  • Cors, Consul, Graylog, HealthEndpoints, Sso — öz validator-ları var, yanlış dəyər host qalxanda OptionsValidationException ilə düşür (ilk sorğuda yox).
  • Swagger, WebUi — yalnız bind olunur: tip uyuşmazlığı (məs. "Enabled": "bəli") start-da düşür, amma məzmun qaydası (hansı dəyər məqbuldur) yoxlanılmır.

appsettings.json nümunəsi (dəyərlər yalnız nümunədir):

{
  "Cors": { "AllowedOrigins": ["https://staff.atis.edu.az"] },
  "Consul": {
    "Enabled": true,
    "ServiceId": "atis-staff-1",
    "ServiceName": "atis-staff",
    "ConsulAddress": "http://consul:8500",
    "ServiceAddress": "http://atis-staff:8080"
  },
  "Graylog": { "Enabled": true, "Host": "graylog.internal", "Port": 12201 },
  "Sso": { "Authority": "https://user.atis.edu.az", "Audience": "atis.staff.api" }
}

Boru xətti sırası

Ortaq hissə (UseAtisSharedHost, hər iki host tipində eyni, sabit sıra):

UseForwardedHeaders → UseAtisCorrelationId → UseExceptionHandler → (Cors bölməsi varsa) UseCors → health middleware

Korrelyasiya id-si exception handler-dən əvvəl gəlməlidir ki, xəta cavabı korrelyasiya id-sini daşısın. Health uc nöqtəsi burada IApplicationBuilder-in UseHealthChecks(...) middleware overload-u ilə qoşulur (endpoint routing yox) — ona görə /api/health UseRouting()-dən əvvəl işləyir.

API host davamı: UseSerilogRequestLogging → (prod deyilsə və Swagger:Enabled-dırsa) UseSwagger/UseSwaggerUI → UseRouting → (sxem qeydiyyatdaydırsa) UseAuthentication → UseAuthorization

WebUI host davamı: UseSerilogRequestLogging → UseStaticFiles → UseRouting → (sxem qeydiyyatdaydırsa) UseAuthentication → UseAuthorization → UseSession

Nə üçün UseRouting()-i özün çağırmamalısan

Həm UseAtisApiHost(), həm də UseAtisWebUiHost() UseRouting()-i özü çağırır — servis app.UseRouting() YAZMAMALIDIR, birbaşa MapControllers()/MapControllerRoute(...)-ə keçməlidir. Səbəb: AuthorizationMiddleware [Authorize] metadata-sını context.GetEndpoint()-dən oxuyur, bu isə routing icra olunmayınca null-dur — routing-dən əvvəl autentifikasiya/icazələndirmə qoyulsa, icazə heç vaxt yoxlanılmır.

UseSession() niyə UseAuthorization()-dan sonra gəlir

Mövcud servislərdə ISessionManager institution scoping-i session-da keşləyir və bunu authorization-dan sonra oxuyur — sıra bunu qorumaq üçün qəsdən belədir. AddAtisWebUiHost AddDistributedMemoryCache()-i TryAdd-based qeyd edir, ona görə servis onu AddStackExchangeRedisCache() və s. ilə əvəz edə bilər.

AddAuthorization() niyə şərtsiz çağırılır

AddAtisSharedHost Sso/sxem mövcudluğundan asılı olmayaraq şərtsiz AddAuthorization() çağırır — əks halda UseAuthorization() (hər iki host tipində şərtsiz çağırılır) IAuthorizationPolicyProvider tapmadığına görə host qalxarkən InvalidOperationException atardı. Servis öz permission siyasətlərini AddAtisAuthorization/AddAuthorization ilə əlavə etdikdə bu, sadəcə əlavə konfiqurasiya kimi üstünə gəlir — konflikt yaratmır.

X-Correlation-ID

CorrelationIdMiddleware gələn X-Correlation-ID başlığını götürür; yoxdursa (və ya 128 simvoldan uzundursa — log-injection/başlıq şişməsi riski) yeni GUID yaradır. Dəyər HttpContext.Items["Atis.CorrelationId"]-ə yazılır, cavab başlığına (Response.OnStarting ilə) əlavə olunur və Serilog LogContext-inə (CorrelationId property-si kimi) sorğunun sonuna qədər push olunur.

ProblemDetails cavabı

Tutulmamış exception RFC 7807 application/problem+json cavabına çevrilir. Cavabda exception mesajı və stack trace OLMUR — onlar yalnız loga (ILogger) yazılır. Cavab yalnız ümumi mətn, Status=500, traceId (HttpContext.TraceIdentifier) və (varsa) correlationId extension-larını daşıyır.

WebUI host-da bu, Accept başlığından asılı olaraq fərqlənir: brauzer sorğuları (Accept: text/html, application/json olmadan) ProblemDetails ALMIR — WebUi:ErrorPath-a (default "/Home/Error", portlanmış ATIS HomeController.Error) yenidən icra olunur və HTML xəta səhifəsi qaytarılır. JSON/AJAX sorğular (Accept: application/json, ya da Accept heç göndərilməyibsə) adi qaydada ProblemDetails alır. API host-da bu fərqləndirmə YOXDUR — AtisHtmlErrorPolicy.None qeydiyyatdadır, ona görə hər sorğu (brauzer olsa belə) həmişə ProblemDetails alır.

Audit körpüsü

AddAtisSharedHost (deməli, AddAtisApiHost/AddAtisWebUiHost-un ikisi də) IAuditUserProvider-i CurrentUserAuditProvider-ə (Atis.Identity.Token-in ICurrentUser-i üzərində qurulub) açıq AddScoped ilə bağlayır — Atis.Persistence-in AddAtisPersistence-də TryAddScoped ilə qeyd etdiyi NullAuditUserProvider-i həmişə əvəz edir (sıra əhəmiyyətsizdir, "son qeydiyyat qalib gəlir"). Servis eyni zamanda AddAtisPersistence ilə işlədəndə CreatedUserId/UpdatedUserId sütunları avtomatik dolur — servisin özü heç nə etmir.

AddAtisSharedHost həmçinin (TryAdd-based, idempotent) AddAtisIdentity()-ni özü çağırır — körpü konstruktorunda ICurrentUser tələb etdiyi üçün, servis yalnız AddAtisApiHost/ AddAtisWebUiHost çağırıb heç bir permission siyasəti (AddAtisAuthorization) qurmasa belə, host özü-özünə bəs edən qalır.

ForwardedHeaders

KnownProxies/KnownNetworks təmizlənir — yəni gələn X-Forwarded-For/X-Forwarded-Proto başlıqlarına etibar edilir. Bu, yalnız etibarlı reverse proxy (nginx/ingress/Consul-Connect və s.) arxasında düzgündür; açıq internetə birbaşa ifşa olunan host üçün yararsızdır (client-in özü bu başlıqları saxtalaşdıra bilər).

Swagger (API host)

Swagger:Enabled (default true) DI-da AddSwaggerGen-i qoşur — Bearer security scheme ilə birlikdə (Swagger UI-dan "Authorize" düyməsi işləsin deyə, Sso qoşulmasa belə). XML doc faylı yalnız servisin öz assembly-sindən ({EntryAssembly}.xml) və yalnız mövcuddursa qoşulur. UseSwagger()/UseSwaggerUI() — yuxarıdakı ⚠ Deploy-dan ƏVVƏL bölməsinə bax.

API versiyalama

Asp.Versioning ilə: DefaultApiVersion = 1.0, AssumeDefaultVersionWhenUnspecified = true, ReportApiVersions = true, URL-də qrup formatı 'v'VVV (SubstituteApiVersionInUrl = true).

JWT bearer (yalnız API host, Sso bölməsi mövcud olanda)

Dəyərlər xam bölmədən yox, tipli və start-da yoxlanılan SsoOptions-dan gəlir:

o.Authority = sso.Value.Authority;
o.Audience = sso.Value.Audience;
o.RequireHttpsMetadata = sso.Value.RequireHttpsMetadata;
o.MapInboundClaims = false;
o.TokenValidationParameters.RoleClaimType = "role";
o.TokenValidationParameters.NameClaimType = "sub";

MapInboundClaims = false, RoleClaimType = "role", NameClaimType = "sub" MƏCBURİDİR — Atis.Identity.Token-in claim oxuyucuları OIDC token-inin xam "role"/"sub" claim adlarını gözləyir. MapInboundClaims true olsa (defolt), JwtBearerHandler bu adları ClaimTypes.Role/ClaimTypes.NameIdentifier-ə çevirər və Atis.Identity.Token-in oxuyucuları heç nə tapmaz. Bu üç seçim məhz buna görə Atis.Identity.Token-in OIDC token-ləri ilə işləməsini təmin edir.

FluentValidation (yalnız WebUI host)

AddAtisWebUiHost(configuration, validatorAssembly) — validatorAssembly verilməyibsə (default null), FluentValidation heç qoşulmur. Bu paket özü heç vaxt Assembly.GetExecutingAssembly() işlətmir (bu, paketin öz assembly-sini qaytarardı, servisinkini yox) — servis onu açıq ötürməlidir: services.AddAtisWebUiHost(cfg, Assembly.GetExecutingAssembly()). Verilibsə, AddFluentValidationAutoValidation() (MVC model validasiyasını avtomatlaşdırır) və AddValidatorsFromAssembly(validatorAssembly) (servisin öz IValidator<T> implementasiyalarını qeyd edir) cütü qoşulur — FluentValidation.AspNetCore-un [Obsolete] AddFluentValidation(...) genişləndirməsi əvəzinə.

Session (yalnız WebUI host)

WebUi:SessionTimeoutMinutes (default 60) idle timeout təyin edir; cookie HttpOnly=true, IsEssential=true. IDistributedCache AddDistributedMemoryCache() ilə TryAdd-based qeyd olunur — servis onu Redis və s. ilə əvəz edə bilər.

Razor runtime compilation (yalnız WebUI host)

WebUi:RazorRuntimeCompilation (default true) aktivdirsə, AddRazorRuntimeCompilation() qoşulur — .cshtml dəyişiklikləri dotnet build etmədən dərhal görünür.

HSTS (preview.4)

UseAtisSharedHost (deməli UseAtisApiHost/UseAtisWebUiHost) ForwardedHeaders-dən dərhal sonra, Development xaric, UseHsts() çağırır. Konteyner TLS proxy arxasında plain HTTP-dədir — Request.IsHttps yalnız X-Forwarded-Proto oxunandan sonra doğrudur; başlıq bütün cavablara (static fayllar, /api/health, OIDC challenge 302, 403) düşür. Servis özü UseHsts() çağırmasın — ikiqat Strict-Transport-Security başlığı yaranar (atis-service-user WebUI-də hələ var, təmizlənməlidir).

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0-preview.5 42 10/1/2026
2.0.0-preview.4 93 9/14/2026

2.0.0-preview.1: AddAtisApiHost/AddAtisWebUiHost, ConfigureAtisSerilog, X-Correlation-ID, ProblemDetails, tipli və start-da yoxlanılan options (Consul, Cors, Graylog, HealthEndpoints, Sso, Swagger, WebUi). DİQQƏT: konfiqurasiya bölmə adları köhnə servislərdən fərqlidir — README-yə bax. preview.2: WebUi:ErrorPath + AtisHtmlErrorPolicy — WebUI host-da brauzer (Accept: text/html) sorğuları tutulmamış exception-da ProblemDetails yox, HTML xəta səhifəsinə (ErrorPath) yenidən icra olunur; API host-da davranış dəyişmir. preview.4: UseAtisSharedHost ForwardedHeaders-dən dərhal sonra (Development xaric) UseHsts() çağırır — servis özü UseHsts çağırmasın (ikiqat başlıq); proxy arxasında IsHttps düzəldikdən sonra bütün cavablar (static, health, challenge, 403) HSTS daşıyır.