【问题标题】:ASP.NET Core MVC Loading Session AsynchronouslyASP.NET Core MVC 异步加载会话
【发布时间】:2017-04-04 08:14:21
【问题描述】:

我一直在阅读Session and application state 官方文档,偶然发现了以下段落:

异步加载会话

ASP.NET Core 中的默认会话提供程序加载会话记录 仅当从底层 IDistributedCache ISession.LoadAsync 方法在 TryGetValue 之前显式调用, SetRemove 方法。如果没有首先调用 LoadAsync,则 底层会话记录是同步加载的,这可以 可能会影响应用的扩展能力。

要让应用程序强制执行此模式,请将 DistributedSessionStoreDistributedSession 实现 如果未调用 LoadAsync 方法则引发异常的版本 在 TryGetValueSetRemove 之前。 在 服务容器

包装本身对我来说不是问题,但为了实现它,我需要:

  1. 参考原始实现
  2. 注册打包版本

目前,我创建了以下包装类:

public class WrappedDistributedSession : ISession
  {
    private DistributedSession _service;
    private bool loaded = false;

    public WrappedDistributedSession(DistributedSession service)
    {
      _service = service;
    }

    public bool IsAvailable => _service.IsAvailable;

    public string Id => _service.Id;

    public IEnumerable<string> Keys => _service.Keys;

    public void Clear() => _service.Clear();

    public Task CommitAsync() => _service.CommitAsync();

    public Task LoadAsync()
    {
      loaded = true;
      return _service.LoadAsync();
    }

    public void Remove(string key)
    {
      if(loaded)
      {
        _service.Remove(key);
      } else
      {
        throw new Exception();
      }
    }

    public void Set(string key, byte[] value)
    {
      if (loaded)
      {
        _service.Set(key, value);
      }
      else
      {
        throw new Exception();
      }
    }

    public bool TryGetValue(string key, out byte[] value)
    {
      if (loaded)
      {
        return _service.TryGetValue(key, out value);
      }
      else
      {
        throw new Exception();
      }
    }
  }

我已经在Startup.ConfigureServices注册了

services.AddScoped<ISession, WrappedDistributedSession>();

显然,由于我正在写这个问题,所以我的解决方案不起作用。我哪里出错了,如何“在服务容器中注册包装的版本”?

【问题讨论】:

  • 您还有AddSession 电话吗?更重要的是:您是否在 AddSession 之前或之后注册了您的实现?这很重要,因为大多数AddXxx 类使用TryAddcoped|Transient|Singleton 而不是AddScoped|Transient|Singleton,所以必须先注册。
  • 不要实现ISession,而是定义您自己的特定于应用程序的抽象。这种抽象可以很小(最好是一个成员)并且可以根据您的应用程序的需求量身定制。如果您这样做,您将大大简化您的实现,您甚至不必“如果在 TryGetValue 之前未调用 LoadAsync 方法则引发异常”,因为您将防止这种情况发生在您自己的会话实现中。如果这样做,您将有效地遵循依赖倒置原则和接口隔离原则。
  • @Steven:所以我应该编写自己的层来为我调用LoadAsync,而不是使用ISession?但是,如果我通过HttpContext.Session 访问会话,我可以直接引用ISession 呢?我不是这个项目的唯一开发人员,我真的更喜欢防弹解决方案。
  • @alesc:我不是在这里谈论层。我只是在谈论定义您自己的接口并将其放在您已经拥有的“适配器”实现上。此适配器可以放置在您的合成根中或附近,此时您可以访问特定于 ASP.NET 的所有内容,例如HttpContext。所以你的适配器可以以适当的方式调用HttpContext.Session
  • @Steven 给出答案很好,这通常会导致解决方案。您没有提供几乎足够的信息来显示如何执行所要求的操作,以注册包装的版本。您似乎在要求我们去学习如何以您认为正确的方式编码。我就像提问者一样。它不起作用,我不知道为什么。诚然,微软应该在他们的文档中提供示例,但可惜他们没有。所以我们在这里。如果您知道如何,请发布解决方案。

标签: c# asp.net dependency-injection asp.net-core asp.net-core-mvc


【解决方案1】:

您似乎也需要实现ISessonStore(实际上在您引用的文档中提到了),因为它是唯一在AddSession 扩展方法中注册的。

public static IServiceCollection AddSession(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    services.AddTransient<ISessionStore, DistributedSessionStore>();
    services.AddDataProtection();
    return services;
}

ISessionStore(因此DistributedSessionStore)有一个Create(参见source)方法,它返回ISession。在这里,您需要返回您的自定义实现。

https://github.com/aspnet/Session/blob/rel/1.1.0/src/Microsoft.AspNetCore.Session/SessionServiceCollectionExtensions.cs#L27-L29

然后你可以在AddSession之前加上

services.AddTransient<ISessionStore, AsyncDistributedSessionStore>();

【讨论】:

  • 目前我还没有实现ISessonStore,因为我没有实现。所以基本上我必须重写这两个类,以便使用包装的版本。在这种情况下我还需要AddSession,因为我已经手动注册了所有内容吗?
  • ISessionStore 只是一个具有单个Create 方法的工厂。请参阅此处github.com/aspnet/Session/blob/rel/1.1.0/src/… 的默认分布式会话实现。 ISession 不是通过 DI 解决的,而是通过这个工厂方法解决的
  • 这些都不适用于 .NET Core 2.0。我在任何地方都找不到详细推荐模式的示例,听听微软!
【解决方案2】:

使用风险自负。这似乎在会话之后在 Configure 方法中起作用。 此解决方案是对此单元测试的改编: https://github.com/dotnet/aspnetcore/blob/cd0eab88eaa230fa276c27ab5dc71ea267efe14f/src/Middleware/Session/test/SessionTests.cs#L654-L656

 app.UseSession();
 app.Use(async (context, next) =>
 {
    await context.Session.LoadAsync();
    await next();
 });

或者作为更合格的包装扩展:

public static class SesssionAsyncExtensions
{
    /// <summary>
    /// Have sessions be asyncronous. This adaptation is needed to force the session provider to use async calls instead of syncronous ones for session. 
    /// Someone surprisingly for something that seems common, Microsoft didn't make this aspect super nice.
    /// </summary>
    /// <param name="app">App builder instance.</param>
    /// <returns>App builder instance for chaining.</returns>
    /// <remarks>
    /// From Microsoft Documentation (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-5.0):
    /// The default session provider in ASP.NET Core will only load the session record from the underlying IDistributedCache store asynchronously if the
    /// ISession.LoadAsync method is explicitly called before calling the TryGetValue, Set or Remove methods. 
    /// Failure to call LoadAsync first will result in the underlying session record being loaded synchronously,
    /// which could potentially impact the ability of an application to scale.
    /// 
    /// See also:
    /// https://github.com/dotnet/aspnetcore/blob/d2a0cbc093e1e7bb3e38b55cd6043e4e2a0a2e9a/src/Middleware/Session/src/DistributedSession.cs#L268
    /// https://github.com/dotnet/AspNetCore.Docs/issues/1840#issuecomment-454182594
    /// https://bartwullems.blogspot.com/2019/12/aspnet-core-load-session-state.html
    /// </remarks>
    public static IApplicationBuilder UseAsyncSession(this IApplicationBuilder app)
    {
        app.UseSession();
        app.Use(async (context, next) =>
        {
            await context.Session.LoadAsync();
            await next();
        });
        return app;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-06
    • 2022-01-18
    • 2019-12-03
    • 1970-01-01
    相关资源
    最近更新 更多