Asp.net Azure SignalR service on a distributed system - c#

I have a web app running on Asp.net 4.7.2. This app contains a few REST endpoints that are queried and then delegate work off to a windows service that sits on another machine.
When the work on this service is complete, I want to use SignalR to send a message to the client to indicate that the work is done. Because this service is on another machine, this is proving quite tricky.
I have tried using an Azure SignalR Service as an abstracted level above this. My intention was to use the REST capabilities to call this Azure service, have it run code from a Hub (which I have currently defined and created in the web app) and then broadcast a message to the clients.
At the moment, I am not sure if this is possible. The notes say that the REST provision is only available in the asp.net CORE version of the library. I have made calls to the endpoint and received an accepted response, but no luck.
My question is, then, how do I get the following architecture working under my conditions? If I cannot, what other suggestions do you have?
Machine 1:
Windows service running bespoke code that takes an unpredictable length of time
When code completes, send message to SignalR hub via azure service
Machine 2:
Webapp containing the SignalR hub definitions and client logic.

You can actually accomplish this without the service using just your existing webapp. The key is to create an endpoint that your Windows service to call that is linked to your hub.
So if I create a simple SingalR hub in .Net 4.x:
public class NotificationHub : Hub
{
public void Send(string message)
{
Clients.All.addNewMessageToPage(name, message);
}
}
I can access it in a WebApi controller:
public class NotificationController : ApiController
{
IHubContext context;
public NotificationController()
{
context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
}
public HttpResponseMessage Get(string message)
{
object[] args = { message };
context.Clients.All.Send("Send", args) ;
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Using a REST endpoint is going to be simplest in frameworks like MVC and WebApi, but it is possible to add a messaging service in between like Service Bus if you need to do more than simply return a message to the clients.

Related

HttpClient hanging in long-running service

We have a service deployed in AKS that makes HTTP requests to an external API outside of the cluster. The path of the request is AKS -> API Management -> ExpressRoute -> API.
We have been encountering an issue where all requests from an instance of the service will hang when calling the external API. The requests never reach the API. Due to the timeouts that we have configured, the requests will be cancelled after 10 seconds. We most recently saw this on an instance of the service that had been running for over 30 days without any issues. Restarting the instance resolves the issue.
We use Microsoft.Extensions.Http (6.0.0) to register the API client class with the following code:
serviceCollection.AddHttpClient<IMyApiClient, MyApiClient>();
Our API client looks like this:
public class MyApiClient : IMyApiClient
{
private readonly HttpClient _httpClient;
public MyApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task CallApi(CancellationToken ctx)
{
await _httpClient.GetAsync("api-uri", ctx);
}
}
Obviously there is complexity in the hops between AKS and the API, however, I first want to understand if we are doing anything wrong with HttpClient. As far as I can tell, we are following the reccommended usage.
We are unable to reproduce this issue locally and we have no additional logs as the requests are simply taking a long time and then being cancelled, there are no actual errors.

How to fetch data from a third party API in ASP.NET Core 6 Web API

I am currently trying to make a request to a third party library in my ASP.NET Core 6 Web API and work with this data (it really has to run over a Web API template).
That means I'm importing data from another API.
Unfortunately I don't know how to tell my application to make a call.
I would have expected the Task to run automatically when the application starts. Unfortunately this is not the case.
I first wanted to test whether this works at all when the application is started. Later, I would build in a scheduler, which sends requests accordingly.
It should be possible, right?
It would be great if someone could tell me as well if it's possible to put the URL "localhost:xxx/" in the constructor somehow, but still not get any dependency injection errors with AddScoped.
I use Flurl.Http to make Http Requests.
If it is important. My program.cs is in a console application and DataImport in an empty project
Unfortunately, I am relatively new to the ASP.NET world and I hope that the question is not too unprofessional. Otherwise I apologize. It's kind of hard to google for a problem like this and find something
using Flurl;
using Flurl.Http;
public class DataImport
{
private readonly Service service;
public DataImport(Servie service)
{
_service = service;
}
public async Task<IEnumerable<Data>> ImportData()
{
var data = await "localhost:xxx/".AppendPathSegment("data").GetJsonAsync<DataDto[]>();
return _service.Add(data.Select(it => new DatoDtoToData(it)));
}
}
program.cs:
builder
.Services
... Service Injections
....
.AddScoped<DataImport>()
I would encourage you to look into Azure Functions.
Have a look at Microsoft's Introduction to Azure Functions. In the Scenarios section we can read.
The following are a common, but by no means exhaustive, set of scenarios for Azure Functions.
If you want to...
then...
Build a web API
Implement an endpoint for your web applications using the HTTP trigger
...
Build a serverless workflow
Chain a series of functions together using durable functions
...
Run scheduled tasks
Execute code on pre-defined timed intervals
...
I really think this could work well for you because:
You can run/host it locally or host it in Azure.
It's very easy to start with. Microsoft's tutorials are:
Quickstart: Create your first C# function in Azure using Visual Studio, and
Quickstart: Create a C# function in Azure using Visual Studio Code
It supports multiple ways of triggering your functions. There are over 20 tiggers; with 2 of the most relevant for you should be:
Azure Functions HTTP trigger
Timer trigger for Azure Functions

How to use SignalR 2 with WEB API, calling SignalR methods from API and from Clients

I've been struggling for the past two days on how to get SignalR 2 to work with Web API. I tried to follow a few tutorials, from Microsoft and from others, about the topic, but still took me time to understand where I was wrong. I'm posting this question to help someone else that ends up in the same situation I was on.
I needed to create a Hub on SignalR that could respond to a Console Application (C#) and a Web App (AngularJs) as clients, and I also needed it to send signals after a certain method on API being hit.
I followed these (and others) tutorials:
ASP.NET SignalR Hubs API Guide - Server (C#)
ASP.NET SignalR Hubs API Guide - .NET Client (C#)
Stream Web API Transactions Using SignalR
And some more, but those are the best I could find. Also, a dozen of others questions, here and on other sites.
The best I could come up with was a solution that only answered to the API method being hit, but the clients wasn't able to fire any of the Hub's methods (they could only listen to it).
This was my Hub's code:
public class MyHub : Hub
{
private static IHubContext hubContext =
GlobalHost.ConnectionManager.GetHubContext<MyHub>();
public static void GetStatus(string message)
{
hubContext.Clients.All.acknowledgeMessage($"GetStatus: {message}");
}
public void GetMessage(string message)
{
hubContext.Clients.User(id).acknowledgeMessage($"GetStatus: {message}");
}
}
And my question was: How can I make the clients hit those methods on Hub, so they can get some personal response, if needed?
Ok, so the main problem here was that somewhere when I started, I found out how to make the Hub respond to API's methods (I couldn't find where by the time I'm posting this answer), and then I only found different ways for the clients to call the Hub's methods, and one did not work with the other. I could not set both to the same way.
This is the code from my API's controller:
public IHttpActionResult TesteComunicacao(string mensagem)
{
MyHub.GetStatus("Message here!");
return Ok("ok");
}
And the Hub's method:
// Use this for Server Methods
public static void GetStatus(string message)
{
hubContext.Clients.All.acknowledgeMessage($"GetStatus: {message}");
}
This is the only way I found to make it happen. The hub will not respond with the method used by the clients. Those must have a different code, like this Hub's method here:
// Use this for Clients Methods
public void GetMessage(string message)
{
Clients.Caller.acknowledgeMessage($"GetMessage: {message}");
}
I was trying to implement my "hubContext" solution on the clients method, but it wasn't working at all, and when I tried to implement the "Clients" solution, later found, to be consumed by my API, I had to make it a "non-static method", and then call it on the controller, which only resulted in this error: "using hubcontext outside hub pipeline is unsoported"
And then, I found this blessed answer here on Stack Overflow, that finaly showed me where I was wrong.
And that's exactly what I was doing wrong. I must implement then differently based on the method's objective. The ones to be consumed by my API use "hubContext", and the ones to be consumed by my clients application use "Clients".
I hope it helps other people struggling with this problem to find it faster, since the blessed answer there was about ASP.NET MVC, not Web API.

Update configuration and restart self-hosted ASP.NET Web API 2 on HTTP request

I have a self-hosted OWIN web app running ASP.NET Web API 2.2, which is executed as a windows service. I want to achieve a remote configuration scenario: from a client, I want to make an HTTP request and in the called Web API action, I want to update a configuration file (e.g. to change a DB connection string) and then restart the service (so that the new configuration is loaded). However, here is the tricky part:
If I simply call Environment.Exit, then the respone won't finish and the client won't get the message that it worked. Is there any way to write to the response stream and finish it before restarting the service from within a Web-API action? Or should I use something else for this, maybe a filter? Or is there any other way that you would rather suggest?
I am not interested in any security discussion - the service is only available in an intranet and the corresponding Web API action is secured with authentication and authorization.
Thanks for your help in advance.
Look like sucker punch, but it works:
[HttpGet]
public async Task<IHttpActionResult> ActionMethod()
{
var httpActionResult = Ok(); //some work instead it
new Thread(() =>
{
Thread.Sleep(500);
Environment.Exit(1);
}).Start();
return httpActionResult;
}
I not shure, but if you exit with exitCode != 0, then services subsystem will think, that your service crashed and try restat it (If you setup it in service settings)

Passing additional information from SoapExtension to WebMethod implementation

Due to network architecture of our software, our application servers cannot connect directly to the web service of our customer. Because of this we have an integration server between the application servers and customer's servers. This integration server hosts a proxy web service. The problem is that the necessary credentials and some other additional information needs to be passed from the database at our application server to our proxy web service at the integration server.
I wouldn't want to pollute the API and pass the object containing credentials and additional information on each web service request. Additionally we have multiple integration servers which can be shut down at will so I cannot just initialize the web service with credentials and other information in a separate method because the subsequent web service requests might be passed to another integration server.
Is there a way to add some kind of SoapExtension which could be used to pass the information to my web service instance on each method? If not, is there something else I could do besides adding an argument to each web method and use that to pass the information?
The answer was actually quite obvious.
Firstly I need to create a class which is derived from SoapHeader. This class is used to store all credentials and other additional information. For easier explaining, let's give this class a name CredentialContainer.
In the actual web service class we need to add a new public property of type CredentialContainer. The property in this example is named Container.
Lastly, we have to add new attribute called SoapHeader to each method with WebMethod attribute. This handles transferring the information passed in the header of SOAP message to our CredentialContainer instance. Because new web service instance is created for each web service request, there are no risk even with multiple concurrent web service requests.
Here's the example code:
[WebService]
public class ExampleWebService
{
public CredentialContainer Container { get; set; }
[WebMethod]
[SoapHeader("Container")]
public void PerformSomething(string value)
{
var actualWebServiceClient = new MyWebServiceClient(Container.Url, ...);
actualWebServiceClient.SendValue(value);
}
}
public class CredentialContainer : SoapHeader
{
public string Url { get; set; }
...
}

Categories

Resources