How to create a Net Core Middleware to validate a route parameter

509 Views Asked by At

Using Net Core 7 I have Razor Pages with a culture route parameter:

@page "/{culture:validculture}/about"

I created a custom route constraint to check if culture has a valid value.

When the culture value is invalid I am redirected to a 404 error page.

public class CultureRouteConstraint : IRouteConstraint {

    private readonly RequestLocalizationOptions _options;

  public CultureRouteConstraint(IOptionsSnapshot<RequestLocalizationOptions> options) {

        _options = options.Value;

  } 

  public Boolean Match(HttpContext? httpContext, IRouter? route, String routeKey, RouteValueDictionary values, RouteDirection routeDirection) {
    
    String? culture = Convert.ToString(value, CultureInfo.InvariantCulture);

    List<String>? cultures = _options.SupportedCultures?.Select(x => x.TwoLetterISOLanguageName).ToList();

    if (culture != null && cultures != null)
      return cultures.Contains(culture, StringComparer.InvariantCultureIgnoreCase);

  }

}

This works great if I hardcode de valid cultures, e.g:

List<String>? cultures = new() { "en", "pt" };

But if I inject RequestLocalizationOptions I get the error:

RouteCreationException: An error occurred while trying to create an instance of 'CultureRouteConstraint'.

Maybe I need to use a Middleware for this? How can I do this?

3

There are 3 best solutions below

0
On BEST ANSWER

Just because IOptionsSnapshot Is registered as Scoped and therefore can't be injected into a Singleton service.

You could try with IOptions (Not Options<>)instead If you don't have to update the options for different request),For Example,I tried as below and it works well in my case:

In program.cs:

builder.Services.Configure<RequestLocalizationOptions>(opts =>
{
    var supportedcultures = new List<CultureInfo>()
    {
        new CultureInfo("en-US"),
        new CultureInfo("zh-CN")
    };
    opts.SupportedCultures = supportedcultures;
    opts.SupportedUICultures= supportedcultures;
});
builder.Services.AddRazorPages();
builder.Services.AddRouting(options =>
    options.ConstraintMap.Add("validculture", typeof(CultureRouteConstraint)));



var app = builder.Build();

The constraint:

public class CultureRouteConstraint : IRouteConstraint
    {



       private readonly RequestLocalizationOptions _options;



       public CultureRouteConstraint(IOptions<RequestLocalizationOptions> options)
        {



           _options = options.Value;



       }



       public Boolean Match(HttpContext? httpContext, IRouter? route, String routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {



           String? culture = Convert.ToString(values["culture"], CultureInfo.InvariantCulture);



           List<String>? cultures = _options.SupportedCultures?.Select(x => x.TwoLetterISOLanguageName).ToList();



           if (culture != null && cultures != null)
                return cultures.Contains(culture, StringComparer.InvariantCultureIgnoreCase);
            else
                return false;



       }



   }

The Result:

enter image description here

You could also check the document related

0
On

Seems like you cannot use a DI constructor when working with implementations of IRouteConstraint, but you have the HttpContext in your Match method. So, you can resolve the IOptionsSnapshot from there. This should work:

var opts = ctx
    .RequestServices
    .GetService<IOptionsSnapshot<RequestLocalizationOptions>>();
1
On

Looks like you are using DI to inject the RequestLocalizationOptions into your CultureRouteConstraint class. However, the IRouteConstraint interface does not support DI, so you cannot inject dependencies into it directly.

One option you could consider is using a middleware to handle the routing constraint. The middleware could use DI to inject the RequestLocalizationOptions and then check the culture value before passing control to the appropriate Razor page.

Here's an example of what this might look like:

public class CultureMiddleware
{
    private readonly RequestDelegate _next;
    private readonly RequestLocalizationOptions _options;

    public CultureMiddleware(RequestDelegate next, IOptionsSnapshot<RequestLocalizationOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Check the culture value
        string culture = context.Request.RouteValues["culture"].ToString();
        if (!_options.SupportedCultures.Select(x => x.TwoLetterISOLanguageName).Contains(culture, StringComparer.InvariantCultureIgnoreCase))
        {
            // Redirect to a 404 page if the culture value is invalid
            context.Response.StatusCode = 404;
            return;
        }

        // If the culture value is valid, pass control to the next middleware
        await _next(context);
    }
}