Update: SignalR 2.0.3 Migration; Noticeboards

Migrate all SignalR 1.x Persistent Connections to SignalR 2.x Hubs.
Abstracts ScheduledTaskStatus with core interface and adds a Mock for
optional status reporting. Noticeboards rewritten (with new theme) to be
more resilient and accurate.
This commit is contained in:
Gary Sharp
2014-06-01 23:27:07 +10:00
parent f6fae26bc7
commit 4cd57f4a90
116 changed files with 9874 additions and 6462 deletions
@@ -12,7 +12,8 @@ namespace Disco.BI.DocumentTemplateBI.Importer
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
public override bool LogExceptionsOnly { get { return true; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
// Trigger Daily @ 12:30am
@@ -16,6 +16,7 @@ namespace Disco.BI.Expressions
public override string TaskName { get { return "Expression Cache - Preload Task"; } }
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
public override bool LogExceptionsOnly { get { return true; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{
+6 -13
View File
@@ -29,16 +29,13 @@ namespace Disco.BI.Interop.Community
return string.Format("{0}.{1}.{2:0000}.{3:0000}", v.Major, v.Minor, v.Build, v.Revision);
}
public static UpdateResponse Check(DiscoDataContext Database, bool UseProxy, ScheduledTaskStatus status = null)
public static UpdateResponse Check(DiscoDataContext Database, bool UseProxy, IScheduledTaskStatus status)
{
if (status != null)
status.UpdateStatus(10, "Building Update Request");
status.UpdateStatus(10, "Building Update Request");
var request = BuildRequest(Database);
//var requestJson = JsonConvert.SerializeObject(request);
if (status != null)
status.UpdateStatus(40, "Sending Request");
status.UpdateStatus(40, "Sending Request");
var DiscoBIVersion = CurrentDiscoVersionFormatted();
@@ -61,21 +58,18 @@ namespace Disco.BI.Interop.Community
XmlSerializer xml = new XmlSerializer(typeof(UpdateRequestV1));
xml.Serialize(wrStream, request);
}
if (status != null)
status.UpdateStatus(50, "Waiting for Response");
status.UpdateStatus(50, "Waiting for Response");
using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
{
if (webResponse.StatusCode == HttpStatusCode.OK)
{
if (status != null)
status.UpdateStatus(90, "Reading Response");
status.UpdateStatus(90, "Reading Response");
UpdateResponse result;
using (var wResStream = webResponse.GetResponseStream())
{
XmlSerializer xml = new XmlSerializer(typeof(UpdateResponse));
result = (UpdateResponse)xml.Deserialize(wResStream);
}
//var result = JsonConvert.DeserializeObject<UpdateResponse>(responseContent);
Database.DiscoConfiguration.UpdateLastCheck = result;
Database.SaveChanges();
@@ -85,8 +79,7 @@ namespace Disco.BI.Interop.Community
}
else
{
if (status != null)
status.SetTaskException(new WebException(string.Format("Server responded with: [{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription)));
status.SetTaskException(new WebException(string.Format("Server responded with: [{0}] {1}", webResponse.StatusCode, webResponse.StatusDescription)));
return null;
}
}
@@ -1,35 +0,0 @@
using Disco.Services.Users;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.BI.Interop.SignalRHandlers
{
public class AuthorizedPersistentConnection : PersistentConnection
{
private string authorizedClaim = null;
protected virtual string AuthorizedClaim { get { return authorizedClaim; } }
protected override bool AuthorizeRequest(IRequest request)
{
if (!request.User.Identity.IsAuthenticated)
return false;
else
{
var authToken = UserService.CurrentAuthorization;
if (authToken == null)
return false; // No Current User
if (authorizedClaim == null)
return true; // Just Authenticate - no Authorization
else
return authToken.Has(authorizedClaim);
}
}
}
}
@@ -1,72 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.AspNet.SignalR;
using System.Reactive.Linq;
using Disco.Data.Repository.Monitor;
using Disco.Models.Repository;
namespace Disco.BI.Interop.SignalRHandlers
{
public class HeldDeviceNotifications : PersistentConnection
{
private static bool subscribed = false;
private static object subscribeLock = new object();
private static IPersistentConnectionContext notificationContext;
static HeldDeviceNotifications()
{
if (!subscribed)
lock (subscribeLock)
if (!subscribed)
{
notificationContext = GlobalHost.ConnectionManager.GetConnectionContext<HeldDeviceNotifications>();
Disco.Data.Repository.Monitor.RepositoryMonitor.StreamAfterCommit.Where(e => e.EntityType == typeof(Job)).Subscribe(JobUpdated);
Disco.Data.Repository.Monitor.RepositoryMonitor.StreamAfterCommit.Where(e =>
e.EntityType == typeof(Device) &&
(e.ModifiedProperties.Contains("Location") ||
e.ModifiedProperties.Contains("DeviceModelId") ||
e.ModifiedProperties.Contains("DeviceProfileId") ||
e.ModifiedProperties.Contains("DeviceBatchId") ||
e.ModifiedProperties.Contains("ComputerName") ||
e.ModifiedProperties.Contains("AssignedUserId"))
).Subscribe(DeviceUpdated);
Disco.Data.Repository.Monitor.RepositoryMonitor.StreamAfterCommit.Where(e =>
e.EntityType == typeof(User) &&
e.ModifiedProperties.Contains("DisplayName")
).Subscribe(UserUpdated);
subscribed = true;
}
}
private static void JobUpdated(RepositoryMonitorEvent e)
{
Job j = (Job)e.Entity;
if (j.DeviceSerialNumber != null)
notificationContext.Connection.Broadcast(j.DeviceSerialNumber);
}
private static void DeviceUpdated(RepositoryMonitorEvent e)
{
Device d = (Device)e.Entity;
notificationContext.Connection.Broadcast(d.SerialNumber);
}
private static void UserUpdated(RepositoryMonitorEvent e)
{
User u = (User)e.Entity;
var userDevices = e.Database.Devices.Where(d => d.AssignedUserId == u.UserId);
foreach (var userDevice in userDevices)
{
notificationContext.Connection.Broadcast(userDevice.SerialNumber);
}
}
}
}
@@ -1,79 +0,0 @@
using Disco.Services.Authorization;
using Disco.Services.Logging;
using Disco.Services.Logging.Models;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.BI.Interop.SignalRHandlers
{
public class LogNotifications : AuthorizedPersistentConnection
{
public static bool initialized = false;
protected override string AuthorizedClaim { get { return Claims.DiscoAdminAccount; } }
public LogNotifications()
{
if (!initialized)
{
initialized = true;
Disco.Services.Logging.Targets.LogLiveContext.LogBroadcast += Broadcast;
}
}
protected override Task OnConnected(IRequest request, string connectionId)
{
string addToGroups = request.QueryString["addToGroups"];
if (!string.IsNullOrWhiteSpace(addToGroups))
{
var groups = addToGroups.Split(',');
foreach (var g in groups)
{
this.Groups.Add(connectionId, g);
}
}
return base.OnConnected(request, connectionId);
}
protected override System.Threading.Tasks.Task OnReceived(IRequest request, string connectionId, string data)
{
// Add to Group
if (!string.IsNullOrWhiteSpace(data) && data.StartsWith("/addToGroups:") && data.Length > 13)
{
var groups = data.Substring(13).Split(',');
foreach (var g in groups)
{
this.Groups.Add(connectionId, g);
}
}
return base.OnReceived(request, connectionId, data);
}
internal static void Broadcast(LogBase logModule, LogEventType eventType, DateTime Timestamp, params object[] Arguments)
{
var message = LogLiveEvent.Create(logModule, eventType, Timestamp, Arguments);
var connectionManager = GlobalHost.ConnectionManager;
var connectionContext = connectionManager.GetConnectionContext<LogNotifications>();
connectionContext.Groups.Send(_GroupNameAll, message);
connectionContext.Groups.Send(logModule.ModuleName, message);
}
private const string _GroupNameAll = "__All";
public static string AllNotifications
{
get
{
return _GroupNameAll;
}
}
}
}
@@ -1,57 +0,0 @@
using Disco.Data.Repository.Monitor;
using Disco.Services.Authorization;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.BI.Interop.SignalRHandlers
{
public class RepositoryMonitorNotifications : AuthorizedPersistentConnection
{
protected override string AuthorizedClaim { get { return Claims.DiscoAdminAccount; } }
public static void Initialize()
{
RepositoryMonitor.StreamAfterCommit.Subscribe(AfterCommit);
}
protected override Task OnConnected(IRequest request, string connectionId)
{
string addToGroups = request.QueryString["addToGroups"];
if (!string.IsNullOrWhiteSpace(addToGroups))
{
var groups = addToGroups.Split(',');
foreach (var g in groups)
{
this.Groups.Add(connectionId, g);
}
}
return base.OnConnected(request, connectionId);
}
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
// Add to Group
if (!string.IsNullOrWhiteSpace(data) && data.StartsWith("/addToGroups:") && data.Length > 13)
{
var groups = data.Substring(13).Split(',');
foreach (var g in groups)
{
this.Groups.Add(connectionId, g);
}
}
return base.OnReceived(request, connectionId, data);
}
private static void AfterCommit(RepositoryMonitorEvent e)
{
GlobalHost.ConnectionManager.GetConnectionContext<RepositoryMonitorNotifications>().Groups.Send(e.EntityType.Name, e);
}
}
}
@@ -1,75 +0,0 @@
using Disco.Services.Authorization;
using Disco.Services.Tasks;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Disco.BI.Interop.SignalRHandlers
{
public class ScheduledTasksStatusNotifications : AuthorizedPersistentConnection
{
public static bool initialized = false;
protected override string AuthorizedClaim { get { return Claims.DiscoAdminAccount; } }
public ScheduledTasksStatusNotifications()
{
if (!initialized)
{
initialized = true;
Disco.Services.Tasks.ScheduledTaskStatus.UpdatedBroadcast += Broadcast;
}
}
protected override Task OnConnected(IRequest request, string connectionId)
{
string addToGroups = request.QueryString["addToGroups"];
if (!string.IsNullOrWhiteSpace(addToGroups))
{
var groups = addToGroups.Split(',');
foreach (var g in groups)
{
this.Groups.Add(connectionId, g);
}
}
return base.OnConnected(request, connectionId);
}
protected override System.Threading.Tasks.Task OnReceived(IRequest request, string connectionId, string data)
{
// Add to Group
if (!string.IsNullOrWhiteSpace(data) && data.StartsWith("/addToGroups:") && data.Length > 13)
{
var groups = data.Substring(13).Split(',');
foreach (var g in groups)
{
this.Groups.Add(connectionId, g);
}
}
return base.OnReceived(request, connectionId, data);
}
internal static void Broadcast(ScheduledTaskStatusLive SessionStatus)
{
var connectionManager = GlobalHost.ConnectionManager;
var connectionContext = connectionManager.GetConnectionContext<ScheduledTasksStatusNotifications>();
connectionContext.Groups.Send(_GroupNameAll, SessionStatus);
connectionContext.Groups.Send(SessionStatus.SessionId, SessionStatus);
}
private const string _GroupNameAll = "__All";
public static string AllNotifications
{
get
{
return _GroupNameAll;
}
}
}
}
@@ -1,58 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Routing;
using System.Web.Security;
using System.Web.SessionState;
using Microsoft.AspNet.SignalR;
using Owin;
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
namespace Disco.BI.Interop.SignalRHandlers
{
/// <summary>
/// Required for SignalR 1.1.0 NTLM support in Firefox & Safari
/// Returns 401 (Unauthorized) instead of 403 (Forbidden) when an unauthenticated request is processed
///
/// TODO: Remove this workaround when implementing SignalR 2.x
///
/// Thanks to David Fowler (@davidfowl)
/// </summary>
public static class SignalRAuthenticationWorkaround
{
public static void AddMiddleware(IAppBuilder app)
{
Func<AppFunc, AppFunc> convert403To401 = Convert403To401;
app.Use(convert403To401);
}
private static AppFunc Convert403To401(AppFunc next)
{
return env =>
{
// Execute the SignalR pipeline
Task task = next(env);
// Get the status code
int statusCode = 0;
if (env.ContainsKey("owin.ResponseStatusCode"))
{
statusCode = (int)env["owin.ResponseStatusCode"];
}
// If its 403 then convert it to 401 (we shouldn't do
// this if it's a cross domain request since it doesn't make sense)
if (statusCode == 403)
{
env["owin.ResponseStatusCode"] = 401;
}
// Return the original task
return task;
};
}
}
}
@@ -1,82 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.AspNet.SignalR;
using System.Reactive.Linq;
using Disco.Data.Repository.Monitor;
using Disco.Models.Repository;
namespace Disco.BI.Interop.SignalRHandlers
{
public class UserHeldDeviceNotifications : PersistentConnection
{
private static bool subscribed = false;
private static object subscribeLock = new object();
private static IPersistentConnectionContext notificationContext;
static UserHeldDeviceNotifications()
{
if (!subscribed)
lock (subscribeLock)
if (!subscribed)
{
notificationContext = GlobalHost.ConnectionManager.GetConnectionContext<UserHeldDeviceNotifications>();
Disco.Data.Repository.Monitor.RepositoryMonitor.StreamAfterCommit.Where(e => e.EntityType == typeof(Job)).Subscribe(JobUpdated);
Disco.Data.Repository.Monitor.RepositoryMonitor.StreamBeforeCommit.Where(e =>
e.EntityType == typeof(Device) &&
(e.ModifiedProperties.Contains("DeviceModelId") ||
e.ModifiedProperties.Contains("DeviceProfileId") ||
e.ModifiedProperties.Contains("DeviceBatchId") ||
e.ModifiedProperties.Contains("AssignedUserId"))
).Subscribe(DeviceUpdated);
Disco.Data.Repository.Monitor.RepositoryMonitor.StreamAfterCommit.Where(e =>
e.EntityType == typeof(User) &&
e.ModifiedProperties.Contains("DisplayName")
).Subscribe(UserUpdated);
subscribed = true;
}
}
private static void JobUpdated(RepositoryMonitorEvent e)
{
Job j = (Job)e.Entity;
if (j.DeviceSerialNumber != null)
{
var jobDevice = e.Database.Devices.Where(d => d.SerialNumber == j.DeviceSerialNumber).FirstOrDefault();
if (jobDevice.AssignedUserId != null)
notificationContext.Connection.Broadcast(jobDevice.AssignedUserId);
}
}
private static void DeviceUpdated(RepositoryMonitorEvent e)
{
Device d = (Device)e.Entity;
string previouslyAssignedUserId = null;
if (e.ModifiedProperties.Contains("AssignedUserId"))
previouslyAssignedUserId = e.GetPreviousPropertyValue<string>("AssignedUserId");
e.ExecuteAfterCommit(me =>
{
if (previouslyAssignedUserId != null)
notificationContext.Connection.Broadcast(previouslyAssignedUserId);
if (d.AssignedUserId != null)
notificationContext.Connection.Broadcast(d.AssignedUserId);
});
}
private static void UserUpdated(RepositoryMonitorEvent e)
{
User u = (User)e.Entity;
notificationContext.Connection.Broadcast(u.UserId);
}
}
}
@@ -24,6 +24,7 @@ namespace Disco.BI.JobBI.Statistics
public override string TaskName { get { return "Job Statistics - Daily Opened/Closed Task"; } }
public override bool SingleInstanceTask { get { return true; } }
public override bool CancelInitiallySupported { get { return false; } }
public override bool LogExceptionsOnly { get { return true; } }
public override void InitalizeScheduledTask(DiscoDataContext Database)
{