Call a Web Api method from signalr hub - c#

I have an Angular (Angular 9) application as client and a ASP.NET WebApi as server.
The server has an additional Signalr2 interface.
First thing is I don't know if the server where my application will be installed supports websockets.
If not, my angular application would create an http request to the WEB API and get the response asynchrounus, so normal REST.
If the server supports websockets, I would like to do a websocket request from my client side and get the response from signalR.
The client side is clear, I would do some kind of HTTPClient abstraction to generate one or the other and wait for the response.
The other thing is, how can I call a Web APi method from my SignalRHub without using the .Net HttpClient?
And if I have to use the HttpClient, is the overhead so big, that doing a normal http request would result the same?
This could be my SignalR data:
public WebApiRequest{
public string HttpMethod{ get; set;}
public string Route{ get; set;}
public OrderedDictionary<string, object> Parameters{ get; set;}
public string Content{ get; set;}
}
And this the SignalR response data:
public WebApiResponse{
public int StatusCode{ get; set;}
public object Data{ get; set;}
}
This the Test Web Api Controller:
[RoutePrefix("api/test")]
[Authorize]
public class TestController : ApiController
{
[HttpPost]
[Route("calculate")]
public async Task<IHttpActionResult> Calculate(int id, Calculatedata calc)
{
return Ok(CalculateMyTestData(id, calc));
}
}
Now with the data from the signalr request I would like to call the 'Calculate' method of the 'TestController' and give the response as WebApiResponse back.
An idea, this is inside my Hub:
public void DoWebApirequest(WebApiRequest data)
{
var test = new TestController()
{
var method = GetHttpMethod(data.HttpMethod);
Request = new HttpRequestMessage(method , this.Context.Request.Url)
{
Content = new StringContent(data.Content, Encoding.UTF8, "application/json")
}
};
foreach (var contextHeader in this.Context.Headers)
{
test.Request.Headers.Add(contextHeader.Key, contextHeader.Value);
}
// Now fill the parameters and call the method you like by reflection...
test.Calculate(...);
}
Could this work?

Related

How do I fix my error when deserializing json from HttpRequest in Azure Function App?

I've recently started using azure function apps. I am passing some json data from Postman into my app when it's running so that I can test it and I'm getting an error of an unexpected character '{'.
{
"testId": "001",
"shopItems": {
"itemName": "something"
}
}
Here is what is happening.
Firstly, I have my very basic function, which can be called via it's route in postman. I pass the above request in the body as JSON to this function which then calls a service to work with it.
[FunctionName("UpdateCart")]
public async Task<IActionResult> UpdateCart(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/cart/update")] HttpRequest req,
ILogger log)
{
await _myService.UpdateCartAsync(req.Body);
//Return ok
return new OkResult();
}
The body property of 'HttpRequest' is a 'Stream' and therefore my service and it's interface expect a stream to be passed in.
Interface
namespace MyProject.Services.Interface
{
public interface IMyService
{
Task UpdateCartAsync(Stream contents);
}
}
Service
Since we know it's a stream, I use "ReadToEndAsync" which then produces a string from the result. I pass that string to the "JsonConvert.Deserializer" to map that to my model and that's where it fails with the unexpected character '{' which is the bracer before "itemName".
public async Task UpdateCartAsync(Stream content)
{
var result = await new StreamReader(content).ReadToEndAsync();
Basket cart = JsonConvert.DeserializeObject<Cart>(result); //fails here
var db = _connectionMultiplexer.GetDatabase();
await db.StringSetAsync(cart.CartId, cart.CartContents);
}
Model
public class Cart
{
public string CartId { get; set; }
public string CartContents { get; set; }
}
I'm guessing here that the Steam string isn't actually accurate or that I perhaps need to serialize first, I'm not sure. Is there another way to pass JSON from the body to a functions app?

SignalR .NET Client connecting to Azure SignalR Service in a Blazor .NET Core 3 application

I'm trying to make a connection between my ASP.NET Core 3.0 Blazor (server-side) application and the Azure SignalR Service. I'll end up injecting my SignalR client (service) in to a few Blazor components so they'll update my UI/DOM in realtime.
My issue is that I'm receiving the following message when I call my .StartAsync() method on the hub connection:
Response status code does not indicate success: 404 (Not Found).
BootstrapSignalRClient.cs
This file loads my configuration for the SignalR Service including the URL, connection string, key, method name, and hub name. These settings are captured in the static class SignalRServiceConfiguration and used later.
public static class BootstrapSignalRClient
{
public static IServiceCollection AddSignalRServiceClient(this IServiceCollection services, IConfiguration configuration)
{
SignalRServiceConfiguration signalRServiceConfiguration = new SignalRServiceConfiguration();
configuration.Bind(nameof(SignalRServiceConfiguration), signalRServiceConfiguration);
services.AddSingleton(signalRServiceConfiguration);
services.AddSingleton<ISignalRClient, SignalRClient>();
return services;
}
}
SignalRServiceConfiguration.cs
public class SignalRServiceConfiguration
{
public string ConnectionString { get; set; }
public string Url { get; set; }
public string MethodName { get; set; }
public string Key { get; set; }
public string HubName { get; set; }
}
SignalRClient.cs
public class SignalRClient : ISignalRClient
{
public delegate void ReceiveMessage(string message);
public event ReceiveMessage ReceiveMessageEvent;
private HubConnection hubConnection;
public SignalRClient(SignalRServiceConfiguration signalRConfig)
{
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConfig.Url + signalRConfig.HubName)
.Build();
}
public async Task<string> StartListening(string id)
{
// Register listener for a specific id
hubConnection.On<string>(id, (message) =>
{
if (ReceiveMessageEvent != null)
{
ReceiveMessageEvent.Invoke(message);
}
});
try
{
// Start the SignalR Service connection
await hubConnection.StartAsync(); //<---I get an exception here
return hubConnection.State.ToString();
}
catch (Exception ex)
{
return ex.Message;
}
}
private void ReceiveMessage(string message)
{
response = JsonConvert.DeserializeObject<dynamic>(message);
}
}
I have experience using SignalR with .NET Core where you add it so the Startup.cs file using .AddSignalR().AddAzureSignalR() and map a hub in the app config and doing it this way requires certain 'configuration' parameters to be established (i.e. connection string).
Given my situation, where does HubConnectionBuilder get the connection string or a key to authenticate to the SignalR Service?
Is it possible the 404 message is a result of the missing key/connection string?
Okay so it turns out the documentation is lacking a key piece of information here. If you're using the .NET SignalR Client connecting to the Azure SignalR Service, you need to request a JWT token and present it when creating the hub connection.
If you need to authenticate on behalf of a user you can use this example.
Otherwise, you can set up a "/negotiate" endpoint using a web API such as an Azure Function to retrive a JWT token and client URL for you; this is what I ended up doing for my use case. Information about creating an Azure Function to get your JWT token and URL can be found here.
I created a class to hold these two values as such:
SignalRConnectionInfo.cs
public class SignalRConnectionInfo
{
[JsonProperty(PropertyName = "url")]
public string Url { get; set; }
[JsonProperty(PropertyName = "accessToken")]
public string AccessToken { get; set; }
}
I also created a method inside my SignalRService to handle the interaction with the web API's "/negotiate" endpoint in Azure, the instantiation of the hub connection, and the use of an event + delegate for receiving messages as follows:
SignalRClient.cs
public async Task InitializeAsync()
{
SignalRConnectionInfo signalRConnectionInfo;
signalRConnectionInfo = await functionsClient.GetDataAsync<SignalRConnectionInfo>(FunctionsClientConstants.SignalR);
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConnectionInfo.Url, options =>
{
options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
})
.Build();
}
The functionsClient is simply a strongly typed HttpClient pre-configured with a base URL and the FunctionsClientConstants.SignalR is a static class with the "/negotiate" path which is appended to the base URL.
Once I had this all set up I called the await hubConnection.StartAsync(); and it "connected"!
After all this I set up a static ReceiveMessage event and a delegate as follows (in the same SignalRClient.cs):
public delegate void ReceiveMessage(string message);
public static event ReceiveMessage ReceiveMessageEvent;
Lastly, I implemented the ReceiveMessage delegate:
await signalRClient.InitializeAsync(); //<---called from another method
private async Task StartReceiving()
{
SignalRStatus = await signalRClient.ReceiveReservationResponse(Response.ReservationId);
logger.LogInformation($"SignalR Status is: {SignalRStatus}");
// Register event handler for static delegate
SignalRClient.ReceiveMessageEvent += signalRClient_receiveMessageEvent;
}
private async void signalRClient_receiveMessageEvent(string response)
{
logger.LogInformation($"Received SignalR mesage: {response}");
signalRReservationResponse = JsonConvert.DeserializeObject<SignalRReservationResponse>(response);
await InvokeAsync(StateHasChanged); //<---used by Blazor (server-side)
}
I've provided documentation updates back to the Azure SignalR Service team and sure hope this helps someone else!
Update: the sample with the serverless sample is deprecated for the management SDK (sample). The management SDK uses a negotiation server.

ServiceStack 3 service not able to be called

I'm working on a legacy ServiceStack application, and I'm trying to add a new endpoint. It's a servicestack 3 application. I created new Response, Request, and Service classes for it, like I've done countless times for other endpoints, but for some reason I can't seem to call it.
I even tried just copying previously existing files and rewriting them to fit my needs, but that didn't work either.
Here's my request:
[Route("/OSTicket/UpdateList", "POST")]
public class OSTicketUpdateListByDBIDRequest
{
public int DatabaseID { get; set; }
}
Here's my response:
public class OSTicketUpdateListResponse : IHasResponseStatus
{
public ResponseStatus ResponseStatus { get; set; }
}
And here's my service endpoint:
public OSTicketUpdateListResponse Post(OSTicketUpdateListByDBIDRequest request)
{
OSTicketUpdateListResponse response = new OSTicketUpdateListResponse();
response.ResponseStatus = new ResponseStatus();
response.ResponseStatus.ErrorCode = "200";
return response;
}
Do I need to register these files somewhere? I don't remember having to do that in the past, but it's very possible that I'm missing something. Whenever I POST to the application using DHC, I get a 404.
Figured it out. In my service declaration:
public class OSTicketService
I forgot to inherit Service.

Call ASP.NET from c# (win32 based) client

I've got a web application created with ASP.NET and a windows native client program written in c#.
The windows native program needs to send and fetch data from the ASP.NET web application.
I guess in the web application I'll need a controller for the external calls. And in the client Software I somehow Need to call them.
Is there a way to achieve calls with complex data types (lists of classes) as parameters?
How do I secure the calls from the client? Simple http-logon?
for example I'd like to transfer an instance of this class to or from the ASP.NET web application:
public class Address
{
public String Street {get;set;}
public String City {get;set;}
}
public class CustomerInformation
{
public String No {get;set;}
public String Name {get;set;}
public List<Address> Addresses {get;set;}
}
Of course the Windows client is running somewhere local while the ASP.NET Service is running in the web.
I would add API controller and put some methods there. For instance
// Addresses API
public class AddressController : ApiController
{
private readonly IRepository<Address> _repository;
public AddressController(IRepository<Address> repository)
{
_repository = repository;
}
[BasicAuthorize]
public IList<Address> GetList()
{
return _repository.GetAll();
}
}
// Constomer information API
public class CustomerInformationController : ApiController
{
private readonly IRepository<CustomerInformation> _repository;
public CustomerInformationController(IRepository<CustomerInformation> repository)
{
_repository = repository;
}
[BasicAuthorize]
public IList<CustomerInformation> GetList()
{
return _repository.GetAll();
}
}
To secure those methods you can use Basic authentication. This means that you can add authorization header for each request:
For example how it looks for user "myuser" with password "test"
Authorization: basic bXl1c2VyOnRlc3Q=
// Custom attribute for Basic authentication
public class BasicAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
private readonly string[] _permissionNames;
public BasicAuthorizeAttribute()
{
}
public BasicAuthorizeAttribute(params string[] permissionNames)
{
_permissionNames = permissionNames;
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// check if user has been already authorized
if (base.IsAuthorized(actionContext))
return true;
var user = AuthenticateUser(actionContext);
// here you can check roles and permissions
return user != null;
}
private IUser AuthenticateUser(HttpActionContext context)
{
var request = context.Request;
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader != null)
{
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && authHeader.Parameter != null)
return AuthenticateUser(authHeader.Parameter);
}
return null;
}
private IUser AuthenticateUser(string credentials)
{
try
{
// parse values
var encoding = Encoding.GetEncoding("iso-8859-1");
credentials = encoding.GetString(Convert.FromBase64String(credentials));
var credentialsArray = credentials.Split(':');
var username = credentialsArray[0];
var password = credentialsArray[1];
// authentication
var membershipService = new IMembershipService();
return membershipService.ValidateUser(username, password);
}
catch (Exception)
{
// Credentials were not formatted correctly.
return null;
}
}
}
On client side you can use HttpClient to send async request
public async Task<Address[]> GetAddresses() {
var client = new HttpClient {BaseAddress = new Uri(_settingsService.GetHost())};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var base64 = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "myuser", "test")));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",base64);
HttpResponseMessage response = await client.GetAsync("api/addresses");
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception(response.ReasonPhrase);
string content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Address[]>(content);
}
Is there a way to achieve calls with complex data types (lists of classes) as parameters?
Yes, The server application as ASP.NET or ASP.NET MVC or (preferably) ASP.NET WEB API can provide services with complex data types. In fact there is no limitation in declaring methods.
How do I secure the calls from the client? Simple http-logon?
There are wide ranage of authentication and authorization mechanism in ASP.NET (MVC, WEB API) which give you opportunity to choose one them.
The data transfers between your client and server via XML or JSON.
The "WebClient" class provides everything that you need to make a call from client to server.
More information:
http://www.codeproject.com/Articles/33798/HTTP-GET-with-NET-WebClient
How to post data to specific URL using WebClient in C#
How do I log into a site with WebClient?

How to call a WebAPI from Windows Service

I have an application written in Windows Service and this app need to make a call to a WebAPI written in Asp.Net MVC 4 WebAPi. this method in WebAPI return a DTO with primitive type, something like:
class ImportResultDTO {
public bool Success { get; set; }
public string[] Messages { get; set; }
}
and in my webapi
public ImportResultDTO Get(int clientId) {
// process.. and create the dto result.
return dto;
}
My question is, how can I call the webApi from the Windows Service? I have my URL and value of parameter, but I don't know how to call and how to deserialize the xml result to the DTO.
Thank you
You could use System.Net.Http.HttpClient. You will obviously need to edit the fake base address and request URI in the example below but this also shows a basic way to check the response status as well.
// Create an HttpClient instance
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:8888/");
// Usage
HttpResponseMessage response = client.GetAsync("api/importresults/1").Result;
if (response.IsSuccessStatusCode)
{
var dto = response.Content.ReadAsAsync<ImportResultDTO>().Result;
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
You can install this NuGet package Microsoft ASP.NET Web API Client Libraries to your Windows Service project.
Here is a simple code snippet demonstrating how to use HttpClient:
var client = new HttpClient();
var response = client.GetAsync(uriOfYourService).Result;
var content = response.Content.ReadAsAsync<ImportResultDTO>().Result;
(I'm calling .Result() here for the sake of simplicity...)
For more sample of HttpClient, please check this out: List of ASP.NET Web API and HttpClient Samples.

Categories