C# Await till object is in list, for controller - c#

Hi i currently have a web api system where i send a command to a Machine at another location. Id like to wait for the response from the system to be available to send back to the requester. Currently when a request is made i generate a UUID and once data comes back from the other location it matches with that UUID and adds it to a response list. Is there any way i can await that a list contains x
If not is there any other way that i might be able to transfer this object from the very distant class and thread to the controller for the response to be sent back?
Code from webrequest
[HttpGet]
[Route("Areas")]
public async Task<IHttpActionResult> GetArea(GetAreas getAreas)
{
OutScaleManager.LogRequest("Request to getAreas with ip: " + getAreas.IP + " i " + getAreas.startArea + " i " + getAreas.endArea);
foreach (NoxApiService noxService in OutScaleManager.nUtil.connectedInstance)
{
if (noxService.param.IPAddress == getAreas.IP)
{
noxService.GetAreas(getAreas.startArea, getAreas.endArea);
OutScaleManager.LogRequest("getAreas Sent, now moving to queue checks");
string UUID = OutScaleManager.nRAR.NoxRequestHandler(getAreas, RequestType.GETAREAS);
if (OutScaleManager.nRAR.GetAreasResponseSend)
{
await OutScaleManager.nRAR.ResponseList.ContainsKey(UUID);
}
//if (OutScaleManager.nRAR.GetAreasResponseSend) { return Ok(OutScaleManager.nRAR.NoxRequestHandler(getAreas, RequestType.GETAREAS)); }
}
}
OutScaleManager.LogRequest("This request was not sent, please create an instance and connected through the propper channels.");
return Ok("This request was not sent, please create an instance and connected through the propper channels.");
}

Hi i currently have a web api system where i send a command to a Machine at another location. Id like to wait for the response from the system to be available to send back to the requester. Currently when a request is made i generate a UUID and once data comes back from the other location it matches with that UUID and adds it to a response list.
Instead of using a list of available responses, structure your request/response system as a dictionary of outstanding requests. The dictionary would map GUIDs to TaskCompletionSource<T> where the T is the type of response data. For example, if your response data was TResponse, it could look like this:
private ConcurrentDictionary<Guid, TaskCompletionSource<TResponse>> _requests = new();
public Task<TResponse> GetAreasAsync(GetAreas getAreas)
{
var guid = OutScaleManager.nRAR.NoxRequestHandler(getAreas, RequestType.GETAREAS);
TaskCompletionSource<TResponse> tcs = new();
_requests.TryAdd(guid, tcs);
return tcs.Task;
}
// Called when a GetAreas request completes:
public void SaveAreasResponse(Guid requestId, TResponse data)
{
if (!_requests.TryRemove(requestId, out var tcs))
throw new InvalidOperationException("Response received for unknown request GUID.");
tcs.TrySetResult(data);
}

Related

How can I send response from a controller method without returning from the method itself

I have created a method in my ASP.net core controller which is sending response back on returning.
How I can send multiple responses back without returning from the method.
[HttpGet]
[Route("{id:guid}")]
public async Task<IActionResult> GetWalkByIdAsync(Guid id)
{
try
{
var walk = await walkRepository.GetWalkByIdAsync(id);
if(walk == null)
{
return NotFound();
}
var walkResponse = mapper.Map<WalkResponse>(walk);
//////I want to send some additional responses back from here in the function//////
return Ok(walkResponse);
}
catch
{
return StatusCode(StatusCodes.Status500InternalServerError, "Sorry server down");
}
}
I am expecting to send additional responses before line "return Ok(walkResponse)"
You can not send multiple returns or responses at once. Instead, Package your target responses as one object package then perform necessary steps at response receiving end to process your responses accordingly.

How to map a SignalR hub to a path with an argument

I'm building a backend with Asp.Net Core & SignalR. The data are not themself located on the Asp.Net Core, but must be requested to some other devices, that will send the data to the Asp.Net Core server, and then the server must give them back to the client through the web socket.
I would like to react to the OnConnectedAsync to request my devices to send periodically some data to Asp.Net Core.
But this means that my hub must be able to know which data I need to be querying.
So I would like to have a Hub responding to the url /api/data/{id} and in my Hub, be able to know which ID has been requested.
app.MapHub<NotificationsHub>("/api/data/{id}");
The id identify a big group of data(that is requested and forwared as bulk), and multiple clients will request the same ID.
I can't find in the doc:
If this is possible
How should I specify the ID parameter?
How do I retrieve this ID in the hub?
If anybody has some pointer, it would be helpful, thank you very much
You can pass parameters as query string parameters. Pass the id from the connection url:
_hubConnection = new HubConnectionBuilder()
.WithUrl("/api/data?id=123")
.Build();
Then read it inside hub:
public override async Task OnConnectedAsync()
{
string id = Context.GetHttpContext().Request.Query["id"];
var connectionId = Context.ConnectionId;
await Groups.AddToGroupAsync(connectionId, id);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
string id = Context.GetHttpContext().Request.Query["id"];
var connectionId = Context.ConnectionId
await Groups.RemoveFromGroupAsync(connectionId, id);
await base.OnDisconnectedAsync(exception);
}
Send data:
_hubContext.Group("123").SomeMethod(dataBatch);
Map hub:
app.MapHub<NotificationsHub>("/api/data")

SignalR call to specific client from controller ASP.NET Core 2.1

I need to send an instant message from the server to the client after the user has submitted a form in a browser.
I followed the Microsoft steps here to set up a signalR connection, created a Hub class, signalr.js etc.
The problem is that I can only invoke a message to all clients, but I need to invoke the message to the specific caller who initiated the request (otherwise everyone will get the message).
This is my POST Action in the HomeController.cs:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
{
//Invoke signal to all clients sending a message to initSignal WORKS FINE
await _signalHubContext.Clients.All.SendAsync("initSignal", "This is a message from the server!");
//Invoke signal to specified client where signalRConnectionId = connection.id DOES NOT WORK
await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);
return RedirectToAction("Success", inputs);
}
my client javascript file:
//Create connection and start it
const connection = new signalR.HubConnectionBuilder()
.withUrl("/signalHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start().catch(err => console.error(err.toString()));
console.log("connectionID: " + connection.id);
$("#signalRconnectionId").attr("value", connection.id);
//Signal method invoked from server
connection.on("initSignal", (message) => {
console.log("We got signal! and the message is: " + message);
});
I have tried debugging the action method and I get correctly passed in the connectionId which is "0" (incrementing by 1 per connection)
So here's the final solution I came up with inspired by the answer from this thread
I got the connectionId by calling the Hub class from client and then from client calling the controller passing in the connectionId.
Hub class:
public class SignalHub : Hub
{
public string GetConnectionId()
{
return Context.ConnectionId;
}
}
client-side javascript code executed at startup:
connection.start().catch(err => console.error(err.toString())).then(function(){
connection.invoke('getConnectionId')
.then(function (connectionId) {
// Send the connectionId to controller
console.log("connectionID: " + connectionId);
$("#signalRconnectionId").attr("value", connectionId);
});
});
HomeController.cs:
public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
{
//Invoke signal to specified client WORKS NOW
await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);
return RedirectToAction("Success", inputs);
}
It works fine, but still feels a little like a roundtrip, it would have been easier if we didn't have to go through the hub class to make this happen. Maybe just having the connectionId from the client-side to begin with, but maybe there is a good reason for the design :)
According to Microsoft, you can not access to the ConnectionId and Caller from outside a hub
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1
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.

Getting POST data from WebAPI

We're working on developing an application that uses Plivo for sending and receiving SMS messages. For every request that Plivo sends, they also send a signature in the HTTP header so that we can verify the request came from Plivo and not from a random user.
https://www.plivo.com/docs/xml/request/#validation
To do this validation, we require the POST content as a query string (eg: To=15555555555&From=11234567890&TotalRate=0&Units=1&Text=Text!&TotalAmount=0&Type=sms&MessageUUID=2be622bc-79f8-11e6-8dc0-06435fceaad7).
Current solution
This is what we have so far:
private bool VerifyPlivo(object thing, HttpRequestMessage Request)
{
if (Request.Headers.Contains("X-Plivo-Signature"))
{
Dictionary<string, string> reqParams = (from x in thing.GetType().GetProperties() select x).ToDictionary(x => x.Name, x => (x.GetGetMethod().Invoke(thing, null) == null ? "" : x.GetGetMethod().Invoke(thing, null).ToString()));
IEnumerable<string> headerValues = Request.Headers.GetValues("X-Plivo-Signature");
string signature = headerValues.FirstOrDefault();
return XPlivoSignature.Verify(Request.RequestUri.ToString(), reqParams, signature, plivoToken);
}
else
{
return false;
}
}
[Route("RecieveSMS")]
[HttpPost]
public HttpResponseMessage RecieveSMS(PlivoRecieveSMS req)
{
if (!VerifyPlivo(req, Request))
{
return new HttpResponseMessage(HttpStatusCode.Forbidden);
}
... // do actual work here
}
This works by using the object that it maps to PlivoRecieveSMS and doing some reflection to get the properties and values, and sticking them in a Dictionary. This works well especially given our lack of the preferred solution...
Preferred solution
Right now, we require a model (PlivoRecieveSMS) to map the data, and then do introspection to find the key/values. We would like to move the logic to an extension of System.Web.Http.AuthorizeAttribute, so that we can do something as simple as:
[AuthorizedPlivoApi]
[Route("RecieveSMS")]
[HttpPost]
public HttpResponseMessage RecieveSMS(PlivoRecieveSMS req)
{
... // do actual work here
}
The actual authorization is done in AuthorizedPlivoApi - if it's not valid, the request never reaches the controller. But we cannot do this at the moment because we can't map it to a specific object inside of AuthorizedPlivoApi.
I would like to access the POST key's / values directly, or perhaps map it to a dynamic object that isn't pre-defined before hand. If I can do that, we can then achieve our preferred solution.
tl;dr: is there any way to push application/x-www-form-urlencoded data from a POST request into a Dictionary<string,string>() without using a specific model?

Multiple call using Web Api Service

I am using Web Api Service to Pass the Data to my Mobile Devices.
There can be 2 scenario for this.
Single Device Requesting Multiple Times
Multiple Device Requesting Multiple Times
In Both the Scenario i am not able to handle multiple Request, I don't know what is the actual problem but it's keep giving me the 403 Response and 500 Response.
I need to handle Multiple request within seconds as I am Dealing with more then 1000 Devices at the same time and side by side i also need to respond them within seconds because we don't want that our devices wait for the Response for more then few Seconds.
Currently I am using Azure platform for the Web Api services and working with MVC 4.0 Using LINQ. If you want my code then i will provide you the Code (I am using repository Pattern in my Project)
Code
Controller :
[HttpPost]
public JObject GetData(dynamic data)
{
JObject p = new JObject();
try
{
MyModelWithObjectInData transactionbatchData = JsonConvert.DeserializeObject<MyModelWithObjectInData>(data.ToString());
return _batchDataService.batchAllDataResponse(transactionbatchData);//Call Repository
}
}
Repository :
public JObject batchAllDataResponse(MyModelWithObjectInData oData)
{
JObject p = new JObject();
var serviceResponseModel = new MyModelWithObjectInData();
using (var transaction = new TransactionScope())
{
//Insert in Tables
}
//Select Inserted Records
var batchData = (from p in datacontext.Table1
where p.PK == oData.ID select p).FirstOrDefault();
if (batchData != null)
{
serviceResponseModel.GetBatchDataModel.Add(new BatchDataModel //List Residing in MyModelWithObjectInData Class File
{
//add values to list
});
}
//Performing 3 Operations to Add Data in Different List (All 3 are Selecting the Values from Different Tables) as i need to Give Response with 3 Different List.
return p = JObject.FromObject(new
{
batchData = serviceResponseModel.GetBatchDataModel,
otherdata1 = serviceResponseModel.otherdata1, //list Residing in my MyModelWithObjectInData Model
otherdata2 = serviceResponseModel.otherdata2 //list Residing in my MyModelWithObjectInData Model
});
I have used below code to track all the request coming through the Service but i am getting error within this.
//here i am getting error
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//var content = request.Content.ReadAsStringAsync().Result;
ServiceTypes myObjs = JsonConvert.DeserializeObject<ServiceTypes>(request.Content.ReadAsStringAsync().Result);
bool result = _serviceLogService.InsertLogs(request, myObjs); //Getting Error while inserting data here on multiple request
return base.SendAsync(request, cancellationToken).ContinueWith((task) =>
{
HttpResponseMessage response = task.Result;
return response;
});
}
Any Help in this would be Much Appreciated.
Providing a code snippet of your data calls from your api service would be most handy. Their are a few ways to handle concurrent queries; if you have not explored it yet the async and await method would be best to leverage in this scenario. But this is from a vague stand point I would need to look at your code to provide you a definitive answer.
Additional information "If you're new to asynchronous programming or do not understand how an async method uses the await keyword to do potentially long-running work without blocking the caller’s thread, you should read the introduction in Asynchronous Programming with Async and Await (C# and Visual Basic)."
Hopefully, this information puts you on the right track.
Best.
I found the Solution using Async Method to Handled Recurring Request of Services
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//Logic to Track all the Request
var response = await base.SendAsync(request, cancellationToken);
return response;
}
My Controller in not able to initiate the context file as request it's already initiated and "concurrent requests" that dead locking my objects because i am getting multiple request from the devices so that i have modified he code to Logs all the Service within SendAsync Method and await helps me to wait until the task completed.

Categories

Resources