定制的MVC框架

介绍 本文旨在提供如何从. net web应用程序开发MVC框架的教育见解。它概述了在开发MVC框架时需要考虑的一些关键因素以及我所面临的一些挑战。 由于System.Web的引入,. net Web表单中的路由成为可能。路由名称空间。该路由引擎允许开发人员将传入的HTTP请求从其物理文件中解耦,从而允许使用更干净的url。正是这个名称空间使我能够试验将web表单应用程序转换为自定义MVC框架的想法。 路由 第一个挑战是将传入的HTTP请求路由到处理程序,并让处理程序启动MVC分派进程。路由由位于全局中的Application_Start()方法完成。asax,在应用程序第一次启动时触发。下面的清单1.1显示了Global.asax的摘录。 清单1.1 隐藏,复制Code

.........
void Application_Start(object sender, EventArgs e)
{
	RouteTable.Routes.Add(new Route("{*url}", new RouteHandler()));
}

正如您在上面的代码中看到的,已经向RouteTable集合添加了单个路由。路线{* url},指示每个HTTP请求都应该由RouteHandler类处理。RouteHandler实现了IRouteHandler接口,并在此过程中实现了GetHttpHandler方法。该方法必须返回一个知道如何实际处理HTTP请求的IHttpHandler实例。下面的清单1.2显示了RouteHandler类的GetHttpHandler方法的摘录。 清单1.2 隐藏,复制Code

.........
 
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
	var page = new Mvc.MvcHandler();
	return page as IHttpHandler; 
}

注意,正在创建并返回一个新的MvcHandler实例。现在我已经有了处理请求的方法,下一步就是决定如何配置框架,例如,如何告诉框架我想要使用的模板引擎的类型。我考虑过把这些可配置的项目放到Web上。但是经过进一步的考虑,我决定创建一个Bootstrap类,它将位于应用程序的根目录中。 Bootstrapping  我需要一种方法来配置我的应用程序,而不必使用Web。配置文件。我决定使用一个引导类,它将从始终出现在MVC应用程序中的MvcHandler类实例化。您可以将它看作应用程序的主要入口点。让我们看一下示例应用程序中提供的引导程序类。 清单1.3 隐藏,复制Code

using System;
using System.Web;
using Mvc;
using Mvc.Route;
 
public class Bootstrap
{
    public void Load(HttpContext context)
    {
        Mvc.Application app = new Mvc.Application();
        app.Module.Add("admin");
 
        app.Route.Add(new StaticRoute("training.html", "default", "index", "training"));
        app.Route.Add(new StaticRoute("prices.html", "default", "index", "prices"));
        app.Route.Add(new StaticRoute("about.html", "default", "index", "about"));
        app.Route.Add(new StaticRoute("contactus.html", "default", "index", "contactus"));
 
        app.TemplateEngine = new Mvc.View.TemplateView(context.Server.MapPath("Application/"));
        app.DebugController = false;
        app.Dispatch(context);
    }
}

上面的清单1.3显示了示例应用程序中的完整引导类。注意,它有一个Load()方法,该方法接受单个参数。加载方法是从MvcHandler类调用的,如前所述,该类负责实例化引导类。MvcHandler类将其HttpContext对象传递给Load方法,并允许我使用存储在引导类上下文中的请求和响应对象。 现在,我有了一个入口点,我可以开始加载应用程序,并可以根据需要配置它。 应用程序 随着引导阶段的完成,我必须做出一些决定,我希望我的MVC框架如何运行,以及我想实现哪些关键特性。对于第一个版本,我想保持它的简单,所以我决定使用以下主要特性。 静态/动态Routing ,模板配置每个模块自定义请求对象 静态/动态路由 在某些情况下,我可能想要路由一个URL并将它指向我选择的控制器/动作。例如,我可能想要路由以下URL http://www.domain.com/aboutus.html,以便它映射到带有Aboutus操作的索引控制器。至少我需要有三种类型的路线。一个路由静态url,其中路径/查询与我指定的路由完全匹配。 例子 隐藏,复制Code

http://www.domain.com/aboutus.html

路线:“aboutus相匹配。"成功映射到索引控制器,关于动作。, 我还需要一个动态路由,这样我就可以匹配部分URL路径/查询。匹配将基于正则表达式。任何匹配该模式的URL都将映射到预定义的控制器和操作。 最后,我需要一个能够从URL动态提取控制器/动作名称的路由。我还希望路由具有灵活性,以便在以后的版本中可以添加其他路由类型。为了实现这一点,我创建了以下抽象路由类,如清单1.4所示: 清单1.4 隐藏,复制Code

using System;
using Mvc.Http;
 
namespace Mvc.Route
{
    public abstract class Route
    {
        private HttpRequest _request = null;
 
        public HttpRequest Request
        {
            get { return this._request; }
            set { this._request = value; }
        }
 
        public abstract bool ExecuteRoute();
    }
}

上面的抽象路由类定义了一个抽象方法ExecuteRoute。当由派生类实现时,它必须返回一个布尔值,指示路由是否成功。它不需要知道控制器/操作类/方法是否存在。例如,我可以创建一个自定义路由,并为HttpRequest _request(一个自定义Htt)设置模块化名、ControllerName和ActonName属性ExecuteRoute()方法中的pRequest对象然后返回true,如下面的示例所示。 隐藏,复制Code

public override bool ExecuteRoute()
{
    this.Request.Module = "Default";
    this.Request.Controller = "Index";
    this.Request.Action = "Index";
    return true;
}

因为这个路由返回true,所以我的MVC框架将总是尝试用一个索引方法加载一个索引类。这种方法的好处是,我可以控制自己想要如何路由url。如果我正在开发一个博客,我可能想把所有以/Blog开头的url路由到一个预定义的控制器。 模板 在它本身中创建一个模板引擎并不是一个小项目。要开发的最快的模板引擎是用存储在集合中的项替换占位符变量的模板。这并不是什么新技术,该技术已被用于各种应用程序,如电子邮件模板。这种方法的缺点是我不能在模板中使用c#代码,所以控制语句和循环是不可能的。 经过进一步的研究,我发现存在各种模板引擎。这意味着我不必将模板引擎绑定到我的MVC框架中,而开发人员可以选择他们想要使用的模板引擎。为了提供这种灵活性,我需要一个模板引擎抽象类。当继承这个类时,它将为我的MVC框架提供所需的所有方法,以理解如何加载变量并将其传递给视图。下面的清单1.5显示了在MVC框架中找到的TemplateEngine类的摘录。 清单1.5 隐藏,收缩,复制Code

public abstract class TemplateEngine
{
    private Mvc.Http.HttpRequest _request;
    private System.Web.HttpResponse _response;
    private string _module;
    private string _controller;
    private string _action;
    private Dictionary<string,> _viewData = new Dictionary<string,object>();

    public Mvc.Http.HttpRequest Request
    {
        get { return this._request; }
        set { this._request = value; }
    }

    public System.Web.HttpResponse Response
    {
        get { return this._response; }
        set { this._response = value; }
    }

    public string Module
    {
        get { return this._module; }
        set { this._module = value; }
    }

    public string Controller
    {
        get { return this._controller; }
        set { this._controller = value; }
    }

    public string Action
    {
        get { return this._action; }
        set { this._action = value; }
    }

    public Dictionary<string,object> ViewData
    {
        get { return this._viewData; }
        set { this._viewData = value; }
    }

    public abstract string Render();
}

只有几个公共属性需要设置。ViewData是传递给TemplateEngine的键/值集合。大多数情况下,该数据将从action方法内部传递。还要注意有一个抽象方法Render()。此方法必须返回要发送到浏览器的输出字符串。例如,如果我使用第三方模板引擎,我想创建一个自定义模板引擎的类继承自抽象TemplateEngine类,我会实现第三方需要呈现的代码模板引擎的渲染方法,返回视图输出只 派遣一个控制器 到目前为止,我已经讨论了MVC框架所需要的几个关键组件。现在是解释控制器分派过程的时候了。让我们从引导阶段开始。在启动Mvc的时候。创建应用程序实例。一旦Mvc。应用程序实例已经配置,Mvc的Dispatch()方法。必须调用Application类。它接受一个参数,该参数是HttpConext的一个实例。在调用分派方法时,会发生以下情况。 一个新的Mvc实例。创建HttpRequest。 隐藏,复制Code

Http.HttpRequest mvcRequest = new Http.HttpRequest(context);

从上面的代码中并不明显,但HttpRequest类实际上是一个位于Mvc中的自定义类。Http命名空间。它之所以是一个自定义类,是因为它可以提供额外的属性,如IsPost、IsGet来确定HTTP请求方法以及获取post和查询值的方法。 接下来,通过遍历路由集合来确定路由。请注意,在下面的示例代码中,一个UriRoute实例被添加到路由集合中。这是默认路由,它被添加到集合的末尾,因此它是最后一个要检查的路由,并且该路由总是返回true。它返回true,因为如果URL中没有模块/控制器/动作名称,则使用默认的模块/控制器/动作名称。 隐藏,复制Code

this.Route.Add(new UriRoute(this._modules));

foreach(Mvc.Route.Route route in this.Route)
{
    route.Request = mvcRequest;
    if (route.ExecuteRoute())
    {
        break;
    }
}

确定路由之后,mvcRequest对象将保存模块/控制器/操作的名称。使用这些名称,可以动态创建类的实例,其中类名是控制器名,类上要调用的方法是操作名。 在尝试实例化控制器类之前,需要准备好备份控制器。毕竟,如果一个URL指向一个不存在的控制器会发生什么?看看下面的代码。 隐藏,复制Code

// Initalize controller meta data for three controllers.
// The front, custom error and default error controllers.
ControllerMetaData frontController = new ControllerMetaData();
frontController.Namespace = exeAssembly + ".Application." + 
  mvcRequest.Module + ".Controllers." + mvcRequest.Controller + "Controller";
frontController.Module = mvcRequest.Module;
frontController.Controller = mvcRequest.Controller;
frontController.Action = mvcRequest.Action;
 
ControllerMetaData customErrorController = new ControllerMetaData();
customErrorController.Namespace = exeAssembly + ".Application." + 
  mvcRequest.Module + ".Controllers.ErrorController";
customErrorController.Module = mvcRequest.Module;
customErrorController.Controller = "Error";
customErrorController.Action = "NotFound";
 
ControllerMetaData errorController = new ControllerMetaData();
errorController.Namespace = "Mvc.ErrorController";
errorController.Controller = "Error";
errorController.Action = "NotFound";
 
// Add The three controllers to a collection.
List<controllermetadata> controllers = new List<controllermetadata>();
controllers.Add(frontController);
controllers.Add(customErrorController);
controllers.Add(errorController);

注意,在上面的代码中,我使用了一个类ControllerMetaData来保存关于一个特定控制器的信息。第一个元对象保存前端控制器的详细信息,而第二个元对象保存一个错误控制器的详细信息(自定义错误和系统错误)。然后,元对象被添加到一个列表集合并遍历。如果没有找到前端控制器,列表中的下一个控制器将被检查,这是一个自定义的ErrorController,它需要与前端控制器在同一个模块中。使用这个自定义的ErrorController,当前端控制器没有找到时,我可以自定义404响应。最后,如果没有自定义的ErrorController,则使用来自我的MVC框架的默认ErrorController。 流程的最后一部分涉及到尝试使用Reflection an动态创建控制器实例d动态类型。控制器必须从Mvc继承。它是一个抽象类,为派生的控制器类提供属性和方法。 抽象类Mvc。控制器有三个从分派方法调用的虚方法。第一个被调用的方法是初始化控制器的Init()方法。接下来调用Load()方法。如果您想为控制器中的每个动作运行一些代码,比如身份验证,那么此方法非常有用。在调用Load()方法之后,将调用动作方法,方法的结果将存储到一个变量中,该变量稍后将被发送到浏览器。如果一个动作方法想要向浏览器发送输出,它需要返回输出,或者使用Response.Write()方法。最后,在Action方法被调用之后,会调用一个Unload方法,这个方法在处理对象和清理资源方面很有用。 分派方法还尝试为每个模块加载一个module .cs类,并调用一个名为load()的方法。如果模块.cs文件带有一个Load()方法,那么它将在调用属于该模块的任何控制器时被调用。例如,我可以通过在模块的Load()方法中添加代码来对模块进行身份验证,这样就不需要对模块中的每个控制器进行身份验证。 这个项目仍在进行中,我欢迎和反馈。 本文转载于:http://www.diyabc.com/frontweb/news15871.html

posted @ 2020-08-12 13:00  Dincat  阅读(154)  评论(0编辑  收藏  举报