Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Подсказка
Это фрагмент из книги, архитектор современных веб-приложений с ASP.NET Core и Azure, доступный в .NET Docs или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.
Важно не обязательно сделать правильно с первой попытки. Это жизненно важно сделать это правильно в последний раз." - Эндрю Хант и Дэвид Томас
ASP.NET Core — это кроссплатформенная платформа с открытым исходным кодом для создания современных облачных веб-приложений. ASP.NET Основные приложения являются упрощенными и модульными, благодаря встроенной поддержке внедрения зависимостей, что обеспечивает более высокую удобство тестирования и удобство обслуживания. В сочетании с MVC, которая поддерживает создание современных веб-API в дополнение к приложениям на основе просмотра, ASP.NET Core — это мощная платформа для создания корпоративных веб-приложений.
MVC и Razor Pages
ASP.NET Core MVC предлагает множество функций, полезных для создания веб-API и приложений. Термин MVC означает "Model-View-Controller", шаблон пользовательского интерфейса, который разбивает обязанности реагирования на запросы пользователей на несколько частей. Помимо этого шаблона, вы также можете реализовать функции в приложениях ASP.NET Core как Razor Pages.
Razor Pages встроены в ASP.NET Core MVC и используют те же функции для маршрутизации, привязки модели, фильтров, авторизации и т. д. Однако вместо того, чтобы иметь отдельные папки и файлы для контроллеров, моделей, представлений и т. д. и использования маршрутизации на основе атрибутов, Razor Pages помещаются в одну папку ("/Pages"), маршрут на основе их относительного расположения в этой папке, а также обрабатывают запросы с обработчиками вместо действий контроллера. В результате при работе с Razor Pages все необходимые файлы и классы обычно находятся в одном месте, а не распределяются по всему веб-проекту.
Узнайте больше о том, как применяются MVC, Razor Pages и связанные шаблоны в примере приложения eShopOnWeb.
При создании нового приложения ASP.NET Core у вас должен быть план для создаваемого приложения. При создании проекта в интегрированной среде разработки или с помощью dotnet new
команды CLI можно выбрать один из нескольких шаблонов. Наиболее распространенными шаблонами проектов являются пустые, веб-API, веб-приложение и веб-приложение (модель-View-Controller). Хотя вы можете принять это решение только при первом создании проекта, это не необратимое решение. Проект веб-API использует стандартные контроллеры моделиView-Controller. По умолчанию он просто не имеет представлений. Аналогичным образом, шаблон веб-приложения по умолчанию использует Razor Pages, поэтому не хватает папки Views. Вы можете добавить папку Views в эти проекты позже для поддержки поведения на основе представлений. Веб-API и проектыView-Controller по умолчанию не включают папку Pages, но ее можно добавить позже для поддержки поведения на основе Razor Pages. Эти три шаблона можно рассматривать как поддерживающие три различных типа взаимодействия пользователей по умолчанию: данные (веб-API), страницы и представления. Однако при желании можно смешивать и комбинировать любые или все эти шаблоны в одном проекте.
Почему Razor Pages?
Razor Pages — это подход по умолчанию для новых веб-приложений в Visual Studio. Razor Pages предлагает более простой способ создания функций приложения на основе страниц, таких как формы, отличные от SPA. Использование контроллеров и представлений было распространено для приложений с очень большими контроллерами, которые работали с различными зависимостями и моделями представления и возвращали множество различных представлений. Это приводило к усложнению и часто к тому, что контроллеры не эффективно соблюдали принцип единой ответственности или принцип открытости/закрытости. Razor Pages решает эту проблему, инкапсулируя логику на стороне сервера для определённой логической «страницы» в веб-приложении с разметкой Razor. Страница Razor, которая не имеет логики на стороне сервера, может состоять только из файла Razor (например, Index.cshtml). Однако в большинстве нетривиальных страниц Razor будет присутствовать связанный класс PageModel, который по общему соглашению называется так же, как и файл страницы Razor с расширением ".cs" (например, "Index.cshtml.cs").
Модель страницы Razor Page объединяет обязанности контроллера MVC и модели представления. Вместо обработки запросов с помощью методов управления контроллером выполняются обработчики модели страницы, такие как OnGet(), которые автоматически отображают связанную страницу. Razor Pages упрощает процесс создания отдельных страниц в приложении ASP.NET Core, предоставляя все архитектурные функции ASP.NET Core MVC. Это хороший выбор по умолчанию для новых функциональных возможностей на основе страниц.
Когда следует использовать MVC
Если вы создаете веб-API, шаблон MVC имеет больше смысла, чем пытаться использовать Razor Pages. Если проект будет предоставлять только конечные точки веб-API, вы должны в идеале начать с шаблона проекта веб-API. В противном случае можно легко добавлять контроллеры и связанные конечные точки API в любое приложение ASP.NET Core. Используйте подход MVC на основе представления, если вы переносите существующее приложение из ASP.NET MVC 5 или более ранней версии в ASP.NET Core MVC и хотите сделать это с минимальным количеством усилий. После первоначальной миграции вы можете оценить, следует ли использовать Razor Pages для новых функций или даже как полную миграцию. Дополнительные сведения о переносе приложений .NET 4.x в .NET 8 см. в статье Перенос существующих ASP.NET приложений в ASP.NET Core eBook.
Независимо от того, хотите ли вы создать веб-приложение с помощью представлений Razor Pages или MVC, ваше приложение будет иметь аналогичную производительность и будет включать поддержку внедрения зависимостей, фильтров, привязки модели, проверки и т. д.
Сопоставление запросов на ответы
В основе ASP.NET приложения Core сопоставляют входящие запросы с исходящими ответами. На низком уровне это сопоставление выполняется с помощью средства промежуточного слоя, а простые приложения ASP.NET Core и микрослужбы могут состоять исключительно из пользовательского промежуточного слоя. При использовании ASP.NET Core MVC можно работать на более высоком уровне, думать с точки зрения маршрутов, контроллеров и действий. Каждый входящий запрос сравнивается с таблицей маршрутизации приложения и при обнаружении соответствующего маршрута вызывается связанный метод действия (принадлежащий контроллеру) для обработки запроса. Если маршрут сопоставления не найден, вызывается обработчик ошибок (в данном случае возвращается результат NotFound).
ASP.NET приложения Core MVC могут использовать обычные маршруты, маршруты атрибутов или оба. Обычные маршруты определяются в коде, указывая соглашения о маршрутизации с помощью синтаксиса, как показано в следующем примере:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
В этом примере в таблицу маршрутизации добавлен маршрут с именем default. Он определяет шаблон маршрута с заполнителями для controller
, action
и id
. Заполнители controller
и action
имеют заданные значения по умолчанию (Home
и Index
соответственно), а заполнитель id
является необязательным (в силу применения к нему "?"). Соглашение, определенное здесь, указывает, что первая часть запроса должна соответствовать имени контроллера, второй части действия, а при необходимости третья часть будет представлять параметр идентификатора. Обычные маршруты обычно определяются в одном месте для приложения, например в Program.cs , где настроен конвейер ПО промежуточного слоя запроса.
Маршруты, задаваемые атрибутами, применяются непосредственно к контроллерам и действиям, вместо того чтобы быть заданными глобально. Этот подход имеет преимущество сделать их гораздо более обнаруживаемыми при просмотре определенного метода, но означает, что сведения о маршрутизации не хранятся в одном месте в приложении. С помощью маршрутов атрибутов можно легко указать несколько маршрутов для заданного действия, а также объединить маршруты между контроллерами и действиями. Рассмотрим пример.
[Route("Home")]
public class HomeController : Controller
{
[Route("")] // Combines to define the route template "Home"
[Route("Index")] // Combines to define route template "Home/Index"
[Route("/")] // Does not combine, defines the route template ""
public IActionResult Index() {}
}
Маршруты можно указать в [HttpGet] и аналогичных атрибутах, избегая добавления отдельных атрибутов [Route]. Маршруты атрибутов также могут использовать маркеры для уменьшения необходимости повторять имена контроллеров или действий, как показано ниже:
[Route("[controller]")]
public class ProductsController : Controller
{
[Route("")] // Matches 'Products'
[Route("Index")] // Matches 'Products/Index'
public IActionResult Index() {}
}
Razor Pages не использует маршрутизацию атрибутов. Дополнительные сведения о шаблоне маршрута для страницы Razor можно указать в рамках директивы @page
:
@page "{id:int}"
В предыдущем примере страница под вопросом будет соответствовать маршруту с целым id
параметром. Например, страница Products.cshtml , расположенная в корневом каталоге /Pages
, отвечает на запросы, как в следующем:
/Products/123
После сопоставления заданного запроса с маршрутом, но перед вызовом метода действия ASP.NET Core MVC выполнит привязку модели и проверку модели в запросе. Привязка модели отвечает за преобразование входящих HTTP-данных в типы .NET, указанные в качестве параметров вызываемого метода действия. Например, если метод действия ожидает параметр вида int id
, привязка модели попытается извлечь этот параметр из значения, предоставленного в рамках запроса. Для этого привязка модели ищет значения в размещенной форме, значения в самом маршруте и строковые значения запроса. Если id
значение найдено, оно будет преобразовано в целочисленное число перед передачей в метод действия.
После привязки модели, но перед вызовом метода действия выполняется проверка модели. Проверка модели использует необязательные атрибуты для типа модели и может помочь обеспечить соответствие предоставленного объекта модели определенным требованиям к данным. Некоторые значения могут быть указаны как обязательные или ограничены определенной длиной или числовым диапазоном и т. д. Если указаны атрибуты проверки, но модель не соответствует их требованиям, свойство ModelState.IsValid будет false, а набор правил проверки сбоя будет доступен для отправки клиенту, выполняющего запрос.
Если вы используете проверку модели, убедитесь, что модель действительна перед выполнением любых команд изменения состояния, чтобы убедиться, что приложение не повреждено недопустимыми данными. Фильтр можно использовать, чтобы избежать необходимости добавления кода для этой проверки в каждом действии. ASP.NET фильтры Core MVC предлагают способ перехвата групп запросов, чтобы общие политики и перекрестные проблемы могли применяться на целевой основе. Фильтры можно применять к отдельным действиям, целым контроллерам или глобально для приложения.
Для веб-API ASP.NET Core MVC поддерживает согласование содержимого, позволяя запросам указать способ форматирования ответов. На основе заголовков, предоставленных в запросе, действия, возвращающие данные, форматируют ответ в ФОРМАТЕ XML, JSON или другом поддерживаемом формате. Эта функция позволяет использовать один и тот же API для нескольких клиентов с различными требованиями к формату данных.
Проекты веб-API следует рассмотреть возможность использования [ApiController]
атрибута, который может применяться к отдельным контроллерам, к базовому классу контроллера или ко всей сборке. Этот атрибут добавляет автоматическую проверку модели и любое действие с недопустимой моделью возвращает BadRequest с подробными сведениями об ошибках проверки. Атрибут также требует, чтобы все действия использовали атрибутивный маршрут вместо обычного, и возвращает более подробную информацию о деталях проблемы в ответ на ошибки.
Хранение контроллеров под контролем
Для приложений на основе страниц Razor Pages помогает сделать так, чтобы контроллеры не становились слишком большими. Каждой отдельной странице выделяются собственные файлы и классы, предназначенные исключительно для её обработчиков. До введения Razor Pages многие приложения, ориентированные на представление, будут иметь большие классы контроллеров, ответственные за множество различных действий и представлений. Эти классы, естественно, будут иметь много обязанностей и зависимостей, что делает их труднее поддерживать. Если вы обнаружите, что контроллеры с представлениями становятся слишком большими, рассмотрите возможность рефакторинга с использованием Razor Pages или внедрите шаблон, такой как посредник.
Шаблон проектирования «Посредник» используется для уменьшения зависимости между классами, при этом позволяя им общаться. В ASP.NET приложениях Core MVC этот шаблон часто используется для разбиения контроллеров на небольшие части с помощью обработчиков для выполнения работы методов действий. Для этого часто используется популярный пакет NuGet MediatR . Как правило, контроллеры включают множество различных методов действий, каждый из которых может требовать определенных зависимостей. Набор всех зависимостей, необходимых любому действию, должен быть передан в конструктор контроллера. При использовании MediatR единственной зависимостью контроллера обычно является экземпляр посредника. Затем каждое действие использует экземпляр посредника для отправки сообщения, которое обрабатывается обработчиком. Обработчик предназначен для одного конкретного действия и поэтому нуждается только в зависимостях, необходимых для этого действия. Ниже показан пример контроллера с помощью MediatR:
public class OrderController : Controller
{
private readonly IMediator _mediator;
public OrderController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> MyOrders()
{
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
return View(viewModel);
}
// other actions implemented similarly
}
В действии MyOrders
вызов Send
для обработки GetMyOrders
сообщения происходит в этом классе.
public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
{
private readonly IOrderRepository _orderRepository;
public GetMyOrdersHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request, CancellationToken cancellationToken)
{
var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
var orders = await _orderRepository.ListAsync(specification);
return orders.Select(o => new OrderViewModel
{
OrderDate = o.OrderDate,
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
{
PictureUrl = oi.ItemOrdered.PictureUri,
ProductId = oi.ItemOrdered.CatalogItemId,
ProductName = oi.ItemOrdered.ProductName,
UnitPrice = oi.UnitPrice,
Units = oi.Units
}).ToList(),
OrderNumber = o.Id,
ShippingAddress = o.ShipToAddress,
Total = o.Total()
});
}
}
Конечный результат этого подхода заключается в том, чтобы контроллеры были гораздо меньше и сосредоточены в первую очередь на маршрутизации и привязке модели, а отдельные обработчики отвечают за конкретные задачи, необходимые данной конечной точке. Этот подход также можно достичь без MediatR с помощью пакета NuGet ApiEndpoints, который пытается перенести в контроллеры API те же преимущества, что Razor Pages приносит контроллерам на основе просмотра.
Ссылки — сопоставление запросов на ответы
- Маршрутизация на действия контроллера
https://learn.microsoft.com/aspnet/core/mvc/controllers/routing- Привязка модели
https://learn.microsoft.com/aspnet/core/mvc/models/model-binding- Проверка модели
https://learn.microsoft.com/aspnet/core/mvc/models/validation- Фильтры
https://learn.microsoft.com/aspnet/core/mvc/controllers/filters- Атрибут ApiController
https://learn.microsoft.com/aspnet/core/web-api/
Работа с зависимостями
ASP.NET Core имеет встроенную поддержку и внутренне использует метод, известный как внедрение зависимостей. Внедрение зависимостей — это метод, обеспечивающий свободное связывание между разными частями приложения. Свободное связывание желательно, так как это упрощает изоляцию частей приложения, что позволяет выполнять тестирование или замену. Это также делает его менее вероятным, что изменение в одной части приложения приведет к неожиданному влиянию в другом месте приложения. Внедрение зависимостей основано на принципе инверсии зависимостей и часто является ключом к достижению открытого или закрытого принципа. При оценке того, как приложение работает со своими зависимостями, остерегайтесь запаха кода статического связывания и помните афоризм "новое - это склейка".
Статическое прилипание возникает, когда ваши классы делают вызовы статических методов или получают доступ к статическим свойствам, которые имеют побочные эффекты или зависимости от инфраструктуры. Например, если у вас есть метод, который вызывает статический метод, который, в свою очередь, записывает в базу данных, метод тесно связан с базой данных. Все, что ломает вызов к базе данных, сломает ваш метод. Тестирование таких методов является печально сложным, так как такие тесты либо требуют коммерческих библиотек макетирования для имитации статических вызовов, либо могут быть проверены только с помощью тестовой базы данных. Статические вызовы, которые не имеют никакой зависимости от инфраструктуры, особенно те вызовы, которые полностью без состояния, можно безопасно вызывать и они не влияют на сцепление или возможность тестирования, за исключением привязки кода к самому статическому вызову.
Многие разработчики понимают риски статического сцепления и глобального состояния, но все же продолжают тесно связывать свой код с конкретными реализациями посредством прямого создания экземпляров. "«Новое соединяет» предназначено для напоминания об этом связывании, а не для общего осуждения использования ключевого слова new
." Так же, как и при вызовах статических методов, новые экземпляры типов без внешних зависимостей обычно не приводят к тесной связи кода с деталями реализации или не усложняют тестирование. Но каждый раз, когда создается экземпляр класса, уделите немного времени, чтобы рассмотреть, имеет ли смысл жестко кодировать конкретный экземпляр в этом конкретном местоположении или лучше запросить этот экземпляр в качестве зависимости.
Объявление зависимостей
ASP.NET Core строится вокруг того, что методы и классы объявляют их зависимости, запрашивая их в качестве аргументов. ASP.NET приложения обычно настраиваются в Program.cs или в Startup
классе.
Замечание
Настройка приложений полностью в Program.cs — это подход по умолчанию для приложений .NET 6 (и более поздних версий) и Visual Studio 2022. Шаблоны проектов были обновлены, чтобы приступить к работе с этим новым подходом. Проекты ASP.NET Core по-прежнему могут использовать класс Startup
, если пожелают.
Настройка служб в Program.cs
Для очень простых приложений можно подключить зависимости непосредственно в файле Program.cs с помощью .WebApplicationBuilder
После добавления всех необходимых служб построитель используется для создания приложения.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
Настройка служб в Startup.cs
Сама Startup.cs настроена для поддержки внедрения зависимостей в нескольких точках. Если вы используете Startup
класс, вы можете предоставить его конструктору, и он может запрашивать зависимости через него, например:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
}
}
Класс Startup
интересен в том, что для него нет явных требований к типу. Он не наследует от специального Startup
базового класса и не реализует какой-либо определенный интерфейс. Вы можете задать конструктор или не задавать его, и можете указать столько параметров в конструкторе, сколько захотите. Когда хост, настроенный для вашего приложения, запустится, он вызовет класс Startup
(если вы указали его использовать), и воспользуется внедрением зависимостей, чтобы предоставить все зависимости, необходимые для класса Startup
. Конечно, если вы запрашиваете параметры, которые не настроены в контейнере служб, используемом ASP.NET Core, вы получите исключение, но если вы придерживаетесь зависимостей, о которых контейнер знает, вы можете запросить что угодно.
Внедрение зависимостей встроено в приложения ASP.NET Core прямо с самого начала при создании экземпляра запуска. Это еще не конец для класса Startup. Можно также запросить зависимости в методе Configure
:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
}
Метод ConfigureServices является исключением из этого поведения; он должен принимать только один параметр типа IServiceCollection
. Он действительно не нуждается в поддержке внедрения зависимостей, так как с одной стороны он несет ответственность за добавление объектов в контейнер служб, а с другой — доступ ко всем настроенным службам через IServiceCollection
параметр. Таким образом, вы можете работать с зависимостями, определенными в коллекции служб ASP.NET Core, в любой части класса Startup
, либо запрашивая необходимую службу в качестве параметра, либо работая с элементом IServiceCollection
в ConfigureServices
.
Замечание
Если необходимо убедиться, что определенные службы доступны для вашего Startup
класса, их можно настроить с помощью IWebHostBuilder
метода и метода ConfigureServices
внутри CreateDefaultBuilder
вызова.
Класс Startup является моделью того, как следует структурировать другие части вашего приложения ASP.NET Core: от контроллеров и промежуточного слоя до фильтров и собственных служб. В каждом случае следует следовать принципу явных зависимостей, запрашивая зависимости, а не напрямую создавать их, а использовать внедрение зависимостей во всем приложении. Будьте осторожны с тем, где и как вы непосредственно создаете экземпляры объектов, особенно если это касается сервисов и объектов, работающих с инфраструктурой или имеющих побочные эффекты. Предпочитайте работать с абстракциями, определенными в ядре приложения, и передавать их в качестве аргументов для жесткой кодировки ссылок на определенные типы реализаций.
Структурирование приложения
Монолитные приложения обычно имеют одну точку входа. В случае веб-приложения ASP.NET Core точка входа будет веб-проектом ASP.NET Core. Однако это не означает, что решение должно состоять только из одного проекта. Полезно разбить приложение на разные уровни, чтобы следовать разделению проблем. После разделения на слои полезно выйти за рамки папок в отдельные проекты, которые могут помочь достичь лучшей инкапсуляции. Лучший подход к достижению этих целей с помощью приложения ASP.NET Core — это вариант чистой архитектуры, рассмотренной в главе 5. В соответствии с этим подходом решение приложения будет содержать отдельные библиотеки для пользовательского интерфейса, инфраструктуры и ApplicationCore.
Помимо этих проектов, также включены отдельные тестовые проекты (тестирование рассматривается в главе 9).
Объектная модель и интерфейсы приложения должны быть помещены в проект ApplicationCore. Этот проект будет иметь минимальное количество зависимостей (и не будет зависеть от конкретных инфраструктурных проблем), а другие проекты в составе решения будут использовать его. Бизнес-сущности, которые необходимо сохранить, определяются в проекте ApplicationCore, как и службы, которые не зависят от инфраструктуры напрямую.
Сведения о реализации, такие как выполнение сохраняемости или способ отправки уведомлений пользователю, хранятся в проекте инфраструктуры. Этот проект будет ссылаться на пакеты, относящиеся к реализации, например Entity Framework Core, но не должны предоставлять подробные сведения об этих реализациях за пределами проекта. Службы инфраструктуры и репозитории должны реализовывать интерфейсы, определенные в проекте ApplicationCore, а его реализации сохраняемости отвечают за получение и хранение сущностей, определенных в ApplicationCore.
Проект пользовательского интерфейса ASP.NET Core отвечает за любые проблемы уровня пользовательского интерфейса, но не должен включать бизнес-логику или сведения о инфраструктуре. На самом деле, в идеале он даже не должен иметь зависимость от проекта инфраструктуры, что поможет гарантировать, что зависимость между двумя проектами не будет введена случайно. Это можно сделать с помощью стороннего контейнера DI, такого как Autofac, который позволяет определять правила DI в классах модулей в каждом проекте.
Другой подход к разобщечению приложения от сведений о реализации заключается в том, чтобы вызвать микрослужбы приложения, возможно, развернутые в отдельных контейнерах Docker. Это обеспечивает еще большее разделение проблем и разбиение, чем использование DI между двумя проектами, но имеет дополнительную сложность.
Организация функций
По умолчанию приложения ASP.NET Core упорядочивают структуру папок, чтобы включить контроллеры, представления, а также часто встречается ViewModels. Клиентский код для поддержки этих серверных структур обычно хранится отдельно в папке wwwroot. Однако крупные приложения могут столкнуться с проблемами с этой организацией, так как для работы с любой данной функцией часто требуется переход между этими папками. Это становится все сложнее, так как количество файлов и вложенных папок в каждой папке растет, что требует большого количества прокрутки через Solution Explorer. Одним из решений этой проблемы является упорядочение кода приложения по функциям , а не по типу файла. Этот стиль организации обычно называется папками компонентов или срезами компонентов (см. также: вертикальные срезы).
ASP.NET Core MVC поддерживает области для этой цели. С помощью областей можно создавать отдельные наборы папок контроллеров и представлений (а также любые связанные модели) в каждой папке "Область". На рисунке 7-1 показана пример структуры папок с помощью областей.
Рис. 7-1. Пример организации области
При использовании областей необходимо использовать атрибуты для декорирования контроллеров именем области, к которой они относятся:
[Area("Catalog")]
public class HomeController
{}
Вам также нужно добавить поддержку областей в ваши маршруты.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "areaRoute", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
Помимо встроенной поддержки областей, вы также можете использовать собственную структуру папок и соглашения вместо атрибутов и пользовательских маршрутов. Это позволит вам иметь функциональные папки, которые не включают в себя отдельные папки для представлений, контроллеров и т. д., сохраняя иерархию более плоской и упрощая просмотр всех связанных файлов в одном месте для каждой функции. Для API можно использовать папки для замены контроллеров, и каждая папка может содержать все конечные точки API и связанные с ними DTOs.
ASP.NET Core использует встроенные типы соглашений для управления его поведением. Эти соглашения можно изменить или заменить. Например, можно создать соглашение, которое автоматически получает имя функции для заданного контроллера на основе его пространства имен (который обычно коррелирует с папкой, в которой находится контроллер):
public class FeatureConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
controller.Properties.Add("feature",
GetFeatureName(controller.ControllerType));
}
private string GetFeatureName(TypeInfo controllerType)
{
string[] tokens = controllerType.FullName.Split('.');
if (!tokens.Any(t => t == "Features")) return "";
string featureName = tokens
.SkipWhile(t => !t.Equals("features", StringComparison.CurrentCultureIgnoreCase))
.Skip(1)
.Take(1)
.FirstOrDefault();
return featureName;
}
}
Затем вы указываете это соглашение в качестве параметра при добавлении поддержки MVC в приложение ( ConfigureServices
или в Program.cs):
// ConfigureServices
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));
// Program.cs
builder.Services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));
ASP.NET Core MVC также использует соглашение для поиска представлений. Вы можете переопределить его с помощью настраиваемого соглашения, чтобы представления находились в папках функций (используя имя функции, предоставленное соглашением FeatureConvention выше). Вы можете узнать больше об этом подходе и скачать рабочий пример из статьи MSDN Magazine, срезы компонентов для ASP.NET Core MVC.
API и Blazor приложения
Если приложение содержит набор веб-API, которые должны быть защищены, эти API должны быть идеально настроены как отдельный проект из приложения View или Razor Pages. Разделение API, особенно общедоступных API, от веб-приложения на стороне сервера имеет ряд преимуществ. Эти приложения часто имеют уникальные характеристики развертывания и загрузки. Они также, скорее всего, будут применять различные механизмы безопасности. Стандартные приложения на основе форм используют аутентификацию на основе файлов cookie, а API - аутентификацию на основе токенов.
Кроме того, приложения, независимо от того, используется ли сервер Blazor или BlazorBlazor, должны создаваться в виде отдельных проектов. Приложения имеют различные характеристики среды выполнения, а также модели безопасности. Они, скорее всего, будут совместно использовать общие типы с серверным веб-приложением (или проектом API), и эти типы должны быть определены в общем проекте.
Blazor
WebAssembly Добавление интерфейса администрирования в eShopOnWeb требует добавления нескольких новых проектов. Сам BlazorWebAssemblyBlazorAdmin
проект. В проекте BlazorAdmin
определен новый набор общедоступных конечных точек API, используемых PublicApi
и настроенных для использования проверки подлинности, основанной на токене. И некоторые общие типы, используемые обоими этими проектами, хранятся в новом BlazorShared
проекте.
Можно спросить, зачем добавлять отдельный BlazorShared
проект, когда уже существует общий ApplicationCore
проект, который можно использовать для совместного использования любых типов, необходимых как для PublicApi
, так и BlazorAdmin
. Ответ заключается в том, что этот проект включает в себя всю бизнес-логику приложения и таким образом гораздо больше, чем необходимо, а также гораздо более вероятно, что необходимо обеспечить безопасность на сервере. Помните, что любая библиотека, BlazorAdmin
на которую ссылается ссылка, будет загружена в браузеры пользователей при загрузке Blazor приложения.
В зависимости от того, используется ли шаблон серверныхFor-Frontends (BFF), API, используемые BlazorWebAssembly приложением, могут не совместно использовать свои типы 100% Blazor. В частности, общедоступный API, который предназначен для использования многими различными клиентами, может определять собственные типы запросов и результатов, а не совместно использовать их в общем проекте для конкретного клиента. В примере eShopOnWeb предполагается, что PublicApi
проект, на самом деле, размещает общедоступный API, поэтому не все типы запросов и ответов приходят из BlazorShared
проекта.
Сквозные функции
По мере роста приложений все более важно учитывать перекрестные проблемы для устранения дублирования и поддержания согласованности. Некоторые примеры перекрестных проблем в приложениях ASP.NET Core: проверка подлинности, правила проверки модели, кэширование выходных данных и обработка ошибок, хотя существует много других. ASP.NET фильтры Core MVC позволяют запускать код до или после определенных шагов в конвейере обработки запросов. Например, фильтр может выполняться до и после привязки модели, до и после действия, а также до и после результата действия. Вы также можете использовать фильтр авторизации для управления доступом к остальной части конвейера. На рисунке 7-2 показано, как выполняется выполнение запроса через фильтры, если настроено.
Рис. 7-2. Выполнение запроса с помощью фильтров и конвейера запросов.
Фильтры обычно реализуются как атрибуты, поэтому их можно применять к контроллерам или действиям (или даже глобально). При добавлении в этом случае фильтры, указанные на уровне действия, переопределяют или дополняют фильтры, указанные на уровне контроллера, фильтры которых переопределяют глобальные фильтры. Например, [Route]
атрибут можно использовать для создания маршрутов между контроллерами и действиями. Аналогичным образом авторизацию можно настроить на уровне контроллера, а затем переопределить отдельными действиями, как показано в следующем примере:
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous] // overrides the Authorize attribute
public async Task<IActionResult> Login() {}
public async Task<IActionResult> ForgotPassword() {}
}
Первый метод Login использует [AllowAnonymous]
фильтр (атрибут) для переопределения набора фильтров авторизации на уровне контроллера. Действие ForgotPassword
(и любое другое действие в классе, у которых нет атрибута AllowAnonymous), потребует проверки подлинности запроса.
Фильтры можно использовать для устранения дублирования в виде распространенных политик обработки ошибок для API. Например, типичная политика API заключается в возврате ответа NotFound на запросы, которые ссылаются на несуществующие ключи, и ответа BadRequest
в случае, если проверка модели завершается ошибкой. В следующем примере показаны эти две политики в действии:
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
{
return NotFound(id);
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
author.Id = id;
await _authorRepository.UpdateAsync(author);
return Ok();
}
Не позволяйте методам ваших действий становиться загромождёнными условным кодом подобного рода. Вместо этого перенесите политики в фильтры, которые можно применять по мере необходимости. В этом примере проверка модели, которая должна происходить в любой момент отправки команды в API, может быть заменена следующим атрибутом:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
Вы можете добавить в проект ValidateModelAttribute
в качестве зависимости NuGet, включив пакет Ardalis.ValidateModel. Для API можно использовать атрибут ApiController
, чтобы применить это поведение принудительно, без необходимости в отдельном фильтре ValidateModel
.
Аналогичным образом, фильтр можно использовать для проверки наличия записи и возврата 404 перед выполнением действия, устраняя необходимость выполнения этих проверок в действии. После извлечения общих соглашений и упорядочения решения для разделения кода инфраструктуры и бизнес-логики из пользовательского интерфейса методы действий MVC должны быть очень тонкими:
[HttpPut("{id}")]
[ValidateAuthorExists]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
await _authorRepository.UpdateAsync(author);
return Ok();
}
Дополнительные сведения о реализации фильтров и скачивании рабочего примера см. в статье msdn Magazine Real-World ASP.NET Core MVC Filters.
Если вы обнаружите, что у вас есть ряд общих ответов от API на основе распространенных сценариев, таких как ошибки проверки (неправильный запрос), ресурс не найден и ошибки сервера, можно рассмотреть возможность использования абстракции результата . Результат абстракции будет возвращен службами, используемыми конечными точками API, а действие контроллера или конечная точка будут использовать фильтр для преобразования этих IActionResults
данных.
Ссылки — структурирование приложений
- Области
https://learn.microsoft.com/aspnet/core/mvc/controllers/areas- Msdn Magazine — срезы компонентов для ASP.NET Core MVC
https://learn.microsoft.com/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc- Фильтры
https://learn.microsoft.com/aspnet/core/mvc/controllers/filters- Журнал MSDN — фильтры MVC в реальном мире ASP.NET Core
https://learn.microsoft.com/archive/msdn-magazine/2016/august/asp-net-core-real-world-asp-net-core-mvc-filters- Результат eShopOnWeb
https://github.com/dotnet-architecture/eShopOnWeb/wiki/Patterns#result
Безопасность
Защита веб-приложений — это большая тема, с множеством рекомендаций. На самом базовом уровне безопасность включает в себя обеспечение того, от кого поступает конкретный запрос, а затем гарантирует, что запрос имеет доступ только к ресурсам, к которым он должен иметь доступ. Проверка подлинности — это процесс сравнения учетных данных, предоставленных запросом к данным в доверенном хранилище данных, чтобы узнать, следует ли рассматривать запрос как поступающий из известной сущности. Авторизация — это процесс ограничения доступа к определенным ресурсам на основе удостоверения пользователя. Третья проблема безопасности заключается в защите запросов от перехвата сторонними лицами, для которых необходимо по крайней мере убедиться, что SSL используется вашим приложением.
Идентичность
ASP.NET Core Identity — это система членства, используемая для поддержки функций входа в приложение. Она поддерживает локальные учетные записи пользователей, а также поддержку внешних поставщиков входа от таких поставщиков, как Учетная запись Майкрософт, Twitter, Facebook, Google и многое другое. Кроме ASP.NET Core Identity, ваше приложение может использовать проверку подлинности Windows или стороннего поставщика удостоверений, например Identity Server.
ASP.NET Core Identity включается в новые шаблоны проектов, если выбран параметр "Отдельные учетные записи пользователей". Этот шаблон включает поддержку регистрации, входа, внешних имен входа, забытых паролей и дополнительных функций.
Рис. 7-3. Выберите учетные записи отдельных пользователей, чтобы предварительно настроить идентификацию.
Поддержка аутентификации настраивается в Program.cs или Startup
, и включает в себя настройки служб, а также ПО промежуточного слоя.
Настройка Identity в Program.cs
В Program.cs вы настраиваете службы из экземпляра WebHostBuilder
, а затем, как только приложение создано, настраиваете его промежуточное ПО. Ключевыми моментами являются вызов AddDefaultIdentity
для необходимых служб и вызовы UseAuthentication
и UseAuthorization
, которые добавляют необходимое промежуточное ПО.
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Настройка идентификации при запуске приложения
// Add framework services.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddMvc();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
Важно, чтобы UseAuthentication
и UseAuthorization
отображались раньше MapRazorPages
. При настройке служб идентификации вы заметите вызов AddDefaultTokenProviders
. Это не имеет ничего общего с маркерами, которые могут использоваться для защиты веб-коммуникаций, но вместо этого относится к поставщикам, которые создают запросы, которые могут быть отправлены пользователям через SMS или электронную почту, чтобы они могли подтвердить свое удостоверение.
Дополнительные сведения о настройке двухфакторной проверки подлинности и включении внешних поставщиков входа см. в официальных документах ASP.NET Core.
Аутентификация
Проверка подлинности — это процесс определения доступа к системе. Если вы используете ASP.NET Core Identity и методы конфигурации, показанные в предыдущем разделе, он автоматически настраивает некоторые значения проверки подлинности по умолчанию в приложении. Однако вы также можете настроить эти значения по умолчанию вручную или переопределить значения, заданные AddIdentity. Если вы используете Identity, он по умолчанию настраивает аутентификацию на основе файлов cookie как схему аутентификации.
В веб-проверке подлинности обычно выполняется до пяти действий, которые могут выполняться при проверке подлинности клиента системы. К ним относятся:
- Пройти проверку подлинности. Используйте сведения, предоставленные клиентом, чтобы создать удостоверение для их использования в приложении.
- Вызов. Это действие используется для идентификации клиента.
- Запретить. Сообщите клиенту, что им запрещено выполнять действие.
- Войти. Сохраните существующего клиента любыми доступными средствами.
- Завершение сеанса. Удалите клиента из хранения данных.
Существует ряд распространенных методов выполнения проверки подлинности в веб-приложениях. Они называются схемами. Данная схема определяет действия для некоторых или всех указанных выше параметров. Некоторые схемы поддерживают только подмножество действий, и может потребоваться отдельная схема для выполнения тех, которые он не поддерживает. Например, схема OpenId-Connect (OIDC) не поддерживает авторизацию или деавторизацию, но обычно настраивается для использования проверки подлинности с помощью куки для обеспечения постоянства.
В приложении ASP.NET Core можно настроить DefaultAuthenticateScheme
и дополнительные схемы для каждого из описанных выше действий. Например, DefaultChallengeScheme
и DefaultForbidScheme
. Вызов AddIdentity настраивает ряд аспектов приложения и добавляет множество необходимых служб. Он также включает этот вызов для настройки схемы проверки подлинности:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
Эти схемы используют файлы cookie для сохраняемости и перенаправления на страницы входа для проверки подлинности по умолчанию. Эти схемы подходят для веб-приложений, взаимодействующих с пользователями через веб-браузеры, но не рекомендуется для API. Вместо этого API обычно используют другую форму аутентификации, например токены JWT.
Веб-API используются кодом, например HttpClient
в приложениях .NET и эквивалентных типах в других платформах. Эти клиенты ожидают доступный ответ от вызова API или кода состояния, указывающего, что, если есть, возникла проблема. Эти клиенты не взаимодействуют через браузер и не обрабатывают или не взаимодействуют с HTML-кодом, который может возвращать API. Таким образом, конечные точки API не подходят для перенаправления клиентов на страницы входа, если они не прошли проверку подлинности. Другая схема является более подходящей.
Чтобы настроить проверку подлинности для API, можно настроить проверку подлинности, например следующую, используемую PublicApi
проектом в справочном приложении eShopOnWeb:
builder.Services
.AddAuthentication(config =>
{
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
Хотя можно настроить несколько различных схем проверки подлинности в одном проекте, гораздо проще настроить одну схему по умолчанию. По этой причине эталонное приложение eShopOnWeb выделяет свои API-интерфейсы в отдельный проект PublicApi
, отдельно от основного проекта Web
, который включает представления приложения и Razor Pages.
Проверка подлинности в Blazor приложениях
Blazor Серверные приложения могут использовать те же функции проверки подлинности, что и любое другое приложение ASP.NET Core. Blazor WebAssembly Приложения не могут использовать встроенные поставщики удостоверений и проверки подлинности, так как они выполняются в браузере. Blazor WebAssembly приложения могут хранить состояние проверки подлинности пользователей локально и получать доступ к утверждениям, чтобы определить, какие действия должны выполнять пользователи. Однако все проверки подлинности и авторизации должны выполняться на сервере независимо от любой логики, реализованной внутри BlazorWebAssembly приложения, так как пользователи могут легко обойти приложение и взаимодействовать с API напрямую.
Ссылки — проверка подлинности
- Действия проверки подлинности и значения по умолчанию
https://stackoverflow.com/a/52493428- Аутентификация и авторизация для SPA
https://learn.microsoft.com/aspnet/core/security/authentication/identity-api-authorization- ASP.NET Core Blazor аутентификация и авторизация
https://learn.microsoft.com/aspnet/core/blazor/security/- Безопасность: проверка подлинности и авторизация в веб-формах ASP.NET и Blazor
https://learn.microsoft.com/dotnet/architecture/blazor-for-web-forms-developers/security-authentication-authorization
Авторизация
Простейшая форма авторизации включает ограничение доступа к анонимным пользователям. Эту функцию можно достичь, применяя [Authorize]
атрибут к определенным контроллерам или действиям. Если используются роли, атрибут можно дополнительно расширить, чтобы ограничить доступ пользователей, принадлежащих определенным ролям, как показано ниже.
[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{
}
В этом случае пользователи, принадлежащие к ролям HRManager
или Finance
(или к обеим), будут иметь доступ к контроллеру зарплат (SalaryController). Чтобы требовать, чтобы пользователь принадлежал нескольким ролям (а не только одному из нескольких), можно применять атрибут несколько раз, указывая требуемую роль каждый раз.
Указание определенных наборов ролей в виде строк во многих разных контроллерах и действиях может привести к нежелательному повторению. Как минимум, определите константы для этих строковых литералах и используйте константы в любом месте, где необходимо указать строку. Вы также можете настроить политики авторизации, которые инкапсулируют правила авторизации, а затем указать политику вместо отдельных ролей при применении атрибута [Authorize]
:
[Authorize(Policy = "CanViewPrivateReport")]
public IActionResult ExecutiveSalaryReport()
{
return View();
}
Используя политики таким образом, вы можете разделить типы действий, которые ограничиваются от конкретных ролей или правил, применяемых к ним. Позже при создании новой роли, необходимой для доступа к определенным ресурсам, можно просто обновить политику, а не обновлять каждый список ролей для каждого [Authorize]
атрибута.
Претензии
Утверждения — это пары значений имени, представляющие свойства прошедшего проверку подлинности пользователя. Например, можно сохранить номер сотрудника пользователей в качестве утверждения. Затем утверждения можно использовать в рамках политик авторизации. Можно создать политику с именем "EmployeeOnly", которая требует наличия предъявления с именем "EmployeeNumber"
, как показано в этом примере:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});
}
Затем эту политику можно использовать с атрибутом [Authorize]
для защиты любого контроллера и/или действия, как описано выше.
Защита веб-API
Большинство веб-API должны реализовать систему аутентификации на основе токенов. Проверка подлинности токена является не сохраняющей состояние и предназначена быть масштабируемой. В системе аутентификации на основе токенов клиент должен сначала пройти проверку подлинности у поставщика аутентификации. В случае успешного выполнения клиенту выдается токен, который представляет собой просто криптографически значимую строку символов. Наиболее распространенный формат маркеров — это веб-токен JSON или JWT (часто выраженный как "jot"). Когда клиент должен выдать запрос API, он добавляет этот маркер в качестве заголовка в запросе. Затем сервер проверяет маркер, найденный в заголовке запроса, прежде чем завершить запрос. На рисунке 7-4 показан этот процесс.
Рис. 7-4. Аутентификация на основе токенов для веб-API.
Вы можете создать собственную службу проверки подлинности, интегрировать с Azure AD и OAuth или реализовать службу с помощью средства с открытым исходным кодом, например IdentityServer.
Токены JWT могут внедрять утверждения о пользователе, которые можно прочитать на клиенте или сервере. Для просмотра содержимого токена JWT можно использовать такие средства , как jwt.io . Не сохраняйте конфиденциальные данные, такие как пароли или ключи в токенах JTW, так как их содержимое легко считывается.
При использовании маркеров JWT с SPA или BlazorWebAssembly приложениями необходимо хранить маркер где-то на клиенте, а затем добавить его в каждый вызов API. Обычно это действие выполняется как заголовок, как показано в следующем коде:
// AuthService.cs in BlazorAdmin project of eShopOnWeb
private async Task SetAuthorizationHeader()
{
var token = await GetToken();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
После вызова приведенного выше метода запросы, сделанные с _httpClient
, будут иметь маркер, внедренный в заголовки запроса, что позволит API на стороне сервера пройти проверку подлинности и авторизовать запрос.
Настраиваемая безопасность
Осторожность
Как правило, избегайте реализации собственных пользовательских реализаций безопасности.
Будьте особенно осторожны при создании собственной реализации криптографии, членства пользователей или системы генерации токенов. Существует множество коммерческих и открытых альтернатив, которые почти наверняка будут иметь лучшую безопасность, чем пользовательская реализация.
Ссылки — безопасность
- Общие сведения о документации по безопасности
https://learn.microsoft.com/aspnet/core/security/- Принудительное применение SSL в приложении ASP.NET Core
https://learn.microsoft.com/aspnet/core/security/enforcing-ssl- Общие сведения об удостоверении
https://learn.microsoft.com/aspnet/core/security/authentication/identity- Общие сведения о авторизации
https://learn.microsoft.com/aspnet/core/security/authorization/introduction- Проверка подлинности и авторизация для приложений API в службах приложений Azure
https://learn.microsoft.com/azure/app-service-api/app-service-api-authentication- Сервер удостоверений
https://github.com/IdentityServer
Обмен данными с клиентами
Помимо обслуживания страниц и реагирования на запросы к данным через веб-API, ASP.NET приложения Core могут взаимодействовать напрямую с подключенными клиентами. Этот исходящий обмен данными может использовать различные транспортные технологии, наиболее распространенные из которых являются WebSockets. ASP.NET Core SignalR — это библиотека, которая упрощает добавление функций обмена данными между клиентами в режиме реального времени в приложения. SignalR поддерживает различные транспортные технологии, включая WebSockets, и абстрагирует от разработчика многие детали реализации.
Обмен данными клиента в режиме реального времени, будь то использование WebSockets напрямую или других методов, полезен в различных сценариях приложений. Ниже приведены некоторые примеры:
Приложения комнаты чата в реальном времени
Мониторинг приложений
Обновления хода выполнения работы
Уведомления
Приложения интерактивных форм
При создании взаимодействия клиента в приложениях обычно существует два компонента:
Диспетчер соединений на стороне сервера (SignalR Hub, WebSocketManager WebSocketHandler)
Клиентская библиотека
Клиенты не ограничены браузерами— мобильными приложениями, консольными приложениями и другими собственными приложениями также могут взаимодействовать с помощью SignalR/WebSockets. Следующая простая программа отражает все содержимое, отправленное в приложение чата в консоль, в рамках примера приложения WebSocketManager:
public class Program
{
private static Connection _connection;
public static void Main(string[] args)
{
StartConnectionAsync();
_connection.On("receiveMessage", (arguments) =>
{
Console.WriteLine($"{arguments[0]} said: {arguments[1]}");
});
Console.ReadLine();
StopConnectionAsync();
}
public static async Task StartConnectionAsync()
{
_connection = new Connection();
await _connection.StartConnectionAsync("ws://localhost:65110/chat");
}
public static async Task StopConnectionAsync()
{
await _connection.StopConnectionAsync();
}
}
Рассмотрите способы, в которых ваши приложения взаимодействуют напрямую с клиентскими приложениями, и рассмотрите, улучшит ли взаимодействие в режиме реального времени взаимодействие с пользователем приложения.
Ссылки — взаимодействие с клиентами
- ASP.NET Core SignalR
https://github.com/dotnet/aspnetcore/tree/main/src/SignalR- Диспетчер WebSocket
https://github.com/radu-matei/websocket-manager
Дизайн на основе домена— следует ли применить его?
Domain-Driven Проектирование (DDD) — это гибкий подход к созданию программного обеспечения, который подчеркивает акцент на бизнес-домене. Он уделяет большое внимание коммуникации и взаимодействию с экспертами бизнес-предметной области, которые могут объяснить разработчикам, как функционирует реальная система. Например, если вы создаете систему, которая обрабатывает биржевые сделки, вашим экспертом в данной области может быть опытный биржевой брокер. DDD предназначен для решения больших, сложных бизнес-проблем и часто не подходит для небольших, простых приложений, так как инвестиции в понимание и моделирование домена не стоит его.
При создании программного обеспечения, следуя подходу DDD, ваша команда (включая нетехнических участников и заинтересованных лиц) должна разработать вездесущий язык для описания проблемного пространства. То есть для моделироваемой концепции реального мира следует использовать ту же терминологию, эквивалентную программному обеспечению и любые структуры, которые могут существовать для сохранения концепции (например, таблиц баз данных). Таким образом, понятия, описанные в универсальном языке, должны быть основой для модели домена.
Модель домена состоит из объектов, взаимодействующих друг с другом для представления поведения системы. Эти объекты могут быть разделены на следующие категории:
Сущности, представляющие объекты с нитью идентичности. Сущности обычно хранятся в постоянном хранилище с ключом, с помощью которого их можно извлечь позже.
Агрегаты, представляющие группы объектов, которые должны сохраняться в виде единицы.
Объекты значений, представляющие понятия, которые можно сравнить на основе суммы значений их свойств. Например, DateRange, состоящая из даты начала и окончания.
События домена, представляющие собой события в системе, которые представляют интерес для других частей системы.
Модель домена DDD должна инкапсулировать сложное поведение в модели. Сущности, в частности, не должны быть только коллекциями свойств. Когда модель предметной области не имеет поведения и просто представляет состояние системы, она, как говорят, является анимической моделью, которая является нежелательной в DDD.
В дополнение к этим типам моделей DDD обычно использует различные шаблоны:
Репозиторий для абстрагирования сведений о сохраняемости.
Фабрика для инкапсуляции процесса создания сложных объектов.
Службы для инкапсулирования сложного поведения и (или) сведений о реализации инфраструктуры.
Команда для отделения процесса выдачи команд и их выполнения.
Спецификация для инкапсулирования сведений о запросе.
DDD также рекомендует использовать ранее описанную ранее чистую архитектуру, что позволяет свободно объединить, инкапсулировать и код, который можно легко проверить с помощью модульных тестов.
Когда следует применить DDD
DDD хорошо подходит для крупных приложений с значительной сложностью бизнеса (а не только технической). Приложению должны потребоваться знания экспертов по домену. В самой модели домена должно быть значительное поведение, представляющее бизнес-правила и взаимодействия, помимо простого хранения и получения текущего состояния различных записей из хранилищ данных.
Когда вам не следует применять DDD
DDD включает инвестиции в моделирование, архитектуру и коммуникацию, которые могут не быть оправданы для небольших приложений или тех, которые по сути лишь выполняют операции CRUD (создание/чтение/обновление/удаление данных). Если вы решили подходить к своему приложению, следуя DDD, но обнаружили, что у вашего домена анемичная модель без поведения, вам может потребоваться переосмыслить подход. Приложение может не нуждаться в DDD, или вам может потребоваться помощь в рефакторинге приложения, чтобы инкапсулировать бизнес-логику в модели домена, а не в базе данных или пользовательском интерфейсе.
Гибридный подход подразумевает использование DDD для транзакционных или более сложных областей приложения, но не для более простых частей, таких как операции CRUD или участки, предназначенные только для чтения. Например, вам не нужны ограничения агрегата, если вы запрашиваете данные для отображения отчета или визуализации данных для панели мониторинга. Совершенно приемлемо иметь отдельную, простую модель чтения для таких требований.
Ссылки — Domain-Driven Design
- DDD на простом английском языке (ответ StackOverflow)
https://stackoverflow.com/questions/1222392/can-someone-explain-domain-driven-design-ddd-in-plain-english-please/1222488#1222488
Развертывание
В процессе развертывания приложения ASP.NET Core выполняется несколько шагов, независимо от того, где он будет размещен. Первым шагом является публикация приложения, которое можно сделать с помощью dotnet publish
команды CLI. Этот шаг компилирует приложение и помещнет все файлы, необходимые для запуска приложения в назначенную папку. При развертывании из Visual Studio этот шаг выполняется автоматически. Папка публикации содержит .exe и .dll файлы для приложения и его зависимостей. Автономное приложение также будет включать версию среды выполнения .NET. ASP.NET основные приложения также будут включать файлы конфигурации, статические клиентские ресурсы и представления MVC.
ASP.NET Основные приложения — это консольные приложения, которые должны быть запущены при загрузке сервера и перезапуске, если приложение (или сервер) завершает работу. Диспетчер процессов можно использовать для автоматизации этого процесса. Наиболее распространенными диспетчерами процессов для ASP.NET Core являются Nginx и Apache в Linux и IIS или службе Windows в Windows.
Помимо диспетчера процессов, ASP.NET приложения Core могут использовать обратный прокси-сервер. Обратный прокси-сервер получает HTTP-запросы из Интернета и пересылает их в Kestrel после предварительной обработки. Обратные прокси-серверы обеспечивают уровень безопасности для приложения. Kestrel также не поддерживает размещение нескольких приложений на одном порту, поэтому методы, такие как заголовки узлов, нельзя использовать с ним для включения размещения нескольких приложений на одном порту и IP-адресе.
Рис. 7-5. ASP.NET размещено на сервере Kestrel за обратным прокси-сервером
Другой сценарий, в котором можно использовать обратный прокси-сервер, заключается в защите нескольких приложений с помощью SSL/HTTPS. В этом случае только обратный прокси-сервер должен быть настроен с использованием SSL. Обмен данными между обратным прокси-сервером и Kestrel может происходить по протоколу HTTP, как показано на рис. 7-6.
Рис. 7-6. ASP.NET размещается за обратным прокси-сервером с защитой HTTPS
Все более популярным подходом является размещение приложения ASP.NET Core в контейнере Docker, который затем можно разместить локально или развернуть в Azure для облачного размещения. Контейнер Docker может содержать код приложения, работающий в Kestrel, и будет развернут за обратным прокси-сервером, как показано выше.
Если вы размещаете приложение в Azure, вы можете использовать Шлюз приложений Microsoft Azure в качестве выделенного виртуального устройства для предоставления нескольких служб. Помимо использования обратного прокси-сервера для отдельных приложений, шлюз приложений также может предложить следующие функции:
Балансировка нагрузки HTTP
Разгрузка SSL (SSL только для интернета)
Сквозное шифрование SSL
Маршрутизация с несколькими сайтами (консолидация до 20 сайтов в одном шлюзе приложений)
Файрвол для веб-приложений
Поддержка Websocket
Расширенная диагностика
Дополнительные сведения о вариантах развертывания Azure см. в главе 10.
Ссылки — развертывание
- Общие сведения о размещении и развертывании
https://learn.microsoft.com/aspnet/core/publishing/- Когда использовать Kestrel с обратным прокси-сервером
https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel#when-to-use-kestrel-with-a-reverse-proxy- Размещение приложений ASP.NET Core в Docker
https://learn.microsoft.com/aspnet/core/publishing/docker- Знакомство с шлюзом приложений Azure
https://learn.microsoft.com/azure/application-gateway/application-gateway-introduction