I am using SignalR library to show online Users. At present i am able to display total number of online users but when i am diplay the list of online user with their name it makes me confused.
I don't have any idea that how could i manage the particular online status at present i want to manage the particular online user status by database. I have table which i want to manage on Onconnected and OnDisconnected method.
Please give me an idea that how could i display list of online user
Here it is my code.
<script src="~/Content/Scripts/jquery-ui.min.js"></script>
<script src="~/Content/Scripts/jquery.ui.touch-punch.js"></script>
<script src="~/Content/Scripts/jquery.signalR-1.1.3.js"></script>
<script src="/signalr/hubs" type="text/javascript"></script>
<script>
$(document).ready(function () {
$(function () {
// Reference the auto-generated proxy for the hub.
var userActivity = $.connection.userActivityHub;
var Chat = $.connection.Chat;
// Create a function that the hub can call back to display messages.
userActivity.client.updateUsersOnlineCount = function (count) {
// Add the message to the page.
console.log('Count :' + count);
$('#usersCount').text(count);
};
//Chat.server.SetName($.connection.hub.id, $("#displayname").val())
$.connection.hub.start();
});
});
</script>
I have a HubClass named UserActivityHub.
using System.Collections.Generic;
using System;
using System.Web;
using IMWedding.BAL.UserInfos;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace IMWedding.Utils
{
[HubName("userActivityHub")]
public class UserActivityHub : Hub
{
IUserInfosRepository _userRepo;
public UserActivityHub()
{
this._userRepo = new UserInfosRepository();
}
/// <summary>
/// The count of users connected.
/// </summary>
public static List<string> Users = new List<string>();
/// <summary>
/// Sends the update user count to the listening view.
/// </summary>
/// <param name="count">
/// The count.
/// </param>
public void Send(int count)
{
// Call the addNewMessageToPage method to update clients.
var context = GlobalHost.ConnectionManager.GetHubContext<UserActivityHub>();
context.Clients.All.updateUsersOnlineCount(count);
}
/// <summary>
/// The OnConnected event.
/// </summary>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
///
public override System.Threading.Tasks.Task OnConnected()
{
string clientId = GetClientId();
if (Users.IndexOf(clientId) == -1)
{
Users.Add(clientId);
}
//if (!string.IsNullOrEmpty(Convert.ToString(HttpContext.Current.Session["UserInfoID"])))
//{
// var detail = _userRepo.GetUserDetailByUserID(UserId);
// if (detail != null)
// {
// if (!string.IsNullOrEmpty(clientId))
// {
// detail.CreatedBy = UserId;
// bool Result = _userRepo.AddUserDetail(detail);
// }
// }
//}
// Send the current count of users
Send(Users.Count);
return base.OnConnected();
}
public void SetName(string cid, string name)
{
//Users.Find(uo => uo.con_id == cid).client_name = name;
}
/// <summary>
/// The OnReconnected event.
/// </summary>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
//public override System.Threading.Tasks.Task OnReconnected()
//{
// string clientId = GetClientId();
// if (Users.IndexOf(clientId) == -1)
// {
// Users.Add(clientId);
// }
// // Send the current count of users
// Send(Users.Count);
// return base.OnReconnected();
//}
/// <summary>
/// The OnDisconnected event.
/// </summary>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public override System.Threading.Tasks.Task OnDisconnected(bool StopCalled)
{
string clientId = GetClientId();
if (Users.IndexOf(clientId) > -1)
{
Users.Remove(clientId);
}
if (!string.IsNullOrEmpty(clientId))
{
bool Result = _userRepo.RemoveDetail(clientId);
}
// Send the current count of users
Send(Users.Count);
return base.OnDisconnected(StopCalled);
}
/// <summary>
/// Get's the currently connected Id of the client.
/// This is unique for each client and is used to identify
/// a connection.
/// </summary>
/// <returns>The client Id.</returns>
private string GetClientId()
{
string clientId = "";
if (Context.QueryString["clientId"] != null)
{
// clientId passed from application
clientId = this.Context.QueryString["clientId"];
}
if (string.IsNullOrEmpty(clientId.Trim()))
{
clientId = Context.ConnectionId;
}
return clientId;
}
}
}
The application_start method in global.asax File
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
MailSchedulerModel objmodel = new MailSchedulerModel();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
// GlobalFilters.Filters.Add(new )
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Here it is my Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.MapSignalR();
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
Firstly, create SendUserList method inside UserActivityHub hub.
public void SendUserList(List<string> users)
{
var context = GlobalHost.ConnectionManager.GetHubContext<UserActivityHub>();
context.Clients.All.updateUserList(users);
}
Then, inside System.Threading.Tasks.Task OnConnected() method call SendUserList method;
public override System.Threading.Tasks.Task OnConnected()
{
string clientId = GetClientId();
if (Users.IndexOf(clientId) == -1)
{
Users.Add(clientId);
}
//if (!string.IsNullOrEmpty(Convert.ToString(HttpContext.Current.Session["UserInfoID"])))
//{
// var detail = _userRepo.GetUserDetailByUserID(UserId);
// if (detail != null)
// {
// if (!string.IsNullOrEmpty(clientId))
// {
// detail.CreatedBy = UserId;
// bool Result = _userRepo.AddUserDetail(detail);
// }
// }
//}
// Send the current users
SendUserList(Users);
return base.OnConnected();
}
Lastly, inside javascript part insert updateUserList function to handle userList which is pushed from server;
$(document).ready(function () {
$(function () {
// Reference the auto-generated proxy for the hub.
var userActivity = $.connection.userActivityHub;
var Chat = $.connection.Chat;
// Create a function that the hub can call back to display messages.
userActivity.client.updateUsersOnlineCount = function (count) {
// Add the message to the page.
console.log('Count :' + count);
$('#usersCount').text(count);
};
userActivity.client.updateUserList = function (userList) {
//Take action with userList
};
//Chat.server.SetName($.connection.hub.id, $("#displayname").val())
$.connection.hub.start();
});
});
Related
My records are coming back from the server in C# in this format:
(via network tab / preview on the network call)
DateModified: "/Date(1640035318903)/"
What would cause this issue? Why won't it just come back like this?
This is how I'm returning it via my controller via a post:
/// <summary>
/// Gets the application file manager file system items.
/// </summary>
/// <param name="fileManagerJsParam">The file manager js parameter.</param>
/// <returns>
/// ActionResult
/// </returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> GetFileManagerFileSystemItems(FileManagerJsParamModel fileManagerJsParam)
{
if (fileManagerJsParam == null)
{
throw new ArgumentNullException(nameof(fileManagerJsParam));
}
if (!ModelState.IsValid)
{
return Json(new
{
success = false,
errorMessage = string.Join(
",",
ModelState.Values.Where(e => e.Errors.Count > 0)
.SelectMany(e => e.Errors)
.Select(e => e.ErrorMessage)
.ToArray())
});
}
var csModel = mapper.Map<FileManagerCsParamModel>(fileManagerJsParam);
var result = await fileManagerUtilities.GetApplicationFileManagerFileSystemItems(csModel, 107);
return Json(result.FileSystemItems);
}
Here is my javascript that calls it:
function getItemsFileManager(e) {
var deferred = $.Deferred()
let fullUrl = baseEndPoint + loadAction;
// check to see if we have different base end point / load action urls
if (e && e.dataItem) {
var fileManagerAPIItems = getFileManagerAPIItems(e.dataItem);
if (fileManagerAPIItems) {
fullUrl = fileManagerAPIItems.BaseEndPoint + fileManagerAPIItems.LoadAction;
}
}
let fileManagerJsParam = getFileManagerParams(e, true);
if (!fileManagerJsParam) {
console.log("fileManagerParms not set in getItemsFileManager # " + new Date());
genericErrorMessage();
return;
}
$.post(BuildSafeURL(fullUrl, null), fileManagerJsParam)
.done(function (data) {
deferred.resolve(data);
})
.fail(function (error) {
deferred.reject(getItemGenericErrorMessage(e));
if (error && error.responseJSON && error.responseJSON.errorMessage) {
console.log("error⚠️", error.responseJSON.errorMessage);
}
else {
console.log("error⚠️", error);
}
});
return deferred.promise();
}
I found the result from this post: https://stackoverflow.com/a/9302054 (Approach 2).
I had to update all my controller http post calls to return Task<JsonResult> / return new CustomJsonResult { Data = result.FileSystemItems };
Here is the code:
namespace WebPortal.MVC.Utilities
{
using System;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
/// <summary>
/// CustomJsonResult
/// </summary>
/// <seealso cref="JsonResult" />
public class CustomJsonResult : JsonResult
{
private const string DateFormat = "yyyy-MM-ddTHH:mm:ssZ";
/// <summary>
/// Executes the result.
/// </summary>
/// <param name="context">The context.</param>
/// <exception cref="System.ArgumentNullException">context</exception>
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
// Using Json.NET serializer
var isoConvert = new IsoDateTimeConverter();
isoConvert.DateTimeFormat = DateFormat;
response.Write(JsonConvert.SerializeObject(Data, isoConvert));
}
}
}
}
/// <summary>
/// Gets the application file manager file system items.
/// </summary>
/// <param name="fileManagerJsParam">The file manager js parameter.</param>
/// <returns>
/// ActionResult
/// </returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<JsonResult> GetFileManagerFileSystemItems(FileManagerJsParamModel fileManagerJsParam)
{
if (fileManagerJsParam == null)
{
throw new ArgumentNullException(nameof(fileManagerJsParam));
}
if (!ModelState.IsValid)
{
return Json(new
{
success = false,
errorMessage = string.Join(
",",
ModelState.Values.Where(e => e.Errors.Count > 0)
.SelectMany(e => e.Errors)
.Select(e => e.ErrorMessage)
.ToArray())
});
}
var csModel = mapper.Map<FileManagerCsParamModel>(fileManagerJsParam);
var result = await fileManagerUtilities.GetApplicationFileManagerFileSystemItems(csModel, 107);
return new CustomJsonResult { Data = result.FileSystemItems };
}
Results:
I'm thinking I must be missing something obvious, but im trying to create a new DesignAutomationClient object like follows:
private void runDAButton_Click(object sender, EventArgs e)
{
createWorkItem();
}
private async Task createWorkItem()
{
var forgeConfig = new Autodesk.Forge.Core.ForgeConfiguration();
forgeConfig.ClientId = clientID;
forgeConfig.ClientSecret = clientSecret;
var apiInstance = new DesignAutomationClient();
// Code to create work item will go here
}
but when I do, the following error appears in my Visual Studio Debug/Immediate window after trying to execute the var apiInstance = new DesignAutomationClient(); line:
Exception thrown: 'System.TypeLoadException' in mscorlib.dll
Am i missing something obvious? The design automation client was downloaded using NuGet so i should have all the required depencies, but searches of forums for this kind of error all say it means I'm either missing a DLL file, or the type I'm looking for doesn't exist within a DLL, neither of which I believe are true.
This code is in a simple windows form application written in C#
There are no web servers or ASP.NET involved.
The user clicks a button on the form which runs the runDAButton_Click function(which in turn runs the createWorkItem() function). That function should create an instance of the API, and then use it to create my work item.
Can anyone help?
We need more information to troubleshoot, is it a ASP .NET core? how you are handling DI
but if your app is .NET core console app from the code as it appears.
The right way to do is.
dotnet new console
dotnet add package Autodesk.Forge.DesignAutomation --version 3.0.3
Code:
namespace daconsole
{
using Autodesk.Forge.Core;
using Autodesk.Forge.DesignAutomation;
using Autodesk.Forge.DesignAutomation.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Defines the <see cref="ConsoleHost" />.
/// </summary>
class ConsoleHost : IHostedService
{
/// <summary>
/// The StartAsync.
/// </summary>
/// <param name="cancellationToken">The cancellationToken<see cref="CancellationToken"/>.</param>
/// <returns>The <see cref="Task"/>.</returns>
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <summary>
/// The StopAsync.
/// </summary>
/// <param name="cancellationToken">The cancellationToken<see cref="CancellationToken"/>.</param>
/// <returns>The <see cref="Task"/>.</returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
/// <summary>
/// Defines the <see cref="App" />.
/// </summary>
internal class App
{
/// <summary>
/// Defines the api.
/// </summary>
public DesignAutomationClient api;
/// <summary>
/// Defines the config.
/// </summary>
public ForgeConfiguration config;
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
/// <param name="api">The api<see cref="DesignAutomationClient"/>.</param>
/// <param name="config">The config<see cref="IOptions{ForgeConfiguration}"/>.</param>
public App(DesignAutomationClient api, IOptions<ForgeConfiguration> config)
{
this.api = api;
this.config = config.Value;
}
/// <summary>
/// The CreateWorkItem.
/// </summary>
/// <returns>The <see cref="Task"/>.</returns>
private async Task CreateWorkItem()
{
//step1:
var forgeEnginesApi = api.EnginesApi;
ApiResponse<Page<string>> engines = await forgeEnginesApi.GetEnginesAsync();
if (engines.HttpResponse.IsSuccessStatusCode)
{
Console.WriteLine(JsonConvert.SerializeObject(engines.Content, Formatting.Indented));
}
//step2:
Console.WriteLine("\nActiviy Start");
var activitiesApi = api.ActivitiesApi;
ApiResponse<Page<string>> activitiesResp = await activitiesApi.GetActivitiesAsync();
List<string> listOfActivities = new List<string>();
string activityName = null;
if (activitiesResp.HttpResponse.IsSuccessStatusCode)
{
var page = activitiesResp.Content.PaginationToken;
activitiesResp.Content.Data.ForEach(e => listOfActivities.Add(e));
while (page != null)
{
activitiesResp = await activitiesApi.GetActivitiesAsync(page);
page = activitiesResp.Content.PaginationToken;
activitiesResp.Content.Data.ForEach(e => listOfActivities.Add(e));
}
var activities = listOfActivities.Where(a => a.Contains("PlotToPDF")).Select(a => a);
if (activities.Count() > 0)
{
activityName = activities.FirstOrDefault();
}
}
//step3:
Console.WriteLine("\nWorkItem Start...");
var workItemsApi = api.WorkItemsApi;
ApiResponse<WorkItemStatus> workItemStatus = await workItemsApi.CreateWorkItemAsync(new Autodesk.Forge.DesignAutomation.Model.WorkItem()
{
ActivityId = activityName,
Arguments = new Dictionary<string, IArgument>() {
{
"HostDwg",
new XrefTreeArgument() {
Url = "http://download.autodesk.com/us/samplefiles/acad/blocks_and_tables_-_metric.dwg",
Verb = Verb.Get
}
}, {
"Result",
new XrefTreeArgument() {
Verb = Verb.Put, Url = "azure blob storage url",
Headers = new Dictionary<string,string>()
{
{ "Content-Type","application/octet-stream" },
{ "x-ms-blob-type","BlockBlob" }
}
}
}
}
});
Console.Write("\tPolling status");
while (!workItemStatus.Content.Status.IsDone())
{
await Task.Delay(TimeSpan.FromSeconds(2));
workItemStatus = await workItemsApi.GetWorkitemStatusAsync(workItemStatus.Content.Id);
Console.Write(".");
}
Console.WriteLine(JsonConvert.SerializeObject(workItemStatus.Content, Formatting.Indented));
}
/// <summary>
/// The RunAsync.
/// </summary>
/// <returns>The <see cref="Task"/>.</returns>
public async Task RunAsync()
{
await CreateWorkItem();
}
}
/// <summary>
/// Defines the <see cref="Program" />.
/// </summary>
internal class Program
{
/// <summary>
/// The Main.
/// </summary>
/// <param name="args">The args<see cref="string[]"/>.</param>
/// <returns>The <see cref="Task"/>.</returns>
static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration(builder =>
{
builder.AddEnvironmentVariables();
builder.AddForgeAlternativeEnvironmentVariables();
}).ConfigureServices((hostContext, services) =>
{ // add our no-op host (required by the HostBuilder)
services.AddHostedService<ConsoleHost>();
// our own app where all the real stuff happens
services.AddSingleton<App>();
// add and configure DESIGN AUTOMATION
services.AddDesignAutomation(hostContext.Configuration);
services.AddOptions();
})
.UseConsoleLifetime()
.Build();
using (host)
{
await host.StartAsync();
// Get a reference to our App and run it
var app = host.Services.GetRequiredService<App>();
await app.RunAsync();
await host.StopAsync();
}
}
}
}
add Forge Env to your launchSettings.json
{
"profiles": {
"daconsole": {
"commandName": "Project",
"environmentVariables": {
"FORGE_CLIENT_SECRET": "",
"FORGE_CLIENT_ID": ""
}
}
}
}
To run:
dotnet run --launch-profile daconsole
I am using RestSharp to make requests to an API which uses bearer token authentication. Most requests run as expected, but there is a specific endpoint which always redirects the request to a dynamic location. When this redirect occurs, the Authorization header is lost (by design), thus resulting in a Bad Request.
I've done some looking into the issue and found one similar issue here, but the custom AuthenticationModule I made is never having the Authenticate function called.
Am I missing something obvious in the setup that is preventing the Authentication module from being used, or is something else going on?
Thanks!
My Authenticator class:
public class AdpAuthenticator : IAuthenticator
{
/// <summary>
/// The current access token for making requests to the API.
/// </summary>
private static string AccessToken { get; set; }
/// <summary>
/// When the current access token expires.
/// </summary>
private static DateTime TokenExpiresOn { get; set; }
private static CredentialCache CredentialCache { get; set; }
/// <summary>
/// Singleton instance for making requests for access tokens.
/// </summary>
private static IRestClient AuthenticationClient { get; set; }
/// <summary>
/// Singleton instance of the request for obtaining access tokens.
/// </summary>
private static IRestRequest AuthenticationRequest { get; set; }
/// <summary>
/// Construct a new AdpAuthenticator.
/// </summary>
/// <param name="adpClientId"></param>
/// <param name="adpClientSecret"></param>
/// <param name="adpCertPath"></param>
public AdpAuthenticator(string adpClientId, string adpClientSecret, string adpCertPath)
{
if (string.IsNullOrWhiteSpace(adpClientId)) throw new ArgumentNullException("Passed adpClientId was empty or null.");
if (string.IsNullOrWhiteSpace(adpClientSecret)) throw new ArgumentNullException("Passed adpClientSecret was empty or null.");
if (CredentialCache == null)
{
CredentialCache = new CredentialCache
{
{new Uri("https://api.adp.com"), "Basic", new NetworkCredential(adpClientId, adpClientSecret) }
};
}
if (AuthenticationClient == null)
{
X509Certificate2Collection certificateCollection;
X509Certificate2 certificate = new X509Certificate2(adpCertPath);
certificateCollection = new X509Certificate2Collection
{
certificate
};
AuthenticationClient = new RestClient("https://api.adp.com")
{
ClientCertificates = certificateCollection,
Authenticator = new HttpBasicAuthenticator(adpClientId, adpClientSecret)
};
AuthenticationClient.UseSerializer(new JsonNetSerializer());
}
if (AuthenticationRequest == null)
{
AuthenticationRequest = new RestRequest("auth/oauth/v2/token", Method.POST)
{
Credentials = CredentialCache
};
AuthenticationRequest.AddOrUpdateParameter("grant_type", "client_credentials", ParameterType.QueryString);
}
RegisterAuthenticationModule(new Uri("https://api.adp.com/"));
}
/// <summary>
/// Authenticate a request.
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
public void Authenticate(IRestClient client, IRestRequest request)
{
//If accessToken is null or expired, get a new one.
if (!HasValidToken())
{
RefreshAccessToken();
}
//request.AddOrUpdateParameter("Authorization", AccessToken, ParameterType.HttpHeader);
//var newCache = new CredentialCache
//{
// {new Uri("https://api.adp.com/"), "Bearer", new NetworkCredential(AccessToken, "") }
//};
var newCache = new CredentialCache();
newCache.Add(new Uri("https://api.adp.com/"), AdpAuthenticationModule.TheAuthenticationType, new NetworkCredential(AccessToken, ""));
request.Credentials = newCache;
//request.AddOrUpdateParameter("Authorization", "Bearer " + AccessToken, ParameterType.HttpHeader);
}
private void RefreshAccessToken()
{
try
{
var response = AuthenticationClient.Execute<AuthorizationResponse>(AuthenticationRequest);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new FailedAuthenticationException($"Authentication failed to refresh access token, returned with code {response.StatusCode}. Content: \"{response.Content}\".", null);
}
if (string.IsNullOrWhiteSpace(response.Data.access_token))
{
throw new Exception("Error: response returned during access token refresh gave Status 200 OK, but access_token returned was null or whitespace.");
}
AccessToken = response.Data.access_token;
if (response.Data.expires_in <= 0)
{
throw new Exception("Error: response returned during access token refresh gave Status 200 OK, but expires_in value returned was <=0.");
}
TokenExpiresOn = DateTime.Now.AddSeconds(response.Data.expires_in);
}
catch (FailedAuthenticationException)
{
throw;
}
catch (Exception e)
{
throw new FailedAuthenticationException($"Authentication failed to refresh access token, see inner exception details.", e);
}
}
/// <summary>
/// Returns whether the current access token is valid.
/// </summary>
/// <returns>False if token is null or has 10 or less minutes until expiry; else returns true.</returns>
public bool HasValidToken()
{
return !string.IsNullOrEmpty(AccessToken) && DateTime.Now.CompareTo(TokenExpiresOn.AddMinutes(-10.0)) < 0;
}
private static AdpAuthenticationModule RegisterAuthenticationModule(Uri loginServerUrl)
{
var registeredModules = AuthenticationManager.RegisteredModules;
AdpAuthenticationModule authenticationModule;
while (registeredModules.MoveNext())
{
object current = registeredModules.Current;
if (current is AdpAuthenticationModule)
{
authenticationModule = (AdpAuthenticationModule)current;
if (authenticationModule.LoginServerUrl.Equals(loginServerUrl))
{
return authenticationModule;
}
}
}
authenticationModule = new AdpAuthenticationModule(loginServerUrl);
AuthenticationManager.Register(authenticationModule);
return authenticationModule;
}
}
My Custom Authentication Module:
public class AdpAuthenticationModule : IAuthenticationModule
{
/// <summary>
/// The name of the custom authentication type.
/// </summary>
public string AuthenticationType => TheAuthenticationType;
public static string TheAuthenticationType => "AdpAuthentication";
/// <summary>
/// Returns false, as this IAuthenticationModule cannot pre-authenticate.
/// </summary>
public bool CanPreAuthenticate => false;
private readonly CredentialCache credentialCache = new CredentialCache();
private readonly Uri loginServerUrl;
internal CredentialCache CredentialCache
{
get
{
return credentialCache;
}
}
internal Uri LoginServerUrl
{
get
{
return loginServerUrl;
}
}
internal AdpAuthenticationModule(Uri loginServerUrl)
{
this.loginServerUrl = loginServerUrl ?? throw new ArgumentNullException("AdpAuthenticationModule.loginServerUrl");
}
/// <summary>
/// Builds and returns a <see cref="Authorization"/> object for a request.
/// </summary>
/// <param name="challenge"></param>
/// <param name="request"></param>
/// <param name="credentials"></param>
/// <returns></returns>
public Authorization Authenticate(string challenge, WebRequest request, ICredentials credentials)
{
Authorization result = null;
if (request != null && credentials != null)
{
NetworkCredential creds = credentials.GetCredential(LoginServerUrl, AuthenticationType);
if (creds == null)
{
return null;
}
ICredentialPolicy policy = AuthenticationManager.CredentialPolicy;
if (policy != null && !policy.ShouldSendCredential(LoginServerUrl, request, creds, this))
{
return null;
}
string token = Convert.ToBase64String(Encoding.UTF8.GetBytes(creds.UserName));
result = new Authorization(string.Format("Bearer {0}", token));
}
return result;
}
/// <summary>
/// Returns null, since this IAuthenticationModule cannot pre-authenticate.
/// </summary>
/// <param name="request"></param>
/// <param name="credentials"></param>
/// <returns></returns>
public Authorization PreAuthenticate(WebRequest request, ICredentials credentials)
{
return null;
}
}
Implementation of IAuthenticationModule need to be registered in the AuthenticationManager class from System.Net.
Use the following code :
AuthenticationManager.Register(new AdpAuthenticationModule());
Yesterday I coded myself a simple RESTful web API in .NET Core (solution named Vault) with a single method that gets the profile of the user depending only on the Windows user name. I now have a second solution that will call some requests to my self-hosting service previously mentioned. When I use Postman, I can retrieve the data with ease when I call a GET on my only method in Vault, but when I build the URI in Mainframe and execute, I get an Unauthorized error and it confuses me as Vault does not require specific login like username and password. I also put a breakpoint in Vault and unlike when I'm using Postman, it does not reach my code when calling via the Mainframe solution.
Here where is build my REST request and call the service (GetProfile())
public VaultApiClient(ConfigurationManagerWrap configuration)
{
this.configuration = configuration;
this.client = new RestClient(new Uri(this.configuration.GetAppSetting<string>(ConfigurationKeys.VaultApiURL)));
}
/// <summary>
/// The get profile.
/// </summary>
/// <returns>
/// The <see cref="UserProfile"/>.
/// </returns>
public UserProfile GetProfile()
{
var request = new RestRequest("profile") { Method = Method.GET};
//request.AddParameter("profile", ParameterType.UrlSegment);
var response = this.client.Execute(request);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception(
$"Could not get the user profile ({response.StatusCode} {response.StatusDescription})");
}
return RestJsonSerializer.Default.Deserialize<UserProfile>(response);
}
Im hosting local so the base URI, aka ConfigurationKeys.VaultApiURL, is localhost5000/api/
My Mainframe controller :
public HomeController()
: this(new VaultApiClient(new ConfigurationManagerWrap()))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="HomeController"/> class.
/// </summary>
/// <param name="vaultApiClient">
/// The vault api client.
/// </param>
public HomeController(IVaultApiClient vaultApiClient)
{
this.vaultApiClient = vaultApiClient;
}
/// <summary>
/// The index.
/// </summary>
/// <returns>
/// The <see cref="ActionResult"/>.
/// </returns>
public ActionResult Index()
{
var profile = this.GetProfile();
this.ViewBag.IsEdit = false;
this.ViewBag.IsError = false;
this.ViewBag.ErrorMessage = "";
if (this.TempData.ContainsKey("IsEdit"))
{
this.ViewBag.IsEdit = true;
this.TempData.Remove("IsEdit");
if (this.TempData.ContainsKey("ErrorMessage"))
{
this.ViewBag.IsError = true;
this.ViewBag.ErrorMessage = this.TempData["ErrorMessage"];
this.TempData.Remove("ErrorMessage");
}
}
return this.View("Index", profile);
}
private UserProfile GetProfile()
{
return this.vaultApiClient.GetProfile();
}
And here is the vault controller method that handles the GET request in question:
[HttpGet]
[Route("/api/Profile")]
[Produces(typeof(UserProfile))]
public IActionResult SearchProfile()
{
try
{
if (!this.currentuser.IsAuthenticated)
{
throw new Exception("This service does not support anonymous calls.");
}
var profile = Task.Run(() => this.personalizationService.GetUserProfileAsync(this.currentuser.GetCurrentWindowsIdentityName)).Result;
var userProfile = this.persistenceToDataModelConverter.Convert(profile);
userProfile.UserAdLogin = this.currentuser.GetCurrentWindowsIdentityName;
return this.Ok(userProfile);
}
catch (Exception ex)
{
return this.NotFound(ex);
}
}
Lastly, here are a few pics of before and when the error is thrown.
Credential information must be provided with client request in order to authenticate with the server.
I have an issue where I can't seem to send new data to the connected Signal R clients from a ChangedEventHandler. The docs says that I can get the hub context by using:-
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
However nothing gets sent to the clients (checked on fiddler) or any errors reported. My onchange event is wired up at the moment from Application_Start as I am creating a proof of concept. I should point out the hub does work on start up and retrieves the data from the initial GetAll call
protected void Application_Start()
{
...
_sqlTableDependency.OnChanged += _sqlTableDependency_OnChanged;
_sqlTableDependency.Start();
...
}
private void _sqlTableDependency_OnChanged(object sender, RecordChangedEventArgs<BiddingText> e)
{
switch (e.ChangeType)
{
case ChangeType.Insert:
foreach (var insertedCustomer in e.ChangedEntities)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
biddingTextList.Add(insertedCustomer);
}
break;
}
}
When I put a breakpoint on the hub context I get my ChatHub back.
My Javascript code:
$.connection.hub.url = "http://localhost:37185/signalr";
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
chat.client.initialText = function(data) {
var index;
//console.log(data.length);
for (index = 0; index < data.List.length; ++index) {
$('#list').append("<li>" + data.List[index].text + "</li>");
}
};
chat.client.addToList = function(data) {
console.log(data);
$('#list').append("<li>" + data.text + "</li>");
};
// Start the connection.
$.connection.hub.start({ jsonp: true }).done(function () {
chat.server.getAll(1831);
});
My Hub code:
public class ChatHub : Microsoft.AspNet.SignalR.Hub
{
private readonly IMediator mediator;
public ChatHub(IMediator mediator)
{
this.mediator = mediator;
}
public void GetAll(int saleId)
{
var model = mediator.Request(new BiddingTextQuery { SaleId = saleId});
Clients.Caller.initialText(model);
}
}
Not sure if this is relevant but the Clients.Connection.Identity is different everytime I use GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
Can anyone help?
I had some similar issues a while back setting up a Nancy API to publish some events to SignalR clients.
The core issue I had was that I had failed to ensure Nancy and SignalR was using the same DI container at the SignalR global level.
SignalR, as Nancy, has a default DependencyResolver that is used to resolve any dependencies in your hubs. When I failed to implement the same source of dependencies for Nancy and SignalR I basically ended up with two separate applications.
Small disclaimer: You have not posted your config code, so my solution here is based on some assumptions (as well as the following twitter answer from David Fowler when you reached out on twitter:
#rippo you have a custom dependency resolver and global has has another. You need to use one container
(https://twitter.com/davidfowl/status/635000470340153344)
Now some code:
First you need to implement a custom SignalR depependency resolver, and ensure it uses the same source of dependencies as the rest of your application.
This is the implementation I used for an Autofac container:
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
namespace LabCommunicator.Server.Configuration
{
internal class AutofacSignalrDependencyResolver : DefaultDependencyResolver, IRegistrationSource
{
private ILifetimeScope LifetimeScope { get; set; }
public AutofacSignalrDependencyResolver(ILifetimeScope lifetimeScope)
{
LifetimeScope = lifetimeScope;
var currentRegistrationSource = LifetimeScope.ComponentRegistry.Sources.FirstOrDefault(s => s.GetType() == GetType());
if (currentRegistrationSource != null)
{
((AutofacSignalrDependencyResolver)currentRegistrationSource).LifetimeScope = lifetimeScope;
}
else
{
LifetimeScope.ComponentRegistry.AddRegistrationSource(this);
}
}
public override object GetService(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetService(serviceType);
}
if (LifetimeScope.TryResolve(serviceType, out result))
{
return result;
}
return null;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetServices(serviceType);
}
if (LifetimeScope.TryResolve(typeof(IEnumerable<>).MakeGenericType(serviceType), out result))
{
return (IEnumerable<object>)result;
}
return Enumerable.Empty<object>();
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var typedService = service as TypedService;
if (typedService != null)
{
var instances = base.GetServices(typedService.ServiceType);
if (instances != null)
{
return instances
.Select(i => RegistrationBuilder.ForDelegate(i.GetType(), (c, p) => i).As(typedService.ServiceType)
.InstancePerLifetimeScope()
.PreserveExistingDefaults()
.CreateRegistration());
}
}
return Enumerable.Empty<IComponentRegistration>();
}
bool IRegistrationSource.IsAdapterForIndividualComponents
{
get { return false; }
}
}
}
Next, in your SignalR config, you need to assign the custom dependency resolver:
Note: My app was using owin, so you may not need the HubConfiguration bit, but you need the GlobalHost bit (which is the one I messed up when my stuff was not working).
var resolver = new AutofacSignalrDependencyResolver(container);
'Owin config options.
var config = new HubConfiguration()
{
Resolver = resolver,
EnableDetailedErrors = true,
EnableCrossDomain = true
};
GlobalHost.DependencyResolver = resolver;
'More owin stuff
app.MapHubs(config);
Hope this will help resolve your issue.
You need to keep track of the clients that connect to the hub, and then send them the new messages, something like this
This is a base class I wrote for my Hubs
/// <summary>
/// base class for Hubs in the system.
/// </summary>
public class HubBase : Hub {
/// <summary>
/// The hub users
/// </summary>
protected static ConcurrentDictionary<Guid, HubUser> Users = new ConcurrentDictionary<Guid, HubUser>();
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnConnected() {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user = Users.GetOrAdd(userName, _ => new HubUser {
UserId = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
/// <summary>
/// Called when a connection disconnects from this hub gracefully or due to a timeout.
/// </summary>
/// <param name="stopCalled">true, if stop was called on the client closing the connection gracefully;
/// false, if the connection has been lost for longer than the
/// <see cref="P:Microsoft.AspNet.SignalR.Configuration.IConfigurationManager.DisconnectTimeout" />.
/// Timeouts can be caused by clients reconnecting to another SignalR server in scaleout.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) {
try {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user;
Users.TryGetValue(userName, out user);
if (user != null) {
lock (user.ConnectionIds) {
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any()) {
HubUser removedUser;
Users.TryRemove(userName, out removedUser);
}
}
}
} catch {
//Bug in SignalR causing Context.User.Identity.Name to sometime be null
//when user disconnects, thus remove the connection manually.
lock (Users) {
HubUser entry = Users.Values.FirstOrDefault(v => v.ConnectionIds.Contains(Context.ConnectionId));
if (entry != null) entry.ConnectionIds.Remove(Context.ConnectionId);
}
}
return base.OnDisconnected(stopCalled);
}
private Guid RetrieveUserId() {
Cookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket decryptedCookie = FormsAuthentication.Decrypt(authCookie.Value);
var user = JsonConvert.DeserializeObject<User>(decryptedCookie.UserData);
return user.Id;
}
}
Then the Hub code is
/// <summary>
/// A hub for sending alerts to users.
/// </summary>
public class AlertHub : HubBase, IAlertHub {
/// <summary>
/// Sends the alert.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="userId">The user identifier.</param>
public void SendAlert(string message, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).sendAlert(message);
}
}
/// <summary>
/// Send alert to user.
/// </summary>
/// <param name="returnId">The return identifier.</param>
/// <param name="userId">The user identifier.</param>
public void ReturnProcessedAlert(Guid returnId, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).returnProcessedAlert(returnId);
}
}
}