📜 ⬆️ ⬇️

Easy way to add routing / key / value in ASP .NET MVC

The other day I had a chance to get acquainted with ASP .NET MVC 1.0, which I had read more than once in blogs and here, in Habré. At first glance, I liked the simplicity of the concept used and the consistency of communication with the ASP .NET architecture (the usual aspx, ascx, masterpages, Global.asax - only now used a little differently). However, for all the conveniences, I did not find the way to specify the routing and transfer of parameters in the form of {Controller} / key1 / value1 / key2 / value2 . They can be set as many as you like, but, unfortunately, at the same time they should stand strictly on the indicated place, and this is sometimes very inconvenient, especially when transmitting a large number of values. After all, some arguments may have default values, and pushing them into the URL forcibly is not the best solution. Of course, it would be possible to use the standard ,? Key1 = value1 & key2 = value2 method, but for some reason I personally wanted to be able to set parameters in this, “MVC-style”, so to speak :)


It turned out that completely, and without much effort. To start, I leafed through a couple of articles on ASP .NET MVC architecture, found a description of the main stages of the request life cycle: www.asp.net/learn/mvc/tutorial-22-cs.aspx . Judging by the description, in order to implement your routing handler, it is necessary to replace or modify the standard UrlRoutingModule so that in the absence of a suitable route additional checks are made to match the “special” routes that will identify the Action methods of some controller with an undefined number parameters. Looking at Reflector using the UrlRoutingModule code, you can see that the main work that this class does is handling the Application.PostMapRequestHandler and Application.PostResolveRequestCache events. The code of the method to which the execution of the event is delegated suggests an idea that the existing modification code is safe for operation:

public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this .RouteCollection.GetRouteData(context);
// routeData == null,
if (routeData != null )
{
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null )
{
throw new InvalidOperationException( string .Format(...));
}
if (!(routeHandler is StopRoutingHandler))
{
RequestContext requestContext = new RequestContext(context, routeData);
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
...
}
}
}


* This source code was highlighted with Source Code Highlighter .

')
Further, if we add special logic to this method, we need to distinguish our “special” routes in the RouteTable table from the ordinary ones. For this, I used the {...} signature, which means that in this place there can be any number of parameters that will be organized in pairs / key / value, that is, the URL will be valid for the MyExtendedController / {...} template, for example, such a plan: / MyExtendedController / searchBy / name / page / 3 / pageSize / 15

So, the task is to add code that will take Request from the context context, check it for compliance with one of the “special” routes, and slip the fake routeData instead of legal null, which will lead to the transfer of control to the desired controller and its Action method. Then you need to attach the modified code to the application. There were several ways to go, the first — modifying the System.Web.Routing assembly — we immediately dismiss the unnecessary complexity and inconvenience, the second — inheriting from the UrlRoutingModule and redefining the corresponding virtual method — is good, but I chose the third way, namely tearing out the UrlRoutingModule code from the System.Web.Routing assembly with a reflector (since it does not carry extraneous dependencies) and simple modification of the necessary methods. Everything went well, the added code looks like this:

// Try to find an extended routing from routes table
if (routeData == null ) {
foreach (RouteBase routeBase in this .RouteCollection) {
if (routeBase is Route) {
Route route = routeBase as Route;
RouteGhost routeGhost = new RouteGhost(route.Url, route.Defaults, route.Constraints, route.DataTokens, route.RouteHandler);
if ((routeData = routeGhost.GetRouteData(context)) != null ) {
break ;
}
}
}
}


* This source code was highlighted with Source Code Highlighter .


here RouteGhost is a dummy class inherited from Route, where we can copy all the data from the Route being checked and check if it is suitable for us. Method Overridden in RouteGhost

RouteData GetRouteData(HttpContextBase httpContext)

* This source code was highlighted with Source Code Highlighter .


which just checks the matching of the request from context.Request to the Route.Url pattern with {...} taken into account.

So, our module-analogue UrlRoutingModule has been created, and now in order to register it in the application, in the web.config config you just need to fix one line:

< httpModules >
< add name ="ScriptModule" type ="System.Web.Handlers.ScriptModule, System.Web.Extensions,
Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
/>
< add name ="UrlRoutingModule" type ="System.Web.Routing.UrlRoutingModule, System.Web.Routing,
Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
/>
< httpModules >


* This source code was highlighted with Source Code Highlighter .

and a similar line in the section in the <system.webServer> configuration, replacing the type with your own and pointing to its assembly. After that, everything should work in the same mode as before, except that now, in addition to the standard way of resolving routes, we have our own advanced mechanism that we can customize as we want.

For the demonstration, I sketched a microproject based on a standard helovorld template from the ASP .NET MVC delivery, created a TestController test controller:

public class TestController : Controller
{
public ActionResult Index( int ? param1, string param2, string param3)
{
ViewData[ "param1" ] = (param1 == null ) ? "null" : param1.ToString();
ViewData[ "param2" ] = param2 ?? "null" ;
ViewData[ "param3" ] = param3 ?? "null" ;
//
return View( "Test" );
}
}

* This source code was highlighted with Source Code Highlighter .


and View

< asp:Content ID ="Content2" ContentPlaceHolderID ="MainContent" runat ="server" >
< h2 > Test </ h2 >
< b > param1 = <% = ViewData[ "param1" ] %> </ b >
< br />
< b > param2 = <% = ViewData[ "param2" ] %> </ b >
< br />
< b > param3 = <% = ViewData[ "param3" ] %> </ b >
</ asp:Content >


* This source code was highlighted with Source Code Highlighter .


Due to the fact that our extended routing only works if no match was found with the usual “normal” routes, we had to change the standard

routes.MapRoute(
"Default" ,
"{controller}/{action}/{id}" ,
new { controller = "Home" , action = "Index" , id = "" }
);


* This source code was highlighted with Source Code Highlighter .


on the sequence

routes.MapRoute(
Account_Default ",
Account/{action}/{id}"
,
new { controller = "Account" , action = "Index" , id = "" }
);
routes.MapRoute(
"Home_Default" ,
"Home/{action}/{id}" ,
new { controller = "Home" , action = "Index" , id = "" }
);
routes.MapRoute(
"Default" ,
"{controller}" ,
new { controller = "Home" , action = "Index" , id = "" }
);


* This source code was highlighted with Source Code Highlighter .


otherwise, if you request / Test / or / Test / param1 / value1 /, the standard {controller} / {action} / {method} would work .

Well, in the end we add

routes.MapRoute(
"Test_Extended" ,
"Test/{...}" ,
new { controller = "Test" , action = "Index" , id = "" }
);


* This source code was highlighted with Source Code Highlighter .


Run, it works!

image

The test project itself can be downloaded here.

Source: https://habr.com/ru/post/60652/


All Articles