mnwsmit
mnwsmit

Reputation: 1173

MVC Controller Action should only handle certain parameter values, 404 otherwise

I have an MVC controller action with one parameter that should only be called with certain values for that parameter (null/empty and some specific strings), in other cases it should not be hit (404 mostly). I've tried using a RegexRouteConstraint like below. But that doesn't filter the specific strings.

var route = new Route("{soortAanbod}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(new { controller = "Homepage", action = "Index", soortAanbod = UrlParameter.Optional }),
    Constraints = new RouteValueDictionary(new { soortAanbod = new RegexRouteConstraint("a|b|c|d|e") }),
    DataTokens = new RouteValueDictionary { { "area", context.AreaName } }
};
context.Routes.Add("Homepage_soortAanbod", route);

The controller looks like this: public ActionResult Index(string soortAanbod)

I've also tried using an action filter but that messes up other other filters. How can I make this route only match on the specified values for soortAanbod?

Upvotes: 1

Views: 685

Answers (3)

NightOwl888
NightOwl888

Reputation: 56909

You have 2 issues:

  1. Your RegEx does not contain anchors (^ and $) to delimit the string you are attempting to match. So, it is matching any string that contains any of these letters.
  2. Your URL will always match the default route, so even if your custom route doesn't match, you will still get to the page. The default route will match any URL that is 0, 1, 2, or 3 segments in length.

You can get around this by removing your custom route and using an IgnoreRoute (which behind the scenes uses a StopRoutingHandler) to prevent those specific URLs from matching.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.IgnoreRoute("Homepage/Index/{soortAanbod}", 
            new { soortAanbod = new NegativeRegexRouteConstraint(@"^a$|^b$|^c$|^d$|^e$") });

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

There is a caveat, though. RegEx's are not very good at doing negative matches, so if you are not a RegEx guru the simplest solution is to build a NegativeRegexRouteConstraint to handle this scenario.

public class NegativeRegexRouteConstraint : IRouteConstraint
{
    private readonly string _pattern;
    private readonly Regex _regex;

    public NegativeRegexRouteConstraint(string pattern)
    {
        _pattern = pattern;
        _regex = new Regex(pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (parameterName == null)
            throw new ArgumentNullException("parameterName");
        if (values == null)
            throw new ArgumentNullException("values");

        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
            return !_regex.IsMatch(valueString);
        }
        return true;
    }
}

Upvotes: 1

Haitham Shaddad
Haitham Shaddad

Reputation: 4456

You can create a custom constraint and use attribute routing, use the custom constraint there and make the constraint constructor accepts the list of strings that you want to avoid

Custom MVC Route Constraint

Custom Constraint with Attribute Routing

Upvotes: 1

romandemidov
romandemidov

Reputation: 76

I think you can try attribute routing or maybe write your own attribute for action to check params or redirect somewhere.

public class SomeAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var yourstring = filterContext.RequestContext.HttpContext.Request.QueryString["string"];
        if (!string.IsNullOrWhiteSpace(yourstring))
        {
            if (yourstring is not ok) 
            filterContext.Result =
                new RedirectToRouteResult(
                    new RouteValueDictionary
                    {
                        {"controller", "SomeCtrl"},
                        {"action", "SomeAction"}
                    });
        }
        base.OnActionExecuting(filterContext);

    }

Upvotes: 1

Related Questions