Reading the response body in owin middleware - c#

I'm trying to get a copy of a response after my MVC controller action has executed but from other questions on here I can't get this code working (even though this appeared to be a well answered problem) ...
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(async (context, next) =>
{
var resultStream = context.Response.Body;
context.Response.Body = new MemoryStream();
// allow the response to be written in future request lifecycle events
await next.Invoke();
// fetch the repsonse
context.Response.Body.Seek(0, SeekOrigin.Begin);
var headers = context.Response.Headers;
var body = new StreamReader(context.Response.Body).ReadToEnd();
// ... other code omitted for question clarity
// write the response to the client
context.Response.Body.Seek(0, SeekOrigin.Begin);
await context.Response.Body.CopyToAsync(resultStream);
context.Response.Body = resultStream;
});
}
// ... other code omitted for question clarity
}
When I get to the second seek the variable "body" is empty.
Any ideas why this may be the case when the result after this is a page with content ?

So it turns out the problem here is to do with the fact that Owin and asp.net are not intertwined in the full lifecycle together as I had thought.
In short, the request lifecycle looks something like ...
Do Owin stuff (all owin middlewares)
Do MVC stuff
Do server stuff
... what I need is ...
Do owin stuff
Do MVC stuff
Do more Owin stuff
Do server stuff
... of course i'm hugely over simplifying the process here but i guess the short way to explain it is when you do ...
app.Use((context, next) => { ... }).UsestageMarker(?);
... there is no stage marker for "response processing complete".
Interestingly aspnet core gives us much more control as the tight integration with all the pieces throughout the request lifecycle are less dependent on predefined stages and more about you the user defining your own process for handling requests and constructing the response.
In short ... in aspnet core I can do what i'm trying to do but this does not appear to be possible in owin with .net 4.6
I did however a few references to using filters and handling "OnActionExectuted" which if you look you have a completely different Request and Response object than those given to you by owin middleware's pipeline (thus adding more evidence that these things are not in fact one single process but simply two that happen in an order).
I have since spent the time looking at migrating my application to aspnet core ... which is proving to be more of a headache than i had anticipated, but i'm ever hopeful of the final result being cleaner and faster.

Related

ASP.NET - How To Return Both A Web Response And A View?

I've got a servers controller with an action along the lines of ...
[ApiController]
[Route("[controller]")]
[Consumes("application/json")]
public class ServersController : Controller
{
public ServersController(IMemoryCache memoryCache) => Cache = memoryCache;
private IMemoryCache Cache { get; }
[HttpGet(Name = "Get Servers")]
public IActionResult GetServers()
=> Ok(Cache.Get<List<ServerGetDTO>>(Context.ServerList));
}
... and I've also got a razor page along the lines of ...
#page "/servers"
<PageTitle>Get Servers</PageTitle>
<h1>Gets The Servers (Only Via Response, For Now)</h1>
I'm adding the razor/blazor stuff ...
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
app.MapBlazorHub();
... but when I navigate to https://.../servers I'm not getting the JSON response that I'm expecting. The expectation is that navigating to the page issues a Get request to the endpoint and then I would both get the view to render on the front-end and also the list of servers as JSON as the response for the Get call.
What am I doing wrong?
UPDATE #1: After having added Blazor on top of my API, I've noticed now that Swagger returns HTML when calling the https://.../servers endpoint, so the HTML document and the JSON response seem to be mutually-exclussive. I'm almost sure I'm missing something, I'll keep investigating.
UPDATE #2: OK, I've figured it out. Now that I know what the problem was, this question, in hindsight, feels silly. This is my first Blazor experiment, and now I know that I've started with a misunderstanding of the technology at an architectural level. Basically, I've bolted onto my already existing API a Blazor Server project, whereas, in fact I should have used a Blazor WASM project instead. Now that I've replaced Blazor Server with Blazor WASM, everything works exactly as expected!
If you want to bolt on a front-end to an already existing API, make sure you're adding Blazor WASM, not Blazor Server.

Custom middleware exception handler response content not passed

I'm having a strange problem with ASP.Net Core custom middleware.
I've got an exception handler that I'm trying to log exceptions and then send a generic message to the caller without the exception details that seem to be sent by default.
The problem is that the response is being sent without a body.
My code:
public async Task Invoke(HttpContext context)
{
try
{
await next.Invoke(context);
}
catch (Exception e)
{
logger.LogError(e, "Request threw an exception");
context.Response.StatusCode = 500;
using (var writer = new StreamWriter(context.Response.Body))
{
await writer.WriteAsync($"Error on server processing request");
await writer.FlushAsync();
}
}
}
I'm using .Net 5. This project is a Web API project, in case that makes any difference.
I'm invoking my API via swagger UI and I'm also checking the responses using the dev tools in Edge (Chromium version) to make sure that swagger isn't hiding the body.
It appears that there is no body to the response, it's just an empty response with a 500 code.
Running in the debugger shows that it is executing the code and if I change the response code to 566 that response code is received by SwaggerUI so it's doing something.
Note: before I added the FlushAsync() call, I was getting an exception sent to Swagger saying that the dispose was using a synchronous write when flushing so that seems to be necessary.
Update:
Pipeline configuration, as requested:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment() || env.IsTesting())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Clients v1"));
}
app.UseRequestLoggingMiddleware(); // My custom middleware
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Behaviour is the same regardless of the environment (development, testing, staging or production)
I'm uncertain why using context.Response.Body is not working, but what I've done instead is use the methods directly on context.Response:
context.Response.StatusCode = 500;
await context.Response.WriteAsync($"Error on server processing request");
Update: Given your middleware pipeline starts with app.UseDeveloperExceptionPage() I suspect that's what's overwriting the response - at least if you're running with dev or test as environment. This is the middleware that actually exposes the full exception details you are saying you're trying to avoid. In the ASP.NET Core project boilerplate/template this is intentionally only added when not running on a production environment.
So perhaps your problem will be solved by changing the ASPNETCORE_ENVIRONMENT environment variable to something other than Development or Test. Or if you still want your own middleware, you should probably remove app.UseDeveloperExceptionPage() and perhaps even move your own app.UseRequestLoggingMiddleware() up as the first line in Configure (although I don't think the Swagger stuff should interfere - but I make no promises :) )
I found the problem. The code I needed was
context.Response.StatusCode = 500;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync($"Error on server processing request");
I noticed that if I requested the API by typing the url into the web browser, I was getting the text back but when I requested via Swagger UI, it was not sending the text.
SwaggerUI was setting an accept: text/plain header with the request so ASP.Net was ignoring any content that wasn't set as this type (the ContentType is null by default).
The browser had */* in its accept header so the content was being sent regardless of type.

How to log the selected ASP.NET Core MVC route?

Is there a simple way to log (using nlog, e.g. log.Debug(…)) the selected route in an ASP.NET Core MVC application? I use HttpGet/HttpPost attributes to define my routes, and I am looking for a simple way to log the route that MVC chooses to handle for each incoming request.
Note: Similar questions have been answered, but they relate to debugging and analyzing routes:
How do you debug MVC 4 API routes?
ASP.NET Routing Debugger
For my purposes, though, I just want to know which route MVC chose to handle for the request and have it logged. (And, ideally, I don't want to add a log to each controller method; there must be some way to do this once generically for all incoming requests.)
a simple generic way of logging all request is using a registered middleware component.
public class PerformanceMiddleware
{
private readonly RequestDelegate _next;
public PerformanceMiddleware(RequestDelegate requestDelegate)
{
_next = requestDelegate;
}
public Task Invoke(HttpContext httpContext)
{
var watch = new Stopwatch();
watch.Start();
var nextTask = _next.Invoke(httpContext);
nextTask.ContinueWith(t =>
{
var time = watch.ElapsedMilliseconds;
var requestString = $"[{httpRequest.Method}]{httpRequest.Path}?{httpRequest.QueryString}";
if (t.Status == TaskStatus.RanToCompletion)
{
.. log.Info ..($"{time}ms {requestString}");
}
else
{
.. log.Warn ..($"{time}ms [{t.Status}] - {requestString}", t.Exception?.InnerException);
}
});
return nextTask;
}
}
this also logs the execution time
In your startup you register it as the first, before UseMvc and any other middleware components.
app.UseMiddleware<PerformanceMiddleware>();
On IIS, you can use the web.config file. In this file, by default, there is an option stdoutLogEnabled=false. Switch it to true. A log will be generated with the info you want.
Edit : Updated my answer to specify it's only correct for IIS according to the comments below

Asp.net mvc core 2.1 response caching not working [duplicate]

I want to use server-side response caching (output cache) with asp.net core 2.0 and found out about Response Caching Middleware and wanted to give it a try with a brand new asp.core mvc project.
Here is the description from the link above which makes me think this could be used like output cache.
The middleware determines when responses are cacheable, stores responses, and serves responses from cache.
Here is how my startup.cs looks like.
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCaching();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseResponseCaching();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
and here is the HomeController.cs
[ResponseCache(Duration = 60)]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
there is also a timestamp at the bottom of _Layout.cshtml file so i can tell when the page is rendered, like below.
<p>© 2018 - ResponseCachingMiddleware - #DateTime.UtcNow</p>
Cache-Control headers seem to be fine, this is what I get in headers when I load the page but time stamp keeps getting updated on every refresh every second.
Cache-Control:public,max-age=60
What I'm understanding from MS documentations is Response Caching Middleware is the server-side caching mechanism that takes care of caching the response while Response Caching seems to be just a filter to manipulate response headers for caching.
Can't tell if there is something wrong with my understanding or code and I wanna complain that I'm feeling this way too often since I started prototyping with ASP.Net Core. Maybe you could also suggest better resources as a side topic.
I've checked out this post before
ASP.NET Core 2.0 - Http Response Caching Middleware - Nothing cached
Also checked this out but it seems like the only difference is I'm using mvc.
https://github.com/aspnet/ResponseCaching/blob/dev/samples/ResponseCachingSample/Startup.cs
Thanks
Edit: I'm seeing the message below in the output window, cannot find anything about it on google except the few places I already checked for response caching middleware.
Microsoft.AspNetCore.ResponseCaching.ResponseCachingMiddleware:Information:
The response could not be cached for this request.
Note: I wish I could create #response-caching-middleware tag. Not sure #responsecache is relevant.
I had the same issue, I was about to pull my hairs over it, I'd set app.UseResponseCaching(); as well as services.AddResponseCaching(); and add ResponseCache on top of my action exactly like what was told in Microsoft official Docs, despite the the cache-controll header was set correctly on response returning from server but still nothing cached at server-side.
After couple of hours of sweating on this issue I figured out where the problem arises and why nothing cached at server.
Browsers by default set cache-controll value to max-age=0 for the request (if the request is not caused by back or forward) even though you set cache-controller correctly in your response by adding ResponseCache attribute on top of you action (or controller) since the cache-controller sent by request is set to max-age=0, the server is unable to cache response, I think this must be added to list of Response Caching limitation as well
Anyway you can override browser default behavior by adding few line of code right before calling app.UseResponseCaching(); on the other hand you need to add a custom middle-ware to modify request cache-control header value before calling app.UseResponseCaching();.
See code below, worked for me hope work for you too
app.Use(async (ctx, next) =>
{
ctx.Request.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(60)
};
await next();
}
);
app.UseResponseCaching();
for ensuring that ResponseCaching works as expected you can also use postman but you must set 'Send no-cache Header' to off in the setting, see image below
I had this same confusion recently.
ASP.Net Core's ResponseCaching does provide both client-side caching (through HTTP response headers) & server-side (through a memory cache'd middleware that short-circuits other middlewares if the response is in the cache). The server-side portion reads the HTTP response cache headers to determine if it should do server-side caching (similar to what an ISP or CDN might do).
Unfortunately, debugging the server-side ResponseCaching is tricky because it has weird rules & there's not adequate logging. In my case I pulled down Microsoft's source code to step through it & find the issue with my code.
The note you found in the output window "The response could not be cached for this request" is a clue.
There's 2 parts to the server-side caching of a request. The server has to prime the cache the first time the url is requested. It will serve the cached version the 2nd time it's requested. Pay attention to when the error message shows up, if it's on the 1st or 2nd request. That'll tell you if it couldn't be stored in the cache or if it couldn't be retrieved from the cache.
The rules for both storage & retrieval are in this source code file:
https://github.com/aspnet/ResponseCaching/blob/3bf5f6a1ce69b65c998d6f5c739822a9bed4a67e/src/Microsoft.AspNetCore.ResponseCaching/Internal/ResponseCachingPolicyProvider.cs
Your "Cache-Control:public,max-age=60" header should match these rules just fine.
My guess is you actually had it working, but didn't know how to test it correctly.
There is a counter-intuitive portion of ResponseCaching noted in this issue: https://github.com/aspnet/Home/issues/2607
Essentially, if the browser sends a no-cache or no-store header (when you hit CTRL+F5 or have your debugger tools open), ASP.Net Core's ResponseCaching will honor the browser's request & re-generate the response.
So, to test if your code was working you probably loaded the page, which primed the cache, then you hit CTRL+F5 to force-refresh your browser & you expected the server-side to respond with a cached entry rather than running your WebAPI code. However, it honored the no-cache request header & bypassed the cache (& wrote that message in your output log).
The way to test this would be to clear your browser cache in-between requests (or switch to incognito), rather than using CTRL+F5.
On a side note, honoring the no-cache/no-store request headers was probably a poor design choice since ASP.Net Core's ResponseCache will most likely be used by a server who owns the response, rather than an intermediary cache like a CDN/ISP. I've extended the base ResponseCache with an option to disable honoring these headers (as well as serialize the cache to disk, rather than in-memory only). It's an easy drop-in replacement for the default cache.
You can find my extension here:
https://github.com/speige/AspNetCore.ResponseCaching.Extensions
https://www.nuget.org/packages/AspNetCore.ResponseCaching.Extensions
There are also a few other other gotchas with ResponseCaching to watch out for which you may have already read about in the blog urls you posted. Authenticated requests & responses with set-cookie won't be cached. Only requests using GET or HEAD method will be cached. If the QueryString is different, it'll make a new cache entry. Also, usually you'll want a "Vary" header to prevent caching if certain conditions of a request differ from the previously-cached request (example: user-agent, accept-encoding, etc). Finally, if a Middleware handles a request it'll short-circuit later Middlewares. Make sure your app.UseResponseCaching() is registered before app.UseMVC()
If the Cache-Control header is coming through, then it's working. That's all the server can do from that perspective. The client ultimately makes the decision whether or not to actually cache the resource. Sending the header doesn't force the client to do anything; in fact, the server, in general, cannot force the client to do anything.

In .NET MVC 4 (Web API), how do I intercept a request for a controller and change it's Content-Type?

Thanks for looking!
Background
I am writing an API layer for a company that will be used by disparate apps and external consumers.
On the consumption end, most consumers will call a service via ajax using a jQuery $.post(); however, Internet Explorer makes our lives more challenging (of course!). For IE, I must use the XDomainRequest object because IE will not run a jQuery $.post() and because if I use IE's XMLHttpRequest(), I get a security message which is unacceptable (again--of course!):
Otherwise, XMLHttpRequest() works fine.
I am using C#, .NET MVC 4 (WebApi)
Problem
The problem is that XDomainRequest does not allow you to set the Content-Type header and always defaults to text-plain which MVC 4 WebApi controllers will not accept (and yet again--of course!!).
Question
How can I intercept requests for my controllers, detect the presence of text-plain content types and change them to text-json content-type on the fly?
Thanks in advance!
Well after two days and pouring over documentation and reading in this thread I've been able to make this work. So please forgive me if my description of the solution is poor; this is the first time I answer one of these types of threads. Since it took me so long to find the problem I figured it is worth saving some soul from falling into this same problem.
The source for my help came from the above link by byterot.
First thing I did was to create a DelegatingHandler. So in my helper folder or where every you want to create a class and call it what you want.
Here is mine:
public class ContentTypeHandler : DelegatingHandler
{
/** Check that this is an IE browser. */
if ((request.Headers.UserAgent.ToString().IndexOf("MSIE", System.StringComparison.Ordinal) > -1))
{
MediaTypeHeaderValue contentTypeValue;
if (MediaTypeHeaderValue.TryParse("application/json", out contentTypeValue))
{
request.Content.Headers.ContentType = contentTypeValue;
request.Content.Headers.ContentType.CharSet = "utf-8";
}
}
/** Return request to flow. */
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
// work on the response
var response = task.Result;
return response;
});
}
Last think that you have to do is call the Handler in your Global.asax.cs file inside your Application_Start():
GlobalConfiguration.Configuration.MessageHandlers.Add(new ContentTypeHandler());
That's all I did and it worked. So good luck I hope this helps someone.
There is no problem modifying request in HTTP stack. It can be done by writing and registering your custom DelegatingHandler before it gets to the controller. Delegating handler can take care of this early-on in the game, so your request will arrive to the controller in the form you want it to. It could be route-specific handler as well.
http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler.aspx
Did you try $.ajax instead of $.post ?

Categories

Resources