• web form中自定义HttpHandler仿mvc


    web form中自定义HttpHandler仿mvc

    前言

      在mvc大行其道的今天,仍然有不少公司的项目还是使用web form来实现的(其实mvc也是基于web form的),如果要在项目中引入mvc,不得不新建一个mvc的项目,然后将当前项目的功能一点点的转移过去,实在是很麻烦的一件事情,而且项目的改造周期也会加长,更别说一边改造一边添加新功能了,那么如果中间出现那么一点点的小差错,那么开发人员和测试人员估计想死的心都有了。

      基于以上的情景,我们可以通过自定义HttpHandler来仿造mvc的模式,大概的实现思路如下:

    1. 给页面提供一个PageBase<TModel>的类来继承,中间类似于mvc中存放Model的容器
    2. 通过类似/mvc/controller/action方式的url对于Controller内Action的调用(之前《C#实现简易ajax调用后台方法》这篇文章有简单介绍过)
    3. 不同的action返回不同的ActionResult(如文本、Json等)
    4. 将自定义的MvcHandler在web.config中进行配置并引用相关的库即可

    实现

      首先我们需要自定义一个IHttpHandler来处理我们定义的mvc规则,并对其进行解析,其实原理就是上面提到的文章,只是Controller的Action会跟mvc的相似,返回ActionResult,代码大致如下:

    publicabstractclassActionResult{publicabstractvoidExecuteResult(HttpContext context);}publicclassMvcHandler:IHttpHandler,IRequiresSessionState{publicconststring PREFIX ="/mvc/";//其他代码略publicvoidProcessRequest(HttpContext context){string path = context.Request.AppRelativeCurrentExecutionFilePath.Substring(PREFIX.Length);Int32 index = path.LastIndexOf("/");string route = path.Substring(0, index).ToLower();string actionName = path.Substring(index +1);//反射获取Controller和Actionvar controller =null;var action =null;var actionParamters = action.GetParameters();object[] parameters =Array.ConvertAll(actionParamters, p =>{if(p.ParameterType==typeof(HttpPostedFile)){return context.Request.Files[p.Name];}returnConvert.ChangeType(collection[key], type);});var result = action.Invoke(controller, parameters,null)asActionResult;if(result !=null)
                result.ExecuteResult(context);}  

      然后在web.config内的HttpHandlers内添加<add path="/mvc/*/*" type="Infrastructure.MvcHandler" verb="POST,GET"/>,规则可以任意定制,但是得注意url的格式,如果定义成了*/*/*那么多拦截到全部的请求,那么难度就增加了。

      接下来是页面,与以往aspx页面不同的是,我们需要在页面上调用到相应的Model,那么对于PageBase<TModel>就需要一个可以get Model的属性,代码如下:

    publicclassDynamicPageBase:Page{public T Model{protectedget;set;}}

      但是由于我们在页面内调用Model之前,是要对其赋值的,因此就需要一个接口,代码改造如下:

    publicinterfaceIMvcPage{voidSetModel(object model);}publicclassDynamicPageBase:Page,IMvcPage{private T m_Model =default(T);protected T Model{get{return m_Model;}}publicvoidSetModel(object model){if(model !=null)
                m_Model =(T)model;}}

      在页面上,我们就可以使用<%=Model.XXX%>的方式来获取Model内的相关属性了,对于页面的改造大致已经完成了

      那么我们怎么样像mvc那样通过/controller/action的方式来返回html呢,使用过mvc的朋友应该知道,我们的view是要放在一些特定的位置下的,如相应的Controller文件夹内包含着相应的Action aspx页面或razor页面

      因此我们也可以在Web Form的目录下创建一个Views的文件夹,专门用来存放所有对应的Action页面,然后通过对url的解析来获取相应的页面,并将页面转化为html返回给客户端,ViewResult大致代码如下:

    string html ="";try{string childPath = context.Request.AppRelativeCurrentExecutionFilePath.Replace(MvcHandler.PREFIX,string.Empty);string virtualPath =string.Format("~/Views/{0}.aspx", childPath);IMvcPage page =PageParser.GetCompiledPageInstance(virtualPath, context.Server.MapPath(virtualPath), context)asIMvcPage;if(page !=null)
            page.SetModel(m_model);using(StringWriter sw =newStringWriter()){
            context.Server.Execute(page, sw,false);
            html = sw.ToString();}}catch(Exception){
        html ="无法访问该视图";}
    context.Response.Write(html);

      其他的ActionResult都是根据返回类型的不同而有不同的实现,我就不详细列举出来了。

    扩展

      相信留意过老赵博客的朋友都看过《技巧:使用User Control做HTML生成》《方案改进:直接通过User Control生成HTML》这两篇关于UserControl的文章,那么我们可以参考里面的实现来对页面也添加相似的功能,并整合两种方案,让你的ViewResult可以生成aspx或ascx的html,我自己实现的规则是在页面不存在的情况下,则查找对应的UserControl是否存在,如果存在则返回UserControl的html,不存在的话则返回以上的无法访问视图的提示,代码改造大致如下:

    //MvcHandler内string pageVirtualPath ="页面虚拟路径";string controlVirtualPath ="用户控件虚拟路径";//aspxif(File.Exists(context.Server.MapPath(pageVirtualPath))){var page = manager.LoadPage(pageVirtualPath)asIMvcPage;if(page !=null)
            page.SetModel(m_model);
        html = manager.RenderView();}//ascxelseif(File.Exists(context.Server.MapPath(controlVirtualPath))){var control = manager.LoadControl(controlVirtualPath);
        html = manager.RenderView();}else{
        html ="无法访问该视图";}publicclassViewManager{//其他代码略publicPageLoadPage(string virtualPath){
            m_page =PageParser.GetCompiledPageInstance(virtualPath, m_context.Server.MapPath(virtualPath), m_context)asPage;
            s_cache.SetViewPropertyValue(m_page, m_context.Request);return m_page;}publicControlLoadControl(string virtualPath){
            m_page =newPage();
            m_control = m_page.LoadControl(virtualPath);
            m_page.Controls.Add(m_control);
            s_cache.SetViewPropertyValue(m_control, m_context.Request);return m_control;}}

      对于MvcHandler而言,我们可以将部分的可变参数抽离出去,然后额外的进行实现,那么仿mvc的代码就可以整理到一个dll中,可以让其他的项目重用了。

      然后就是可以在MvcHandler内再添加一些Filter的功能,抽离出过滤的接口,来对于一些请求的过滤,那么功能上就可以被进一步的扩展了。

    结尾

      由于以往在写文章的时候,都会提供详细的实现源码,但是后来发现这样并不能给其他人自己实现的机会,因此这次就不提供源码了,大部分重点的想法已经在文章中了,大家可以尝试自己去实现,由于写的文章也不多,如果有阅读上的困难,请告诉我,我会发源码给各位,文章中如有任何错误和遗漏请大家指出,谢谢大家。

     

    简单的mvc之一:简单的开始

      mvc学习到现在,相对所学到的一系列的知识做一个总结,于是就有了这个标题—简单的mvc。文如名,写的是简单的mvc的知识,目标群也不言而喻。这一篇来个简单的开始,从头建立一个web项目,比如hello world。

      asp.net项目的请求处理核心是IHttpHandler,不论是之前的Page,还是之后MVC。所以最简单的web项目,就是只有一个IHttpHandler的项目。项目只有两个文件,一个.ashx文件,内容:

    <%@ WebHandler Class="Danyuers.SimpleMvc.Hello" Language="C#" %>

    另外一个是相应的代码文件,建立一个名为Hello的类,也就是上面的Class属性所指向的类别,代码如下:

    复制代码
    namespace Danyuers.SimpleMvc {
        public class Hello : IHttpHandler {
            public bool IsReusable {
                get { return false; }
            }
    
            public void ProcessRequest(HttpContext context) {
                context.Response.Write("<h1>hello world!</h1>");
                context.Response.End();
            }
        }
    }
    复制代码

    然后编译(请将编译目标文件改为bin),映射到虚拟目录,通过localhost:xxxx/xx.ashx即可访问到建立的项目。记得添加相应的引用(system.web)。

      好了,简单的项目搭建已经完成,但这不是真正的mvc,mvc最直观的表现就是路由映射,区别于webform的文件映射。一个简单的mvc项目需要哪些东西呢?第一,global.asax文件,定位到HttpApplication;第二,路由映射表,定义路由;第三,路由映射对象,也就是控制器。如同上面的ashx一样,global.asax也只是包含一行指令:

    <%@ Application Codebehind="Global.cs" Inherits="Danyuers.SimpleMvc.MvcApplication" Language="C#" %>

    还是同上,建立global.cs,在其中建立SimpleMvcApplication,代码如下:

    复制代码
    using System;
    using System.Web;
    using System.Web.Mvc;
    using System.Web.Routing;
    
    namespace Danyuers.SimpleMvc{
        public class MvcApplication : HttpApplication {
            protected void Application_Start() {
                RouteRegister.Regist(RouteTable.Routes);
            }
        }
    }
    复制代码

    注意其中的RouteRegister,其中定义了我们需要的路由映射。建立单独的代码文件RouteRegister.cs,代码如下:

    复制代码
    using System;
    using System.Web.Mvc;
    using System.Web.Routing;
    
    namespace Danyuers.SimpleMvc{
        internal class RouteRegister {
            public static void Regist(RouteCollection routes) {
                routes.RouteExistingFiles = false;
                routes.Ignore("{resources}.axd/{pathInfo*}");
    
                routes.MapRoute(
                    name: "default",
                    url: "{controller}/{action}/{id}",
                    defaults: new { controller = "Hello", action = "Index", id = UrlParameter.Optional },
                    namespaces:new String[]{"Danyuers.SimpleMvc.Controllers*"}
                );
            }
        }
    }
    复制代码

    从其中可以很明显看到建立一个通用路由:{controller}/{action}/{id}。最后一步,建立控制器。建立Hello.cs,代码如下:

    复制代码
    using System;
    using System.Web.Mvc;
    
    namespace Danyuers.SimpleMvc.Controllers {
        public class HelloController:Controller{
            public void Index() {
                Response.Write("<h1>Hello,world!!</h1>");
                Response.End();
            }
        }
    }
    复制代码

    好了,最后编译,iis映射,运行即可,相较于最初的单httphandler项目,这次直接访问域名即可。

      相较于webform,mvc的代码分布更加分散,却也更加整洁,也更加富有弹性。

     
    分类: C#想法
  • 相关阅读:
    数据库的未来:ORM+LINQ+RX
    工具论-科学是实用工具
    事务、锁与原子性
    ORM-面向对象&关系数据库
    swift Class的内存布局
    使用phpexcel导出到xls文件的时候出现乱码解决
    苹果CMS
    js网页如何获取手机屏幕宽度
    常用正则说明
    php中的线程、进程和并发区别
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3289305.html
Copyright © 2020-2023  润新知