使用AsyncLocal存储请求信息?

时间:2017-11-03 07:52:43

标签: c# asp.net asp.net-core

我们从ASP.NET Core 2开始。我们需要一种方法,用于将消息写入消息处理程序的请求中涉及的每个元素。

一些限制:

  • 我们不会使用HttpContext.ItemsHttpContext在我们在Controller中使用的课程中不可用,我们不想在那里转发整个上下文)
  • 我们试图在没有依赖注入的情况下使用它,因为如果我们有多个不同的服务,我们在构造函数中将有太多的参数。
  • 还必须使用async / await

我们尝试了使用AsyncLocal<T>的方法。

为此我们创建了一个类:

public class NotificationExecutionContext
{
    private static readonly AsyncLocal<NotificationHandler> NotificationHandler =
        new AsyncLocal<NotificationHandler>();

    public static NotificationHandler Instance =>
        NotificationHandler.Value ?? (NotificationHandler.Value = new NotificationHandler());
}

将创建一个NotificationHandler,每个请求都应该存在。 NotificationHandler是一个简单的类,您可以在其中添加/从集合中获取消息:

public class NotificationHandler : INotificationHandler
{
    public List<NotificationBase> Notifications { get; } = new List<NotificationBase>();

    public void AddNotification(NotificationBase notification)
    {
        Notifications.Add(notification);
    }

    public void AddNotificationRange(List<NotificationBase> notifications)
    {
        Notifications.AddRange(notifications);
    }
}

使用此解决方案,我可以轻松获取此上下文的NotificationHandler并添加通知。

NotificationExecutionContext.Instance.AddNotification(new NotificationBase(){..})

在中间件中,我们正在等待Response.OnStarting()事件,然后我们从NotificationHandler获取所有消息并添加响应头:

public async Task Invoke(HttpContext context)
{
    var e = NotificationExecutionContext.Instance; // Required so that notification handler will be created in this context

    context.Response.OnStarting((state) =>
    {
        List<NotificationBase> notifications = NotificationExecutionContext.Instance.Notifications;
        if (notifications.Count > 0)
        {
            string messageString = JsonConvert.SerializeObject(notifications, Formatting.None);
            context.Response.Headers.Add("NotificationHeader", messageString);
        }

        return Task.FromResult(0);
    }, null);

    await Next(context);
}

此代码有效,但是我们不知道有哪些陷阱?或者有更好的解决方案吗?

1 个答案:

答案 0 :(得分:2)

你应该使用这样的静态单例。在代码中具有类似静态依赖性会破坏依赖注入的整个目的。你应该在这里接受依赖注入,这会使这个变得非常简单:

/* in Startup.ConfigureServices */
// register the notification handler as a scoped dependency, this automatically makes the
// instance shared per request but not outside of it
services.AddScoped<INotificationHandler, NotificationHandler>();

/* in Startup.Configure */
// register your custom middleware
app.Use<NotificationHandlerMiddleware>();
public class NotificationHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly NotificationHandler _notificationHandler;

    public NotificationHandlerMiddleware(RequestDelegate next, INotificationHandler notificationHandler)
    {
        _next = next;
        _notificationHandler = notificationHandler;
    }

    public void Invoke(HttpContext context)
    {
        // do whatever with _notificationHandler

        await _next(context);
    }
}

就是这样。无需引入静态,但使用完全依赖注入使您的代码完全可测试并且所有依赖项都清晰。

  

我们试图在没有依赖注入的情况下使用它,因为如果我们有多个不同的服务,我们将在构造函数中拥有许多参数。

构造函数参数太多是违反single responsibility principle的明显标志。如果您发现您的服务需要很多依赖项,那么您应该考虑将其拆分。您可能还想考虑refactoring to facade services