I need to run a validation routine looking for some header information on every request to the server. I would use OnActionExecuting in ASP.NET MVC, or an ActionInvoker, to run on every request, but I've been looking in Web API, and haven't found something specific.
If something could be implemented for both synchronous and asynchronous, that would be the best.
For Web API you should resort to MessageHandlers
Message handlers always run first, before anything else in the pipeline, and they are also able to run last (after Web API returns response, just prior to the response reaching the client).
More about message handlers can be found here - http://www.asp.net/web-api/overview/working-with-http/http-message-handlers.
And here is a simple example, validating an API key:
public class WebApiKeyHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string apikey = HttpUtility.ParseQueryString(request.RequestUri.Query).Get("apikey");
if (apikey != "something")
{
HttpResponseMessage response = request.CreateErrorResponse(HttpStatusCode.Forbidden, "You can't use the API without the key.");
throw new HttpResponseException(response);
}
else
{
return base.SendAsync(request, cancellationToken);
}
}
}
In this example only request with the key "something": i.e./api/values/?apikey=something will be allowed, all other will be rejected.
In your case, you can simply access the request.Headers and validate whatever you need.
Related
Hello I'm developing a Server-Client application that communicate with SignalR. What I have to implement is a mechanism that will allow my server to call method on client and get a result of that call. Both applications are developed with .Net Core.
My concept is, Server invokes a method on Client providing Id of that invocation, the client executes the method and in response calls the method on the Server with method result and provided Id so the Server can match the Invocation with the result.
Usage is looking like this:
var invocationResult = await Clients
.Client(connectionId)
.GetName(id)
.AwaitInvocationResult<string>(ClientInvocationHelper._invocationResults, id);
AwaitInvocationResult - is a extension method to Task
public static Task<TResultType> AwaitInvocationResult<TResultType>(this Task invoke, ConcurrentDictionary<string, object> lookupDirectory, InvocationId id)
{
return Task.Run(() =>
{
while (!ClientInvocationHelper._invocationResults.ContainsKey(id.Value)
|| ClientInvocationHelper._invocationResults[id.Value] == null)
{
Thread.Sleep(500);
}
try
{
object data;
var stingifyData = lookupDirectory[id.Value].ToString();
//First we should check if invocation response contains exception
if (IsClientInvocationException(stingifyData, out ClientInvocationException exception))
{
throw exception;
}
if (typeof(TResultType) == typeof(string))
{
data = lookupDirectory[id.Value].ToString();
}
else
{
data = JsonConvert.DeserializeObject<TResultType>(stingifyData);
}
var result = (TResultType)data;
return Task.FromResult(result);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
});
}
As you can see basically I have a dictionary where key is invocation Id and value is a result of that invocation that the client can report. In a while loop I'm checking if the result is already available for server to consume, if it is, the result is converted to specific type.
This mechanism is working pretty well but I'm observing weird behaviour that I don't understand.
If I call this method with await modifier the method in Hub that is responsible to receive a result from client is never invoked.
///This method gets called by the client to return a value of specific invocation
public Task OnInvocationResult(InvocationId invocationId, object data)
{
ClientInvocationHelper._invocationResults[invocationId.Value] = data;
return Task.CompletedTask;
}
In result the while loop of AwaitInvocationResult never ends and the Hub is blocked.
Maby someone can explain this behaviour to me so I can change my approach or improve my code.
As it was mentioned in the answer by Brennan, before ASP.NET Core 5.0 SignalR connection was only able to handle one not streaming invocation of hub method at time. And since your invocation was blocked, server wasn't able to handle next invocation.
But in this case you probably can try to handle client responses in separate hub like below.
public class InvocationResultHandlerHub : Hub
{
public Task HandleResult(int invocationId, string result)
{
InvoctionHelper.SetResult(invocationId, result);
return Task.CompletedTask;
}
}
While hub method invocation is blocked, no other hub methods can be invoked by caller connection. But since client have separate connection for each hub, he will be able to invoke methods on other hubs. Probably not the best way, because client won't be able to reach first hub until response will be posted.
Other way you can try is streaming invocations. Currently SignalR doesn't await them to handle next message, so server will handle invocations and other messages between streaming calls.
You can check this behavior here in Invoke method, invocation isn't awaited when it is stream
https://github.com/dotnet/aspnetcore/blob/c8994712d8c3c982111e4f1a09061998a81d68aa/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs#L371
So you can try to add some dummy streaming parameter that you will not use:
public async Task TriggerRequestWithResult(string resultToSend, IAsyncEnumerable<int> stream)
{
var invocationId = InvoctionHelper.ResolveInvocationId();
await Clients.Caller.SendAsync("returnProvidedString", invocationId, resultToSend);
var result = await InvoctionHelper.ActiveWaitForInvocationResult<string>(invocationId);
Debug.WriteLine(result);
}
and on the client side you will also need to create and populate this parameter:
var stringResult = document.getElementById("syncCallString").value;
var dummySubject = new signalR.Subject();
resultsConnection.invoke("TriggerRequestWithResult", stringResult, dummySubject);
dummySubject.complete();
More details: https://learn.microsoft.com/en-us/aspnet/core/signalr/streaming?view=aspnetcore-5.0
If you can use ASP.NET Core 5, you can try to use new MaximumParallelInvocationsPerClient hub option. It will allow several invocations to execute in parallel for one connection. But if your client will call too much hub methods without providing result, connection will hang.
More details: https://learn.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-5.0&tabs=dotnet
Actually, since returning values from client invocations isn't implemented by SignalR, maybe you can try to look into streams to return values into hubs?
This is supported in .NET 7 now https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-7.0#client-results
By default a client can only have one hub method running at a time on the server. This means that when you wait for a result in the first hub method, the second hub method will never run since the first hub method is blocking the processing loop.
It would be better if the OnInvocationResult method ran the logic in your AwaitInvocationResult extension and the first hub method just registers the id and calls the client.
I have 2 asp.net MVC web applications , as follow:-
ApplicationA . which is an Asp.net mvc-4 deployed under iis-8.
ApplicationB. which is an Asp.net mvc-5 deployed under iis-8.
now inside my ApplicationA i have the following method,which will call an action method (home/sync) on applicationB , as follow:-
public List<Technology> GetTechnology(int? currentfiltertype)
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "home/sync?filtertype=" + currentfiltertype;
wc.Headers.Add("Authorization", token);
string json = wc.DownloadString(url);
List<Technology> result = JsonConvert.DeserializeObject<List<Technology>>(json);
return result;
}
}
catch (Exception e){}
}
now i have noted that when the WebClient calls the action method, and the method did not receive a response within around 2 minutes it will raise a timeout exception. But since the home/sync action method on web application B needs around 30 minutes to complete.. so i was searching for a solution to extend the web-client timeout period. so i tried changing my code to use async methods as follow,mainly by replacing wc.DownloadString with wc.DownloadStringTaskAsync as follow:-
public async Task<List<Technology>> GetTechnology(int? currentfiltertype)
{
try
{
using (WebClient wc = new WebClient())
{
string url = currentURL + "home/sync?filtertype=" + currentfiltertype;
wc.Headers.Add("Authorization", token);
string json = await wc.DownloadStringTaskAsync(url);
List<Technology> result = JsonConvert.DeserializeObject<List<Technology>>(json);
return result;
}
}
catch (Exception e) {}
}
and now seems the WebClient will never expired ... i tried calling the action method and the web client keep waiting for a response for more than 20 minutes without raising any timeout exception, then it received the response from web applicationB and everything worked well..
so can anyone advice why changing my code to use async methods as shown in the above code, caused the WebClient to not timeout ?? i can not understand the relation between using async logic and extending the timeout period for the web-client (not sure if the WebClient will ever timeout inside async methods!!)?
can anyone advice why changing my code to use async methods as shown in the above code, caused the WebClient to not timeout ??
The answer is a bit convoluted: WebClient is based on WebRequest, and HttpWebRequest's Timeout property is only honored for synchronous requests.
(noy sure if the WebClient will ever timeout inside async methods!!)?
It does not directly support asynchronous timeouts, but it does support (its own kind of) cancellation, which you can trigger after a timer.
I have a asp.net web api page where the user can upload some files. I am using jquery-file-upload. Based on some condition, I want to cancel the upload from the server side but it is not working. No matter what I do, the file always goes to the server before asp.net returns the error. Example, I can keep just this when uploading:
public async Task<HttpResponseMessage> Post(int id, CancellationToken token)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Cant upload");
}
If I put a breakpoint on the return, I can see that it is hitted as soon as the upload starts but I have to wait the upload to end and only then the javascript error handler gets called. Is it not possible to end the request imediatelly, cancelling the upload?
Update 1:
I replaced jquery-file-upload with jquery-form and now I am using ajaxSubmit on my form. This doen't changed anything.
I also tried to implement a DelegatingHandler, like this:
public class TestErrorHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
//throw new HttpException(403, "You can't upload");
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
response.ReasonPhrase = "You can't upload";
return Task.FromResult<HttpResponseMessage>(response).Result;
}
}
config.MessageHandlers.Add(new TestErrorHandler());
That did not work either.
And I tried to disable buffer on requests:
public class NoBufferPolicySelector : WebHostBufferPolicySelector
{
public override bool UseBufferedInputStream(object hostContext)
{
return false;
}
}
config.Services.Replace(typeof(IHostBufferPolicySelector), new NoBufferPolicySelector());
No game - it still upload all the file before returning the error.
All I need is to cancel a upload request. Is this impossible with web api or I am missing something here?
I had a similar problem, and the only (admittedly ham-fisted) solution I could find to stop the client from uploading the data was to close the TCP connection:
var ctx = Request.Properties["MS_HttpContext"] as HttpContextBase;
if (ctx != null) ctx.Request.Abort();
This works for IIS hosting.
The HttpClient custom Client Message Handlers insert themselves in the client-side pipeline when making web api calls. This info is from this article
The author says that these message handlers go into action as the request is being sent out and also when the response is received back from the server.
I understand these client message handler's role as request is being sent out from client to the Server because the sendAsync method provides the request object. One can add custom request headers, etc.
I do not understand the usefulness of these message handlers AFTER the response is received from the server because the sendAsync method does not provide access to the response object. So, I am not sure how these client side message handlers are useful on the return journey of the request.
I am obviously missing something here.
Actually you do get access to the response message inside the message handler. For example, in the following handler, I am logging outgoing request and incoming response. Now as long as I use this instance of HttpClient, all calls made through it write traces of requests and response.
HttpClient client = new HttpClient(new LoggingHandler(new HttpClientHandler()));
public class LoggingHandler : DelegatingHandler
{
public LoggingHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine("Request:");
Console.WriteLine(request.ToString());
if (request.Content != null)
{
Console.WriteLine(await request.Content.ReadAsStringAsync());
}
Console.WriteLine();
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
Console.WriteLine("Response:");
Console.WriteLine(response.ToString());
if (response.Content != null)
{
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
Console.WriteLine();
return response;
}
}
My goal is to authenticate Web API requests using a AuthorizationFilter or DelegatingHandler. I want to look for the client id and authentication token in a few places, including the request body. At first it seemed like this would be easy, I could do something like this
var task = _message.Content.ReadAsAsync<Credentials>();
task.Wait();
if (task.Result != null)
{
// check if credentials are valid
}
The problem is that the HttpContent can only be read once. If I do this in a Handler or a Filter then the content isn't available for me in my action method. I found a few answers here on StackOverflow, like this one: Read HttpContent in WebApi controller that explain that it is intentionally this way, but they don't say WHY. This seems like a pretty severe limitation that blocks me from using any of the cool Web API content parsing code in Filters or Handlers.
Is it a technical limitation? Is it trying to keep me from doing a VERY BAD THING(tm) that I'm not seeing?
POSTMORTEM:
I took a look at the source like Filip suggested. ReadAsStreamAsync returns the internal stream and there's nothing stopping you from calling Seek if the stream supports it. In my tests if I called ReadAsAsync then did this:
message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait();
The automatic model binding process would work fine when it hit my action method. I didn't use this though, I opted for something more direct:
var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor());
var formatters = _message.GetConfiguration().Formatters;
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType);
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials;
With an extension method (I'm in .NET 4.0 with no await keyword)
public static class TaskExtensions
{
public static T WaitFor<T>(this Task<T> task)
{
task.Wait();
if (task.IsCanceled) { throw new ApplicationException(); }
if (task.IsFaulted) { throw task.Exception; }
return task.Result;
}
}
One last catch, HttpContent has a hard-coded max buffer size:
internal const int DefaultMaxBufferSize = 65536;
So if your content is going to be bigger than that you'll need to manually call LoadIntoBufferAsync with a larger size before you try to call ReadAsByteArrayAsync.
The answer you pointed to is not entirely accurate.
You can always read as string (ReadAsStringAsync)or as byte[] (ReadAsByteArrayAsync) as they buffer the request internally.
For example the dummy handler below:
public class MyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var body = await request.Content.ReadAsStringAsync();
//deserialize from string i.e. using JSON.NET
return base.SendAsync(request, cancellationToken);
}
}
Same applies to byte[]:
public class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestMessage = await request.Content.ReadAsByteArrayAsync();
//do something with requestMessage - but you will have to deserialize from byte[]
return base.SendAsync(request, cancellationToken);
}
}
Each will not cause the posted content to be null when it reaches the controller.
I'd put the clientId and the authentication key in the header rather than content.
In which way, you can read them as many times as you like!