asp.net-coreasp.net-core-mvcasp.net-core-routing

How to restrict parameter type in controller action and area in Asp.Net Core


I have the Asp.Net Core MVC web application where I have the area called AdminUI.

My controller looks like this:

[Area("AdminUI")]
public class ConfigurationController : BaseController
{
    public async Task<IActionResult> Client(int? id)
    {
        if (id == default)
        {
            var clientDto = _clientService.BuildClientViewModel();
            return View(clientDto);
        }

        var client = await _clientService.GetClientAsync(id.Value);
        client = _clientService.BuildClientViewModel(client);

        return View(client);
    }
}

And I have also following method for adding the feature to prefix whole area with some specific name:

public static IEndpointConventionBuilder MapIdentityServer4AdminUI(
    this IEndpointRouteBuilder endpoint, string patternPrefix = "/")
{
    return endpoint.MapAreaControllerRoute("AdminUI", "AdminUI", 
        patternPrefix + "{controller=Home}/{action=Index}/{id?}");
}

If I use this:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
    ILoggerFactory loggerFactory)
{
    app.UseRouting();
    
    app.UseEndpoints(endpoint =>
    {
        endpoint.MapIdentityServer4AdminUI("/myPrefix/");
    });
}

I can access the AdminUI area on following link:

https://localhost:43000/myPrefix/Configuration/Client
https://localhost:43000/myPrefix/Configuration/Client/2

It works well, but now I need restrict the URL above, that is possible to access only method Client with id which is number or without id parameter in URL. I do not know how to achieve, if I want to keep the prefix in URL above.

I have tried this solution, which does not work:

[Route("[area]/[controller]/[action]")]
[Route("[area]/[controller]/[action]/{id:int}")]
public async Task<IActionResult> Client(int id)
{
    ...
}

This works that I am able to use only ID like number and without ID, but this breaks the URL, because it ignores my prefix above.

URL is now:

https://localhost:43000/AdminUI/Configuration/Client

not:

https://localhost:43000/myPrefix/Configuration/Client

How can I achieve following behavior?


Solution

  • [Route("[area]/[controller]/[action]")]
    [Route("[area]/[controller]/[action]/{id:int}")]
    public async Task<IActionResult> Client(int id)
    {
        ...
    }
    

    The issue is related to above Route attribute.

    As we all known, Actions are either conventionally routed or attribute routed. Placing a route on the controller or the action makes it attribute routed. Actions that define attribute routes cannot be reached through the conventional routes and vice-versa. Any route attribute on the controller makes all actions in the controller attribute routed.

    So, by using the above code, it will use the attribute routes. Try to change your code as below:

    [Route("myPrefix/[controller]/[action]")]
    [Route("myPrefix/[controller]/[action]/{id:int}")]
    public async Task<IActionResult> Client(int id)
    {
        ...
    }
    

    Reference: Mixed routing: Attribute routing vs conventional routing.

    Update

    Besides, you could also change the route template as below:

        public static IEndpointConventionBuilder MapIdentityServer4AdminUI(this IEndpointRouteBuilder endpoint, string patternPrefix = "/")
        {
            return endpoint.MapAreaControllerRoute("AdminUI", "AdminUI",
                patternPrefix + "{controller=Home}/{action=Index}/{id:int?}");
        }
    

    Then, in the controller, remove the Route attribute.

    [Area("AdminUI")]
    public class ConfigurationController : BaseController
    {
        public async Task<IActionResult> Client(int? id)
        {
            ...             
        }
    }
    

    Edit

    I would like to say this one: for whole AdminUI area use

    endpoint.MapAreaControllerRoute("AdminUI", "AdminUI",
          patternPrefix + "{controller=Home}/{action=Index}/{id?}") 
    

    and for ConfigurationController and action Client use this one:

    endpoint.MapAreaControllerRoute("AdminUI", "AdminUI", patternPrefix + "{controller=Home}/{action=Index}/{id:int?}"); 
    

    any idea, how to combine these two settings?

    I'm afraid we can't do that (add two route templates: one for the whole AdminUI area and one for the AdminUI area action method). Because, if we add both of them, if the url not match the route template for the action method, it will use the area route template. The only way I can think is add multiple route templates for each action method, without add a route template for the whole area. Like this:

    endpoint.MapAreaControllerRoute("AdminUI", "AdminUI", patternPrefix + "Home/Index2/{id:int}");
    

    [Note]The above route only for the Home controller Index2 action method.

    Or using Route attribute.