Adding a SignalR Hub Group from an MVC Controller - c#

I have a SignalR Hub with a non-static method that adds creates a new group based on the email address entered in a form:
public class EmailHub : Hub
{
public void AddEmail(string email)
{
base.Groups.Add(base.Context.ConnectionId, email);
}
}
I would like to call this Hub method from my MVC controller. My method currently looks something like this:
public class MyController : Controller
{
public ActionResult AddEmail(string email)
{
var hub = GlobalHost.ConnectionManager.GetHubContext<EmailHub>();
hub.Clients.All.AddEmail(email);
return View();
}
}
However, the code in the controller does not call the hub method. What can I change to be able to invoke the hub method successfully?

You'd have to pass your ConnectionId as a parameter, and you can't get that until SignalR is already connected.
SignalR connections are only present for one "page view" on the client. In other words, if I go to /chat/rooms/1, I get a ConnectionId, then if I navigate to /chat/rooms/2, I get a different ConnectionId. Because of that, base.Context.ConnectionId essentially doesn't exist when you're trying to use it here.
That leaves you with two options.
Subscribe to updates after SignalR connects on each page. In this scenario, you'd file a typical AddEmail request, then in JavaScript after that View() loads, you load SignalR, connect, then file a hub.server.addEmail(email). This is a standard approach in SignalR.
This is essentially the same thing, but if you were using an SPA framework that lets you persist your SignalR connection between views, that would work. Of course, that's a pretty significant change.
I've based all of this on the assumption that your action AddEmail is actually a page, which I inferred from that it returns a ViewResult. If that's called with AJAX, you could just append the ConnectionId as a query parameter and all of this would be moot.

Related

How can I ask for something from frontend in controller using SignalR and ASP.NET Core MVC

In my manage cars controller I have an action method
ParkCar(licenseplate, latitude, longitude)
that is called after I press a button on frontend. When I press that button, an ajax request is sent with my current location and the selected car that I want to park.
When the action starts running I want to check some info about the location (ex: if there are to many cars nearby) that was sent and send and display a form on frontend with the message :
Are you sure you want to park here?
After the question was answered on frontend I want to catch that answer in my controller's action and resume it and park the car if the user wants to or to send a failed attempt if the users doesn't.
I want to obtain something like this:
public IActionResult ParkCar(string licenseplate,double lat,double lng)
{
var car = _carsService.GetCarByLicensePlate(licenseplate);
// Check how many cars are nearby (1km)
// If there are too many cars... Send a message to frotend: "Are you sure you want to park here?
// get the message from frontend
// if the message sent by user is YES
_carsService.ParkCar(car, lat, lng);
_carsService.FindAndAssignCityToCar(car);
return Ok("Car parked successfully!");
// else return failed
}
Edit: I forgot to mention that I want this strictly with SignalR.
If you'd like to implement real-time client-to-server and server-to-client communications with SignalR in your application, you can refer to this doc to create a hub and hub methods based on your actual requirement and scenario.
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-5.0#create-and-use-hubs
then you can implement SignalR JavaScript client code in your corresponding view page(s), and connect to your hub server and invoke the hub method(s) in your code logic.
https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-5.0
How can I ask for something from frontend in controller using SignalR and ASP.NET Core MVC
Based on the code of your ParkCar action method, we can find that your call _carsService methods to do business logic, so you can inject same service in your hub method and perform same code logic in your hub method(s).

How can I specify when creating a web request that I want to receive certain fields from [FromBody]

I am creating a web service for user authentication. (I am new in c# using entity framework core)
[HttpPost, Route("login")]
public async Task<ActionResult<Usuario>> Login([FromBody] User user)
{
}
my model User has the next fields:
{
name,
password,
phone,
email
}
I don't know how to specify so that from the client side, my users can see the fields that my web service needs to receive and when I use some plugin to document my API it can be clearly seen that I ONLY need the name to be sent and password only.
I think of something like that, I hope to make myself understand:
public async Task<ActionResult<Usuario>> Login([FromBody] string email, [FromBody] string password)
so in this way when the API is documented, I would like it to be understood that you need to send email and password only
and so from the client side
{"password": "212346", "email": "myemail#hotmail.com" }
is sent
Your view model should contain ONLY the fields each API method requires. If there are fields in your request that are not required, they should not be in the method body. If you use something like Swagger to document your API, then it will show just the fields required for each method.
Generally, I hear questions like this when the developer tries to use a DTO or even a database entity as a view model (both of which are incorrect uses).
I make sure each API method has a different view model (even if the contents are identical), because most of the time, eventually they will be different, but not always at the start.

Retrieving SignalR connectionId on Web API calls

I have the following constellation:
I am using a asp.net core web api project which also includes a HubContext. The first thing a user has to do is to make an api call to my UsersController : BaseController. Here he/she calls the api/login route passing the according credentials. The return value of the Login() function is a JwtBearerToken, which the user from then on uses for all other api calls.
Once the token has been issued, the user (Client) establishes a SignalR connection over my ConnectionHub : Hub.
So far everything works well, the user gets authenticated using the token when calling api methods and I also can track the according Session state inside the scope of my ConnectionHub.
Now I have to retrieve the users (SignalR) session id whenever he/she makes an api call.
i. e. When the user calls a method in my UsersController I want to do something like this:
[HttpGet]
public ActionResult<List<User>> GetAll()
{
// Here I want to retrieve the (SignalR) session id of the user calling this method.
return Ok( userRepository.GetAllUsers() );
}
So far the only idea I have is to make the user send his SignalR-SessionId with the according api call, but what I'd like to achieve is to read the Id on the server side. How can I achieve this?
According to the Microsoft documentation, it is not possible to get the user's connectionId outside the hub (e.g. in a controller):
When hub methods are called from outside of the Hub class, there's no caller associated with the invocation. Therefore, there's no access to the ConnectionId, Caller, and Others properties.
However, you can get the users with JavaScript by calling a hub method. Here you have access to the connectionId and to the database using your repository (make sure it is available via Dependency Injection).
I don't know what you want to do with the user's exactly, but you can simply return the users in the hub method and do something with the connectionId.
YourHubClass.cs
public Task GetAllUsers()
{
// Get the ConnectionId
var connectionId = Context.ConnectionId;
// Get the users list
var users = userRepository.GetAllUsers();
// ...
return Clients.User(user).SendAsync("UserListRequested", users);
}

401-Unauthorized when calling UmbracoAuthorizedApiController from backoffice

Problem
When I change my controller to inherit from UmbracoAuthorizedApiController instead of UmbracoApiController I will get 401-Unauthorized and I will be redirected to loging page.
Mode Details
I want to call some of my backend Api's from the back-office and to do that I've followed the article in our.umbraco.
First I've implemented a controller inheriting from UmbracoApiController to be able to call my services from postman. Everything went fine and I could call my code and read data from Umbraco:
[RoutePrefix("api/admins")]
public class AdminsController : UmbracoApiController
{
[HttpGet]
[Route("getdata")]
public DataViewModel GetData(string id)
{
....
}
}
Then I've called my service from JavaScript in Dashboard using the plugins
$http.get(vm.baseUrl + '/getdata?id=' + id, {})
.then(function (response) {....}
Everything works fine, I can see that my cookies (containing token) has been sent in the request headers.
Then I've updated my controller to inherit from UmbracoAuthorizedApiController and now I don't have access to my Apis.
The controller is now like this:
[RoutePrefix("api/admins")]
public class AdminsController : UmbracoAuthorizedApiController
What did I do wrong?
Authorized controllers (same as other wrapped MVC controllers in Umbraco) are automatically routed. Backoffice authorisation will work when /umbraco/backoffice/ path will be present in the route.
Check: https://our.umbraco.org/documentation/reference/routing/Authorized/
and: https://our.umbraco.org/documentation/reference/routing/webapi/authorization
It's directly said:
In order for Umbraco to authentication a request for the back office,
the routing needs to be specific. Any URL that routes to :
/umbraco/backoffice/*
will be authenticated. If you have a controller
that is not routed within the prefix, it will not be authenticated for
back office use.

Redirect from one api controller method to another, and reapply custom authorize filters

In WebApi I have a controller action that I want to be able to redirect to another ApiController action. I decorate these methods with custom AuthorizeAttribute (CustomAuthorizaton) properties so it's imperative that any redirect passes through these incoming security filters.
Here's an example:
public class SomeController : ApiController
{
[CustomAuthorization("Foo")]
[System.Web.Http.HttpGet]
public CustomResponse SomeMethod(int arg1, int arg2)
{
....
}
}
public class AnotherController : ApiController
{
[CustomAuthorization("Bar")]
[System.Web.Http.HttpGet]
public CustomResponse AnotherMethod(int arg1, int arg2)
{
if(arg1 == 2){
return Redirect to SomeMethod(...) in SomeController ???
}
}
}
You'll notice I also return my own custom response object (CustomResponse in this example).
So what I need help with is how can I return the CustomResponse that results from a redirect, and have that redirect pass through the CustomAuthorization filter.
To clarify, calling AnotherMethod only requires the "Bar" permission, but during a redirect to SomeMethod we need to verify that the caller has the "Foo" permission also.
It would be a bit of a security hole to just perform the redirect/transfer without checking the caller is authorized correctly to perform the call, particularly as in this case it requires different permissions.
Any help is much appreciated.
Many thanks.
To emulate a redirect without paying the cost of a network round trip you could create an in memory server that has the same configuration as your web API and use a HTTPClient instance to call it.
You do something like this to setup the in-memory server,
var httpConfiguration = new HttpConfiguration();
WebApiConfig.Register(httpConfiguration)
var httpServer = new HttpServer(httpConfiguration);
and then you can make calls to it like this
var httpClient = new HttpClient(httpServer);
httpClient.GetAsync("http://mysite.com/mycontroller/redirectUrl");
It would be good to store these objects globally somewhere so that you are not recreating them everytime. HttpClient is threadsafe so there are no worries about re-using it.
This approach ensures that your requests go through the exact same steps as any real network requests (except for the ASP.NET pipeline).
So in case you want to pass data between two get actions you can serialize the object you want to pass - add it to routes and thed do a redirect like
return RedirectToAction(ActionName,ControllerName, new { a = Seriliaze(someObject)})
then you will be able to deserialize it on the other action and pass to your custom responce. But there will be limitation of url max length. Or you can try to use a TempData (at least it works nice for Post-Redirect-Get pattern when you need to pass some error etc.)

Categories

Resources