I have some logging middleware to get request body in aspnet API that was working fine for dotnet5. But since I upgraded to dotnet6, I noticed that HttpContext.Request.Body has zero length.
My code so far:
context.Request.EnableBuffering();
await using var tmpStream = RecyclableMemoryStreamManager.GetStream();
await context.Request.Body.CopyToAsync(tmpStream); // context.Request.Body.Length is 0
Is there some way to get request body in dotnet6?
I read there's a Microsoft middleware to log request, but I would prefer to use our implementation, which have custom features.
Related
I have added the following to my service configuration.
services.AddCors(options
=> options.AddDefaultPolicy(builder
=> builder
//.AllowAnyHeader()
.WithHeaders("header-that-nobody-knows")
.WithOrigins("http://localhost:44304")));
My expectation was that the calls would bounce (as I don't add header-that-nobody-knows to my headers). However, the request is carried out just as if AllowAnyHeader() was set.
Manipulating the port, domain or protocol in WithOrigins() produces the expected result, so the config seems to be wired up properly. I suspect it's a special case somehow because I'm getting unexpected behavior with WithMetod() when it comes to GET and POST (while other methods are blocked/allowed depending on the paramers passed).
Checking MSDN gave nothing I recon as explanation.
I doubt that it matters but for completeness sake, here's the Angular code invoking the call.
let url = "https://localhost:44301/security/corstest?input=blobb";
this.http.get<any>(url).subscribe(
next => console.log("next", next),
err => console.warn("err", err));
The action method looks as below.
[HttpGet("corstest")]
public IActionResult CorsTest([FromQuery] string input)
{
return Ok(new { data = input + " indeed..." });
}
When you try to send a request to a cross-origin URL with a "non-standard" header,
the browser will perform a preflight OPTIONS request with the Access-Control-Request-Headers header that contains the non-standard headers.
OPTIONS /corstest
Access-Control-Request-Method: GET
Access-Control-Request-Headers: header-that-nobody-knows
Origin: https://my.api
ASP.NET Core inspects this value and checks if the CORS policy has AllowAnyHeader or if it explicitly allows it with .WithHeaders, if not it will issue a non-200 response and the browser will refuse to send the actual request.
So, not adding header-that-nobody-knows to request headers doesn't mean ASP.NET Core will refuse to serve the request, it means if you set header-that-nobody-knows header in a cross-origin request, it will allow it instead of issuing a non-200 response (assuming you allowed it with WithHeaders or AllowAllHeaders)
So in a nutshell:
You have to allow some/all origins + some/all headers at minimum for a CORS policy to take effect.
Browser expects both Access-Control-Allow-Headers and Access-Control-Allow-Origin in the preflight request to match the main request.
You can only send a subset (which includes 0) of the allowed headers.
References
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests
I want to customize the request processing flow myself. I don't want to use the Controller under Asp.net core to process the request. But there is a premise that there must be a class method or a delegate (containing Request and response formal parameters), where all requests are processed
For response processing, I hope to use Asp.net core's default response processing method (if it can be done), such as Ajax requests, dynamic page output, response pictures, file downloads, etc.
Envisioned codeļ¼
var handler=HTTP.handler((req, res) => {
if(req.getHeader("x-requested-with")){
if(req.para("username")==null){
res.endError(403)
}else{
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World');
}
}else{
res.view('/test/index.cshtml');
}
});
You want to write custom middleware. It can be registered in Startup to be called as a part of request processing and will be called for all requests.
There is a HttpContext object so you can get access to the current request.
The docs have some good examples that you can modify.
Custom middleware documentation
I am so confused about when IIS sends back the response, I really want to know how this works under the hood since I am doing response logging and I don't want to corrupt the body.
This is my logging middleware Invoke which is called before the MVC middleware (controllers). Is the response sent IMMEDIATELY after controller returns its Ok method or does it wait for the rest of the middleware? Is there any chance of corrupting the response if I do logging this way?
public async Task Invoke(HttpContext context)
{
var requestResponseLog = new RequestResponseLog
{
RequestTime = DateTimeOffset.UtcNow,
Request = await FormatRequest(context)
};
Stream originalBody = context.Response.Body;
using (MemoryStream newResponseBody = _recyclableMemoryStreamManager.GetStream())
{
context.Response.Body = newResponseBody;
await _next(context);
newResponseBody.Seek(0, SeekOrigin.Begin);
await newResponseBody.CopyToAsync(originalBody);
newResponseBody.Seek(0, SeekOrigin.Begin);
requestResponseLog.ResponseTime = DateTimeOffset.UtcNow;
requestResponseLog.Response = await FormatResponse(context, newResponseBody);
_requestResponseHandler(requestResponseLog);
}
}
Think of each registered middleware as a step along the path of creating a request. Your invoke method is "your" step on that path. The await _next(context); instructs the framework to move onto the next middleware in the pipeline at that point - but - come back after the rest of the middlewares have executed, so that your code can be picked back up again.
So.. With that in mind, lets assume we have 3 middlewares set up. The pipeline works like this:
[REQUEST COMES IN]
Middleware-1 => Middleware-2 => Middleware-3
[THE RESPONSE IS PRODUCED]
Middleware-3 => Middleware-2 => Middleware-1
[RESPONSE GOES OUT TO CALLER]
Lets say you added await _next(context) into Middleware2's Invoke method. When the code reaches that point, it skips onto the next middleware in the pipeline, but it knows to stop there when it comes back through with the response. So you can intercept it again.
If you are logging a request, your custom code would go before that await _next(context) call. If you are logging a response, it would come after that call.
Note: Additionally, middleware ordering is determined by the order they are registered in your Startup class.
There is a authentication library that I have to use that helpfully does things like
Response.Redirect(url, false);
inside of it's method calls. I can't change this libraries code and it's fine for MVC style apps but in angular SPA -> WebApi apps this is just awful.
I really need a 401 otherwise I get into trouble with CORS when my angular scripts, using $http, try to call out to the auth server on another domain in response to the 302, that's if it even could as the Response.Redirect also sends down the object moved html and the angle brackets cause an error to be thrown.
Since I have to make the call to the auth library first the Response.Redirect is already in the response pipeline and so I need to clean it up to remove the body content and convert the 302 into a 401. I thought I could just:
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent("data");
}
but this just gets appended to the response and doesn't replace it plus I also need the Location: header which I can't seem to access via WebApi methods.
So instead I've had to do this in my ApiController:
var ctxw = this.Request.Properties["MS_HtpContext"] as HttpContextWrapper;
var ctx = ctxw.ApplicationInstance.Context;
var url = ctx.Response.RedirectLocation;
ctx.Response.ClearContent();
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent(url);
}
But this seems terrible and counter to webapi "feel". Plus I'm tied to the controller in doing this. I can't get the wrapper in a MessageHandler for example.
What I'd like to do is monitor the response for a given route in a message handler or in an AuthorizationFilterAttribute, if its a 302, I want to read it's headers, take what I want, wipe it and replace it with my own "fresh" response as a 401. How can I do this?
You might want to write your own ActionFilter and override its OnActionExecuted method where you can access HttpActionExecutedContext. From there, you can check response code, for example, and overwrite response with whatever you want.
Ref: https://msdn.microsoft.com/en-us/library/system.web.http.filters.actionfilterattribute.onactionexecuted%28v=vs.118%29.aspx#M:System.Web.Http.Filters.ActionFilterAttribute.OnActionExecuted%28System.Web.Http.Filters.HttpActionExecutedContext%29
Since ASP.NET Web API RC I was using some approach based on declaring void API controller's operations.
I was customizing a response object DTO (instead of using HttpResponseMessage) using AOP and PostSharp, and finally this was sent to the client using HttpContext.Response.Write(...) serializing the DTO into a JSON string.
When I upgraded my solution to ASP.NET Web API RTM, this approach didn't work anymore.
Whenever I send a response from the Web API and I receive it in the client-side, I find that the response is sent with a 204 status (NoContent) while I was setting a 200 status (OK) for the response itself.
Because this approach was working in the RC version of WebAPI I suspect that's an unknown breaking change when WebAPI development team transitioned to RTM version.
Am I wrong?
As far as I know, since RTM if a POST action does not return an HttpResponseMessage the default status code is 204 (and not 200 as was back in RC). There are two things, I know, we can do to keep clients from complaining about 204.
a) Change the response message from within your action:
[HttpPost]
public HttpResponseMessage DoWork(MyModel model)
{
// Do work
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new ObjectContent<MyModel>(model, FormatterConfig.JsonFormatter) };
}
b) Change the response in a DelegatingHandler (dirty by generic way)
public class ResponseHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = base.SendAsync(request, cancellationToken);
response.Result.StatusCode = response.Result.IsSuccessStatusCode ? System.Net.HttpStatusCode.OK : response.Result.StatusCode;
return response;
}
}
I am not aware of such breaking change but I can confirm that this doesn't work in the RTM. Anyway, that's such a wrong approach of using the Web API that it's probably a good thing that it doesn't work. You are killing the whole point of the Web API if you are going to manually write the response to the client. If you have some existing code that does this that you cannot modify then I would recommend you using a Generic ASHX handler until you are ready to upgrade.