简体   繁体   中英

Multiple optional parameters including enum type in ASP.NET Core Web API routing

I am creating a method in my ASP.NET Core Web Api that takes multiple parameters, all of which are optional. I am getting an error while calling this method on postman:

System.InvalidOperationException: The constraint reference 'StatusEnum' could not be resolved to a type. Register the constraint type with 'Microsoft.AspNetCore.Routing.RouteOptions.ConstraintMap'

How can I work with enums as my param type in routing? My controller:

[HttpPost("filter-reports/{reportStatus:StatusEnum?}/{IsNecessary:bool?}/{completionStatus: completionStatusEnum?}")]
    [Authorize]
    public ObjectResult GetDamageReportByFilters(StatusEnum? reportStatus, bool? IsNecessary, completionStatusEnum? completionStatus)
    {
        var result = _reportService.GetReportByFilters(reportStatus,IsNecessary, completionStatus);

        return StatusCode(200, result);
    }

How can I fix this?

You cannot have optional parameters in the route;

How would the url look like if reportStatus and isNecessary were optional?

../filter-reports/?/?/COMPLETED ?

Make a model from you parameters and send it in the body of post method, then they could be optional

Your issue is that by wanting to restrict the values of your route parameters to the enum values, you're using an improper route constraint: you can't use the name of the enum as a route constraint (for reference, the valid route constraints are listed on this page )

You don't need to specify the constraint to mark a route parameter as optional, you can just use the question mark:

[HttpPost("filter-reports/{reportStatus?}/{IsNecessary:bool?}/{completionStatus?}")]
[Authorize]
public ObjectResult GetDamageReportByFilters(StatusEnum? reportStatus, bool? IsNecessary, completionStatusEnum? completionStatus)
{
    var result = _reportService.GetReportByFilters(reportStatus,IsNecessary, completionStatus);

    return StatusCode(200, result);
}

Note that since there's no route constraint, any value can be passed to the enum parameter. If that's not a valid value, the actual value passed to the function is 0. If 0 is mapped to a valid enum value, then that one is used, otherwise you just need to deal with the 0 value (in C#, any int value can be stored in an enum).

If you only want the values to be parsed into the Enum object, then you just need to add custom model binders for each of the Enum types

Custom Model Binding in ASP.NET Core

It has been a long time since this question was created, but I hope this help others who are looking for the same question: https://nickheath.net/2019/02/20/asp-net-core-enum-route-constraints/

I just personalized to:

namespace MyWebApp
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    public static partial class Extensions
    {
        public class CustomRouteConstraint<TEnum> : IRouteConstraint
            where TEnum : struct, Enum
        {
            public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
            {
                // retrieve the candidate value
                var candidate = values[routeKey]?.ToString();
                // attempt to parse the candidate to the required Enum type, and return the result
                return Enum.TryParse(candidate, true, out TEnum _);
            }
        }
    }
}

on startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    //...
    //! Enum route constraints: ref https://nickheath.net/2019/02/20/asp-net-core-enum-route-constraints/
    services.Configure<Microsoft.AspNetCore.Routing.RouteOptions>(options =>
    {
        options.ConstraintMap.Add("MyCustomEnum", typeof(MyWebApp.Extensions.CustomRouteConstraint<Models.MyCustomEnum>));
    });
    //...
}

finally on action:

[Route("/{area:MyCustomEnum}")]
public async Task<IActionResult> ViewAsync(MyCustomEnum area)
{
    //...
}

One of the simpler ways (without having to care about constraint maps) is to replace the StateEnum parameter of you method with a simple string/int representation of the actual enum value and parse it in your method as part of parameter.

[HttpPost("Test/{state}")]
public IActionResult TestMethod(string state)
{
    if (!Enum.TryParse(state, true, out StateEnum stateEnumValue))
        return BadRequest();

    // Do stuff here

    return Accepted();
}

[HttpPost("Test2/{state}")]
public IActionResult TestMethod2(int state)
{
    StateEnum stateEnumValue = (StateEnum) state;
    // Do stuff here
    return Accepted();
}

public enum StateEnum
{
    FirstState = 1,
    SecondState = 2
}

But since your parameters are optional, you could try to use a Query string instead, allowing you to omit the unneeded parameters for you request. Or (as the other answer suggested) use an pbject that represents your parameters instead (as it is easier to manage optional content there)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM