【问题标题】:How to use session outside a controller如何在控制器外使用会话
【发布时间】:2016-08-24 20:56:03
【问题描述】:

我有一个名为 NotificationHub 的 Signalr Hub,它负责向连接的客户端发送新通知。 NotificationHub 类使用 NotificationManager 类来检索通知数据。现在,我希望能够使用会话来存储上次访问新通知的时间,但是在 NotificationManager中使用 HttpContext.Current.Session["lastRun"] 时> 我得到一个 NullReferenceException。为了更清楚,这里是两个类的一些代码:

通知中心

[HubName("notification")]
    public class NotificationHub : Hub
    {
        private NotificationManager _manager;
        private ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public NotificationManager Manager
        {
            get { return _manager; }
            set { _manager = value; }
        }

        public NotificationHub()
        {
            _manager = NotificationManager.GetInstance(PushLatestNotifications);
        }

        public void PushLatestNotifications(ActivityStream stream)
        {
            logger.Info($"Adding {stream.TotalItems} notifications ");
            Clients.Caller.addLatestNotifications(stream);
        }
//.....
}

NotificationManager

 public class NotificationManager
        {
            private static NotificationManager _manager;
            private DateTime _lastRun;
            private DbUpdateNotifier _updateNotifier;
            private readonly INotificationService _notificationService;
            private readonly Action<ActivityStream> _dispatcher;
            private long _userId;
            private IUnitOfWork unitOfWork;
            public NotificationService NotificationService => (NotificationService)_notificationService;
            public DbUpdateNotifier UpdateNotifier
            {
                get { return _updateNotifier; }
                set { _updateNotifier = value; }
            }

            public static NotificationManager GetInstance(Action<ActivityStream> dispatcher)
            {
                return _manager ?? new NotificationManager(dispatcher);
            }



            private NotificationManager(Action<ActivityStream> dispatcher)
            {
                _userId = HttpContext.Current.User.Identity.CurrentUserId();
                _updateNotifier = new DbUpdateNotifier(_userId);
                _updateNotifier.NewNotification += NewNotificationHandler;
                unitOfWork = new UnitOfWork();
                _notificationService = new NotificationService(_userId, unitOfWork);
                _dispatcher = dispatcher;

            }



         private void NewNotificationHandler(object sender, SqlNotificationEventArgs evt)
                {

                  //Want to store lastRun variable in a session here
                    var notificationList = _notificationService.GetLatestNotifications();
                    _dispatcher(BuilActivityStream(notificationList));
                }
           //....
}

我希望能够将 lastRun 的值存储到一个会话中,以便下次有新通知到达时可以检索该会话。我怎样才能做到这一点?

编辑:

为了澄清事情,我想在会话中存储的是服务器最后一次向客户端推送新通知的时间。我可以使用此值仅获取在 lastRun 的当前值之后发生的通知,然后将 lastRun 更新为 DateTime.Now。例如:假设用户有三个新(未读)通知,然后有两个新通知到达。在这种情况下,服务器必须知道最后一次新通知推送到客户端的时间,以便它只会发送这两个新通知。

【问题讨论】:

  • private static DateTime lastRun
  • 如果我错了,请纠正我,但将 lastRun 更改为静态字段将使其可供单独的用户使用。但在这里,我想为每个用户存储一个会话。
  • "我希望能够存储上次访问新通知的时间" "以便下次有新通知到达时可以检索" - 这意味着您想知道“新通知”何时在全球范围内发生,而不是每个用户。你能在问题文本中澄清一下吗?
  • SignalR 被设计为“始终连接”(以简化的方式) - 所以当您收到“新通知”时,您会立即将它们发送给客户端 - 无需“最后发送/接收”。你什么时候会触发这个? (你什么时候比较当前时间和存储时间?在什么情况下等等?)听起来你在考虑控制器/动作而不是信号器。
  • 只需使用 SignalR。正如您所描述的,SignalR 开箱即用地做您想做的事。 ofc 我可能会遗漏一些东西。如果您不确定,请从头开始使用 SignalR。

标签: c# asp.net asp.net-mvc session


【解决方案1】:

正如@Ryios 提到的,您可以访问HttpContext.Current.Session。不过,主要问题是,当您不在 HTTP 上下文中时,HttpContext.Current 为空;例如,当您运行单元测试时。您正在寻找依赖注入的内容。

HttpContext.Current.SessionSystem.Web.SessionState.HttpSessionState 的一个实例,因此您可以更新您的NotificationManager 构造函数以接受一个HttpSessionState 实例,并且调用它的控制器会将HttpContext.Current.Session 作为参数传递。

使用您的示例,对 NotificationManager.GetInstance 的调用将更改为

    public NotificationHub()
    {
        _manager = NotificationManager.GetInstance(PushLatestNotifications, HttpContext.Current.Session);
    }

【讨论】:

【解决方案2】:

如果您对另一个数据源没问题,我建议您通过 DI 将其抽象为 @Babak 所暗示的那样。

这是我为这个问题所做的 - 这应该可以解决问题。

我偏爱 Autofac,但任何 IoC 组件都可以使用。

  1. 定义两个接口(NotificationUpdateService 和 NotificationUpdateDataProvider)。

NotificationUpdateService 是您将与之交互的对象。 NotificationUpdateDataProvider 抽象出后备存储 - 您可以将其更改为任何内容。在这个例子中,我使用了缓存对象。

public interface INotificationUpdateDataProvider
{
    string UserId { get;  }
    DateTime LastUpdate { get; set; }
}

public interface INotificationUpdateService
{
    DateTime GetLastUpdate();

    void SetLastUpdate(DateTime timesptamp);
}
  1. 实现接口。数据提供者是一个使用 HttpContext 的简单类。从那里我们得到 userId - 使这个实现特定于用户。

对于缓存项 - 我定义了一个 Dictionary 对象 - 以 UserId 作为键,以 DateTime 作为值。

public class NotificationUpdateDataProvider : INotificationUpdateDataProvider
{
    private readonly Dictionary<string, DateTime> _lastUpdateCollection;
    private readonly string _userId;
    private Cache _cache;

    public NotificationUpdateDataProvider()
    {
        _cache = HttpRuntime.Cache;
        //Stack Overflow - get the User from the HubCallerContext object
        //http://stackoverflow.com/questions/12130590/signalr-getting-username
        _userId = Context.User.Identity.GetUserId();
        _lastUpdateCollection =(Dictionary<string,DateTime>) _cache["LastUpdateCollection"];

        //If null - create it and stuff it in cache
        if (_lastUpdateCollection == null)
        {
            _lastUpdateCollection = new Dictionary<string, DateTime>();
            _cache["LastUpdateCollection"] = _lastUpdateCollection;
        }
    }

    public DateTime LastUpdate
    {
        get { return _lastUpdateCollection[_userId]; }

        set
        {
            //add to existing or insert new
            if (_lastUpdateCollection.ContainsKey(_userId))
            {
                _lastUpdateCollection[_userId] = value;
            }
            else
            {
                _lastUpdateCollection.Add(_userId, value);
            }    

        }
    }

    public string UserId => _userId;
}



public class NotificationUpdateService : INotificationUpdateService
{
    private readonly INotificationUpdateDataProvider _provider;

    public NotificationUpdateService(INotificationUpdateDataProvider provider)
    {
        _provider = provider;
    }

    public DateTime GetLastUpdate()
    {
        return _provider.LastUpdate;
    }

    public void SetLastUpdate(DateTime timestamp)
    {
        _provider.LastUpdate = timestamp;
    }
}
  1. 我为 Autofac 注册添加了另一个静态类:

    public static void RegisterComponents()
    {
        var builder = new ContainerBuilder();
    
        //First register the NotificationDataProvider
        builder.RegisterType<NotificationUpdateDataProvider>()
            .As<INotificationUpdateDataProvider>();
    
        //Register the update service
        builder.RegisterType<NotificationUpdateService>()
            .As<INotificationUpdateService>();
    
        var container = builder.Build();
    
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
    
    }
    
  2. 更新 Global.asax

        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        //I am partial Autofac - but unity, Ninject, etc - the concept is the same
        AutofacConfig.RegisterComponents();
    
  3. 如果您希望 Autofac 解析服务,则需要将构造函数修改为 public。

    public class NotificationManager
    {
            private static NotificationManager _manager;
            private DateTime _lastRun;
            private DbUpdateNotifier _updateNotifier;
            private readonly INotificationService _notificationService;
            private readonly Action<ActivityStream> _dispatcher;
            private long _userId;
            private IUnitOfWork unitOfWork;
            public NotificationService NotificationService => (NotificationService)_notificationService;
            private readonly INotificationUpdateService _updateService;                                         
            public DbUpdateNotifier UpdateNotifier
            {
                get { return _updateNotifier; }
                set { _updateNotifier = value; }
            }
    
            public static NotificationManager GetInstance(Action<ActivityStream> dispatcher)
            {
                return _manager ?? new NotificationManager(dispatcher);
            }
    
    
            //You'll need to make the constructor accessible for autofac to resolve your dependency
            public NotificationManager(Action<ActivityStream> dispatcher,  INotificationUpdateService updateService)
            {
                _userId = HttpContext.Current.User.Identity.CurrentUserId();
                _updateNotifier = new DbUpdateNotifier(_userId);
                _updateNotifier.NewNotification += NewNotificationHandler;
                unitOfWork = new UnitOfWork();
                _notificationService = new NotificationService(_userId, unitOfWork);
                _dispatcher = dispatcher;
                _updateService = updateService;
            }
    
    
    
            private void NewNotificationHandler(object sender, SqlNotificationEventArgs evt)
            {
    
                //Want to store lastRun variable in a session here
    
                //just put the datetime in through the service
                _updateService.SetLastUpdate(DateTime.Now);
    
                var notificationList = _notificationService.GetLatestNotifications();
                _dispatcher(BuilActivityStream(notificationList));
            }
    
    
    }
    
  4. 如果你不想修改你的构造函数——那么就这样做吧:

            //This is not the preferred way  - but it does the job
            public NotificationManager(Action<ActivityStream> dispatcher)
            {
                _userId = HttpContext.Current.User.Identity.CurrentUserId();
                _updateNotifier = new DbUpdateNotifier(_userId);
                _updateNotifier.NewNotification += NewNotificationHandler;
                unitOfWork = new UnitOfWork();
                _notificationService = new NotificationService(_userId, unitOfWork);
                _dispatcher = dispatcher;
                _updateService = DependencyResolver.Current.GetService<INotificationUpdateService>(); //versus having autofac resolve in the constructor
            }
    
  5. 最后 - 使用它:

       private void NewNotificationHandler(object sender, SqlNotificationEventArgs evt)
            {
    
                //Want to store lastRun variable in a session here
    
                //just put the datetime in through the service
                _updateService.SetLastUpdate(DateTime.Now);
    
                var notificationList = _notificationService.GetLatestNotifications();
                _dispatcher(BuilActivityStream(notificationList));
            }
    

这不使用会话 - 但它确实解决了您正在尝试做的事情。它还为您提供了一个灵活性元素,您可以更改您的支持数据提供者。

【讨论】:

  • 你的方法很好。但是,HttpContext.Current 有时为 null 并引发 NullReferenceException。
  • 那我有点糊涂了。你怎么有 _userId = HttpContext.Current.User.Identity.CurrentUserId();在职的?想多了一点。 HttpContext 可能为 null 的唯一方法是分离线程。如果来自客户端的请求(在这种情况下是从连接的客户端到集线器的轮询) - 您会在请求中获得上下文。你没有得到会话,但你得到了一个上下文。从那里你可以得到身份和缓存。
  • 对此进行一些研究。您真正需要的只是用户的身份。找到this,了解如何使用信号 R 获得它。有了这个 - 我将修改我的解决方案以使其工作。还发现了为什么 HttpContext.Current 为空(有时)。 This 大概解释了。
  • 我已经更新了解决方案。我将 HttpContext.Current - 更改为 Context。由于您使用的是 SignalR - 我假设您的启动中有 app.MapSignalR() 某处。这将使您可以访问 HubCallerContext(顺便说一下会产生上下文)。
【解决方案3】:

this answer:

您不应将 Session 与 SignalR 一起使用(请参阅 SignalR doesn't use Session on server)。您可以通过连接 ID 识别逻辑连接,您可以map to user names

根本的问题是access to SessionState is serialized in ASP.NET为了保证状态的一致性,所以对hub的每个请求都会阻塞其他的请求。过去,通过设置EnableSessionstate to read-only,可以防止我描述的锁定问题,进行有限的只读访问(我假设(但由于要点不存在无法确认),但support for this was dropped。另请参阅various other places,SignalR 团队在其中发表了类似的声明。最后:官方documentation有一个关于HTTPContext.Current.Session的声明。

我只是将其标记为问题的完全重复,但由于您有赏金,因此无法关闭此问题。

【讨论】:

    【解决方案4】:

    你可以按照下面的解决方案,它对我很有效 -

    Notification HUB 代码后面 -

    public class NotificationsHub : Hub
    {
        public void NotifyAllClients(string s_Not, DateTime d_LastRun)
        {
           IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>();
           context.Clients.All.displayNotification(s_Not, d_LastRun);
        }
    }
    

    您可以使用以下方式将变量提供给 Notification HUB(例如,您可以根据需要进行更改)-

    NotificationsHub nHub = new NotificationsHub();
    nHub.NotifyAllClients("Test Notification", Now.Date);
    

    现在,如果您想将上次运行时间保存在会话变量中,您可以使用 Javascript -

    <script type="text/javascript">            
            $(function () {
                var notify = $.connection.notificationsHub;
    
                notify.client.displayNotification = function (s_Not, d_LastRun) {
                    "<%=System.Web.HttpContext.Current.Session("lastRun")="' + d_LastRun + '"%>";                    
                };
    
                $.connection.hub.start();
    
            });
    
        </script> 
    

    我希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2016-08-25
      • 1970-01-01
      • 1970-01-01
      • 2017-10-04
      • 1970-01-01
      • 1970-01-01
      • 2018-09-11
      • 1970-01-01
      • 2019-01-27
      相关资源
      最近更新 更多