SignalR Client Authentication with Host - c#

I came here reaching for your help. I am fairly new when it comes to SignalR and after several days of reading and retrying stuff again and again I have reached a dead end. Straight to the point.
I have a solution which includes several projects in it. The ones that are giving me trouble are the Web and a class library.
I have SignalR installed in the Web project and the SignalR Client in the Class Library.
My target is to track and monitor the progress of Long running processes that are running in the Class Library. I have SignalR working properly from the Web.
This is my Hub method that I am trying to call:
public void SendProgress(string progressMessage, int progressCount, int totalItems)
{
var percentage = (progressCount * 100) / totalItems;
Clients.All.sendProgress(progressMessage, percentage + "%");
}
Moreover I have a method in the Class Library (ImportProcess) which contains a foreach loop. Inside that foreach loop I am calling:
public void SendProgress(string message, int currentItem, int totalItems)
The body of the SendProgress is this:
public void SendProgress(string message, int currentItem, int totalItems)
{
var hubConnection = new HubConnection("http://localhost:13310/");
IHubProxy statementsHubProxy = hubConnection.CreateHubProxy("statementsHub");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
hubConnection.Start().Wait();
statementsHubProxy.Invoke("SendProgress", message, currentItem, totalItems);
}
The problem is that since I am calling the SendProgress inside a foreach, I consider it wrong to define the connection everytime that SendProgress is called.
I tried including the Connection properties to the constructor of the class. It worked but it only returns the first iteration of the foreach and ignored all the others.
I have to mention though, that if I have the connection properties inside the SendProgress method, the code works and I am receiving the information I want in the cosnole, but again, I think it shouldn't be like that.
This is the MVC controller which calls a rest service which in turn calls the class library
public JsonResult AProcess()
{
_dataImportService.DataImportProcess("2018-02-27", "2018-02-28", "This is another parameter");
return Json("", JsonRequestBehavior.AllowGet);
}
Please ignore the parameters of the DataImportProcess as they are irrelevant at this point.
This is my Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
I am using SignalR 2.2.2
Edit #1
I included the connection properties to the constructor of the class again like this:
static IHubProxy _proxy { get; set; }
public ImportClass()
{
var hubConnection = new HubConnection("http://localhost:13310/");
hubConnection.Credentials = CredentialCache.DefaultNetworkCredentials;
IHubProxy statementsHubProxy = hubConnection.CreateHubProxy("statementsHub");
_proxy = statementsHubProxy;
hubConnection.Start().Wait();
}
But no luck. SendProgress method is not sending any updates at all.
Any help is much, much appreciated.
Best regards,
Konstantinos.

Related

Blazor WASM inject settings into a Razor Class Library

My intent is to create a payment processing service I can bolt onto the side of any website. My issue is injecting settings, such as a Stripe Public Key, a Paypal Merchant Id, and the specific url on the server to send the token and to charge stripe.
This is simplified for this question (and the Payments projects will end up being dll's), but the project structure looks like this:
The WebApp is the ASP.NET Core hosted Blazor WASM project template. WebApp.Client has dependencies on Payments.Shared and Payments.Ui while WebApp.Server has a dependency on Payments.Backend as well (for Stripe charge response etc models).
Since I want to use this Payments service on any site, and since these will be different for every site, I'll need to set the props in PaymentSettings.cs at startup.
Is there a way to do this? I only plan on using with Blazor WASM websites, if that matters.
I feel like I might be able to pass these settings down to the WebApp.Client through string[] args in Program.cs's Main method:
public static async Task Main(string[] args)
{
//omitted for brevity
//relevant line here
builder.Services
.AddSingleton(sp => new Payments.Shared.Infrastructure.PaymentSettings(args));
await builder.Build().RunAsync();
}
and then from there into the constructor for PaymentSettings.cs:
public class PaymentSettings
{
public PaymentSettings(string[] args)
{
//set props from args
}
public string PaypalMerchantId { get; set; }
public string StripeKey { get; set; }
public string ChargeStripeUrl { get; set; }
}
But I don't see any way to do this from the WebApp.Server project.
Maybe I'm overthinking this, I mean, if I can at least inject the base url of WebApp.Server into the Payments.Shared project, I can then run a get request for the rest of it.
Any help much appreciated.
I was overthinking it. Since it's all public info, I just hard-coded the info into Program.cs in the WebApp.Client:
public static async Task Main(string[] args)
{
//omitted for brevity
string stripeKey = "Hello dolly";
string paypalId = "How ye be";
string chargeStripeUrl = "https://all-about-dolly.com/chargeIt";
//relevant line here
builder.Services.AddSingleton(sp => new PaymentSettings
{
StripeKey = stripeKey,
PaypalMerchantId = paypalId,
ChargeStripeUrl = chargeStripeUrl
});
await builder.Build().RunAsync();
}
Might not be best practices, but it works for me.

How to efficiently count HTTP Calls in asp.net core?

I have an abstract class called HttpHelper it has basic methods like, GET, POST, PATCH, PUT
What I need to achieve is this:
Store the url, time & date in the database each time the function is called GET, POST, PATCH, PUT
I don't want to store directly to the database each time the functions are called (that would be slow) but to put it somewhere (like a static queue-memory-cache) which must be faster and non blocking, and have a background long running process that will look into this cache-storage-like which will then store the values in the database.
I have no clear idea how to do this but the main purpose of doing so is to take the count of each calls per hour or day, by domain, resource and url query.
I'm thinking if I could do the following:
Create a static class which uses ConcurrentQueue<T> to store data and call that class in each function inside HttpHelper class
Create a background task similar to this: Asp.Net core long running/background task
Or use Hangfire, but that might be too much for simple task
Or is there a built-in method for this in .netcore?
Both Hangfire and background tasks would do the trick as consumers of the queue items.
Hangfire was there before long running background tasks (pre .net core), so go with the long running tasks for net core implementations.
There is a but here though.
How important is to you that you will not miss a call? If it is, then neither can help you.
The Queue or whatever static construct you have will be deleted the time your application crashes/machine restarts or just plain recycling of the application pools.
You need to consider some kind of external Queuing mechanism like rabbit mq with persistence on.
You can also append to a file, but that might also cause some delays as read/write.
I do not know how complex your problem is but I would consider two solutions.
First is calling Async Insert Method which will not block your main thread but will start task. You can return response without waiting for your log to be appended to database. Since you want it to be implemented in only some methods, I would do it using Attributes and Middleware.
Simplified example:
public IActionResult SomePostMethod()
{
LogActionAsync("This Is Post Method");
return StatusCode(201);
}
public static Task LogActionAsync(string someParameter)
{
return Task.Run(() => {
// Communicate with database (X ms)
});
}
Better solution is creating buffer which will not communicate with database each time but only when filled or at interval. It would look like this:
public IActionResult SomePostMethod()
{
APILog.Log(new APILog.Item() { Date = DateTime.Now, Item1 = "Something" });
return StatusCode(201);
}
public partial class APILog
{
private static List<APILog.Item> _buffer = null;
private cont int _msTimeout = 60000; // Timeout between updates
private static object _updateLock = new object();
static APILog()
{
StartDBUpdateLoopAsync();
}
private void StartDBUpdateLoopAsync()
{
// check if it has been already and other stuff
Task.Run(() => {
while(true) // Do not use true but some other expression that is telling you if your application is running.
{
Thread.Sleep(60000);
lock(_updateLock)
{
foreach(APILog.Item item in _buffer)
{
//Import into database here
}
}
}
});
}
public static void Log(APILog.Item item)
{
lock(_updateLock)
{
if(_buffer == null)
_buffer = new List<APILog.Item>();
_buffer.Add(item);
}
}
}
public partial class APILog
{
public class Item
{
public string Item1 { get; set; }
public DateTime Date { get; set; }
}
}
Also in this second example I would not call APILog.Log() each time but use Middleware in combination with Attribute

SignalR Core how to send message to clients every n seconds

AKA "Send messages to clients from a background service"
I would like my SignalR server to update a dashboard every n seconds. I'm using this right now:
public class MyHub : Hub
{
public override Task OnConnectedAsync()
{
Observable.Interval(TimeSpan.FromSeconds(0.5)).Subscribe(l =>
{
var alt = CalcAltitude(l);
SendMessage(alt);
});
return Task.CompletedTask;
}
private void SendMessage(double alt)
{
Clients.All.SendAsync("SendAction", new Status() {Altitude = alt});
}
private double CalcAltitude(long l)
{
return 100 * Math.Sin((double) l / 100) + 200;
}
}
public class Status
{
public double Altitude { get; set; }
}
When my code is executed, it throws an exception saying that
cannot access a disposed object
I suspect I'm doing something wrong here.
So, what's the correct way to make send messages to all the clients on a timely manner?
OK, this time the answer didn't come from Stack Overflow, but from another source.
This can be done with this code in ASP.NET Core + SignalR Core.
You'll need 2 parts.
the Background Service itself (updates the clients): https://github.com/davidfowl/UT3/blob/fb12e182d42d2a5a902c1979ea0e91b66fe60607/UTT/Scavenger.cs
the Setup (wiring) part: https://github.com/davidfowl/UT3/blob/fb12e182d42d2a5a902c1979ea0e91b66fe60607/UTT/Startup.cs#L46
Big thanks to David Fowler for the reply!
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.All.SendnewData(Data);
Put this code where your data is updated or calculated in every second.
Like: Your DB process is done then Broadcast new data
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Infrastructure;
using Microsoft.AspNet.Mvc;
public class TestController : Controller
{
private IHubContext testHub;
public TestController(IConnectionManager connectionManager)
{
testHub = connectionManager.GetHubContext<TestHub>();
testHub.Clients.All.SendnewData(Data);
}
}

Client not receiving SignalR message from controller

I am trying to send a message from a controller to client(s) group using SignalR.
When an event occurs in one of my controllers, I need to push a message using a signalr hub to be displayed like an alert on my clients screens.
i know a lot of questions have been asked here, and I've read and tried a lot of them. Since I am new to SignalR some of them even already helped me to put things in place.
At the moment everything seems in place. Clients can connect to the hub and join groups and controller can call methods from the hub. But the client never receive the message and I can't figure out why. I suspect that hub's method called by controller don't "see" the clients but I can't understand what's wrong.
Hub's code :
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
[HubName("myHub")]
public class MyHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
public void Notify(string groupName, string message)
{
Clients.Group(groupName).displayNotification(message);
}
public static void Static_Notify(string groupName, string message)
{
var toto = UserHandler.ConnectedIds.Count();
hubContext.Clients.Group(groupName).displayNotification(message);
hubContext.Clients.All.displayNotification(message);//for testing purpose
}
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool StopCalled)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnected(StopCalled);
}
}
Here is the call from my controller :
//(For simplification and readability I define here variables actually obtained by treating some data )
//I already checked that problem did not come from missing data here
string groupName = "theGroupName";
string message = "My beautifull message.";
//prepare signalR call
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
//Following commented lines are different attempts made based on exemples and answers I found here and on others sites.
//The uncommented one is the one in use at the moment and is also based on an answer (from SO i think)
//context.Clients.Group(_userEmail).displayNotification(message);
//context.Clients.Group(_userEmail).Notify(_userEmail,message);
MyHub.Static_Notify(_userEmail, message);
And here is the client-side code :
$(document).ready(function () {
var userGroup = 'theGroupName';
$.connection.hub.url = 'http://localhost/SignalRHost/signalr';
var theHub = $.connection.myHub;
console.log($.connection)
console.log($.connection.myHub)
theHub.client.displayNotification = function (message) {
console.log('display message');
alert(message);
};
$.connection.hub.start()
.done(function () {
theHub.server.joinGroup(userGroup);
console.log("myHub hub started : " + $.connection.hub.id)
console.log(theHub)
})
.fail(function () {
console.log('myHub hub failed to connect')
});
});
Please help me understand what logic I failed to understand or what error I am missing.
EDIT :
To answer Alisson's comment:
The Startup.cs I forgot to show
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR();
});
}
Important stuff I forgot to mention too :
The Controller, the hub, and the client are 3 different projects (because of the global application architecture I had to separate hub logic)
All are on my localhost IIS but on different ports
I've set breakpoints on "OnConnected" and "onDiconnected" events and client connects and disconnects normally
You just need one app to act as server, in your case it should be the SIgnalRHost project. Your controller project should be a client of the server, therefore it just needs this package:
Install-Package Microsoft.AspNet.SignalR.Client
Your controller project doesn't actually need to reference the project containing the hub class. In your controller, you'll use C# SignalR client to connect to the server (as you would do in javascript client), join the group and invoke the hub method:
var hubConnection = new HubConnection("http://localhost/SignalRHost/signalr");
IHubProxy myHub = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
myHub.Invoke("JoinGroup", "theGroupName");
myHub.Invoke("Notify", "theGroupName", "My beautifull message.");
...finally, you don't need that Static_Notify at all.
Since you are passing the group name as a parameter on the Notify
method, you don't really need to join the group from your controller.
You'd need only if you were trying to send the message to the same
group the controller was connected (then you wouldn't need to pass the
group name as parameter anymore).
SignalR C# Client Reference.

Async WCF: wait for another call

We have an old Silverlight UserControl + WCF component in our framework and we would like to increase the reusability of this feature. The component should work with basic functionality by default, but we would like to extend it based on the current project (without modifying the original, so more of this control can appear in the full system with different functionality).
So we made a plan, where everything looks great, except one thing. Here is a short summary:
Silverlight UserControl can be extended and manipulated via ContentPresenter at the UI and ViewModel inheritance, events and messaging in the client logic.
Back-end business logic can be manipulated with module loading.
This gonna be okay I think. For example you can disable/remove fields from the UI with overriden ViewModel properties, and at the back-end you can avoid some action with custom modules.
The interesting part is when you add new fields via the ContentPresenter. Ok, you add new properties to the inherited ViewModel, then you can bind to them. You have the additional data. When you save base data, you know it's succeeded, then you can start saving your additional data (additional data can be anything, in a different table at back-end for example). Fine, we extended our UserControl and the back-end logic and the original userControl still doesn't know anything about our extension.
But we lost transaction. For example we can save base data, but additional data saving throws an exception, we have the updated base data but nothing in the additional table. We really doesn't want this possibility, so I came up with this idea:
One WCF call should wait for the other at the back-end, and if both arrived, we can begin cross thread communication between them, and of course, we can handle the base and the additional data in the same transaction, and the base component still doesn't know anything about the other (it just provide a feature to do something with it, but it doesn't know who gonna do it).
I made a very simplified proof of concept solution, this is the output:
1 send begins
Press return to send the second piece
2 send begins
2 send completed, returned: 1
1 send completed, returned: 2
Service
namespace MyService
{
[ServiceContract]
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Service1
{
protected bool _sameArrived;
protected Piece _same;
[OperationContract]
public Piece SendPiece(Piece piece)
{
_sameArrived = false;
Mediator.Instance.WaitFor(piece, sameArrived);
while (!_sameArrived)
{
Thread.Sleep(100);
}
return _same;
}
protected void sameArrived(Piece piece)
{
_same = piece;
_sameArrived = true;
}
}
}
Piece (entity)
namespace MyService
{
[DataContract]
public class Piece
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string SameIdentifier { get; set; }
}
}
Mediator
namespace MyService
{
public sealed class Mediator
{
private static Mediator _instance;
private static object syncRoot = new Object();
private List<Tuple<Piece, Action<Piece>>> _waitsFor;
private Mediator()
{
_waitsFor = new List<Tuple<Piece, Action<Piece>>>();
}
public static Mediator Instance
{
get
{
if (_instance == null)
{
lock (syncRoot)
{
_instance = new Mediator();
}
}
return _instance;
}
}
public void WaitFor(Piece piece, Action<Piece> callback)
{
lock (_waitsFor)
{
var waiter = _waitsFor.Where(i => i.Item1.SameIdentifier == piece.SameIdentifier).FirstOrDefault();
if (waiter != null)
{
_waitsFor.Remove(waiter);
waiter.Item2(piece);
callback(waiter.Item1);
}
else
{
_waitsFor.Add(new Tuple<Piece, Action<Piece>>(piece, callback));
}
}
}
}
}
And the client side code
namespace MyClient
{
class Program
{
static void Main(string[] args)
{
Client c1 = new Client(new Piece()
{
ID = 1,
SameIdentifier = "customIdentifier"
});
Client c2 = new Client(new Piece()
{
ID = 2,
SameIdentifier = "customIdentifier"
});
c1.SendPiece();
Console.WriteLine("Press return to send the second piece");
Console.ReadLine();
c2.SendPiece();
Console.ReadLine();
}
}
class Client
{
protected Piece _piece;
protected Service1Client _service;
public Client(Piece piece)
{
_piece = piece;
_service = new Service1Client();
}
public void SendPiece()
{
Console.WriteLine("{0} send begins", _piece.ID);
_service.BeginSendPiece(_piece, new AsyncCallback(sendPieceCallback), null);
}
protected void sendPieceCallback(IAsyncResult result)
{
Piece returnedPiece = _service.EndSendPiece(result);
Console.WriteLine("{0} send completed, returned: {1}", _piece.ID, returnedPiece.ID);
}
}
}
So is it a good idea to wait for another WCF call (which may or may not be invoked, so in a real example it would be more complex), and process them together with cross threading communication? Or not and I should look for another solution?
Thanks in advance,
negra
If you want to extend your application without changing any existing code, you can use MEF that is Microsoft Extensibility Framework.
For using MEF with silverlight see: http://development-guides.silverbaylabs.org/Video/Silverlight-MEF
I would not wait for 2 WCF calls from Silverlight, for the following reasons:
You are making your code more complex and less maintainable
You are storing business knowledge, that two services should be called together, in the client
I would call a single service that aggreagated the two services.
It doesn't feel like a great idea to me, to be honest. I think it would be neater if you could package up both "partial" requests in a single "full" request, and wait for that. Unfortunately I don't know the best way of doing that within WCF. It's possible that there's a generalized mechanism for this, but I don't know about it. Basically you'd need some loosely typed service layer where you could represent a generalized request and a generalized response, routing the requests appropriately in the server. You could then represent a collection of requests and responses easily.
That's the approach I'd look at, personally - but I don't know how neatly it will turn out in WCF.

Categories