Adding Middleware to Pipe in ASP.Net Core - c#

Previously in ASP.Net I would have written a Generic Http Handler to do this but it looks like in ASP.Net Core you write Middleware.
I am trying to get my Middleware to be called when the page GetUser is requested.
I've tried to understand how from this page https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0 - but have only got so far.
The Middleware code is as follows;
public class GetUser
{
public GetUser(RequestDelegate next)
{
// note this is a handler so no need to store the next
}
public async Task Invoke(HttpContext context)
{
String response = String.Empty;
try
{
response = GenerateResponse(context);
context.Response.ContentType = "text/plain";
context.Response.StatusCode = 200;
await context.Response.WriteAsync(response);
}
catch ( System.Exception ex)
{
response = ex.Message;
}
}
public String GenerateResponse(HttpContext context)
{
String response = "";
response = "a response";
return response;
}
}
public static class GetUserExtension
{
public static IApplicationBuilder UseGetUser(this IApplicationBuilder builder)
{
return builder.UseMiddleware<GetUser>();
}
}
In Program.cs I have added the following line;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
app.Map("/GetUser",GetUserExtension.UseGetUser(builder));
Which fails because builder is a WebApplicationBuilder and not IApplicationBuilder.
So how can I use app.Map so that it calls GetUserExtension.UseGetUser when the GetUser Page is requested.

Make sure your middleware is correctly placed in the program.cs file, which when it is called contains all the resources that it needs to return the request.
This is not much but hopes this helps.

Related

.Net Core: Return IActionResult from a custom Exception Middleware

I have created a new Exception middleware in my .Net Core application. All the exceptions throughout the application are captured and logged here. What I want is to return a IActionResult type like InternalServerError() or NotFound() from the Exception Middleware and not do response.WriteAsync as below.
Controller Method:
public async Task<IActionResult> Post()
{
//Do Something
return Ok();
}
Middleware:
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var response = context.Response;
var statusCode = (int)HttpStatusCode.InternalServerError;
var message = exception.Message;
var description = exception.Message;
response.ContentType = "application/json";
response.StatusCode = statusCode;
await response.WriteAsync(JsonConvert.SerializeObject(new ErrorResponse
{
Message = message,
Description = description
}));
}
}
IActionResult is a thing from MVC, so it is only available within the MVC pipeline (including Razor Pages). Just before the MVC middleware terminates, it will execute those action results using ExecuteResultAsync. That method is then responsible of writing that response to HttpContext.Response.
So in custom middleware, you cannot just set an action result and have it executed, since you are not running within the MVC pipeline. However, with that knowledge, you can simply execute the result yourself.
Let’s say you want to execute a NotFoundResult which is what Controller.NotFound() creates. So you create that result and call ExecuteResultAsync with an . That executor will be able to execute that result object and write to the response:
var result = new NotFoundResult();
await result.ExecuteResultAsync(new ActionContext
{
HttpContext = context
});
That's not really possible due to where IActionResult and middleware sit in relation to one another in the architecture. Middleware sits much lower, and so it can't reach further up the stack to IActionResult. Here's an answer that talks more about it: https://stackoverflow.com/a/43111292/12431728
What you're trying to do can be done by simply adding this line:
app.UseStatusCodePagesWithReExecute("/Public/Error", "?statusCode={0}");
to the Configure method in the Startup.cs. Then you can create your Public Controller with Error method that does the following:
[AllowAnonymous]
public IActionResult Error(int? statusCode = null)
{
// Retrieve error information in case of internal errors.
var error = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
var path = HttpContext.Features.Get<IExceptionHandlerPathFeature>()?.Path;
// TODO: Redirect here based on your status code or perhaps just render different views for different status codes.
}
There is also another middleware that allows you to do a similar thing:
app.UseStatusCodePages(async context =>
{
if (context.HttpContext.Response.StatusCode == 401)
{
context.HttpContext.Response.Redirect("Errors/Unauthorized/");
}
else if (context.HttpContext.Response.StatusCode == 500)
{
// TODO: Redirect for 500 and so on...
}
});

How do I test the integration between the Global error handling middleware and my controller in ASP.Net Core?

I'm trying to write some test with XUnit, specifically I'd like to have a test that ensures that when a certain exception is thrown it gets remapped into a meaningful error code.
I already set up the Global error handling middleware and it works correctly.
Here there is some example code of how my solution works:
My controller with a post endpoint that can return 200 or 404
//Controller
[HttpPost]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<StatusCodeResult> Create([FromBody] Request request) {
//Process request
handler.Handle(request);
return Ok();
}
The Middleware for the Global error handling that remaps exceptions into Error codes
//StartUp Middleware
app.UseExceptionHandler(builder => {
builder.Run(handler: async context => {
IExceptionHandlerFeature error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null) {
int statusCode = (int)GetStatusCodeForException(error.Error);
context.Response.StatusCode = statusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(new ErrorDetails { StatusCode = statusCode, Message = error.Error.Message }.ToString());
}
});
});
And then my test in where I arrange some mocks, instantiate the controller and call the Create method
//UnitTest
[Fact]
public async Task Test()
{
//Arrange
var mockHandler = new Mock<IHandler>();
mockHandler.Setup(handler => handler.Handle(It.IsAny<Request>())).Throws(new CustomException(It.IsAny<string>()));
MyController myController = new MyController();
//Act
var statusCodeResult = await myController.Create(request);
//Assert
StatusCodeResult result = Assert.IsType<NotFoundResult>(statusCodeResult);
}
Here I want to ensure that the CustomException is remapped into a 404 status code. How do I do it? Any help is appreciated.
In your test the middleware is not available. You need to spin up a hosting environment to do that, the package Microsoft.AspNetCore.TestHost provides you with one that you can use for testing:
[Fact]
public async Task Test1()
{
using var host = new TestServer(Program.CreateHostBuilder(null));
var client = host.CreateClient();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "/api/controller");
var result = await client.SendAsync(requestMessage);
var status = result.StatusCode;
// TODO: assertions
}
Now when you call your API in a way an exception is thrown, the middleware should be executed and covered.
You can use the WebApplicationFactory class from the Microsoft.AspNetCore.Mvc.Testing nuget package. This bootstraps your application in-memory to allow for end to end functional tests. Rather than calling individual action methods you can make calls via HttpClient to ensure all middleware etc is called.
You use the Startup class that you have already defined in your main entry project and can add mock/fake objects to the IoC container as required. This allows you to verify/setup any dependencies.
You can then inject this as an IClassFixture. Once injected calling .CreateClient() on the instance will return an HttpClient, through which you can make requests.
Here is an example implementation:
// app factory instance
public class TestAppFactory : WebApplicationFactory<Startup>
{
// mock for setup/verify
public Mock<IHandler> MockHandler { get; } = new Mock<IHandler>();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton(MockHandler);
});
}
}
public class MyTestClass : IClassFixture<TestAppFactory>
{
private readonly TestAppFactory _factory;
private readonly HttpClient _client;
private readonly Mock<IHandler> _mockHandler;
public MyTestClass(TestAppFactory factory)
{
_factory = factory;
_client = factory.CreateClient();
_mockHandler = factory.MockHandler;
}
[Fact]
public async Task Test()
{
// make calls via _client
}
}

.net core different error handling for API and Web Page controllers

I'm trying to setup exception handling in a new .net Core 2.2 Web application project.
Currently I tried two approaches - being
Use the built-in ExceptionHandlers: app.UseExceptionHandler, app.UseStatusCodePages, etc...
Created my own middleware to handle exceptions which I've added to the bottom of this question.
Now, the main issue is that I can use my own custom middleware, but (of course) this is used on all requests, and not only on API calls.
What I would like to achieve is have separated exception logic for Web API and Web Pages, so that I can use my custom middleware and return format for API, and use app.UseDeveloperExceptionPage() for example for web pages.
Exception middleware for reference:
public static class ExceptionMiddleware
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILoggerFactory loggerFactory,
bool includeStackTrace = false)
{
app.UseExceptionHandler(builder =>
builder.Run(async context =>
{
var logger = loggerFactory.CreateLogger(nameof(ExceptionMiddleware));
// Set the response status code
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
// Get the current exception
var currentException = contextFeature.Error;
logger.LogError(currentException, $"Something went wrong: {currentException}");
ErrorDetails errorDetails;
if (includeStackTrace)
errorDetails = new ExtendedErrorDetails
{
StatusCode = (HttpStatusCode) context.Response.StatusCode,
Message = currentException.Message,
StackTrace = currentException.StackTrace
};
else
errorDetails = new ErrorDetails
{
StatusCode = (HttpStatusCode) context.Response.StatusCode,
Message = "Internal Server Error."
};
// Write the response
await context.Response.WriteAsync(JsonConvert.SerializeObject(errorDetails));
}
}));
}
private class ErrorDetails
{
public HttpStatusCode StatusCode { [UsedImplicitly] get; set; }
public string Message { [UsedImplicitly] get; set; }
}
private class ExtendedErrorDetails : ErrorDetails
{
public string StackTrace { [UsedImplicitly] get; set; }
}
}
You could not identify the error from exception, I suggest you try to make different route for mvc and web api. For web api, add attribute route with api, and then check the request path in the middleware or exception handler like
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var error = context.Features.Get<IExceptionHandlerFeature>() as ExceptionHandlerFeature;
var requestPath = error.Path;
});
});
Update:
app.UseDeveloperExceptionPage();
app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), subApp =>
{
subApp.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await context.Response.WriteAsync("Error");
});
});
});

Getting empty response on ASP.NET Core middleware on exception

I am trying to create a middleware that can log the response body as well as manage exception globally and I was succeeded about that. My problem is that the custom message that I put on exception it's not showing on the response.
Middleware Code 01:
public async Task Invoke(HttpContext context)
{
context.Request.EnableRewind();
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
try
{
context.Response.Body = responseBody;
await next(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var response = await new StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Seek(0, SeekOrigin.Begin);
// Process log
var log = new LogMetadata();
log.RequestMethod = context.Request.Method;
log.RequestUri = context.Request.Path.ToString();
log.ResponseStatusCode = context.Response.StatusCode;
log.ResponseTimestamp = DateTime.Now;
log.ResponseContentType = context.Response.ContentType;
log.ResponseContent = response;
// Keep Log to text file
CustomLogger.WriteLog(log);
await responseBody.CopyToAsync(originalBodyStream);
}
catch (Exception ex)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var jsonObject = JsonConvert.SerializeObject(My Custom Model);
await context.Response.WriteAsync(jsonObject, Encoding.UTF8);
return;
}
}
}
If I write my middleware like that, my custom exception is working fine but I unable to log my response body.
Middleware Code 02:
public async Task Invoke(HttpContext context)
{
context.Request.EnableRewind();
try
{
await next(context);
}
catch (Exception ex)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var jsonObject = JsonConvert.SerializeObject(My Custom Model);
await context.Response.WriteAsync(jsonObject, Encoding.UTF8);
return;
}
}
My Controller Action :
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
throw new Exception("Exception Message");
}
Now I want to show my exception message with my middleware 01, but it doesn't work but its work on my middleware 02.
So my observation is the problem is occurring for reading the context response. Is there anything I have missed in my middleware 01 code?
Is there any better way to serve my purpose that log the response body as well as manage exception globally?
I think what you are saying is that this code isn't sending it's response to the client.
catch (Exception ex)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var jsonObject = JsonConvert.SerializeObject(My Custom Model);
await context.Response.WriteAsync(jsonObject, Encoding.UTF8);
return;
}
The reason for this is that await context.Response.WriteAsync(jsonObject, Encoding.UTF8); isn't writing to the original body stream it's writing to the memory stream that is seekable. So after you write to it you have to copy it to the original stream. So I believe the code should look like this:
catch (Exception ex)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var jsonObject = JsonConvert.SerializeObject(My Custom Model);
await context.Response.WriteAsync(jsonObject, Encoding.UTF8);
context.Response.Body.Seek(0, SeekOrigin.Begin); //IMPORTANT!
await responseBody.CopyToAsync(originalBodyStream); //IMPORTANT!
return;
}
There is a wonderful article explaining in detail your problem - Using Middleware to trap Exceptions in Asp.Net Core.
What you need to remember about middleware is the following:
Middleware is added to your app during Startup, as you saw above. The order in which you call the Use... methods does matter! Middleware is "waterfalled" down through until either all have been executed, or one stops execution.
The first things passed to your middleware is a request delegate. This is a delegate that takes the current HttpContext object and executes it. Your middleware saves this off upon creation, and uses it in the Invoke() step.
Invoke() is where the work is done. Whatever you want to do to the request/response as part of your middleware is done here. Some other usages for middleware might be to authorize a request based on a header or inject a header in to the request or response
So what you do, you write a new exception type, and a middleware handler to trap your exception:
New Exception type class:
public class HttpStatusCodeException : Exception
{
public int StatusCode { get; set; }
public string ContentType { get; set; } = #"text/plain";
public HttpStatusCodeException(int statusCode)
{
this.StatusCode = statusCode;
}
public HttpStatusCodeException(int statusCode, string message) : base(message)
{
this.StatusCode = statusCode;
}
public HttpStatusCodeException(int statusCode, Exception inner) : this(statusCode, inner.ToString()) { }
public HttpStatusCodeException(int statusCode, JObject errorObject) : this(statusCode, errorObject.ToString())
{
this.ContentType = #"application/json";
}
}
And the middlware handler:
public class HttpStatusCodeExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<HttpStatusCodeExceptionMiddleware> _logger;
public HttpStatusCodeExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_logger = loggerFactory?.CreateLogger<HttpStatusCodeExceptionMiddleware>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (HttpStatusCodeException ex)
{
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started, the http status code middleware will not be executed.");
throw;
}
context.Response.Clear();
context.Response.StatusCode = ex.StatusCode;
context.Response.ContentType = ex.ContentType;
await context.Response.WriteAsync(ex.Message);
return;
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class HttpStatusCodeExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseHttpStatusCodeExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<HttpStatusCodeExceptionMiddleware>();
}
}
Then use your new middleware:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseHttpStatusCodeExceptionMiddleware();
}
else
{
app.UseHttpStatusCodeExceptionMiddleware();
app.UseExceptionHandler();
}
app.UseStaticFiles();
app.UseMvc();
}
The end use is simple:
throw new HttpStatusCodeException(StatusCodes.Status400BadRequest, #"You sent bad stuff");

Changing response headers

I am learning ASP.NET Core and for that I am trying to implement a simple proxy server.
Setup idea:
I will set the proxy settings of my browser to point to my server (currently localhost). The server will duplicate the http request and send to the intended server and receive the request. Then the original response header will be edited to match the received header from the actual request and sent to the client.
example: I type http://bing.com in my browser --> the proxy settings will take this request to my server --> I will create a http request called tempRequest with values from the original request --> the tempRequest is sent and a tempResponse is received from http://bing.com --> I will edit the original Http response to match tempResponse --> send the modified response to the browser.
I am trying to achieve this using middleware.
So far, I think I am successfully getting the response back from the original server (http://bing.com). I am having some trouble sending this received response to the browser.
Startup class:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseProxyServer();
}
}
Middleware implementation:
public static class ProxyMiddlewareExtensions
{
public static IApplicationBuilder UseProxyServer(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ProxyMiddleware>();
}
}
public class ProxyMiddleware
{
private readonly RequestDelegate _next;
public ProxyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalRequest = context.Request;
var absoluteUri = string.Concat(
originalRequest.Scheme,
"://",
originalRequest.Host.ToUriComponent(),
originalRequest.PathBase.ToUriComponent(),
originalRequest.Path.ToUriComponent(),
originalRequest.QueryString.ToUriComponent());
if(!absoluteUri.Contains("localhost"))
{
HttpWebRequest newRequest = WebRequest.CreateHttp(absoluteUri);
//Make a copy of the original request
CopyRequestTo(context.Request, newRequest);
//Get a response using the copied http request.
var response = (HttpWebResponse)await newRequest.GetResponseAsync();
//Modify the response going back to the browser to match the response
//received from the intended server.
CopyResponseTo(response, context);
}
await this._next(context);
}
private void CopyResponseTo(HttpWebResponse source, HttpContext destination)
{
destination.Response.OnStarting(async() =>
{
await source.GetResponseStream().CopyToAsync(destination.Response.Body);
foreach (string headerKey in source.Headers.AllKeys)
{
//============Exception Here=========
destination.Response.Headers[headerKey] = source.Headers.Get(headerKey);
}
destination.Response.StatusCode = (int)source.StatusCode;
});
}
public static void CopyRequestTo(HttpRequest source, HttpWebRequest destination)
{
destination.Method = source.Method;
foreach (var headerKey in source.Headers.Keys)
{
destination.Headers[headerKey] = source.Headers[headerKey];
}
destination.ContentType = source.ContentType;
if (source.Method != "GET" && source.Method != "HEAD" && source.ContentLength > 0)
{
var destinationStream = destination.GetRequestStream();
source.Body.CopyTo(destinationStream);
destinationStream.Close();
}
}
}
But while changing the response I get this exception:
Headers are read only, response has already started
Why this exception even when I am using Response.OnStarting ?
I am running the application from console ( not using IIS )

Categories

Resources