Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Note
Это не последняя версия этой статьи. В текущей версии см. версию .NET 10 этой статьи.
Warning
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.
Этот документ:
- Предоставляет краткий справочник по минимальным API.
- Предназначен для опытных разработчиков. Общие сведения см. в руководстве по созданию минимального API с помощью ASP.NET Core.
Минимальные API состоят из следующих:
WebApplication
Шаблон ASP.NET Core создает следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Приведенный выше код можно создать, набрав dotnet new web в командной строке или выбрав в Visual Studio шаблон пустого веб-проекта.
Следующий код создает WebApplication (app) без явного создания WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create инициализирует новый экземпляр класса WebApplication с предварительно настроенными значениями по умолчанию.
WebApplication автоматически добавляет следующее ПО промежуточного слоя в минимальные приложения API в зависимости от определенных условий:
-
UseDeveloperExceptionPageсначала добавляется при выполненииHostingEnvironment"Development"действия . -
UseRoutingдобавляется второй, если пользовательский код еще не вызвалUseRoutingи если настроены конечные точки, напримерapp.MapGet. -
UseEndpointsдобавляется в конце конвейера ПО промежуточного слоя, если настроены какие-либо конечные точки. -
UseAuthenticationдобавляется сразу после того,UseRoutingкак пользовательский код еще не звонилUseAuthenticationи можетIAuthenticationSchemeProviderбыть обнаружен в поставщике услуг.IAuthenticationSchemeProviderдобавляется по умолчанию при использованииAddAuthentication, а службы обнаруживаются с помощьюIServiceProviderIsService. -
UseAuthorizationдобавляется далее, если пользовательский код еще не вызвалUseAuthorizationи можетIAuthorizationHandlerProviderбыть обнаружен в поставщике услуг.IAuthorizationHandlerProviderдобавляется по умолчанию при использованииAddAuthorization, а службы обнаруживаются с помощьюIServiceProviderIsService. - Пользовательские ПО промежуточного слоя и конечные точки добавляются между
UseRoutingиUseEndpoints.
Следующий код фактически представляет собой то, что добавляется в приложение автоматический по промежуточному слоям:
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 => {});
В некоторых случаях конфигурация ПО промежуточного слоя по умолчанию не является правильной для приложения и требует изменения. Например, UseCors следует вызывать до UseAuthentication и UseAuthorization. Приложение должно вызываться UseAuthentication и UseAuthorization при UseCors вызове:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Если по промежуточному слоям следует запустить перед сопоставлением маршрутов, следует вызвать и UseRouting по промежуточному слоям следует поместить перед вызовом UseRouting.
UseEndpoints Не требуется в этом случае, так как он автоматически добавляется, как описано ранее:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
При добавлении по промежуточного слоя терминала:
- По промежуточному слоям необходимо добавить после
UseEndpoints. - Приложение должно вызываться
UseRoutingиUseEndpointsтаким образом, чтобы по промежуточному слоя терминала можно было разместить в правильном расположении.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
ПО промежуточного слоя терминала — это ПО промежуточного слоя, которое выполняется, если конечная точка не обрабатывает запрос.
Использование портов
Если вы создаете веб-приложение с помощью Visual Studio или dotnet new, автоматически создается файл Properties/launchSettings.json с указанием портов, на которых отвечает это приложение. Запуск приложения из Visual Studio с параметрами портов, представленными в следующих примерах, возвращает диалоговое окно с сообщением об ошибке Unable to connect to web server 'AppName'. Visual Studio возвращает ошибку, так как ожидается порт, указанный в Properties/launchSettings.json, но приложение использует порт, указанный в app.Run("http://localhost:3000"). Выполните в командной строке следующий пример кода для изменения портов.
В следующих разделах задается порт, на котором отвечает приложение.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
В приведенном выше коде приложение использует порт 3000.
Несколько портов
В следующем коде приложение использует порты 3000 и 4000.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Настройка порта из командной строки
Следующая команда настраивает для приложения работу с портом 7777:
dotnet run --urls="https://localhost:7777"
Если в файле Kestrel настроена еще и конечная точка appsettings.json, то используется файл с URL-адресом, указанным в appsettings.json. Дополнительные сведения см. в разделе Конфигурация конечной точки Kestrel.
Получение порта из среды
Следующий код считывает значение порта из среды:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Для настройки порта из среды лучше всего использовать переменную среды ASPNETCORE_URLS, как показано в следующем разделе.
Настройка портов через переменную среды ASPNETCORE_URLS
Для настройки порта существует переменная среды ASPNETCORE_URLS:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS поддерживает несколько URL-адресов:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Ожидание передачи данных на всех интерфейсах
В следующих примерах демонстрируется ожидание передачи данных на всех интерфейсах
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();
Ожидание передачи данных на всех интерфейсах с помощью ASPNETCORE_URLS
В предыдущих примерах можно использовать ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Прослушивание всех интерфейсов с помощью ASPNETCORE_HTTPS_PORTS
Приведенные выше примеры могут использовать ASPNETCORE_HTTPS_PORTS и ASPNETCORE_HTTP_PORTS.
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
Дополнительные сведения см. в разделе "Настройка конечных точек" для веб-сервера ASP.NET Core Kestrel
Выбор протокола HTTPS с сертификатом разработки
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения о сертификате разработки см. в разделе Доверие к сертификату разработки HTTPS в среде ASP.NET Core на ОС Windows и macOS.
Указание протокола HTTPS с пользовательским сертификатом
В следующих разделах показано, как указать пользовательский сертификат с помощью appsettings.json файла и конфигурации.
Настройка пользовательского сертификата в appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Настройка пользовательского сертификата в конфигурации
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();
Использование API сертификатов
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();
Чтение данных из среды
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();
Дополнительные сведения об использовании среды см. в средах выполнения ASP.NET Core
Configuration
Следующий код считывает данные из системы конфигурации:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
Дополнительные сведения см. в статье Конфигурация в ASP.NET Core.
Logging
Следующий код записывает сообщение в журнал при запуске приложения:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения см. в разделе "Ведение журнала" в .NET и ASP.NET Core
Доступ к контейнеру внедрения зависимостей (DI)
В следующем коде показано, как получить службы из контейнера DI во время запуска приложения.
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();
В следующем коде показано, как получить доступ к ключам из контейнера DI с помощью атрибута [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.";
}
Дополнительные сведения о внедрении зависимостей см. в разделе ASP.NET Core.
WebApplicationBuilder
Пример кода в этом разделе использует WebApplicationBuilder.
Изменение корневой папки содержимого, имени приложения и среды
Следующий код задает корневую папку для содержимого, имя приложения и среду:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию.
Дополнительные сведения см. в статье Обзор основных понятий ASP.NET Core.
Изменение корневого каталога содержимого, имени приложения и среды с помощью переменных среды или командной строки
В следующей таблице представлены переменные среды и аргументы командной строки, которые позволяют изменить корневую папку содержимого, имя приложения и среду:
| feature | Переменная среды | Аргумент командной строки |
|---|---|---|
| Имя приложения | ASPNETCORE_APPLICATIONNAME | --applicationName |
| Имя среды | ASPNETCORE_ENVIRONMENT | --environment |
| Корневой каталог содержимого | ASPNETCORE_CONTENTROOT | --contentRoot |
Все поставщики конфигурации
Следующий пример добавляет поставщик конфигурации INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Дополнительные сведения см. в разделе Поставщики конфигурации файлов в статье Конфигурация в ASP.NET Core.
Конфигурация чтения
По умолчанию WebApplicationBuilder считывает конфигурацию из нескольких источников, в том числе:
-
appSettings.jsonиappSettings.{environment}.json. - Переменные среды
- Командная строка
Полный список источников конфигурации см. в разделе "Конфигурация по умолчанию" в ASP.NET Core.
Следующий код считывает из конфигурации значение HelloKey и отображает его в конечной точке /. Если это значение конфигурации равно NULL, в message сохраняется значение "Hello":
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Чтение данных из среды
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Добавление поставщиков ведения журнала
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();
Добавление служб
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();
Настройка IHostBuilder
К существующим методам расширения IHostBuilder можно обращаться через свойство 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();
Настройка IWebHostBuilder
К существующим методам расширения IWebHostBuilder можно обращаться через свойство 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();
Изменение корневой папки веб-сайта
Корневая папка веб-сайта задается относительно корневой папки содержимого. По умолчанию это wwwroot. Веб-корень — это место, где Промежуточное ПО для статических файлов ищет статические файлы. Корневой веб-сайт можно изменить с помощью WebHostOptions, командной строки или метода UseWebRoot:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Пользовательский контейнер внедрения зависимостей (DI)
В следующем примере используется 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();
Добавление ПО промежуточного слоя
Любое существующее ПО промежуточного слоя для ASP.NET Core можно настроить в WebApplication:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
Страница со сведениями об исключении для разработчика
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию. Страница исключений для разработчиков включена в предварительно настроенных параметрах по умолчанию. При выполнении следующего кода в среде разработки переход к адресу / отображает страницу с удобным представлением информации об исключении.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ПО промежуточного слоя ASP.NET Core
В следующей таблице перечислены некоторые из по промежуточного слоя, часто используемого с минимальными API.
| Middleware | Description | API |
|---|---|---|
| Authentication | Обеспечивает поддержку проверки подлинности. | UseAuthentication |
| Authorization | Обеспечивает поддержку авторизации. | UseAuthorization |
| CORS | Настраивает общий доступ к ресурсам независимо от источника. | UseCors |
| Обработчик исключений | Глобально обрабатывает исключения, создаваемые конвейером ПО промежуточного слоя. | UseExceptionHandler |
| Переадресация заголовков | Пересылает заголовки, переданные через прокси-сервер, в текущий запрос. | UseForwardedHeaders |
| Перенаправление HTTPS | Перенаправляет все запросы с HTTP на HTTPS. | UseHttpsRedirection |
| Строгое обеспечение безопасности транспорта HTTP (HSTS) | ПО промежуточного слоя для повышения безопасности, которое добавляет специальный заголовок ответа. | UseHsts |
| Ведение журнала запросов | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них. | UseHttpLogging |
| Время ожидания запроса | Предоставляет поддержку настройки времени ожидания запроса, глобального значения по умолчанию и для каждой конечной точки. | UseRequestTimeouts |
| Ведение журнала запросов W3C | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них в формате консорциума W3C. | UseW3CLogging |
| Кэширование ответов | Обеспечивает поддержку для кэширования откликов. | UseResponseCaching |
| Сжатие ответов | Обеспечивает поддержку для сжатия откликов. | UseResponseCompression |
| Session | Обеспечивает поддержку для управления пользовательскими сеансами. | UseSession |
| Статические файлы | Обеспечивает поддержку для обработки статических файлов и просмотра каталогов. | UseStaticFiles, UseFileServer |
| WebSockets | Обеспечивает поддержку протокола WebSockets. | UseWebSockets |
В следующих разделах рассматриваются обработка запросов: маршрутизация, привязка параметров и ответы.
Routing
Настроенный WebApplication метод поддерживает Map{Verb} и MapMethods где {Verb} используется метод HTTP с регистром верблюда, например Get, Post, Putили 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();
Аргументы Delegate , передаваемые этим методам, называются обработчиками маршрутов.
Обработчики маршрутов
Обработчики маршрутов — это методы, которые выполняются при обнаружении соответствия для маршрута. В роли обработчика маршрут может выступать лямбда-выражение, локальная функция, метод экземпляра или статический метод. Обработчики маршрутов могут быть синхронными или асинхронными.
Лямбда-выражение
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();
Локальная функция
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Метод экземпляра
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";
}
}
Статический метод
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";
}
}
Конечная точка, определенная вне Program.cs
Минимальные API не должны находиться в 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" });
});
}
}
См. также группы маршрутов далее в этой статье.
Именованные конечные точки и создание ссылок
Конечные точки можно указать имена для создания URL-адресов конечной точки. Использование именованной конечной точки позволяет избежать сложных путей кода в приложении:
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();
Приведенный выше код отображает The link to the hello route is /hello из конечной точки /.
ПРИМЕЧАНИЕ. Имена конечных точек чувствительны к регистру.
Имена конечных точек:
- Оно должно быть глобально уникальным.
- используются в качестве идентификатора операции OpenAPI, если включена поддержка OpenAPI. Дополнительные сведения см. в статье OpenAPI.
Параметры маршрута
Параметры маршрута могут быть захвачены в составе определения шаблона маршрута:
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();
Приведенный выше код возвращает The user id is 3 and book id is 7 из URI /users/3/books/7.
Обработчик маршрута может объявлять параметры, которые нужно захватывать. Когда запрос выполняется в маршрут с параметрами, объявленными для записи, параметры анализируются и передаются обработчику. Это позволяет легко получать значения в строго типизированном виде. В приведенном выше коде userId и bookId имеют тип int.
В приведенном выше коде создается исключение, если значение маршрута не может быть преобразовано в тип int. Запрос GET по маршруту /users/hello/books/3 выдает следующее исключение:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Использование подстановочных знаков и перехват всех маршрутов
Следующая функция перехвата всех маршрутов возвращает значение Routing to hello из конечной точки "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ограничения маршрута
Ограничения маршрута ограничивают возможности сопоставления маршрута.
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();
В приведенной ниже таблице перечислены представленные выше примеры шаблонов маршрутов и их поведение.
| Шаблон маршрута | Пример соответствующего URI |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Дополнительные сведения см. в разделе Справочник по ограничениям маршрутов в статье Маршрутизация в ASP.NET Core.
Группы маршрутов
Метод MapGroup расширения помогает упорядочивать группы конечных точек с общим префиксом. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata которые добавляют метаданные конечной точки.
Например, следующий код создает две аналогичные группы конечных точек:
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;
}
В этом сценарии можно использовать относительный адрес заголовка Location201 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);
}
Первая группа конечных точек будет соответствовать только запросам, префиксным и /public/todos доступным без какой-либо проверки подлинности. Вторая группа конечных точек будет соответствовать только запросам, префиксным и /private/todos требующим проверки подлинности.
Фабрика QueryPrivateTodos фильтров конечных точек — это локальная функция, которая изменяет параметры обработчика TodoDb маршрутов, чтобы разрешить доступ к частным данным и хранить данные о частных объектах.
Группы маршрутов также поддерживают вложенные группы и сложные шаблоны префикса с параметрами маршрута и ограничениями. В следующем примере обработчик маршрутов, сопоставленный с user группой, может записывать {org} параметры маршрута, {group} определенные в префиксах внешней группы.
Префикс также может быть пустым. Это может быть полезно для добавления метаданных конечной точки или фильтров в группу конечных точек без изменения шаблона маршрута.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Добавление фильтров или метаданных в группу ведет себя так же, как и их отдельно к каждой конечной точке перед добавлением дополнительных фильтров или метаданных, которые могли быть добавлены во внутреннюю группу или определенную конечную точку.
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);
});
В приведенном выше примере внешний фильтр регистрирует входящий запрос до внутреннего фильтра, даже если он был добавлен вторым. Так как фильтры были применены к разным группам, порядок их добавления относительно друг друга не имеет значения. Фильтры заказов добавляются, если они применяются к той же группе или определенной конечной точке.
Запрос, который /outer/inner/ будет регистрировать следующее:
/outer group filter
/inner group filter
MapGet filter
Привязка параметров
Привязка параметров — это процесс преобразования данных запроса в строго типизированные параметры, выраженные обработчиками маршрутов. Источник привязки определяет, откуда будут привязаны параметры. Источники привязки могут быть явными или выведенными на основе метода HTTP и типа параметра.
Поддерживаемые источники привязки:
- Значения маршрута
- Строка запроса
- Header
- текст (в формате JSON);
- Значения формы
- службы, предоставляемые путем внедрения зависимостей;
- Custom
В следующем обработчике маршрутов GET используются некоторые из этих источников привязки параметров:
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 { }
Функции привязки ключевых параметров
-
Явная привязка: используйте такие атрибуты, как
[FromRoute],[FromQuery],[FromHeader],[FromBody][FromForm]и[FromServices]для явного указания источников привязки. -
Привязка формы: Привязывайте значения формы с помощью
[FromForm]атрибута, включая поддержкуIFormFileиIFormFileCollectionдля загрузки файлов. - Сложные типы: привязка к коллекциям и сложным типам из форм, строк запроса и заголовков.
-
Настраиваемая привязка: реализуйте пользовательскую логику привязки с помощью
TryParse,BindAsyncили интерфейсаIBindableFromHttpContext<T>. - Необязательные параметры: поддержка типов, допускающих значение NULL, и значений по умолчанию для необязательных параметров.
- Внедрение зависимостей: параметры автоматически привязаны к службам, зарегистрированным в контейнере DI.
-
Специальные типы: автоматическая привязка для
HttpContext,HttpRequest,HttpResponse,CancellationTokenClaimsPrincipalиStreamPipeReader.
Подробнее: Подробные сведения о привязке параметров, включая расширенные сценарии, проверку, приоритет привязки и устранение неполадок, см. в разделе "Привязка параметров" в минимальных приложениях API.
Десериализация Json+PipeReader в минимальных API
Начиная с .NET 10, следующие функциональные области ASP.NET Core используют перегрузки JsonSerializer.DeserializeAsync на основе PipeReader вместо Stream:
- Minimal APIs (привязка параметров, чтение тела запроса)
- MVC (входные форматы, модель)
- Методы расширения HttpRequestJsonExtensions для чтения тела запроса как JSON.
Для большинства приложений переход с Stream на PipeReader обеспечивает более высокую производительность, не требуя изменений в коде приложения. Но если в вашем приложении используется пользовательский преобразователь, то преобразователь может неправильно обработать Utf8JsonReader.HasValueSequence. Если это не так, результатом могут быть такие ошибки, как ArgumentOutOfRangeException, или отсутствие данных при десериализации. Чтобы ваш преобразователь работал без ошибок, связанных с PipeReader, у вас есть следующие варианты.
Вариант 1. Временное решение
Краткое решение заключается в том, чтобы вернуться к использованию Stream без поддержки PipeReader. Чтобы реализовать эту опцию, установите переключатель AppContext "Microsoft.AspNetCore.UseStreamBasedJsonParsing" в значение "true". Мы рекомендуем сделать это только в качестве временного обходного решения и обновить преобразователь, чтобы обеспечить поддержку HasValueSequence как можно скорее. Переключатель может быть удален в .NET 11. Его единственной целью было дать разработчикам время обновить их преобразователи.
Вариант 2: Быстрое решение для JsonConverter реализаций
Для этого исправления вы выделяете массив из ReadOnlySequence. В этом примере показано, как выглядит код:
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
// previous code
}
Вариант 3. Более сложное, но более эффективное исправление
Это исправление включает настройку отдельного пути кода для 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
}
}
Дополнительные сведения см. в разделе
Поддержка проверки в минимальных API
Включение проверки позволяет среде выполнения ASP.NET Core выполнять проверки, определенные в следующих параметрах:
- Query
- Header
- Основное содержание запроса
Проверки определяются с помощью атрибутов в DataAnnotations пространстве имен.
Атрибуты проверки автоматически применяются, если параметр для конечной точки Minimal API является классом или записью. Рассмотрим пример.
public record Product(
[Required] string Name,
[Range(1, 1000)] int Quantity);
Разработчики настраивают поведение системы проверки следующим образом:
- Создание пользовательских
[Validation]реализаций атрибутов . - Реализация интерфейса
IValidatableObjectдля сложной логики проверки.
Если проверка завершается ошибкой, среда выполнения возвращает ответ 400 — недопустимый запрос с подробными сведениями об ошибках проверки.
Включение встроенной поддержки проверки для минимальных API
Включите встроенную поддержку проверки минимальных API, вызвав AddValidation метод расширения, чтобы зарегистрировать необходимые службы в контейнере службы для приложения:
builder.Services.AddValidation();
Реализация автоматически обнаруживает типы, определенные в минимальных обработчиках API или в качестве базовых типов типов, определенных в обработчиках API Min. Фильтр конечной точки выполняет проверку этих типов и добавляется для каждой конечной точки.
Проверка может быть отключена для определенных конечных точек с помощью DisableValidation метода расширения, как показано в следующем примере:
app.MapPost("/products",
([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
=> TypedResults.Ok(productId))
.DisableValidation();
Настройка ответов на ошибки проверки с помощью IProblemDetailsService
Настройте ответы на ошибки из минимальной IProblemDetailsService логики проверки API с помощью реализации. Зарегистрируйте эту службу в коллекции служб приложения, чтобы обеспечить более согласованные и пользовательские ответы на ошибки. Поддержка минимальной проверки API появилась в ASP.NET Core в .NET 10.
Чтобы реализовать пользовательские ответы на ошибки проверки, выполните следующее:
- Реализация IProblemDetailsService или использование реализации по умолчанию
- Регистрация службы в контейнере DI
- Система проверки автоматически использует зарегистрированную службу для форматирования ответов на ошибки проверки.
Дополнительные сведения о настройке ответов на ошибки проверки с помощью IProblemDetailsService см. в статье "Создание ответов в приложениях API "Минимальный".
Responses
Обработчики маршрутов поддерживают следующие типы возвращаемых значений:
- на основе
IResult, напримерTask<IResult>иValueTask<IResult>; -
string, напримерTask<string>иValueTask<string>; -
T(любой другой тип), напримерTask<T>иValueTask<T>.
| Возвращаемое значение | Behavior | Content-Type |
|---|---|---|
IResult |
Платформа вызывает IResult.ExecuteAsync | Определяется реализацией IResult |
string |
Платформа записывает строку непосредственно в ответ | text/plain |
T (любой другой тип) |
Платформа JSON-сериализует ответ | application/json |
Более подробное руководство по возврату возвращаемых значений обработчика маршрутов см. в статье "Создание ответов в приложениях API с минимальным количеством"
Примеры возвращаемых значений
Строковые возвращаемые значения
app.MapGet("/hello", () => "Hello World");
Возвращаемые значения в формате JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Возврат типизированных результатов
Следующий код возвращает TypedResultsследующий код:
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Возвращаемые значения в формате IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
В следующем примере для настройки ответа используются встроенные типы результатов:
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" }));
Пользовательский код состояния
app.MapGet("/405", () => Results.StatusCode(405));
Text
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");
});
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
Встроенные результаты
Распространенные вспомогательные средства результатов существуют в и Results статических TypedResults классах.
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Изменение заголовков
Используйте объект HttpResponse для изменения заголовков ответов:
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";
});
Настройка результатов
Приложения могут управлять ответами через пользовательскую реализацию типа IResult. Следующий код является примером результата с типом 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);
}
}
Мы рекомендуем добавить в Microsoft.AspNetCore.Http.IResultExtensions метод расширения, чтобы эти пользовательские результаты было проще искать.
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();
Типизированные результаты
Интерфейс IResult может представлять значения, возвращаемые из минимальных API, которые не используют неявную поддержку сериализации возвращаемого объекта в ответ HTTP. Статический класс Results используется для создания разных объектов IResult, которые представляют разные типы ответов. Например, установка кода статуса ответа или перенаправление на другой URL-адрес.
Реализуемые типы IResult являются общедоступными, что позволяет использовать утверждения типов при тестировании. Рассмотрим пример.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Вы можете просмотреть возвращаемые типы соответствующих методов в статическом классе TypedResults , чтобы найти правильный общедоступный IResult тип для приведения.
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Filters
Дополнительные сведения см. в статьях "Фильтры" в приложениях API "Минимальный".
Authorization
Для защиты маршрутов можно применять политики авторизации. Они могут быть объявлены через атрибут [Authorize] или с помощью метода 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();
Приведенный выше код можно создать с использованием RequireAuthorization:
app.MapGet("/auth", () => "This endpoint requires authorization")
.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("/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();
Разрешение доступа к конечной точке пользователям, не прошедшим проверку подлинности
[AllowAnonymous] разрешает доступ к конечным точкам пользователям, не прошедшим проверку подлинности:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Для маршрутов можно включить CORS с помощью политик CORS. CORS можно объявить через атрибут [EnableCors] или с помощью метода RequireCors. В следующих примерах включается 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();
Дополнительные сведения см. в статье Включение запросов CORS в ASP.NET Core.
ValidateScopes и ValidateOnBuild
ValidateScopes и ValidateOnBuild включены по умолчанию в среде разработки , но отключены в других средах.
Когда ValidateOnBuild это trueтак, контейнер DI проверяет конфигурацию службы во время сборки. Если конфигурация службы недопустима, сборка завершается ошибкой при запуске приложения, а не во время выполнения при запросе службы.
Когда ValidateScopes это trueтак, контейнер DI проверяет, не разрешена ли служба с областью действия из корневой области. Разрешение ограниченной службы из корневой области может привести к утечке памяти, так как служба хранится в памяти дольше, чем область запроса.
ValidateScopes значение ValidateOnBuild false по умолчанию в режимах, отличных от разработки, по соображениям производительности.
Следующий код по ValidateScopes умолчанию включен в режиме разработки, но отключен в режиме выпуска:
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 { }
Следующий код по ValidateOnBuild умолчанию включен в режиме разработки, но отключен в режиме выпуска:
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 { }
Следующий код отключает ValidateScopes и ValidateOnBuild в 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;
});
}
См. также
- Краткий справочник по минимальным API
- Создание документов OpenAPI
- Создание ответов в минимальных приложениях API
- Фильтры в минимальных приложениях API
- Обработка ошибок в api ASP.NET Core
- Проверка подлинности и авторизация в минимальных API
- Тестирование минимальных приложений API
- Маршрутизация с коротким каналом
- Identity Конечные точки API
- Поддержка контейнера внедрения зависимостей к ключу службы
- Взгляд за кулисами минимальных конечных точек API
- Организация api-интерфейсов ASP.NET Core
- Обсуждение проверки Fluent на сайте GitHub
Этот документ:
- Предоставляет краткий справочник по минимальным API.
- Предназначен для опытных разработчиков. Общие сведения см. в руководстве по созданию минимального API с помощью ASP.NET Core.
В набор минимальных API входят следующие элементы:
WebApplication
Шаблон ASP.NET Core создает следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Приведенный выше код можно создать, набрав dotnet new web в командной строке или выбрав в Visual Studio шаблон пустого веб-проекта.
Следующий код создает WebApplication (app) без явного создания WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create инициализирует новый экземпляр класса WebApplication с предварительно настроенными значениями по умолчанию.
WebApplication автоматически добавляет следующее ПО промежуточного слоя в минимальные приложения API в зависимости от определенных условий:
-
UseDeveloperExceptionPageсначала добавляется при выполненииHostingEnvironment"Development"действия . -
UseRoutingдобавляется второй, если пользовательский код еще не вызвалUseRoutingи если настроены конечные точки, напримерapp.MapGet. -
UseEndpointsдобавляется в конце конвейера ПО промежуточного слоя, если настроены какие-либо конечные точки. -
UseAuthenticationдобавляется сразу после того,UseRoutingкак пользовательский код еще не звонилUseAuthenticationи можетIAuthenticationSchemeProviderбыть обнаружен в поставщике услуг.IAuthenticationSchemeProviderдобавляется по умолчанию при использованииAddAuthentication, а службы обнаруживаются с помощьюIServiceProviderIsService. -
UseAuthorizationдобавляется далее, если пользовательский код еще не вызвалUseAuthorizationи можетIAuthorizationHandlerProviderбыть обнаружен в поставщике услуг.IAuthorizationHandlerProviderдобавляется по умолчанию при использованииAddAuthorization, а службы обнаруживаются с помощьюIServiceProviderIsService. - Пользовательские ПО промежуточного слоя и конечные точки добавляются между
UseRoutingиUseEndpoints.
Следующий код фактически представляет собой то, что добавляется в приложение автоматический по промежуточному слоям:
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 => {});
В некоторых случаях конфигурация ПО промежуточного слоя по умолчанию не является правильной для приложения и требует изменения. Например, UseCors следует вызывать до UseAuthentication и UseAuthorization. Приложение должно вызываться UseAuthentication и UseAuthorization при UseCors вызове:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Если по промежуточному слоям следует запустить перед сопоставлением маршрутов, следует вызвать и UseRouting по промежуточному слоям следует поместить перед вызовом UseRouting.
UseEndpoints Не требуется в этом случае, так как он автоматически добавляется, как описано ранее:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
При добавлении по промежуточного слоя терминала:
- По промежуточному слоям необходимо добавить после
UseEndpoints. - Приложение должно вызываться
UseRoutingиUseEndpointsтаким образом, чтобы по промежуточному слоя терминала можно было разместить в правильном расположении.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
ПО промежуточного слоя терминала — это ПО промежуточного слоя, которое выполняется, если конечная точка не обрабатывает запрос.
Использование портов
Если вы создаете веб-приложение с помощью Visual Studio или dotnet new, автоматически создается файл Properties/launchSettings.json с указанием портов, на которых отвечает это приложение. Запуск приложения из Visual Studio с параметрами портов, представленными в следующих примерах, возвращает диалоговое окно с сообщением об ошибке Unable to connect to web server 'AppName'. Visual Studio возвращает ошибку, так как ожидается порт, указанный в Properties/launchSettings.json, но приложение использует порт, указанный в app.Run("http://localhost:3000"). Выполните в командной строке следующий пример кода для изменения портов.
В следующих разделах задается порт, на котором отвечает приложение.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
В приведенном выше коде приложение использует порт 3000.
Несколько портов
В следующем коде приложение использует порты 3000 и 4000.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Настройка порта из командной строки
Следующая команда настраивает для приложения работу с портом 7777:
dotnet run --urls="https://localhost:7777"
Если в файле Kestrel настроена еще и конечная точка appsettings.json, то используется файл с URL-адресом, указанным в appsettings.json. Дополнительные сведения см. в разделе Конфигурация конечной точки Kestrel.
Получение порта из среды
Следующий код считывает значение порта из среды:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Для настройки порта из среды лучше всего использовать переменную среды ASPNETCORE_URLS, как показано в следующем разделе.
Настройка портов через переменную среды ASPNETCORE_URLS
Для настройки порта существует переменная среды ASPNETCORE_URLS:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS поддерживает несколько URL-адресов:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Ожидание передачи данных на всех интерфейсах
В следующих примерах демонстрируется ожидание передачи данных на всех интерфейсах
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();
Ожидание передачи данных на всех интерфейсах с помощью ASPNETCORE_URLS
В предыдущих примерах можно использовать ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Прослушивание всех интерфейсов с помощью ASPNETCORE_HTTPS_PORTS
Приведенные выше примеры могут использовать ASPNETCORE_HTTPS_PORTS и ASPNETCORE_HTTP_PORTS.
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
Дополнительные сведения см. в разделе "Настройка конечных точек" для веб-сервера ASP.NET Core Kestrel
Выбор протокола HTTPS с сертификатом разработки
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения о сертификате разработки см. в разделе Доверие к сертификату разработки HTTPS в среде ASP.NET Core на ОС Windows и macOS.
Указание протокола HTTPS с пользовательским сертификатом
В следующих разделах показано, как указать пользовательский сертификат с помощью appsettings.json файла и конфигурации.
Настройка пользовательского сертификата в appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Настройка пользовательского сертификата в конфигурации
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();
Использование API сертификатов
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();
Чтение данных из среды
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();
Дополнительные сведения об использовании среды см. в средах выполнения ASP.NET Core
Configuration
Следующий код считывает данные из системы конфигурации:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
Дополнительные сведения см. в статье Конфигурация в ASP.NET Core.
Logging
Следующий код записывает сообщение в журнал при запуске приложения:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения см. в разделе "Ведение журнала" в .NET и ASP.NET Core
Доступ к контейнеру внедрения зависимостей (DI)
В следующем коде показано, как получить службы из контейнера DI во время запуска приложения.
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();
В следующем коде показано, как получить доступ к ключам из контейнера DI с помощью атрибута [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.";
}
Дополнительные сведения о внедрении зависимостей см. в разделе ASP.NET Core.
WebApplicationBuilder
Пример кода в этом разделе использует WebApplicationBuilder.
Изменение корневой папки содержимого, имени приложения и среды
Следующий код задает корневую папку для содержимого, имя приложения и среду:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию.
Дополнительные сведения см. в статье Обзор основных понятий ASP.NET Core.
Изменение корневого каталога содержимого, имени приложения и среды с помощью переменных среды или командной строки
В следующей таблице представлены переменные среды и аргументы командной строки, которые позволяют изменить корневую папку содержимого, имя приложения и среду:
| feature | Переменная среды | Аргумент командной строки |
|---|---|---|
| Имя приложения | ASPNETCORE_APPLICATIONNAME | --applicationName |
| Имя среды | ASPNETCORE_ENVIRONMENT | --environment |
| Корневой каталог содержимого | ASPNETCORE_CONTENTROOT | --contentRoot |
Все поставщики конфигурации
Следующий пример добавляет поставщик конфигурации INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Дополнительные сведения см. в разделе Поставщики конфигурации файлов в статье Конфигурация в ASP.NET Core.
Конфигурация чтения
По умолчанию WebApplicationBuilder считывает конфигурацию из нескольких источников, в том числе:
-
appSettings.jsonиappSettings.{environment}.json. - Переменные среды
- Командная строка
Полный список источников конфигурации см. в разделе "Конфигурация по умолчанию" в ASP.NET Core.
Следующий код считывает из конфигурации значение HelloKey и отображает его в конечной точке /. Если это значение конфигурации равно NULL, в message сохраняется значение "Hello":
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Чтение данных из среды
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Добавление поставщиков ведения журнала
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();
Добавление служб
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();
Настройка IHostBuilder
К существующим методам расширения IHostBuilder можно обращаться через свойство 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();
Настройка IWebHostBuilder
К существующим методам расширения IWebHostBuilder можно обращаться через свойство 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();
Изменение корневой папки веб-сайта
Корневая папка веб-сайта задается относительно корневой папки содержимого. По умолчанию это wwwroot. Веб-корень — это место, где Промежуточное ПО для статических файлов ищет статические файлы. Корневой веб-сайт можно изменить с помощью WebHostOptions, командной строки или метода UseWebRoot:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Пользовательский контейнер внедрения зависимостей (DI)
В следующем примере используется 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();
Добавление ПО промежуточного слоя
Любое существующее ПО промежуточного слоя для ASP.NET Core можно настроить в WebApplication:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
Страница со сведениями об исключении для разработчика
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию. Страница исключений для разработчиков включена в предварительно настроенных параметрах по умолчанию. При выполнении следующего кода в среде разработки переход к адресу / отображает страницу с удобным представлением информации об исключении.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ПО промежуточного слоя ASP.NET Core
В следующей таблице собраны некоторые примеры ПО промежуточного слоя, часто используемого с минимальными API.
| Middleware | Description | API |
|---|---|---|
| Authentication | Обеспечивает поддержку проверки подлинности. | UseAuthentication |
| Authorization | Обеспечивает поддержку авторизации. | UseAuthorization |
| CORS | Настраивает общий доступ к ресурсам независимо от источника. | UseCors |
| Обработчик исключений | Глобально обрабатывает исключения, создаваемые конвейером ПО промежуточного слоя. | UseExceptionHandler |
| Переадресация заголовков | Пересылает заголовки, переданные через прокси-сервер, в текущий запрос. | UseForwardedHeaders |
| Перенаправление HTTPS | Перенаправляет все запросы с HTTP на HTTPS. | UseHttpsRedirection |
| Строгое обеспечение безопасности транспорта HTTP (HSTS) | ПО промежуточного слоя для повышения безопасности, которое добавляет специальный заголовок ответа. | UseHsts |
| Ведение журнала запросов | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них. | UseHttpLogging |
| Время ожидания запроса | Предоставляет поддержку настройки времени ожидания запроса, глобального значения по умолчанию и для каждой конечной точки. | UseRequestTimeouts |
| Ведение журнала запросов W3C | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них в формате консорциума W3C. | UseW3CLogging |
| Кэширование ответов | Обеспечивает поддержку для кэширования откликов. | UseResponseCaching |
| Сжатие ответов | Обеспечивает поддержку для сжатия откликов. | UseResponseCompression |
| Session | Обеспечивает поддержку для управления пользовательскими сеансами. | UseSession |
| Статические файлы | Обеспечивает поддержку для обработки статических файлов и просмотра каталогов. | UseStaticFiles, UseFileServer |
| WebSockets | Обеспечивает поддержку протокола WebSockets. | UseWebSockets |
В следующих разделах рассматриваются обработка запросов: маршрутизация, привязка параметров и ответы.
Routing
Настроенный WebApplication метод поддерживает Map{Verb} и MapMethods где {Verb} используется метод HTTP с регистром верблюда, например Get, PostPut или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();
Аргументы Delegate , передаваемые этим методам, называются обработчиками маршрутов.
Обработчики маршрутов
Обработчики маршрутов — это методы, которые выполняются при обнаружении соответствия для маршрута. В роли обработчика маршрут может выступать лямбда-выражение, локальная функция, метод экземпляра или статический метод. Обработчики маршрутов могут быть синхронными или асинхронными.
Лямбда-выражение
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();
Локальная функция
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Метод экземпляра
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";
}
}
Статический метод
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";
}
}
Конечная точка, определенная вне Program.cs
Минимальные API не должны находиться в 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" });
});
}
}
См. также группы маршрутов далее в этой статье.
Именованные конечные точки и создание ссылок
Конечные точки можно указать имена для создания URL-адресов конечной точки. Использование именованной конечной точки позволяет избежать сложных путей кода в приложении:
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();
Приведенный выше код отображает The link to the hello route is /hello из конечной точки /.
ПРИМЕЧАНИЕ. Имена конечных точек чувствительны к регистру.
Имена конечных точек:
- Оно должно быть глобально уникальным.
- используются в качестве идентификатора операции OpenAPI, если включена поддержка OpenAPI. Дополнительные сведения см. в статье OpenAPI.
Параметры маршрута
Параметры маршрута могут быть захвачены в составе определения шаблона маршрута:
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();
Приведенный выше код возвращает The user id is 3 and book id is 7 из URI /users/3/books/7.
Обработчик маршрута может объявлять параметры, которые нужно захватывать. Когда запрос выполняется в маршрут с параметрами, объявленными для записи, параметры анализируются и передаются обработчику. Это позволяет легко получать значения в строго типизированном виде. В приведенном выше коде userId и bookId имеют тип int.
В приведенном выше коде создается исключение, если значение маршрута не может быть преобразовано в тип int. Запрос GET по маршруту /users/hello/books/3 выдает следующее исключение:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Использование подстановочных знаков и перехват всех маршрутов
Следующая функция перехвата всех маршрутов возвращает значение Routing to hello из конечной точки "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ограничения маршрута
Ограничения маршрута ограничивают возможности сопоставления маршрута.
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();
В приведенной ниже таблице перечислены представленные выше примеры шаблонов маршрутов и их поведение.
| Шаблон маршрута | Пример соответствующего URI |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Дополнительные сведения см. в разделе Справочник по ограничениям маршрутов в статье Маршрутизация в ASP.NET Core.
Группы маршрутов
Метод MapGroup расширения помогает упорядочивать группы конечных точек с общим префиксом. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata которые добавляют метаданные конечной точки.
Например, следующий код создает две аналогичные группы конечных точек:
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;
}
В этом сценарии можно использовать относительный адрес заголовка Location201 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);
}
Первая группа конечных точек будет соответствовать только запросам, префиксным и /public/todos доступным без какой-либо проверки подлинности. Вторая группа конечных точек будет соответствовать только запросам, префиксным и /private/todos требующим проверки подлинности.
Фабрика QueryPrivateTodos фильтров конечных точек — это локальная функция, которая изменяет параметры обработчика TodoDb маршрутов, чтобы разрешить доступ к частным данным и хранить данные о частных объектах.
Группы маршрутов также поддерживают вложенные группы и сложные шаблоны префикса с параметрами маршрута и ограничениями. В следующем примере обработчик маршрутов, сопоставленный с user группой, может записывать {org} параметры маршрута, {group} определенные в префиксах внешней группы.
Префикс также может быть пустым. Это может быть полезно для добавления метаданных конечной точки или фильтров в группу конечных точек без изменения шаблона маршрута.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Добавление фильтров или метаданных в группу ведет себя так же, как и их отдельно к каждой конечной точке перед добавлением дополнительных фильтров или метаданных, которые могли быть добавлены во внутреннюю группу или определенную конечную точку.
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);
});
В приведенном выше примере внешний фильтр регистрирует входящий запрос до внутреннего фильтра, даже если он был добавлен вторым. Так как фильтры были применены к разным группам, порядок их добавления относительно друг друга не имеет значения. Фильтры заказов добавляются, если они применяются к той же группе или определенной конечной точке.
Запрос, который /outer/inner/ будет регистрировать следующее:
/outer group filter
/inner group filter
MapGet filter
Привязка параметров
Привязка параметров — это процесс преобразования данных запроса в строго типизированные параметры, выраженные обработчиками маршрутов. Источник привязки определяет, откуда будут привязаны параметры. Источники привязки могут быть явными или выведенными на основе метода HTTP и типа параметра.
Поддерживаемые источники привязки:
- Значения маршрута
- Строка запроса
- Header
- текст (в формате JSON);
- Значения формы
- службы, предоставляемые путем внедрения зависимостей;
- Custom
В следующем обработчике маршрутов GET используются некоторые из этих источников привязки параметров:
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 { }
Функции привязки ключевых параметров
-
Явная привязка: используйте такие атрибуты, как
[FromRoute],[FromQuery],[FromHeader],[FromBody][FromForm]и[FromServices]для явного указания источников привязки. -
Привязка формы: Привязывайте значения формы с помощью
[FromForm]атрибута, включая поддержкуIFormFileиIFormFileCollectionдля загрузки файлов. - Сложные типы: привязка к коллекциям и сложным типам из форм, строк запроса и заголовков.
-
Настраиваемая привязка: реализуйте пользовательскую логику привязки с помощью
TryParse,BindAsyncили интерфейсаIBindableFromHttpContext<T>. - Необязательные параметры: поддержка типов, допускающих значение NULL, и значений по умолчанию для необязательных параметров.
- Внедрение зависимостей: параметры автоматически привязаны к службам, зарегистрированным в контейнере DI.
-
Специальные типы: автоматическая привязка для
HttpContext,HttpRequest,HttpResponse,CancellationTokenClaimsPrincipalиStreamPipeReader.
Подробнее: Подробные сведения о привязке параметров, включая расширенные сценарии, проверку, приоритет привязки и устранение неполадок, см. в разделе "Привязка параметров" в минимальных приложениях API.
Responses
Обработчики маршрутов поддерживают следующие типы возвращаемых значений:
- на основе
IResult, напримерTask<IResult>иValueTask<IResult>; -
string, напримерTask<string>иValueTask<string>; -
T(любой другой тип), напримерTask<T>иValueTask<T>.
| Возвращаемое значение | Behavior | Content-Type |
|---|---|---|
IResult |
Платформа вызывает IResult.ExecuteAsync | Определяется реализацией IResult |
string |
Платформа записывает строку непосредственно в ответ | text/plain |
T (любой другой тип) |
Платформа JSON-сериализует ответ | application/json |
Более подробное руководство по возврату возвращаемых значений обработчика маршрутов см. в статье "Создание ответов в приложениях API с минимальным количеством"
Примеры возвращаемых значений
Строковые возвращаемые значения
app.MapGet("/hello", () => "Hello World");
Возвращаемые значения в формате JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Возврат типизированных результатов
Следующий код возвращает TypedResultsследующий код:
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Возвращаемые значения в формате IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
В следующем примере для настройки ответа используются встроенные типы результатов:
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" }));
Пользовательский код состояния
app.MapGet("/405", () => Results.StatusCode(405));
Text
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");
});
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
Встроенные результаты
Распространенные вспомогательные средства результатов существуют в и Results статических TypedResults классах.
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Изменение заголовков
Используйте объект HttpResponse для изменения заголовков ответов:
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";
});
Настройка результатов
Приложения могут управлять ответами через пользовательскую реализацию типа IResult. Следующий код является примером результата с типом 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);
}
}
Мы рекомендуем добавить в Microsoft.AspNetCore.Http.IResultExtensions метод расширения, чтобы эти пользовательские результаты было проще искать.
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();
Типизированные результаты
Интерфейс IResult может представлять значения, возвращаемые минимальными API, которые не используют неявную поддержку сериализации возвращаемого объекта в ответ HTTP. Статический класс Results используется для создания разных объектов IResult, которые представляют разные типы ответов. Например, установка кода статуса ответа или перенаправление на другой URL-адрес.
Реализуемые типы IResult являются общедоступными, что позволяет использовать утверждения типов при тестировании. Рассмотрим пример.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Вы можете просмотреть возвращаемые типы соответствующих методов в статическом классе TypedResults , чтобы найти правильный общедоступный IResult тип для приведения.
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Filters
Дополнительные сведения см. в статьях "Фильтры" в приложениях API "Минимальный".
Authorization
Для защиты маршрутов можно применять политики авторизации. Они могут быть объявлены через атрибут [Authorize] или с помощью метода 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();
Приведенный выше код можно создать с использованием RequireAuthorization:
app.MapGet("/auth", () => "This endpoint requires authorization")
.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("/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();
Разрешение доступа к конечной точке пользователям, не прошедшим проверку подлинности
[AllowAnonymous] разрешает доступ к конечным точкам пользователям, не прошедшим проверку подлинности:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Для маршрутов можно включить CORS с помощью политик CORS. CORS можно объявить через атрибут [EnableCors] или с помощью метода RequireCors. В следующих примерах включается 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();
Дополнительные сведения см. в статье Включение запросов CORS в ASP.NET Core.
ValidateScopes и ValidateOnBuild
ValidateScopes и ValidateOnBuild включены по умолчанию в среде разработки , но отключены в других средах.
Когда ValidateOnBuild это trueтак, контейнер DI проверяет конфигурацию службы во время сборки. Если конфигурация службы недопустима, сборка завершается ошибкой при запуске приложения, а не во время выполнения при запросе службы.
Когда ValidateScopes это trueтак, контейнер DI проверяет, не разрешена ли служба с областью действия из корневой области. Разрешение ограниченной службы из корневой области может привести к утечке памяти, так как служба хранится в памяти дольше, чем область запроса.
ValidateScopes значение ValidateOnBuild false по умолчанию в режимах, отличных от разработки, по соображениям производительности.
Следующий код по ValidateScopes умолчанию включен в режиме разработки, но отключен в режиме выпуска:
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 { }
Следующий код по ValidateOnBuild умолчанию включен в режиме разработки, но отключен в режиме выпуска:
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 { }
Следующий код отключает ValidateScopes и ValidateOnBuild в 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;
});
}
См. также
- Краткий справочник по минимальным API
- Создание документов OpenAPI
- Создание ответов в минимальных приложениях API
- Фильтры в минимальных приложениях API
- Обработка ошибок в api ASP.NET Core
- Проверка подлинности и авторизация в минимальных API
- Тестирование минимальных приложений API
- Маршрутизация с коротким каналом
- Identity Конечные точки API
- Поддержка контейнера внедрения зависимостей к ключу службы
- Взгляд за кулисами минимальных конечных точек API
- Организация api-интерфейсов ASP.NET Core
- Обсуждение проверки Fluent на сайте GitHub
Этот документ:
- Предоставляет краткий справочник по минимальным API.
- Предназначен для опытных разработчиков. Общие сведения см. в руководстве по созданию минимального API с помощью ASP.NET Core.
В набор минимальных API входят следующие элементы:
WebApplication
Шаблон ASP.NET Core создает следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Приведенный выше код можно создать, набрав dotnet new web в командной строке или выбрав в Visual Studio шаблон пустого веб-проекта.
Следующий код создает WebApplication (app) без явного создания WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create инициализирует новый экземпляр класса WebApplication с предварительно настроенными значениями по умолчанию.
WebApplication автоматически добавляет следующее ПО промежуточного слоя в минимальные приложения API в зависимости от определенных условий:
-
UseDeveloperExceptionPageсначала добавляется при выполненииHostingEnvironment"Development"действия . -
UseRoutingдобавляется второй, если пользовательский код еще не вызвалUseRoutingи если настроены конечные точки, напримерapp.MapGet. -
UseEndpointsдобавляется в конце конвейера ПО промежуточного слоя, если настроены какие-либо конечные точки. -
UseAuthenticationдобавляется сразу после того,UseRoutingкак пользовательский код еще не звонилUseAuthenticationи можетIAuthenticationSchemeProviderбыть обнаружен в поставщике услуг.IAuthenticationSchemeProviderдобавляется по умолчанию при использованииAddAuthentication, а службы обнаруживаются с помощьюIServiceProviderIsService. -
UseAuthorizationдобавляется далее, если пользовательский код еще не вызвалUseAuthorizationи можетIAuthorizationHandlerProviderбыть обнаружен в поставщике услуг.IAuthorizationHandlerProviderдобавляется по умолчанию при использованииAddAuthorization, а службы обнаруживаются с помощьюIServiceProviderIsService. - Пользовательские ПО промежуточного слоя и конечные точки добавляются между
UseRoutingиUseEndpoints.
Следующий код фактически представляет собой то, что добавляется в приложение автоматический по промежуточному слоям:
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 => {});
В некоторых случаях конфигурация ПО промежуточного слоя по умолчанию не является правильной для приложения и требует изменения. Например, UseCors следует вызывать до UseAuthentication и UseAuthorization. Приложение должно вызываться UseAuthentication и UseAuthorization при UseCors вызове:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Если по промежуточному слоям следует запустить перед сопоставлением маршрутов, следует вызвать и UseRouting по промежуточному слоям следует поместить перед вызовом UseRouting.
UseEndpoints Не требуется в этом случае, так как он автоматически добавляется, как описано ранее:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
При добавлении по промежуточного слоя терминала:
- По промежуточному слоям необходимо добавить после
UseEndpoints. - Приложение должно вызываться
UseRoutingиUseEndpointsтаким образом, чтобы по промежуточному слоя терминала можно было разместить в правильном расположении.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
ПО промежуточного слоя терминала — это ПО промежуточного слоя, которое выполняется, если конечная точка не обрабатывает запрос.
Использование портов
Если вы создаете веб-приложение с помощью Visual Studio или dotnet new, автоматически создается файл Properties/launchSettings.json с указанием портов, на которых отвечает это приложение. Запуск приложения из Visual Studio с параметрами портов, представленными в следующих примерах, возвращает диалоговое окно с сообщением об ошибке Unable to connect to web server 'AppName'. Visual Studio возвращает ошибку, так как ожидается порт, указанный в Properties/launchSettings.json, но приложение использует порт, указанный в app.Run("http://localhost:3000"). Выполните в командной строке следующий пример кода для изменения портов.
В следующих разделах задается порт, на котором отвечает приложение.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
В приведенном выше коде приложение использует порт 3000.
Несколько портов
В следующем коде приложение использует порты 3000 и 4000.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Настройка порта из командной строки
Следующая команда настраивает для приложения работу с портом 7777:
dotnet run --urls="https://localhost:7777"
Если в файле Kestrel настроена еще и конечная точка appsettings.json, то используется файл с URL-адресом, указанным в appsettings.json. Дополнительные сведения см. в разделе Конфигурация конечной точки Kestrel.
Получение порта из среды
Следующий код считывает значение порта из среды:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Для настройки порта из среды лучше всего использовать переменную среды ASPNETCORE_URLS, как показано в следующем разделе.
Настройка портов через переменную среды ASPNETCORE_URLS
Для настройки порта существует переменная среды ASPNETCORE_URLS:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS поддерживает несколько URL-адресов:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Ожидание передачи данных на всех интерфейсах
В следующих примерах демонстрируется ожидание передачи данных на всех интерфейсах
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();
Ожидание передачи данных на всех интерфейсах с помощью ASPNETCORE_URLS
В предыдущих примерах можно использовать ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Прослушивание всех интерфейсов с помощью ASPNETCORE_HTTPS_PORTS
Приведенные выше примеры могут использовать ASPNETCORE_HTTPS_PORTS и ASPNETCORE_HTTP_PORTS.
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000
Дополнительные сведения см. в разделе "Настройка конечных точек" для веб-сервера ASP.NET Core Kestrel
Выбор протокола HTTPS с сертификатом разработки
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения о сертификате разработки см. в разделе Доверие к сертификату разработки HTTPS в среде ASP.NET Core на ОС Windows и macOS.
Указание протокола HTTPS с пользовательским сертификатом
В следующих разделах показано, как указать пользовательский сертификат с помощью appsettings.json файла и конфигурации.
Настройка пользовательского сертификата в appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Настройка пользовательского сертификата в конфигурации
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();
Использование API сертификатов
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();
Чтение данных из среды
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();
Дополнительные сведения об использовании среды см. в средах выполнения ASP.NET Core
Configuration
Следующий код считывает данные из системы конфигурации:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
Дополнительные сведения см. в статье Конфигурация в ASP.NET Core.
Logging
Следующий код записывает сообщение в журнал при запуске приложения:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения см. в разделе "Ведение журнала" в .NET и ASP.NET Core
Доступ к контейнеру внедрения зависимостей (DI)
В следующем коде показано, как получить службы из контейнера DI во время запуска приложения.
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();
В следующем коде показано, как получить доступ к ключам из контейнера DI с помощью атрибута [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.";
}
Дополнительные сведения о внедрении зависимостей см. в разделе ASP.NET Core.
WebApplicationBuilder
Пример кода в этом разделе использует WebApplicationBuilder.
Изменение корневой папки содержимого, имени приложения и среды
Следующий код задает корневую папку для содержимого, имя приложения и среду:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию.
Дополнительные сведения см. в статье Обзор основных понятий ASP.NET Core.
Изменение корневого каталога содержимого, имени приложения и среды с помощью переменных среды или командной строки
В следующей таблице представлены переменные среды и аргументы командной строки, которые позволяют изменить корневую папку содержимого, имя приложения и среду:
| feature | Переменная среды | Аргумент командной строки |
|---|---|---|
| Имя приложения | ASPNETCORE_APPLICATIONNAME | --applicationName |
| Имя среды | ASPNETCORE_ENVIRONMENT | --environment |
| Корневой каталог содержимого | ASPNETCORE_CONTENTROOT | --contentRoot |
Все поставщики конфигурации
Следующий пример добавляет поставщик конфигурации INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Дополнительные сведения см. в разделе Поставщики конфигурации файлов в статье Конфигурация в ASP.NET Core.
Конфигурация чтения
По умолчанию WebApplicationBuilder считывает конфигурацию из нескольких источников, в том числе:
-
appSettings.jsonиappSettings.{environment}.json. - Переменные среды
- Командная строка
Полный список источников конфигурации см. в разделе "Конфигурация по умолчанию" в ASP.NET Core.
Следующий код считывает из конфигурации значение HelloKey и отображает его в конечной точке /. Если это значение конфигурации равно NULL, в message сохраняется значение "Hello":
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Чтение данных из среды
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Добавление поставщиков ведения журнала
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();
Добавление служб
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();
Настройка IHostBuilder
К существующим методам расширения IHostBuilder можно обращаться через свойство 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();
Настройка IWebHostBuilder
К существующим методам расширения IWebHostBuilder можно обращаться через свойство 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();
Изменение корневой папки веб-сайта
Корневая папка веб-сайта задается относительно корневой папки содержимого. По умолчанию это wwwroot. Веб-корень — это место, где Промежуточное ПО для статических файлов ищет статические файлы. Корневой веб-сайт можно изменить с помощью WebHostOptions, командной строки или метода UseWebRoot:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Пользовательский контейнер внедрения зависимостей (DI)
В следующем примере используется 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();
Добавление ПО промежуточного слоя
Любое существующее ПО промежуточного слоя для ASP.NET Core можно настроить в WebApplication:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
Страница со сведениями об исключении для разработчика
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию. Страница исключений для разработчиков включена в предварительно настроенных параметрах по умолчанию. При выполнении следующего кода в среде разработки переход к адресу / отображает страницу с удобным представлением информации об исключении.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ПО промежуточного слоя ASP.NET Core
В следующей таблице собраны некоторые примеры ПО промежуточного слоя, часто используемого с минимальными API.
| Middleware | Description | API |
|---|---|---|
| Authentication | Обеспечивает поддержку проверки подлинности. | UseAuthentication |
| Authorization | Обеспечивает поддержку авторизации. | UseAuthorization |
| CORS | Настраивает общий доступ к ресурсам независимо от источника. | UseCors |
| Обработчик исключений | Глобально обрабатывает исключения, создаваемые конвейером ПО промежуточного слоя. | UseExceptionHandler |
| Переадресация заголовков | Пересылает заголовки, переданные через прокси-сервер, в текущий запрос. | UseForwardedHeaders |
| Перенаправление HTTPS | Перенаправляет все запросы с HTTP на HTTPS. | UseHttpsRedirection |
| Строгое обеспечение безопасности транспорта HTTP (HSTS) | ПО промежуточного слоя для повышения безопасности, которое добавляет специальный заголовок ответа. | UseHsts |
| Ведение журнала запросов | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них. | UseHttpLogging |
| Время ожидания запроса | Предоставляет поддержку настройки времени ожидания запроса, глобального значения по умолчанию и для каждой конечной точки. | UseRequestTimeouts |
| Ведение журнала запросов W3C | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них в формате консорциума W3C. | UseW3CLogging |
| Кэширование ответов | Обеспечивает поддержку для кэширования откликов. | UseResponseCaching |
| Сжатие ответов | Обеспечивает поддержку для сжатия откликов. | UseResponseCompression |
| Session | Обеспечивает поддержку для управления пользовательскими сеансами. | UseSession |
| Статические файлы | Обеспечивает поддержку для обработки статических файлов и просмотра каталогов. | UseStaticFiles, UseFileServer |
| WebSockets | Обеспечивает поддержку протокола WebSockets. | UseWebSockets |
В следующих разделах рассматриваются обработка запросов: маршрутизация, привязка параметров и ответы.
Routing
Настроенный WebApplication метод поддерживает Map{Verb} и MapMethods где {Verb} используется метод HTTP с регистром верблюда, например Get, PostPut или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();
Аргументы Delegate , передаваемые этим методам, называются обработчиками маршрутов.
Обработчики маршрутов
Обработчики маршрутов — это методы, которые выполняются при обнаружении соответствия для маршрута. В роли обработчика маршрут может выступать лямбда-выражение, локальная функция, метод экземпляра или статический метод. Обработчики маршрутов могут быть синхронными или асинхронными.
Лямбда-выражение
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();
Локальная функция
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Метод экземпляра
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";
}
}
Статический метод
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";
}
}
Конечная точка, определенная вне Program.cs
Минимальные API не должны находиться в 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" });
});
}
}
См. также группы маршрутов далее в этой статье.
Именованные конечные точки и создание ссылок
Конечные точки можно указать имена для создания URL-адресов конечной точки. Использование именованной конечной точки позволяет избежать сложных путей кода в приложении:
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();
Приведенный выше код отображает The link to the hello route is /hello из конечной точки /.
ПРИМЕЧАНИЕ. Имена конечных точек чувствительны к регистру.
Имена конечных точек:
- Оно должно быть глобально уникальным.
- используются в качестве идентификатора операции OpenAPI, если включена поддержка OpenAPI. Дополнительные сведения см. в статье OpenAPI.
Параметры маршрута
Параметры маршрута могут быть захвачены в составе определения шаблона маршрута:
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();
Приведенный выше код возвращает The user id is 3 and book id is 7 из URI /users/3/books/7.
Обработчик маршрута может объявлять параметры, которые нужно захватывать. Когда запрос выполняется в маршрут с параметрами, объявленными для записи, параметры анализируются и передаются обработчику. Это позволяет легко получать значения в строго типизированном виде. В приведенном выше коде userId и bookId имеют тип int.
В приведенном выше коде создается исключение, если значение маршрута не может быть преобразовано в тип int. Запрос GET по маршруту /users/hello/books/3 выдает следующее исключение:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Использование подстановочных знаков и перехват всех маршрутов
Следующая функция перехвата всех маршрутов возвращает значение Routing to hello из конечной точки "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ограничения маршрута
Ограничения маршрута ограничивают возможности сопоставления маршрута.
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();
В приведенной ниже таблице перечислены представленные выше примеры шаблонов маршрутов и их поведение.
| Шаблон маршрута | Пример соответствующего URI |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Дополнительные сведения см. в разделе Справочник по ограничениям маршрутов в статье Маршрутизация в ASP.NET Core.
Группы маршрутов
Метод MapGroup расширения помогает упорядочивать группы конечных точек с общим префиксом. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata которые добавляют метаданные конечной точки.
Например, следующий код создает две аналогичные группы конечных точек:
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;
}
В этом сценарии можно использовать относительный адрес заголовка Location201 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);
}
Первая группа конечных точек будет соответствовать только запросам, префиксным и /public/todos доступным без какой-либо проверки подлинности. Вторая группа конечных точек будет соответствовать только запросам, префиксным и /private/todos требующим проверки подлинности.
Фабрика QueryPrivateTodos фильтров конечных точек — это локальная функция, которая изменяет параметры обработчика TodoDb маршрутов, чтобы разрешить доступ к частным данным и хранить данные о частных объектах.
Группы маршрутов также поддерживают вложенные группы и сложные шаблоны префикса с параметрами маршрута и ограничениями. В следующем примере обработчик маршрутов, сопоставленный с user группой, может записывать {org} параметры маршрута, {group} определенные в префиксах внешней группы.
Префикс также может быть пустым. Это может быть полезно для добавления метаданных конечной точки или фильтров в группу конечных точек без изменения шаблона маршрута.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Добавление фильтров или метаданных в группу ведет себя так же, как и их отдельно к каждой конечной точке перед добавлением дополнительных фильтров или метаданных, которые могли быть добавлены во внутреннюю группу или определенную конечную точку.
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);
});
В приведенном выше примере внешний фильтр регистрирует входящий запрос до внутреннего фильтра, даже если он был добавлен вторым. Так как фильтры были применены к разным группам, порядок их добавления относительно друг друга не имеет значения. Фильтры заказов добавляются, если они применяются к той же группе или определенной конечной точке.
Запрос, который /outer/inner/ будет регистрировать следующее:
/outer group filter
/inner group filter
MapGet filter
Привязка параметров
Привязка параметров — это процесс преобразования данных запроса в строго типизированные параметры, выраженные обработчиками маршрутов. Источник привязки определяет, откуда будут привязаны параметры. Источники привязки могут быть явными или выведенными на основе метода HTTP и типа параметра.
Поддерживаемые источники привязки:
- Значения маршрута
- Строка запроса
- Header
- текст (в формате JSON);
- Значения формы
- службы, предоставляемые путем внедрения зависимостей;
- Custom
В следующем обработчике маршрутов GET используются некоторые из этих источников привязки параметров:
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 { }
Функции привязки ключевых параметров
-
Явная привязка: используйте такие атрибуты, как
[FromRoute],[FromQuery],[FromHeader],[FromBody][FromForm]и[FromServices]для явного указания источников привязки. -
Привязка формы: Привязывайте значения формы с помощью
[FromForm]атрибута, включая поддержкуIFormFileиIFormFileCollectionдля загрузки файлов. - Сложные типы: привязка к коллекциям и сложным типам из форм, строк запроса и заголовков.
-
Настраиваемая привязка: реализуйте пользовательскую логику привязки с помощью
TryParse,BindAsyncили интерфейсаIBindableFromHttpContext<T>. - Необязательные параметры: поддержка типов, допускающих значение NULL, и значений по умолчанию для необязательных параметров.
- Внедрение зависимостей: параметры автоматически привязаны к службам, зарегистрированным в контейнере DI.
-
Специальные типы: автоматическая привязка для
HttpContext,HttpRequest,HttpResponse,CancellationTokenClaimsPrincipalиStreamPipeReader.
Подробнее: Подробные сведения о привязке параметров, включая расширенные сценарии, проверку, приоритет привязки и устранение неполадок, см. в разделе "Привязка параметров" в минимальных приложениях API.
Responses
Обработчики маршрутов поддерживают следующие типы возвращаемых значений:
- на основе
IResult, напримерTask<IResult>иValueTask<IResult>; -
string, напримерTask<string>иValueTask<string>; -
T(любой другой тип), напримерTask<T>иValueTask<T>.
| Возвращаемое значение | Behavior | Content-Type |
|---|---|---|
IResult |
Платформа вызывает IResult.ExecuteAsync | Определяется реализацией IResult |
string |
Платформа записывает строку непосредственно в ответ | text/plain |
T (любой другой тип) |
Платформа JSON-сериализует ответ | application/json |
Более подробное руководство по возврату возвращаемых значений обработчика маршрутов см. в статье "Создание ответов в приложениях API с минимальным количеством"
Примеры возвращаемых значений
Строковые возвращаемые значения
app.MapGet("/hello", () => "Hello World");
Возвращаемые значения в формате JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Возврат типизированных результатов
Следующий код возвращает TypedResultsследующий код:
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Возвращаемые значения в формате IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
В следующем примере для настройки ответа используются встроенные типы результатов:
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" }));
Пользовательский код состояния
app.MapGet("/405", () => Results.StatusCode(405));
Text
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");
});
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
Встроенные результаты
Распространенные вспомогательные средства результатов существуют в и Results статических TypedResults классах.
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Изменение заголовков
Используйте объект HttpResponse для изменения заголовков ответов:
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";
});
Настройка результатов
Приложения могут управлять ответами через пользовательскую реализацию типа IResult. Следующий код является примером результата с типом 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);
}
}
Мы рекомендуем добавить в Microsoft.AspNetCore.Http.IResultExtensions метод расширения, чтобы эти пользовательские результаты было проще искать.
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();
Типизированные результаты
Интерфейс IResult может представлять значения, возвращаемые минимальными API, которые не используют неявную поддержку сериализации возвращаемого объекта в ответ HTTP. Статический класс Results используется для создания разных объектов IResult, которые представляют разные типы ответов. Например, установка кода статуса ответа или перенаправление на другой URL-адрес.
Реализуемые типы IResult являются общедоступными, что позволяет использовать утверждения типов при тестировании. Рассмотрим пример.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Вы можете просмотреть возвращаемые типы соответствующих методов в статическом классе TypedResults , чтобы найти правильный общедоступный IResult тип для приведения.
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Filters
Дополнительные сведения см. в статьях "Фильтры" в приложениях API "Минимальный".
Authorization
Для защиты маршрутов можно применять политики авторизации. Они могут быть объявлены через атрибут [Authorize] или с помощью метода 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();
Приведенный выше код можно создать с использованием RequireAuthorization:
app.MapGet("/auth", () => "This endpoint requires authorization")
.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("/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();
Разрешение доступа к конечной точке пользователям, не прошедшим проверку подлинности
[AllowAnonymous] разрешает доступ к конечным точкам пользователям, не прошедшим проверку подлинности:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Для маршрутов можно включить CORS с помощью политик CORS. CORS можно объявить через атрибут [EnableCors] или с помощью метода RequireCors. В следующих примерах включается 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();
Дополнительные сведения см. в статье Включение запросов CORS в ASP.NET Core.
ValidateScopes и ValidateOnBuild
ValidateScopes и ValidateOnBuild включены по умолчанию в среде разработки , но отключены в других средах.
Когда ValidateOnBuild это trueтак, контейнер DI проверяет конфигурацию службы во время сборки. Если конфигурация службы недопустима, сборка завершается ошибкой при запуске приложения, а не во время выполнения при запросе службы.
Когда ValidateScopes это trueтак, контейнер DI проверяет, не разрешена ли служба с областью действия из корневой области. Разрешение ограниченной службы из корневой области может привести к утечке памяти, так как служба хранится в памяти дольше, чем область запроса.
ValidateScopes значение ValidateOnBuild false по умолчанию в режимах, отличных от разработки, по соображениям производительности.
Следующий код по ValidateScopes умолчанию включен в режиме разработки, но отключен в режиме выпуска:
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 { }
Следующий код по ValidateOnBuild умолчанию включен в режиме разработки, но отключен в режиме выпуска:
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 { }
Следующий код отключает ValidateScopes и ValidateOnBuild в 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;
});
}
См. также
- Краткий справочник по минимальным API
- Создание документов OpenAPI
- Создание ответов в минимальных приложениях API
- Фильтры в минимальных приложениях API
- Обработка ошибок в api ASP.NET Core
- Проверка подлинности и авторизация в минимальных API
- Тестирование минимальных приложений API
- Маршрутизация с коротким каналом
- Identity Конечные точки API
- Поддержка контейнера внедрения зависимостей к ключу службы
- Взгляд за кулисами минимальных конечных точек API
- Организация api-интерфейсов ASP.NET Core
- Обсуждение проверки Fluent на сайте GitHub
Этот документ:
- Предоставляет краткий справочник по минимальным API.
- Предназначен для опытных разработчиков. Общие сведения см . в руководстве по созданию минимального API с помощью ASP.NET Core
В набор минимальных API входят следующие элементы:
WebApplication
Шаблон ASP.NET Core создает следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Приведенный выше код можно создать, набрав dotnet new web в командной строке или выбрав в Visual Studio шаблон пустого веб-проекта.
Следующий код создает WebApplication (app) без явного создания WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create инициализирует новый экземпляр класса WebApplication с предварительно настроенными значениями по умолчанию.
WebApplication автоматически добавляет следующее ПО промежуточного слоя в минимальные приложения API в зависимости от определенных условий:
-
UseDeveloperExceptionPageсначала добавляется при выполненииHostingEnvironment"Development"действия . -
UseRoutingдобавляется второй, если пользовательский код еще не вызвалUseRoutingи если настроены конечные точки, напримерapp.MapGet. -
UseEndpointsдобавляется в конце конвейера ПО промежуточного слоя, если настроены какие-либо конечные точки. -
UseAuthenticationдобавляется сразу после того,UseRoutingкак пользовательский код еще не звонилUseAuthenticationи можетIAuthenticationSchemeProviderбыть обнаружен в поставщике услуг.IAuthenticationSchemeProviderдобавляется по умолчанию при использованииAddAuthentication, а службы обнаруживаются с помощьюIServiceProviderIsService. -
UseAuthorizationдобавляется далее, если пользовательский код еще не вызвалUseAuthorizationи можетIAuthorizationHandlerProviderбыть обнаружен в поставщике услуг.IAuthorizationHandlerProviderдобавляется по умолчанию при использованииAddAuthorization, а службы обнаруживаются с помощьюIServiceProviderIsService. - Пользовательские ПО промежуточного слоя и конечные точки добавляются между
UseRoutingиUseEndpoints.
Следующий код фактически представляет собой то, что добавляется в приложение автоматический по промежуточному слоям:
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 => {});
В некоторых случаях конфигурация ПО промежуточного слоя по умолчанию не является правильной для приложения и требует изменения. Например, UseCors следует вызывать до UseAuthentication и UseAuthorization. Приложение должно вызываться UseAuthentication и UseAuthorization при UseCors вызове:
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
Если по промежуточному слоям следует запустить перед сопоставлением маршрутов, следует вызвать и UseRouting по промежуточному слоям следует поместить перед вызовом UseRouting.
UseEndpoints Не требуется в этом случае, так как он автоматически добавляется, как описано ранее:
app.Use((context, next) =>
{
return next(context);
});
app.UseRouting();
// other middleware and endpoints
При добавлении по промежуточного слоя терминала:
- По промежуточному слоям необходимо добавить после
UseEndpoints. - Приложение должно вызываться
UseRoutingиUseEndpointsтаким образом, чтобы по промежуточному слоя терминала можно было разместить в правильном расположении.
app.UseRouting();
app.MapGet("/", () => "hello world");
app.UseEndpoints(e => {});
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
ПО промежуточного слоя терминала — это ПО промежуточного слоя, которое выполняется, если конечная точка не обрабатывает запрос.
Использование портов
Если вы создаете веб-приложение с помощью Visual Studio или dotnet new, автоматически создается файл Properties/launchSettings.json с указанием портов, на которых отвечает это приложение. Запуск приложения из Visual Studio с параметрами портов, представленными в следующих примерах, возвращает диалоговое окно с сообщением об ошибке Unable to connect to web server 'AppName'. Visual Studio возвращает ошибку, так как ожидается порт, указанный в Properties/launchSettings.json, но приложение использует порт, указанный в app.Run("http://localhost:3000"). Выполните в командной строке следующий пример кода для изменения портов.
В следующих разделах задается порт, на котором отвечает приложение.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
В приведенном выше коде приложение использует порт 3000.
Несколько портов
В следующем коде приложение использует порты 3000 и 4000.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Настройка порта из командной строки
Следующая команда настраивает для приложения работу с портом 7777:
dotnet run --urls="https://localhost:7777"
Если в файле Kestrel настроена еще и конечная точка appsettings.json, то используется файл с URL-адресом, указанным в appsettings.json. Дополнительные сведения см. в разделе Конфигурация конечной точки Kestrel.
Получение порта из среды
Следующий код считывает значение порта из среды:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Для настройки порта из среды лучше всего использовать переменную среды ASPNETCORE_URLS, как показано в следующем разделе.
Настройка портов через переменную среды ASPNETCORE_URLS
Для настройки порта существует переменная среды ASPNETCORE_URLS:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS поддерживает несколько URL-адресов:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Дополнительные сведения об использовании среды см. в средах выполнения ASP.NET Core
Ожидание передачи данных на всех интерфейсах
В следующих примерах демонстрируется ожидание передачи данных на всех интерфейсах
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();
Ожидание передачи данных на всех интерфейсах с помощью ASPNETCORE_URLS
В предыдущих примерах можно использовать ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Выбор протокола HTTPS с сертификатом разработки
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения о сертификате разработки см. в разделе Доверие к сертификату разработки HTTPS в среде ASP.NET Core на ОС Windows и macOS.
Указание протокола HTTPS с пользовательским сертификатом
В следующих разделах показано, как указать пользовательский сертификат с помощью appsettings.json файла и конфигурации.
Настройка пользовательского сертификата в appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Настройка пользовательского сертификата в конфигурации
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();
Использование API сертификатов
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
Следующий код считывает данные из системы конфигурации:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Config failed!";
app.MapGet("/", () => message);
app.Run();
Дополнительные сведения см. в статье Конфигурация в ASP.NET Core.
Logging
Следующий код записывает сообщение в журнал при запуске приложения:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения см. в разделе "Ведение журнала" в .NET и ASP.NET Core
Доступ к контейнеру внедрения зависимостей (DI)
В следующем коде показано, как получить службы из контейнера DI во время запуска приложения.
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();
Дополнительные сведения см. в статье Внедрение зависимостей в ASP.NET Core.
WebApplicationBuilder
Пример кода в этом разделе использует WebApplicationBuilder.
Изменение корневой папки содержимого, имени приложения и среды
Следующий код задает корневую папку для содержимого, имя приложения и среду:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию.
Дополнительные сведения см. в статье Обзор основных понятий ASP.NET Core.
Изменение корневой папки содержимого, имени приложения и среды через переменные среды или командную строку
В следующей таблице представлены переменные среды и аргументы командной строки, которые позволяют изменить корневую папку содержимого, имя приложения и среду:
| feature | Переменная среды | Аргумент командной строки |
|---|---|---|
| Имя приложения | ASPNETCORE_APPLICATIONNAME | --applicationName |
| Имя среды | ASPNETCORE_ENVIRONMENT | --environment |
| Корневой каталог содержимого | ASPNETCORE_CONTENTROOT | --contentRoot |
Все поставщики конфигурации
Следующий пример добавляет поставщик конфигурации INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Дополнительные сведения см. в разделе Поставщики конфигурации файлов в статье Конфигурация в ASP.NET Core.
Конфигурация чтения
По умолчанию WebApplicationBuilder считывает конфигурацию из нескольких источников, в том числе:
-
appSettings.jsonиappSettings.{environment}.json. - Переменные среды
- Командная строка
Следующий код считывает из конфигурации значение HelloKey и отображает его в конечной точке /. Если это значение конфигурации равно NULL, в message сохраняется значение "Hello":
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Полный список считываемых источников конфигурации представлен в разделе Конфигурация по умолчанию в статье Конфигурация в ASP.NET Core.
Добавление поставщиков ведения журнала
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();
Добавление служб
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();
Настройка IHostBuilder
К существующим методам расширения IHostBuilder можно обращаться через свойство 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();
Настройка IWebHostBuilder
К существующим методам расширения IWebHostBuilder можно обращаться через свойство 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();
Изменение корневой папки веб-сайта
Корневая папка веб-сайта задается относительно корневой папки содержимого. По умолчанию это wwwroot. Веб-корень — это место, где Промежуточное ПО для статических файлов ищет статические файлы. Корневой веб-сайт можно изменить с помощью WebHostOptions, командной строки или метода UseWebRoot:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Пользовательский контейнер внедрения зависимостей (DI)
В следующем примере используется 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();
Добавление ПО промежуточного слоя
Любое существующее ПО промежуточного слоя для ASP.NET Core можно настроить в WebApplication:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
Страница со сведениями об исключении для разработчика
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию. Страница исключений для разработчиков включена в предварительно настроенных параметрах по умолчанию. При выполнении следующего кода в среде разработки переход к адресу / отображает страницу с удобным представлением информации об исключении.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ПО промежуточного слоя ASP.NET Core
В следующей таблице собраны некоторые примеры ПО промежуточного слоя, часто используемого с минимальными API.
| Middleware | Description | API |
|---|---|---|
| Authentication | Обеспечивает поддержку проверки подлинности. | UseAuthentication |
| Authorization | Обеспечивает поддержку авторизации. | UseAuthorization |
| CORS | Настраивает общий доступ к ресурсам независимо от источника. | UseCors |
| Обработчик исключений | Глобально обрабатывает исключения, создаваемые конвейером ПО промежуточного слоя. | UseExceptionHandler |
| Переадресация заголовков | Пересылает заголовки, переданные через прокси-сервер, в текущий запрос. | UseForwardedHeaders |
| Перенаправление HTTPS | Перенаправляет все запросы с HTTP на HTTPS. | UseHttpsRedirection |
| Строгое обеспечение безопасности транспорта HTTP (HSTS) | ПО промежуточного слоя для повышения безопасности, которое добавляет специальный заголовок ответа. | UseHsts |
| Ведение журнала запросов | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них. | UseHttpLogging |
| Время ожидания запроса | Предоставляет поддержку настройки времени ожидания запроса, глобального значения по умолчанию и для каждой конечной точки. | UseRequestTimeouts |
| Ведение журнала запросов W3C | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них в формате консорциума W3C. | UseW3CLogging |
| Кэширование ответов | Обеспечивает поддержку для кэширования откликов. | UseResponseCaching |
| Сжатие ответов | Обеспечивает поддержку для сжатия откликов. | UseResponseCompression |
| Session | Обеспечивает поддержку для управления пользовательскими сеансами. | UseSession |
| Статические файлы | Обеспечивает поддержку для обработки статических файлов и просмотра каталогов. | UseStaticFiles, UseFileServer |
| WebSockets | Обеспечивает поддержку протокола WebSockets. | UseWebSockets |
В следующих разделах рассматриваются обработка запросов: маршрутизация, привязка параметров и ответы.
Routing
Настроенный WebApplication метод поддерживает Map{Verb} и MapMethods где {Verb} используется метод HTTP с регистром верблюда, например Get, PostPut или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();
Аргументы Delegate , передаваемые этим методам, называются обработчиками маршрутов.
Обработчики маршрутов
Обработчики маршрутов — это методы, которые выполняются при обнаружении соответствия для маршрута. В роли обработчика маршрут может выступать лямбда-выражение, локальная функция, метод экземпляра или статический метод. Обработчики маршрутов могут быть синхронными или асинхронными.
Лямбда-выражение
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();
Локальная функция
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Метод экземпляра
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";
}
}
Статический метод
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";
}
}
Конечная точка, определенная вне Program.cs
Минимальные API не должны находиться в 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" });
});
}
}
См. также группы маршрутов далее в этой статье.
Именованные конечные точки и создание ссылок
Конечные точки можно указать имена для создания URL-адресов конечной точки. Использование именованной конечной точки позволяет избежать сложных путей кода в приложении:
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();
Приведенный выше код отображает The link to the hello route is /hello из конечной точки /.
ПРИМЕЧАНИЕ. Имена конечных точек чувствительны к регистру.
Имена конечных точек:
- Оно должно быть глобально уникальным.
- используются в качестве идентификатора операции OpenAPI, если включена поддержка OpenAPI. Дополнительные сведения см. в статье OpenAPI.
Параметры маршрута
Параметры маршрута могут быть захвачены в составе определения шаблона маршрута:
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();
Приведенный выше код возвращает The user id is 3 and book id is 7 из URI /users/3/books/7.
Обработчик маршрута может объявлять параметры, которые нужно захватывать. Когда запрос выполняется в маршрут с параметрами, объявленными для записи, параметры анализируются и передаются обработчику. Это позволяет легко получать значения в строго типизированном виде. В приведенном выше коде userId и bookId имеют тип int.
В приведенном выше коде создается исключение, если значение маршрута не может быть преобразовано в тип int. Запрос GET по маршруту /users/hello/books/3 выдает следующее исключение:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Использование подстановочных знаков и перехват всех маршрутов
Следующая функция перехвата всех маршрутов возвращает значение Routing to hello из конечной точки "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ограничения маршрута
Ограничения маршрута ограничивают возможности сопоставления маршрута.
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();
В приведенной ниже таблице перечислены представленные выше примеры шаблонов маршрутов и их поведение.
| Шаблон маршрута | Пример соответствующего URI |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Дополнительные сведения см. в разделе Справочник по ограничениям маршрутов в статье Маршрутизация в ASP.NET Core.
Группы маршрутов
Метод MapGroup расширения помогает упорядочивать группы конечных точек с общим префиксом. Это уменьшает повторяющийся код и позволяет настраивать целые группы конечных точек с одним вызовом методов, таких как RequireAuthorization и WithMetadata которые добавляют метаданные конечной точки.
Например, следующий код создает две аналогичные группы конечных точек:
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;
}
В этом сценарии можно использовать относительный адрес заголовка Location201 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);
}
Первая группа конечных точек будет соответствовать только запросам, префиксным и /public/todos доступным без какой-либо проверки подлинности. Вторая группа конечных точек будет соответствовать только запросам, префиксным и /private/todos требующим проверки подлинности.
Фабрика QueryPrivateTodos фильтров конечных точек — это локальная функция, которая изменяет параметры обработчика TodoDb маршрутов, чтобы разрешить доступ к частным данным и хранить данные о частных объектах.
Группы маршрутов также поддерживают вложенные группы и сложные шаблоны префикса с параметрами маршрута и ограничениями. В следующем примере обработчик маршрутов, сопоставленный с user группой, может записывать {org} параметры маршрута, {group} определенные в префиксах внешней группы.
Префикс также может быть пустым. Это может быть полезно для добавления метаданных конечной точки или фильтров в группу конечных точек без изменения шаблона маршрута.
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Добавление фильтров или метаданных в группу ведет себя так же, как и их отдельно к каждой конечной точке перед добавлением дополнительных фильтров или метаданных, которые могли быть добавлены во внутреннюю группу или определенную конечную точку.
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);
});
В приведенном выше примере внешний фильтр регистрирует входящий запрос до внутреннего фильтра, даже если он был добавлен вторым. Так как фильтры были применены к разным группам, порядок их добавления относительно друг друга не имеет значения. Фильтры заказов добавляются, если они применяются к той же группе или определенной конечной точке.
Запрос, который /outer/inner/ будет регистрировать следующее:
/outer group filter
/inner group filter
MapGet filter
Привязка параметров
Привязка параметров — это процесс преобразования данных запроса в строго типизированные параметры, выраженные обработчиками маршрутов. Источник привязки определяет, откуда будут привязаны параметры. Источники привязки могут быть явными или выведенными на основе метода HTTP и типа параметра.
Поддерживаемые источники привязки:
- Значения маршрута
- Строка запроса
- Header
- текст (в формате JSON);
- службы, предоставляемые путем внедрения зависимостей;
- Custom
Привязка из значений формы изначально не поддерживается в .NET 6 и 7.
В следующем GET обработчике маршрутов используются некоторые из этих источников привязки параметров:
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 { }
В следующей таблице показана связь между параметрами, используемыми в предыдущем примере, и связанными источниками привязки.
| Parameter | Источник привязки |
|---|---|
id |
Значение маршрута |
page |
Строка запроса |
customHeader |
header |
service |
Предоставляется путем внедрения зависимостей |
Методы HTTP GET, HEAD, OPTIONS и DELETE не выполняют неявную привязку из текста запроса. Чтобы выполнить привязку из тела (как JSON) для этих методов HTTP, следует явно выполнить привязку с [FromBody] или прочитать из HttpRequest.
В следующем примере обработчик маршрута POST использует источник привязки тела (как JSON) для параметра person:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Все параметры в предыдущих примерах автоматически привязываются из данных запроса. Чтобы продемонстрировать удобство привязки параметров, в следующих обработчиках маршрутов показано, как считывать данные запроса непосредственно из запроса:
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>();
// ...
});
Явная привязка параметров
Атрибуты можно использовать для явного объявления источника, из которого привязываются параметры.
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 | Источник привязки |
|---|---|
id |
Значение маршрута с именем id |
page |
Строка запроса с именем "p" |
service |
Предоставляется путем внедрения зависимостей |
contentType |
Заголовок с именем "Content-Type" |
Note
Привязка из значений формы изначально не поддерживается в .NET 6 и 7.
Привязка параметров с внедрением зависимостей
Привязка параметров для минимальных API позволяет привязать параметры с помощью внедрения зависимостей при настройке типа в качестве службы. Явное применение атрибута [FromServices] для параметра не является обязательным. В следующем коде оба действия возвращают время:
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();
Необязательные параметры
Параметры, объявленные в обработчиках маршрутов, считаются обязательными:
- Если запрос соответствует маршруту, то обработчик маршрутов выполняется только в том случае, если в запросе есть все обязательные параметры.
- Отсутствие любого из обязательных параметров приводит к ошибке.
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 возвращено |
/products |
BadHttpRequestException: Обязательный параметр "int pageNumber" не указан в строке запроса. |
/products/1 |
Ошибка HTTP 404, нет соответствующего маршрута |
Чтобы сделать pageNumber необязательным, определите для него тип optional (необязательный) или укажите значение по умолчанию:
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 возвращено |
/products |
1 возвращено |
/products2 |
1 возвращено |
Приведенные выше значения nullable и default применяется ко всем источникам:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Приведенный выше код вызывает метод со значением NULL для параметра product, если текст запроса не отправлен.
ПРИМЕЧАНИЕ: если предоставлены недопустимые данные и параметр допускает значение NULL, обработчик маршрутов не выполняется.
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 |
Возвращается: 3 |
/products |
Возвращается: 1 |
/products?pageNumber=two |
BadHttpRequestException: Не удалось выполнить привязку параметра "Nullable<int> pageNumber" для "two". |
/products/two |
Ошибка HTTP 404, нет соответствующего маршрута |
Дополнительные сведения см. в разделе Ошибки привязки.
Специальные типы
Следующие типы привязываются без явно заданных атрибутов:
HttpContext — контекст, содержащий все сведения о текущем HTTP-запросе или ответе:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest и HttpResponse — HTTP-запрос и ответ HTTP:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken — маркер отмены, связанный с текущим HTTP-запросом:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal — пользователь, связанный с запросом, привязанным из HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Привязка текста запроса в виде Stream или PipeReader
Тело запроса может привязываться как Stream или PipeReader для эффективной поддержки сценариев, в которых пользователю необходимо обрабатывать данные и:
- Хранить данные в хранилище BLOB-объектов или поставить их в очередь у поставщика очередей.
- Обрабатывать хранимые данные с помощью рабочего процесса или облачной функции.
Например, данные могут быть помещены в очередь в Хранилище очередей Azure или храниться в Хранилище BLOB-объектов Azure.
Следующий код реализует фоновую очередь:
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;
}
Следующий код привязывает текст запроса к 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);
});
Следующий код демонстрирует полный файл Program.cs:
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();
- При чтении данных
Stream— это тот же объект, что иHttpRequest.Body. - Текст запроса по умолчанию не буферизуется. После чтения текст не перематывается назад. Поток не может быть прочитан несколько раз.
-
StreamиPipeReaderнельзя использовать за пределами обработчика минимального действия, так как базовые буферы будут удалены или использованы повторно.
Отправка файлов с помощью IFormFile и IFormFileCollection
Следующий код использует IFormFile и IFormFileCollection, чтобы отправить файл:
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();
Поддерживаются запросы на отправку файлов с проверкой подлинности посредством заголовка авторизации, сертификата клиента или заголовка cookie.
Отсутствует встроенная поддержка защиты от подделки в ASP.NET Core в .NET 7.
Антифальсификация доступна в ASP.NET Core в версии .NET 8 или более поздней версии. Однако ее можно реализовать с помощью службы IAntiforgery.
Привязка массивов и строковых значений из заголовков и строк запроса
Следующий код демонстрирует привязку строк запроса к массиву примитивных типов, массивам строк и 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]}");
Привязка строк запроса или значений заголовков к массиву сложных типов поддерживается, если для типа реализовать TryParse. Следующий код выполняет привязку к массиву строк и возвращает все элементы с указанными тегами.
// 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();
});
В следующем коде показана модель и требуемая реализация TryParse.
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;
}
}
В следующем коде выполняется привязка к массиву 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();
});
Чтобы протестировать предыдущий код, добавьте следующую конечную точку, чтобы заполнить базу данных элементами 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);
});
Используйте средство тестирования API, например HttpRepl передать следующие данные в предыдущую конечную точку:
[
{
"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"
}
}
]
В следующем коде выполняется привязка к ключу заголовка X-Todo-Id и возвращаются элементы Todo с соответствующими значениями 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
При привязке string[] из строки запроса отсутствие соответствующего значения строки запроса приведет к пустому массиву вместо значения NULL.
Привязка параметров для списков аргументов с помощью [AsParameters]
AsParametersAttribute обеспечивает простую привязку параметров к типам, а не сложную или рекурсивную привязку модели.
Рассмотрим следующий код:
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.
Рассмотрим следующую конечную точку GET:
app.MapGet("/todoitems/{id}",
async (int Id, TodoDb Db) =>
await Db.Todos.FindAsync(Id)
is Todo todo
? Results.Ok(new TodoItemDTO(todo))
: Results.NotFound());
Для замены указанных выше выделенных параметров можно использовать следующую структуру (struct):
struct TodoItemRequest
{
public int Id { get; set; }
public TodoDb Db { get; set; }
}
Оптимизированная конечная точка GET использует приведенную выше структуру (struct) с атрибутом 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());
В приведенном ниже коде показаны дополнительные конечные точки в приложении:
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();
});
Для рефакторинга списков параметров используются следующие классы:
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!;
}
В следующем коде показаны оптимизированные конечные точки, использующие AsParameters, предыдущую структуру (struct) и классы:
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();
});
Для замены предыдущих параметров можно использовать следующие типы record:
record TodoItemRequest(int Id, TodoDb Db);
record CreateTodoItemRequest(TodoItemDTO Dto, TodoDb Db);
record EditTodoItemRequest(int Id, TodoItemDTO Dto, TodoDb Db);
Использование struct с AsParameters может обеспечить лучшую производительность, чем использование типа record.
Полный пример кода приведен в репозитории AspNetCore.Docs.Samples.
Настраиваемая привязка
Существует три способа настройки привязки параметров:
- Если в качестве источника привязки маршрутов используется маршрут, запрос или заголовок, привяжите пользовательские типы путем добавления статического метода
TryParseдля нужного типа. - Управление процессом привязки осуществляется путем реализации метода
BindAsyncдля этого типа. - Для расширенных сценариев реализуйте интерфейс IBindableFromHttpContext<TSelf>, чтобы предоставить пользовательскую логику привязки непосредственно из
HttpContext.
TryParse
TryParse имеет два API:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Следующий код отображает Point: 12.3, 10.1 для 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 имеет следующие API:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Следующий код отображает SortBy:xyz, SortDirection:Desc, CurrentPage:99 для 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
}
Настраиваемая привязка параметров с помощью IBindableFromHttpContext
ASP.NET Core обеспечивает поддержку пользовательской привязки параметров в минимальных API с помощью IBindableFromHttpContext<TSelf> интерфейса. Этот интерфейс, представленный статическими абстрактными элементами C# 11, позволяет создавать типы, которые могут быть привязаны из контекста HTTP непосредственно в параметрах обработчика маршрутов.
public interface IBindableFromHttpContext<TSelf>
where TSelf : class, IBindableFromHttpContext<TSelf>
{
static abstract ValueTask<TSelf?> BindAsync(HttpContext context, ParameterInfo parameter);
}
Реализуя IBindableFromHttpContext<TSelf> интерфейс, можно создавать пользовательские типы, обрабатывающие собственную логику привязки из HttpContext. Если обработчик маршрута включает параметр этого типа, платформа автоматически вызывает статический метод BindAsync для создания экземпляра:
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}";
});
Ниже приведен пример реализации пользовательского параметра, который привязывается из заголовка 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
});
}
}
Вы также можете реализовать проверку в пользовательской логике привязки:
app.MapGet("/validated", (ValidatedParameter param) =>
{
if (string.IsNullOrEmpty(param.Value))
{
return Results.BadRequest("Value cannot be empty");
}
return Results.Ok($"Validated value: {param.Value}");
});
Просмотр или скачивание примера кода (как скачать)
Сбои привязки
Если привязка выполняется неудачно, платформа регистрирует сообщение отладки и возвращает клиенту коды состояния, которые могут быть разными в зависимости от условий сбоя.
| Режим сбоя | Тип параметра, допускающий значение NULL | Источник привязки | Код состояния |
|---|---|---|---|
{ParameterType}.TryParse возвращает false |
yes | route/query/header | 400 |
{ParameterType}.BindAsync возвращает null |
yes | custom | 400 |
Выдает {ParameterType}.BindAsync |
Не имеет значения | custom | 500 |
| Не удалось десериализовать текст JSON | Не имеет значения | body | 400 |
Неправильный тип содержимого (не application/json) |
Не имеет значения | body | 415 |
Приоритет привязки
Правила для определения источника привязки на основе параметра:
- Явный атрибут, определенный для атрибутов параметра (From*) в следующем порядке:
- значения маршрута:
[FromRoute]; - Строка запроса:
[FromQuery] - заголовок:
[FromHeader]; - Текст:
[FromBody] - служба:
[FromServices]; - Значения параметров:
[AsParameters]
- значения маршрута:
- Специальные типы
HttpContext-
HttpRequest(HttpContext.Request) -
HttpResponse(HttpContext.Response) -
ClaimsPrincipal(HttpContext.User) -
CancellationToken(HttpContext.RequestAborted) -
IFormFileCollection(HttpContext.Request.Form.Files) -
IFormFile(HttpContext.Request.Form.Files[paramName]) -
Stream(HttpContext.Request.Body) -
PipeReader(HttpContext.Request.BodyReader)
- Тип параметра имеет допустимый статический
BindAsyncметод. - Тип параметра является строкой или имеет допустимый статический
TryParseметод.- Если имя параметра существует в шаблоне маршрута. В
app.Map("/todo/{id}", (int id) => {});,idпривязана к маршруту. - Привязывается из строки запроса.
- Если имя параметра существует в шаблоне маршрута. В
- Если тип параметра является службой, предоставляемой путем внедрения зависимостей, то в качестве источника он использует эту службу.
- Параметр извлекается из текста запроса.
Настройка параметров десериализации JSON для привязки текста
Источник привязки тела используется System.Text.Json для десериализации. Изменить этот параметр по умолчанию невозможно, но можно настроить параметры сериализации и десериализации JSON.
Настройка параметров десериализации JSON глобально
Параметры, которые применяются глобально для приложения, можно настроить путем ConfigureHttpJsonOptionsвызова. В следующем примере содержатся общедоступные поля и форматы выходных данных 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
// }
Так как пример кода настраивает сериализацию и десериализацию, он может считывать NameField и включать NameField в выходной КОД JSON.
Настройка параметров десериализации JSON для конечной точки
ReadFromJsonAsync имеет перегрузки, принимаюющие JsonSerializerOptions объект. В следующем примере содержатся общедоступные поля и форматы выходных данных 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
// }
Так как приведенный выше код применяет настраиваемые параметры только к десериализации, выходные данные JSON исключаются NameField.
Считывание текста запроса
Считайте текст запроса напрямую, используя параметр HttpContext или 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();
Предыдущий код:
- Обращается к тексту запроса с помощью HttpRequest.BodyReader.
- Копирует текст запроса в локальный файл.
Responses
Обработчики маршрутов поддерживают следующие типы возвращаемых значений:
- на основе
IResult, напримерTask<IResult>иValueTask<IResult>; -
string, напримерTask<string>иValueTask<string>; -
T(любой другой тип), напримерTask<T>иValueTask<T>.
| Возвращаемое значение | Behavior | Content-Type |
|---|---|---|
IResult |
Платформа вызывает IResult.ExecuteAsync | Определяется реализацией IResult |
string |
Платформа записывает строку непосредственно в ответ | text/plain |
T (любой другой тип) |
Платформа JSON-сериализует ответ | application/json |
Более подробное руководство по возврату возвращаемых значений обработчика маршрутов см. в статье "Создание ответов в приложениях API с минимальным количеством"
Примеры возвращаемых значений
Строковые возвращаемые значения
app.MapGet("/hello", () => "Hello World");
Возвращаемые значения в формате JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Возврат типизированных результатов
Следующий код возвращает TypedResultsследующий код:
app.MapGet("/hello", () => TypedResults.Ok(new Message() { Text = "Hello World!" }));
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Возвращаемые значения в формате IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
В следующем примере для настройки ответа используются встроенные типы результатов:
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" }));
Пользовательский код состояния
app.MapGet("/405", () => Results.StatusCode(405));
Text
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");
});
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Redirect
app.MapGet("/old-path", () => Results.Redirect("/new-path"));
File
app.MapGet("/download", () => Results.File("myfile.text"));
Встроенные результаты
Распространенные вспомогательные средства результатов существуют в и Results статических TypedResults классах.
TypedResults Возврат предпочтительнее возвращатьResults. Дополнительные сведения см. в разделе TypedResults и Results.
Настройка результатов
Приложения могут управлять ответами через пользовательскую реализацию типа IResult. Следующий код является примером результата с типом 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);
}
}
Мы рекомендуем добавить в Microsoft.AspNetCore.Http.IResultExtensions метод расширения, чтобы эти пользовательские результаты было проще искать.
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();
Типизированные результаты
Интерфейс IResult может представлять значения, возвращаемые минимальными API, которые не используют неявную поддержку сериализации возвращаемого объекта в ответ HTTP. Статический класс Results используется для создания разных объектов IResult, которые представляют разные типы ответов. Например, установка кода статуса ответа или перенаправление на другой URL-адрес.
Реализуемые типы IResult являются общедоступными, что позволяет использовать утверждения типов при тестировании. Рассмотрим пример.
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Вы можете просмотреть возвращаемые типы соответствующих методов в статическом классе TypedResults , чтобы найти правильный общедоступный IResult тип для приведения.
Дополнительные примеры см. в статье "Создание ответов в минимальных приложениях API".
Filters
Просмотр фильтров в минимальных приложениях API
Authorization
Для защиты маршрутов можно применять политики авторизации. Они могут быть объявлены через атрибут [Authorize] или с помощью метода 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();
Приведенный выше код можно создать с использованием RequireAuthorization:
app.MapGet("/auth", () => "This endpoint requires authorization")
.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("/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();
Разрешение доступа к конечной точке пользователям, не прошедшим проверку подлинности
[AllowAnonymous] разрешает доступ к конечным точкам пользователям, не прошедшим проверку подлинности:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Для маршрутов можно включить CORS с помощью политик CORS. CORS можно объявить через атрибут [EnableCors] или с помощью метода RequireCors. В следующих примерах включается 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();
Дополнительные сведения см. в статье Включение запросов CORS в ASP.NET Core.
См. также
Этот документ:
- Предоставляет краткий справочник по минимальным API.
- Предназначен для опытных разработчиков. Общие сведения см . в руководстве по созданию минимального API с помощью ASP.NET Core
В набор минимальных API входят следующие элементы:
- WebApplication и WebApplicationBuilder.
- Обработчики маршрутов
WebApplication
Шаблон ASP.NET Core создает следующий код:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Приведенный выше код можно создать, набрав dotnet new web в командной строке или выбрав в Visual Studio шаблон пустого веб-проекта.
Следующий код создает WebApplication (app) без явного создания WebApplicationBuilder:
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run();
WebApplication.Create инициализирует новый экземпляр класса WebApplication с предварительно настроенными значениями по умолчанию.
Использование портов
Если вы создаете веб-приложение с помощью Visual Studio или dotnet new, автоматически создается файл Properties/launchSettings.json с указанием портов, на которых отвечает это приложение. Запуск приложения из Visual Studio с параметрами портов, представленными в следующих примерах, возвращает диалоговое окно с сообщением об ошибке Unable to connect to web server 'AppName'. Выполните в командной строке следующий пример кода для изменения портов.
В следующих разделах задается порт, на котором отвечает приложение.
var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello World!");
app.Run("http://localhost:3000");
В приведенном выше коде приложение использует порт 3000.
Несколько портов
В следующем коде приложение использует порты 3000 и 4000.
var app = WebApplication.Create(args);
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.MapGet("/", () => "Hello World");
app.Run();
Настройка порта из командной строки
Следующая команда настраивает для приложения работу с портом 7777:
dotnet run --urls="https://localhost:7777"
Если в файле Kestrel настроена еще и конечная точка appsettings.json, то используется файл с URL-адресом, указанным в appsettings.json. Дополнительные сведения см. в разделе Конфигурация конечной точки Kestrel.
Получение порта из среды
Следующий код считывает значение порта из среды:
var app = WebApplication.Create(args);
var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";
app.MapGet("/", () => "Hello World");
app.Run($"http://localhost:{port}");
Для настройки порта из среды лучше всего использовать переменную среды ASPNETCORE_URLS, как показано в следующем разделе.
Настройка портов через переменную среды ASPNETCORE_URLS
Для настройки порта существует переменная среды ASPNETCORE_URLS:
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS поддерживает несколько URL-адресов:
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
Ожидание передачи данных на всех интерфейсах
В следующих примерах демонстрируется ожидание передачи данных на всех интерфейсах
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();
Ожидание передачи данных на всех интерфейсах с помощью ASPNETCORE_URLS
В предыдущих примерах можно использовать ASPNETCORE_URLS
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Выбор протокола HTTPS с сертификатом разработки
var app = WebApplication.Create(args);
app.Urls.Add("https://localhost:3000");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения о сертификате разработки см. в разделе Доверие к сертификату разработки HTTPS в среде ASP.NET Core на ОС Windows и macOS.
Указание протокола HTTPS с пользовательским сертификатом
В следующих разделах показано, как указать пользовательский сертификат с помощью appsettings.json файла и конфигурации.
Настройка пользовательского сертификата в appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
Настройка пользовательского сертификата в конфигурации
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();
Использование API сертификатов
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();
Чтение данных из среды
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();
Дополнительные сведения об использовании среды см. в средах выполнения ASP.NET Core
Configuration
Следующий код считывает данные из системы конфигурации:
var app = WebApplication.Create(args);
var message = app.Configuration["HelloKey"] ?? "Hello";
app.MapGet("/", () => message);
app.Run();
Дополнительные сведения см. в статье Конфигурация в ASP.NET Core.
Logging
Следующий код записывает сообщение в журнал при запуске приложения:
var app = WebApplication.Create(args);
app.Logger.LogInformation("The app started");
app.MapGet("/", () => "Hello World");
app.Run();
Дополнительные сведения см. в разделе "Ведение журнала" в .NET и ASP.NET Core
Доступ к контейнеру внедрения зависимостей (DI)
В следующем коде показано, как получить службы из контейнера DI во время запуска приложения.
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();
Дополнительные сведения см. в статье Внедрение зависимостей в ASP.NET Core.
WebApplicationBuilder
Пример кода в этом разделе использует WebApplicationBuilder.
Изменение корневой папки содержимого, имени приложения и среды
Следующий код задает корневую папку для содержимого, имя приложения и среду:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});
Console.WriteLine($"Application Name: {builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name: {builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path: {builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");
var app = builder.Build();
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию.
Дополнительные сведения см. в статье Обзор основных понятий ASP.NET Core.
Изменение корневой папки содержимого, имени приложения и среды через переменные среды или командную строку
В следующей таблице представлены переменные среды и аргументы командной строки, которые позволяют изменить корневую папку содержимого, имя приложения и среду:
| feature | Переменная среды | Аргумент командной строки |
|---|---|---|
| Имя приложения | ASPNETCORE_APPLICATIONNAME | --applicationName |
| Имя среды | ASPNETCORE_ENVIRONMENT | --environment |
| Корневой каталог содержимого | ASPNETCORE_CONTENTROOT | --contentRoot |
Все поставщики конфигурации
Следующий пример добавляет поставщик конфигурации INI:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddIniFile("appsettings.ini");
var app = builder.Build();
Дополнительные сведения см. в разделе Поставщики конфигурации файлов в статье Конфигурация в ASP.NET Core.
Конфигурация чтения
По умолчанию WebApplicationBuilder считывает конфигурацию из нескольких источников, в том числе:
-
appSettings.jsonиappSettings.{environment}.json. - Переменные среды
- Командная строка
Полный список считываемых источников конфигурации представлен в разделе Конфигурация по умолчанию в статье Конфигурация в ASP.NET Core.
Следующий код считывает из конфигурации значение HelloKey и отображает его в конечной точке /. Если это значение конфигурации равно NULL, в message сохраняется значение "Hello":
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Чтение данных из среды
var builder = WebApplication.CreateBuilder(args);
var message = builder.Configuration["HelloKey"] ?? "Hello";
var app = builder.Build();
app.MapGet("/", () => message);
app.Run();
Добавление поставщиков ведения журнала
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();
Добавление служб
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();
Настройка IHostBuilder
К существующим методам расширения IHostBuilder можно обращаться через свойство 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();
Настройка IWebHostBuilder
К существующим методам расширения IWebHostBuilder можно обращаться через свойство 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();
Изменение корневой папки веб-сайта
Корневая папка веб-сайта задается относительно корневой папки содержимого. По умолчанию это wwwroot. Веб-корень — это место, где Промежуточное ПО для статических файлов ищет статические файлы. Корневой веб-сайт можно изменить с помощью WebHostOptions, командной строки или метода UseWebRoot:
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});
var app = builder.Build();
app.Run();
Пользовательский контейнер внедрения зависимостей (DI)
В следующем примере используется 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();
Добавление ПО промежуточного слоя
Любое существующее ПО промежуточного слоя для ASP.NET Core можно настроить в WebApplication:
var app = WebApplication.Create(args);
// Setup the file server to serve static files.
app.UseFileServer();
app.MapGet("/", () => "Hello World!");
app.Run();
Дополнительные сведения см. в статье ПО промежуточного слоя ASP.NET Core.
Страница со сведениями об исключении для разработчика
WebApplication.CreateBuilder инициализирует новый экземпляр класса WebApplicationBuilder с предварительно настроенными значениями по умолчанию. Страница исключений для разработчиков включена в предварительно настроенных параметрах по умолчанию. При выполнении следующего кода в среде разработки переход к адресу / отображает страницу с удобным представлением информации об исключении.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an exception.");
});
app.Run();
ПО промежуточного слоя ASP.NET Core
В следующей таблице собраны некоторые примеры ПО промежуточного слоя, часто используемого с минимальными API.
| Middleware | Description | API |
|---|---|---|
| Authentication | Обеспечивает поддержку проверки подлинности. | UseAuthentication |
| Authorization | Обеспечивает поддержку авторизации. | UseAuthorization |
| CORS | Настраивает общий доступ к ресурсам независимо от источника. | UseCors |
| Обработчик исключений | Глобально обрабатывает исключения, создаваемые конвейером ПО промежуточного слоя. | UseExceptionHandler |
| Переадресация заголовков | Пересылает заголовки, переданные через прокси-сервер, в текущий запрос. | UseForwardedHeaders |
| Перенаправление HTTPS | Перенаправляет все запросы с HTTP на HTTPS. | UseHttpsRedirection |
| Строгое обеспечение безопасности транспорта HTTP (HSTS) | ПО промежуточного слоя для повышения безопасности, которое добавляет специальный заголовок ответа. | UseHsts |
| Ведение журнала запросов | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них. | UseHttpLogging |
| Ведение журнала запросов W3C | Обеспечивает поддержку ведения журнала для HTTP-запросов и ответов на них в формате консорциума W3C. | UseW3CLogging |
| Кэширование ответов | Обеспечивает поддержку для кэширования откликов. | UseResponseCaching |
| Сжатие ответов | Обеспечивает поддержку для сжатия откликов. | UseResponseCompression |
| Session | Обеспечивает поддержку для управления пользовательскими сеансами. | UseSession |
| Статические файлы | Обеспечивает поддержку для обработки статических файлов и просмотра каталогов. | UseStaticFiles, UseFileServer |
| WebSockets | Обеспечивает поддержку протокола WebSockets. | UseWebSockets |
Обработка запросов
В следующих разделах рассматриваются механизмы маршрутизации, привязки параметров и ответов.
Routing
Настроенный WebApplication поддерживает Map{Verb} и 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();
Обработчики маршрутов
Обработчики маршрутов — это методы, которые выполняются при обнаружении соответствия для маршрута. Обработчики маршрутов могут быть функцией любой фигуры, включая синхронную или асинхронную. В роли обработчика маршрут может выступать лямбда-выражение, локальная функция, метод экземпляра или статический метод.
Лямбда-выражение
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();
Локальная функция
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
string LocalFunction() => "This is local function";
app.MapGet("/", LocalFunction);
app.Run();
Метод экземпляра
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";
}
}
Статический метод
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";
}
}
Именованные конечные точки и создание ссылок
Конечные точки можно указать имена для создания URL-адресов конечной точки. Использование именованной конечной точки позволяет избежать сложных путей кода в приложении:
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();
Приведенный выше код отображает The link to the hello endpoint is /hello из конечной точки /.
ПРИМЕЧАНИЕ. Имена конечных точек чувствительны к регистру.
Имена конечных точек:
- Оно должно быть глобально уникальным.
- используются в качестве идентификатора операции OpenAPI, если включена поддержка OpenAPI. Дополнительные сведения см. в статье OpenAPI.
Параметры маршрута
Параметры маршрута могут быть захвачены в составе определения шаблона маршрута:
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();
Приведенный выше код возвращает The user id is 3 and book id is 7 из URI /users/3/books/7.
Обработчик маршрута может объявлять параметры, которые нужно захватывать. Когда выполняется запрос по маршруту, для которого объявлены захватываемые параметры, эти параметры анализируются и передаются в обработчик. Это позволяет легко получать значения в строго типизированном виде. В приведенном выше коде userId и bookId имеют тип int.
В приведенном выше коде создается исключение, если значение маршрута не может быть преобразовано в тип int. Запрос GET по маршруту /users/hello/books/3 выдает следующее исключение:
BadHttpRequestException: Failed to bind parameter "int userId" from "hello".
Использование подстановочных знаков и перехват всех маршрутов
Следующая функция перехвата всех маршрутов возвращает значение Routing to hello из конечной точки "/posts/hello":
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/posts/{*rest}", (string rest) => $"Routing to {rest}");
app.Run();
Ограничения маршрута
Ограничения маршрута ограничивают возможности сопоставления маршрута.
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();
В приведенной ниже таблице перечислены представленные выше примеры шаблонов маршрутов и их поведение.
| Шаблон маршрута | Пример соответствующего URI |
|---|---|
/todos/{id:int} |
/todos/1 |
/todos/{text} |
/todos/something |
/posts/{slug:regex(^[a-z0-9_-]+$)} |
/posts/mypost |
Дополнительные сведения см. в разделе Справочник по ограничениям маршрутов в статье Маршрутизация в ASP.NET Core.
Привязка параметров
Привязка параметров — это процесс преобразования данных запроса в строго типизированные параметры, выраженные обработчиками маршрутов. Источник привязки определяет, откуда будут привязаны параметры. Источники привязки могут быть явными или выведенными на основе метода HTTP и типа параметра.
Поддерживаемые источники привязки:
- Значения маршрута
- Строка запроса
- Header
- текст (в формате JSON);
- службы, предоставляемые путем внедрения зависимостей;
- Custom
Note
Привязка из значений форм не имеет встроенной поддержки в .NET.
В следующем примере обработчик маршрута GET использует некоторые из этих источников привязки параметров:
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 { }
В следующей таблице показана связь между параметрами, используемыми в предыдущем примере, и связанными источниками привязки.
| Parameter | Источник привязки |
|---|---|
id |
Значение маршрута |
page |
Строка запроса |
customHeader |
header |
service |
Предоставляется путем внедрения зависимостей |
Методы HTTP GET, HEAD, OPTIONS и DELETE не выполняют неявную привязку из текста запроса. Чтобы выполнить привязку из тела (как JSON) для этих методов HTTP, следует явно выполнить привязку с [FromBody] или прочитать из HttpRequest.
В следующем примере обработчик маршрута POST использует источник привязки тела (как JSON) для параметра person:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/", (Person person) => { });
record Person(string Name, int Age);
Все параметры в предыдущих примерах автоматически привязываются из данных запроса. Чтобы продемонстрировать удобство использования привязки параметров, в следующих примерах обработчиков маршрутов показано, как считывать данные запроса непосредственно из запроса:
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>();
// ...
});
Явная привязка параметров
Атрибуты можно использовать для явного объявления источника, из которого привязываются параметры.
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 | Источник привязки |
|---|---|
id |
Значение маршрута с именем id |
page |
Строка запроса с именем "p" |
service |
Предоставляется путем внедрения зависимостей |
contentType |
Заголовок с именем "Content-Type" |
Note
Привязка из значений форм не имеет встроенной поддержки в .NET.
Привязка параметров с помощью внедрения зависимостей
Привязка параметров для минимальных API позволяет привязать параметры с помощью внедрения зависимостей при настройке типа в качестве службы. Явное применение атрибута [FromServices] для параметра не является обязательным. В следующем коде оба действия возвращают время:
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();
Необязательные параметры
Параметры, объявленные в обработчиках маршрутов, считаются обязательными:
- Если запрос соответствует маршруту, то обработчик маршрутов выполняется только в том случае, если в запросе есть все обязательные параметры.
- Отсутствие любого из обязательных параметров приводит к ошибке.
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 возвращено |
/products |
BadHttpRequestException: Обязательный параметр "int pageNumber" не указан в строке запроса. |
/products/1 |
Ошибка HTTP 404, нет соответствующего маршрута |
Чтобы сделать pageNumber необязательным, определите для него тип optional (необязательный) или укажите значение по умолчанию:
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 возвращено |
/products |
1 возвращено |
/products2 |
1 возвращено |
Приведенные выше значения nullable и default применяется ко всем источникам:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/products", (Product? product) => { });
app.Run();
Приведенный выше код вызывает метод со значением NULL для параметра product, если текст запроса не отправлен.
ПРИМЕЧАНИЕ: если предоставлены недопустимые данные и параметр допускает значение NULL, обработчик маршрутов не выполняется.
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 |
Возвращается: 3 |
/products |
Возвращается: 1 |
/products?pageNumber=two |
BadHttpRequestException: Не удалось выполнить привязку параметра "Nullable<int> pageNumber" для "two". |
/products/two |
Ошибка HTTP 404, нет соответствующего маршрута |
Дополнительные сведения см. в разделе Ошибки привязки.
Специальные типы
Следующие типы привязываются без явно заданных атрибутов:
HttpContext — контекст, содержащий все сведения о текущем HTTP-запросе или ответе:
app.MapGet("/", (HttpContext context) => context.Response.WriteAsync("Hello World"));HttpRequest и HttpResponse — HTTP-запрос и ответ HTTP:
app.MapGet("/", (HttpRequest request, HttpResponse response) => response.WriteAsync($"Hello World {request.Query["name"]}"));CancellationToken — маркер отмены, связанный с текущим HTTP-запросом:
app.MapGet("/", async (CancellationToken cancellationToken) => await MakeLongRunningRequestAsync(cancellationToken));ClaimsPrincipal — пользователь, связанный с запросом, привязанным из HttpContext.User:
app.MapGet("/", (ClaimsPrincipal user) => user.Identity.Name);
Настраиваемая привязка
Существует два способа настроить привязку параметров.
- Если в качестве источника привязки маршрутов используется маршрут, запрос или заголовок, привяжите пользовательские типы путем добавления статического метода
TryParseдля нужного типа. - Управление процессом привязки осуществляется путем реализации метода
BindAsyncдля этого типа.
TryParse
TryParse имеет два API:
public static bool TryParse(string value, out T result);
public static bool TryParse(string value, IFormatProvider provider, out T result);
Следующий код отображает Point: 12.3, 10.1 для 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 имеет следующие API:
public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter);
public static ValueTask<T?> BindAsync(HttpContext context);
Следующий код отображает SortBy:xyz, SortDirection:Desc, CurrentPage:99 для 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
}
Сбои привязки
Если привязка выполняется неудачно, платформа регистрирует сообщение отладки и возвращает клиенту коды состояния, которые могут быть разными в зависимости от условий сбоя.
| Режим сбоя | Тип параметра, допускающий значение NULL | Источник привязки | Код состояния |
|---|---|---|---|
{ParameterType}.TryParse возвращает false |
yes | route/query/header | 400 |
{ParameterType}.BindAsync возвращает null |
yes | custom | 400 |
Выдает {ParameterType}.BindAsync |
Не имеет значения | custom | 500 |
| Не удалось десериализовать текст JSON | Не имеет значения | body | 400 |
Неправильный тип содержимого (не application/json) |
Не имеет значения | body | 415 |
Приоритет привязки
Правила для определения источника привязки на основе параметра:
- Явный атрибут, определенный для атрибутов параметра (From*) в следующем порядке:
- значения маршрута:
[FromRoute]; - Строка запроса:
[FromQuery] - заголовок:
[FromHeader]; - Текст:
[FromBody] - служба:
[FromServices];
- значения маршрута:
- Специальные типы
- Тип параметра имеет допустимый метод
BindAsync. - Тип параметра имеет строковое значение или допустимый метод
TryParse.- Если имя параметра существует в шаблоне маршрута. В
app.Map("/todo/{id}", (int id) => {});,idпривязана к маршруту. - Привязывается из строки запроса.
- Если имя параметра существует в шаблоне маршрута. В
- Если тип параметра является службой, предоставляемой путем внедрения зависимостей, то в качестве источника он использует эту службу.
- Параметр извлекается из текста запроса.
Настройка привязки JSON
Текст запроса в качестве источника привязки использует System.Text.Json для десериализации. Изменить это значение по умолчанию нельзя, но привязку можно настроить с помощью других методов, описанных выше. Чтобы настроить параметры сериализатора JSON, используйте код следующего вида.
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;
}
Предыдущий код:
- Настраивает входные и выходные параметры JSON по умолчанию.
- Возвращает следующий код JSON
При публикации{ "id": 1, "name": "Joe Smith" }{ "Id": 1, "Name": "Joe Smith" }
Считывание текста запроса
Считайте текст запроса напрямую, используя параметр HttpContext или 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();
Предыдущий код:
- Обращается к тексту запроса с помощью HttpRequest.BodyReader.
- Копирует текст запроса в локальный файл.
Responses
Обработчики маршрутов поддерживают следующие типы возвращаемых значений:
- на основе
IResult, напримерTask<IResult>иValueTask<IResult>; -
string, напримерTask<string>иValueTask<string>; -
T(любой другой тип), напримерTask<T>иValueTask<T>.
| Возвращаемое значение | Behavior | Content-Type |
|---|---|---|
IResult |
Платформа вызывает IResult.ExecuteAsync | Определяется реализацией IResult |
string |
Платформа записывает строку непосредственно в ответ | text/plain |
T (любой другой тип) |
Платформа будет выполнять сериализацию ответа в формат JSON | application/json |
Примеры возвращаемых значений
Строковые возвращаемые значения
app.MapGet("/hello", () => "Hello World");
Возвращаемые значения в формате JSON
app.MapGet("/hello", () => new { Message = "Hello World" });
Возвращаемые значения в формате IResult
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
В следующем примере для настройки ответа используются встроенные типы результатов:
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" }));
Пользовательский код состояния
app.MapGet("/405", () => Results.StatusCode(405));
Text
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"));
Встроенные результаты
В статическом классе Microsoft.AspNetCore.Http.Results есть несколько распространенных вспомогательных методов результата.
| Description | Тип ответа | Код состояния | API |
|---|---|---|---|
| Записывает ответ JSON с дополнительными параметрами | application/json | 200 | Results.Json |
| Записывает ответ в формате JSON | application/json | 200 | Results.Ok |
| Записывает текстовый ответ | по умолчанию text/plain, можно настроить | 200 | Results.Text |
| Записывает ответ в формате байтов | По умолчанию application/octet-stream, можно настроить | 200 | Results.Bytes |
| Записывает в ответ поток байтов | По умолчанию application/octet-stream, можно настроить | 200 | Results.Stream |
| Выполняет потоковую передачу файла для скачивания с заголовком content-disposition | По умолчанию application/octet-stream, можно настроить | 200 | Results.File |
| Задает код состояния 404 с необязательным ответом JSON | N/A | 404 | Results.NotFound |
| Задает код состояния 204 | N/A | 204 | Results.NoContent |
| Задает код состояния 422 с необязательным ответом JSON | N/A | 422 | Results.UnprocessableEntity |
| Задает код состояния 400 с необязательным ответом JSON | N/A | 400 | Results.BadRequest |
| Задает код состояния 409 с необязательным ответом JSON | N/A | 409 | Results.Conflict |
| Записывает в ответ объект в формате JSON со сведениями о проблеме | N/A | По умолчанию 500, можно настроить | Results.Problem |
| Записывает в ответ объект в формате JSON со сведениями о проблеме и ошибками проверки | N/A | N/A, настраиваемая | Results.ValidationProblem |
Настройка результатов
Приложения могут управлять ответами через пользовательскую реализацию типа IResult. Следующий код является примером результата с типом 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);
}
}
Мы рекомендуем добавить в Microsoft.AspNetCore.Http.IResultExtensions метод расширения, чтобы эти пользовательские результаты было проще искать.
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
Для защиты маршрутов можно применять политики авторизации. Они могут быть объявлены через атрибут [Authorize] или с помощью метода 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();
Приведенный выше код можно создать с использованием RequireAuthorization:
app.MapGet("/auth", () => "This endpoint requires authorization")
.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("/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();
Разрешение доступа к конечной точке пользователям, не прошедшим проверку подлинности
[AllowAnonymous] разрешает доступ к конечным точкам пользователям, не прошедшим проверку подлинности:
app.MapGet("/login", [AllowAnonymous] () => "This endpoint is for all roles.");
app.MapGet("/login2", () => "This endpoint also for all roles.")
.AllowAnonymous();
CORS
Для маршрутов можно включить CORS с помощью политик CORS. CORS можно объявить через атрибут [EnableCors] или с помощью метода RequireCors. В следующих примерах включается 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();
Дополнительные сведения см. в статье Включение запросов CORS в ASP.NET Core.