Could someone confirm that the app.UseExceptionHandler() does not work for server-side blazor?
I have seen several cases where my custom ErrorHandler does not catch exceptions being thrown by my application. Example code
Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
...
app.UseExceptionHandler(new ExceptionHandlerOptions { ExceptionHandler = ErrorHandler.HandleError });
...
}
ErrorHandler.cs:
public static async Task HandleError(HttpContext context)
{
var error = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var message = error?.Message ?? "[EXCEPTION NOT FOUND]";
return;
}
An example are when my repository are throwing an exception as such:
The instance of entity type cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
My MVC solution are catching all exceptions and it is using similar ErrorHandling implementations.
Indeed lots of ASP Core middleware scenarios will not work completely in a server-side Blazor application.
This is because Blazor works with SignalR and the Blazor Hub.
You will see that when starting a Blazor application there are first a few HTTP requests that will pass the pipeline till the end. (in most cases these are the initial page load and then a negotiation phase).
But then comes a request to "/_blazor" and this is the moment that the connection stays open to continue communication through websockets.
If you have an exception after this phase it will not enter your exception handler.
You can observe this by creating a small middleware class that is registered through the UseMiddleware extension method on IApplicationBuilder .
Such a middleware class requires an Invoke method like this for example:
.....
public class TestMiddleware
{
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next
}
public async Task Invoke(HttpContext context)
{
await _next(context);
}
....
If you put a breakpoint in the Invoke, you will notice that you will not step further once the context parameter is for path "/_blazor" .
Here is another link that discusses similar problems, different from the exception handler one but also related to the ASP.NET middleware:
https://github.com/aspnet/SignalR/issues/1334
Thanks to #reno answer , i just completed his answer
follow just 3 steps :
1.Creat an ExceptionMiddleware :
public class ExceptionMiddleware
{
public readonly RequestDelegate _next;
string _path;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(ILogger<ExceptionMiddleware> logger, RequestDelegate next,string Path)
{
_next = next;
_path = Path;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (System.Exception ex)
{
_logger.LogError(ex,$"RequsetPath: {context.Request.Path}",default);
context.Response.Redirect(_path);
}
}
}
2.create a custom MiddlewareExtension:
public static class MiddlewareExtensions
{
public static IApplicationBuilder UsemycustomException(
this IApplicationBuilder builder,string path)
{
return builder.UseMiddleware<ExceptionMiddleware>(path);
}
}
3.in Configure use this middleware as first middleware:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UsemycustomException("/Error");
.
.
.
}
note : The Error.razor file in Pages Must have the route path :
#page "/error"
see the Outputwindow Logging in vs:
You can catch any exception including blazor signalR exceptions
using ErrorBoundary on larger scope
For Example in MainLayout.razor :
<main>
<article class="content px-4">
<ErrorBoundary #ref="boundary">
<ChildContent >
#Body
</ChildContent>
<ErrorContent Context="ex">
Your Custom Error Handling Goes Here
</ErrorContent>
</ErrorBoundary>
</article>
</main>
It's important to recover the exception to init state so you don't stuck on exception loop
And in code behind MainLayout.razor.cs or #code section :
ErrorBoundary? boundary;
protected override void OnParametersSet()
{
boundary?.Recover();
}
Related
I have an ASP.NET Core application that we are looking to enhance our logging on. Basically, we want to take the value from a request header and log it as a property on anything that is logged during that request. Here's an example:
public class ExampleController : Controller
{
private readonly ILogger _logger;
public ExampleController(ILogger<ExampleController> logger)
{
_logger = logger;
}
[HttpGet]
[AllowAnonymous]
public IActionResult TestLogging()
{
_logger.LogInformation("Insert Message Here");
return Ok();
}
}
So if we send this request GET /Example/TestLogging, we would like to see the aforementioned header attached to the "Insert Message Here" log message.
It seems like this should be possible either via middleware or filters or something, but I'm not really sure how to go about it. We're using Serilog as our log provider, but it'd be nice to do this generically with ILogger (presumably using BeginScope) to stay implementation unaware. I know we could write something like this in our method:
using (var scope = _logger.BeginScope(new Dictionary<string, object>() {{"HeaderName": "HeaderValue"}}))
{
_logger.LogInformation("Insert Message Here");
}
but we'd like to do that generically for everything instead of on a per method basis.
Create a simple middleware, that can extract from HttpContext the information you want for each request, using BeginScope as you said:
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public LoggingMiddleware(
RequestDelegate next,
ILogger<LoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var state = new Dictionary<string, object>
{
["headers:MY_HEADER"] = httpContext.Request.Headers["MY_HEADER"].ToString(),
};
using (_logger.BeginScope(state))
{
await _next(httpContext);
}
}
}
Add your middleware to the Dependency Injection:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMiddleware<LoggingMiddleware>();
}
I had the idea of implementing a TransactionMiddleware that will call the SaveChanges method of a EFCore context. The implementation for this TransactionMiddleware looks like this:
namespace DotNetCMS.Persistence.EntityFrameworkCore.AspNetCore
{
public sealed class TransactionMiddleware
{
private readonly RequestDelegate _next;
public TransactionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, CmsContext cmsContext)
{
await _next(httpContext);
await cmsContext.SaveChangesAsync();
}
}
}
And I've registered it in the Startup.cs file:
namespace DotNetCMS.Rest
{
public class Startup
{
// ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, CmsContext context)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseMiddleware<TransactionMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
This works as expected, the only crucial thing that is missing is proper error handling in case the SaveChangesAsync call fails for some reason. I've tried to throw an exception in the TransactionMiddleware to simulate that, and then I had to realize that the response is already sent (so the user does not see any error message and assumes that everything has gone right) and instead the error is outputted to stdout.
Now the question is: How do I get ASP.NET to return a response with an error message instead?
I already tried to register the TransactionMiddleware after UseEndpoints, but then it is not called at all, so it seems that the UseEndpoints is a terminating middleware...
EDIT:
The controller action I am testing this against:
namespace DotNetCMS.Rest.Controllers
{
[ApiController]
[Route("[controller]")]
public class PagesController : ControllerBase
{
private readonly PageService _pageService;
public PagesController(PageService pageService)
{
_pageService = pageService;
}
// ...
[HttpPost]
public ActionResult<Page> PostPage(CreateCommand createCommand)
{
var page = _pageService.Create(createCommand);
return CreatedAtAction(nameof(GetPage), new { id = page.Id }, page);
}
// ...
}
}
This approach has a few problems:
You can't throw an exception from the last middleware in the pipeline. No code will handle it.
You can't modify a response after it has started. To explain this in more details:
Your middleware is called and it invokes the next middleware
The action does it job and returns a result (regardless of status code), anything other than an exception
The MVC pipeline writes the result to the response (with formatting and everything)
Your middleware gets the result from the inner middleware, at which point the response has already started
Your middleware, when saving changes fails, would then try to modify the response that was already written.
You have a few options, though, to achieve what you want:
You can use a temporary MemoryStream as a helper, by replacing context.Response.Body before calling the next middleware.
You can use something else than a middleware, for example, an IAsyncResultFilter.
From the documentation of Prometheus, I implemented a middleware in order to create metrics. Prometheus out puts text file of these metrics by default in /metrics end point ... it works perfectly fine but the problem is that that middleware get called for each and every page hit which make app super slow...
how can I make that middleware to be called only when user request for /metrics ?
Im sorry if question is not that clear because this is my first experience with Prometheus on asp.net core app
I used Prometheus-net.AspNetCore library
MetricsMiddleware.cs
public class MetricsMiddleware
{
private readonly RequestDelegate _next;
public MetricsMiddleware(RequestDelegate next)
{
this._next = next;
}
public async Task Invoke(HttpContext httpContext)
{
await _next.Invoke(httpContext);
//custome metrics created here
}
public static class MetricsMiddlewareExtensions
{
public static IApplicationBuilder UseRequestMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestMiddleware>();
}
}
}
stratup.cs file:
public void Configure(IApplicationBuilder app, ....)
{
app.UseMetricServer();
app.UseMetricsMiddleware();
}
You can use Map function to apply a middleware to specifc route. Like code below.
public void Configure(IApplicationBuilder app)
{
app.Map("/metrics", innerApp =>
{
innerApp.UseMetricsMiddleware());
innerApp.UseMetricServer();
}
}
So in Web API 2 in .net framework 4.7.1 if you have a filter which handles exception, defined as followed:
public sealed class RequestExceptionFilter : ExceptionFilterAttribute..
And in WebApiConfig:
config.Filters.Add(new MyAuthorizationFilter());
config.Filters.Add(new RequestExceptionFilter());
if any exception occured in MyAuthorizationFilter it would've been caught in the RequestExceptionFilter.
In .net core 2.1 I have the following:
services.AddMvc(options =>
{
options.Filters.Add(new MyExceptionFilter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication("Basic").AddScheme<AuthenticationSchemeOptions, UserAuthenticator>("Basic", null)
// configure DI for application services
services.AddScoped<IUserAuthenticator, UserAuthenticatorHandler>();
And I have the following handler which is:
public sealed class UserAuthenticator: AuthenticationHandler<AuthenticationSchemeOptions>
Now, If I throw an exception in protected override async Task<AuthenticateResult> HandleAuthenticateAsync() which is a method of UserAuthenticator, the server returns Internal Server Error and skips the exception handling.
Can I make it propagate to the exception filter?
According to https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.1 it seems authorization filters run before exception filters.
Maybe if you moved the exception handling from a filter to be added as middleware
In the public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) method:
app.UseMiddleware<MyErrorHandling>();
public class MyErrorHandling
{
private readonly RequestDelegate _next;
public MyErrorHandling(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception e)
{
// Do stuff?
await context.Response.WriteAsync("it broke. :(");
}
}
}
I think this method would be a bit more flexible than using filters.
Create an extension method like this
public static void UseGlobalExceptionHandler(this IApplicationBuilder appBuilder, ILogger logger)
{
appBuilder.UseExceptionHandler(app =>
{
app.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
//AuthException is your custom exception class
if (ex != null && ex.GetType() == typeof(AuthException))
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("Unautherized");
}
});
});
}
Use it in startup.cs file under Configure method
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Use it like this
app.UseGlobalExceptionHandler();
}
In .net core a lot of the functions of filters (especially global filters) have been replaced by Middleware.
The execution order of filters in MVC was fixed by the framework - MSDN Link
In .net core Middleware is executed in the order that is is configured in the
Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) method in Startup.cs
This means that you authorization filter will not work if there is an exception in your middleware. The best way round this is to move your exception handling into Middleware, and to ensure that it is added first or nearly first in that method.
One other option could be to enable the developer exception page for testing.
I've answered the how to in more detail in this SO answer:
How to catch an exception and respond with a status code in .NET Core
I would like to get the initial timestamp of the current HTTP request in an ASP.NET Core MVC controller.
This timestamp used to be accessible (pre ASP.NET Core) by HttpContext.Timestamp, but Timestamp doesn't seem to be a property of HttpContext anymore.
Where is this property moved to? Or - when it is no longer available - how can I get the timestamp of the HTTP request?
You can add your own middleware to the pipeline which adds additional data to the request. For example:
public void Configure(IApplicationBuilder app)
{
//Make sure this code is placed at the very start to ensure it
//executes as soon as possible
app.Use(async (context, next) =>
{
context.Items.Add("RequestStartedOn", DateTime.UtcNow);
await next();
};
//The rest of your code here...
}
Then later on in the pipeline:
var requestStartedOn = (DateTime)httpContext.Items["RequestStartedOn"];
As an aside, if you intend to reuse this code elsewhere, I would put it in it's own library. For example:
public class RequestTimestampMiddleware
{
private readonly RequestDelegate _next;
public RequestTimestampMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
context.Items.Add("RequestStartedOn", DateTime.UtcNow);
// Call the next delegate/middleware in the pipeline
return this._next(context);
}
}
And then add an extension method to make it easy to use:
public static class RequestTimestampMiddlewareExtensions
{
public static IApplicationBuilder UseRequestTimestamp(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestTimestampMiddleware>();
}
}
Now your Configure method will look a lot nicer:
public void Configure(IApplicationBuilder app)
{
app.UseRequestTimestamp();
//The rest of your code here...
}