Compartilhar via


Consulta rápida sobre as APIs mínimas

Note

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão do .NET 10 deste artigo.

Warning

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Este documento:

As APIs mínimas consistem em:

WebApplication

O código a seguir é gerado por um modelo do ASP.NET Core:

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

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

app.Run();

O código anterior pode ser criado por meio de dotnet new web na linha de comando ou da seleção do modelo Empty Web no Visual Studio.

O código a seguir cria um WebApplication (app) sem criar explicitamente um WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create inicializa uma nova instância da classe WebApplication com padrões pré-configurados.

WebApplication adiciona automaticamente o seguinte middleware em aplicativos de API mínima , dependendo de determinadas condições:

  • UseDeveloperExceptionPage é adicionado primeiro quando o HostingEnvironment é "Development".
  • UseRouting será adicionado em segundo se o código do usuário ainda não tiver chamado UseRouting e se houver pontos de extremidade configurados, por exemplo app.MapGet.
  • UseEndpoints será adicionado no final do pipeline de middleware se algum ponto de extremidade estiver configurado.
  • UseAuthentication será adicionado imediatamente após UseRouting se o código do usuário ainda não tiver chamado UseAuthentication e se IAuthenticationSchemeProvider puder ser detectado no provedor de serviços. IAuthenticationSchemeProvider é adicionado por padrão ao usar AddAuthentication, e os serviços são detectados usando IServiceProviderIsService.
  • UseAuthorization será adicionado em seguida se o código do usuário ainda não tiver chamado UseAuthorization e se IAuthorizationHandlerProvider puder ser detectado no provedor de serviços. IAuthorizationHandlerProvider é adicionado por padrão ao usar AddAuthorization, e os serviços são detectados usando IServiceProviderIsService.
  • O middleware e os pontos de extremidade configurados pelo usuário são adicionados entre UseRouting e UseEndpoints.

O código a seguir é de fato aquilo que o middleware automático que está sendo adicionado ao aplicativo produz:

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

Em alguns casos, a configuração de middleware padrão não é a correta para o aplicativo e requer modificação. Por exemplo, UseCors deve ser chamado antes de UseAuthentication e de UseAuthorization. O aplicativo precisa chamar UseAuthentication e UseAuthorization se UseCors for chamado:

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

Se o middleware tiver que ser executado antes da correspondência de rotas ocorrer, UseRouting deverá ser chamado e o middleware deverá ser colocado antes da chamada para UseRouting. UseEndpoints não é necessário nesse caso, já que é adicionado automaticamente conforme descrito anteriormente:

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

app.UseRouting();

// other middleware and endpoints

Ao adicionar um middleware de terminal:

  • O middleware deve ser adicionado após UseEndpoints.
  • O aplicativo precisa chamar UseRouting e UseEndpoints para que o middleware do terminal possa ser colocado no local correto.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

O middleware de terminal é um middleware que é executado se nenhum ponto de extremidade lidar com a solicitação.

Trabalho com portas

Quando um aplicativo Web é criado com o Visual Studio ou com o dotnet new, aparece um arquivo Properties/launchSettings.json que especifica as portas às quais responde aplicativo. Nos exemplos de configuração de portas a seguir, a execução do aplicativo no Visual Studio retorna uma caixa de diálogo de erro Unable to connect to web server 'AppName'. O Visual Studio retorna um erro porque espera a porta especificada em Properties/launchSettings.json, mas o aplicativo está usando a porta especificada por app.Run("http://localhost:3000"). Execute a porta a seguir, alterando exemplos da linha de comando.

As seções a seguir definem a porta à qual responde o aplicativo.

var app = WebApplication.Create(args);

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

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

No código anterior, o aplicativo responde à porta 3000.

Várias portas

No código a seguir, o aplicativo responde às portas 3000 e 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Defina a porta na linha de comando

O comando a seguir faz com que o aplicativo responda à porta 7777:

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

Se o ponto de extremidade Kestrel também estiver configurado no arquivo appsettings.json, a URL especificada do arquivo appsettings.jsonserá usada. Para obter mais informações, consulte Configuração de ponto de extremidade Kestrel

Leia a porta do ambiente

O código a seguir lê a porta do ambiente:

var app = WebApplication.Create(args);

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

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

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

A maneira preferencial de definir a porta do ambiente é usar a variável de ambiente ASPNETCORE_URLS, que é mostrada na seção a seguir.

Defina as portas por meio da variável de ambiente ASPNETCORE_URLS

A variável de ambiente ASPNETCORE_URLS está disponível para definir a porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS dá suporte a várias URLs:

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

Escute em todas as interfaces

Os exemplos a seguir demonstram a escuta em todas as interfaces

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

Escute em todas as interfaces usando ASPNETCORE_URLS

Os exemplos anteriores podem usar ASPNETCORE_URLS

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

Escute em todas as interfaces usando ASPNETCORE_HTTPS_PORTS

Os exemplos anteriores podem usar ASPNETCORE_HTTPS_PORTS e ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Para obter mais informações, consulte Configure pontos de extremidade para o Kestrel servidor de rede ASP.NET Core

Especifique HTTPS com certificado de desenvolvimento

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações sobre o certificado de desenvolvimento, consulte Confie no certificado de desenvolvimento HTTPS ASP.NET Core no Windows e no macOS.

Especifique HTTPS usando um certificado personalizado

As seções a seguir mostram como especificar o certificado personalizado usando o arquivo appsettings.json e por meio de configuração.

Especifique o certificado personalizado com appsettings.json

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

Especifique o certificado personalizado por meio de configuração

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

Use as APIs de certificado

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

Leia o ambiente

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

Para obter mais informações sobre como usar o ambiente, consulte ASP.NET Ambientes de runtime do Core

Configuration

O código a seguir lê a partir do sistema de configuração:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Configuração no ASP.NET Core

Logging

O código a seguir grava uma mensagem na inicialização do aplicativo de logon:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Log no .NET e no ASP.NET Core

Acesse o contêiner Injeção de Dependência (DI)

O código a seguir mostra como obter serviços do contêiner de DI durante a inicialização do aplicativo:


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

O código a seguir mostra como acessar chaves do contêiner de DI usando o atributo [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.";
}

Para obter mais informações sobre DI, confira Injeção de dependência no ASP.NET Core.

WebApplicationBuilder

Esta seção contém o código de exemplo usando o WebApplicationBuilder.

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente

O código a seguir define a raiz do conteúdo, o nome do aplicativo e o ambiente:

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

O WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com os padrões pré-configurados.

Para obter mais informações, consulte visão geral dos conceitos básicos do ASP.NET Core

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente usando variáveis de ambiente ou linha de comando

A tabela a seguir mostra a variável de ambiente e o argumento de linha de comando usados para alterar a raiz do conteúdo, o nome do aplicativo e o ambiente:

funcionalidade Variável de ambiente Argumento de linha de comando
Nome do aplicativo ASPNETCORE_APPLICATIONNAME --applicationName
Nome do ambiente ASPNETCORE_ENVIRONMENT --environment
Raiz de conteúdo ASPNETCORE_CONTENTROOT --contentRoot

Adicionar provedores de configuração

O exemplo a seguir adiciona o provedor de configuração INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Para maior detalhamento, consulte Provedores de configuração de arquivo em Configuração no ASP.NET Core.

Configuração de leitura

Por padrão, o WebApplicationBuilder lê a configuração de várias fontes, incluindo:

  • appSettings.json e appSettings.{environment}.json
  • Variáveis de ambiente
  • A linha de comando

Para obter uma lista completa das fontes de configuração lidas, confira a Configuração padrão em Configuração no ASP.NET Core.

O código a seguir lê HelloKey a partir da configuração e exibe o valor no ponto de extremidade/. Se o valor de configuração for nulo, "Hello" será atribuído a message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Leia o ambiente

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Adicione provedores de login

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

Adicionar serviços

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

Personalize o IHostBuilder

Os métodos de extensão existentes no IHostBuilder podem ser acessados usando a propriedade 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();

Personalize o IWebHostBuilder

Os métodos de extensão no IWebHostBuilder podem ser acessados usando a propriedade 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();

Altere a raiz da Web

Por padrão, a raiz da Web refere-se à raiz do conteúdo na pasta wwwroot. Raiz da Web é onde o Middleware de Arquivo Estático procura arquivos estáticos. A raiz da Web pode ser alterada com o WebHostOptions, com a linha de comando ou com o método UseWebRoot:

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

var app = builder.Build();

app.Run();

Personalize o contêiner de Injeção de Dependência (DI)

O exemplo a seguir usa o 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();

Adicionar middleware

Qualquer middleware do ASP.NET Core pode ser configurado no WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Middleware do ASP.NET Core

Página de exceção do desenvolvedor

WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com padrões pré-configurados. A página de exceção do desenvolvedor está habilitada nos padrões pré-configurados. Quando o código a seguir é executado no ambiente de desenvolvimento, navegar para / renderiza uma página amigável que mostra a exceção.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Middleware do ASP.NET Core

A tabela a seguir lista alguns dos middlewares usados com frequência com APIs mínimas.

Middleware Description API
Authentication Fornece suporte à autenticação. UseAuthentication
Authorization Fornece suporte à autorização. UseAuthorization
CORS Configura o Compartilhamento de Recursos entre Origens. UseCors
Manipulador de exceção Lida globalmente com exceções geradas pelo pipeline de middleware. UseExceptionHandler
Cabeçalhos encaminhados Encaminha cabeçalhos como proxy para a solicitação atual. UseForwardedHeaders
Redirecionamento https Redireciona todas as solicitações HTTP para HTTPS. UseHttpsRedirection
Segurança de Transporte Estrita de HTTP (HSTS) Middleware de aprimoramento de segurança que adiciona um cabeçalho de resposta especial. UseHsts
Solicitar registro em log Fornece suporte para registro de solicitações e respostas HTTP. UseHttpLogging
Tempos limite de solicitação Fornece suporte para configurar tempos limite de solicitação, padrão global e por ponto de extremidade. UseRequestTimeouts
Registro em log de solicitação em W3C Fornece suporte para registro em log de solicitações e respostas HTTP no formato W3C. UseW3CLogging
Cache de resposta Fornece suporte para as respostas em cache. UseResponseCaching
Compactação de resposta Fornece suporte para a compactação de respostas. UseResponseCompression
Session Fornece suporte para gerenciar sessões de usuário. UseSession
Arquivos estáticos Fornece suporte para servir arquivos estáticos e pesquisa no diretório. UseStaticFiles, UseFileServer
WebSockets Habilita o protocolo WebSockets. UseWebSockets

As seções a seguir abrangem o tratamento de solicitações: roteamento, associação de parâmetros e respostas.

Routing

Um suporte configurado e onde está um método HTTP com maiúsculas WebApplication e minúsculas, comoMap{Verb}, MapMethods, {Verb}ou Get:PostPutDelete

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

Os argumentos Delegate passados para esses métodos são chamados de "manipuladores de rota".

Manipuladores de Rotas

Manipuladores de rotas são métodos executados quando a rota corresponde. Os manipuladores de rotas podem ser uma expressão lambda, uma função local, um método de instância ou um método estático. Os manipuladores de rota podem ser síncronos ou assíncronos.

Expressão Lambda

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

Função local

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

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

app.MapGet("/", LocalFunction);

app.Run();

Método de instância

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

Método estático

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

Ponto de extremidade definido fora do Program.cs

As APIs mínimas não precisam estar localizadas em 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" });
        });
    }
}

Também consulte Grupos de rota mais adiante neste artigo.

Os pontos de extremidade podem receber nomes para gerar URLs para o ponto de extremidade. O uso de um ponto de extremidade nomeado evita ter que codificar caminhos em um aplicativo:

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

O código anterior exibe The link to the hello route is /hello do ponto de extremidade /.

OBSERVAÇÃO: os nomes de ponto de extremidade diferenciam maiúsculas e minúsculas.

Nomes de ponto de extremidade:

  • Deve ser globalmente exclusivo.
  • São usados como a ID da operação OpenAPI quando o suporte ao OpenAPI está habilitado. Para obter mais informações, confira OpenAPI.

Parâmetros de rota

Os parâmetros de rota podem ser capturados como parte da definição do padrão de rota:

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

O código anterior retorna The user id is 3 and book id is 7 do URI /users/3/books/7.

O manipulador de rotas pode declarar os parâmetros a serem capturados. Quando uma solicitação é feita em uma rota com parâmetros declarados para captura, os parâmetros são analisados e passados para o manipulador. Isso facilita a captura dos valores de uma maneira segura de tipo. No código anterior, userId e bookId são int.

No código anterior, se qualquer valor de rota não puder ser convertido em um int, uma exceção será gerada. A solicitação GET /users/hello/books/3 gera a seguinte exceção:

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

Caractere curinga e capturar todas as rotas

A rota catch-all a seguir retorna Routing to hello do ponto de extremidade '/posts/hello':

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

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

app.Run();

Restrições da rota

As restrições de rota restringem o comportamento correspondente de uma rota.

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

A tabela a seguir demonstra modelos de rota anteriores e seu comportamento:

Modelo de rota URI de correspondência de exemplo
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para obter mais informações, confira Referência de restrição de rota no Roteamento no ASP.NET Core.

Grupos de rotas

O método de extensão MapGroup ajuda a organizar grupos de pontos de extremidade com um prefixo comum. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata, que adicionam os metadados de ponto de extremidade.

Por exemplo, o código a seguir cria dois grupos de pontos de extremidade semelhantes:

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

Nesse cenário, você pode usar um endereço relativo para o cabeçalho Location no resultado 201 Created:

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

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

O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /public/todos e estará acessível sem autenticação. O segundo grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodosfábrica de filtro de ponto de extremidade é uma função local que modifica os TodoDb parâmetros do manipulador de rota para permitir o acesso e armazenamento de dados privados de tarefas.

Os grupos de rotas também permitem grupos aninhados e padrões de prefixo complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de rotas mapeado para o grupo user pode capturar os parâmetros de rota {org} e {group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou filtros de ponto de extremidade a um grupo de pontos de extremidade, sem alterar o padrão de rota.

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

Adicionar filtros ou metadados a um grupo é igual a adicioná-los individualmente a cada ponto de extremidade, antes de adicionar filtros ou metadados extras que possam ter sido adicionados a um grupo interno ou ponto de extremidade específico.

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

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro interno, mesmo que tenha sido adicionado depois. Como os filtros foram aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos outros não importa. A ordem em que os filtros são adicionados importa se aplicados ao mesmo grupo ou ponto de extremidade específico.

Uma solicitação para /outer/inner/ registrará o seguinte:

/outer group filter
/inner group filter
MapGet filter

Associação de parâmetros

Associação de parâmetro é o processo de converter dados de solicitação em parâmetros fortemente tipados que são expressos por manipuladores de rota. A origem de uma associação determina de onde os parâmetros são associados. As origens de associação podem ser explícitas ou inferidas com base no método HTTP e no tipo de parâmetro.

Origens de associação com suporte:

  • Valores de rota
  • Cadeia de consulta
  • Header
  • Corpo (como JSON)
  • Valores de formulário
  • Serviços fornecidos pela injeção de dependência
  • Custom

O seguinte manipulador de rotas GET usa algumas dessas fontes de associação de parâmetros:

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

Principais recursos de associação de parâmetro

  • Associação explícita: use atributos como [FromRoute], [FromQuery], [FromHeader], , [FromBody][FromForm]e [FromServices] para especificar explicitamente fontes de associação.
  • : usar o atributo para associar os valores do formulário, incluindo suporte para e para uploads de arquivo.
  • Tipos complexos: Vincular a coleções e tipos complexos de formulários, strings de consulta e cabeçalhos.
  • Associação personalizada: implemente a lógica de associação personalizada usando TryParse, BindAsyncou a IBindableFromHttpContext<T> interface.
  • Parâmetros opcionais: dão suporte a tipos anuláveis e valores padrão para parâmetros opcionais.
  • Injeção de dependência: os parâmetros são automaticamente associados a serviços registrados no contêiner de DI.
  • Tipos especiais: associação automática para HttpContext, HttpRequest, HttpResponse, CancellationToken, , ClaimsPrincipal, Streame PipeReader.

Saiba Mais: Para obter informações detalhadas sobre a associação de parâmetros, incluindo cenários avançados, validação, precedência de associação e solução de problemas, consulte Associação de parâmetros em aplicativos de API mínimos.

Desserialização de Json+PipeReader em APIs mínimas

A partir do .NET 10, as seguintes áreas funcionais do ASP.NET Core usam sobrecargas de JsonSerializer.DeserializeAsync baseadas no PipeReader em vez do Stream.

  • APIs mínimas (associação de parâmetro, leitura do corpo da solicitação)
  • MVC (formatores de entrada, modelo)
  • Os métodos de extensão HttpRequestJsonExtensions para ler o corpo da solicitação como JSON.

Para a maioria dos aplicativos, uma transição do Stream para o PipeReader fornece melhor desempenho sem a necessidade de alterações no código do aplicativo. No entanto, se o aplicativo tiver um conversor personalizado, o conversor poderá não lidar Utf8JsonReader.HasValueSequence corretamente. Se isso não ocorrer, o resultado poderá ser erros como ArgumentOutOfRangeException ou dados ausentes ao desserializar. Você tem as seguintes opções para fazer o conversor funcionar sem erros relacionados ao PipeReader.

Opção 1: solução alternativa temporária

A solução alternativa rápida é voltar a usar o Stream sem o suporte do PipeReader. Para implementar essa alternativa, defina a opção "Microsoft.AspNetCore.UseStreamBasedJsonParsing" no AppContext como "true". Recomendamos que você faça isso apenas como uma solução alternativa temporária e atualize o conversor para dar suporte HasValueSequence o mais rápido possível. A opção pode ser removida no .NET 11. Sua única finalidade era dar aos desenvolvedores tempo para atualizar seus conversores.

Opção 2: uma correção rápida para JsonConverter implementações

Para esta correção, você aloca um array a partir do ReadOnlySequence. Este exemplo mostra como seria a aparência do código:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

Opção 3: Uma correção mais complicada, mas com melhor desempenho

Essa correção envolve a configuração de um caminho de código separado para o tratamento do ReadOnlySequence.

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

Para obter mais informações, consulte

Suporte à validação em APIs mínimas

Habilitar a validação permite que o runtime do ASP.NET Core execute validações definidas no:

  • Query
  • Header
  • Corpo da solicitação

As validações são definidas usando atributos no DataAnnotations namespace.

Quando um parâmetro de um endpoint de Minimal API é um tipo de classe ou tipo de registro, os atributos de validação são aplicados automaticamente. Por exemplo:

public record Product(
    [Required] string Name,
    [Range(1, 1000)] int Quantity);

Os desenvolvedores personalizam o comportamento do sistema de validação:

Se a validação falhar, o runtime retornará uma resposta 400 – Solicitação Incorreta com detalhes dos erros de validação.

Habilitar o suporte de validação interna para APIs mínimas

Habilite o suporte de validação interna para APIs mínimas chamando o AddValidation método de extensão para registrar os serviços necessários no contêiner de serviço para seu aplicativo:

builder.Services.AddValidation();

A implementação descobre automaticamente tipos definidos em manipuladores de API mínimos ou como tipos base de tipos definidos em manipuladores de API Mínimo. Um filtro de ponto de extremidade executa a validação nesses tipos e é adicionado para cada ponto de extremidade.

A validação pode ser desabilitada para pontos de extremidade específicos usando o DisableValidation método de extensão, como no exemplo a seguir:

app.MapPost("/products",
    ([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
        => TypedResults.Ok(productId))
    .DisableValidation();

Personalizar respostas de erro de validação usando IProblemDetailsService

Personalize as respostas de erro da lógica de validação de API mínima com uma implementação IProblemDetailsService . Registre esse serviço na coleção de serviços do aplicativo para habilitar respostas de erro mais consistentes e específicas do usuário. O suporte para validação mínima de API foi introduzido no ASP.NET Core no .NET 10.

Para implementar respostas de erro de validação personalizadas:

  • Implementar IProblemDetailsService ou usar a implementação padrão
  • Registrar o serviço no contêiner de DI
  • O sistema de validação usa automaticamente o serviço registrado para formatar respostas de erro de validação

Para obter mais informações sobre como personalizar respostas de erro de validação com IProblemDetailsService, consulte Criar respostas em aplicativos de API mínimos.

Responses

Os manipuladores de rota dão suporte aos seguintes tipos de valores retornados:

  1. Baseado em IResult - Inclui Task<IResult> e ValueTask<IResult>
  2. string - Inclui Task<string> e ValueTask<string>
  3. T (Qualquer outro tipo) – inclui Task<T> e ValueTask<T>
Valor de retorno Behavior Content-Type
IResult A estrutura chama IResult.ExecuteAsync Decidido pela implementação de IResult
string A estrutura grava a cadeia de caracteres diretamente na resposta text/plain
T (Qualquer outro tipo) A estrutura JSON serializa a resposta application/json

Obtenha um guia mais detalhado dos valores retornados do manipulador de rota em Crie respostas em aplicativos de APIs mínimas

Exemplo de valores retornados

valores retornados de cadeias de caracteres

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

Valores JSON retornados

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

Retornar TypedResults

O código a seguir retorna um TypedResults:

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

É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Valores retornados de IResult

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

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:

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

Código de status personalizado

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

Texto

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

Stream

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

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Redirect

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

File

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

Resultados internos

Auxiliares de resultados comuns existem no Results e nas classes estáticas TypedResults. É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Modificando cabeçalhos

Use o objeto HttpResponse para modificar cabeçalhos de resposta:

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

Personalizando resultados

Aplicativos podem controlar as respostas implementando um tipo IResult personalizado. O código a seguir é um exemplo de um tipo de resultado HTML:

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

Recomenda-se adicionar um método de extensão a Microsoft.AspNetCore.Http.IResultExtensions para tornar esses resultados personalizados mais detectáveis.

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

Resultados tipado

A IResult interface pode representar valores retornados de APIs mínimas que não utilizam o suporte implícito para O JSON serializando o objeto retornado para a resposta HTTP. A classe estática Results é usada para criar objetos IResult variados que representam diferentes tipos de respostas. Por exemplo, configurando o código de status de resposta ou redirecionando para outra URL.

Os tipos de implementação IResult são públicos, permitindo instruções de declaração de tipo durante o teste. Por exemplo:

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

É possível examinar os tipos de retorno dos métodos correspondentes na classe estática TypedResults e localizar o tipo público IResult correto para converter.

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Filters

Para obter mais informações, veja Filtros em aplicativos API mínimos.

Authorization

Rotas podem ser protegidas por meio de políticas de autorização. É possível declará-las por meio do atributo [Authorize] ou utilizando o método 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();

O código anterior pode ser gravado com RequireAuthorization:

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

O exemplo a seguir utiliza a autorização baseada em políticas:

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

Permita o acesso de usuários não autenticados a um ponto de extremidade

O [AllowAnonymous] permite o acesso de usuários não autenticados a pontos de extremidade:

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


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

CORS

Rotas podem ser habilitadas para CORS com as políticas CORS. O CORS pode ser declarado por meio do atributo [EnableCors] ou utilizando o método RequireCors. As seguintes amostras habilitam o 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();

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core

ValidateScopes e ValidateOnBuild

ValidateScopes e ValidateOnBuild são habilitados por padrão no ambiente de Desenvolvimento, mas desabilitados em outros ambientes.

Quando ValidateOnBuild é true, o contêiner de DI valida a configuração do serviço no momento do build. Se a configuração do serviço for inválida, o build falhará na inicialização do aplicativo, em vez de em runtime, quando o serviço for solicitado.

Quando ValidateScopes é true, o contêiner de DI valida que um serviço com escopo não está resolvido no escopo raiz. Resolver um serviço com escopo no escopo raiz pode resultar em perda de memória porque o serviço é mantido na memória por mais tempo do que o escopo da solicitação.

ValidateScopes e ValidateOnBuild são falsos por padrão nos modos que não são de desenvolvimento por questão de desempenho.

O código a seguir mostra que ValidateScopes está habilitada por padrão no modo de desenvolvimento, mas desabilitada no modo de versão:

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

O código a seguir mostra que ValidateOnBuild está habilitada por padrão no modo de desenvolvimento, mas desabilitada no modo de versão:

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

O código a seguir desabilita ValidateScopes e ValidateOnBuild em 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;
    });
}

Consulte também

Este documento:

As APIs mínimas consistem em:

WebApplication

O código a seguir é gerado por um modelo do ASP.NET Core:

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

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

app.Run();

O código anterior pode ser criado por meio de dotnet new web na linha de comando ou da seleção do modelo Empty Web no Visual Studio.

O código a seguir cria um WebApplication (app) sem criar explicitamente um WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create inicializa uma nova instância da classe WebApplication com padrões pré-configurados.

WebApplication adiciona automaticamente o seguinte middleware em aplicativos de API mínima , dependendo de determinadas condições:

  • UseDeveloperExceptionPage é adicionado primeiro quando o HostingEnvironment é "Development".
  • UseRouting será adicionado em segundo se o código do usuário ainda não tiver chamado UseRouting e se houver pontos de extremidade configurados, por exemplo app.MapGet.
  • UseEndpoints será adicionado no final do pipeline de middleware se algum ponto de extremidade estiver configurado.
  • UseAuthentication será adicionado imediatamente após UseRouting se o código do usuário ainda não tiver chamado UseAuthentication e se IAuthenticationSchemeProvider puder ser detectado no provedor de serviços. IAuthenticationSchemeProvider é adicionado por padrão ao usar AddAuthentication, e os serviços são detectados usando IServiceProviderIsService.
  • UseAuthorization será adicionado em seguida se o código do usuário ainda não tiver chamado UseAuthorization e se IAuthorizationHandlerProvider puder ser detectado no provedor de serviços. IAuthorizationHandlerProvider é adicionado por padrão ao usar AddAuthorization, e os serviços são detectados usando IServiceProviderIsService.
  • O middleware e os pontos de extremidade configurados pelo usuário são adicionados entre UseRouting e UseEndpoints.

O código a seguir é de fato aquilo que o middleware automático que está sendo adicionado ao aplicativo produz:

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

Em alguns casos, a configuração de middleware padrão não é a correta para o aplicativo e requer modificação. Por exemplo, UseCors deve ser chamado antes de UseAuthentication e de UseAuthorization. O aplicativo precisa chamar UseAuthentication e UseAuthorization se UseCors for chamado:

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

Se o middleware tiver que ser executado antes da correspondência de rotas ocorrer, UseRouting deverá ser chamado e o middleware deverá ser colocado antes da chamada para UseRouting. UseEndpoints não é necessário nesse caso, já que é adicionado automaticamente conforme descrito anteriormente:

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

app.UseRouting();

// other middleware and endpoints

Ao adicionar um middleware de terminal:

  • O middleware deve ser adicionado após UseEndpoints.
  • O aplicativo precisa chamar UseRouting e UseEndpoints para que o middleware do terminal possa ser colocado no local correto.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

O middleware de terminal é um middleware que é executado se nenhum ponto de extremidade lidar com a solicitação.

Trabalho com portas

Quando um aplicativo Web é criado com o Visual Studio ou com o dotnet new, aparece um arquivo Properties/launchSettings.json que especifica as portas às quais responde aplicativo. Nos exemplos de configuração de portas a seguir, a execução do aplicativo no Visual Studio retorna uma caixa de diálogo de erro Unable to connect to web server 'AppName'. O Visual Studio retorna um erro porque espera a porta especificada em Properties/launchSettings.json, mas o aplicativo está usando a porta especificada por app.Run("http://localhost:3000"). Execute a porta a seguir, alterando exemplos da linha de comando.

As seções a seguir definem a porta à qual responde o aplicativo.

var app = WebApplication.Create(args);

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

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

No código anterior, o aplicativo responde à porta 3000.

Várias portas

No código a seguir, o aplicativo responde às portas 3000 e 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Defina a porta na linha de comando

O comando a seguir faz com que o aplicativo responda à porta 7777:

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

Se o ponto de extremidade Kestrel também estiver configurado no arquivo appsettings.json, a URL especificada do arquivo appsettings.jsonserá usada. Para obter mais informações, consulte Configuração de ponto de extremidade Kestrel

Leia a porta do ambiente

O código a seguir lê a porta do ambiente:

var app = WebApplication.Create(args);

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

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

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

A maneira preferencial de definir a porta do ambiente é usar a variável de ambiente ASPNETCORE_URLS, que é mostrada na seção a seguir.

Defina as portas por meio da variável de ambiente ASPNETCORE_URLS

A variável de ambiente ASPNETCORE_URLS está disponível para definir a porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS dá suporte a várias URLs:

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

Escute em todas as interfaces

Os exemplos a seguir demonstram a escuta em todas as interfaces

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

Escute em todas as interfaces usando ASPNETCORE_URLS

Os exemplos anteriores podem usar ASPNETCORE_URLS

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

Escute em todas as interfaces usando ASPNETCORE_HTTPS_PORTS

Os exemplos anteriores podem usar ASPNETCORE_HTTPS_PORTS e ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Para obter mais informações, consulte Configure pontos de extremidade para o Kestrel servidor de rede ASP.NET Core

Especifique HTTPS com certificado de desenvolvimento

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações sobre o certificado de desenvolvimento, consulte Confie no certificado de desenvolvimento HTTPS ASP.NET Core no Windows e no macOS.

Especifique HTTPS usando um certificado personalizado

As seções a seguir mostram como especificar o certificado personalizado usando o arquivo appsettings.json e por meio de configuração.

Especifique o certificado personalizado com appsettings.json

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

Especifique o certificado personalizado por meio de configuração

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

Use as APIs de certificado

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

Leia o ambiente

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

Para obter mais informações sobre como usar o ambiente, consulte ASP.NET Ambientes de runtime do Core

Configuration

O código a seguir lê a partir do sistema de configuração:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Configuração no ASP.NET Core

Logging

O código a seguir grava uma mensagem na inicialização do aplicativo de logon:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Log no .NET e no ASP.NET Core

Acesse o contêiner Injeção de Dependência (DI)

O código a seguir mostra como obter serviços do contêiner de DI durante a inicialização do aplicativo:


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

O código a seguir mostra como acessar chaves do contêiner de DI usando o atributo [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.";
}

Para obter mais informações sobre DI, confira Injeção de dependência no ASP.NET Core.

WebApplicationBuilder

Esta seção contém o código de exemplo usando o WebApplicationBuilder.

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente

O código a seguir define a raiz do conteúdo, o nome do aplicativo e o ambiente:

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

O WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com os padrões pré-configurados.

Para obter mais informações, consulte visão geral dos conceitos básicos do ASP.NET Core

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente usando variáveis de ambiente ou linha de comando

A tabela a seguir mostra a variável de ambiente e o argumento de linha de comando usados para alterar a raiz do conteúdo, o nome do aplicativo e o ambiente:

funcionalidade Variável de ambiente Argumento de linha de comando
Nome do aplicativo ASPNETCORE_APPLICATIONNAME --applicationName
Nome do ambiente ASPNETCORE_ENVIRONMENT --environment
Raiz de conteúdo ASPNETCORE_CONTENTROOT --contentRoot

Adicionar provedores de configuração

O exemplo a seguir adiciona o provedor de configuração INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Para maior detalhamento, consulte Provedores de configuração de arquivo em Configuração no ASP.NET Core.

Configuração de leitura

Por padrão, o WebApplicationBuilder lê a configuração de várias fontes, incluindo:

  • appSettings.json e appSettings.{environment}.json
  • Variáveis de ambiente
  • A linha de comando

Para obter uma lista completa das fontes de configuração lidas, confira a Configuração padrão em Configuração no ASP.NET Core.

O código a seguir lê HelloKey a partir da configuração e exibe o valor no ponto de extremidade/. Se o valor de configuração for nulo, "Hello" será atribuído a message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Leia o ambiente

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Adicione provedores de login

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

Adicionar serviços

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

Personalize o IHostBuilder

Os métodos de extensão existentes no IHostBuilder podem ser acessados usando a propriedade 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();

Personalize o IWebHostBuilder

Os métodos de extensão no IWebHostBuilder podem ser acessados usando a propriedade 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();

Altere a raiz da Web

Por padrão, a raiz da Web refere-se à raiz do conteúdo na pasta wwwroot. Raiz da Web é onde o Middleware de Arquivo Estático procura arquivos estáticos. A raiz da Web pode ser alterada com o WebHostOptions, com a linha de comando ou com o método UseWebRoot:

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

var app = builder.Build();

app.Run();

Personalize o contêiner de Injeção de Dependência (DI)

O exemplo a seguir usa o 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();

Adicionar middleware

Qualquer middleware do ASP.NET Core pode ser configurado no WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Middleware do ASP.NET Core

Página de exceção do desenvolvedor

WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com padrões pré-configurados. A página de exceção do desenvolvedor está habilitada nos padrões pré-configurados. Quando o código a seguir é executado no ambiente de desenvolvimento, navegar para / renderiza uma página amigável que mostra a exceção.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Middleware do ASP.NET Core

A tabela a seguir lista alguns dos middleware usados com frequência com as APIs mínimas.

Middleware Description API
Authentication Fornece suporte à autenticação. UseAuthentication
Authorization Fornece suporte à autorização. UseAuthorization
CORS Configura o Compartilhamento de Recursos entre Origens. UseCors
Manipulador de exceção Lida globalmente com exceções geradas pelo pipeline de middleware. UseExceptionHandler
Cabeçalhos encaminhados Encaminha cabeçalhos como proxy para a solicitação atual. UseForwardedHeaders
Redirecionamento https Redireciona todas as solicitações HTTP para HTTPS. UseHttpsRedirection
Segurança de Transporte Estrita de HTTP (HSTS) Middleware de aprimoramento de segurança que adiciona um cabeçalho de resposta especial. UseHsts
Solicitar registro em log Fornece suporte para registro de solicitações e respostas HTTP. UseHttpLogging
Tempos limite de solicitação Fornece suporte para configurar tempos limite de solicitação, padrão global e por ponto de extremidade. UseRequestTimeouts
Registro em log de solicitação em W3C Fornece suporte para registro em log de solicitações e respostas HTTP no formato W3C. UseW3CLogging
Cache de resposta Fornece suporte para as respostas em cache. UseResponseCaching
Compactação de resposta Fornece suporte para a compactação de respostas. UseResponseCompression
Session Fornece suporte para gerenciar sessões de usuário. UseSession
Arquivos estáticos Fornece suporte para servir arquivos estáticos e pesquisa no diretório. UseStaticFiles, UseFileServer
WebSockets Habilita o protocolo WebSockets. UseWebSockets

As seções a seguir abrangem o tratamento de solicitações: roteamento, associação de parâmetros e respostas.

Routing

Um WebApplication configurado dá suporte a Map{Verb} e MapMethods, onde {Verb} é um método HTTP com maiúsculas e minúsculas, como Get, Post, Put ou 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();

Os argumentos Delegate passados para esses métodos são chamados de "manipuladores de rota".

Manipuladores de Rotas

Manipuladores de rotas são métodos executados quando a rota corresponde. Os manipuladores de rotas podem ser uma expressão lambda, uma função local, um método de instância ou um método estático. Os manipuladores de rota podem ser síncronos ou assíncronos.

Expressão Lambda

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

Função local

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

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

app.MapGet("/", LocalFunction);

app.Run();

Método de instância

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

Método estático

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

Ponto de extremidade definido fora do Program.cs

As APIs mínimas não precisam estar localizadas em 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" });
        });
    }
}

Também consulte Grupos de rota mais adiante neste artigo.

Os pontos de extremidade podem receber nomes para gerar URLs para o ponto de extremidade. O uso de um ponto de extremidade nomeado evita ter que codificar caminhos em um aplicativo:

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

O código anterior exibe The link to the hello route is /hello do ponto de extremidade /.

OBSERVAÇÃO: os nomes de ponto de extremidade diferenciam maiúsculas e minúsculas.

Nomes de ponto de extremidade:

  • Deve ser globalmente exclusivo.
  • São usados como a ID da operação OpenAPI quando o suporte ao OpenAPI está habilitado. Para obter mais informações, confira OpenAPI.

Parâmetros de rota

Os parâmetros de rota podem ser capturados como parte da definição do padrão de rota:

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

O código anterior retorna The user id is 3 and book id is 7 do URI /users/3/books/7.

O manipulador de rotas pode declarar os parâmetros a serem capturados. Quando uma solicitação é feita em uma rota com parâmetros declarados para captura, os parâmetros são analisados e passados para o manipulador. Isso facilita a captura dos valores de uma maneira segura de tipo. No código anterior, userId e bookId são int.

No código anterior, se qualquer valor de rota não puder ser convertido em um int, uma exceção será gerada. A solicitação GET /users/hello/books/3 gera a seguinte exceção:

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

Caractere curinga e capturar todas as rotas

A rota catch-all a seguir retorna Routing to hello do ponto de extremidade '/posts/hello':

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

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

app.Run();

Restrições da rota

As restrições de rota restringem o comportamento correspondente de uma rota.

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

A tabela a seguir demonstra modelos de rota anteriores e seu comportamento:

Modelo de rota URI de correspondência de exemplo
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para obter mais informações, confira Referência de restrição de rota no Roteamento no ASP.NET Core.

Grupos de rotas

O método de extensão MapGroup ajuda a organizar grupos de pontos de extremidade com um prefixo comum. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata, que adicionam os metadados de ponto de extremidade.

Por exemplo, o código a seguir cria dois grupos de pontos de extremidade semelhantes:

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

Nesse cenário, você pode usar um endereço relativo para o cabeçalho Location no resultado 201 Created:

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

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

O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /public/todos e estará acessível sem autenticação. O segundo grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodosfábrica de filtro de ponto de extremidade é uma função local que modifica os TodoDb parâmetros do manipulador de rota para permitir o acesso e armazenamento de dados privados de tarefas.

Os grupos de rotas também permitem grupos aninhados e padrões de prefixo complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de rotas mapeado para o grupo user pode capturar os parâmetros de rota {org} e {group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou filtros de ponto de extremidade a um grupo de pontos de extremidade, sem alterar o padrão de rota.

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

Adicionar filtros ou metadados a um grupo é igual a adicioná-los individualmente a cada ponto de extremidade, antes de adicionar filtros ou metadados extras que possam ter sido adicionados a um grupo interno ou ponto de extremidade específico.

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

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro interno, mesmo que tenha sido adicionado depois. Como os filtros foram aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos outros não importa. A ordem em que os filtros são adicionados importa se aplicados ao mesmo grupo ou ponto de extremidade específico.

Uma solicitação para /outer/inner/ registrará o seguinte:

/outer group filter
/inner group filter
MapGet filter

Associação de parâmetros

Associação de parâmetro é o processo de converter dados de solicitação em parâmetros fortemente tipados que são expressos por manipuladores de rota. A origem de uma associação determina de onde os parâmetros são associados. As origens de associação podem ser explícitas ou inferidas com base no método HTTP e no tipo de parâmetro.

Origens de associação com suporte:

  • Valores de rota
  • Cadeia de consulta
  • Header
  • Corpo (como JSON)
  • Valores de formulário
  • Serviços fornecidos pela injeção de dependência
  • Custom

O seguinte manipulador de rotas GET usa algumas dessas fontes de associação de parâmetros:

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

Principais recursos de associação de parâmetro

  • Associação explícita: use atributos como [FromRoute], [FromQuery], [FromHeader], , [FromBody][FromForm]e [FromServices] para especificar explicitamente fontes de associação.
  • : usar o atributo para associar os valores do formulário, incluindo suporte para e para uploads de arquivo.
  • Tipos complexos: Vincular a coleções e tipos complexos de formulários, strings de consulta e cabeçalhos.
  • Associação personalizada: implemente a lógica de associação personalizada usando TryParse, BindAsyncou a IBindableFromHttpContext<T> interface.
  • Parâmetros opcionais: dão suporte a tipos anuláveis e valores padrão para parâmetros opcionais.
  • Injeção de dependência: os parâmetros são automaticamente associados a serviços registrados no contêiner de DI.
  • Tipos especiais: associação automática para HttpContext, HttpRequest, HttpResponse, CancellationToken, , ClaimsPrincipal, Streame PipeReader.

Saiba Mais: Para obter informações detalhadas sobre a associação de parâmetros, incluindo cenários avançados, validação, precedência de associação e solução de problemas, consulte Associação de parâmetros em aplicativos de API mínimos.

Responses

Os manipuladores de rota dão suporte aos seguintes tipos de valores retornados:

  1. Baseado em IResult - Inclui Task<IResult> e ValueTask<IResult>
  2. string - Inclui Task<string> e ValueTask<string>
  3. T (Qualquer outro tipo) – inclui Task<T> e ValueTask<T>
Valor de retorno Behavior Content-Type
IResult A estrutura chama IResult.ExecuteAsync Decidido pela implementação de IResult
string A estrutura grava a cadeia de caracteres diretamente na resposta text/plain
T (Qualquer outro tipo) A estrutura JSON serializa a resposta application/json

Obtenha um guia mais detalhado dos valores retornados do manipulador de rota em Crie respostas em aplicativos de APIs mínimas

Exemplo de valores retornados

valores retornados de cadeias de caracteres

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

Valores JSON retornados

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

Retornar TypedResults

O código a seguir retorna um TypedResults:

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

É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Valores retornados de IResult

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

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:

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

Código de status personalizado

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

Texto

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

Stream

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

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Redirect

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

File

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

Resultados internos

Auxiliares de resultados comuns existem no Results e nas classes estáticas TypedResults. É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Modificando cabeçalhos

Use o objeto HttpResponse para modificar cabeçalhos de resposta:

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

Personalizando resultados

Aplicativos podem controlar as respostas implementando um tipo IResult personalizado. O código a seguir é um exemplo de um tipo de resultado HTML:

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

Recomenda-se adicionar um método de extensão a Microsoft.AspNetCore.Http.IResultExtensions para tornar esses resultados personalizados mais detectáveis.

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

Resultados tipado

A interface IResult pode representar valores retornados de APIs mínimas que não aproveitam o suporte implícito para JSON, serializando o objeto retornado para a resposta HTTP. A classe estática Results é usada para criar objetos IResult variados que representam diferentes tipos de respostas. Por exemplo, configurando o código de status de resposta ou redirecionando para outra URL.

Os tipos de implementação IResult são públicos, permitindo instruções de declaração de tipo durante o teste. Por exemplo:

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

É possível examinar os tipos de retorno dos métodos correspondentes na classe estática TypedResults e localizar o tipo público IResult correto para converter.

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Filters

Para obter mais informações, veja Filtros em aplicativos API mínimos.

Authorization

Rotas podem ser protegidas por meio de políticas de autorização. É possível declará-las por meio do atributo [Authorize] ou utilizando o método 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();

O código anterior pode ser gravado com RequireAuthorization:

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

O exemplo a seguir utiliza a autorização baseada em políticas:

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

Permita o acesso de usuários não autenticados a um ponto de extremidade

O [AllowAnonymous] permite o acesso de usuários não autenticados a pontos de extremidade:

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


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

CORS

Rotas podem ser habilitadas para CORS com as políticas CORS. O CORS pode ser declarado por meio do atributo [EnableCors] ou utilizando o método RequireCors. As seguintes amostras habilitam o 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();

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core

ValidateScopes e ValidateOnBuild

ValidateScopes e ValidateOnBuild são habilitados por padrão no ambiente de Desenvolvimento, mas desabilitados em outros ambientes.

Quando ValidateOnBuild é true, o contêiner de DI valida a configuração do serviço no momento do build. Se a configuração do serviço for inválida, o build falhará na inicialização do aplicativo, em vez de em runtime, quando o serviço for solicitado.

Quando ValidateScopes é true, o contêiner de DI valida que um serviço com escopo não está resolvido no escopo raiz. Resolver um serviço com escopo no escopo raiz pode resultar em perda de memória porque o serviço é mantido na memória por mais tempo do que o escopo da solicitação.

ValidateScopes e ValidateOnBuild são falsos por padrão nos modos que não são de desenvolvimento por questão de desempenho.

O código a seguir mostra que ValidateScopes está habilitada por padrão no modo de desenvolvimento, mas desabilitada no modo de versão:

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

O código a seguir mostra que ValidateOnBuild está habilitada por padrão no modo de desenvolvimento, mas desabilitada no modo de versão:

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

O código a seguir desabilita ValidateScopes e ValidateOnBuild em 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;
    });
}

Consulte também

Este documento:

As APIs mínimas consistem em:

WebApplication

O código a seguir é gerado por um modelo do ASP.NET Core:

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

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

app.Run();

O código anterior pode ser criado por meio de dotnet new web na linha de comando ou da seleção do modelo Empty Web no Visual Studio.

O código a seguir cria um WebApplication (app) sem criar explicitamente um WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create inicializa uma nova instância da classe WebApplication com padrões pré-configurados.

WebApplication adiciona automaticamente o seguinte middleware em aplicativos de API mínima , dependendo de determinadas condições:

  • UseDeveloperExceptionPage é adicionado primeiro quando o HostingEnvironment é "Development".
  • UseRouting será adicionado em segundo se o código do usuário ainda não tiver chamado UseRouting e se houver pontos de extremidade configurados, por exemplo app.MapGet.
  • UseEndpoints será adicionado no final do pipeline de middleware se algum ponto de extremidade estiver configurado.
  • UseAuthentication será adicionado imediatamente após UseRouting se o código do usuário ainda não tiver chamado UseAuthentication e se IAuthenticationSchemeProvider puder ser detectado no provedor de serviços. IAuthenticationSchemeProvider é adicionado por padrão ao usar AddAuthentication, e os serviços são detectados usando IServiceProviderIsService.
  • UseAuthorization será adicionado em seguida se o código do usuário ainda não tiver chamado UseAuthorization e se IAuthorizationHandlerProvider puder ser detectado no provedor de serviços. IAuthorizationHandlerProvider é adicionado por padrão ao usar AddAuthorization, e os serviços são detectados usando IServiceProviderIsService.
  • O middleware e os pontos de extremidade configurados pelo usuário são adicionados entre UseRouting e UseEndpoints.

O código a seguir é de fato aquilo que o middleware automático que está sendo adicionado ao aplicativo produz:

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

Em alguns casos, a configuração de middleware padrão não é a correta para o aplicativo e requer modificação. Por exemplo, UseCors deve ser chamado antes de UseAuthentication e de UseAuthorization. O aplicativo precisa chamar UseAuthentication e UseAuthorization se UseCors for chamado:

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

Se o middleware tiver que ser executado antes da correspondência de rotas ocorrer, UseRouting deverá ser chamado e o middleware deverá ser colocado antes da chamada para UseRouting. UseEndpoints não é necessário nesse caso, já que é adicionado automaticamente conforme descrito anteriormente:

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

app.UseRouting();

// other middleware and endpoints

Ao adicionar um middleware de terminal:

  • O middleware deve ser adicionado após UseEndpoints.
  • O aplicativo precisa chamar UseRouting e UseEndpoints para que o middleware do terminal possa ser colocado no local correto.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

O middleware de terminal é um middleware que é executado se nenhum ponto de extremidade lidar com a solicitação.

Trabalho com portas

Quando um aplicativo Web é criado com o Visual Studio ou com o dotnet new, aparece um arquivo Properties/launchSettings.json que especifica as portas às quais responde aplicativo. Nos exemplos de configuração de portas a seguir, a execução do aplicativo no Visual Studio retorna uma caixa de diálogo de erro Unable to connect to web server 'AppName'. O Visual Studio retorna um erro porque espera a porta especificada em Properties/launchSettings.json, mas o aplicativo está usando a porta especificada por app.Run("http://localhost:3000"). Execute a porta a seguir, alterando exemplos da linha de comando.

As seções a seguir definem a porta à qual responde o aplicativo.

var app = WebApplication.Create(args);

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

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

No código anterior, o aplicativo responde à porta 3000.

Várias portas

No código a seguir, o aplicativo responde às portas 3000 e 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Defina a porta na linha de comando

O comando a seguir faz com que o aplicativo responda à porta 7777:

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

Se o ponto de extremidade Kestrel também estiver configurado no arquivo appsettings.json, a URL especificada do arquivo appsettings.jsonserá usada. Para obter mais informações, consulte Configuração de ponto de extremidade Kestrel

Leia a porta do ambiente

O código a seguir lê a porta do ambiente:

var app = WebApplication.Create(args);

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

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

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

A maneira preferencial de definir a porta do ambiente é usar a variável de ambiente ASPNETCORE_URLS, que é mostrada na seção a seguir.

Defina as portas por meio da variável de ambiente ASPNETCORE_URLS

A variável de ambiente ASPNETCORE_URLS está disponível para definir a porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS dá suporte a várias URLs:

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

Escute em todas as interfaces

Os exemplos a seguir demonstram a escuta em todas as interfaces

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

Escute em todas as interfaces usando ASPNETCORE_URLS

Os exemplos anteriores podem usar ASPNETCORE_URLS

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

Escute em todas as interfaces usando ASPNETCORE_HTTPS_PORTS

Os exemplos anteriores podem usar ASPNETCORE_HTTPS_PORTS e ASPNETCORE_HTTP_PORTS.

ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Para obter mais informações, consulte Configure pontos de extremidade para o Kestrel servidor de rede ASP.NET Core

Especifique HTTPS com certificado de desenvolvimento

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações sobre o certificado de desenvolvimento, consulte Confie no certificado de desenvolvimento HTTPS ASP.NET Core no Windows e no macOS.

Especifique HTTPS usando um certificado personalizado

As seções a seguir mostram como especificar o certificado personalizado usando o arquivo appsettings.json e por meio de configuração.

Especifique o certificado personalizado com appsettings.json

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

Especifique o certificado personalizado por meio de configuração

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

Use as APIs de certificado

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

Leia o ambiente

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

Para obter mais informações sobre como usar o ambiente, consulte ASP.NET Ambientes de runtime do Core

Configuration

O código a seguir lê a partir do sistema de configuração:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Configuração no ASP.NET Core

Logging

O código a seguir grava uma mensagem na inicialização do aplicativo de logon:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Log no .NET e no ASP.NET Core

Acesse o contêiner Injeção de Dependência (DI)

O código a seguir mostra como obter serviços do contêiner de DI durante a inicialização do aplicativo:


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

O código a seguir mostra como acessar chaves do contêiner de DI usando o atributo [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.";
}

Para obter mais informações sobre DI, confira Injeção de dependência no ASP.NET Core.

WebApplicationBuilder

Esta seção contém o código de exemplo usando o WebApplicationBuilder.

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente

O código a seguir define a raiz do conteúdo, o nome do aplicativo e o ambiente:

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

O WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com os padrões pré-configurados.

Para obter mais informações, consulte visão geral dos conceitos básicos do ASP.NET Core

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente usando variáveis de ambiente ou linha de comando

A tabela a seguir mostra a variável de ambiente e o argumento de linha de comando usados para alterar a raiz do conteúdo, o nome do aplicativo e o ambiente:

funcionalidade Variável de ambiente Argumento de linha de comando
Nome do aplicativo ASPNETCORE_APPLICATIONNAME --applicationName
Nome do ambiente ASPNETCORE_ENVIRONMENT --environment
Raiz de conteúdo ASPNETCORE_CONTENTROOT --contentRoot

Adicionar provedores de configuração

O exemplo a seguir adiciona o provedor de configuração INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Para maior detalhamento, consulte Provedores de configuração de arquivo em Configuração no ASP.NET Core.

Configuração de leitura

Por padrão, o WebApplicationBuilder lê a configuração de várias fontes, incluindo:

  • appSettings.json e appSettings.{environment}.json
  • Variáveis de ambiente
  • A linha de comando

Para obter uma lista completa das fontes de configuração lidas, confira a Configuração padrão em Configuração no ASP.NET Core.

O código a seguir lê HelloKey a partir da configuração e exibe o valor no ponto de extremidade/. Se o valor de configuração for nulo, "Hello" será atribuído a message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Leia o ambiente

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Adicione provedores de login

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

Adicionar serviços

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

Personalize o IHostBuilder

Os métodos de extensão existentes no IHostBuilder podem ser acessados usando a propriedade 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();

Personalize o IWebHostBuilder

Os métodos de extensão no IWebHostBuilder podem ser acessados usando a propriedade 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();

Altere a raiz da Web

Por padrão, a raiz da Web refere-se à raiz do conteúdo na pasta wwwroot. Raiz da Web é onde o Middleware de Arquivo Estático procura arquivos estáticos. A raiz da Web pode ser alterada com o WebHostOptions, com a linha de comando ou com o método UseWebRoot:

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

var app = builder.Build();

app.Run();

Personalize o contêiner de Injeção de Dependência (DI)

O exemplo a seguir usa o 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();

Adicionar middleware

Qualquer middleware do ASP.NET Core pode ser configurado no WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Middleware do ASP.NET Core

Página de exceção do desenvolvedor

WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com padrões pré-configurados. A página de exceção do desenvolvedor está habilitada nos padrões pré-configurados. Quando o código a seguir é executado no ambiente de desenvolvimento, navegar para / renderiza uma página amigável que mostra a exceção.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Middleware do ASP.NET Core

A tabela a seguir lista alguns dos middleware usados com frequência com as APIs mínimas.

Middleware Description API
Authentication Fornece suporte à autenticação. UseAuthentication
Authorization Fornece suporte à autorização. UseAuthorization
CORS Configura o Compartilhamento de Recursos entre Origens. UseCors
Manipulador de exceção Lida globalmente com exceções geradas pelo pipeline de middleware. UseExceptionHandler
Cabeçalhos encaminhados Encaminha cabeçalhos como proxy para a solicitação atual. UseForwardedHeaders
Redirecionamento https Redireciona todas as solicitações HTTP para HTTPS. UseHttpsRedirection
Segurança de Transporte Estrita de HTTP (HSTS) Middleware de aprimoramento de segurança que adiciona um cabeçalho de resposta especial. UseHsts
Solicitar registro em log Fornece suporte para registro de solicitações e respostas HTTP. UseHttpLogging
Tempos limite de solicitação Fornece suporte para configurar tempos limite de solicitação, padrão global e por ponto de extremidade. UseRequestTimeouts
Registro em log de solicitação em W3C Fornece suporte para registro em log de solicitações e respostas HTTP no formato W3C. UseW3CLogging
Cache de resposta Fornece suporte para as respostas em cache. UseResponseCaching
Compactação de resposta Fornece suporte para a compactação de respostas. UseResponseCompression
Session Fornece suporte para gerenciar sessões de usuário. UseSession
Arquivos estáticos Fornece suporte para servir arquivos estáticos e pesquisa no diretório. UseStaticFiles, UseFileServer
WebSockets Habilita o protocolo WebSockets. UseWebSockets

As seções a seguir abrangem o tratamento de solicitações: roteamento, associação de parâmetros e respostas.

Routing

Um WebApplication configurado dá suporte a Map{Verb} e MapMethods, onde {Verb} é um método HTTP com maiúsculas e minúsculas, como Get, Post, Put ou 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();

Os argumentos Delegate passados para esses métodos são chamados de "manipuladores de rota".

Manipuladores de Rotas

Manipuladores de rotas são métodos executados quando a rota corresponde. Os manipuladores de rotas podem ser uma expressão lambda, uma função local, um método de instância ou um método estático. Os manipuladores de rota podem ser síncronos ou assíncronos.

Expressão Lambda

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

Função local

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

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

app.MapGet("/", LocalFunction);

app.Run();

Método de instância

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

Método estático

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

Ponto de extremidade definido fora do Program.cs

As APIs mínimas não precisam estar localizadas em 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" });
        });
    }
}

Também consulte Grupos de rota mais adiante neste artigo.

Os pontos de extremidade podem receber nomes para gerar URLs para o ponto de extremidade. O uso de um ponto de extremidade nomeado evita ter que codificar caminhos em um aplicativo:

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

O código anterior exibe The link to the hello route is /hello do ponto de extremidade /.

OBSERVAÇÃO: os nomes de ponto de extremidade diferenciam maiúsculas e minúsculas.

Nomes de ponto de extremidade:

  • Deve ser globalmente exclusivo.
  • São usados como a ID da operação OpenAPI quando o suporte ao OpenAPI está habilitado. Para obter mais informações, confira OpenAPI.

Parâmetros de rota

Os parâmetros de rota podem ser capturados como parte da definição do padrão de rota:

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

O código anterior retorna The user id is 3 and book id is 7 do URI /users/3/books/7.

O manipulador de rotas pode declarar os parâmetros a serem capturados. Quando uma solicitação é feita em uma rota com parâmetros declarados para captura, os parâmetros são analisados e passados para o manipulador. Isso facilita a captura dos valores de uma maneira segura de tipo. No código anterior, userId e bookId são int.

No código anterior, se qualquer valor de rota não puder ser convertido em um int, uma exceção será gerada. A solicitação GET /users/hello/books/3 gera a seguinte exceção:

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

Caractere curinga e capturar todas as rotas

A rota catch-all a seguir retorna Routing to hello do ponto de extremidade '/posts/hello':

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

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

app.Run();

Restrições da rota

As restrições de rota restringem o comportamento correspondente de uma rota.

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

A tabela a seguir demonstra modelos de rota anteriores e seu comportamento:

Modelo de rota URI de correspondência de exemplo
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para obter mais informações, confira Referência de restrição de rota no Roteamento no ASP.NET Core.

Grupos de rotas

O método de extensão MapGroup ajuda a organizar grupos de pontos de extremidade com um prefixo comum. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata, que adicionam os metadados de ponto de extremidade.

Por exemplo, o código a seguir cria dois grupos de pontos de extremidade semelhantes:

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

Nesse cenário, você pode usar um endereço relativo para o cabeçalho Location no resultado 201 Created:

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

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

O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /public/todos e estará acessível sem autenticação. O segundo grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodosfábrica de filtro de ponto de extremidade é uma função local que modifica os TodoDb parâmetros do manipulador de rota para permitir o acesso e armazenamento de dados privados de tarefas.

Os grupos de rotas também permitem grupos aninhados e padrões de prefixo complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de rotas mapeado para o grupo user pode capturar os parâmetros de rota {org} e {group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou filtros de ponto de extremidade a um grupo de pontos de extremidade, sem alterar o padrão de rota.

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

Adicionar filtros ou metadados a um grupo é igual a adicioná-los individualmente a cada ponto de extremidade, antes de adicionar filtros ou metadados extras que possam ter sido adicionados a um grupo interno ou ponto de extremidade específico.

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

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro interno, mesmo que tenha sido adicionado depois. Como os filtros foram aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos outros não importa. A ordem em que os filtros são adicionados importa se aplicados ao mesmo grupo ou ponto de extremidade específico.

Uma solicitação para /outer/inner/ registrará o seguinte:

/outer group filter
/inner group filter
MapGet filter

Associação de parâmetros

Associação de parâmetro é o processo de converter dados de solicitação em parâmetros fortemente tipados que são expressos por manipuladores de rota. A origem de uma associação determina de onde os parâmetros são associados. As origens de associação podem ser explícitas ou inferidas com base no método HTTP e no tipo de parâmetro.

Origens de associação com suporte:

  • Valores de rota
  • Cadeia de consulta
  • Header
  • Corpo (como JSON)
  • Valores de formulário
  • Serviços fornecidos pela injeção de dependência
  • Custom

O seguinte manipulador de rotas GET usa algumas dessas fontes de associação de parâmetros:

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

Principais recursos de associação de parâmetro

  • Associação explícita: use atributos como [FromRoute], [FromQuery], [FromHeader], , [FromBody][FromForm]e [FromServices] para especificar explicitamente fontes de associação.
  • : usar o atributo para associar os valores do formulário, incluindo suporte para e para uploads de arquivo.
  • Tipos complexos: Vincular a coleções e tipos complexos de formulários, strings de consulta e cabeçalhos.
  • Associação personalizada: implemente a lógica de associação personalizada usando TryParse, BindAsyncou a IBindableFromHttpContext<T> interface.
  • Parâmetros opcionais: dão suporte a tipos anuláveis e valores padrão para parâmetros opcionais.
  • Injeção de dependência: os parâmetros são automaticamente associados a serviços registrados no contêiner de DI.
  • Tipos especiais: associação automática para HttpContext, HttpRequest, HttpResponse, CancellationToken, , ClaimsPrincipal, Streame PipeReader.

Saiba Mais: Para obter informações detalhadas sobre a associação de parâmetros, incluindo cenários avançados, validação, precedência de associação e solução de problemas, consulte Associação de parâmetros em aplicativos de API mínimos.

Responses

Os manipuladores de rota dão suporte aos seguintes tipos de valores retornados:

  1. Baseado em IResult - Inclui Task<IResult> e ValueTask<IResult>
  2. string - Inclui Task<string> e ValueTask<string>
  3. T (Qualquer outro tipo) – inclui Task<T> e ValueTask<T>
Valor de retorno Behavior Content-Type
IResult A estrutura chama IResult.ExecuteAsync Decidido pela implementação de IResult
string A estrutura grava a cadeia de caracteres diretamente na resposta text/plain
T (Qualquer outro tipo) A estrutura JSON serializa a resposta application/json

Obtenha um guia mais detalhado dos valores retornados do manipulador de rota em Crie respostas em aplicativos de APIs mínimas

Exemplo de valores retornados

valores retornados de cadeias de caracteres

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

Valores JSON retornados

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

Retornar TypedResults

O código a seguir retorna um TypedResults:

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

É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Valores retornados de IResult

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

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:

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

Código de status personalizado

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

Texto

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

Stream

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

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Redirect

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

File

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

Resultados internos

Auxiliares de resultados comuns existem no Results e nas classes estáticas TypedResults. É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Modificando cabeçalhos

Use o objeto HttpResponse para modificar cabeçalhos de resposta:

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

Personalizando resultados

Aplicativos podem controlar as respostas implementando um tipo IResult personalizado. O código a seguir é um exemplo de um tipo de resultado HTML:

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

Recomenda-se adicionar um método de extensão a Microsoft.AspNetCore.Http.IResultExtensions para tornar esses resultados personalizados mais detectáveis.

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

Resultados tipado

A interface IResult pode representar valores retornados de APIs mínimas que não aproveitam o suporte implícito para JSON, serializando o objeto retornado para a resposta HTTP. A classe estática Results é usada para criar objetos IResult variados que representam diferentes tipos de respostas. Por exemplo, configurando o código de status de resposta ou redirecionando para outra URL.

Os tipos de implementação IResult são públicos, permitindo instruções de declaração de tipo durante o teste. Por exemplo:

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

É possível examinar os tipos de retorno dos métodos correspondentes na classe estática TypedResults e localizar o tipo público IResult correto para converter.

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Filters

Para obter mais informações, veja Filtros em aplicativos API mínimos.

Authorization

Rotas podem ser protegidas por meio de políticas de autorização. É possível declará-las por meio do atributo [Authorize] ou utilizando o método 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();

O código anterior pode ser gravado com RequireAuthorization:

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

O exemplo a seguir utiliza a autorização baseada em políticas:

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

Permita o acesso de usuários não autenticados a um ponto de extremidade

O [AllowAnonymous] permite o acesso de usuários não autenticados a pontos de extremidade:

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


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

CORS

Rotas podem ser habilitadas para CORS com as políticas CORS. O CORS pode ser declarado por meio do atributo [EnableCors] ou utilizando o método RequireCors. As seguintes amostras habilitam o 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();

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core

ValidateScopes e ValidateOnBuild

ValidateScopes e ValidateOnBuild são habilitados por padrão no ambiente de Desenvolvimento, mas desabilitados em outros ambientes.

Quando ValidateOnBuild é true, o contêiner de DI valida a configuração do serviço no momento do build. Se a configuração do serviço for inválida, o build falhará na inicialização do aplicativo, em vez de em runtime, quando o serviço for solicitado.

Quando ValidateScopes é true, o contêiner de DI valida que um serviço com escopo não está resolvido no escopo raiz. Resolver um serviço com escopo no escopo raiz pode resultar em perda de memória porque o serviço é mantido na memória por mais tempo do que o escopo da solicitação.

ValidateScopes e ValidateOnBuild são falsos por padrão nos modos que não são de desenvolvimento por questão de desempenho.

O código a seguir mostra que ValidateScopes está habilitada por padrão no modo de desenvolvimento, mas desabilitada no modo de versão:

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

O código a seguir mostra que ValidateOnBuild está habilitada por padrão no modo de desenvolvimento, mas desabilitada no modo de versão:

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

O código a seguir desabilita ValidateScopes e ValidateOnBuild em 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;
    });
}

Consulte também

Este documento:

As APIs mínimas consistem em:

WebApplication

O código a seguir é gerado por um modelo do ASP.NET Core:

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

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

app.Run();

O código anterior pode ser criado por meio de dotnet new web na linha de comando ou da seleção do modelo Empty Web no Visual Studio.

O código a seguir cria um WebApplication (app) sem criar explicitamente um WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create inicializa uma nova instância da classe WebApplication com padrões pré-configurados.

WebApplication adiciona automaticamente o seguinte middleware em aplicativos de API mínima , dependendo de determinadas condições:

  • UseDeveloperExceptionPage é adicionado primeiro quando o HostingEnvironment é "Development".
  • UseRouting será adicionado em segundo se o código do usuário ainda não tiver chamado UseRouting e se houver pontos de extremidade configurados, por exemplo app.MapGet.
  • UseEndpoints será adicionado no final do pipeline de middleware se algum ponto de extremidade estiver configurado.
  • UseAuthentication será adicionado imediatamente após UseRouting se o código do usuário ainda não tiver chamado UseAuthentication e se IAuthenticationSchemeProvider puder ser detectado no provedor de serviços. IAuthenticationSchemeProvider é adicionado por padrão ao usar AddAuthentication, e os serviços são detectados usando IServiceProviderIsService.
  • UseAuthorization será adicionado em seguida se o código do usuário ainda não tiver chamado UseAuthorization e se IAuthorizationHandlerProvider puder ser detectado no provedor de serviços. IAuthorizationHandlerProvider é adicionado por padrão ao usar AddAuthorization, e os serviços são detectados usando IServiceProviderIsService.
  • O middleware e os pontos de extremidade configurados pelo usuário são adicionados entre UseRouting e UseEndpoints.

O código a seguir é de fato aquilo que o middleware automático que está sendo adicionado ao aplicativo produz:

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

Em alguns casos, a configuração de middleware padrão não é a correta para o aplicativo e requer modificação. Por exemplo, UseCors deve ser chamado antes de UseAuthentication e de UseAuthorization. O aplicativo precisa chamar UseAuthentication e UseAuthorization se UseCors for chamado:

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

Se o middleware tiver que ser executado antes da correspondência de rotas ocorrer, UseRouting deverá ser chamado e o middleware deverá ser colocado antes da chamada para UseRouting. UseEndpoints não é necessário nesse caso, já que é adicionado automaticamente conforme descrito anteriormente:

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

app.UseRouting();

// other middleware and endpoints

Ao adicionar um middleware de terminal:

  • O middleware deve ser adicionado após UseEndpoints.
  • O aplicativo precisa chamar UseRouting e UseEndpoints para que o middleware do terminal possa ser colocado no local correto.
app.UseRouting();

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

app.UseEndpoints(e => {});

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

O middleware de terminal é um middleware que é executado se nenhum ponto de extremidade lidar com a solicitação.

Trabalho com portas

Quando um aplicativo Web é criado com o Visual Studio ou com o dotnet new, aparece um arquivo Properties/launchSettings.json que especifica as portas às quais responde aplicativo. Nos exemplos de configuração de portas a seguir, a execução do aplicativo no Visual Studio retorna uma caixa de diálogo de erro Unable to connect to web server 'AppName'. O Visual Studio retorna um erro porque espera a porta especificada em Properties/launchSettings.json, mas o aplicativo está usando a porta especificada por app.Run("http://localhost:3000"). Execute a porta a seguir, alterando exemplos da linha de comando.

As seções a seguir definem a porta à qual responde o aplicativo.

var app = WebApplication.Create(args);

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

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

No código anterior, o aplicativo responde à porta 3000.

Várias portas

No código a seguir, o aplicativo responde às portas 3000 e 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Defina a porta na linha de comando

O comando a seguir faz com que o aplicativo responda à porta 7777:

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

Se o ponto de extremidade Kestrel também estiver configurado no arquivo appsettings.json, a URL especificada do arquivo appsettings.jsonserá usada. Para obter mais informações, consulte Configuração de ponto de extremidade Kestrel

Leia a porta do ambiente

O código a seguir lê a porta do ambiente:

var app = WebApplication.Create(args);

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

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

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

A maneira preferencial de definir a porta do ambiente é usar a variável de ambiente ASPNETCORE_URLS, que é mostrada na seção a seguir.

Defina as portas por meio da variável de ambiente ASPNETCORE_URLS

A variável de ambiente ASPNETCORE_URLS está disponível para definir a porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS dá suporte a várias URLs:

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

Para obter mais informações sobre como usar o ambiente, consulte ASP.NET Ambientes de runtime do Core

Escute em todas as interfaces

Os exemplos a seguir demonstram a escuta em todas as interfaces

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

Escute em todas as interfaces usando ASPNETCORE_URLS

Os exemplos anteriores podem usar ASPNETCORE_URLS

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

Especifique HTTPS com certificado de desenvolvimento

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações sobre o certificado de desenvolvimento, consulte Confie no certificado de desenvolvimento HTTPS ASP.NET Core no Windows e no macOS.

Especifique HTTPS usando um certificado personalizado

As seções a seguir mostram como especificar o certificado personalizado usando o arquivo appsettings.json e por meio de configuração.

Especifique o certificado personalizado com appsettings.json

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

Especifique o certificado personalizado por meio de configuração

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

Use as APIs de certificado

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

Configuration

O código a seguir lê a partir do sistema de configuração:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Configuração no ASP.NET Core

Logging

O código a seguir grava uma mensagem na inicialização do aplicativo de logon:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Log no .NET e no ASP.NET Core

Acesse o contêiner Injeção de Dependência (DI)

O código a seguir mostra como obter serviços do contêiner de DI durante a inicialização do aplicativo:


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

Para obter mais informações, consulte Injeção de dependência no ASP.NET Core.

WebApplicationBuilder

Esta seção contém o código de exemplo usando o WebApplicationBuilder.

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente

O código a seguir define a raiz do conteúdo, o nome do aplicativo e o ambiente:

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

O WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com os padrões pré-configurados.

Para obter mais informações, consulte visão geral dos conceitos básicos do ASP.NET Core

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente por variáveis de ambiente ou linha de comando

A tabela a seguir mostra a variável de ambiente e o argumento de linha de comando usados para alterar a raiz do conteúdo, o nome do aplicativo e o ambiente:

funcionalidade Variável de ambiente Argumento de linha de comando
Nome do aplicativo ASPNETCORE_APPLICATIONNAME --applicationName
Nome do ambiente ASPNETCORE_ENVIRONMENT --environment
Raiz de conteúdo ASPNETCORE_CONTENTROOT --contentRoot

Adicionar provedores de configuração

O exemplo a seguir adiciona o provedor de configuração INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Para maior detalhamento, consulte Provedores de configuração de arquivo em Configuração no ASP.NET Core.

Configuração de leitura

Por padrão, o WebApplicationBuilder lê a configuração de várias fontes, incluindo:

  • appSettings.json e appSettings.{environment}.json
  • Variáveis de ambiente
  • A linha de comando

O código a seguir lê HelloKey a partir da configuração e exibe o valor no ponto de extremidade/. Se o valor de configuração for nulo, "Hello" será atribuído a message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Para obter uma lista completa das fontes de configuração lida, consulte Configuração padrão em Configuração no ASP.NET Core

Adicione provedores de login

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

Adicionar serviços

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

Personalize o IHostBuilder

Os métodos de extensão existentes no IHostBuilder podem ser acessados usando a propriedade 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();

Personalize o IWebHostBuilder

Os métodos de extensão no IWebHostBuilder podem ser acessados usando a propriedade 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();

Altere a raiz da Web

Por padrão, a raiz da Web refere-se à raiz do conteúdo na pasta wwwroot. Raiz da Web é onde o Middleware de Arquivo Estático procura arquivos estáticos. A raiz da Web pode ser alterada com o WebHostOptions, com a linha de comando ou com o método UseWebRoot:

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

var app = builder.Build();

app.Run();

Personalize o contêiner de Injeção de Dependência (DI)

O exemplo a seguir usa o 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();

Adicionar middleware

Qualquer middleware do ASP.NET Core pode ser configurado no WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Middleware do ASP.NET Core

Página de exceção do desenvolvedor

WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com padrões pré-configurados. A página de exceção do desenvolvedor está habilitada nos padrões pré-configurados. Quando o código a seguir é executado no ambiente de desenvolvimento, navegar para / renderiza uma página amigável que mostra a exceção.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Middleware do ASP.NET Core

A tabela a seguir lista alguns dos middleware usados com frequência com as APIs mínimas.

Middleware Description API
Authentication Fornece suporte à autenticação. UseAuthentication
Authorization Fornece suporte à autorização. UseAuthorization
CORS Configura o Compartilhamento de Recursos entre Origens. UseCors
Manipulador de exceção Lida globalmente com exceções geradas pelo pipeline de middleware. UseExceptionHandler
Cabeçalhos encaminhados Encaminha cabeçalhos como proxy para a solicitação atual. UseForwardedHeaders
Redirecionamento https Redireciona todas as solicitações HTTP para HTTPS. UseHttpsRedirection
Segurança de Transporte Estrita de HTTP (HSTS) Middleware de aprimoramento de segurança que adiciona um cabeçalho de resposta especial. UseHsts
Solicitar registro em log Fornece suporte para registro de solicitações e respostas HTTP. UseHttpLogging
Tempos limite de solicitação Fornece suporte para configurar tempos limite de solicitação, padrão global e por ponto de extremidade. UseRequestTimeouts
Registro em log de solicitação em W3C Fornece suporte para registro em log de solicitações e respostas HTTP no formato W3C. UseW3CLogging
Cache de resposta Fornece suporte para as respostas em cache. UseResponseCaching
Compactação de resposta Fornece suporte para a compactação de respostas. UseResponseCompression
Session Fornece suporte para gerenciar sessões de usuário. UseSession
Arquivos estáticos Fornece suporte para servir arquivos estáticos e pesquisa no diretório. UseStaticFiles, UseFileServer
WebSockets Habilita o protocolo WebSockets. UseWebSockets

As seções a seguir abrangem o tratamento de solicitações: roteamento, associação de parâmetros e respostas.

Routing

Um WebApplication configurado dá suporte a Map{Verb} e MapMethods, onde {Verb} é um método HTTP com maiúsculas e minúsculas, como Get, Post, Put ou 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();

Os argumentos Delegate passados para esses métodos são chamados de "manipuladores de rota".

Manipuladores de Rotas

Manipuladores de rotas são métodos executados quando a rota corresponde. Os manipuladores de rotas podem ser uma expressão lambda, uma função local, um método de instância ou um método estático. Os manipuladores de rota podem ser síncronos ou assíncronos.

Expressão Lambda

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

Função local

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

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

app.MapGet("/", LocalFunction);

app.Run();

Método de instância

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

Método estático

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

Ponto de extremidade definido fora do Program.cs

As APIs mínimas não precisam estar localizadas em 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" });
        });
    }
}

Também consulte Grupos de rota mais adiante neste artigo.

Os pontos de extremidade podem receber nomes para gerar URLs para o ponto de extremidade. O uso de um ponto de extremidade nomeado evita ter que codificar caminhos em um aplicativo:

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

O código anterior exibe The link to the hello route is /hello do ponto de extremidade /.

OBSERVAÇÃO: os nomes de ponto de extremidade diferenciam maiúsculas e minúsculas.

Nomes de ponto de extremidade:

  • Deve ser globalmente exclusivo.
  • São usados como a ID da operação OpenAPI quando o suporte ao OpenAPI está habilitado. Para obter mais informações, confira OpenAPI.

Parâmetros de rota

Os parâmetros de rota podem ser capturados como parte da definição do padrão de rota:

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

O código anterior retorna The user id is 3 and book id is 7 do URI /users/3/books/7.

O manipulador de rotas pode declarar os parâmetros a serem capturados. Quando uma solicitação é feita em uma rota com parâmetros declarados para captura, os parâmetros são analisados e passados para o manipulador. Isso facilita a captura dos valores de uma maneira segura de tipo. No código anterior, userId e bookId são int.

No código anterior, se qualquer valor de rota não puder ser convertido em um int, uma exceção será gerada. A solicitação GET /users/hello/books/3 gera a seguinte exceção:

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

Caractere curinga e capturar todas as rotas

A rota catch-all a seguir retorna Routing to hello do ponto de extremidade '/posts/hello':

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

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

app.Run();

Restrições da rota

As restrições de rota restringem o comportamento correspondente de uma rota.

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

A tabela a seguir demonstra modelos de rota anteriores e seu comportamento:

Modelo de rota URI de correspondência de exemplo
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para obter mais informações, confira Referência de restrição de rota no Roteamento no ASP.NET Core.

Grupos de rotas

O método de extensão MapGroup ajuda a organizar grupos de pontos de extremidade com um prefixo comum. Isso reduz o código repetitivo e permite personalizar grupos inteiros de pontos de extremidade com uma única chamada a métodos como RequireAuthorization e WithMetadata, que adicionam os metadados de ponto de extremidade.

Por exemplo, o código a seguir cria dois grupos de pontos de extremidade semelhantes:

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

Nesse cenário, você pode usar um endereço relativo para o cabeçalho Location no resultado 201 Created:

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

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

O primeiro grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /public/todos e estará acessível sem autenticação. O segundo grupo de pontos de extremidade corresponderá apenas às solicitações prefixadas com /private/todos e exigirá autenticação.

A QueryPrivateTodosfábrica de filtro de ponto de extremidade é uma função local que modifica os TodoDb parâmetros do manipulador de rota para permitir o acesso e armazenamento de dados privados de tarefas.

Os grupos de rotas também permitem grupos aninhados e padrões de prefixo complexos com parâmetros de rota e restrições. No exemplo a seguir, o manipulador de rotas mapeado para o grupo user pode capturar os parâmetros de rota {org} e {group} definidos nos prefixos do grupo externo.

O prefixo também pode estar vazio. Isso pode ser útil para adicionar metadados ou filtros de ponto de extremidade a um grupo de pontos de extremidade, sem alterar o padrão de rota.

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

Adicionar filtros ou metadados a um grupo é igual a adicioná-los individualmente a cada ponto de extremidade, antes de adicionar filtros ou metadados extras que possam ter sido adicionados a um grupo interno ou ponto de extremidade específico.

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

No exemplo acima, o filtro externo registrará a solicitação de entrada antes do filtro interno, mesmo que tenha sido adicionado depois. Como os filtros foram aplicados a grupos diferentes, a ordem em que foram adicionados em relação uns aos outros não importa. A ordem em que os filtros são adicionados importa se aplicados ao mesmo grupo ou ponto de extremidade específico.

Uma solicitação para /outer/inner/ registrará o seguinte:

/outer group filter
/inner group filter
MapGet filter

Associação de parâmetros

Associação de parâmetro é o processo de converter dados de solicitação em parâmetros fortemente tipados que são expressos por manipuladores de rota. A origem de uma associação determina de onde os parâmetros são associados. As origens de associação podem ser explícitas ou inferidas com base no método HTTP e no tipo de parâmetro.

Origens de associação com suporte:

  • Valores de rota
  • Cadeia de consulta
  • Header
  • Corpo (como JSON)
  • Serviços fornecidos pela injeção de dependência
  • Custom

Não há suporte nativo para associação de valores de formulário no .NET 6 e 7.

O manipulador de rota GET a seguir usa algumas dessas origens de associação de parâmetros:

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

A tabela a seguir mostra a relação entre os parâmetros usados no exemplo anterior e as origens de associação correspondentes.

Parameter Origem da associação
id valor de rota
page cadeia de caracteres de consulta
customHeader cabeçalho
service Fornecido pela injeção de dependência

Os métodos HTTP GET, HEAD, OPTIONS, e DELETE não se associam implicitamente a partir do corpo. Para associar a partir do corpo (como JSON) para esses métodos HTTP, associe explicitamente com [FromBody] ou leia de HttpRequest.

O seguinte exemplo de manipulador de rota POST usa uma origem de associação de corpo (como JSON) para o parâmetro person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Os parâmetros nos exemplos anteriores são todos associados automaticamente a partir de dados de solicitação. Para demonstrar a conveniência que a associação de parâmetros fornece, os seguintes manipuladores de rota mostram como ler dados de solicitação diretamente da solicitação:

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

    // ...
});

Associação de parâmetro explícita

Os atributos podem ser usados para declarar explicitamente de onde os parâmetros são associados.

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 Origem da associação
id valor de rota com o nome id
page cadeia de caracteres de consulta com o nome "p"
service Fornecido pela injeção de dependência
contentType cabeçalho com o nome "Content-Type"

Note

Não há suporte nativo para associação de valores de formulário no .NET 6 e 7.

Associação de parâmetros com injeção de dependência

A associação de parâmetros para APIs mínimas vincula parâmetros por meio de injeção de dependência quando o tipo é configurado como serviço. Não é necessário aplicar explicitamente o atributo [FromServices] a um parâmetro. No código a seguir, ambas as ações retornam a hora:

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

Parâmetros opcionais

Os parâmetros declarados em manipuladores de rota são tratados conforme a exigência:

  • Se uma solicitação corresponder à rota, o manipulador de rota só será executado se todos os parâmetros necessários forem fornecidos na solicitação.
  • O não fornecimento de todos os parâmetros necessários resulta em erro.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

app.Run();
URI result
/products?pageNumber=3 3 retornados
/products BadHttpRequestException: o parâmetro necessário "int pageNumber" não foi recebido da cadeia de caracteres de consulta.
/products/1 Erro HTTP 404, nenhuma rota correspondente

Para tornar pageNumber opcional, defina o tipo como opcional ou forneça um valor padrão:

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 result
/products?pageNumber=3 3 retornados
/products 1 retornado
/products2 1 retornado

O valor padrão e anulável anteriores aplica-se a todas as fontes:

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

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

app.Run();

O código anterior chama o método com um produto nulo se nenhum corpo da solicitação for enviado.

OBSERVAÇÃO: se dados inválidos forem fornecidos e o parâmetro for anulável, o manipulador de rota não será executado.

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

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

app.Run();
URI result
/products?pageNumber=3 retornou 3
/products retornou 1
/products?pageNumber=two BadHttpRequestException: falha ao associar o parâmetro "Nullable<int> pageNumber" de "dois".
/products/two Erro HTTP 404, nenhuma rota correspondente

Obtenha mais informações na seção Falhas de Associação.

Tipos especiais

Os seguintes tipos são associados sem atributos explícitos:

  • HttpContext: o contexto que contém todas as informações sobre a solicitação ou resposta HTTP atual:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest e HttpResponse: a solicitação HTTP e a resposta HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: o token de cancelamento associado à solicitação HTTP atual:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: o usuário associado à solicitação, associado de HttpContext.User:

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

Associar o corpo da solicitação como um Stream ou PipeReader

O corpo da solicitação pode ser associado como um Stream ou PipeReader para dar o suporte necessário a cenários em que o usuário precisa processar dados e:

  • Armazenar os dados no armazenamento de blobs ou enfileirar os dados para um provedor de fila.
  • Processar os dados armazenados com um processo de trabalho ou uma função de nuvem.

Por exemplo, os dados podem ser enfileirados no Armazenamento de Filas do Azure ou armazenados no Armazenamento de Blobs do Azure.

O código a seguir implementa uma fila em segundo plano:

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

O código a seguir associa o corpo da solicitação a um 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);
});

O código a seguir mostra o arquivo Program.cs completo:

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();
  • Ao ler dados, o Stream é o mesmo objeto que HttpRequest.Body.
  • O corpo da solicitação não é armazenado em buffer por padrão. Depois que o corpo for lido, não será possível retrocedê-lo. O fluxo não pode ser lido várias vezes.
  • O Stream e PipeReader não são utilizáveis fora do manipulador de ação mínima, pois os buffers subjacentes serão descartados ou reutilizados.

Uploads de arquivos usando IFormFile e IFormFileCollection

O código a seguir usa IFormFile e IFormFileCollection para carregar o arquivo:

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

Há suporte a solicitações de upload de arquivos autenticados com o uso de um cabeçalho de autorização, um certificado de cliente ou um cabeçalho de cookie.

Não há suporte interno para antiforgeria no ASP.NET Core no .NET 7. A antiforgeria está disponível no ASP.NET Core no .NET 8 ou posterior. No entanto, ele pode ser implementado usando o IAntiforgery serviço.

Associe matrizes e valores de cadeia de caracteres a partir de cabeçalhos e cadeias de caracteres de consulta

O código a seguir demonstra a associação de cadeias de caracteres de consulta a uma matriz de tipos primitivos, matrizes de cadeia de caracteres e 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]}");

Há suporte à associação de cadeias de caracteres de consulta ou valores de cabeçalho a uma matriz de tipos complexos quando o tipo tem TryParse implementado. O código a seguir é associado a uma matriz de cadeia de caracteres e retorna todos os itens com as tags especificadas:

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

O código a seguir mostra o modelo e a implementação TryParse necessária:

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

O código a seguir é associado a uma matriz int:

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

Para testar o código anterior, adicione o seguinte ponto de extremidade para preencher o banco de dados com itens Todo:

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

    return Results.Ok(todos);
});

Use uma ferramenta de teste de API como a HttpRepl passar os seguintes dados para o ponto de extremidade anterior:

[
    {
        "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"
        }
    }
]

O código a seguir associa-se à chave X-Todo-Id de cabeçalho e retorna os itens Todo com valores correspondentes Id:

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

Note

Ao associar um string[] de uma cadeia de caracteres de consulta, a ausência de qualquer valor de cadeia de caracteres de consulta correspondente resultará em uma matriz vazia em vez de um valor nulo.

Associação de parâmetros para listas de argumentos com [AsParameters]

AsParametersAttribute permite a associação de parâmetros simples a tipos e não a model binding complexa ou recursiva.

Considere o código a seguir:

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.

Considere o ponto de extremidade GET a seguir:

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

O seguinte struct pode ser usado para substituir os parâmetros realçados anteriores:

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

O ponto de extremidade refatorado GET usa o anterior struct com o atributo 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());

O código a seguir mostra pontos de extremidade adicionais no aplicativo:

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

As seguintes classes são usadas para refatorar as listas de parâmetros:

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

O código a seguir mostra os pontos de extremidade refatorados usando AsParameters, os struct e classes anteriores:

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

Os seguintes tipos record podem ser usados para substituir os parâmetros anteriores:

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

Usar um struct com AsParameters pode ser mais eficaz do que usar um tipo record.

O código de exemplo completo no repositório AspNetCore.Docs.Samples.

Associação Personalizada

Há três maneiras de personalizar a associação de parâmetros:

  1. Para as origens da associação de rota, de consulta e de cabeçalho, associe tipos personalizados adicionando um método estático TryParse para o tipo.
  2. Controlar o processo de associação implementando um método BindAsync em um tipo.
  3. Para cenários avançados, implemente a IBindableFromHttpContext<TSelf> interface para fornecer lógica de associação personalizada diretamente do HttpContext.

TryParse

TryParse tem duas APIs:

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

O código a seguir exibe Point: 12.3, 10.1 com o 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 tem as seguintes APIs:

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

O código a seguir exibe SortBy:xyz, SortDirection:Desc, CurrentPage:99 com o 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
}

Vinculação de parâmetro personalizado com IBindableFromHttpContext

ASP.NET Core fornece suporte para associação de parâmetro personalizado em APIs mínimas usando a IBindableFromHttpContext<TSelf> interface. Essa interface, introduzida com os membros abstratos estáticos do C# 11, permite criar tipos que podem ser associados a partir de um contexto HTTP diretamente nos parâmetros do manipulador de rotas.

public interface IBindableFromHttpContext<TSelf>
    where TSelf : class, IBindableFromHttpContext<TSelf>
{
    static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}

Implementando a IBindableFromHttpContext<TSelf> interface, você pode criar tipos personalizados que lidam com sua própria lógica de associação do HttpContext. Quando um manipulador de rotas inclui um parâmetro desse tipo, a estrutura chama automaticamente o método BindAsync estático para criar a instância:

using CustomBindingExample;

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

app.UseHttpsRedirection();

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

app.MapGet("/custom-binding", (CustomBoundParameter param) =>
{
    return $"Value from custom binding: {param.Value}";
});

app.MapGet("/combined/{id}", (int id, CustomBoundParameter param) =>
{
    return $"ID: {id}, Custom Value: {param.Value}";
});

Veja a seguir um exemplo de implementação de um parâmetro personalizado que se associa a partir de um cabeçalho HTTP:

using System.Reflection;

namespace CustomBindingExample;

public class CustomBoundParameter : IBindableFromHttpContext<CustomBoundParameter>
{
    public string Value { get; init; } = default!;

    public static ValueTask<CustomBoundParameter?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        // Custom binding logic here
        // This example reads from a custom header
        var value = context.Request.Headers["X-Custom-Header"].ToString();
        
        // If no header was provided, you could fall back to a query parameter
        if (string.IsNullOrEmpty(value))
        {
            value = context.Request.Query["customValue"].ToString();
        }
        
        return ValueTask.FromResult<CustomBoundParameter?>(new CustomBoundParameter 
        {
            Value = value
        });
    }
}

Você também pode implementar a validação dentro de sua lógica de associação personalizada:

app.MapGet("/validated", (ValidatedParameter param) =>
{
    if (string.IsNullOrEmpty(param.Value))
    {
        return Results.BadRequest("Value cannot be empty");
    }
    
    return Results.Ok($"Validated value: {param.Value}");
});

Exibir ou baixar o código de exemplo (como baixar)

Falhas de associação

Quando a associação falha, a estrutura registra uma mensagem de depuração e retorna vários códigos de status ao cliente, dependendo do modo de falha.

Modo de falha Tipo de parâmetro anulável Origem da associação Código de status
{ParameterType}.TryParse retorna false yes route/query/header 400
{ParameterType}.BindAsync retorna null yes custom 400
{ParameterType}.BindAsync gera não importa custom 500
Falha na desserialização do corpo JSON não importa body 400
Tipo de conteúdo incorreto (não application/json) não importa body 415

Precedência de associação

As regras que determinam uma origem de associação de um parâmetro:

  1. Atributo explícito definido no parâmetro (De atributos*) na seguinte ordem:
    1. Valores da rota: [FromRoute]
    2. Cadeia de caracteres de consulta: [FromQuery]
    3. Cabeçalho: [FromHeader]
    4. Corpo: [FromBody]
    5. Serviço: [FromServices]
    6. Valores de parâmetro: [AsParameters]
  2. Tipos especiais
    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. O tipo de parâmetro tem um método estático BindAsync válido.
  4. O tipo de parâmetro é uma cadeia de caracteres ou tem um método estático TryParse válido.
    1. Se o nome do parâmetro existir no modelo de rota. Em app.Map("/todo/{id}", (int id) => {});, id, está vinculado pela rota.
    2. Associação a partir da cadeia de caracteres de consulta.
  5. Se o tipo de parâmetro for um serviço fornecido pela injeção de dependência, ele usará esse serviço como origem.
  6. O parâmetro se origina do corpo.

Configurar opções de desserialização JSON para associação de corpo

A origem da associação do corpo usa System.Text.Json para desserialização. Não é possível alterar esse padrão, mas as opções de serialização e desserialização JSON podem ser configuradas.

Configurar opções de desserialização JSON globalmente

As opções que se aplicam globalmente a um aplicativo podem ser configuradas por meio da invocação de ConfigureHttpJsonOptions. O exemplo a seguir inclui campos públicos e formata a saída JSON.

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

Como o código de exemplo configura a serialização e a desserialização, ele pode ler NameField e incluir NameField na saída JSON.

Configurar opções de desserialização JSON para um ponto de extremidade

ReadFromJsonAsync tem sobrecargas que aceitam um objeto JsonSerializerOptions. O exemplo a seguir inclui campos públicos e formata a saída JSON.

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

Como o código anterior aplica as opções personalizadas somente à desserialização, o JSON de saída exclui NameField.

Leia o corpo da solicitação.

Leia o corpo da solicitação diretamente usando um parâmetro HttpContext ou HttpRequest:

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

O código anterior:

  • Acessa o corpo da solicitação usando HttpRequest.BodyReader.
  • Copia o corpo da solicitação em um arquivo local.

Responses

Os manipuladores de rota dão suporte aos seguintes tipos de valores retornados:

  1. Baseado em IResult - Inclui Task<IResult> e ValueTask<IResult>
  2. string - Inclui Task<string> e ValueTask<string>
  3. T (Qualquer outro tipo) – inclui Task<T> e ValueTask<T>
Valor de retorno Behavior Content-Type
IResult A estrutura chama IResult.ExecuteAsync Decidido pela implementação de IResult
string A estrutura grava a cadeia de caracteres diretamente na resposta text/plain
T (Qualquer outro tipo) A estrutura JSON serializa a resposta application/json

Obtenha um guia mais detalhado dos valores retornados do manipulador de rota em Crie respostas em aplicativos de APIs mínimas

Exemplo de valores retornados

valores retornados de cadeias de caracteres

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

Valores JSON retornados

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

Retornar TypedResults

O código a seguir retorna um TypedResults:

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

É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Valores retornados de IResult

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

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:

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

Código de status personalizado

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

Texto

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

Stream

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

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Redirect

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

File

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

Resultados internos

Auxiliares de resultados comuns existem no Results e nas classes estáticas TypedResults. É preferível retornar TypedResults a retornar Results. Para obter mais informações, consulte TypedResults vs Resultados.

Personalizando resultados

Aplicativos podem controlar as respostas implementando um tipo IResult personalizado. O código a seguir é um exemplo de um tipo de resultado HTML:

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

Recomenda-se adicionar um método de extensão a Microsoft.AspNetCore.Http.IResultExtensions para tornar esses resultados personalizados mais detectáveis.

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

Resultados tipado

A interface IResult pode representar valores retornados de APIs mínimas que não aproveitam o suporte implícito para JSON, serializando o objeto retornado para a resposta HTTP. A classe estática Results é usada para criar objetos IResult variados que representam diferentes tipos de respostas. Por exemplo, configurando o código de status de resposta ou redirecionando para outra URL.

Os tipos de implementação IResult são públicos, permitindo instruções de declaração de tipo durante o teste. Por exemplo:

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

É possível examinar os tipos de retorno dos métodos correspondentes na classe estática TypedResults e localizar o tipo público IResult correto para converter.

Obtenha mais exemplos em Crie respostas em aplicativos de APIs mínimas.

Filters

Consulte Filtros em aplicativos de APIs mínimas

Authorization

Rotas podem ser protegidas por meio de políticas de autorização. É possível declará-las por meio do atributo [Authorize] ou utilizando o método 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();

O código anterior pode ser gravado com RequireAuthorization:

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

O exemplo a seguir utiliza a autorização baseada em políticas:

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

Permita o acesso de usuários não autenticados a um ponto de extremidade

O [AllowAnonymous] permite o acesso de usuários não autenticados a pontos de extremidade:

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


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

CORS

Rotas podem ser habilitadas para CORS com as políticas CORS. O CORS pode ser declarado por meio do atributo [EnableCors] ou utilizando o método RequireCors. As seguintes amostras habilitam o 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();

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core

Consulte também

Este documento:

As APIs mínimas consistem em:

WebApplication

O código a seguir é gerado por um modelo do ASP.NET Core:

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

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

app.Run();

O código anterior pode ser criado por meio de dotnet new web na linha de comando ou da seleção do modelo Empty Web no Visual Studio.

O código a seguir cria um WebApplication (app) sem criar explicitamente um WebApplicationBuilder:

var app = WebApplication.Create(args);

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

app.Run();

WebApplication.Create inicializa uma nova instância da classe WebApplication com padrões pré-configurados.

Trabalho com portas

Quando um aplicativo Web é criado com o Visual Studio ou com o dotnet new, aparece um arquivo Properties/launchSettings.json que especifica as portas às quais responde aplicativo. Nos exemplos de configuração de portas a seguir, a execução do aplicativo no Visual Studio retorna uma caixa de diálogo de erro Unable to connect to web server 'AppName'. Execute a porta a seguir, alterando exemplos da linha de comando.

As seções a seguir definem a porta à qual responde o aplicativo.

var app = WebApplication.Create(args);

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

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

No código anterior, o aplicativo responde à porta 3000.

Várias portas

No código a seguir, o aplicativo responde às portas 3000 e 4000.

var app = WebApplication.Create(args);

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

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

app.Run();

Defina a porta na linha de comando

O comando a seguir faz com que o aplicativo responda à porta 7777:

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

Se o ponto de extremidade Kestrel também estiver configurado no arquivo appsettings.json, a URL especificada do arquivo appsettings.jsonserá usada. Para obter mais informações, consulte Configuração de ponto de extremidade Kestrel

Leia a porta do ambiente

O código a seguir lê a porta do ambiente:

var app = WebApplication.Create(args);

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

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

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

A maneira preferencial de definir a porta do ambiente é usar a variável de ambiente ASPNETCORE_URLS, que é mostrada na seção a seguir.

Defina as portas por meio da variável de ambiente ASPNETCORE_URLS

A variável de ambiente ASPNETCORE_URLS está disponível para definir a porta:

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS dá suporte a várias URLs:

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

Escute em todas as interfaces

Os exemplos a seguir demonstram a escuta em todas as interfaces

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

Escute em todas as interfaces usando ASPNETCORE_URLS

Os exemplos anteriores podem usar ASPNETCORE_URLS

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

Especifique HTTPS com certificado de desenvolvimento

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações sobre o certificado de desenvolvimento, consulte Confie no certificado de desenvolvimento HTTPS ASP.NET Core no Windows e no macOS.

Especifique HTTPS usando um certificado personalizado

As seções a seguir mostram como especificar o certificado personalizado usando o arquivo appsettings.json e por meio de configuração.

Especifique o certificado personalizado com appsettings.json

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

Especifique o certificado personalizado por meio de configuração

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

Use as APIs de certificado

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

Leia o ambiente

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

Para obter mais informações sobre como usar o ambiente, consulte ASP.NET Ambientes de runtime do Core

Configuration

O código a seguir lê a partir do sistema de configuração:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Configuração no ASP.NET Core

Logging

O código a seguir grava uma mensagem na inicialização do aplicativo de logon:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Log no .NET e no ASP.NET Core

Acesse o contêiner Injeção de Dependência (DI)

O código a seguir mostra como obter serviços do contêiner de DI durante a inicialização do aplicativo:


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

Para obter mais informações, consulte Injeção de dependência no ASP.NET Core.

WebApplicationBuilder

Esta seção contém o código de exemplo usando o WebApplicationBuilder.

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente

O código a seguir define a raiz do conteúdo, o nome do aplicativo e o ambiente:

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

O WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com os padrões pré-configurados.

Para obter mais informações, consulte visão geral dos conceitos básicos do ASP.NET Core

Altere a raiz do conteúdo, o nome do aplicativo e o ambiente por variáveis de ambiente ou linha de comando

A tabela a seguir mostra a variável de ambiente e o argumento de linha de comando usados para alterar a raiz do conteúdo, o nome do aplicativo e o ambiente:

funcionalidade Variável de ambiente Argumento de linha de comando
Nome do aplicativo ASPNETCORE_APPLICATIONNAME --applicationName
Nome do ambiente ASPNETCORE_ENVIRONMENT --environment
Raiz de conteúdo ASPNETCORE_CONTENTROOT --contentRoot

Adicionar provedores de configuração

O exemplo a seguir adiciona o provedor de configuração INI:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

Para maior detalhamento, consulte Provedores de configuração de arquivo em Configuração no ASP.NET Core.

Configuração de leitura

Por padrão, o WebApplicationBuilder lê a configuração de várias fontes, incluindo:

  • appSettings.json e appSettings.{environment}.json
  • Variáveis de ambiente
  • A linha de comando

Para obter uma lista completa das fontes de configuração lida, consulte Configuração padrão em Configuração no ASP.NET Core

O código a seguir lê HelloKey a partir da configuração e exibe o valor no ponto de extremidade/. Se o valor de configuração for nulo, "Hello" será atribuído a message:

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Leia o ambiente

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

app.Run();

Adicione provedores de login

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

Adicionar serviços

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

Personalize o IHostBuilder

Os métodos de extensão existentes no IHostBuilder podem ser acessados usando a propriedade 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();

Personalize o IWebHostBuilder

Os métodos de extensão no IWebHostBuilder podem ser acessados usando a propriedade 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();

Altere a raiz da Web

Por padrão, a raiz da Web refere-se à raiz do conteúdo na pasta wwwroot. Raiz da Web é onde o Middleware de Arquivo Estático procura arquivos estáticos. A raiz da Web pode ser alterada com o WebHostOptions, com a linha de comando ou com o método UseWebRoot:

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

var app = builder.Build();

app.Run();

Personalize o contêiner de Injeção de Dependência (DI)

O exemplo a seguir usa o 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();

Adicionar middleware

Qualquer middleware do ASP.NET Core pode ser configurado no WebApplication:

var app = WebApplication.Create(args);

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

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

app.Run();

Para obter mais informações, consulte Middleware do ASP.NET Core

Página de exceção do desenvolvedor

WebApplication.CreateBuilder inicializa uma nova instância da classe WebApplicationBuilder com padrões pré-configurados. A página de exceção do desenvolvedor está habilitada nos padrões pré-configurados. Quando o código a seguir é executado no ambiente de desenvolvimento, navegar para / renderiza uma página amigável que mostra a exceção.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run();

Middleware do ASP.NET Core

A tabela a seguir lista alguns dos middleware usados com frequência com as APIs mínimas.

Middleware Description API
Authentication Fornece suporte à autenticação. UseAuthentication
Authorization Fornece suporte à autorização. UseAuthorization
CORS Configura o Compartilhamento de Recursos entre Origens. UseCors
Manipulador de exceção Lida globalmente com exceções geradas pelo pipeline de middleware. UseExceptionHandler
Cabeçalhos encaminhados Encaminha cabeçalhos como proxy para a solicitação atual. UseForwardedHeaders
Redirecionamento https Redireciona todas as solicitações HTTP para HTTPS. UseHttpsRedirection
Segurança de Transporte Estrita de HTTP (HSTS) Middleware de aprimoramento de segurança que adiciona um cabeçalho de resposta especial. UseHsts
Solicitar registro em log Fornece suporte para registro de solicitações e respostas HTTP. UseHttpLogging
Registro em log de solicitação em W3C Fornece suporte para registro em log de solicitações e respostas HTTP no formato W3C. UseW3CLogging
Cache de resposta Fornece suporte para as respostas em cache. UseResponseCaching
Compactação de resposta Fornece suporte para a compactação de respostas. UseResponseCompression
Session Fornece suporte para gerenciar sessões de usuário. UseSession
Arquivos estáticos Fornece suporte para servir arquivos estáticos e pesquisa no diretório. UseStaticFiles, UseFileServer
WebSockets Habilita o protocolo WebSockets. UseWebSockets

Tratamento de solicitações

As seções a seguir abrangem roteamento, associação de parâmetros e respostas.

Routing

Um WebApplication configurado dá suporte a Map{Verb} e 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();

Manipuladores de Rotas

Manipuladores de rotas são métodos executados quando a rota corresponde. Os manipuladores de rota podem variar de acordo com a forma, sendo síncronos ou assíncronos. Os manipuladores de rotas podem ser uma expressão lambda, uma função local, um método de instância ou um método estático.

Expressão Lambda

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

Função local

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

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

app.MapGet("/", LocalFunction);

app.Run();

Método de instância

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

Método estático

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

Os pontos de extremidade podem receber nomes para gerar URLs para o ponto de extremidade. O uso de um ponto de extremidade nomeado evita ter que codificar caminhos em um aplicativo:

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

O código anterior exibe The link to the hello endpoint is /hello do ponto de extremidade /.

OBSERVAÇÃO: os nomes de ponto de extremidade diferenciam maiúsculas e minúsculas.

Nomes de ponto de extremidade:

  • Deve ser globalmente exclusivo.
  • São usados como a ID da operação OpenAPI quando o suporte ao OpenAPI está habilitado. Para obter mais informações, confira OpenAPI.

Parâmetros de rota

Os parâmetros de rota podem ser capturados como parte da definição do padrão de rota:

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

O código anterior retorna The user id is 3 and book id is 7 do URI /users/3/books/7.

O manipulador de rotas pode declarar os parâmetros a serem capturados. Quando uma solicitação é feita de uma rota com parâmetros declarados para captura, os parâmetros são analisados e passados para o manipulador. Isso facilita a captura dos valores de uma maneira segura de tipo. No código anterior, userId e bookId são int.

No código anterior, se qualquer valor de rota não puder ser convertido em um int, uma exceção será gerada. A solicitação GET /users/hello/books/3 gera a seguinte exceção:

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

Caractere curinga e capturar todas as rotas

A rota catch-all a seguir retorna Routing to hello do ponto de extremidade '/posts/hello':

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

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

app.Run();

Restrições da rota

As restrições de rota restringem o comportamento correspondente de uma rota.

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

A tabela a seguir demonstra modelos de rota anteriores e seu comportamento:

Modelo de rota URI de correspondência de exemplo
/todos/{id:int} /todos/1
/todos/{text} /todos/something
/posts/{slug:regex(^[a-z0-9_-]+$)} /posts/mypost

Para obter mais informações, confira Referência de restrição de rota no Roteamento no ASP.NET Core.

Associação de parâmetro

Associação de parâmetro é o processo de converter dados de solicitação em parâmetros fortemente tipados que são expressos por manipuladores de rota. A origem de uma associação determina de onde os parâmetros são associados. As origens de associação podem ser explícitas ou inferidas com base no método HTTP e no tipo de parâmetro.

Origens de associação com suporte:

  • Valores de rota
  • Cadeia de consulta
  • Header
  • Corpo (como JSON)
  • Serviços fornecidos pela injeção de dependência
  • Custom

Note

Não há suporte nativo para associação de valores de formulário no .NET.

O seguinte exemplo de manipulador de rota GET usa algumas dessas origens de associação de parâmetros:

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

A tabela a seguir mostra a relação entre os parâmetros usados no exemplo anterior e as origens de associação correspondentes.

Parameter Origem da associação
id valor de rota
page cadeia de caracteres de consulta
customHeader cabeçalho
service Fornecido pela injeção de dependência

Os métodos HTTP GET, HEAD, OPTIONS, e DELETE não se associam implicitamente a partir do corpo. Para associar a partir do corpo (como JSON) para esses métodos HTTP, associe explicitamente com [FromBody] ou leia de HttpRequest.

O seguinte exemplo de manipulador de rota POST usa uma origem de associação de corpo (como JSON) para o parâmetro person:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

record Person(string Name, int Age);

Os parâmetros nos exemplos anteriores são todos associados automaticamente a partir de dados de solicitação. Para demonstrar a conveniência oferecida pela associação de parâmetros, os seguintes manipuladores de rota mostram como ler dados de solicitação diretamente da solicitação:

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

    // ...
});

Associação de parâmetro explícita

Os atributos podem ser usados para declarar explicitamente de onde os parâmetros são associados.

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 Origem da associação
id valor de rota com o nome id
page cadeia de caracteres de consulta com o nome "p"
service Fornecido pela injeção de dependência
contentType cabeçalho com o nome "Content-Type"

Note

Não há suporte nativo para associação de valores de formulário no .NET.

Associação de parâmetros com DI

A associação de parâmetros para APIs mínimas vincula parâmetros por meio de injeção de dependência quando o tipo é configurado como serviço. Não é necessário aplicar explicitamente o atributo [FromServices] a um parâmetro. No código a seguir, ambas as ações retornam a hora:

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

Parâmetros opcionais

Os parâmetros declarados em manipuladores de rota são tratados conforme a exigência:

  • Se uma solicitação corresponder à rota, o manipulador de rota só será executado se todos os parâmetros necessários forem fornecidos na solicitação.
  • O não fornecimento de todos os parâmetros necessários resulta em erro.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

app.Run();
URI result
/products?pageNumber=3 3 retornados
/products BadHttpRequestException: o parâmetro necessário "int pageNumber" não foi recebido da cadeia de caracteres de consulta.
/products/1 Erro HTTP 404, nenhuma rota correspondente

Para tornar pageNumber opcional, defina o tipo como opcional ou forneça um valor padrão:

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 result
/products?pageNumber=3 3 retornados
/products 1 retornado
/products2 1 retornado

O valor padrão e anulável anteriores aplica-se a todas as fontes:

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

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

app.Run();

O código anterior chama o método com um produto nulo se nenhum corpo da solicitação for enviado.

OBSERVAÇÃO: se dados inválidos forem fornecidos e o parâmetro for anulável, o manipulador de rota não será executado.

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

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

app.Run();
URI result
/products?pageNumber=3 retornou 3
/products retornou 1
/products?pageNumber=two BadHttpRequestException: falha ao associar o parâmetro "Nullable<int> pageNumber" de "dois".
/products/two Erro HTTP 404, nenhuma rota correspondente

Obtenha mais informações na seção Falhas de Associação.

Tipos especiais

Os seguintes tipos são associados sem atributos explícitos:

  • HttpContext: o contexto que contém todas as informações sobre a solicitação ou resposta HTTP atual:

    app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));
    
  • HttpRequest e HttpResponse: a solicitação HTTP e a resposta HTTP:

    app.MapGet("/", (HttpRequest request, HttpResponse response) =>
        response.WriteAsync($"Hello World {request.Query["name"]}"));
    
  • CancellationToken: o token de cancelamento associado à solicitação HTTP atual:

    app.MapGet("/", async (CancellationToken cancellationToken) => 
        await MakeLongRunningRequestAsync(cancellationToken));
    
  • ClaimsPrincipal: o usuário associado à solicitação, associado de HttpContext.User:

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

Associação Personalizada

Há duas maneiras de personalizar a associação de parâmetros:

  1. Para as origens da associação de rota, de consulta e de cabeçalho, associe tipos personalizados adicionando um método estático TryParse para o tipo.
  2. Controlar o processo de associação implementando um método BindAsync em um tipo.

TryParse

TryParse tem duas APIs:

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

O código a seguir exibe Point: 12.3, 10.1 com o 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 tem as seguintes APIs:

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

O código a seguir exibe SortBy:xyz, SortDirection:Desc, CurrentPage:99 com o 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
}

Falhas de associação

Quando a associação falha, a estrutura registra uma mensagem de depuração e retorna vários códigos de status ao cliente, dependendo do modo de falha.

Modo de falha Tipo de parâmetro anulável Origem da associação Código de status
{ParameterType}.TryParse retorna false yes route/query/header 400
{ParameterType}.BindAsync retorna null yes custom 400
{ParameterType}.BindAsync gera não importa custom 500
Falha na desserialização do corpo JSON não importa body 400
Tipo de conteúdo incorreto (não application/json) não importa body 415

Precedência de associação

As regras que determinam uma origem de associação de um parâmetro:

  1. Atributo explícito definido no parâmetro (De atributos*) na seguinte ordem:
    1. Valores da rota: [FromRoute]
    2. Cadeia de caracteres de consulta: [FromQuery]
    3. Cabeçalho: [FromHeader]
    4. Corpo: [FromBody]
    5. Serviço: [FromServices]
  2. Tipos especiais
    1. HttpContext
    2. HttpRequest (HttpContext.Request)
    3. HttpResponse (HttpContext.Response)
    4. ClaimsPrincipal (HttpContext.User)
    5. CancellationToken (HttpContext.RequestAborted)
  3. O tipo de parâmetro tem um método BindAsync válido.
  4. O tipo de parâmetro é uma cadeia de caracteres ou tem um método TryParse válido.
    1. Se o nome do parâmetro existir no modelo de rota. Em app.Map("/todo/{id}", (int id) => {});, id, está vinculado pela rota.
    2. Associação a partir da cadeia de caracteres de consulta.
  5. Se o tipo de parâmetro for um serviço fornecido pela injeção de dependência, ele usará esse serviço como origem.
  6. O parâmetro se origina do corpo.

Personalize a associação JSON

A origem da associação do corpo usa System.Text.Json para desserialização. Não é possível alterar esse padrão, mas a associação pode ser personalizada usando outras técnicas descritas anteriormente. Para personalizar as opções do serializador JSON, use um código semelhante ao seguinte:

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

O código anterior:

  • Configura as opções JSON padrão de entrada e saída.
  • Retorna o seguinte JSON
    {
      "id": 1,
      "name": "Joe Smith"
    }
    
    Ao postar
    {
      "Id": 1,
      "Name": "Joe Smith"
    }
    

Leia o corpo da solicitação.

Leia o corpo da solicitação diretamente usando um parâmetro HttpContext ou HttpRequest:

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

O código anterior:

  • Acessa o corpo da solicitação usando HttpRequest.BodyReader.
  • Copia o corpo da solicitação em um arquivo local.

Responses

Os manipuladores de rota dão suporte aos seguintes tipos de valores retornados:

  1. Baseado em IResult - Inclui Task<IResult> e ValueTask<IResult>
  2. string - Inclui Task<string> e ValueTask<string>
  3. T (Qualquer outro tipo) – inclui Task<T> e ValueTask<T>
Valor de retorno Behavior Content-Type
IResult A estrutura chama IResult.ExecuteAsync Decidido pela implementação de IResult
string A estrutura grava a cadeia de caracteres diretamente na resposta text/plain
T (Qualquer outro tipo) A estrutura usará JSON para desserializar a resposta application/json

Exemplo de valores retornados

valores retornados de cadeias de caracteres

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

Valores JSON retornados

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

Valores retornados de IResult

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

O exemplo a seguir usa os tipos de resultados internos para personalizar a resposta:

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" }));
Código de status personalizado
app.MapGet("/405", () => Results.StatusCode(405));
Texto
app.MapGet("/text", () => Results.Text("This is some text"));
Stream
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();
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));

Resultados internos

Auxiliares de resultados comuns existem na classe estática Microsoft.AspNetCore.Http.Results.

Description Tipo de resposta Código de status API
Grave uma resposta JSON com opções avançadas application/json 200 Results.Json
Grave uma resposta JSON application/json 200 Results.Ok
Grave uma resposta de texto texto/sem formatação (padrão), configurável 200 Results.Text
Grave a resposta em forma de bytes aplicativo/octet-stream (padrão), configurável 200 Results.Bytes
Grave um fluxo de bytes na resposta aplicativo/octet-stream (padrão), configurável 200 Results.Stream
Transmita um arquivo à resposta para download com o cabeçalho de disposição de conteúdo aplicativo/octet-stream (padrão), configurável 200 Results.File
Configure o código de status para 404, com uma resposta JSON opcional N/A 404 Results.NotFound
Configure o código de status para 204 N/A 204 Results.NoContent
Configure o código de status para 422, com uma resposta JSON opcional N/A 422 Results.UnprocessableEntity
Configure o código de status para 400, com uma resposta JSON opcional N/A 400 Results.BadRequest
Configure o código de status para 409, com uma resposta JSON opcional N/A 409 Results.Conflict
Grave um objeto JSON de detalhes do problema na resposta N/A 500 (padrão), configurável Results.Problem
Grave um objeto JSON de detalhes do problema na resposta com erros de validação N/A N/A, configurável Results.ValidationProblem

Personalizando resultados

Aplicativos podem controlar as respostas implementando um tipo IResult personalizado. O código a seguir é um exemplo de um tipo de resultado HTML:

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

Recomenda-se adicionar um método de extensão a Microsoft.AspNetCore.Http.IResultExtensions para tornar esses resultados personalizados mais detectáveis.

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

Authorization

Rotas podem ser protegidas por meio de políticas de autorização. É possível declará-las por meio do atributo [Authorize] ou utilizando o método 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();

O código anterior pode ser gravado com RequireAuthorization:

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

O exemplo a seguir utiliza a autorização baseada em políticas:

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

Permita o acesso de usuários não autenticados a um ponto de extremidade

O [AllowAnonymous] permite o acesso de usuários não autenticados a pontos de extremidade:

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


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

CORS

Rotas podem ser habilitadas para CORS com as políticas CORS. O CORS pode ser declarado por meio do atributo [EnableCors] ou utilizando o método RequireCors. As seguintes amostras habilitam o 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();

Para obter mais informações, consulte Habilitar solicitações entre origens (CORS) no ASP.NET Core

Consulte também

Suporte do OpenAPI em APIs mínimas