如何在控制器外部使用会话

时间:2016-04-29 15:52:15

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

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

NotificationHub

[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 。例如:假设用户有三个新的(未读)通知,然后两个新通知到达。在这种情况下,服务器必须知道上次新通知推送到客户端的时间,以便它只发送这两个新通知。

4 个答案:

答案 0 :(得分:3)

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

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

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

  1. 定义两个接口(NotificationUpdateService和NotificationUpdateDataProvider)。
  2. NotificationUpdateService是您将与之交互的内容。 NotificationUpdateDataProvider抽象出后备存储 - 您可以将其更改为任何内容。在这个例子中,我使用了缓存对象。

    public interface INotificationUpdateDataProvider
    {
        string UserId { get;  }
        DateTime LastUpdate { get; set; }
    }
    
    public interface INotificationUpdateService
    {
        DateTime GetLastUpdate();
    
        void SetLastUpdate(DateTime timesptamp);
    }
    
    1. 实现接口。数据提供程序是一个使用HttpContext的简单类。从那里我们得到userId - 使这个实现特定于用户。
    2. 对于缓存项 - 我定义了一个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));
                }
        
      6. 这不会使用会话 - 但它确实解决了你想要做的事情。它还为您提供了灵活性,使您可以更改后备数据提供程序。

答案 1 :(得分:0)

正如@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 :(得分:0)

this answer

  

你不应该使用Session与SignalR(见SignalR doesn't use Session on server)。您可以通过其连接ID识别逻辑连接,您可以map to user names

     

底层问题是access to SessionState is serialized in ASP.NET确保状态一致性,因此每个集线器请求都会阻止其他请求。在过去,有限的只读访问权限(我假设(但由于要点已经消失)但可以通过设置EnableSessionstate to read-only来确认,这可以防止我所描述的锁定问题),但是{{3} }。另请参阅support for this was dropped,其中SignalR团队也发表了类似的声明。最后:官方various other places关于HTTPContext.Current.Session的声明。

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

答案 3 :(得分:0)

您可以按照以下解决方案,它对我很有用 -

通知HUB代码背后 -

2016-06-25 18:11:58 7540 [Note] Plugin 'FEDERATED' is disabled.
2016-06-25 18:11:58 7540 [Note] InnoDB: Using atomics to ref count buffer pool pages
2016-06-25 18:11:58 7540 [Note] InnoDB: The InnoDB memory heap is disabled
2016-06-25 18:11:58 7540 [Note] InnoDB: Mutexes and rw_locks use Windows interlocked functions
2016-06-25 18:11:58 7540 [Note] InnoDB: Compressed tables use zlib 1.2.3
2016-06-25 18:11:58 7540 [Note] InnoDB: Not using CPU crc32 instructions
2016-06-25 18:11:58 7540 [Note] InnoDB: Initializing buffer pool, size = 128.0M
2016-06-25 18:11:58 7540 [Note] InnoDB: Completed initialization of buffer pool
2016-06-25 18:11:58 7540 [Note] InnoDB: Highest supported file format is Barracuda.
2016-06-25 18:11:58 7540 [Note] InnoDB: 128 rollback segment(s) are active.
2016-06-25 18:11:58 7540 [Note] InnoDB: Waiting for purge to start
2016-06-25 18:11:58 7540 [Note] InnoDB: 5.6.17 started; log sequence number 1625977
2016-06-25 18:11:58 7540 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: fb218c3f-3b3a-11e6-9517-305a3a7dfc8e.
2016-06-25 18:11:58 7540 [Note] Server hostname (bind-address): '*'; port: 3306
2016-06-25 18:11:58 7540 [Note] IPv6 is available.
2016-06-25 18:11:58 7540 [Note]   - '::' resolves to '::';
2016-06-25 18:11:58 7540 [Note] Server socket created on IP: '::'.
2016-06-25 18:11:58 7540 [Note] Event Scheduler: Loaded 0 events
2016-06-25 18:11:58 7540 [Note] wampmysqld: ready for connections.
Version: '5.6.17'  socket: ''  port: 3306  MySQL Community Server (GPL)
2016-06-25 19:20:52 104c InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:20:52 104c InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:20:52 104c InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:22:07 15d4 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:22:07 15d4 InnoDB: Error: Fetch of persistent statistics requested for table "local"."wp_links" but the required system tables mysql.innodb_table_stats and mysql.innodb_index_stats are not present or have unexpected structure. Using transient stats instead.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:27:52 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:36:01 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:36:01 1288 InnoDB: Error: Table "mysql"."innodb_table_stats" not found.
2016-06-25 19:36:01 1288 InnoDB: Error: Fetch of persistent statistics requested for table "local"."wp_termmeta" but the required system tables mysql.innodb_table_stats and mysql.innodb_index_stats are not present or have unexpected structure. Using transient stats instead.
2016-06-25 19:37:54 7540 [Note] wampmysqld: Arrêt normal du serveur

2016-06-25 19:37:54 7540 [Note] Giving 0 client threads a chance to die gracefully
2016-06-25 19:37:54 7540 [Note] Event Scheduler: Purging the queue. 0 events
2016-06-25 19:37:54 7540 [Note] Shutting down slave threads
2016-06-25 19:37:54 7540 [Note] Forcefully disconnecting 0 remaining clients
2016-06-25 19:37:54 7540 [Note] Binlog end
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'partition'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'PERFORMANCE_SCHEMA'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_DATAFILES'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_TABLESPACES'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_FOREIGN_COLS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_FOREIGN'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_FIELDS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_COLUMNS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_INDEXES'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_TABLESTATS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_SYS_TABLES'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_FT_INDEX_TABLE'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_FT_INDEX_CACHE'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_FT_CONFIG'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_FT_BEING_DELETED'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_FT_DELETED'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_FT_DEFAULT_STOPWORD'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_METRICS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_BUFFER_POOL_STATS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_BUFFER_PAGE_LRU'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_BUFFER_PAGE'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_CMP_PER_INDEX_RESET'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_CMP_PER_INDEX'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_CMPMEM_RESET'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_CMPMEM'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_CMP_RESET'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_CMP'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_LOCK_WAITS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_LOCKS'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'INNODB_TRX'
2016-06-25 19:37:54 7540 [Note] Shutting down plugin 'InnoDB'
2016-06-25 19:37:54 7540 [Note] InnoDB: FTS optimize thread exiting.
2016-06-25 19:37:54 7540 [Note] InnoDB: Starting shutdown...
2016-06-25 19:37:55 7540 [Note] InnoDB: Shutdown completed; log sequence number 2890038
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'BLACKHOLE'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'ARCHIVE'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'MRG_MYISAM'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'MyISAM'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'MEMORY'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'CSV'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'sha256_password'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'mysql_old_password'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'mysql_native_password'
2016-06-25 19:37:55 7540 [Note] Shutting down plugin 'binlog'
2016-06-25 19:37:55 7540 [Note] wampmysqld: Arrêt du serveur terminé

2016-06-25 19:37:58 6148 [Note] Plugin 'FEDERATED' is disabled.
wampmysqld: La table 'mysql.plugin' n'existe pas
2016-06-25 19:37:58 6148 [ERROR] Can't open the mysql.plugin table. Please run mysql_upgrade to create it.
2016-06-25 19:37:58 6148 [Note] InnoDB: Using atomics to ref count buffer pool pages
2016-06-25 19:37:58 6148 [Note] InnoDB: The InnoDB memory heap is disabled
2016-06-25 19:37:58 6148 [Note] InnoDB: Mutexes and rw_locks use Windows interlocked functions
2016-06-25 19:37:58 6148 [Note] InnoDB: Compressed tables use zlib 1.2.3
2016-06-25 19:37:58 6148 [Note] InnoDB: Not using CPU crc32 instructions
2016-06-25 19:37:58 6148 [Note] InnoDB: Initializing buffer pool, size = 128.0M
2016-06-25 19:37:58 6148 [Note] InnoDB: Completed initialization of buffer pool
2016-06-25 19:37:58 6148 [Note] InnoDB: Highest supported file format is Barracuda.
2016-06-25 19:37:59 6148 [Note] InnoDB: 128 rollback segment(s) are active.
2016-06-25 19:37:59 6148 [Note] InnoDB: Waiting for purge to start
2016-06-25 19:37:59 6148 [Note] InnoDB: 5.6.17 started; log sequence number 2890038
2016-06-25 19:37:59 6148 [Note] Server hostname (bind-address): '*'; port: 3306
2016-06-25 19:37:59 6148 [Note] IPv6 is available.
2016-06-25 19:37:59 6148 [Note]   - '::' resolves to '::';
2016-06-25 19:37:59 6148 [Note] Server socket created on IP: '::'.
2016-06-25 19:37:59 6148 [ERROR] Fatal error: Can't open and lock privilege tables: La table 'mysql.user' n'existe pas

您可以使用以下方式将变量提供给通知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);
    }
}

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

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

我希望这会有所帮助。