Sisk

依赖注入

本页由英文自动翻译。 阅读原文

通常,会为请求的生命周期专门分配成员和实例,例如数据库连接、已验证的用户或会话令牌。实现这一点的一种可能方式是通过 HttpContext.RequestBag ,它创建一个在整个请求生命周期中都存在的字典。

该字典可以被 请求处理程序 访问,并在整个请求中定义变量。例如,一个验证用户的请求处理程序将用户设置在 HttpContext.RequestBag 中,在请求逻辑中,可以通过 HttpContext.RequestBag.Get<User>() 来检索该用户。

定义在此字典中的对象的作用域限定于请求生命周期。它们在请求结束时被释放。发送响应并不一定标志着请求生命周期的结束。当 请求处理程序 在发送响应后运行时,RequestBag 对象仍然存在并且尚未被释放。

以下是一个示例:

RequestHandlers/AuthenticateUser.csC#
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
    }
}
Controllers/HelloController.csC#
[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。

可以通过诸如 GetOrAdd 或 GetOrAddAsync 之类的方法定义获取实例的逻辑,当实例尚未在 RequestBag 中定义时。

从 1.3 版本开始,引入了静态属性 HttpContext.Current ,允许访问当前执行的请求上下文的 HttpContext。这使得可以在当前请求之外暴露 HttpContext 的成员,并在路由对象中定义实例。

以下示例定义了一个控制器,该控制器具有常被请求上下文访问的成员。

Controllers/Controller.csC#
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
}

并定义继承自控制器的类型:

Controllers/PostsController.csC#
[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;
    }
}

对于上面的示例,您需要在路由器中配置一个 值处理程序 ,以便路由器返回的对象转换为有效的 HttpResponse。

请注意,方法不带有 HttpRequest request 参数,如其他方法中所示。这是因为,从 1.3 版本开始,路由器支持两种类型的委托用于路由响应:RouteAction ,这是默认的委托,它接收一个 HttpRequest 参数,以及 ParameterlessRouteAction 。HttpRequest 对象仍然可以通过静态 HttpContext 上的 Request 属性访问。

在上面的示例中,我们定义了一个可释放对象 DbContext ,并且我们需要确保在 HTTP 会话结束时释放在 DbContext 中创建的所有实例。为此,我们可以使用两种方法来实现这一点。一种方法是创建一个在路由器操作之后执行的 请求处理程序 ,另一种方法是通过自定义 服务器处理程序。

对于第一种方法,我们可以直接在继承自 RouterModule 的 OnSetup 方法中内联创建请求处理程序:

Controllers/PostsController.csC#
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));
    }
}

提示

从 Sisk 1.4 版本开始,引入了属性 HttpServerConfiguration.DisposeDisposableContextValues ,它默认启用,用于定义 HTTP 服务器是否应在 HTTP 会话关闭时释放上下文包中的所有 IDisposable 值。

上述方法将确保在 HTTP 会话结束时释放 DbContext。您可以为需要在响应结束时释放的其他成员执行此操作。

对于第二种方法,您可以创建一个自定义的 服务器处理程序 ,它将在 HTTP 会话结束时释放 DbContext。

Server/Handlers/ObjectDisposerHandler.csC#
public class ObjectDisposerHandler : HttpServerHandler
{
    protected override void OnHttpRequestClose(HttpServerExecutionResult result)
    {
        result.Context.RequestBag.GetOrDefault<DbContext>()?.Dispose();
    }
}

并在应用程序生成器中使用它:

Program.csC#
using var host = HttpServer.CreateBuilder()
    .UseHandler<ObjectDisposerHandler>()
    .Build();

这是处理代码清理和将请求依赖项按模块类型分离的方法,减少了路由器每个操作中重复的代码量。这与在类似 ASP.NET 的框架中使用依赖注入类似。

输入关键词以搜索文档和 API 参考。