ASP.NET MVC’s inbuilt Route class can handle just about anything you want.  However if you need a bit more control it’s easy to derive your own.  Here’s a simple class called DirectionalRoute that adds a couple of features:

  • CanGetRouteData: Set if this route can be used to parse a URL
  • CanGetVirtualPath: Set if this route can be used to generate a URL
  • Placeholders: Dictionary of placeholder values
  • Excludes: Array of keys to exclude from generated URL

The Placeholders dictionary needs a little explanation.  Setting Default values on a regular route works well, but if your RouteData contains a value that matches a default (or if the default is set to UrlParameter.Optional), it could be excluded completely from generated URLs. In situations where you really need a value to be present, set a Placeholder.

Here’s the code:

public class DirectionalRoute : Route
{
    public bool CanGetRouteData { get; set; }
    public bool CanGetVirtualPath { get; set; }
    public RouteValueDictionary Placeholders { get; set; }
    public string[] Excludes { get; set; }

    public DirectionalRoute(string url, IRouteHandler routeHandler)
        : this( url, null, null, null, routeHandler )
    {
    }

    public DirectionalRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
        : this( url, defaults, null, null, routeHandler )
    {
    }

    public DirectionalRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
        : this( url, defaults, constraints, null, routeHandler )
    {
    }

    public DirectionalRoute( string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler )
        : base( url, defaults, constraints, dataTokens, routeHandler )
    {
        this.CanGetRouteData = true;
        this.CanGetVirtualPath = true;
    }

    public override RouteData GetRouteData( System.Web.HttpContextBase httpContext )
    {
        if ( !CanGetRouteData ) return null;

        var routeData = base.GetRouteData( httpContext );

        if ( routeData != null && Placeholders != null )
        {
            var missing = routeData.Values
                .Where( rv => ( rv.Value == null || rv.Value == UrlParameter.Optional ) && Placeholders.ContainsKey( rv.Key ) )
                .ToArray();

            foreach ( var m in missing ) routeData.Values[ m.Key ] = Placeholders[ m.Key ];
        }

        return routeData;
    }

    public override VirtualPathData GetVirtualPath( RequestContext requestContext, RouteValueDictionary values )
    {
        return CanGetVirtualPath
            ? base.GetVirtualPath( GetRequestContext( requestContext ), GetRouteValues( values ) )
            : null;
    }

    private RequestContext GetRequestContext( RequestContext requestContext )
    {
        if ( Excludes == null || Excludes.Length == 0 ) return requestContext;

        var newRouteData = new RouteData( requestContext.RouteData.Route, requestContext.RouteData.RouteHandler );

        foreach ( var v in requestContext.RouteData.Values.Where( v => !Excludes.Contains( v.Key ) ) ) newRouteData.Values[ v.Key ] = v.Value;
        foreach ( var v in requestContext.RouteData.DataTokens ) newRouteData.DataTokens[ v.Key ] = v.Value;

        return new RequestContext( requestContext.HttpContext, newRouteData );
    }

    private RouteValueDictionary GetRouteValues( RouteValueDictionary values )
    {
        if ( Excludes == null || Excludes.Length == 0 ) return values;

        return new RouteValueDictionary( values.Where( v => !Excludes.Contains( v.Key ) ).ToDictionary( v => v.Key, v => v.Value ) );
    }
}
About these ads