Внедрение зависимостей
Эта страница переведена с английского автоматически. Читать оригинал
Обычно посвящают члены и экземпляры, которые существуют в течение времени жизни запроса, такие как соединение с базой данных, аутентифицированный пользователь или токен сеанса. Одним из возможностей является использование HttpContext.RequestBag, который создает словарь, существующий в течение всего времени жизни запроса.
Этот словарь может быть доступен обработчиками запросов и определять переменные на протяжении всего запроса. Например, обработчик запроса, который аутентифицирует пользователя, устанавливает этого пользователя в HttpContext.RequestBag, и в логике запроса этот пользователь может быть получен с HttpContext.RequestBag.Get<User>().
Объекты, определенные в этом словаре, имеют область видимости запроса. Они удаляются в конце запроса. Не обязательно отправка ответа определяет конец времени жизни запроса. Когда обработчики запросов, которые выполняются после отправки ответа, выполняются, объекты RequestBag仍 существуют и не были удалены.
Вот пример:
public class AuthenticateUser : IRequestHandler
{
public RequestHandlerExecutionMode ExecutionMode { get; init; } = RequestHandlerExecutionMode.BeforeResponse;
public HttpResponse? Execute(HttpRequest request, HttpContext context)
{
User authenticatedUser = AuthenticateUser(request);
context.RequestBag.Set(authenticatedUser);
return null; // advance to the next request handler or request logic
}
}
``
```csharp {title="Controllers/HelloController.cs"}
[RouteGet("/hello")]
[RequestHandler<AuthenticateUser>]
public HttpResponse SayHello(HttpRequest request)
{
var authenticatedUser = request.Bag.Get<User>();
return new HttpResponse()
{
Content = new StringContent($"Hello {authenticatedUser.Name}!")
};
}
``
Это предварительный пример этой операции. Экземпляр `User` был создан в обработчике запроса, посвященном аутентификации, и все маршруты, которые используют этот обработчик запроса, будут иметь гарантию, что в их экземпляре `HttpContext.RequestBag` будет `User`.
Возможно определить логику для получения экземпляров, когда они не были предварительно определены в `RequestBag`, через методы, такие как [GetOrAdd](/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAdd) или [GetOrAddAsync](/api/Sisk.Core.Entity.TypedValueDictionary.GetOrAddAsync).
С версии 1.3 был введен статический свойство [HttpContext.Current](/api/Sisk.Core.Http.HttpContext.Current), которое позволяет получить доступ к текущему контексту запроса. Это позволяет экспонировать члены `HttpContext` вне текущего запроса и определять экземпляры в объектах маршрутов.
В примере ниже определяется контроллер, который имеет члены, часто доступные контекстом запроса.
```csharp {title="Controllers/Controller.cs"}
public abstract class Controller : RouterModule
{
// Получить существующий или создать новый экземпляр базы данных для этого запроса
protected DbContext Database => HttpContext.Current.RequestBag.GetOrAdd(() => new DbContext());
// Ленивая загрузка репозиториев также распространена
protected IUserRepository Users => HttpContext.Current.RequestBag.GetOrAdd(() => new UserRepository(Database));
protected IBlogRepository Blogs => HttpContext.Current.RequestBag.GetOrAdd(() => new BlogRepository(Database));
protected IBlogPostRepository BlogPosts => HttpContext.Current.RequestBag.GetOrAdd(() => new BlogPostRepository(Database));
// следующая строка выдаст исключение, если свойство доступно, когда пользователь не
// определен в пакете запроса
protected User AuthenticatedUser => => HttpContext.Current.RequestBag.Get<User>();
// Экспонирование экземпляра HttpRequest также поддерживается
protected HttpRequest Request => HttpContext.Current.Request
}
``
И определяют типы, которые наследуются от контроллера:
```csharp {title="Controllers/PostsController.cs"}
[RoutePrefix("/api/posts/{author}")]
sealed class PostsController : Controller
{
protected Guid AuthorId => Request.RouteParameters["author"].GetInteger();
[RouteGet]
public IAsyncEnumerable<BlogPost> ListPosts()
{
return BlogPosts.GetPostsAsync(authorId: AuthorId);
}
[RouteGet("<id>")]
public async Task<BlogPost?> GetPost()
{
int postId = Request.RouteParameters["id"].GetInteger();
Post? post = await BlogPosts
.FindPostAsync(post => post.Id == postId && post.AuthorId == AuthorId);
return post;
}
}
``
Для примера выше вам необходимо настроить [обработчик значения](/docs/fundamentals/responses.html#implicit-response-types) в вашем маршрутизаторе, чтобы объекты, возвращаемые маршрутизатором, были преобразованы в допустимый [HttpResponse](/api/Sisk.Core.Http.HttpResponse).
Обратите внимание, что методы не имеют аргумента `HttpRequest request`, как это присутствует в других методах. Это потому, что, начиная с версии 1.3, маршрутизатор поддерживает два типа делегатов для маршрутизации ответов: [RouteAction](/api/Sisk.Core.Routing.RouteAction), который является делегатом по умолчанию, получающим аргумент `HttpRequest`, и [ParameterlessRouteAction](/api/Sisk.Core.Routing.ParameterlessRouteAction). Объект `HttpRequest` все равно может быть доступен обоими делегатами через свойство [Request](/api/Sisk.Core.Http.HttpContext.Request) статического `HttpContext` на потоке.
В примере выше мы определили удаляемый объект, `DbContext`, и нам необходимо обеспечить, чтобы все экземпляры, созданные в `DbContext`, были удалены, когда HTTP-сессия заканчивается. Для этого мы можем использовать два способа достижения этого. Один из них - создать [обработчик запроса](/docs/fundamentals/request-handlers), который выполняется после действия маршрутизатора, а другой способ - через пользовательский [обработчик сервера](/docs/advanced/http-server-handlers).
Для первого метода мы можем создать обработчик запроса trực в методе [OnSetup](/api/Sisk.Core.Routing.RouterModule.OnSetup), унаследованном от `RouterModule`:
```csharp {title="Controllers/PostsController.cs"}
public abstract class Controller : RouterModule
{
...
protected override void OnSetup(Router parentRouter)
{
base.OnSetup(parentRouter);
HasRequestHandler(RequestHandler.Create(
execute: (req, ctx) =>
{
// получить один экземпляр DbContext, определенный в контексте обработчика запроса, и
// удалить его
ctx.RequestBag.GetOrDefault<DbContext>()?.Dispose();
return null;
},
executionMode: RequestHandlerExecutionMode.AfterResponse));
}
}
``
> [!TIP]
>
> Начиная с версии Sisk 1.4, свойство [HttpServerConfiguration.DisposeDisposableContextValues](/api/Sisk.Core.Http.HttpServerConfiguration.DisposeDisposableContextValues) было введено и включено по умолчанию, которое определяет, должен ли HTTP-сервер удалять все `IDisposable`-значения в контекстном пакете, когда HTTP-сессия закрывается.
Метод выше обеспечит удаление `DbContext`, когда HTTP-сессия будет завершена. Вы можете сделать это для других членов, которые необходимо удалить в конце ответа.
Для второго метода вы можете создать пользовательский [обработчик сервера](/docs/advanced/http-server-handlers), который будет удалять `DbContext`, когда HTTP-сессия будет завершена.
```csharp {title="Server/Handlers/ObjectDisposerHandler.cs"}
public class ObjectDisposerHandler : HttpServerHandler
{
protected override void OnHttpRequestClose(HttpServerExecutionResult result)
{
result.Context.RequestBag.GetOrDefault<DbContext>()?.Dispose();
}
}
``
И использовать его в вашем построителе приложения:
```csharp {title="Program.cs"}
using var host = HttpServer.CreateBuilder()
.UseHandler<ObjectDisposerHandler>()
.Build();
``
Это один из способов обработки очистки кода и сохранения зависимостей запроса, разделенных по типу модуля, который будет использоваться, уменьшая количество дублирующего кода внутри каждого действия маршрутизатора. Это похоже на то, для чего используется внедрение зависимостей в фреймворках, таких как ASP.NET.