欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(二)

程序员文章站 2022-07-02 09:31:56
路由 对于一个asp.net mvc应用来说,针对http请求的处理实现在目标controller类型的某个action,每个http请求不在像asp.net web forms应用一样是针对一个物...

路由

对于一个asp.net mvc应用来说,针对http请求的处理实现在目标controller类型的某个action,每个http请求不在像asp.net web forms应用一样是针对一个物理文件,而是针对某个controller的某个action方法。目标controller和action的名称由http请求的url来决定,当asp.net mvc接收到抵达的请求后,其首要任务就是通过当前http请求解析得到目标controller和action的名称,这个过程是通过asp.net mvc的路由来实现的。我们通过如下几个对象构建了一个简易的路由系统。

1.routedata

asp.net定义了一个全局的路由表,路由表中的每个route对象包含一个路由模板。目标controller和action的名称可以通过路由变量以占位符的形式定义在模板中,也可以作为路由对象的默认值(无须出现在路由模板中)。对于每一个抵达的http请求,路由系统会遍历路由表并找到一个具有与当前请求url模式相匹配的route对象,然后利用它解析出以controller和action名称为核心的路由数据。在我们自建的asp.net mvc框架中,通过路由解析得到的路由数据通过具有如下定义的routedata类型表示。

 

public class routedata
    {
        public idictionary values { get; private set; }
        public idictionary datatokens { get; private set; }
        public iroutehandler routehandler { get; set; }
        public routebase route { get; set; }
 
        public routedata()
        {
            this.values = new dictionary();
            this.datatokens = new dictionary();
            this.datatokens.add(namespaces, new list());
        }
 
        public string controller
        {
            get
            {
                object controllername = string.empty;
                this.values.trygetvalue(controller, out controllername);
                return controllername.tostring();
            }
        }
 
        public string actionname
        {
            get
            {
                object actionname = string.empty;
                this.values.trygetvalue(action, out actionname);
                return actionname.tostring();
            }
        }
    }

 

routedata定义了两个字典类型的属性values和datatokens,他们代表具有不同来源的路由变量,前者由对请求url实施路由解析获得。表示controller和action名称的属性直接从values属性的字典中提取,对应的key分别为“controller”和“action”。

asp.net mvc的本质是由自定义的httpmodule和自定义的httphandler两个来实现的。httpmodule从routedata对象的routehandler属性获得。routedata的routehandler属性类型为iroutehandler接口,该接口具有一个唯一的gethttphandler方法返回真正真正处理http请求的httphandler对象。该方法有一个类型为requestcontext的参数。requestcontext表示当前(http)请求的上下文,其核心就是对当前httpcontext和routedata的封装。

 

public interface iroutehandler
    {
        ihttphandler gethttphandler(requestcontext requestcontext);
    }
public class requestcontext
    {
        public virtual httpcontextbase httpcontext { get; set; }
        public virtual routedata routedata { get; set; }
    }


 

2.route和routetable

承载路由变量的routedata对象由路由表中与当前请求相匹配的route对象生成,可以通过routedata的route属性获得这个route对象,该属性的类型为routebase。如下面的代码片段所示,routebase是一个抽象类,他仅仅包含一个返回类型为routedata的getroutedata方法。

 

public abstract class routebase
    {
        public abstract routedata getroutedata(httpcontextbase httpcontext);
    }

 

routebase的getroutedata方法具有一个类型为httpcontextbase的参数,它代表针对当前请求的http上下文。当该方法被执行的时候,它会判断自身定义的路由规则是否与当前请求相匹配,并在成功匹配的情况下实施路由解析,并将得到的路由变量封装成routedata对象返回。如果路由规则与当前请求不匹配,则该方法直接返回null。

我们定义了如下一个继承自routebase的route类型来完成具体的路由工作。一个route对象具有一个代表路由模板的字符串类型的url属性。在实现的getroutedata方法中,我们通过httpcontextbase获取当前请求的url,如果它与路由模板的模式相匹配,则创建一个routedata对象作为返回值。对于返回的routedata对象,其values属性表示的字典对对象包含直接通过解析出来的变量,而对于datatokens字典和routehandler属性,则直接取自route对象的同名属性。

 

 public class route : routebase
    {
        public iroutehandler routehandler { get; set; }
        public string url { get; set; }
        public idictionary datatokens { get; set; }
 
        public route()
        {
            this.datatokens = new dictionary();
            this.routehandler = new mvcroutehandler();
        }
 
        public override routedata getroutedata(httpcontextbase httpcontext)
        {
            idictionary variables;
            if (this.match(httpcontext.request
                 .apprelativecurrentexecutionfilepath.substring(2), out variables))
            {
                routedata routedata = new routedata();
                foreach (var item in variables)
                {
                    routedata.values.add(item.key, item.value);
                }
                foreach (var item in datatokens)
                {
                    routedata.datatokens.add(item.key, item.value);
                }
                routedata.routehandler = this.routehandler;
                return routedata;
            }
            return null;
        }
 
        protected bool match(string requesturl,out idictionary variables)
        {
            variables = new dictionary();
            string[] strarray1 = requesturl.split('/');
            string[] strarray2 = this.url.split('/');
            if (strarray1.length != strarray2.length)
            {
                return false;
            }
            for (int i = 0; i < strarray2.length; i++)
            {
                if (strarray2[i].startswith({) && strarray2[i].endswith(}))
                {
 
                    variables.add(strarray2[i].trim({}.tochararray()), strarray1[i]);
                }
            }
            return true;
        }
    }

一个web应用可以采用多种不同的url模式,所以需要注册多个继承自routebase的route对象,多个route对象组成了一个路由表。在我们自定义的迷你版asp.net mvc框架中,路由表通过类型routetable表示。routetable仅仅具有一个类型为routedictionary的routes属性表示针对整个web应用的全局路由表。

 

 public class routetable
    {
        public static routedictionary routes { get; private set; }
        static routetable()
        {
            routes = new routedictionary();
        }
    }


 

routedictionary表示一个具名的route对象的列表,我们直接让它继承自泛型的字典类型dictionary,其中的key表示route对象的注册名称。在getroutedata方法中,我们遍历集合找到指定的httpcontextbase对象匹配的route对象,并得到对应的routedata。,routebase>

 

public class routedictionary : dictionary
    {
        public routedata getroutedata(httpcontextbase httpcontext)
        {
            foreach (var route in this.values)
            {
                routedata routedata = route.getroutedata(httpcontext);
                if (null != routedata)
                {
                    return routedata;
                }
            }
            return null;
        }
    }

 

在global.asax中我们创建了一个基于指定路由模板的route对象,并将其添加到通过routetable的静态只读属性routes所表示的全局路由表中。

 

public class global : system.web.httpapplication
    {
        protected void application_start(object sender, eventargs e)
        {
            routetable.routes.add(default, new route { url = {controller}/{action} });
        }
    }


 

3.urlroutingmodule

路由表的作用是对当前的http请求实施路由解析,进而得到一个以controller和action名称为核心的路由数据,即上面介绍的routedata对象。整个路由解析工作是通过一个类型为urlroutingmodule的自定义ihttpmodule来完成的。

 

public class urlroutingmodule : ihttpmodule
    {
        public void dispose()
        { }
 
        public void init(httpapplication context)
        {
            context.postresolverequestcache += onpostresolverequestcache;
        }
        protected virtual void onpostresolverequestcache(object sender, eventargs e)
        {
            httpcontextwrapper httpcontext = new httpcontextwrapper(httpcontext.current);
            routedata routedata = routetable.routes.getroutedata(httpcontext);
            if (null == routedata)
            {
                return;
            }
            requestcontext requestcontext = new requestcontext
            {
                routedata = routedata,
                httpcontext = httpcontext
            };
            ihttphandler handler = routedata.routehandler.gethttphandler(requestcontext);
            httpcontext.remaphandler(handler);
        }
    }


 

在实现的init方法中,我们注册了httpapplication的postresolverequestcache事件。当代表当前应用的httpapplication对象的postresolverequestcache事件触发之后,urlroutingmodule通过routetable的静态只读属性routes得到表示全局路由表的routedictionary对象,然后根据当前http上下文创建一个httpcontextwrapper对象(httpcontextwrapper是httpcontextbase的子类),并将其作为参数调用routedictionary对象的getroutedata方法。

如果方法调用返回一个具体的routedata对象,urlroutingmodule会根据该对象本身和之前得到的httpcontextwrapper对象创建一个表示当前上下文的requestcontext对象,并将其作为参数传入routedata的routehandler的gethttphandler方法得到一个httphandler对象。urlroutingmodule最后调用httpcontextwrapper对象的remaphandler方法对得到的httphandler对象进行映射,那么针对当前http请求的后续处理将由这个httphandler来接手。