Contents tagged with Routing

  • Strongly Typed Routes for ASP.NET MVC

    OK, I know there are already a number of these, but since I didn’t actually know any, I wrote my own library.

    I was trying to achieve something like this in a simple way:

       1: RouteTable.Routes
       2:     .ActionWithDefaultParameters<BlogController>(x => x.PostsByDate(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day))
       3:     .Map();

    What we have is:

    • The controller is specified as a template parameter;
    • The action method is obtained from a strongly typed expression;
    • The default values for the action method parameters are specified in the expression.

    I knew I had to start from the RouteCollection class, so I wrote a couple of extension methods:

       1: public static class RouteCollectionExtensions
       2: {
       3:     public static _Route Action<TController>(this RouteCollection routes, Expression<Func<TController, Object>> action, String url = "") where TController : IController, new()
       4:     {
       5:         MethodCallExpression method = action.Body as MethodCallExpression;
       6:  
       7:         _Route route = new _Route(routes, typeof(TController), method.Method);
       8:         route.SetUrl(url);
       9:  
      10:         return (route);
      11:     }
      12:  
      13:     public static _Route ActionWithDefaultParameters<TController>(this RouteCollection routes, Expression<Func<TController, Object>> action, String url = "") where TController : IController, new()
      14:     {
      15:         _Route route = Action<TController>(routes, action, url);
      16:         MethodCallExpression method = action.Body as MethodCallExpression;
      17:         ParameterInfo [] parameters = method.Method.GetParameters();
      18:         Expression [] arguments = method.Arguments.ToArray();
      19:  
      20:         for (Int32 i = 0; i < parameters.Length; ++i)
      21:         {
      22:             if (arguments[i] is ConstantExpression)
      23:             {
      24:                 route.AddDefaultParameterValue(parameters[i].Name, (arguments[i] as ConstantExpression).Value);
      25:             }
      26:             else if (arguments[i] is MemberExpression)
      27:             {
      28:                 route.AddDefaultParameterValue(parameters[i].Name, Expression.Lambda(arguments[i]).Compile().DynamicInvoke());
      29:             }
      30:         }
      31:  
      32:         return (route);
      33:     }
      34: }

    The Action method lets you create a _Route instance with no default parameter values, while the ActionWithDefaultParameters will take any parameters in the expression (constants and simple properties only) and set them as defaults.

    Next is the _Route class (sorry, I couldn’t think of a better name… Winking smile). This class implements a fluent interface for configuring the routes:

       1: public sealed class _Route
       2: {
       3:     private readonly RouteCollection routes;
       4:  
       5:     public _Route(RouteCollection routes, Type controllerType, MethodInfo actionMethod)
       6:     {
       7:         this.routes = routes;
       8:         this.ControllerType = controllerType;
       9:         this.Action = actionMethod;
      10:         this.Defaults = new RouteValueDictionary();
      11:         this.Constraints = new RouteValueDictionary();
      12:         this.Name = String.Concat(controllerType.Name.Remove(controllerType.Name.LastIndexOf("Controller")), "_", actionMethod.Name);
      13:         this.Url = String.Join("/", actionMethod.GetParameters().Select(x => String.Concat("{", x.Name, "}")));
      14:     }
      15:  
      16:     public String Url
      17:     {
      18:         get;
      19:         private set;
      20:     }
      21:  
      22:     public String Name
      23:     {
      24:         get;
      25:         private set;
      26:     }
      27:  
      28:     public RouteValueDictionary Defaults
      29:     {
      30:         get;
      31:         private set;
      32:     }
      33:  
      34:     public RouteValueDictionary Constraints
      35:     {
      36:         get;
      37:         private set;
      38:     }
      39:  
      40:     public Type ControllerType
      41:     {
      42:         get;
      43:         private set;
      44:     }
      45:  
      46:     public MethodInfo Action
      47:     {
      48:         get;
      49:         private set;
      50:     }
      51:  
      52:     public _Route SetUrl(String url)
      53:     {
      54:         if (String.IsNullOrWhiteSpace(url) == false)
      55:         {
      56:             this.Url = url;
      57:         }
      58:  
      59:         return (this);
      60:     }
      61:  
      62:     public _Route SetName(String name)
      63:     {
      64:         this.Name = name;
      65:  
      66:         return (this);
      67:     }
      68:  
      69:     public _Route AddDefaultParameterValue(String parameterName, Object value)
      70:     {
      71:         this.Defaults[parameterName] = value;
      72:  
      73:         return (this);
      74:     }
      75:  
      76:     public _Route SetOptionalParameter(String parameterName)
      77:     {
      78:         this.Defaults[parameterName] = UrlParameter.Optional;
      79:  
      80:         return (this);
      81:     }
      82:  
      83:     public void Map()
      84:     {
      85:         String url = String.Concat(this.ControllerType.Name.Remove(this.ControllerType.Name.LastIndexOf("Controller")), "/", this.Url);            
      86:         Route route = this.routes.MapRoute(this.Name, url, null, null);
      87:  
      88:         foreach (KeyValuePair<String, Object> @default in this.Defaults)
      89:         {
      90:             route.Defaults[@default.Key] = @default.Value;
      91:         }
      92:  
      93:         foreach (KeyValuePair<String, Object> constraint in this.Constraints)
      94:         {
      95:             route.Constraints[constraint.Key] = constraint.Value;
      96:         }
      97:  
      98:         route.Defaults["Controller"] = this.ControllerType.Name.Remove(this.ControllerType.Name.LastIndexOf("Controller"));
      99:         route.Defaults["Action"] = this.Action.Name;
     100:     }
     101:  
     102:     /*
     103:     
     104:     */
     105:  
     106:     public _Route AddConstraints(Object constraints)
     107:     {
     108:         PropertyDescriptorCollection props = TypeDescriptor.GetProperties(constraints);
     109:  
     110:         foreach (PropertyDescriptor prop in props)
     111:         {
     112:             this.AddDefaultParameterValue(prop.Name, prop.GetValue(constraints));
     113:         }
     114:  
     115:         return (this);
     116:     }
     117:  
     118:     public _Route AddConstraint<TRouteConstraing>() where TRouteConstraing : IRouteConstraint, new()
     119:     {
     120:         this.Constraints[Guid.NewGuid().ToString()] = new TRouteConstraing();
     121:  
     122:         return (this);
     123:     }
     124:  
     125:     public _Route AddConstraint(IRouteConstraint constraint)
     126:     {
     127:         this.Constraints[Guid.NewGuid().ToString()] = constraint;
     128:  
     129:         return (this);
     130:     }
     131:  
     132:     public _Route AddConstraint(String parameterName, String regularExpression)
     133:     {
     134:         this.Constraints[parameterName] = regularExpression;
     135:  
     136:         return (this);
     137:     }
     138:  
     139:     public _Route AddConstraint(String parameterName, IRouteConstraint constraint)
     140:     {
     141:         this.Constraints[parameterName] = constraint;
     142:  
     143:         return (this);
     144:     }
     145: }

    It has the following methods:

    • AddConstraint: for adding route constraints (several overloads);
    • AddDefaultParameterValue/SetOptionalParameter: for setting default parameter values;
    • SetName: for setting a name for the route;
    • SetUrl: for setting the URL;
    • Map: for actually registering the route.

    SetName and SetUrl are optional, because it will try to create a URL from the action method parameters and will name the route like Controller_Action.

    Some more usages:

       1: RouteTable.Routes
       2:     .ActionWithDefaultParameters<BlogController>(x => x.GetPostsForDate(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day))
       3:     .AddConstraint<DateRouteConstraint>()
       4:     .Map();
       5:  
       6: RouteTable.Routes
       7:     .Action<AnotherController>(x => x.SomeAction(null))
       8:     .SetOptionalParameter("someParameter")
       9:     .Map();
      10:  
      11: RouteTable.Routes
      12:     .Action<HomeController>(x => x.Index())
      13:     .SetName("Default")
      14:     .Map();

    Hope you enjoy!

    Read more...