Adding custom middleware not working when using IMiddleware - c#

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>();
}

Related

Middleware should fail the request after calling next delegate

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.

Problem registering object and interface on the startup file more specifically in the ConfigureServices method, the app does not execute

Well, I have created an application to start on ASP net core 3.1 from scratch, I have created an API application and I have already created some layers to have better control on my application, However, when I created my object with its interface and registered them in the startup file on this way:
services.AddScoped<IMySpaceService, MySpaceService>();
I have gotten this error when I run the application:
Unhandled exception. System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: MySpaceService.Services.Interfaces.IMySpaceService Lifetime: Scoped ImplementationType:
this is my code:
public class MySpaceService: IMySpaceService
{
private IMySpaceRepository _mySpaceRepository;
public MySpaceService(IMySpaceRepository mySpaceRepository)
{
_mySpaceRepository = mySpaceRepository;
}
public IList<MySpaceDto> getSpaces()
{
List<MySpaceDto> spaces = new List<MySpaceDto>();
var data = _mySpaceRepository.getSpaces();
foreach (var item in data)
{
SpaceDto spaceDto = new SpaceDto();
spaceDto.Identification = item.Identification;
spaceDto.Name = item.Name;
spaces.Add(spaceDto);
}
return spaces;
}
}
My startup code:
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();
services.AddScoped<IMySpaceService, MySpaceService>();
services.AddScoped<IMySpaceRepository, MySpaceRepository>();
}
// 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();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Any Ideas about it?.
Your MySpaceService has only one constructor with parameter IMySpaceRepository. You need to register your repository as well:
services.AddScoped<IMySpaceRepository, MySpaceRepository>();
services.AddScoped<IMySpaceService, MySpaceService>();
Well, definitely the problem was that I had not registered yet a dependency, however, the dependency that I hadn't registered was "Dbcontext" and I am calling it from my repository class on the constructor. Therefore, I have to say that your comments helped me to solve my problem because finally, it was a problem with the dependency that didn't register.
I had to do this on my startup file:
services.AddDbContext<ExampleContext>(
options => options.UseMySql("Server=localhost;port=3306;Database=exampleDB;User=UserRegistered;Password=*******", mySqlOptions => mySqlOptions
.ServerVersion(new ServerVersion(new Version(8, 0, 18), ServerType.MySql))));

C# .net core 2.1 Exception handling of Authorization

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

Where is HttpContext.Timestamp in ASP.NET Core MVC?

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...
}

c# asp.net map middleware for route

I'm implementing some middleWare which I only want to run on a specific route
/api
There are some other middleware's which always should run, some before and some after the api specific middleware. My code in Startup.cs :
app.UseStaticFiles();
app.UseIdentity();
//some more middleware is added here
//my own middleware which should only run for routes at /api
app.UseCookieAuthMiddleware();
//some more middleware is added here
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseKendo(env);
The order of loading the middleware is important so I want to keep this order.
app.UseCookieAuthMiddleware(); should only run on urls which start with /api e.g. "/api, /api/test, /api?test".
I saw app.Map was an option but how does one make sure all the other middleware's after app.Map are added to.
edit: example on when map skips the lines below:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIdentity();
//some more middleware is added here
app.Map("/api", HandleApiRoutes);
//lines below are skipped ONLY when map is used.
//some more middleware should be added here
app.UseKendo(env);
}
private static void HandleApiRoutes(IApplicationBuilder app)
{
app.UseCookieAuthMiddleware();
}
My middleware:
public class CookieCheckMiddleWare
{
private readonly RequestDelegate _next;
public CookieCheckMiddleWare(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
Debug.WriteLine("Middleware is called" );
//Call the next delegate/middleware in the pipeline this._next(context) is called no errors.
return this._next(context);
}
}
public static class CookieCheckMiddleWareMiddlewareExtensions
{
public static IApplicationBuilder UseCookieAuthMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CookieCheckMiddleWare>();
}
}
I can simply solve the problem by always running the middleware and do a check in the middleware.invore to check if the reques url is like "/api":
public Task Invoke(HttpContext context)
{
var url = context.Request.Path.Value.ToString();
var split = url.Split('/');
if (split[1] == "api") //should do a extra check to allow /api?something
{
//middleware functions
}
}
But I like to know how to propperly use the map function.

Categories