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.
Related
Let's assume the following Razor page classes:
public class IndexModel : PageModel
{
public IndexModel() => Console.WriteLine("Index.ctor()");
public void OnGet() => Console.WriteLine("Index.OnGet()");
}
public class Divert : PageModel
{
public Divert() => Console.WriteLine("Divert.ctor()");
public void OnGet() => Console.WriteLine("Divert.OnGet()");
}
And the following middleware:
public class TestMiddleware
{
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Path.ToString().Contains("Divert"))
context.Response.Redirect("/Divert");
await _next(context);
}
}
// Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
app.UseRouting();
app.UseMiddleware<TestMiddleware>();
app.UseEndpoints(endpoints => endpoints.MapRazorPages());
}
As you can see, the middleware redirects every request to /Divert, except if the request goes to /Divert to avoid infinite redirects. When I start the server and try to go to /Index, I would expect to only see Divert.ctor() and Divert.OnGet() messages on the console because of the redirection, but for some reason, the original Index endpoint is still executed and rendered, because the messages Index.ctor() and Index.OnGet() are also printed.
From the browser's side, everything looks okay, /Index returns a 302 redirect code, and the browser then asks for /Divert. Why is Index still executed and rendered on the server, and how can I avoid this?
I have to call the _next delegate, because otherwise the request pipeline is broken, and nothing gets sent to the browser.
To add a bit of context, in my actual project, the middleware will check if a critical configuration file is present, and if it isn't, it redirects the user to a setup page where the file can be created. All the other pages (e.g. Index in this case) rely on this configuration, and they would throw and exception if it's missing.
Yes, I could add a safety check to every single page model class, but I'm hoping that there is a simpler solution that can prevent the original endpoint from being executed after a redirect.
Found the answer, just had to call context.Response.CompleteAsync() in the middleware after the redirect.
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();
}
I am trying to add a custom middleware to the pipeline (to be easier I will pick the .NET Core documentation example).
Let's say we want to have the Spanish culture set whenever a call to API is fired.
Here's the code which runs perfectly:
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
CultureInfo.CurrentCulture = new CultureInfo("es-ES");
CultureInfo.CurrentUICulture = new CultureInfo("es-ES");
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
and the Startup class:
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.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//here is our custom middleware!
app.UseRequestCulture();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
That's fine, but as you can see, the RequestCultureMiddleware does not implement an interface or a base class/abstract class. You just need to remember when defining a middleware to create a constructor that receives the next middleware and also you need to create a method called specifically "InvokeAsync" with "HttpContext" as a parameter.
I tried to find a contract... a base class or an interface and guess what, we have "IMiddleware" which is part of "Microsoft.AspNetCore.Http" assembly. Wow, that's perfect. Let's implement it.
The interface looks like this:
namespace Microsoft.AspNetCore.Http
{
//
// Summary:
// Defines middleware that can be added to the application's request pipeline.
public interface IMiddleware
{
//
// Summary:
// Request handling method.
//
// Parameters:
// context:
// The Microsoft.AspNetCore.Http.HttpContext for the current request.
//
// next:
// The delegate representing the remaining middleware in the request pipeline.
//
// Returns:
// A System.Threading.Tasks.Task that represents the execution of this middleware.
Task InvokeAsync(HttpContext context, RequestDelegate next);
}
}
And here is the implementation:
public class RequestCultureMiddleware : IMiddleware
{
public Task InvokeAsync(HttpContext context, RequestDelegate next)
{
CultureInfo.CurrentCulture = new CultureInfo("es-ES");
CultureInfo.CurrentUICulture = new CultureInfo("es-ES");
// Call the next delegate/middleware in the pipeline
return next(context);
}
}
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
But, when running the API I am getting the following error at run-time:
System.InvalidOperationException: No service for type 'WebApplication1.RequestCultureMiddleware' has been registered.
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Http.MiddlewareFactory.Create(Type middlewareType)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
How exactly I am supposed to register this middleware if not by using the extension "UseMiddleware"?
Thanks.
I'm sure this problem has been solved long ago after 5 months, but I'm writing this advice just in case.
The problem is the "InvokeAsync" method of your custom middleware program is not be executed even though you built in it in "Configure" method of Startup.
I had the same problem the other day and solved it by, but I putting built in code right before the app.UseEndpoints method.
in your case
app.UseAuthorization();
app.UseRequestCulture(); // <- this way.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
By the way, if you put it after the app.UseEndpoints method, the constructor will be called, but the InvokeAsync method will not be executed.
You're using factory-based middleware. As described in those docs, you've missed an important step:
... the IMiddlewareFactory instance registered in the container is used to resolve the IMiddleware implementation instead of using the convention-based middleware activation logic. The middleware is registered as a scoped or transient service in the app's service container.
In your case, that registration would look something like this:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<RequestCultureMiddleware>();
}
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...
}
Background: ASP.NET 5 (ASP.NET Core 1.0) MVC 6 application using Dapper and the Repository Pattern
Obviously, like with every other website/app, I'm trying to eliminate most/all of the exceptions that popup in my website.
I implemented an ExceptionFilter in order to catch all unhandled exceptions like this:
public class UnhandledExceptionFilter : ActionFilterAttribute, IExceptionFilter
{
private readonly IErrorRepo _errorRepo;
public UnhandledExceptionFilter(IErrorRepo errorRepo)
{
_errorRepo = errorRepo;
}
public void OnException(ExceptionContext context)
{
try
{
_errorRepo.SaveException(context.Exception);
}
catch { }
}
}
This works great when the error comes from C# code. But I've purposely put in errors in my razor views (cshtml files) and those are NOT getting caught by this filter.
Is there an additional attribute/interface that I need to inherit in order to catch razor exceptions?
UPDATE:
Here's where the filter attribute is specified, in the startup.cs file in the ConfigureServices method.
services.AddMvc(options =>
{
options.Filters.Add(new UnhandledExceptionFilter(new ErrorRepo(Configuration)));
});
The trick to doing this is not in the attribute - it's by adding a middleware provider. Once your middleware is in the pipeline, you'll be able to catch exceptions thrown at any point (so your attribute will no longer be needed).
The Logger
This is the thing that's actually going to log errors. I've copied what I've seen from your IErrorRepo interface, but of course you could modify it to include any of the additional information passed into the Log method below.
public class UnhandledExceptionLogger : ILogger
{
private readonly IErrorRepo _repo;
public UnhandledExceptionLogger(IErrorRepo repo)
{
_repo = repo;
}
public IDisposable BeginScopeImpl(object state) =>
new NoOpDisposable();
public bool IsEnabled(LogLevel logLevel) =>
logLevel == LogLevel.Critical || logLevel == LogLevel.Error;
public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
if (IsEnabled(logLevel))
{
_repo.SaveException(exception);
}
}
private sealed class NoOpDisposable : IDisposable
{
public void Dispose()
{
}
}
}
The Provider
This is the factory that will create the logger. It's only going to be instantiated once, and will create all the loggers when it goes through the pipeline.
public class UnhandledExceptionLoggerProvider : ILoggerProvider
{
private readonly IErrorRepo _repo;
public UnhandledExceptionLoggerProvider(IErrorRepo repo)
{
_repo = repo;
}
public ILogger CreateLogger(string categoryName) =>
new UnhandledExceptionLogger(_repo);
public void Dispose()
{
}
}
Registering the Provider
Once added to the ILoggerFactory, it will be invoked on each request in the pipeline. Often this is done through a custom extension method, but we've already got a lot of new code so I'll omit that part.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddProvider(new UnhandledExceptionLoggerProvider(new ErrorRepo()));
// the rest
I handle all exception (including Razor ones) with the following code:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
var exceptionHandlingOptions = new ExceptionHandlerOptions()
{
ExceptionHandler = ExceptionHandler.OnException // !! this is the key line !!
// ExceptionHandlingPath = "/Home/Error"
// according to my tests, the line above is useless when ExceptionHandler is set
// after OnException completes the user would receive empty page if you don't write to Resonse in handling method
// alternatively, you may leave ExceptionHandlingPath without assigning ExceptionHandler and call ExceptionHandler.OnException in controller's action instead
// write me if you need a sample code
};
app.UseExceptionHandler(exceptionHandlingOptions);
}
public static class ExceptionHandler
{
public static async Task OnException(HttpContext context)
{
var feature = context.Features.Get<IExceptionHandlerFeature>();
var exception = feature?.Error;
if (exception == null) return;
//TODO: log exception here
}
}
Do not forget to remove IExceptionFilter as it would handle some of the exceptions as well.