我有一个名为 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 。例如:假设用户有三个新的(未读)通知,然后两个新通知到达。在这种情况下,服务器必须知道上次新通知推送到客户端的时间,以便它只发送这两个新通知。
答案 0 :(得分:3)
如果您对另一个数据源没有问题,我建议通过DI将其抽象为@Babak暗示。
这就是我为这个问题所做的 - 这应该可以解决问题。
我偏爱Autofac,但任何IoC组件都可以使用。
NotificationUpdateService是您将与之交互的内容。 NotificationUpdateDataProvider抽象出后备存储 - 您可以将其更改为任何内容。在这个例子中,我使用了缓存对象。
public interface INotificationUpdateDataProvider
{
string UserId { get; }
DateTime LastUpdate { get; set; }
}
public interface INotificationUpdateService
{
DateTime GetLastUpdate();
void SetLastUpdate(DateTime timesptamp);
}
对于缓存项 - 我定义了一个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;
}
}
我在另一个静态类中添加了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));
}
更新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();
如果您希望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));
}
}
如果您不想修改构造函数 - 那么只需执行以下操作:
//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
}
最后 - 使用它:
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));
}
这不会使用会话 - 但它确实解决了你想要做的事情。它还为您提供了灵活性,使您可以更改后备数据提供程序。
答案 1 :(得分:0)
正如@Ryios所提到的,您可以访问HttpContext.Current.Session
。但是,主要问题是当您不在HTTP上下文中时HttpContext.Current
为空;例如,当您运行单元测试时。你在寻找依赖注入。
HttpContext.Current.Session
是System.Web.SessionState.HttpSessionState
的一个实例,因此您可以更新NotificationManager
构造函数以接受HttpSessionState的实例,并且调用它的控制器将HttpContext.Current.Session
作为参数传递
使用您的示例,对NotificationManager.GetInstance
的调用将更改为
public NotificationHub()
{
_manager = NotificationManager.GetInstance(PushLatestNotifications, HttpContext.Current.Session);
}
答案 2 :(得分:0)
你不应该使用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);
我希望这会有所帮助。