Dela via


Snabbreferens för minimala API:er

Not

Det här är inte den senaste versionen av den här artikeln. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella versionen finns i .NET 9-versionen av den här artikeln .

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen av den här artikeln finns i .NET 9 version.

Det här dokumentet:

De minimala API:erna består av:

WebApplication

Följande kod genereras av en ASP.NET Core-mall:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Föregående kod kan skapas via dotnet new web på kommandoraden eller genom att välja mallen Tom webb i Visual Studio.

Följande kod skapar en WebApplication (app) utan att uttryckligen skapa en WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create initierar en ny instans av klassen WebApplication med förkonfigurerade standardvärden.

WebApplication lägger automatiskt till följande mellanprogram i Minimal API applications beroende på vissa villkor:

  • UseDeveloperExceptionPage läggs till först när HostingEnvironment är "Development".
  • UseRouting läggs till som nummer två om användarkoden inte redan har anropat UseRouting och om det finns konfigurerade slutpunkter, till exempel app.MapGet.
  • UseEndpoints läggs till i slutet av pipelinen för mellanprogram om några slutpunkter har konfigurerats.
  • UseAuthentication läggs till omedelbart efter UseRouting om användarkoden inte redan anropade UseAuthentication och om IAuthenticationSchemeProvider kan identifieras i tjänstleverantören. IAuthenticationSchemeProvider läggs till som standard när du använder AddAuthenticationoch tjänster identifieras med hjälp av IServiceProviderIsService.
  • UseAuthorization läggs till nästa om användarkoden inte redan anropar UseAuthorization och om IAuthorizationHandlerProvider kan identifieras i tjänstleverantören. IAuthorizationHandlerProvider läggs till som standard när du använder AddAuthorizationoch tjänster identifieras med hjälp av IServiceProviderIsService.
  • Användar konfigurerade mellanprogram och slutpunkter läggs till mellan UseRouting och UseEndpoints.

Följande kod är i praktiken vad det automatiska mellanprogrammet som läggs till i appen genererar:

if (isDevelopment)
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
    app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
    app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

I vissa fall är standardkonfigurationen för mellanprogram inte korrekt för appen och kräver ändringar. Till exempel ska UseCors anropas före UseAuthentication och UseAuthorization. Appen måste anropa UseAuthentication och UseAuthorization om UseCors anropas:

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

Om mellanprogram ska köras innan routningsmatchning sker ska UseRouting anropas och mellanprogrammet placeras innan anropet till UseRouting. UseEndpoints krävs inte i det här fallet eftersom det läggs till automatiskt enligt beskrivningen ovan:

app.Use((context, next) =>
{
    return next(context);
});

app.UseRouting();

// other middleware and endpoints

När du lägger till ett terminalmellanprogram:

  • Mellanprogrammet måste läggas till efter UseEndpoints.
  • Appen måste anropa UseRouting och UseEndpoints så att terminalmellanprogrammet kan placeras på rätt plats.
app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
});

Terminalmellanprogram är mellanprogram som körs om ingen slutpunkt hanterar begäran.

Arbeta med portar

När en webbapp skapas med Visual Studio eller dotnet newskapas en Properties/launchSettings.json fil som anger de portar som appen svarar på. I portinställningsexemplen som följer returnerar körning av appen från Visual Studio en feldialogruta Unable to connect to web server 'AppName'. Visual Studio returnerar ett fel eftersom den förväntar sig att porten som anges i Properties/launchSettings.json, men appen använder den port som anges av app.Run("http://localhost:3000"). Kör följande portförändrande exempel från kommandoraden.

Följande avsnitt anger den port som appen svarar på.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

I föregående kod svarar appen på port 3000.

Flera portar

I följande kod svarar appen på port 3000 och 4000.

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Ange porten från kommandoraden

Följande kommando gör att appen svarar på port 7777:

dotnet run --urls="https://localhost:7777"

Om slutpunkten Kestrel också har konfigurerats i filen appsettings.json, används den URL som specificerats i filen appsettings.json. Mer information finns i Kestrel slutpunktskonfiguration

Läsa porten från miljön

Följande kod läser porten från miljön:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Det bästa sättet att ange porten från miljön är att använda ASPNETCORE_URLS miljövariabeln, som visas i följande avsnitt.

Ange portarna via miljövariabeln ASPNETCORE_URLS

Miljövariabeln ASPNETCORE_URLS är tillgänglig för att ange porten:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS stöder flera URL:er:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Lyssna på alla gränssnitt

Följande exempel visar hur alla gränssnitt avlyssnas

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Lyssna på alla gränssnitt med hjälp av ASPNETCORE_URLS

Föregående exempel kan använda ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Lyssna på alla gränssnitt med ASPNETCORE_HTTPS_PORTS

Föregående exempel kan använda ASPNETCORE_HTTPS_PORTS och ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Mer information finns i Konfigurera slutpunkter för webbservern ASP.NET Core Kestrel

Ange HTTPS med utvecklingscertifikat

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Mer information om utvecklingscertifikatet finns i Trust the ASP.NET Core HTTPS development certificate on Windows and macOS.

Ange HTTPS med ett anpassat certifikat

Följande avsnitt visar hur du anger det anpassade certifikatet med hjälp av appsettings.json-filen och via konfiguration.

Ange det anpassade certifikatet med appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Ange det anpassade certifikatet via konfiguration

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Använda certifikat-API:erna

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Förstå miljön

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Mer information om hur du använder miljön finns i Använda flera miljöer i ASP.NET Core

Konfiguration

Följande kod läser från konfigurationssystemet:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Mer information finns i Configuration i ASP.NET Core

Skogsavverkning

Följande kod skriver ett meddelande till loggen vid programstart:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Mer information finns i Loggning i .NET Core och ASP.NET Core

Få åtkomst till beroendeinjektionscontainern (DI)

Följande kod visar hur du hämtar tjänster från DI-containern under programstarten:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Följande kod visar hur du kommer åt nycklar från DI-containern med hjälp av attributet [FromKeyedServices]:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));

app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) => smallCache.Get("date"));

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

Mer information om DI finns i Beroendeinmatning i ASP.NET Core.

WebApplicationBuilder

Det här avsnittet innehåller exempelkod med hjälp av WebApplicationBuilder.

Ändra innehållsrot, programnamn och miljö

Följande kod anger innehållsrot, programnamn och miljö:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder initierar en ny instans av klassen WebApplicationBuilder med förkonfigurerade standardvärden.

Mer information finns i översikten över grunderna i ASP.NET Core

Ändra innehållsrot, appnamn och miljö med hjälp av miljövariabler eller kommandorad

I följande tabell visas miljövariabeln och kommandoradsargumentet som används för att ändra innehållsrot, appnamn och miljö:

funktion Miljövariabel Kommandoradsargument
Programnamn ASPNETCORE_APPLICATIONNAME --applicationName
Miljönamn ASPNETCORE_ENVIRONMENT --miljö
Innehållsbas ASPNETCORE_CONTENTROOT --contentRoot

Lägga till konfigurationsprovidrar

Följande exempel lägger till INI-konfigurationsprovidern:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Detaljerad information finns i Filkonfigurationsproviders i Configuration i ASP.NET Core.

Läs konfiguration

Som standard läser WebApplicationBuilder konfigurationen från flera källor, inklusive:

  • appSettings.json och appSettings.{environment}.json
  • Miljövariabler
  • Kommandoraden

En fullständig lista över konfigurationskällor finns i Standardkonfiguration i Configuration i ASP.NET Core.

Följande kod läser HelloKey från konfigurationen och visar värdet vid / slutpunkten. Om konfigurationsvärdet är null tilldelas "Hello" till message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Läsa av miljön

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Lägg till loggningsleverantörer

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Lägga till tjänster

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Anpassa IHostBuilder

Befintliga tilläggsmetoder på IHostBuilder kan nås med egenskapen Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Anpassa IWebHostBuilder

Tilläggsmetoder på IWebHostBuilder kan nås med hjälp av egenskapen WebApplicationBuilder.WebHost.

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Ändra webbplatsens rot

Som standard är webbroten relativ till innehållsroten i mappen wwwroot. Webbroten är där mellanprogrammet för statiska filer letar efter statiska filer. Webbroten kan ändras med WebHostOptions, kommandoraden eller med metoden UseWebRoot:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Container för anpassad beroendeinjektion (DI)

I följande exempel används Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Lägg till mellanprogram

Alla befintliga ASP.NET Core-mellanprogram kan konfigureras på WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Mer information finns i ASP.NET Core Middleware

Undantagssida för utvecklare

WebApplication.CreateBuilder initierar en ny instans av klassen WebApplicationBuilder med förkonfigurerade standardvärden. Undantagssidan för utvecklare är aktiverad i de förkonfigurerade standardvärdena. När följande kod körs i utvecklingsmiljönrenderar navigering till / en vänlig sida som visar undantaget.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

ASP.NET Core Middleware

I följande tabell visas några av de mellanprogram som ofta används med minimala API:er.

Mellanprogram Beskrivning API
autentisering Tillhandahåller autentiseringsstöd. UseAuthentication
Behörighet Tillhandahåller auktoriseringsstöd. UseAuthorization
CORS Konfigurerar resursdelning mellan ursprung. UseCors
undantagshanterare Globalt hanterar undantag som genereras av pipelinen för mellanprogram. UseExceptionHandler
vidarebefordrade rubriker Skickar vidare proxierade huvudrader till den nuvarande begäran. UseForwardedHeaders
HTTPS-omdirigering Omdirigerar alla HTTP-begäranden till HTTPS. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Mellanprogram för säkerhetsförbättringar som lägger till ett särskilt svarshuvud. UseHsts
Begäran Loggning Ger stöd för loggning av HTTP-begäranden och svar. UseHttpLogging
Tidsgränser för begäranden Ger stöd för att konfigurera tidsgränser för begäranden, global standard och per slutpunkt. UseRequestTimeouts
W3C-begärandeloggning Ger stöd för loggning av HTTP-begäranden och svar i W3C-format. UseW3CLogging
Cachelagring av Svar Ger stöd för att cachelagra svar. UseResponseCaching
komprimering av svar Ger stöd för att komprimera svar. UseResponseCompression
Session Ger stöd för hantering av användarsessioner. UseSession
Statiska Filer Ger stöd för att hantera statiska filer och katalogbläddring. UseStaticFiles, UseFileServer
WebSockets Aktiverar WebSockets-protokollet. UseWebSockets

Följande avsnitt beskriver hantering av begäranden: routning, parameterbindning och svar.

Routning

En konfigurerad WebApplication stöder Map{Verb} och MapMethods där {Verb} är en kamelstilskriven HTTP-metod som Get, Post, Put eller Delete:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" }, 
                          () => "This is an options or head request ");

app.Run();

De Delegate argument som skickas till dessa metoder kallas "routningshanterare".

Routningshanterare

Routhanterare är metoder som körs när rutten matchar. Routningshanterare kan vara ett lambda-uttryck, en lokal funktion, en instansmetod eller en statisk metod. Routningshanterare kan vara synkrona eller asynkrona.

Lambda-uttryck

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

Lokal funktion

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

Instansmetod

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}

Statisk metod

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}

Slutpunkt definierad utanför Program.cs

Minimala API:er behöver inte finnas i Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

public static class TodoEndpoints
{
    public static void Map(WebApplication app)
    {
        app.MapGet("/", async context =>
        {
            // Get all todo items
            await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
        });

        app.MapGet("/{id}", async context =>
        {
            // Get one todo item
            await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
        });
    }
}

Se även Ruttgrupper senare i den här artikeln.

Slutpunkter kan ges namn för att generera URL:er till slutpunkten. Om du använder en namngiven slutpunkt undviker du att behöva hårdkoda sökvägar i en app:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () => "Hello named route")
   .WithName("hi");

app.MapGet("/", (LinkGenerator linker) => 
        $"The link to the hello route is {linker.GetPathByName("hi", values: null)}");

app.Run();

Den föregående koden visar The link to the hello route is /hello från slutpunkten /.

NOTE: Ändpunktsnamn är skiftlägeskänsliga.

Slutpunktsnamn:

  • Måste vara globalt unikt.
  • Används som OpenAPI-åtgärds-ID när OpenAPI-stöd är aktiverat. Mer information finns i OpenAPI-.

Routningsparametrar

Routningsparametrar kan samlas in som en del av routningsmönsterdefinitionen:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{userId}/books/{bookId}", 
    (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

app.Run();

Föregående kod returnerar The user id is 3 and book id is 7 från URI-/users/3/books/7.

Routningshanteraren kan deklarera parametrarna som ska avbildas. När en begäran görs till en väg med parametrar som deklarerats för avbildning parsas parametrarna och skickas till hanteraren. Det gör det enkelt att samla in värdena på ett säkert sätt. I föregående kod är både userId och bookIdint.

Om något av routningsvärdena inte kan konverteras till en inti föregående kod genereras ett undantag. GET-begäran /users/hello/books/3 genererar följande undantag:

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

Jokertecken och fånga alla vägar

Följande catch all route returnerar Routing to hello från slutpunkten "/posts/hello":

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

Vägbegränsningar

Routningsbegränsningar begränsar matchningsbeteendet för en väg.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

app.Run();

I följande tabell visas föregående vägmallar och deras beteende:

Routningsmall Exempel på matchande URI
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Mer information finns i referens för routningsbegränsningar i Routning i ASP.NET Core.

Ruttgrupper

Tilläggsmetoden MapGroup hjälper att organisera grupper av slutpunkter med ett gemensamt prefix. Det minskar repetitiv kod och gör det möjligt att anpassa hela grupper av slutpunkter med ett enda anrop till metoder som RequireAuthorization och WithMetadata som lägger till slutpunktsmetadata.

Följande kod skapar till exempel två liknande grupper av slutpunkter:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

I det här scenariot kan du använda en relativ adress för Location-rubriken i 201 Created-resultatet.

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

Den första gruppen med slutpunkter matchar endast begäranden som är prefix med /public/todos och är tillgängliga utan autentisering. Den andra gruppen med slutpunkter matchar endast begäranden som är prefix med /private/todos och kräver autentisering.

QueryPrivateTodos slutpunktfilterfabriken är en lokal funktion som ändrar rout-hanterarens TodoDb parametrar för att tillåta åtkomst till och lagring av privata data.

Routningsgrupper stöder också kapslade grupper och komplexa prefixmönster med vägparametrar och begränsningar. I följande exempel kan routningshanteraren som mappas till den user gruppen avbilda de {org}- och {group} routningsparametrar som definierats i de yttre gruppprefixen.

Prefixet kan också vara tomt. Detta kan vara användbart för att lägga till slutpunktsmetadata eller filter till en grupp slutpunkter utan att ändra vägmönstret.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Att lägga till filter eller metadata i en grupp fungerar på samma sätt som att lägga till dem individuellt i varje slutpunkt innan du lägger till extra filter eller metadata som kan ha lagts till i en inre grupp eller en specifik slutpunkt.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

I exemplet ovan loggar det yttre filtret den inkommande begäran före det inre filtret trots att det lades till tvåa. Eftersom filtren tillämpades på olika grupper spelar det ingen roll vilken ordning de lades till i förhållande till varandra. Orderfiltren som läggs till spelar roll om de tillämpas på samma grupp eller specifika slutpunkt.

En begäran om att /outer/inner/ loggar följande:

/outer group filter
/inner group filter
MapGet filter

Parameterbindning

Parameterbindning är processen att konvertera begärandedata till starkt skrivna parametrar som uttrycks av routningshanterare. En bindningskälla avgör var parametrarna är bundna från. Bindningskällor kan vara explicita eller härledda baserat på HTTP-metod och parametertyp.

Bindningskällor som stöds:

  • Vägvärden
  • Frågesträng
  • Rubrik
  • Kropp (som JSON)
  • Formulärvärden
  • Tjänster som tillhandahålls av dependency injection
  • Anpassad

Följande GET routningshanterare använder några av dessa parameterbindningskällor:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

I följande tabell visas relationen mellan de parametrar som används i föregående exempel och de associerade bindningskällorna.

Parameter Bindningskälla
id ruttvärde
page Frågesträngen
customHeader rubrik
service Tillhandahålls av beroendeinjektion

HTTP-metoderna GET, HEAD, OPTIONSoch DELETE binder inte implicit från brödtexten. Om du vill binda från brödtexten (som JSON) för dessa HTTP-metoder du uttryckligen binda med [FromBody] eller läsa från HttpRequest.

I följande exempel använder POST-routningshanteraren en bindningskälla för brödtext (som JSON) för parametern person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

Parametrarna i föregående exempel är alla bundna från begärandedata automatiskt. För att demonstrera bekvämligheten med parameterbindningen visar följande routningshanterare hur du läser begärandedata direkt från begäran:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Explicit parameterbindning

Attribut kan användas för att explicit deklarera var parametrarna är bundna från.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Bindningskälla
id routningsvärde med namnet id
page frågesträng med namnet "p"
service Tillhandahålls av beroendeinjektion
contentType rubrik med namnet "Content-Type"

Explicit bindning från formulärvärden

Det [FromForm] attributet binder formulärvärden:

app.MapPost("/todos", async ([FromForm] string name,
    [FromForm] Visibility visibility, IFormFile? attachment, TodoDb db) =>
{
    var todo = new Todo
    {
        Name = name,
        Visibility = visibility
    };

    if (attachment is not null)
    {
        var attachmentName = Path.GetRandomFileName();

        using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
        await attachment.CopyToAsync(stream);
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Ok();
});

// Remaining code removed for brevity.

Ett alternativ är att använda attributet [AsParameters] med en anpassad typ som har egenskaper som kommenterats med [FromForm]. Följande kod binder, till exempel, från formulärvärden till egenskaper för NewTodoRequest record struct:

app.MapPost("/ap/todos", async ([AsParameters] NewTodoRequest request, TodoDb db) =>
{
    var todo = new Todo
    {
        Name = request.Name,
        Visibility = request.Visibility
    };

    if (request.Attachment is not null)
    {
        var attachmentName = Path.GetRandomFileName();

        using var stream = File.Create(Path.Combine("wwwroot", attachmentName));
        await request.Attachment.CopyToAsync(stream);

        todo.Attachment = attachmentName;
    }

    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Ok();
});

// Remaining code removed for brevity.
public record struct NewTodoRequest([FromForm] string Name,
    [FromForm] Visibility Visibility, IFormFile? Attachment);

Mer information finns i avsnittet om AsParameters senare i den här artikeln.

Den fullständiga exempelkoden finns på lagringsplatsen AspNetCore.Docs.Samples.

Säker bindning från IFormFile och IFormFileCollection

Komplex formulärbindning stöds med hjälp av IFormFile och IFormFileCollection med hjälp av [FromForm]:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

// Generate a form with an anti-forgery token and an /upload endpoint.
app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = MyUtils.GenerateHtmlForm(token.FormFieldName, token.RequestToken!);
    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>, BadRequest<string>>>
    ([FromForm] FileUploadForm fileUploadForm, HttpContext context,
                                                IAntiforgery antiforgery) =>
{
    await MyUtils.SaveFileWithName(fileUploadForm.FileDocument!,
              fileUploadForm.Name!, app.Environment.ContentRootPath);
    return TypedResults.Ok($"Your file with the description:" +
        $" {fileUploadForm.Description} has been uploaded successfully");
});

app.Run();

Parametrar som är bundna till begäran med [FromForm] innehåller ett antiförfalskningstoken. Antiforgery-token verifieras när begäran bearbetas. Mer information finns i Antiforgery med minimala API:er.

Mer information finns i Formulärbindning i minimala API:er.

Den fullständiga exempelkoden finns på lagringsplatsen AspNetCore.Docs.Samples.

Parameterbindning med beroendeinmatning

Parameterbindning för minimala API:er binder parametrar via beroendeinmatning när typen konfigureras som en tjänst. Det är inte nödvändigt att uttryckligen tillämpa attributet [FromServices] på en parameter. I följande kod returnerar båda åtgärderna tiden:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

Valfria parametrar

Parametrar som deklareras i routningshanterare behandlas efter behov:

  • Om en begäran matchar vägen körs routningshanteraren endast om alla obligatoriska parametrar anges i begäran.
  • Om du inte anger alla obligatoriska parametrar resulterar det i ett fel.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI resultat
/products?pageNumber=3 3 returnerade
/products BadHttpRequestException: Den obligatoriska parametern "int pageNumber" angavs inte från frågesträngen.
/products/1 HTTP 404-fel, ingen matchande väg

Om du vill göra pageNumber valfri definierar du typen som valfri eller anger ett standardvärde:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI resultat
/products?pageNumber=3 3 returnerade
/products 1 returnerade
/products2 1 returnerade

Föregående null- och standardvärde gäller för alla källor:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/products", (Product? product) => { });

app.Run();

Föregående kod anropar metoden med en null-produkt om ingen begärandetext skickas.

NOTE: Om ogiltiga data anges och parametern är null kan routningshanteraren inte köras.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI resultat
/products?pageNumber=3 3 har returnerats
/products 1 returnerade
/products?pageNumber=two BadHttpRequestException: Det gick inte att binda parametern "Nullable<int> pageNumber" från "två".
/products/two HTTP 404-fel, ingen matchande väg

För mer information, se avsnittet bindningsfel.

Särskilda typer

Följande typer är bundna utan explicita attribut:

  • HttpContext: Kontexten som innehåller all information om den aktuella HTTP-begäran eller -svaret:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest och HttpResponse: HTTP-begäran och HTTP-svar:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Den annulleringstoken som är associerad med den aktuella HTTP-begäran:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: Användaren som är associerad med begäran, bunden från HttpContext.User:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

Binda begärandetexten som en Stream eller PipeReader

Begärandetexten kan bindas som en Stream eller PipeReader för att effektivt stödja scenarier där användaren måste bearbeta data och:

  • Lagra data i bloblagring eller köa data till en köleverantör.
  • Bearbeta lagrade data med en arbetsprocess eller molnfunktion.

Data kan till exempel läggas i kö för lagring i Azure Queue Storage eller lagras i Azure Blob Storage.

Följande kod implementerar en bakgrundskö:

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;

class BackgroundQueue : BackgroundService
{
    private readonly Channel<ReadOnlyMemory<byte>> _queue;
    private readonly ILogger<BackgroundQueue> _logger;

    public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
                               ILogger<BackgroundQueue> logger)
    {
        _queue = queue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
                _logger.LogInformation($"{person.Name} is {person.Age} " +
                                       $"years and from {person.Country}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }
}

class Person
{
    public string Name { get; set; } = String.Empty;
    public int Age { get; set; }
    public string Country { get; set; } = String.Empty;
}

Följande kod binder begärandetexten till en Stream:

app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

Följande kod visar den fullständiga Program.cs filen:

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
                     Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));

// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();
  • När du läser data är Stream samma objekt som HttpRequest.Body.
  • Begärandetexten buffrades inte som standard. När brödtexten har lästs kan den inte spolas tillbaka. Dataströmmen kan inte läsas flera gånger.
  • Stream och PipeReader kan inte användas utanför den minimala åtgärdshanteraren eftersom de underliggande buffertarna tas bort eller återanvänds.

Filuppladdningar med IFormFile och IFormFileCollection

Följande kod använder IFormFile och IFormFileCollection för att ladda upp filen:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Autentiserade filuppladdningsbegäranden stöds med hjälp av ett auktoriseringshuvud, ett klientcertifikateller ett cookie-huvud.

Binda till formulär med IFormCollection, IFormFile och IFormFileCollection

Bindning från formulärbaserade parametrar med hjälp av IFormCollection, IFormFileoch IFormFileCollection stöds. OpenAPI- metadata härleds för formulärparametrar för att stödja integration med Swagger UI.

Följande kod laddar upp filer med hjälp av härledd bindning från typen IFormFile.

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
      <html>
        <body>
          <form action="/upload" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
            <input type="file" name="file" placeholder="Upload an image..." accept=".jpg, 
                                                                            .jpeg, .png" />
            <input type="submit" />
          </form> 
        </body>
      </html>
    """;

    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>,
   BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
    var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok("File uploaded successfully!");
});

app.Run();

Varning: När formulär implementeras måste appen förhindraXSRF-attacker (Cross-Site Request Forgery). I föregående kod används IAntiforgery-tjänsten för att förhindra XSRF-attacker genom att generera och verifiera en antiforgery-token:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();
app.UseAntiforgery();

string GetOrCreateFilePath(string fileName, string filesDirectory = "uploadFiles")
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
      <html>
        <body>
          <form action="/upload" method="POST" enctype="multipart/form-data">
            <input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
            <input type="file" name="file" placeholder="Upload an image..." accept=".jpg, 
                                                                            .jpeg, .png" />
            <input type="submit" />
          </form> 
        </body>
      </html>
    """;

    return Results.Content(html, "text/html");
});

app.MapPost("/upload", async Task<Results<Ok<string>,
   BadRequest<string>>> (IFormFile file, HttpContext context, IAntiforgery antiforgery) =>
{
    var fileSaveName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok("File uploaded successfully!");
});

app.Run();

Mer information om XSRF-attacker finns i Antiforgery med minimala API:er

Mer information finns i Formulärbindning i minimala API:er;

Binda till samlingar och komplexa typer från formulär

Stöd för bindning gäller:

  • Samlingar, till exempel List och Dictionary
  • Komplexa typer, till exempel Todo eller Project

Följande kod visar:

  • En minimal slutpunkt som binder en formulärinmatning i flera delar till ett komplext objekt.
  • Hur man använder antiforgery-tjänsterna för att stödja generering och validering av antiforgery-token.
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", (HttpContext context, IAntiforgery antiforgery) =>
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $"""
        <html><body>
           <form action="/todo" method="POST" enctype="multipart/form-data">
               <input name="{token.FormFieldName}" 
                                type="hidden" value="{token.RequestToken}" />
               <input type="text" name="name" />
               <input type="date" name="dueDate" />
               <input type="checkbox" name="isCompleted" value="true" />
               <input type="submit" />
               <input name="isCompleted" type="hidden" value="false" /> 
           </form>
        </body></html>
    """;
    return Results.Content(html, "text/html");
});

app.MapPost("/todo", async Task<Results<Ok<Todo>, BadRequest<string>>> 
               ([FromForm] Todo todo, HttpContext context, IAntiforgery antiforgery) =>
{
    try
    {
        await antiforgery.ValidateRequestAsync(context);
        return TypedResults.Ok(todo);
    }
    catch (AntiforgeryValidationException e)
    {
        return TypedResults.BadRequest("Invalid antiforgery token");
    }
});

app.Run();

class Todo
{
    public string Name { get; set; } = string.Empty;
    public bool IsCompleted { get; set; } = false;
    public DateTime DueDate { get; set; } = DateTime.Now.Add(TimeSpan.FromDays(1));
}

I föregående kod:

  • Målparametern måste kommenteras med attributet [FromForm] för att skilja sig från parametrar som ska läsas från JSON-brödtexten.
  • Bindning från komplexa typer eller samlingstyper stöds inte för minimala API:er som kompileras med generatorn för begärandedelegat.
  • Markeringen visar ytterligare dolda indata med namnet isCompleted och värdet false. Om kryssrutan isCompleted markeras när formuläret skickas skickas både värden true och false som värden. Om kryssrutan är avmarkerad skickas endast det dolda indatavärdet false. Den ASP.NET Core-modellbindningsprocessen läser bara det första värdet vid bindning till ett bool värde, vilket resulterar i true för markerade kryssrutor och false för avmarkerade kryssrutor.

Ett exempel på formulärdata som skickas till föregående slutpunkt ser ut så här:

__RequestVerificationToken: CfDJ8Bveip67DklJm5vI2PF2VOUZ594RC8kcGWpTnVV17zCLZi1yrs-CSz426ZRRrQnEJ0gybB0AD7hTU-0EGJXDU-OaJaktgAtWLIaaEWMOWCkoxYYm-9U9eLV7INSUrQ6yBHqdMEE_aJpD4AI72gYiCqc
name: Walk the dog
dueDate: 2024-04-06
isCompleted: true
isCompleted: false

Binda matriser och strängvärden från rubriker och frågesträngar

Följande kod visar bindning av frågesträngar till en matris med primitiva typer, strängmatriser och StringValues:

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

Bindning av frågesträngar eller sidhuvudvärden till en matris med komplexa typer stöds när typen har TryParse implementerats. Följande kod binder till en strängmatris och returnerar alla objekt med de angivna taggarna:

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
    return await db.Todos
        .Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
        .ToListAsync();
});

Följande kod visar modellen och den nödvändiga TryParse implementeringen:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    // This is an owned entity. 
    public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
    public string? Name { get; set; } = "n/a";

    public static bool TryParse(string? name, out Tag tag)
    {
        if (name is null)
        {
            tag = default!;
            return false;
        }

        tag = new Tag { Name = name };
        return true;
    }
}

Följande kod binder till en int matris:

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Om du vill testa koden ovan lägger du till följande slutpunkt för att fylla databasen med Todo objekt:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Använd ett verktyg som HttpRepl för att skicka följande data till föregående slutpunkt:

[
    {
        "id": 1,
        "name": "Have Breakfast",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 2,
        "name": "Have Lunch",
        "isComplete": true,
        "tag": {
            "name": "work"
        }
    },
    {
        "id": 3,
        "name": "Have Supper",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 4,
        "name": "Have Snacks",
        "isComplete": true,
        "tag": {
            "name": "N/A"
        }
    }
]

Följande kod binder till huvudnyckeln X-Todo-Id och returnerar Todo objekt med matchande Id värden:

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Anteckning

När du binder en string[] från en frågesträng resulterar frånvaron av ett matchande frågesträngsvärde i en tom matris i stället för ett null-värde.

Parameterbindning för argumentlistor med [AsParameters]

AsParametersAttribute möjliggör enkel parameterbindning till typer och inte komplex eller rekursiv modellbindning.

Överväg följande kod:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());
// Remaining code removed for brevity.

Överväg följande GET slutpunkt:

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Följande struct kan användas för att ersätta de föregående markerade parametrarna:

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Den omstrukturerade GET-slutpunkten använder föregående struct med attributet AsParameters:

app.MapGet("/ap/todoitems/{id}",
                                async ([AsParameters] TodoItemRequest request) =>
    await request.Db.Todos.FindAsync(request.Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Följande kod visar ytterligare slutpunkter i appen:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Följande klasser används för att omstrukturera parameterlistorna:

class CreateTodoItemRequest
{
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
    public int Id { get; set; }
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

Följande kod visar de refaktorerade slutpunkterna med hjälp av AsParameters samt de tidigare struct och klasser.

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
    var todoItem = new Todo
    {
        IsComplete = request.Dto.IsComplete,
        Name = request.Dto.Name
    };

    request.Db.Todos.Add(todoItem);
    await request.Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
    var todo = await request.Db.Todos.FindAsync(request.Id);

    if (todo is null) return Results.NotFound();

    todo.Name = request.Dto.Name;
    todo.IsComplete = request.Dto.IsComplete;

    await request.Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
    if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
    {
        request.Db.Todos.Remove(todo);
        await request.Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Följande record typer kan användas för att ersätta de föregående parametrarna:

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

Att använda en struct med AsParameters kan vara mer högpresterande än att använda en record typ.

Den kompletta exempelkoden i repositoryn AspNetCore.Docs.Samples.

Anpassad bindning

Det finns två sätt att anpassa parameterbindning:

  1. För routnings-, fråge- och rubrikbindningskällor binder du anpassade typer genom att lägga till en statisk TryParse metod för typen.
  2. Kontrollera bindningsprocessen genom att implementera en BindAsync-metod för en typ.

TryParse

TryParse har två API:er:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Följande kod visar Point: 12.3, 10.1 med URI-/map?Point=12.3,10.1:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync har följande API:er:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Följande kod visar SortBy:xyz, SortDirection:Desc, CurrentPage:99 med URI-/products?SortBy=xyz&SortDir=Desc&Page=99:

using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Bindningsfel

När bindningen misslyckas loggar ramverket ett felsökningsmeddelande och returnerar olika statuskoder till klienten beroende på felläget.

Felläge Parametertyp som kan vara null Bindningskälla Statuskod
{ParameterType}.TryParse returnerar false Ja rutt/förfrågan/huvud 400
{ParameterType}.BindAsync returnerar null Ja anpassad 400
{ParameterType}.BindAsync kastar spelar ingen roll anpassad 500
Det gick inte att deserialisera JSON-data spelar ingen roll kropp 400
Fel innehållstyp (inte application/json) spelar ingen roll kropp 415

Bindningsprioritet

Reglerna för att fastställa en bindningskälla från en parameter:

  1. Den explicita attribut som definieras på parametern (From*-attribut) i följande ordning:
    1. Routvärden: [FromRoute]
    2. Frågesträng: [FromQuery]
    3. Rubrik: [FromHeader]
    4. Brödtext: [FromBody]
    5. Formulär: [FromForm]
    6. Tjänst: [FromServices]
    7. Parametervärden: [AsParameters]
  2. Särskilda typer
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormCollection (HttpContext.Request.Form)
    7. IFormFileCollection (HttpContext.Request.Form.Files)
    8. IFormFile (HttpContext.Request.Form.Files[paramName])
    9. Stream (HttpContext.Request.Body)
    10. PipeReader (HttpContext.Request.BodyReader)
  3. Parametertypen har en giltig statisk BindAsync-metod.
  4. Parametertypen är en sträng eller har en giltig statisk TryParse-metod.
    1. Om parameternamnet finns i routemallen, till exempel app.Map("/todo/{id}", (int id) => {});, då binds det från routen.
    2. Bunden till frågesträngen.
  5. Om parametertypen är en tjänst som tillhandahålls av beroendeinmatning använder den tjänsten som källa.
  6. Parametern kommer från kroppen.

Konfigurera JSON-deserialiseringsalternativ för brödtextbindning

Kroppsbindningskällan använder System.Text.Json för deserialisering. Det är inte möjligt att ändra den här standardinställningen, men JSON-serialiserings- och deserialiseringsalternativ kan konfigureras.

Konfigurera JSON-deserialiseringsalternativ globalt

Alternativ som gäller globalt för en app kan konfigureras genom att anropa ConfigureHttpJsonOptions. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.

var builder = WebApplication.CreateBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/", (Todo todo) => {
    if (todo is not null) {
        todo.Name = todo.NameField;
    }
    return todo;
});

app.Run();

class Todo {
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "nameField":"Walk dog",
//    "isComplete":false
// }

Eftersom exempelkoden konfigurerar både serialisering och deserialisering kan den läsa NameField och inkludera NameField i utdata-JSON.

Konfigurera JSON-deserialiseringsalternativ för en slutpunkt

ReadFromJsonAsync har överbelastningar som accepterar ett JsonSerializerOptions-objekt. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.

using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { 
    IncludeFields = true, 
    WriteIndented = true
};

app.MapPost("/", async (HttpContext context) => {
    if (context.Request.HasJsonContentType()) {
        var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
        if (todo is not null) {
            todo.Name = todo.NameField;
        }
        return Results.Ok(todo);
    }
    else {
        return Results.BadRequest();
    }
});

app.Run();

class Todo
{
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "isComplete":false
// }

Eftersom föregående kod endast tillämpar de anpassade alternativen för deserialisering utesluter utdata-JSON NameField.

Läs begärandetexten

Läs begärandetexten direkt med hjälp av en HttpContext- eller HttpRequest-parameter:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Föregående kod:

Svaren

Routningshanterare stöder följande typer av returvärden:

  1. IResult-baserad – Detta omfattar Task<IResult> och ValueTask<IResult>
  2. string – Detta inkluderar Task<string> och ValueTask<string>
  3. T (alla andra typer) – Detta inkluderar Task<T> och ValueTask<T>
Returvärde Uppförande Innehållstyp
IResult Ramverket anropar IResult.ExecuteAsync Bestäms av implementeringen av IResult
string Ramverket skriver strängen direkt till svaret text/plain
T (alla andra typer) Ramverket JSON-serialiserar svaret application/json

En mer djupgående guide till routningshanterarens returvärden finns i Skapa svar i minimala API-program

Exempel på returvärden

strängreturvärden

app.MapGet("/hello", () => "Hello World");

JSON-returvärden

app.MapGet("/hello", () => new { Message = "Hello World" });

Returnera TypedResults

Följande kod returnerar en TypedResults:

app.MapGet("/hello", () => TypedResults.Ok(new Message() {  Text = "Hello World!" }));

Att returnera TypedResults är att föredra framför att returnera Results. Mer information finns i TypedResults vs Results.

IResult-returvärden

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

I följande exempel används de inbyggda resultattyperna för att anpassa svaret:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Anpassad statuskod

app.MapGet("/405", () => Results.StatusCode(405));

Text

app.MapGet("/text", () => Results.Text("This is some text"));

Ström

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

Se Skapa svar i Minimala API-program för fler exempel.

Omdirigera

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

Fil

app.MapGet("/download", () => Results.File("myfile.text"));

Inbyggda resultat

Vanliga resultathjälpare finns i Results- och TypedResults statiska klasser. Det är att föredra att returnera TypedResults framför att returnera Results. Mer information finns i TypedResults vs Results.

Ändra rubriker

Använd HttpResponse-objektet för att ändra svarshuvuden:

app.MapGet("/", (HttpContext context) => {
    // Set a custom header
    context.Response.Headers["X-Custom-Header"] = "CustomValue";

    // Set a known header
    context.Response.Headers.CacheControl = $"public,max-age=3600";

    return "Hello World";
});

Anpassa resultat

Program kan styra svar genom att implementera en anpassad IResult typ. Följande kod är ett exempel på en HTML-resultattyp:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

Vi rekommenderar att du lägger till en tilläggsmetod till Microsoft.AspNetCore.Http.IResultExtensions för att göra dessa anpassade resultat lättare att hitta.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Inskrivna resultat

Det IResult gränssnittet kan representera värden som returneras från minimala API:er som inte använder implicit stöd för JSON-serialisering av det returnerade objektet till HTTP-svaret. Klassen static Results används för att skapa varierande IResult objekt som representerar olika typer av svar. Du kan till exempel ange svarsstatuskoden eller omdirigera till en annan URL.

De typer som implementerar IResult är offentliga, vilket möjliggör typkontroller vid testning. Till exempel:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

Du kan titta på returtyperna för motsvarande metoder på den statiska TypedResults-klassen för att hitta rätt offentlig IResult typ att casta till.

Se Skapa svar i Minimala API-program för fler exempel.

Filter

Mer information finns i Filter i Minimal API-appar.

Tillstånd

Vägar kan skyddas med hjälp av auktoriseringsprinciper. Dessa kan deklareras via attributet [Authorize] eller med hjälp av metoden RequireAuthorization:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

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

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Föregående kod kan skrivas med RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

Följande exempel använder principbaserad auktorisering:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

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

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Tillåt oautentiserade användare att komma åt en slutpunkt

Med [AllowAnonymous] kan oautentiserade användare komma åt slutpunkter:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Rutterna kan CORS- aktiveras med CORS-policyer. CORS kan deklareras via attributet [EnableCors] eller med hjälp av metoden RequireCors. Följande exempel aktiverar CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Mer information finns i Aktivera CORS (Cross-Origin Requests) i ASP.NET Core

ValidateScopes och ValidateOnBuild

ValidateScopes och ValidateOnBuild aktiveras som standard i miljön Development men inaktiveras i andra miljöer.

När ValidateOnBuild är truevaliderar DI-containern tjänstkonfigurationen vid byggtillfället. Om tjänstkonfigurationen är ogiltig misslyckas bygget vid appstart i stället för vid körning när tjänsten begärs.

När ValidateScopes är trueverifierar DI-containern att en begränsad tjänst inte matchas från rotomfånget. Om du löser en begränsad tjänst från rotomfånget kan det leda till en minnesläcka eftersom tjänsten behålls i minnet längre än omfånget för begäran.

ValidateScopes och ValidateOnBuild är som standard falska i icke-utvecklingslägen av prestandaskäl.

Följande kod visar ValidateScopes är aktiverat som standard i utvecklingsläge men inaktiverat i versionsläge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    // Intentionally getting service provider from app, not from the request
    // This causes an exception from attempting to resolve a scoped service
    // outside of a scope.
    // Throws System.InvalidOperationException:
    // 'Cannot resolve scoped service 'MyScopedService' from root provider.'
    var service = app.Services.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved");
});

app.Run();

public class MyScopedService { }

Följande kod visar ValidateOnBuild är aktiverat som standard i utvecklingsläge men inaktiverat i versionsläge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyScopedService>();
builder.Services.AddScoped<AnotherService>();

// System.AggregateException: 'Some services are not able to be constructed (Error
// while validating the service descriptor 'ServiceType: AnotherService Lifetime:
// Scoped ImplementationType: AnotherService': Unable to resolve service for type
// 'BrokenService' while attempting to activate 'AnotherService'.)'
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
}
else
{
    Console.WriteLine("Release environment");
}

app.MapGet("/", context =>
{
    var service = context.RequestServices.GetRequiredService<MyScopedService>();
    return context.Response.WriteAsync("Service resolved correctly!");
});

app.Run();

public class MyScopedService { }

public class AnotherService
{
    public AnotherService(BrokenService brokenService) { }
}

public class BrokenService { }

Följande kod inaktiverar ValidateScopes och ValidateOnBuild i Development:

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    Console.WriteLine("Development environment");
    // Doesn't detect the validation problems because ValidateScopes is false.
    builder.Host.UseDefaultServiceProvider(options =>
    {
        options.ValidateScopes = false;
        options.ValidateOnBuild = false;
    });
}

Se även

Det här dokumentet:

De minimala API:erna består av:

WebApplication

Följande kod genereras av en ASP.NET Core-mall:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Föregående kod kan skapas via dotnet new web på kommandoraden eller genom att välja mallen Tom webb i Visual Studio.

Följande kod skapar en WebApplication (app) utan att uttryckligen skapa en WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create initierar en ny instans av klassen WebApplication med förkonfigurerade standardvärden.

WebApplication lägger automatiskt till följande mellanprogram i Minimal API applications beroende på vissa villkor:

  • UseDeveloperExceptionPage läggs till först när HostingEnvironment är "Development".
  • UseRouting läggs till som andra om användarkoden inte redan har anropat UseRouting och om det finns konfigurerade slutpunkter, till exempel app.MapGet.
  • UseEndpoints läggs till i slutet av pipelinen för mellanprogram om några slutpunkter har konfigurerats.
  • UseAuthentication läggs till omedelbart efter UseRouting om användarkoden inte redan anropade UseAuthentication och om IAuthenticationSchemeProvider kan identifieras i tjänstleverantören. IAuthenticationSchemeProvider läggs till som standard när du använder AddAuthenticationoch tjänster identifieras med hjälp av IServiceProviderIsService.
  • UseAuthorization läggs till nästa om användarkoden inte redan anropar UseAuthorization och om IAuthorizationHandlerProvider kan identifieras i tjänstleverantören. IAuthorizationHandlerProvider läggs till som standard när du använder AddAuthorizationoch tjänster identifieras med hjälp av IServiceProviderIsService.
  • Användar konfigurerade mellanprogram och slutpunkter läggs till mellan UseRouting och UseEndpoints.

Följande kod är i praktiken vad det automatiska mellanprogrammet som läggs till i appen genererar:

if (isDevelopment)
{
    app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
    app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
    app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

I vissa fall är standardkonfigurationen för mellanprogram inte korrekt för appen och kräver ändringar. Till exempel ska UseCors anropas före UseAuthentication och UseAuthorization. Appen måste anropa UseAuthentication och UseAuthorization om UseCors anropas:

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

Om mellanprogram ska köras innan routningsmatchning sker ska UseRouting anropas och mellanprogrammet placeras innan anropet till UseRouting. UseEndpoints krävs inte i det här fallet eftersom det läggs till automatiskt enligt beskrivningen ovan:

app.Use((context, next) =>
{
    return next(context);
});

app.UseRouting();

// other middleware and endpoints

När du lägger till ett terminalmellanprogram:

  • Mellanprogrammet måste läggas till efter UseEndpoints.
  • Appen måste anropa UseRouting och UseEndpoints så att terminalmellanprogrammet kan placeras på rätt plats.
app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
});

Terminalmellanprogram är mellanprogram som körs om ingen slutpunkt hanterar begäran.

Arbeta med portar

När en webbapp skapas med Visual Studio eller dotnet newskapas en Properties/launchSettings.json fil som anger de portar som appen svarar på. I portinställningsexemplen som följer returnerar körning av appen från Visual Studio en feldialogruta Unable to connect to web server 'AppName'. Visual Studio returnerar ett fel eftersom den förväntar sig att porten som anges i Properties/launchSettings.json, men appen använder den port som anges av app.Run("http://localhost:3000"). Kör följande portförändrande exempel från kommandoraden.

Följande avsnitt anger den port som appen svarar på.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

I föregående kod svarar appen på port 3000.

Flera portar

I följande kod svarar appen på port 3000 och 4000.

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Ange porten från kommandoraden

Följande kommando gör att appen svarar på port 7777:

dotnet run --urls="https://localhost:7777"

Om slutpunkten Kestrel också har konfigurerats i appsettings.json-filen, används URL:en som anges i appsettings.json-filen. Mer information finns i Kestrel slutpunktskonfiguration

Läs porten från miljövariabeln

Följande kod läser porten från miljön:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Det bästa sättet att ange porten från miljön är att använda ASPNETCORE_URLS miljövariabeln, som visas i följande avsnitt.

Ange portarna via miljövariabeln ASPNETCORE_URLS

Miljövariabeln ASPNETCORE_URLS är tillgänglig för att ange porten:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS stöder flera URL:er:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Mer information om hur du använder miljön finns i Använda flera miljöer i ASP.NET Core

Lyssna på alla gränssnitt

Följande exempel visar hur man lyssnar på alla gränssnitt

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Lyssna på alla gränssnitt med hjälp av ASPNETCORE_URLS

Föregående exempel kan använda ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Ange HTTPS med utvecklingscertifikat

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Mer information om utvecklingscertifikatet finns i Trust the ASP.NET Core HTTPS development certificate on Windows and macOS.

Ange HTTPS med ett anpassat certifikat

Följande avsnitt visar hur du anger det anpassade certifikatet med hjälp av appsettings.json-filen och via konfiguration.

Ange det anpassade certifikatet med appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Ange det anpassade certifikatet via konfiguration

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Använda certifikat-API:erna

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Konfiguration

Följande kod läser från konfigurationssystemet:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Mer information finns i Configuration i ASP.NET Core

Skogsavverkning

Följande kod skriver ett meddelande till loggen vid programstart:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Mer information finns i Loggning i .NET Core och ASP.NET Core

Få åtkomst till Dependency Injection (DI)-containern

Följande kod visar hur du hämtar tjänster från DI-containern under programstarten:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Mer information finns i Beroendeinmatning i ASP.NET Core.

WebApplicationBuilder

Det här avsnittet innehåller exempelkod med hjälp av WebApplicationBuilder.

Ändra innehållsrot, programnamn och miljö

Följande kod anger innehållsrot, programnamn och miljö:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder initierar en ny instans av klassen WebApplicationBuilder med förkonfigurerade standardvärden.

Mer information finns i översikten över grunderna i ASP.NET Core

Ändra innehållsrot, appnamn och miljö efter miljövariabler eller kommandorad

I följande tabell visas miljövariabeln och kommandoradsargumentet som används för att ändra innehållsrot, appnamn och miljö:

egenskap Miljövariabel Kommandoradsargument
Programnamn ASPNETCORE_APPLICATIONNAME --applicationName
Miljönamn ASPNETCORE_ENVIRONMENT --miljö
Innehållsrot ASPNETCORE_CONTENTROOT --contentRoot

Lägga till konfigurationsprovidrar

Följande exempel lägger till INI-konfigurationsprovidern:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Detaljerad information finns i Filkonfigurationsleverantörer i Konfiguration i ASP.NET Core.

Läs konfiguration

Som standard läser WebApplicationBuilder konfigurationen från flera källor, inklusive:

  • appSettings.json och appSettings.{environment}.json
  • Miljövariabler
  • Kommandoraden

Följande kod läser HelloKey från konfigurationen och visar värdet vid / slutpunkten. Om konfigurationsvärdet är null tilldelas "Hello" till message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

En fullständig lista över konfigurationskällor finns i Standardkonfiguration i Configuration i ASP.NET Core

Lägga till loggningsleverantörer

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Lägga till tjänster

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Anpassa IHostBuilder

Befintliga tilläggsmetoder på IHostBuilder kan nås med egenskapen Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Anpassa IWebHostBuilder

Tilläggsmetoder på IWebHostBuilder kan nås med hjälp av egenskapen WebApplicationBuilder.WebHost.

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Ändra webbroten

Som standard är webbroten relativ till innehållsroten i mappen wwwroot. Webbroten är den plats där middleware för statiska filer letar efter dessa filer. Webbroten kan ändras med WebHostOptions, kommandoraden eller med metoden UseWebRoot:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Container för anpassad beroendeinmatning (DI)

I följande exempel används Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Lägg till mellanprogram

Alla befintliga ASP.NET Core-mellanprogram kan konfigureras på WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Mer information finns i ASP.NET Core Middleware

Undantagssida för utvecklare

WebApplication.CreateBuilder initierar en ny instans av klassen WebApplicationBuilder med förkonfigurerade standardvärden. Undantagssidan för utvecklare är aktiverad i de förkonfigurerade standardvärdena. När följande kod körs i utvecklingsmiljönrenderar navigering till / en vänlig sida som visar undantaget.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

ASP.NET Core Middleware

I följande tabell visas några av de mellanprogram som ofta används med minimala API:er.

Middleware Beskrivning API
autentisering Tillhandahåller autentiseringsstöd. UseAuthentication
Auktorisering Tillhandahåller auktoriseringsstöd. UseAuthorization
CORS Konfigurerar resursdelning mellan ursprung. UseCors
undantagshanterare Hanterar globalt undantag som genereras av middleware-pipelinen. UseExceptionHandler
vidarebefordrade rubriker Skickar vidare proxy-rubriker till den aktuella begäran. UseForwardedHeaders
HTTPS-omdirigering Omdirigerar alla HTTP-begäranden till HTTPS. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Mellanprogram för säkerhetsförbättringar som lägger till ett särskilt svarshuvud. UseHsts
Begäran Loggning Ger stöd för loggning av HTTP-begäranden och svar. UseHttpLogging
Tidsgränser för förfrågningar Ger stöd för att konfigurera tidsgränser för begäranden, global standard och per slutpunkt. UseRequestTimeouts
W3C-begärandeloggning Ger stöd för loggning av HTTP-begäranden och svar i W3C-format. UseW3CLogging
Cachelagring av svar Ger stöd för att cachelagra svar. UseResponseCaching
komprimering av svar Ger stöd för att komprimera svar. UseResponseCompression
Session Ger stöd för hantering av användarsessioner. UseSession
Static Files Ger stöd för att hantera statiska filer och katalogbläddring. UseStaticFiles, UseFileServer
WebSockets Aktiverar WebSockets-protokollet. UseWebSockets

Följande avsnitt beskriver hantering av begäranden: routning, parameterbindning och svar.

Routning

En konfigurerad WebApplication stöder Map{Verb} och MapMethods där {Verb} är en HTTP-metod med camel-case som Get, Post, Put eller Delete:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" }, 
                          () => "This is an options or head request ");

app.Run();

De Delegate argument som skickas till dessa metoder kallas "routningshanterare".

Routerhanterare

Routethanterare är metoder som körs när rutten matchar. Routningshanterare kan vara ett lambda-uttryck, en lokal funktion, en instansmetod eller en statisk metod. Routningshanterare kan vara synkrona eller asynkrona.

Lambda-uttryck

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

Lokal funktion

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

Instansmetod

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}

Statisk metod

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}

Slutpunkt definierad utanför Program.cs

Minimala API:er behöver inte finnas i Program.cs.

Program.cs

using MinAPISeparateFile;

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();

TodoEndpoints.Map(app);

app.Run();

TodoEndpoints.cs

namespace MinAPISeparateFile;

public static class TodoEndpoints
{
    public static void Map(WebApplication app)
    {
        app.MapGet("/", async context =>
        {
            // Get all todo items
            await context.Response.WriteAsJsonAsync(new { Message = "All todo items" });
        });

        app.MapGet("/{id}", async context =>
        {
            // Get one todo item
            await context.Response.WriteAsJsonAsync(new { Message = "One todo item" });
        });
    }
}

Se även Ruttgrupper senare i den här artikeln.

Slutpunkter kan ges namn för att generera URL:er till slutpunkten. Om du använder en namngiven slutpunkt undviker du att behöva hårdkoda sökvägar i en app:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () => "Hello named route")
   .WithName("hi");

app.MapGet("/", (LinkGenerator linker) => 
        $"The link to the hello route is {linker.GetPathByName("hi", values: null)}");

app.Run();

Föregående kod visar The link to the hello route is /hello från slutpunkten /.

NOTE: Slutpunktsnamn är skiftlägeskänsliga.

Slutpunktsnamn:

  • Måste vara globalt unikt.
  • Används som OpenAPI-åtgärds-ID när OpenAPI-stöd är aktiverat. Mer information finns i OpenAPI-.

Routningsparametrar

Routningsparametrar kan samlas in som en del av routningsmönsterdefinitionen:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{userId}/books/{bookId}", 
    (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

app.Run();

Föregående kod returnerar The user id is 3 and book id is 7 från URI-/users/3/books/7.

Routningshanteraren kan deklarera parametrarna som ska avbildas. När en begäran görs till en väg med parametrar som deklarerats för avbildning parsas parametrarna och skickas till hanteraren. Det gör det enkelt att samla in värdena på ett säkert sätt. I föregående kod är både userId och bookIdint.

Om något av routningsvärdena inte kan konverteras till en inti föregående kod genereras ett undantag. GET-begäran /users/hello/books/3 genererar följande undantag:

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

Jokertecken och fånga alla vägar

Följande catch all route returnerar Routing to hello från slutpunkten "/posts/hello":

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

Vägbegränsningar

Routningsbegränsningar begränsar matchningsbeteendet för en väg.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

app.Run();

I följande tabell visas föregående vägmallar och deras beteende:

Routningsmall Exempel på matchande URI
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Mer information finns i referens för routningsbegränsningar i Routning i ASP.NET Core.

Ruttgrupper

Metodtillägget MapGroup hjälper till att organisera grupper av slutpunkter med ett gemensamt prefix. Det minskar repetitiv kod och gör det möjligt att anpassa hela grupper av slutpunkter med ett enda anrop till metoder som RequireAuthorization och WithMetadata som lägger till slutpunktsmetadata.

Följande kod skapar till exempel två liknande grupper av slutpunkter:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

I det här scenariot kan du använda en relativ adress för Location-huvudet i 201 Created-resultatet.

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

Den första gruppen med slutpunkter matchar endast begäranden som är prefix med /public/todos och är tillgängliga utan autentisering. Den andra gruppen med slutpunkter matchar endast begäranden som är prefix med /private/todos och kräver autentisering.

QueryPrivateTodos slutpunktsfilterfabriken är en lokal funktion som ändrar routningshanterarens TodoDb parametrar för att tillåta åtkomst till och lagra privata data.

Routningsgrupper stöder också kapslade grupper och komplexa prefixmönster med vägparametrar och begränsningar. I följande exempel kan en routinghanterare som mappas till user-gruppen fånga upp {org}- och {group}-rutparametrarna som definierats i de yttre gruppprefixen.

Prefixet kan också vara tomt. Detta kan vara användbart för att lägga till slutpunktsmetadata eller filter till en grupp slutpunkter utan att ändra vägmönstret.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

Att lägga till filter eller metadata i en grupp fungerar på samma sätt som att lägga till dem individuellt i varje slutpunkt innan du lägger till extra filter eller metadata som kan ha lagts till i en inre grupp eller en specifik slutpunkt.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

I exemplet ovan loggar det yttre filtret den inkommande begäran före det inre filtret trots att det lades till tvåa. Eftersom filtren tillämpades på olika grupper spelar det ingen roll vilken ordning de lades till i förhållande till varandra. Orderfiltren som läggs till spelar roll om de tillämpas på samma grupp eller specifika slutpunkt.

En begäran om att /outer/inner/ loggar följande:

/outer group filter
/inner group filter
MapGet filter

Parameterbindning

Parameterbindning är processen att konvertera begärandedata till starkt skrivna parametrar som uttrycks av routningshanterare. En bindningskälla avgör var parametrarna är bundna från. Bindningskällor kan vara explicita eller härledda baserat på HTTP-metod och parametertyp.

Bindningskällor som stöds:

  • Vägvärden
  • Frågesträngen
  • Rubrik
  • Body (som JSON)
  • Tjänster som tillhandahålls av beroendeinjektion
  • Anpassad

Bindning från formulärvärden är inte stöds internt i .NET 6 och 7.

Följande GET routningshanterare använder några av dessa parameterbindningskällor:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

I följande tabell visas relationen mellan de parametrar som används i föregående exempel och de associerade bindningskällorna.

Parameter Bindningskälla
id routvärde
page Frågesträngen
customHeader rubrik
service Tillhandahålls genom dependency injection

HTTP-metoderna GET, HEAD, OPTIONSoch DELETE binder inte implicit från brödtexten. Om du vill binda från brödtexten (som JSON) för dessa HTTP-metoder du uttryckligen binda med [FromBody] eller läsa från HttpRequest.

I följande exempel använder POST-routningshanteraren en bindningskälla för brödtext (som JSON) för parametern person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

Parametrarna i föregående exempel är alla bundna från begärandedata automatiskt. För att demonstrera bekvämligheten med parameterbindningen visar följande routningshanterare hur du läser begärandedata direkt från begäran:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Explicit parameterbindning

Attribut kan användas för att explicit deklarera var parametrarna är bundna från.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Bindningskälla
id routningsvärde med namnet id
page frågesträng med namnet "p"
service Tillhandahålls av beroendeinmatning
contentType rubrik med namnet "Content-Type"

Anteckning

Bindning från formulärvärden är inte stöds internt i .NET 6 och 7.

Parameterbindning med beroendeinmatning

Parameterbindning för minimala API:er binder parametrar via beroendeinmatning när typen konfigureras som en tjänst. Det är inte nödvändigt att uttryckligen tillämpa attributet [FromServices] på en parameter. I följande kod returnerar båda åtgärderna tiden:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

Valfria parametrar

Parametrar som deklareras i routningshanterare behandlas efter behov:

  • Om en begäran matchar vägen körs routningshanteraren endast om alla obligatoriska parametrar anges i begäran.
  • Om du inte anger alla obligatoriska parametrar resulterar det i ett fel.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI resultat
/products?pageNumber=3 3 returnerade
/products BadHttpRequestException: Den obligatoriska parametern "int pageNumber" angavs inte från frågesträngen.
/products/1 HTTP 404-fel, ingen matchande väg

Om du vill göra pageNumber valfri definierar du typen som valfri eller anger ett standardvärde:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI resultat
/products?pageNumber=3 3 returnerade
/products 1 returnerade
/products2 1 returnerade

Föregående null- och standardvärde gäller för alla källor:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/products", (Product? product) => { });

app.Run();

Föregående kod anropar metoden med en null-produkt om ingen begärandetext skickas.

NOTE: Om ogiltiga data anges och parametern är nullable, körs routningshanteraren inte.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI resultat
/products?pageNumber=3 3 har returnerats
/products 1 returneras
/products?pageNumber=two BadHttpRequestException: Det gick inte att binda parametern "Nullable<int> pageNumber" från "två".
/products/two HTTP 404-fel, ingen matchande väg

Se avsnittet Bindningsfel för mer information.

Särskilda typer

Följande typer är bundna utan explicita attribut:

  • HttpContext: Kontexten som innehåller all information om den aktuella HTTP-begäran eller -svaret:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest och HttpResponse: HTTP-begäran och HTTP-svar:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Den annulleringstoken som är associerad med den aktuella HTTP-begäran:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: Användaren som är associerad med begäran, bunden från HttpContext.User:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

Binda begärandetexten som en Stream eller PipeReader

Begärandetexten kan bindas som en Stream eller PipeReader för att effektivt stödja scenarier där användaren måste bearbeta data och:

  • Lagra data i bloblagring eller lägg data i en köleverantör.
  • Bearbeta lagrade data med en arbetsprocess eller molnfunktion.

Data kan till exempel läggas i kö till Azure Queue Storage eller lagras i Azure Blob Storage.

Följande kod implementerar en bakgrundskö:

using System.Text.Json;
using System.Threading.Channels;

namespace BackgroundQueueService;

class BackgroundQueue : BackgroundService
{
    private readonly Channel<ReadOnlyMemory<byte>> _queue;
    private readonly ILogger<BackgroundQueue> _logger;

    public BackgroundQueue(Channel<ReadOnlyMemory<byte>> queue,
                               ILogger<BackgroundQueue> logger)
    {
        _queue = queue;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var dataStream in _queue.Reader.ReadAllAsync(stoppingToken))
        {
            try
            {
                var person = JsonSerializer.Deserialize<Person>(dataStream.Span)!;
                _logger.LogInformation($"{person.Name} is {person.Age} " +
                                       $"years and from {person.Country}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }
    }
}

class Person
{
    public string Name { get; set; } = String.Empty;
    public int Age { get; set; }
    public string Country { get; set; } = String.Empty;
}

Följande kod binder begärandetexten till en Stream:

app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

Följande kod visar den fullständiga Program.cs filen:

using System.Threading.Channels;
using BackgroundQueueService;

var builder = WebApplication.CreateBuilder(args);
// The max memory to use for the upload endpoint on this instance.
var maxMemory = 500 * 1024 * 1024;

// The max size of a single message, staying below the default LOH size of 85K.
var maxMessageSize = 80 * 1024;

// The max size of the queue based on those restrictions
var maxQueueSize = maxMemory / maxMessageSize;

// Create a channel to send data to the background queue.
builder.Services.AddSingleton<Channel<ReadOnlyMemory<byte>>>((_) =>
                     Channel.CreateBounded<ReadOnlyMemory<byte>>(maxQueueSize));

// Create a background queue service.
builder.Services.AddHostedService<BackgroundQueue>();
var app = builder.Build();

// curl --request POST 'https://localhost:<port>/register' --header 'Content-Type: application/json' --data-raw '{ "Name":"Samson", "Age": 23, "Country":"Nigeria" }'
// curl --request POST "https://localhost:<port>/register" --header "Content-Type: application/json" --data-raw "{ \"Name\":\"Samson\", \"Age\": 23, \"Country\":\"Nigeria\" }"
app.MapPost("/register", async (HttpRequest req, Stream body,
                                 Channel<ReadOnlyMemory<byte>> queue) =>
{
    if (req.ContentLength is not null && req.ContentLength > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // We're not above the message size and we have a content length, or
    // we're a chunked request and we're going to read up to the maxMessageSize + 1. 
    // We add one to the message size so that we can detect when a chunked request body
    // is bigger than our configured max.
    var readSize = (int?)req.ContentLength ?? (maxMessageSize + 1);

    var buffer = new byte[readSize];

    // Read at least that many bytes from the body.
    var read = await body.ReadAtLeastAsync(buffer, readSize, throwOnEndOfStream: false);

    // We read more than the max, so this is a bad request.
    if (read > maxMessageSize)
    {
        return Results.BadRequest();
    }

    // Attempt to send the buffer to the background queue.
    if (queue.Writer.TryWrite(buffer.AsMemory(0..read)))
    {
        return Results.Accepted();
    }

    // We couldn't accept the message since we're overloaded.
    return Results.StatusCode(StatusCodes.Status429TooManyRequests);
});

app.Run();
  • När du läser data är Stream samma objekt som HttpRequest.Body.
  • Begärandetexten buffrades inte som standard. När brödtexten har lästs kan den inte spolas tillbaka. Dataströmmen kan inte läsas flera gånger.
  • Stream och PipeReader kan inte användas utanför den minimala åtgärdshanteraren eftersom de underliggande buffertarna tas bort eller återanvänds.

Filuppladdningar med IFormFile och IFormFileCollection

Följande kod använder IFormFile och IFormFileCollection för att ladda upp filen:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Autentiserade filuppladdningsbegäranden stöds med hjälp av ett auktoriseringshuvud, ett klientcertifikateller ett cookie-huvud.

Det finns inget inbyggt stöd för antiforgery i ASP.NET Core 7.0. Antiforgery finns i ASP.NET Core 8.0 och senare. Den kan dock implementeras med hjälp av IAntiforgery-tjänsten.

Binda matriser och strängvärden från rubriker och frågesträngar

Följande kod visar bindning av frågesträngar till en matris med primitiva typer, strängmatriser och StringValues:

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

Bindning av frågesträngar eller sidhuvudvärden till en matris med komplexa typer stöds när typen har TryParse implementerats. Följande kod binder till en strängmatris och returnerar alla objekt med de angivna taggarna:

// GET /todoitems/tags?tags=home&tags=work
app.MapGet("/todoitems/tags", async (Tag[] tags, TodoDb db) =>
{
    return await db.Todos
        .Where(t => tags.Select(i => i.Name).Contains(t.Tag.Name))
        .ToListAsync();
});

Följande kod visar modellen och den nödvändiga TryParse implementeringen:

public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    // This is an owned entity. 
    public Tag Tag { get; set; } = new();
}

[Owned]
public class Tag
{
    public string? Name { get; set; } = "n/a";

    public static bool TryParse(string? name, out Tag tag)
    {
        if (name is null)
        {
            tag = default!;
            return false;
        }

        tag = new Tag { Name = name };
        return true;
    }
}

Följande kod binder till en int matris:

// GET /todoitems/query-string-ids?ids=1&ids=3
app.MapGet("/todoitems/query-string-ids", async (int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Om du vill testa koden ovan lägger du till följande slutpunkt för att fylla databasen med Todo objekt:

// POST /todoitems/batch
app.MapPost("/todoitems/batch", async (Todo[] todos, TodoDb db) =>
{
    await db.Todos.AddRangeAsync(todos);
    await db.SaveChangesAsync();

    return Results.Ok(todos);
});

Använd ett API-testverktyg som HttpRepl för att skicka följande data till föregående slutpunkt:

[
    {
        "id": 1,
        "name": "Have Breakfast",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 2,
        "name": "Have Lunch",
        "isComplete": true,
        "tag": {
            "name": "work"
        }
    },
    {
        "id": 3,
        "name": "Have Supper",
        "isComplete": true,
        "tag": {
            "name": "home"
        }
    },
    {
        "id": 4,
        "name": "Have Snacks",
        "isComplete": true,
        "tag": {
            "name": "N/A"
        }
    }
]

Följande kod binder till huvudnyckeln X-Todo-Id och returnerar Todo objekt med matchande Id värden:

// GET /todoitems/header-ids
// The keys of the headers should all be X-Todo-Id with different values
app.MapGet("/todoitems/header-ids", async ([FromHeader(Name = "X-Todo-Id")] int[] ids, TodoDb db) =>
{
    return await db.Todos
        .Where(t => ids.Contains(t.Id))
        .ToListAsync();
});

Not

När du binder en string[] från en frågesträng resulterar frånvaron av ett matchande frågesträngsvärde i en tom matris i stället för ett null-värde.

Parameterbindning för argumentlistor med [AsParameters]

AsParametersAttribute möjliggör enkel parameterbindning till typer och inte komplex eller rekursiv modellbindning.

Överväg följande kod:

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());
// Remaining code removed for brevity.

Överväg följande GET slutpunkt:

app.MapGet("/todoitems/{id}",
                             async (int Id, TodoDb Db) =>
    await Db.Todos.FindAsync(Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Följande struct kan användas för att ersätta de föregående markerade parametrarna:

struct TodoItemRequest
{
    public int Id { get; set; }
    public TodoDb Db { get; set; }
}

Den omstrukturerade GET-slutpunkten använder föregående struct med attributet AsParameters:

app.MapGet("/ap/todoitems/{id}",
                                async ([AsParameters] TodoItemRequest request) =>
    await request.Db.Todos.FindAsync(request.Id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

Följande kod visar ytterligare slutpunkter i appen:

app.MapPost("/todoitems", async (TodoItemDTO Dto, TodoDb Db) =>
{
    var todoItem = new Todo
    {
        IsComplete = Dto.IsComplete,
        Name = Dto.Name
    };

    Db.Todos.Add(todoItem);
    await Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int Id, TodoItemDTO Dto, TodoDb Db) =>
{
    var todo = await Db.Todos.FindAsync(Id);

    if (todo is null) return Results.NotFound();

    todo.Name = Dto.Name;
    todo.IsComplete = Dto.IsComplete;

    await Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int Id, TodoDb Db) =>
{
    if (await Db.Todos.FindAsync(Id) is Todo todo)
    {
        Db.Todos.Remove(todo);
        await Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Följande klasser används för att omstrukturera parameterlistorna:

class CreateTodoItemRequest
{
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

class EditTodoItemRequest
{
    public int Id { get; set; }
    public TodoItemDTO Dto { get; set; } = default!;
    public TodoDb Db { get; set; } = default!;
}

Följande kod visar de omstrukturerade slutpunkterna med hjälp av AsParameters och den föregående struct samt klasser:

app.MapPost("/ap/todoitems", async ([AsParameters] CreateTodoItemRequest request) =>
{
    var todoItem = new Todo
    {
        IsComplete = request.Dto.IsComplete,
        Name = request.Dto.Name
    };

    request.Db.Todos.Add(todoItem);
    await request.Db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/ap/todoitems/{id}", async ([AsParameters] EditTodoItemRequest request) =>
{
    var todo = await request.Db.Todos.FindAsync(request.Id);

    if (todo is null) return Results.NotFound();

    todo.Name = request.Dto.Name;
    todo.IsComplete = request.Dto.IsComplete;

    await request.Db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/ap/todoitems/{id}", async ([AsParameters] TodoItemRequest request) =>
{
    if (await request.Db.Todos.FindAsync(request.Id) is Todo todo)
    {
        request.Db.Todos.Remove(todo);
        await request.Db.SaveChangesAsync();
        return Results.Ok(new TodoItemDTO(todo));
    }

    return Results.NotFound();
});

Följande record typer kan användas för att ersätta de föregående parametrarna:

record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);

Att använda en struct med AsParameters kan vara mer högpresterande än att använda en record typ.

Den fullständiga exempelkoden i lagringsplatsen AspNetCore.Docs.Samples.

Anpassad bindning

Det finns två sätt att anpassa parameterbindning:

  1. För routnings-, fråge- och rubrikbindningskällor binder du anpassade typer genom att lägga till en statisk TryParse metod för typen.
  2. Kontrollera bindningsprocessen genom att implementera en BindAsync-metod för en typ.

TryParse

TryParse har två API:er:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Följande kod visar Point: 12.3, 10.1 med URI-/map?Point=12.3,10.1:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync har följande API:er:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Följande kod visar SortBy:xyz, SortDirection:Desc, CurrentPage:99 med URI-/products?SortBy=xyz&SortDir=Desc&Page=99:

using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Bindningsfel

När bindningen misslyckas loggar ramverket ett felsökningsmeddelande och returnerar olika statuskoder till klienten beroende på felläget.

Felläge Parametertyp som kan vara null Bindningskälla Statuskod
{ParameterType}.TryParse returnerar false Ja rutt/förfrågan/rubrik 400
{ParameterType}.BindAsync returnerar null Ja anpassad 400
{ParameterType}.BindAsync kastar spelar ingen roll anpassad 500
Det gick inte att deserialisera JSON-innehåll spelar ingen roll kropp 400
Fel innehållstyp (inte application/json) spelar ingen roll kropp 415

Bindningsprioritet

Reglerna för att fastställa en bindningskälla från en parameter:

  1. Explicit attribut definierat för parametern (From*-attribut) i följande ordning:
    1. Ruttvärden: [FromRoute]
    2. Frågesträng: [FromQuery]
    3. Rubrik: [FromHeader]
    4. Brödtext: [FromBody]
    5. Tjänst: [FromServices]
    6. Parametervärden: [AsParameters]
  2. Särskilda typer
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
    6. IFormFileCollection (HttpContext.Request.Form.Files)
    7. IFormFile (HttpContext.Request.Form.Files[paramName])
    8. Stream (HttpContext.Request.Body)
    9. PipeReader (HttpContext.Request.BodyReader)
  3. Parametertypen har en giltig statisk BindAsync-metod.
  4. Parametertypen är en sträng eller har en giltig statisk TryParse-metod.
    1. Om parameternamnet finns i vägmallen. På app.Map("/todo/{id}", (int id) => {});är id bunden från rutten.
    2. Bunden från frågesträngen.
  5. Om parametertypen är en tjänst som tillhandahålls av beroendeinmatning använder den tjänsten som källa.
  6. Parametern är från kroppen.

Konfigurera JSON-deserialiseringsalternativ för brödtextbindning

Kroppsbindningskällan använder System.Text.Json för deserialisering. Det är inte möjligt att ändra den här standardinställningen, men JSON-serialiserings- och deserialiseringsalternativ kan konfigureras.

Konfigurera JSON-deserialiseringsalternativ globalt

Alternativ som gäller globalt för en app kan konfigureras genom att anropa ConfigureHttpJsonOptions. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.

var builder = WebApplication.CreateBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/", (Todo todo) => {
    if (todo is not null) {
        todo.Name = todo.NameField;
    }
    return todo;
});

app.Run();

class Todo {
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "nameField":"Walk dog",
//    "isComplete":false
// }

Eftersom exempelkoden konfigurerar både serialisering och deserialisering kan den läsa NameField och inkludera NameField i utdata-JSON.

Konfigurera JSON-deserialiseringsalternativ för en slutpunkt

ReadFromJsonAsync har överlagringar som accepterar ett JsonSerializerOptions-objekt. Följande exempel innehåller offentliga fält och formaterar JSON-utdata.

using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { 
    IncludeFields = true, 
    WriteIndented = true
};

app.MapPost("/", async (HttpContext context) => {
    if (context.Request.HasJsonContentType()) {
        var todo = await context.Request.ReadFromJsonAsync<Todo>(options);
        if (todo is not null) {
            todo.Name = todo.NameField;
        }
        return Results.Ok(todo);
    }
    else {
        return Results.BadRequest();
    }
});

app.Run();

class Todo
{
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}
// If the request body contains the following JSON:
//
// {"nameField":"Walk dog", "isComplete":false}
//
// The endpoint returns the following JSON:
//
// {
//    "name":"Walk dog",
//    "isComplete":false
// }

Eftersom föregående kod endast tillämpar de anpassade alternativen för deserialisering utesluter utdata-JSON NameField.

Läs begärandetexten

Läs begärandetexten direkt med hjälp av en HttpContext- eller HttpRequest-parameter:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Föregående kod:

  • Använder begärandetexten med hjälp av HttpRequest.BodyReader.
  • Kopierar begärandetexten till en lokal fil.

Svaren

Routningshanterare stöder följande typer av returvärden:

  1. IResult baserat – Detta omfattar Task<IResult> och ValueTask<IResult>
  2. string – Detta inkluderar Task<string> och ValueTask<string>
  3. T (alla andra typer) – Detta inkluderar Task<T> och ValueTask<T>
Returvärde Uppförande Innehållstyp
IResult Ramverket anropar IResult.ExecuteAsync Bestäms av IResult-implementeringen
string Ramverket skriver strängen direkt till svaret text/plain
T (alla andra typer) Ramverket JSON-serialiserar svaret application/json

En mer djupgående guide till routningshanterarens returvärden finns i Skapa svar i minimala API-program

Exempel på returvärden

strängreturvärden

app.MapGet("/hello", () => "Hello World");

JSON-returvärden

app.MapGet("/hello", () => new { Message = "Hello World" });

Returnera TypedResults

Följande kod returnerar en TypedResults:

app.MapGet("/hello", () => TypedResults.Ok(new Message() {  Text = "Hello World!" }));

Det är att föredra att returnera TypedResults framför att returnera Results. Mer information finns i TypedResults vs Results.

IResult-returvärden

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

I följande exempel används de inbyggda resultattyperna för att anpassa svaret:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);

JSON

app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));

Anpassad statuskod

app.MapGet("/405", () => Results.StatusCode(405));

Text

app.MapGet("/text", () => Results.Text("This is some text"));

Strömning

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

Se Skapa svar i Minimala API-program för fler exempel.

Omdirigera

app.MapGet("/old-path", () => Results.Redirect("/new-path"));

Fil

app.MapGet("/download", () => Results.File("myfile.text"));

Inbyggda resultat

Vanliga resultathjälpare finns i Results- och TypedResults statiska klasser. Att returnera TypedResults föredras framför att returnera Results. Mer information finns i TypedResults vs Results.

Anpassa resultat

Program kan styra svar genom att implementera en anpassad IResult typ. Följande kod är ett exempel på en HTML-resultattyp:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

Vi rekommenderar att du lägger till en tilläggsmetod till Microsoft.AspNetCore.Http.IResultExtensions för att göra dessa anpassade resultat mer lättupptäckta.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Inskrivna resultat

Det IResult gränssnittet kan representera värden som returneras från minimala API:er som inte använder implicit stöd för JSON-serialisering av det returnerade objektet till HTTP-svaret. Klassen static Results används för att skapa varierande IResult objekt som representerar olika typer av svar. Du kan till exempel ange svarsstatuskoden eller omdirigera till en annan URL.

De typer som implementerar IResult är offentliga, vilket möjliggör typkontroller vid testning. Till exempel:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

Du kan titta på returtyperna för motsvarande metoder på den statiska TypedResults-klassen för att hitta rätt offentlig IResult typ att casta till.

Se Skapa svar i Minimala API-program för fler exempel.

Filter

Se filter i minimala API-appar

Tillstånd

Vägar kan skyddas med hjälp av auktoriseringsprinciper. Dessa kan deklareras via attributet [Authorize] eller med hjälp av metoden RequireAuthorization:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

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

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Föregående kod kan skrivas med RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

Följande exempel använder principbaserad auktorisering:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

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

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Tillåt oautentiserade användare att komma åt en slutpunkt

Med [AllowAnonymous] kan oautentiserade användare komma åt slutpunkter:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Vägar kan CORS- aktiveras med CORS-principer. CORS kan deklareras via attributet [EnableCors] eller med hjälp av metoden RequireCors. Följande exempel aktiverar CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Mer information finns i Aktivera CORS (Cross-Origin Requests) i ASP.NET Core

Se även

Det här dokumentet:

De minimala API:erna består av:

WebApplication

Följande kod genereras av en ASP.NET Core-mall:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Föregående kod kan skapas via dotnet new web på kommandoraden eller genom att välja mallen Tom webb i Visual Studio.

Följande kod skapar en WebApplication (app) utan att uttryckligen skapa en WebApplicationBuilder:

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create initierar en ny instans av klassen WebApplication med förkonfigurerade standardvärden.

Arbeta med portar

När en webbapp skapas med Visual Studio eller dotnet newskapas en Properties/launchSettings.json fil som anger de portar som appen svarar på. I portinställningsexemplen som följer returnerar körning av appen från Visual Studio en feldialogruta Unable to connect to web server 'AppName'. Kör följande portförändrande exempel från kommandoraden.

Följande avsnitt anger den port som appen svarar på.

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

I föregående kod svarar appen på port 3000.

Flera portar

I följande kod svarar appen på port 3000 och 4000.

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Ange porten från kommandoraden

Följande kommando gör att appen svarar på port 7777:

dotnet run --urls="https://localhost:7777"

Om slutpunkten Kestrel också har konfigurerats i appsettings.json-filen, används den URL som anges av appsettings.json. Mer information finns i Kestrel slutpunktskonfiguration

Läsa porten från miljön

Följande kod läser porten från miljön:

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

Det bästa sättet att ange porten från miljön är att använda ASPNETCORE_URLS miljövariabeln, som visas i följande avsnitt.

Ange portarna via miljövariabeln ASPNETCORE_URLS

Miljövariabeln ASPNETCORE_URLS är tillgänglig för att ange porten:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS stöder flera URL:er:

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Lyssna på alla gränssnitt

Följande exempel visar att du lyssnar på alla gränssnitt

http://*:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Lyssna på alla gränssnitt med hjälp av ASPNETCORE_URLS

Föregående exempel kan använda ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Ange HTTPS med utvecklingscertifikat

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Mer information om utvecklingscertifikatet finns i Trust the ASP.NET Core HTTPS development certificate on Windows and macOS.

Ange HTTPS med ett anpassat certifikat

Följande avsnitt visar hur du anger det anpassade certifikatet med hjälp av appsettings.json-filen och via konfiguration.

Ange det anpassade certifikatet med appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "cert.pem",
        "KeyPath": "key.pem"
      }
    }
  }
}

Ange det anpassade certifikatet via konfiguration

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key
builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Använda certifikat-API:erna

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        var certPath = Path.Combine(builder.Environment.ContentRootPath, "cert.pem");
        var keyPath = Path.Combine(builder.Environment.ContentRootPath, "key.pem");

        httpsOptions.ServerCertificate = X509Certificate2.CreateFromPemFile(certPath, 
                                         keyPath);
    });
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Uppfatta miljön

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");
app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Mer information om hur du använder miljön finns i Använda flera miljöer i ASP.NET Core

Konfiguration

Följande kod läser från konfigurationssystemet:

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Hello";

app.MapGet("/", () => message);

app.Run();

Mer information finns i Configuration i ASP.NET Core

Skogsavverkning

Följande kod skriver ett meddelande till loggen vid programstart:

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Mer information finns i Loggning i .NET Core och ASP.NET Core

Få åtkomst till beroendeinjektionscontainern (DI)

Följande kod visar hur du hämtar tjänster från DI-containern under programstarten:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())
{
    var sampleService = scope.ServiceProvider.GetRequiredService<SampleService>();
    sampleService.DoSomething();
}

app.Run();

Mer information finns i Beroendeinmatning i ASP.NET Core.

WebApplicationBuilder

Det här avsnittet innehåller exempelkod med hjälp av WebApplicationBuilder.

Ändra innehållsrot, programnamn och miljö

Följande kod anger innehållsrot, programnamn och miljö:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    ApplicationName = typeof(Program).Assembly.FullName,
    ContentRootPath = Directory.GetCurrentDirectory(),
    EnvironmentName = Environments.Staging,
    WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder initierar en ny instans av klassen WebApplicationBuilder med förkonfigurerade standardvärden.

Mer information finns i översikten över grunderna i ASP.NET Core

Ändra innehållsrot, appnamn och miljö efter miljövariabler eller kommandorad

I följande tabell visas miljövariabeln och kommandoradsargumentet som används för att ändra innehållsrot, appnamn och miljö:

egenskap Miljövariabel Kommandoradsargument
Programnamn ASPNETCORE_APPLICATIONNAME --applicationName
Miljönamn ASPNETCORE_ENVIRONMENT --miljö
Innehållsrot ASPNETCORE_CONTENTROOT --contentRoot

Lägga till konfigurationsprovidrar

Följande exempel lägger till INI-konfigurationsprovidern:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();

Detaljerad information finns i Filkonfigurationsproviders i Configuration i ASP.NET Core.

Läs konfiguration

Som standard läser WebApplicationBuilder konfigurationen från flera källor, inklusive:

  • appSettings.json och appSettings.{environment}.json
  • Miljövariabler
  • Kommandoraden

En fullständig lista över konfigurationskällor finns i Standardkonfiguration i Configuration i ASP.NET Core

Följande kod läser HelloKey från konfigurationen och visar värdet vid / slutpunkten. Om konfigurationsvärdet är null tilldelas "Hello" till message:

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Analysera miljön

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Lägg till loggningstjänster

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.
builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Lägga till tjänster

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.
builder.Services.AddMemoryCache();

// Add a custom scoped service.
builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Anpassa IHostBuilder

Befintliga tilläggsmetoder på IHostBuilder kan nås med egenskapen Host:

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.
builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Anpassa IWebHostBuilder

Tilläggsmetoder på IWebHostBuilder kan nås med hjälp av egenskapen WebApplicationBuilder.WebHost.

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based
builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Ändra webbplatsroten

Som standard är webbroten relativ till innehållsroten i mappen wwwroot. Webbroten är platsen där mellanprogrammet för statiska filer letar efter statiska filer. Webbroten kan ändras med WebHostOptions, kommandoraden eller med metoden UseWebRoot:

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    Args = args,
    // Look for static files in webroot
    WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Container för anpassad beroendeinjektion (DI)

I följande exempel används Autofac:

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Lägg till mellanprogram

Alla befintliga ASP.NET Core-mellanprogram kan konfigureras på WebApplication:

var app = WebApplication.Create(args);

// Setup the file server to serve static files.
app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Mer information finns i ASP.NET Core Middleware

Undantagssida för utvecklare

WebApplication.CreateBuilder initierar en ny instans av klassen WebApplicationBuilder med förkonfigurerade standardvärden. Undantagssidan för utvecklare är aktiverad i de förkonfigurerade standardvärdena. När följande kod körs i utvecklingsmiljön, resulterar navigering till / i en vänlig sida som visar undantaget.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
    throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});

app.Run();

ASP.NET Core Middleware

I följande tabell visas några av de mellanprogram som ofta används med minimala API:er.

Mellanprogram Beskrivning API
autentisering Tillhandahåller autentiseringsstöd. UseAuthentication
Auktorisering Tillhandahåller auktoriseringsstöd. UseAuthorization
CORS Konfigurerar resursdelning mellan ursprung. UseCors
undantagshanterare Globalt hanterar undantag som genereras av pipelinen för mellanprogram. UseExceptionHandler
vidarebefordrade rubriker Vidarebefordrar proxyhuvuden till den aktuella begäran. UseForwardedHeaders
HTTPS-omdirigering Omdirigerar alla HTTP-begäranden till HTTPS. UseHttpsRedirection
HTTP Strict Transport Security (HSTS) Mellanprogramvara för säkerhetsförbättringar som lägger till ett särskilt svarshuvud. UseHsts
Begärandeloggning Ger stöd för loggning av HTTP-begäranden och svar. UseHttpLogging
W3C-begärandeloggning Ger stöd för loggning av HTTP-begäranden och svar i W3C-format. UseW3CLogging
cachelagring av svar Ger stöd för cachning av svar. UseResponseCaching
komprimering av svar Ger stöd för att komprimera svar. UseResponseCompression
session Ger stöd för hantering av användarsessioner. UseSession
Statiska filer Ger stöd för att hantera statiska filer och katalogbläddring. UseStaticFiles, UseFileServer
WebSockets Aktiverar WebSockets-protokollet. UseWebSockets

Hantering av begäranden

Följande avsnitt beskriver routning, parameterbindning och svar.

Routning

En konfigurerad WebApplication stöder Map{Verb} och MapMethods:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

app.MapMethods("/options-or-head", new[] { "OPTIONS", "HEAD" }, 
                          () => "This is an options or head request ");

app.Run();

Routningshanterare

Rutt-hanterare är metoder som körs när rutten matchar. Routhanterare kan vara en funktion av vilken form som helst, inklusive synkrona eller asynkrona. Routningshanterare kan vara ett lambda-uttryck, en lokal funktion, en instansmetod eller en statisk metod.

Lambda-uttryck

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/inline", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler);

app.Run();

Lokal funktion

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

string LocalFunction() => "This is local function";

app.MapGet("/", LocalFunction);

app.Run();

Instansmetod

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

app.Run();

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}

Statisk metod

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", HelloHandler.Hello);

app.Run();

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}

Slutpunkter kan ges namn för att generera URL:er till slutpunkten. Om du använder en namngiven slutpunkt undviker du att behöva hårdkoda sökvägar i en app:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () => "Hello named route")
   .WithName("hi");

app.MapGet("/", (LinkGenerator linker) => 
        $"The link to the hello route is {linker.GetPathByName("hi", values: null)}");

app.Run();

Föregående kod visar The link to the hello endpoint is /hello från slutpunkten /.

NOTE: Slutpunktsnamn är skiftlägeskänsliga.

Slutpunktsnamn:

  • Måste vara globalt unikt.
  • Används som OpenAPI-åtgärds-ID när OpenAPI-stöd är aktiverat. Mer information finns i OpenAPI-.

Routningsparametrar

Routningsparametrar kan samlas in som en del av routningsmönsterdefinitionen:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{userId}/books/{bookId}", 
    (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

app.Run();

Föregående kod returnerar The user id is 3 and book id is 7 från URI-/users/3/books/7.

Routningshanteraren kan deklarera parametrarna som ska avbildas. När en begäran görs till en väg med parametrar som deklarerats för att fånga, parsas parametrarna och skickas till hanteraren. Det gör det enkelt att samla in värdena på ett säkert sätt. I föregående kod är både userId och bookIdint.

Om något av routningsvärdena inte kan konverteras till en inti föregående kod genereras ett undantag. GET-begäran /users/hello/books/3 genererar följande undantag:

BadHttpRequestException: Failed to bind parameter "int userId" from "hello".

Jokertecken och fånga alla vägar

Följande catch all route returnerar Routing to hello från slutpunkten "/posts/hello":

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");

app.Run();

Vägbegränsningar

Routningsbegränsningar begränsar matchningsbeteendet för en väg.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text)));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

app.Run();

I följande tabell visas föregående vägmallar och deras beteende:

Routningsmall Exempel på matchande URI
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Mer information finns i referens för routningsbegränsningar i Routning i ASP.NET Core.

Parameterbindning

Parameterbindning är processen att konvertera begärandedata till starkt skrivna parametrar som uttrycks av routningshanterare. En bindningskälla avgör var parametrarna är bundna från. Bindningskällor kan vara explicita eller härledda baserat på HTTP-metod och parametertyp.

Bindningskällor som stöds:

  • Vägvärden
  • Frågesträngen
  • Rubrik
  • Kropp (som JSON)
  • Tjänster som tillhandahålls av beroendeinjektion
  • Anpassad

Not

Bindning från formulärvärden stöds inte internt i .NET.

I följande exempel använder GET-routningshanteraren några av dessa parameterbindningskällor:

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();

app.MapGet("/{id}", (int id,
                     int page,
                     [FromHeader(Name = "X-CUSTOM-HEADER")] string customHeader,
                     Service service) => { });

class Service { }

I följande tabell visas relationen mellan de parametrar som används i föregående exempel och de associerade bindningskällorna.

Parameter Bindningskälla
id ruttvärde
page Frågesträngen
customHeader rubrik
service Tillhandahålls av beroendeinjektion

HTTP-metoderna GET, HEAD, OPTIONSoch DELETE binder inte implicit från brödtexten. Om du vill binda från brödtexten (som JSON) för dessa HTTP-metoder du uttryckligen binda med [FromBody] eller läsa från HttpRequest.

I följande exempel använder POST-routningshanteraren en bindningskälla för brödtext (som JSON) för parametern person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/", (Person person) => { });

record Person(string Name, int Age);

Parametrarna i föregående exempel är alla bundna från begärandedata automatiskt. För att demonstrera bekvämligheten med parameterbindningen visar följande exempel routningshanterare hur du läser begärandedata direkt från begäran:

app.MapGet("/{id}", (HttpRequest request) =>
{
    var id = request.RouteValues["id"];
    var page = request.Query["page"];
    var customHeader = request.Headers["X-CUSTOM-HEADER"];

    // ...
});

app.MapPost("/", async (HttpRequest request) =>
{
    var person = await request.ReadFromJsonAsync<Person>();

    // ...
});

Explicit parameterbindning

Attribut kan användas för att explicit deklarera var parametrarna är bundna från.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

// Added as service
builder.Services.AddSingleton<Service>();

var app = builder.Build();


app.MapGet("/{id}", ([FromRoute] int id,
                     [FromQuery(Name = "p")] int page,
                     [FromServices] Service service,
                     [FromHeader(Name = "Content-Type")] string contentType) 
                     => {});

class Service { }

record Person(string Name, int Age);
Parameter Bindningskälla
id routvärde med namnet id
page frågesträng med namnet "p"
service Tillhandahålls av beroendeinjektion
contentType rubrik med namnet "Content-Type"

Not

Bindning från formulärvärden stöds inte internt i .NET.

Parameterbindning med DI

Parameterbindning för minimala API:er binder parametrar via beroendeinmatning när typen konfigureras som en tjänst. Det är inte nödvändigt att uttryckligen tillämpa attributet [FromServices] på en parameter. I följande kod returnerar båda åtgärderna tiden:

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

var app = builder.Build();

app.MapGet("/",   (               IDateTime dateTime) => dateTime.Now);
app.MapGet("/fs", ([FromServices] IDateTime dateTime) => dateTime.Now);
app.Run();

Valfria parametrar

Parametrar som deklareras i routningshanterare behandlas efter behov:

  • Om en begäran matchar vägen körs routningshanteraren endast om alla obligatoriska parametrar anges i begäran.
  • Om du inte anger alla obligatoriska parametrar resulterar det i ett fel.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int pageNumber) => $"Requesting page {pageNumber}");

app.Run();
URI resultat
/products?pageNumber=3 3 returnerade
/products BadHttpRequestException: Den obligatoriska parametern "int pageNumber" angavs inte från frågesträngen.
/products/1 HTTP 404-fel, ingen matchande väg

Om du vill göra pageNumber valfri definierar du typen som valfri eller anger ett standardvärde:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

string ListProducts(int pageNumber = 1) => $"Requesting page {pageNumber}";

app.MapGet("/products2", ListProducts);

app.Run();
URI resultat
/products?pageNumber=3 3 returnerade
/products 1 returnerade
/products2 1 returnerade

Föregående null- och standardvärde gäller för alla källor:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/products", (Product? product) => { });

app.Run();

Föregående kod anropar metoden med en null-produkt om ingen begärandetext skickas.

NOTE: Om ogiltiga data anges och parametern är null kan routningshanteraren inte köras.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products", (int? pageNumber) => $"Requesting page {pageNumber ?? 1}");

app.Run();
URI resultat
/products?pageNumber=3 3 har återvänt
/products 1 returnerade
/products?pageNumber=two BadHttpRequestException: Det gick inte att binda parametern "Nullable<int> pageNumber" från "två".
/products/two HTTP 404-fel, ingen matchande väg

Mer information finns i avsnittet Bindningsfel.

Särskilda typer

Följande typer är bundna utan explicita attribut:

  • HttpContext: Kontexten som innehåller all information om den aktuella HTTP-begäran eller -svaret:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest och HttpResponse: HTTP-begäran och HTTP-svar:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: Den annulleringstoken som är associerad med den aktuella HTTP-begäran:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: Användaren som är associerad med begäran, bunden från HttpContext.User:

    app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
    

Anpassad bindning

Det finns två sätt att anpassa parameterbindning:

  1. För routnings-, fråge- och rubrikbindningskällor binder du anpassade typer genom att lägga till en statisk TryParse metod för typen.
  2. Kontrollera bindningsprocessen genom att implementera en BindAsync-metod för en typ.

TryParse

TryParse har två API:er:

public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);

Följande kod visar Point: 12.3, 10.1 med URI-/map?Point=12.3,10.1:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /map?Point=12.3,10.1
app.MapGet("/map", (Point point) => $"Point: {point.X}, {point.Y}");

app.Run();

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public static bool TryParse(string? value, IFormatProvider? provider,
                                out Point? point)
    {
        // Format is "(12.3,10.1)"
        var trimmedValue = value?.TrimStart('(').TrimEnd(')');
        var segments = trimmedValue?.Split(',',
                StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments?.Length == 2
            && double.TryParse(segments[0], out var x)
            && double.TryParse(segments[1], out var y))
        {
            point = new Point { X = x, Y = y };
            return true;
        }

        point = null;
        return false;
    }
}

BindAsync

BindAsync har följande API:er:

public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);

Följande kod visar SortBy:xyz, SortDirection:Desc, CurrentPage:99 med URI-/products?SortBy=xyz&SortDir=Desc&Page=99:

using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /products?SortBy=xyz&SortDir=Desc&Page=99
app.MapGet("/products", (PagingData pageData) => $"SortBy:{pageData.SortBy}, " +
       $"SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}");

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask<PagingData?> BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = "sortBy";
        const string sortDirectionKey = "sortDir";
        const string currentPageKey = "page";

        Enum.TryParse<SortDirection>(context.Request.Query[sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query[currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query[sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult<PagingData?>(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}

Bindningsfel

När bindningen misslyckas loggar ramverket ett felsökningsmeddelande och returnerar olika statuskoder till klienten beroende på felläget.

Felläge Parametertyp som kan vara null Bindningskälla Statuskod
{ParameterType}.TryParse returnerar false Ja route/query/header 400
{ParameterType}.BindAsync returnerar null Ja anpassad 400
{ParameterType}.BindAsync kastar spelar ingen roll anpassat 500
Det gick inte att deserialisera JSON-kropp spelar ingen roll kropp 400
Fel innehållstyp (inte application/json) spelar ingen roll kropp 415

Bindningsprioritet

Reglerna för att fastställa en bindningskälla från en parameter:

  1. Explicit attribut definierat på parametern (From*-attribut) i följande ordning:
    1. Vägvärden: [FromRoute]
    2. Frågesträng: [FromQuery]
    3. Rubrik: [FromHeader]
    4. Brödtext: [FromBody]
    5. Tjänst: [FromServices]
  2. Särskilda typer
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. Parametertypen har en giltig BindAsync-metod.
  4. Parametertypen är en sträng eller har en giltig TryParse-metod.
    1. Om parameternamnet finns i vägmallen. På app.Map("/todo/{id}", (int id) => {});är id bunden från rutten.
    2. Bunden från frågesträngen.
  5. Om parametertypen är en tjänst som tillhandahålls av beroendeinjektion, används den tjänsten som källa.
  6. Parametern kommer från kroppen.

Anpassa JSON-koppling

Datainkapslingskällan använder System.Text.Json för deserialisering. Det är inte möjligt att ändra den här standardinställningen, men bindningen kan anpassas med hjälp av andra tekniker som beskrevs tidigare. Om du vill anpassa JSON-serialiseraralternativ använder du kod som liknar följande:

using Microsoft.AspNetCore.Http.Json;

var builder = WebApplication.CreateBuilder(args);

// Configure JSON options.
builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/products", (Product product) => product);

app.Run();

class Product
{
    // These are public fields, not properties.
    public int Id;
    public string? Name;
}

Föregående kod:

  • Konfigurerar JSON-standardalternativen för indata och utdata.
  • Returnerar följande JSON
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    När du publicerar
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

Läs begärandetexten

Läs begärandetexten direkt med hjälp av en HttpContext- eller HttpRequest-parameter:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpRequest request) =>
{
    var filePath = Path.Combine(config["StoredFilesPath"], Path.GetRandomFileName());

    await using var writeStream = File.Create(filePath);
    await request.BodyReader.CopyToAsync(writeStream);
});

app.Run();

Föregående kod:

Svaren

Routningshanterare stöder följande typer av returvärden:

  1. IResult baserat – Detta omfattar Task<IResult> och ValueTask<IResult>
  2. string – Detta inkluderar Task<string> och ValueTask<string>
  3. T (alla andra typer) – Detta inkluderar Task<T> och ValueTask<T>
Returvärde Uppförande Innehållstyp
IResult Ramverket anropar IResult.ExecuteAsync Bestäms av IResult implementeringen
string Ramverket skriver strängen direkt till svaret text/plain
T (alla andra typer) Ramverket serialiserar svaret med JSON application/json

Exempel på returvärden

strängreturvärden

app.MapGet("/hello", () => "Hello World");

JSON-returvärden

app.MapGet("/hello", () => new { Message = "Hello World" });

IResult-returvärden

app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));

I följande exempel används de inbyggda resultattyperna för att anpassa svaret:

app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
         await db.Todos.FindAsync(id) 
         is Todo todo
         ? Results.Ok(todo) 
         : Results.NotFound())
   .Produces<Todo>(StatusCodes.Status200OK)
   .Produces(StatusCodes.Status404NotFound);
JSON
app.MapGet("/hello", () => Results.Json(new { Message = "Hello World" }));
Anpassad statuskod
app.MapGet("/405", () => Results.StatusCode(405));
Text
app.MapGet("/text", () => Results.Text("This is some text"));
Strömning
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var proxyClient = new HttpClient();
app.MapGet("/pokemon", async () => 
{
    var stream = await proxyClient.GetStreamAsync("http://contoso/pokedex.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

app.Run();
Omdirigera
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
Fil
app.MapGet("/download", () => Results.File("myfile.text"));

Inbyggda resultat

Vanliga resultathjälpare finns i den Microsoft.AspNetCore.Http.Results statiska klassen.

Beskrivning Svarstyp Statuskod API
Skriva ett JSON-svar med avancerade alternativ application/json 200 Results.Json
Skriva ett JSON-svar application/json 200 Resultat.Okej
Skriva ett textsvar text/oformaterad (standard), konfigurerbar 200 Resultat.Text
Skriv svaret som byte application/octet-stream (standard), konfigurerbar 200 Results.Bytes
Skriva en ström med bytes till svaret application/octet-stream (standard), konfigurerbar 200 Results.Stream
Strömma en fil till svaret för nedladdning med content-disposition-huvudet application/octet-stream (standard), konfigurerbar 200 Results.File
Ange statuskoden till 404 med ett valfritt JSON-svar Ej tillämpligt 404 Resultat.InteHittat
Ange statuskoden till 204 Ej tillämpligt 204 Resultat.IngenInnehåll
Ange statuskoden till 422 med ett valfritt JSON-svar Ej tillämpligt 422 Results.UnprocessableEntity
Ange statuskoden till 400 med ett valfritt JSON-svar Ej tillämpligt 400 Results.BadRequest
Ange statuskoden till 409 med ett valfritt JSON-svar Ej tillämpligt 409 Results.Conflict
Skriva ett JSON-objekt för probleminformation till svaret Ej tillämpligt 500 (standard) kan konfigureras Resultat.Problem
Skriva ett JSON-objekt för probleminformation till svaret med valideringsfel Ej tillämpligt N/A, kan konfigureras Results.ValidationProblem

Anpassa resultat

Program kan styra svar genom att implementera en anpassad IResult typ. Följande kod är ett exempel på en HTML-resultattyp:

using System.Net.Mime;
using System.Text;
static class ResultsExtensions
{
    public static IResult Html(this IResultExtensions resultExtensions, string html)
    {
        ArgumentNullException.ThrowIfNull(resultExtensions);

        return new HtmlResult(html);
    }
}

class HtmlResult : IResult
{
    private readonly string _html;

    public HtmlResult(string html)
    {
        _html = html;
    }

    public Task ExecuteAsync(HttpContext httpContext)
    {
        httpContext.Response.ContentType = MediaTypeNames.Text.Html;
        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
        return httpContext.Response.WriteAsync(_html);
    }
}

Vi rekommenderar att du lägger till en tilläggsmetod till Microsoft.AspNetCore.Http.IResultExtensions för att göra dessa anpassade resultat mer upptäckbara.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/html", () => Results.Extensions.Html(@$"<!doctype html>
<html>
    <head><title>miniHTML</title></head>
    <body>
        <h1>Hello World</h1>
        <p>The time on the server is {DateTime.Now:O}</p>
    </body>
</html>"));

app.Run();

Tillstånd

Vägar kan skyddas med hjälp av auktoriseringsprinciper. Dessa kan deklareras via attributet [Authorize] eller med hjälp av metoden RequireAuthorization:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

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

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/auth", [Authorize] () => "This endpoint requires authorization.");
app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Föregående kod kan skrivas med RequireAuthorization:

app.MapGet("/auth", () => "This endpoint requires authorization")
   .RequireAuthorization();

Följande exempel använder principbaserad auktorisering:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRPauth.Data;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization(o => o.AddPolicy("AdminsOnly", 
                                  b => b.RequireClaim("admin", "true")));

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

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/admin", [Authorize("AdminsOnly")] () => 
                             "The /admin endpoint is for admins only.");

app.MapGet("/admin2", () => "The /admin2 endpoint is for admins only.")
   .RequireAuthorization("AdminsOnly");

app.MapGet("/", () => "This endpoint doesn't require authorization.");
app.MapGet("/Identity/Account/Login", () => "Sign in page at this endpoint.");

app.Run();

Tillåt oautentiserade användare att komma åt en slutpunkt

Med [AllowAnonymous] kan oautentiserade användare komma åt slutpunkter:

app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");


app.MapGet("/login2", () => "This endpoint also for all roles.")
   .AllowAnonymous();

CORS

Rutter kan CORS aktiveras med CORS-principer. CORS kan deklareras via attributet [EnableCors] eller med hjälp av metoden RequireCors. Följande exempel aktiverar CORS:

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/",() => "Hello CORS!");

app.Run();
using Microsoft.AspNetCore.Cors;

const string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/cors", [EnableCors(MyAllowSpecificOrigins)] () => 
                           "This endpoint allows cross origin requests!");
app.MapGet("/cors2", () => "This endpoint allows cross origin requests!")
                     .RequireCors(MyAllowSpecificOrigins);

app.Run();

Mer information finns i Aktivera CORS (Cross-Origin Requests) i ASP.NET Core

Se även

OpenAPI-stöd i minimala API:er