1.MVC概念
--Model:用于存储数据的组件
--View:根据Model数据进行内容展示的组件--Controller:接受并处理用户指令(操作Model),选择一个View并输出内容。
Controller对View进行引用,但是View不知道Controller的存在。Controller和View都是单向引用Model
MVC变种:Observer模式,MVP模式。
2.mvc路由机制
MVC中重要的路由处理,默认情况是在Global.asax文件中,我们也可以把这块内容独立出来。
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的部分代码:
我们使用传统的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 string ErrorBox( this HtmlHelper htmlHelper, ViewDataBase errorViewData)
{
if (errorViewData.ErrorMessage == null ) return 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 == null ) return 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
3 public string Message { get ; set ; }
4 public string ErrorMessage { get ; set ; }
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 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。
{
ControllerBuilder.Current.DefaultNamespaces.Add( " GuestBook.MVC.Controller " );
ModelBinders.Binders.Add( typeof (GuestBookInfo ), new GuestBookBinder ());
RegisterRoutes(RouteTable.Routes);
}
3:除了上面的注册方法外,可以直接把 ModelBinderAttribute 用在对应的实体上 ,但不推荐这样做。
public class GuestBookInfo
4:除了由 ControllerActionInvoker.GetParameterValue() 自动完成 BindModel 操作外,还提供了两个方法:这两个方法??一的区别在于,TryUpdateModel不会抛异常,前者会。
1>:Controller.UpdateModel()
2>:Controller.TryUpdateModel()
示例:例如更新一则留言时,我们可以这样写:
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方法。
{
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这个接口来完成。
{
IDictionary < string , object > dictionary = tempDataProvider.LoadTempData(controllerContext);
this ._data = (dictionary != null ) ? new Dictionary < string , object > (dictionary,
StringComparer.OrdinalIgnoreCase) : new Dictionary < string , object > (StringComparer.OrdinalIgnoreCase);
this ._initialKeys = new HashSet < string > ( this ._data.Keys);
this ._modifiedKeys.Clear();
}
第三:ITempDataProvider:我们可以在第一条中的代码中发现,接口是这样取的:
{
get
{
if ( this ._tempDataProvider == null )
{
this ._tempDataProvider = new SessionStateTempDataProvider();
}
return this ._tempDataProvider;
}
set
{
this ._tempDataProvider = value;
}
}
第四:SessionStateTempDataProvider,从这个名字我们就可以猜测,数据应该是用Session方式保存。主要包含了两个方法,分别用于加载数据和保存数据。
{
HttpContextBase httpContext = controllerContext.HttpContext;
if (httpContext.Session == null )
{
throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
}
Dictionary < string , object > dictionary = httpContext.Session[ " __ControllerTempData " ] as Dictionary < string ,
object > ;
if (dictionary != null )
{
httpContext.Session.Remove( " __ControllerTempData " );
return dictionary;
}
return new Dictionary < string , object > (StringComparer.OrdinalIgnoreCase);
}
public virtual void SaveTempData(ControllerContext controllerContext, IDictionary < string , object > 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方法:
{
Controller controller = base .CreateController(requestContext, controllerName) as Controller ;
if ( null != controller)
{
controller.TempDataProvider = new MyTempDataProvider();
}
return controller;
}
3:注册我们自定义的MyControllerFactory,这也是最后一步。
{
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。
public ActionResult Edit(int id)
自定义Filter:
这里创建一个没有客户端缓存的NoClientCacheAttribute。需要继承ActionFilterAttribute,且重写OnActionExecuting方法。
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext.Current.Response.CacheControl = " No-Cache " ;
}
}
应用在Action上,特别简单,像C#中的变通特性用法一样。
public ActionResult Details( int id)
8. System.Web.Mvc.Html下的HtmlHelper
System.Web.Mvc.Html下的HtmlHelper只能完成大部分html控件的输出,但像img标签默认是没有提供的,这里需要我们自行来扩展下Helper,毕竟上面的众多方法都是扩展出来的。
扩展Helper,我们可以利用TagBuilder,它能输出所有标签及属性。TagBuilder提供下如下重要方法:
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 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:页面调用。
{border = " 4px " }) %>
9.MVC下开发ajax程序
MVC有个特点,一般情况下一个页面文件都会对应一个Controller,类似于web form模式下的页面后台代码。Conntroller里面的每个公共方法(私有方法不行)都可以通过页面地址中访问,例如我们在HomeController中有这样一个方法:
{
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 string sname { get ; set ; }
public int ID { get ; set ; }
public int Grade { get ; set ; }
}
2:写一个根据学生ID查找学生信息的方法。这里注意下,这个方法的返回类型为JsonResult,它能够给客户端以json类型输出数据(MVC能够把目标对象转换成json格式),这个和平时常见的ActionResult有所区别。
{
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中。
function(data) {
$( " #divStudent " ).html(data.sname);
}
);
分析:以上三步基本上就可以实现一般的ajax程序,如有不同,也只可能是程序写法问题,大体流程都差不多应该相同。这种写法已经非常简洁了,但还有可以提高的地方。
第一:开发人员需要拼接ajax请求的地址。本例中为Home/TestMVC
第二:开发人员需要准备构建ajax方法使用的data参数。本例中为,{i:1,j:2}
解决思路:让程序自动为我们完成上面两步。可以参考ajaxpro的实现原理,每个方法异步请求的方法上加一个自定义特性标签,编译器遇到自定义标签后,自动生成一些js方法,来让开发者前端调用更加方便。例如生成如下代码:i,j分别是异步请求方法的两个参数,callback为异步请求后的回调方法。
TestMVC: function(i, j,callback)
{
$.getJSON( ' /Home/TestMVC?id=& ' ,{i:i, j:j}, callback);
}
}
我们可以这样调用:我们只需要输入相应参数,以及完成回调方法即可。是不是简单了点。下一篇来讲讲具体实现方法。
$( " #divStudent " ).html(data.sname);
});