I have created a class that inherit from DelegatingHandler and overwrite the SendAsync method. I wanted to validate the request parameters and encode them with AntiXss class before passing it to the main controller. Therefore, I created this. Now when I call the respective controller via SoapUI, I successfully get into the Async method and gets the request object.
Difficulty
I am not able to fetch the request parameters from the HTTPREQUESTMESSAGE object that I passed from the soap ui. Below is the snapshot of the request
CODE
protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
//Encode here the request object
var requestObj= request.GetQueryNameValuePairs()
.ToDictionary(kv => kv.Key, kv => kv.Value,
StringComparer.OrdinalIgnoreCase);
// work on the request
System.Diagnostics.Trace.WriteLine(request.RequestUri.ToString());
return base.SendAsync(requestObj, cancellationToken)
.ContinueWith(task =>
{
// work on the response
var response = task.Result;
response.Headers.Add("X-Dummy-Header", Guid.NewGuid().ToString());
return response;
});
}
#endregion
}
I just wanted to get the values of the parameters that I passed from the SOAP UI under the object of the HTTPRequestMessage. But not succeeded till now. Any help is appreciated.
After going through certain articles and question I finally got the solution:
var content = request.Content;
string jsonContent = content.ReadAsStringAsync().Result;
The above code worked perfectly
Related
I have a following situation which I have never seen before. I am using code below to declare a Post action.
[HttpPost]
[Route("")]
public async Task<HttpResponseMessage> Insert(InsertRequest request)
{
var body = await Request.Content.ReadAsStringAsync();
}
Now, when I am sending request to this endpoint using Postman with Content-Type = Application/Json I get some value for request and empty string for body.
If I do PostAsJsonAsync with HttpClient to this endpoint I will get null for request and request content for body.
How is that possible?
To support POST you need to add attribute [FromBody] to the request parameter.
[HttpPost]
[Route("")]
public async Task<HttpResponseMessage> Insert([FromBody] InsertRequest request)
{
var body = await Request.Content.ReadAsStringAsync();
}
I have Web api method like below. The Web API is developed inClassic .Net 4.6.2
[HttpPost]
public async Task<IEnumerable<DocumentDTO>> GetDocuments([FromBody]IEnumerable<string> documentNames)
{
return await _domainService.GetDocuments(documentNames);
}
Then I have ASP.Net Core client that was using HttpClient to post data. I have my own extension method that serializes the input using Newtonsoft.Json.JsonConvert and post it and then de-serializes the response
public static async Task<TResult> MyPostMethodAsync<TSource, TResult>(this HttpClient httpClient, TSource source, string url)
{
// serialize the input
var content = await Task.Factory.StartNew(() => JsonConvert.SerializeObject(source)).ConfigureAwait(false);
var stringContent = new StringContent(content, Encoding.UTF8, "application/json");
//post json string
var httpResponse = await httpClient.PostAsync(url, stringContent).ConfigureAwait(false);
//ensures ok response
httpResponse.EnsureSuccessStatusCode();
// get response string
var result = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
//de-serialize the response
return await Task.Factory.StartNew(() => JsonConvert.DeserializeObject<TResult>(result)).ConfigureAwait(false);
}
The method above working fine. Note that its using PostAsync method.
Then I changed the above method to make use of PostAsJsonAsync extension method that is available in Microsoft.AspNet.WebApi.Client. So the new method looks like below
public static async Task<TResult> MyPostMethodAsync<TSource, TResult>(this HttpClient httpClient, TSource source, string url)
{
// post as json
var httpResponse = await httpClient.PostAsJsonAsync<TSource>(url, source).ConfigureAwait(false);
// Ensures response is okay
httpResponse.EnsureSuccessStatusCode();
// get response string
var result = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
// de-seriazlize the response
return await Task.Factory.StartNew(() => JsonConvert.DeserializeObject<TResult>(result)).ConfigureAwait(false);
}
However the PostAsJsonAsync extension method does not post any data? The Web API Method always receive empty collection for documentnames parameter. (I'm also using my extension method to POST data to additional web api methods as well, but all POST methods receives null values or empty collection)
I am guessing its serialization/deserialization issue but I am not sure which serializer .Net 4.6.2 & .Net Core uses by default.
I want to modify the response body from the token endpoint response.
I've tried to intercept the /Token request with a MessageHandler but it doesn't work.
I'm able to add some additional informations to the response by overriding the OAuthAuthorizationServerProvider.TokenEndpointmethod, but I'm not able to create my own response body.
Is there a way to intercept the /Token request?
Edit
I found out how to remove the response body content from the token endpoint response, like this: HttpContext.Current.Response.SuppressContent = true;
It seems the right way to achieve my goal, but now when I use the context.AdditionalResponseParameters.Add() method to add my custom information, the SuppressContent block any alterations.
Now I have something like this:
// Removing the body from the token endpoint response
HttpContext.Current.Response.SuppressContent = true;
// Add custom informations
context.AdditionalResponseParameters.Add("a", "test");
To simply add new items to the JSON token response, you can use TokenEndpointResponse instead of the TokenEndpoint notification.
If you're looking for a way to completely replace the token response prepared by the OAuth2 authorization server by your own one, there's sadly no easy way to do that because OAuthAuthorizationServerHandler.InvokeTokenEndpointAsync doesn't check the OAuthTokenEndpointContext.IsRequestCompleted property after invoking the TokenEndpointResponse notification.
https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerHandler.cs
This is a known issue, but it was too late to include it in Katana 3 when I suggested to fix it.
You should give Owin.Security.OpenIdConnect.Server a try: it's an a fork of the OAuthAuthorizationServerMiddleware designed for Katana 3.0 and 4.0.
https://www.nuget.org/packages/Owin.Security.OpenIdConnect.Server/1.0.2
Of course, it includes the correct check to allow bypassing the default token request processing (this was even one of the first things I fixed when forking it).
You were almost there +Samoji #Samoji and really helped/inspired me to get the answer.
// Add custom informations
context.AdditionalResponseParameters.Add("a", "test");
// Overwrite the old content
var newToken = context.AccessToken;
context.AdditionalResponseParameters.Add("access_token", newToken);
I found it just replaced my old token with my new.
This question is similar to How to extend IdentityServer4 workflow to run custom code
So you can create custom middleware and register it before OAuth2 service in Startup:
public void Configuration(IAppBuilder app)
{
....
app.Use(ResponseBodyEditorMiddleware.EditResponse);
app.UseOAuthAuthorizationServer(...);
...
}
where custom middleware is:
public static async Task EditResponse(IOwinContext context, Func<Task> next)
{
// get the original body
var body = context.Response.Body;
// replace the original body with a memory stream
var buffer = new MemoryStream();
context.Response.Body = buffer;
// invoke the next middleware from the pipeline
await next.Invoke();
// get a body as string
var bodyString = Encoding.UTF8.GetString(buffer.GetBuffer());
// make some changes to the body
bodyString = $"The body has been replaced!{Environment.NewLine}Original body:{Environment.NewLine}{bodyString}";
// update the memory stream
var bytes = Encoding.UTF8.GetBytes(bodyString);
buffer.SetLength(0);
buffer.Write(bytes, 0, bytes.Length);
// replace the memory stream with updated body
buffer.Position = 0;
await buffer.CopyToAsync(body);
context.Response.Body = body;
}
The best way to intercept request and response is via MessageHandler if you want to avoid doing so after a request has reached the IControllerFactory handler in the pipeline - obviously in that case use a custom 'Attribute'
I have used MessageHandlers in the past to intercept request to api/token, create a new request and get the response, create a new response.
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
//create a new auth request
var authrequest = new HttpRequestMessage();
authrequest.RequestUri = new Uri(string.Format("{0}{1}", customBaseUriFromConfig, yourApiTokenPathFromConfig));
//copy headers from the request into the new authrequest
foreach(var header in request.Headers)
{
authrequest.Headers.Add(header.Key, header.Value);
}
//add authorization header for your SPA application's client and secret verification
//this to avoid adding client id and secret in your SPA
var authorizationHeader =
Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", _clientIdFromConfig, _secretKeyFromConfig)));
//copy content from original request
authrequest.Content = request.Content;
//add the authorization header to the client for api token
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(request.Headers.Authorization.Scheme, authorizationHeader);
var response = await client.PostAsync(authrequest.RequestUri, authrequest.Content, cancellationToken);
if(response.StatusCode == HttpStatusCode.OK)
{
response.Headers.Add("MyCustomHeader", "Value");
//modify other attributes on the response
}
return response;
}
This works for me perfectly. There is, however, the configuration for this handler required in the WebApiConfig.cs file (RouteConfig.cs if you're using ASP.NET MVC).
Can you elaborate on what it is that does not work for you on the handler?
For auditing purposes, I would like to store the raw request (as displayed in Fiddler) as a file when a new request comes in before I processing it. Can this be done and how? Thanks!
Yes, you can do it. Following is an example where I use a message handler to log incoming requests. This handler can be used to log any kind of request(not only the multipartform requests).
//add this handler in your config
config.MessageHandlers.Add(new LoggingMessageHandler());
// Logging message handler
public class LoggingMessageHandler : DelegatingHandler
{
private StringBuilder messageBuilder = null;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
messageBuilder = new StringBuilder();
messageBuilder.AppendFormat("{0} {1}", request.Method.Method, request.RequestUri);
messageBuilder.AppendLine();
//get request headers information
GetHeaders(request.Headers);
//get request content's headers and body
if (request.Content != null)
{
GetHeaders(request.Content.Headers);
// NOTE 1:
// ReadAsStringAsync call buffers the entire request in memory.
// So, even though you could be consuming the request's stream here, since the entire request is buffered
// in memory, you can expect the rest of the call stack to work as expected.
// NOTE 2:
// Look for performance considerations when the request size is too huge.
string body = await request.Content.ReadAsStringAsync();
messageBuilder.AppendLine();
messageBuilder.Append(body);
}
//TODO: log the message here
//logger.Log(messageBuilder.ToString())
// call the rest of the stack as usual
return await base.SendAsync(request, cancellationToken);
}
private void GetHeaders(HttpHeaders headers)
{
foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
{
messageBuilder.AppendLine(string.Format("{0}: {1}", header.Key, string.Join(",", header.Value)));
}
}
}
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!