Interpretation of ASP. NET 5 MVC6 Series Tutorials (12): Strongly typed Routing implementation based on Lamda expressions

  • 2021-07-26 07:28:45
  • OfStack

In the previous section on in-depth understanding of Routing, we mentioned that in MVC, in addition to using the default route registration method of ASP. NET 5, it can also be defined by using Attribute-based features (Route and HttpXXX family methods). In this chapter, we will talk about a strongly typed type based on Lambda expressions.

The basic usage example of this method is as follows:


services.Configure<MvcOptions>(opt =>
{
 opt.EnableTypedRouting();

 opt.GetRoute("homepage", c => c.Action<ProductsController>(x => x.Index()));
 opt.GetRoute("aboutpage/{name}", c => c.Action<ProductsController>(x => x.About(Param<string>.Any)));
 opt.PostRoute("sendcontact", c => c.Action<ProductsController>(x => x.Contact()));
});

As you can see from the example, we can define route by extension methods such as GetRoute or PostRoute, and later use Lambda expressions to determine the type of Controller and the method of Action.

Note that the method name of Action is obtained here by delegating the execution of the Action method (it is not actually executed, but the MethodInfo of the Action is obtained based on this).

Implementation principle

In Stratup.cs Adj. ConfigureServices When configuring services in the method, we can use the core configuration file for the MVC site MvcOptions Configure, where this class has 1 ApplicationModelConventions Attributes ( List<IApplicationModelConvention > ) Can save 1 IApplicationModelConvention Interface collection, modified interface can be used to pipeline the program model of MVC program. The definition of this interface is as follows:


public interface IApplicationModelConvention
{
 void Apply(ApplicationModel application);
}

Interface in the Apply The type of parameter received by the method is ApplicationModel , and ApplicationModel There are two extremely important contents for us to operate on, one is the collection of Controller models, and the other is the collection of various Filter. The class is defined as follows:


public class ApplicationModel
{
 public ApplicationModel();

 public IList<ControllerModel> Controllers { get; }
 public IList<IFilter> Filters { get; }
}

The most important thing here is ControllerModel Class, the instance of this class stores all kinds of important and operable information, such as routing definition data on this class and related Action, API description information, routing constraints, etc., which can be operated.

The new IApplicationModelConvention registration method is as follows:


services.Configure<MvcOptions>(opt =>
{
 opts.ApplicationModelConventions.Add(new MyApplicationModelConvention());
});

Therefore, we can use this method to adjust and modify the whole MVC program model at an appropriate time. The strongly typed routing in this chapter is realized by using this feature.

Implementation steps

First, define a strongly typed routing model ConfigureServices0 Class, which inherits from the AttributeRouteModel , AttributeRouteModel Class is the basic model based on Attribute routing, ConfigureServices0 The code for the class is as follows:


public class TypedRouteModel : AttributeRouteModel
{
 public TypedRouteModel(string template)
 {
  Template = template;
  HttpMethods = new string[0];
 }

 public TypeInfo ControllerType { get; private set; }

 public MethodInfo ActionMember { get; private set; }

 public IEnumerable<string> HttpMethods { get; private set; }

 public TypedRouteModel Controller<TController>()
 {
  ControllerType = typeof(TController).GetTypeInfo();
  return this;
 }

 public TypedRouteModel Action<T, U>(Expression<Func<T, U>> expression)
 {
  ActionMember = GetMethodInfoInternal(expression);
  ControllerType = ActionMember.DeclaringType.GetTypeInfo();
  return this;
 }

 public TypedRouteModel Action<T>(Expression<Action<T>> expression)
 {
  ActionMember = GetMethodInfoInternal(expression);
  ControllerType = ActionMember.DeclaringType.GetTypeInfo();
  return this;
 }

 private static MethodInfo GetMethodInfoInternal(dynamic expression)
 {
  var method = expression.Body as MethodCallExpression;
  if (method != null)
   return method.Method;

  throw new ArgumentException("Expression is incorrect!");
 }

 public TypedRouteModel WithName(string name)
 {
  Name = name;
  return this;
 }

 public TypedRouteModel ForHttpMethods(params string[] methods)
 {
  HttpMethods = methods;
  return this;
 }
}

The main function of this class is to define the Controller type that supports incoming Controller and support chain call.

Then define an inheritance IApplicationModelConvention Interface's TypedRoutingApplicationModelConvention Class. The code is as follows:


public class TypedRoutingApplicationModelConvention : IApplicationModelConvention
{
 internal static readonly Dictionary<TypeInfo, List<TypedRouteModel>> Routes = new Dictionary<TypeInfo, List<TypedRouteModel>>();

 public void Apply(ApplicationModel application)
 {
  foreach (var controller in application.Controllers)
  {
   if (Routes.ContainsKey(controller.ControllerType))
   {
    var typedRoutes = Routes[controller.ControllerType];
    foreach (var route in typedRoutes)
    {
     var action = controller.Actions.FirstOrDefault(x => x.ActionMethod == route.ActionMember);
     if (action != null)
     {
      action.AttributeRouteModel = route;
      // Note that this is a direct replacement, which will affect the existing Controller Above Route Route defined by attribute 
      foreach (var method in route.HttpMethods)
      {
       action.HttpMethods.Add(method);
      }
     }
    }
   }
  }
 }
}

In this class, a static variable Routes is saved, which is used to save all routes declared by Lamda expression, and then find and modify them in the existing Controllers collection, and then replace them AttributeRouteModel Property and set the response's Http Method (if not, all modes are allowed by default).

Here, we simply replace action.AttributeRouteModel Therefore, it will lead to one defect (for example, one Action can only support one routing path, and the last one shall prevail). Students can optimize it according to their own abilities.

When optimizing, pay attention to the Route Collection is saved in the controller.Attributes Property, the Route collection on Action is saved in the action.Attributes Property, you can optimize it.

Then, on MvcOptions, we add one more extension method for TypeRouteModel to make it easy to use. The code is as follows:


public static class MvcOptionsExtensions
{
 public static TypedRouteModel GetRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
 {
  return AddRoute(template, configSetup).ForHttpMethods("GET");
 }

 public static TypedRouteModel PostRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
 {
  return AddRoute(template, configSetup).ForHttpMethods("POST");
 }

 public static TypedRouteModel PutRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
 {
  return AddRoute(template, configSetup).ForHttpMethods("PUT");
 }

 public static TypedRouteModel DeleteRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
 {
  return AddRoute(template, configSetup).ForHttpMethods("DELETE");
 }

 public static TypedRouteModel TypedRoute(this MvcOptions opts, string template, Action<TypedRouteModel> configSetup)
 {
  return AddRoute(template, configSetup);
 }

 private static TypedRouteModel AddRoute(string template, Action<TypedRouteModel> configSetup)
 {
  var route = new TypedRouteModel(template);
  configSetup(route);

  if (TypedRoutingApplicationModelConvention.Routes.ContainsKey(route.ControllerType))
  {
   var controllerActions = TypedRoutingApplicationModelConvention.Routes[route.ControllerType];
   controllerActions.Add(route);
  }
  else
  {
   var controllerActions = new List<TypedRouteModel> { route };
   TypedRoutingApplicationModelConvention.Routes.Add(route.ControllerType, controllerActions);
  }

  return route;
 }

 public static void EnableTypedRouting(this MvcOptions opts)
 {
  opts.ApplicationModelConventions.Add(new TypedRoutingApplicationModelConvention());
 }
}

In the above code, we added 1 EnableTypedRouting Extend the method to the MvcOptions.ApplicationModelConventions Property to add a new TypedRoutingApplicationModelConvention Type example.

Other extension methods are used to declare the relevant route. Notice that in the first example, we saw that the method to get action information is to call the action method through a delegate (but not really call it), but some methods have parameters. What should I do? To do this, we set up an Param class that ignores parameters, and the code is as follows:


public static class Param<TValue>
{
 public static TValue Any
 {
  get { return default(TValue); }
 }
}

In this way, when we schedule the route for the About method with parameters, we can define it as follows:


opt.GetRoute("aboutpage/{name}", c => c.Action<HomeController>(x => x.About(Param<string>.Any)));

In addition, since many methods in TypeRouteModel can be called in a chain, we can also specify a name for route in this way. The example code is as follows:


opt.GetRoute("homepage", c => c.Action<HomeController>(x => x.Index())).WithName("foo");

At this point, the whole function of strongly typed routing has been realized, and when you use it, you have one more choice.

Disadvantages (or Bug)

We see that the implementation above IApplicationModelConvention Interface, we simply set the action.AttributeRouteModel Replace it, that is, if you already have it on Action Route Feature, he will overwrite your information to you, resulting in your route failure. For example, if you define one such custom route:


public interface IApplicationModelConvention
{
 void Apply(ApplicationModel application);
}
0

Then the strongly typed route is defined through the Lamda expression, and the code is as follows:


public interface IApplicationModelConvention
{
 void Apply(ApplicationModel application);
}
1

So, you can only pass /homepage Come and visit, but can't pass through /index Come to visit, because it covers your Route for you.

However, the above Lamda expression does not override the Route feature definition defined on Controller, so if you define the Route feature on ProductsController, the two will be combined into one, for example:


public interface IApplicationModelConvention
{
 void Apply(ApplicationModel application);
}
2

Then your visit website should be /products/homepage Instead of /homepage . However, if your code in Lamda expression mode is as follows:


opt.GetRoute("/homepage", c => c.Action<ProductsController>(x => x.Index()));

Then your visit website should be /homepage Because the routing character is an absolute path /homepage Instead of homepage .

Reference: http://www.strathweb.com/2015/03/strongly-typed-routing-asp-net-mvc-6-iapplicationmodelconvention/


Related articles: