Billpeng Space

技术源自生活
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

MVC笔记

Posted on 2012-09-24 22:19  billpeng  阅读(1745)  评论(0编辑  收藏  举报

1.MVC概念 

--Model:用于存储数据的组件

--View:根据Model数据进行内容展示的组件
--Controller:接受并处理用户指令(操作Model),选择一个View并输出内容。
Controller对View进行引用,但是View不知道Controller的存在。Controller和View都是单向引用Model

MVC变种:Observer模式,MVP模式。

2.mvc路由机制 

MVC中重要的路由处理,默认情况是在Global.asax文件中,我们也可以把这块内容独立出来。

代码
复制代码
 1 public  class MyMvcAppliation:HttpApplication 
 2     {
 3         public static void RegisterRoutes(RouteCollection routes)
 4         {
 5             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 6 
 7             routes.MapRoute(
 8                 "Default",                                              // Route name
 9                 "{controller}/{action}/{id}",                           // URL with parameters
10                 new { controller = "Home", action = "Index", id = "" },  // Parameter defaults
11                 new string[] { "GuestBook.MVC.Controller" }
12             );
13            
14         }
15         protected void Application_Start()
16         {
17             ControllerBuilder.Current.DefaultNamespaces.Add("GuestBook.MVC.Controller");
18             RegisterRoutes(RouteTable.Routes);
19         }
复制代码

 

3. 把Controller类和业务逻辑分离,这里可以采用Repository模式

 创建一个Repository接口:IRepository.cs,里面包含些常见数据处理操作方法:这个接口是一个泛型接口,以实现所有实体类的通用性。public interface IRepository<T>

复制代码
    {
        List
<T> FindAllInfo();
        T GetInfo(T model);
        
bool  Add(T model);
        
bool  Delete(T model);
        
bool  Edit(T model);
    }
复制代码

 

4.MVC中的ViewData 

 View在MVC模式中与用户进行最直接的接触,负责数据的呈现。注意:view只是负责数据的呈现,我们要尽量让view中不涉及业务逻辑的处理。既然View与后台代码是相分离的,但View和Controller是如何联系在一起的呢,答案就是ViewData。

  ASP.NET MVC默认使用WebForm来作为view。新建的aspx页面继承自ViewPage,所有的aspx页面都必须继承自ViewPage。我们再看一下ViewPage的部分代码:

 

public class ViewPage : Page, IViewDataContainer 

   
   我们使用传统的asp.net开发时,经常会为了开发的需要,会写一个类似PageBase类,例如会把部分比较通用的方法写入基类。同样在MVC中,我们也可以这样做。

   第一:创建一个ViewPage<T>类(扩展ViewPage)这 个类主要是完成一个继承功能,对 MvcContrib.FluentHtml.ModelViewPage,MvcContrib.FluentHtml.ModelViewUserControl 的继承,实现System.Web.Mvc.ViewPage的功能。还有一个非常重要的作用就是把所有的扩展方法都体现在这个类中。

复制代码

    
public class ViewPage<T> : MvcContrib.FluentHtml.ModelViewPage<T> where T : class
    {
        
public ViewPage()
        {

        }
    }
    
public class ViewUserControl<T> : MvcContrib.FluentHtml.ModelViewUserControl<T> where T : class
    {
        
public ViewUserControl()
        {

        }
    }
复制代码

 第二.对MVC进行扩展(对ViewData)。例如对Html的扩展,我们在做增删改查类似操作时,当用户提交后一般都会根据系统处理结果显示一段提示文字给用户。

         1:创建扩展类:HtmlHelperExtensions,主要包含两个方法,一个是操作成功后的处理方法,另一个则是失败后的处理结果。

复制代码
public static class HtmlHelperExtensions
      {
        
public static string ErrorBox(this HtmlHelper htmlHelper, ViewDataBase  errorViewData)
        {
            
if (errorViewData.ErrorMessage == nullreturn string.Empty;

            HtmlTextWriter writer = new HtmlTextWriter(new StringWriter());

            writer.AddAttribute("class""error");
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.Write(errorViewData.ErrorMessage);
            writer.RenderEndTag();
            
return writer.InnerWriter.ToString();
        }

        
public static string MessageBox(this HtmlHelper htmlHelper, ViewDataBase messageViewData)
        {
            
if (messageViewData.Message == nullreturn string.Empty;

            HtmlTextWriter writer = new HtmlTextWriter(new StringWriter());

            writer.AddAttribute("class""message");
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.Write(messageViewData.Message);
            writer.RenderEndTag();
            
return writer.InnerWriter.ToString();
        }
    }  
复制代码

 

 

 


  1 public  class ViewDataBase

复制代码
 2     {
 3         public string Message { getset; }
 4         public string ErrorMessage { getset; }
 5 
 6         public ViewDataBase WithErrorMessage(string errorMessage)
 7         {
 8             this.ErrorMessage = errorMessage;
 9             return this;
10         }
11         public ViewDataBase WithMessage(string message)
12         {
13             this.Message = message;
14             return this;
15         }
16     }
复制代码

 

 

5.MVC中的ModelBinder 

MVC提交表单时我们并不用对表单中各个对象进行一一对应,MVC会 自动把表单的相关值赋给对象。这点看起来特别神奇,MVC 为我们提供了一个自动化的操作,这一切都归功于IModelBinder 接口,系统默认会找它DefaultModelBinder来完成这一神圣的任务。DefaultModelBinder 内部通过大量的反射完成最终的赋值操作,基本上能适应开发所需。至于如何实现大家可以到网上去搜索下资料,既然有默认的,我们也可以自定义 ModelBinder。 

 1:创建GuestBookBinder,让它继承IModelBinder,并实现其方法。这里我们可以根据实际业务需要,修改方法,这里只是一个简单实现。

复制代码
public class GuestBookBinder : IModelBinder
    {
        
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var info 
= bindingContext.Model ?? new GuestBookInfo();

            var properties 
= bindingContext.ModelType.GetProperties();

            
foreach (var item in properties)
            {
                
if (bindingContext.PropertyFilter(item.Name))
                {
                    var result 
= bindingContext.ValueProvider[item.Name];
                    
if (null == result)
                    { 
break; }
                    var value 
= result.ConvertTo(item.PropertyType);

                    item.SetValue(info, value, 
null);
                }
            }

            
return info;
        }

    }

复制代码

    
       2:注册GuestBookBinder,自定义Binder写好后,系统并不会自动识别,需要在应用程序初始化进行注 册,ControllerActionInvoker.GetParameterValue 根据 ModelBinders.Binders.GetBinder() 来找对应的 IModelBinder,如没找到则返回默认的 DefaultModelBinder。    

protected void Application_Start()
        {
            ControllerBuilder.Current.DefaultNamespaces.Add(
"GuestBook.MVC.Controller");
            ModelBinders.Binders.Add(
typeof(GuestBookInfo ), new GuestBookBinder ());
            RegisterRoutes(RouteTable.Routes);
        }

    
      3:除了上面的注册方法外,可以直接把 ModelBinderAttribute 用在对应的实体上 ,但不推荐这样做。

 

[ModelBinder(typeof(GuestBookBinder))]
public class GuestBookInfo

     
      4:除了由 ControllerActionInvoker.GetParameterValue() 自动完成 BindModel 操作外,还提供了两个方法:这两个方法唯一的区别在于,TryUpdateModel不会抛异常,前者会。 
        1>:Controller.UpdateModel()
        2>:Controller.TryUpdateModel()

      示例:例如更新一则留言时,我们可以这样写:
     

复制代码
[AcceptVerbs(HttpVerbs.Post)]
       
public ActionResult Edit(int id, FormCollection formValues)
       {
           GuestBookInfo model 
= new GuestBookInfo();
           model.ID 
= id;
           model 
= inter.GetInfo(model);
           UpdateModel(model );
           inter.Edit(model);
           
return RedirectToAction("Index");
       }
复制代码

 

 6.ViewData与TempData

 

ViewData 局限于当前Action,TempData跨Action.

TempData因为并不是通过网页参数传值,所以肯定是把数据存储在某个地方的原因 

   第一:查看Controller类的源码,其中包含一个重要的方法:在这个方面开始前就调用了基类的TempData.Load方法。

复制代码
protected override  void ExecuteCore()
{
    
base.TempData.Load(base.ControllerContext, this.TempDataProvider);
    
try
    {
        
string requiredString = this.RouteData.GetRequiredString("action");
        
if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString))
        {
            
this.HandleUnknownAction(requiredString);
        }
    }
    
finally
    {
        
base.TempData.Save(base.ControllerContext, this.TempDataProvider);
    }
}
复制代码

   
       第二:TempData.Load方法:可以看到最终是由ITempDataProvider这个接口来完成。

复制代码
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
    IDictionary
<stringobject> dictionary = tempDataProvider.LoadTempData(controllerContext);
    
this._data = (dictionary != null? new Dictionary<stringobject>(dictionary, 

StringComparer.OrdinalIgnoreCase) : 
new Dictionary<stringobject>(StringComparer.OrdinalIgnoreCase);
    
this._initialKeys = new HashSet<string>(this._data.Keys);
    
this._modifiedKeys.Clear();
}

复制代码

    
      第三:ITempDataProvider:我们可以在第一条中的代码中发现,接口是这样取的:

复制代码
public ITempDataProvider TempDataProvider
{
    
get
    {
        
if (this._tempDataProvider == null)
        {
            
this._tempDataProvider = new SessionStateTempDataProvider();
        }
        
return this._tempDataProvider;
    }
    
set
    {
        
this._tempDataProvider = value;
    }
}
复制代码


        第四:SessionStateTempDataProvider,从这个名字我们就可以猜测,数据应该是用Session方式保存。主要包含了两个方法,分别用于加载数据和保存数据。

复制代码
public virtual IDictionary<stringobject> LoadTempData(ControllerContext controllerContext)
{
    HttpContextBase httpContext 
= controllerContext.HttpContext;
    
if (httpContext.Session == null)
    {
        
throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
    }
    Dictionary
<stringobject> dictionary = httpContext.Session["__ControllerTempData"as Dictionary<string

object>;
    
if (dictionary != null)
    {
        httpContext.Session.Remove(
"__ControllerTempData");
        
return dictionary;
    }
    
return new Dictionary<stringobject>(StringComparer.OrdinalIgnoreCase);
}

public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<stringobject> values)
{
    HttpContextBase httpContext 
= controllerContext.HttpContext;
    
if (httpContext.Session == null)
    {
        
throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
    }
    httpContext.Session[
"__ControllerTempData"= values;
}

复制代码

   
      小结:TempData虽然用Session来实现数据的存储,但对服务器来讲,代价虽然有,但并不高,因为从代码上看TempData用完一次就会被消 除掉。看到这,我们可以想,是否可以看定义一个TempDataProvider,当然可以。这里我创建一个示例,并没有做功能上的改变,可以根据实际情 况修改:

      1:创建MyTempDataProvider,让它继承ITempDataProvider ,并且实现LoadTempData和SaveTempData。
  
      2:将MyTempDataProvider与Controller联系上,我们可以选择扩展默认控制器工厂(DefaultControllerFactory) ,重写IController CreateController方法:

复制代码
public override IController CreateController(RequestContext requestContext, string controllerName)
        {
            Controller controller 
= base.CreateController(requestContext, controllerName) as Controller ;
            
if (null != controller)
            {
                controller.TempDataProvider 
= new MyTempDataProvider();
            }
            
return controller;

        }

复制代码

    
       3:注册我们自定义的MyControllerFactory,这也是最后一步。

复制代码
protected void Application_Start()
        {
            ControllerBuilder.Current.DefaultNamespaces.Add(
"GuestBook.MVC.Controller");
            ModelBinders.Binders.Add(
typeof(GuestBookInfo ), new GuestBookBinder ());
            ControllerBuilder.Current.SetControllerFactory(
typeof(MyControllerFactory ));
            RegisterRoutes(RouteTable.Routes);
        }
复制代码

7.Action和Filter

  Filter在Asp.net MVC中它只能限制于Action,Controller。 继承于ActionFilterAttribute,且可以覆写如下几个重要方法。
       1:void OnActionExecuting(ActionExecutingContext):Action执行前的操作

       2:void OnActionExecuted(ActionExecutedContext):Action执行后的操作

       3:void OnResultExecuting(ResultExecutingContext):解析ActionResult前执行

    4:void OnResultExecuted(ResultExecutedContext):解析ActionResult后执行 

系统提供的比较常见的Filter:
       1:AcceptVerbs
       2:ActionName,上面两个都限制了对Action的访问条件;

       3:OutputCache,设置缓存;      

   4:ValidateInput,增加数据验证。 

 如:[AcceptVerbs(HttpVerbs.Get )]

     public ActionResult Index()

  3:ActionNameAttribute的用法,和 AcceptVerbsAttribute 方式差不多,如果不指定ActionName,则系统会默认找名称和方法名相同之处的View。

[ActionName ("Edit")]

public ActionResult Edit(int id)

 

  自定义Filter:

              这里创建一个没有客户端缓存的NoClientCacheAttribute。需要继承ActionFilterAttribute,且重写OnActionExecuting方法。

复制代码
    public class NoClientCacheAttribute : ActionFilterAttribute
    {
        
public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            HttpContext.Current.Response.CacheControl 
= "No-Cache";
        }

    }
复制代码

   
     应用在Action上,特别简单,像C#中的变通特性用法一样。

[NoClientCache]
public ActionResult Details(int id)

 


8. System.Web.Mvc.Html下的HtmlHelper 

   System.Web.Mvc.Html下的HtmlHelper只能完成大部分html控件的输出,但像img标签默认是没有提供的,这里需要我们自行来扩展下Helper,毕竟上面的众多方法都是扩展出来的。

        扩展Helper,我们可以利用TagBuilder,它能输出所有标签及属性。TagBuilder提供下如下重要方法:

复制代码
// Methods
    public TagBuilder(string tagName);
    
public void AddCssClass(string value);//增加样式
    public void GenerateId(string name);//设置控件ID
    private string GetAttributesString();
    
public void MergeAttribute(string key, string value);//设置属性值
    public void MergeAttribute(string key, string value, bool replaceExisting);
    
public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes);
    
public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes, bool replaceExisting);
    
public void SetInnerText(string innerText);//设置显示文本
    public override string ToString();
    
public string ToString(TagRenderMode renderMode);//输出控件html
复制代码

    
       1:创建ImageHelper,利用TagBuilder部分方法最终输出img标签。

复制代码
public static class ImageHelper
    {
        
public static string Image(this HtmlHelper helper, string id, string url, string alternateText)
        {
            
return Image(helper, id, url, alternateText, null);
        }

        
public static string Image(this HtmlHelper helper, string id, string url, string alternateText, object 

htmlAttributes)
        {
            
// 创建IMG标签
            var builder = new TagBuilder("img");

            
// 增加ID属性
            builder.GenerateId(id);

            
// 增加属性
            builder.MergeAttribute("src", url);
            builder.MergeAttribute(
"alt", alternateText);
            builder.MergeAttributes(
new RouteValueDictionary(htmlAttributes));

            
// 输出完整的img标签
            return builder.ToString(TagRenderMode.SelfClosing);
        }

    }

复制代码

    
       2:页面调用。

<%= Html.Image("img1""http://a.lakequincy.com/img/633820582974214892.jpg""这是一张图片"new 

{border
="4px"})%>

9.MVC下开发ajax程序

  MVC有个特点,一般情况下一个页面文件都会对应一个Controller,类似于web form模式下的页面后台代码。Conntroller里面的每个公共方法(私有方法不行)都可以通过页面地址中访问,例如我们在 HomeController中有这样一个方法:

        

        public   void Test(int i)
        {
            System .Web .HttpContext .Current .Response .Write (
"aaa"+i .ToString ());
        }

  

       我们可以在浏览器中输入/Home/Test?i=1,此时页面上就会输出我们想要的内容,这也是web form模式没有办法直接实现的。即然MVC能够直接调用Controller中的方法,也就是我们不用单独创建一些类来实现,这点和ajaxpro的功 能有点相似。下面我们就来实现在asp.net mvc中应用ajax,当然我选用jquery做为js框架,熟悉jquery的朋友看起来就非常容易了。
     
      1:创建一个学生类的集合,学生类结构如下:
     

    public class student
    {
        
public string sname { getset; }
        
public int ID { getset; }
        
public int Grade { getset; }
    }

 

     2:写一个根据学生ID查找学生信息的方法。这里注意下,这个方法的返回类型为JsonResult,它能够给客户端以json类型输出数据(MVC能够把目标对象转换成json格式),这个和平时常见的ActionResult有所区别。
     

复制代码
代码
public JsonResult TestMVC(int i, int j)
        {
            
int I = 0;
            List
<student> list = new List<student>();
            
for (int k = 0; k < 10; k++)
            {
                student sd 
= new student() { sname = "aaa" + k.ToString() + j.ToString(), ID = k, Grade = k * 10 };
                list.Add(sd);
            }
            var stu 
= (from m in list
                       
where m.ID == i
                       select m
                         ).FirstOrDefault();

            JsonResult J 
= new JsonResult();
            J.Data 
= stu;
            
return J;
        }
复制代码

      
      3:客户端代码:从后台取得数据后,填充到div中。
       

$.getJSON('/Home/TestMVC',{i:1,j:2},
                function(data) {
                    $(
"#divStudent").html(data.sname);
                }
                );

             
       分析:以上三步基本上就可以实现一般的ajax程序,如有不同,也只可能是程序写法问题,大体流程都差不多应该相同。这种写法已经非常简洁了,但还有可以提高的地方。


             第一:开发人员需要拼接ajax请求的地址。本例中为Home/TestMVC
             第二:开发人员需要准备构建ajax方法使用的data参数。本例中为,{i:1,j:2}
       
      解决思路:让程序自动为我们完成上面两步。可以参考ajaxpro的实现原理,每个方法异步请求的方法上 加一个自定义特性标签,编译器遇到自定义标签后,自动生成一些js方法,来让开发者前端调用更加方便。例如生成如下代码:i,j分别是异步请求方法的两个 参数,callback为异步请求后的回调方法。
       

复制代码
代码
   var HomeController = {
            TestMVC: function(i, j,callback)
                                                {
                                                     $.getJSON(
'/Home/TestMVC?id=&',{i:i, j:j}, callback);
                                                }
        }
复制代码

  

     我们可以这样调用:我们只需要输入相应参数,以及完成回调方法即可。是不是简单了点。下一篇来讲讲具体实现方法。
        

 

HomeController.TestMVC(j,j+1, function(data) {
             $(
"#divStudent").html(data.sname);
             });    
文章的出处:http://www.cnblogs.com/ASPNET2008/archive/2010/03/07/1680061.html