5

I want to create an IRouteConstraint that filters a value against possible values of an enum. I tried to google it for myself, but that didn't result in anything.

Any ideas?

tereško
  • 58,060
  • 25
  • 98
  • 150
Jaap
  • 3,081
  • 2
  • 29
  • 50

3 Answers3

11

This is what I came up with:

public class EnumRouteConstraint<T> : IRouteConstraint
  where T : struct
{

  private readonly HashSet<string> enumNames;

  public EnumRouteConstraint()
  {
    string[] names = Enum.GetNames(typeof(T));
    enumNames = new HashSet<string>(from name in names select name.ToLowerInvariant());
  }

  public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
  {
    return enumNames.Contains(values[parameterName].ToString().ToLowerInvariant());
  }
}

I think a HashSet will perform much better than Enum.GetNames on each Match. Also the use generics makes it look a bit more fluent when you use the constraint.

Unfortunately, where T : Enum is not allowed by the compiler.

Jaap
  • 3,081
  • 2
  • 29
  • 50
  • 2
    *Unfortunately, where T : Enum is not allowed by the compiler* This is true, but you can still try to enforce it at runtime. http://stackoverflow.com/a/2936591/242520 – ta.speot.is Jul 03 '13 at 05:54
  • 2
    The constructor Enum.GetNames(typeof(T)) throws an exception if T is not an enum: ArgumentException: Type provided must be an Enum. – Jaap Jul 03 '13 at 09:34
  • C# 7.3 adds the option to use T: enum. However it doesn't work as one might expect, see https://stackoverflow.com/questions/50218754/c-sharp-7-3-enum-constraint-why-cant-i-use-the-enum-keyword – Bernard Vander Beken Dec 05 '19 at 12:56
5

See this

Essentially, you need

  private Type enumType;

  public EnumConstraint(Type enumType)
  {
    this.enumType = enumType;
  }

  public bool Match(HttpContextBase httpContext, 
    Route route, 
    string parameterName,     
    RouteValueDictionary values, 
    RouteDirection routeDirection)
  {
    // You can also try Enum.IsDefined, but docs say nothing as to
    // is it case sensitive or not.
    return Enum.GetNames(enumType).Any(s => s.ToLowerInvariant() == values[parameterName].ToString());
  }
Chris
  • 3,210
  • 1
  • 33
  • 35
Anton Gogolev
  • 113,561
  • 39
  • 200
  • 288
  • on my blog too - http://mikemilleresq.wordpress.com/2010/03/12/starting-small-mvc-constraints/ – Mike Miller Jun 07 '11 at 09:43
  • `Enum.IsDefined()` is case sensitive, so should be avoided if your route constraint should work regardless of case. – Chris Apr 04 '14 at 11:23
0

----------------- 2022 Update -----------------

Try to use a generic RouteConstraint


builder.Services.Configure<RouteOptions>(routeOptions =>
{
    routeOptions.ConstraintMap.Add(nameof(Status) , typeof(EnumConstraint<Status>));
});


public class EnumConstraint<TEnum> : IRouteConstraint
        where TEnum : struct, IConvertible
{
    bool IRouteConstraint.Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (httpContext == null)
            throw new ArgumentNullException(nameof(httpContext));

        if (route == null)
            throw new ArgumentNullException(nameof(route));

        if (routeKey == null)
            throw new ArgumentNullException(nameof(routeKey));

        if (values == null)
            throw new ArgumentNullException(nameof(values));

        if (values.TryGetValue(routeKey, out var routeValue))
        {
            if(!typeof(TEnum).IsEnum)
                throw new ArgumentException($"{nameof(TEnum)} is not enum!");
            var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
            if (parameterValueString is null)
                return false;
            EnumConverter enumConverter = new (typeof(TEnum));
            if (enumConverter.ConvertFromString(parameterValueString) is not null)
                return true;
        }
        return false;
    }
}
  • You should probably test whether creating an EnumConvertor on every request doesn't create too much garbage. – Jaap Dec 20 '22 at 18:54