ASP.NET MVC 入门10、Action Filter 与 内置的Filter实现(实例-防盗链)

by 陈少俊 2011.5.25 09:35
本系列文章基于ASP.NET MVC Preview5. 前一篇中我们已经了解了Action Filter 与 内置的Filter实现,现在我们就来写一个实例。就写一个防盗链的Filter吧。 首先继承自FilterAttribute类同时实现IActionFilter接口,代码如下: /**//// <summary> /// 防盗链Filter. /// </summary> public class AntiOutSiteLinkAttribute : ActionFilterAttribute, IActionFilter { public AntiOutSiteLinkAttribute(FileType fileType) { this.FileType = fileType;     } /**//// <summary> /// 请求的文件类型.(文件或图片) /// </summary> public FileType FileType { get; set; } IActionFilter 成员#region IActionFilter 成员 void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext) {         HttpContextBase httpContext = filterContext.HttpContext; if (null != httpContext.Request.UrlReferrer) { string serverDomain = httpContext.Request.Url.Host; string refDomain = httpContext.Request.UrlReferrer.Host; if (GetRootDomain(refDomain).Equals(GetRootDomain(serverDomain), StringComparison.OrdinalIgnoreCase)) { return;//如果根域名相同就返回             }         }         ContentResult cr = new ContentResult(); if (FileType == FileType.Image) {             cr.ContentType = "image/jpeg";             FileInfo fi = new FileInfo(httpContext.Server.MapPath("~/Content/images/outsitelink.jpg")); if (fi.Exists) {                 httpContext.Response.WriteFile(fi.FullName);             } else {                 Bitmap bmp = new Bitmap(200, 50);                 Graphics g = Graphics.FromImage(bmp);                 g.FillRectangle(Brushes.White, 0, 0, 200, 50);                 g.DrawString("请不要盗链", new Font("Arial", 15), Brushes.Red, new PointF(0, 0));                 bmp.Save(httpContext.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif);             }         } else {             cr.ContentType = "text/html";             cr.Content = string.Format("请不要盗链。返回<a href='{0}'>{1}</a>", Utils.AbsoluteWebRoot, BlogSettings.Instance.Name);         } //将当前的上下文的ActionResult设置为我们的cr(ContentResult)         filterContext.Result = cr;     } #endregion /**//// <summary> /// 获取网站的根域名 /// </summary> /// <param name="domain">网站的域名,不带"Http://"</param> /// <returns></returns> private string GetRootDomain(string domain) { if (string.IsNullOrEmpty(domain)) { throw new ArgumentNullException("参数'domain'不能为空");         } string[] arr = domain.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); if (arr.Length <= 2) { return domain;         } else { return arr[arr.Length - 2] + "." + arr[arr.Length - 1];         }     } } public enum FileType {     File = 1,     Image } 然后我们建立一个用于处理文件请求的Controller,并应用上我们刚才建立的Filter: public class FilesController : BaseController {     [AntiOutSiteLink(FileType.Image)] public ActionResult Image(string file)     { return Content("Image From 4mvc");     }      [AntiOutSiteLink(FileType.File)] public ActionResult File(string file)     { return Content("File From 4mvc");     } } 简单测试一下: 对于ActionFilter的应用是很广泛的,这需要你的灵活运用。对于其他应用,你可以参考我之前翻译的ASP.NET MVC Action Filter - 缓存与压缩 这一篇文章。 Enjoy!这个没有什么代码,就暂不传代码了。

ASP.NET MVC 入门9、Action Filter 与 内置的Filter实现(介绍)

by 陈少俊 2011.5.25 09:35
本系列文章基于ASP.NET MVC Preview5. 有时候你想在调用action方法之前或者action方法之后处理一些逻辑,为了支持这个,ASP.NET MVC允许你创建action过滤器。Action过滤器是自定义的Attributes,用来标记添加Action方法之前或者Action方法之后的行为到控制器类中的Action方法中。 一些可能用到Action过滤器的地方有: 日志,异常处理 身份验证和授权 - 限制用户的访问 输出缓存 - 保存一个Action的结果 网络爬虫的过滤 本地化 动态Action - 将一个Action注入到控制器中 ASP.NET MVC为我们提供了下面的几个Filter接口: IActionFilter IAuthorizationFilter IExceptionFilter IResultFilter 要实现一个Filter,我们需要继承自FilterAttribute类同时实现上面的一个或几个接口: public class MyFilter : FilterAttribute, IActionFilter, IResultFilter { } 这几个接口提供的方法如下: 上图的方法和Filter接口对应的方法按名称对号入座就可以。 IActionFilter接口有两个方法: 其中OnActionExecuting在执行Action方法之前会被调用,OnActionExecuted会在Action方法执行后调用。注意他们的参数分别是ActionExecutingContext和ActionExecutedContext。 ActionExecutedContext类包含一个 Canceled的属性,允许你取消当前的Action(怎么原来在P3中是在ActionExecutingContext的Canceled属性在P5中没有了呢?神奇.那么在OnActionExecuting的时候怎么取消一个Action呢?)。 FilterExcutedContext 类包含一个Exception属性和一个ExceptionHandled属性。如果Exception属性为null,则没有异常在action stack中,表明Action方法运行并没有发生错误。反之则为出现异常。如果将ExceptionHandled属性设置为true则表明在这个Filter中已经处理了异常。 IResultFilter接口也提供了两个方法: 他们分别在Action返回结果(例如return View();)之前和之后执行。和IActionFilter差不多就不多说了。 IAuthorizationFilter是一个用于身份验证的Filter。只提供了一个void OnAuthorization(AuthorizationContext filterContext)方法。 IExceptionFilter会在出现异常的时候调用,也是只 提供一个void OnException(ExceptionContext filterContext)的方法; 这些Filter可以被应用在类或者方法上,下面我们来看一下他们的执行顺序。首先我们写一个BaseController并加上两个Filter: [MyFilter2(Target = "BaseController")] [MyFilter1(Target="BaseController")] public class BaseController : Controller {  } 应为Controller类是实现这几个Filter接口的,所以我们在HomeController中重写Controller基类中的所有Filter接口的方法,并在HomeController类和里面的Filter方法加上我们自定义的MyFilter: HomeController [MyFilter2(Target = "HomeController")] //[MyFilter1(Target = "HomeController")]//注意我在这里把MyFilter1注释了. [HandleError] public class HomeController : BaseController {     [MyFilter2(Target = "HomeController.Filter")]     [MyFilter1(Target = "HomeController.Filter")] public ActionResult Filter()     { return Content("<div>这是在Action方法里面返回的内容!</div>");     }  protected override void OnActionExecuted(ActionExecutedContext filterContext)     {         filterContext.HttpContext.Response.Write("<div>这是在HomeController里面重写OnActionExecuted方法添加的内容!</div>");     }  protected override void OnActionExecuting(ActionExecutingContext filterContext)     {         filterContext.HttpContext.Response.Write("<div>这是在HomeController里面重写OnActionExecuting方法添加的内容!</div>");     }  protected override void OnAuthorization(AuthorizationContext filterContext)     {         filterContext.HttpContext.Response.Write("<div>这是在HomeController里面重写OnAuthorization方法添加的内容!</div>");     }  protected override void OnException(ExceptionContext filterContext)     {         filterContext.HttpContext.Response.Write("<div>这是在HomeController里面重写OnException方法添加的内容!</div>");         filterContext.ExceptionHandled = true;     }  protected override void OnResultExecuted(ResultExecutedContext filterContext)     {         filterContext.HttpContext.Response.Write("<div>这是在HomeController里面重写OnResultExecuted方法添加的内容!</div>");     }  protected override void OnResultExecuting(ResultExecutingContext filterContext)     {         filterContext.HttpContext.Response.Write("<div>这是在HomeController里面重写OnResultExecuting方法添加的内容!</div>");     } } 然后我们运行一下看看结果如何: 从运行结果我们可以看到,在Controller中重写的Filter会最先执行,然后到应用在类上的Filter,然后再到应用在类方法上的Filter。 而4个接口的方法执行顺序如下:IAuthorizationFilter -> IActionFilter -> IResultFilter -> IExceptionFilter . 而对于同一个Filter,例如IAuthorizationFilter在MyFilter1和MyFilter2里里面的实现,又根据他们的加载顺序不同而不同。 在BaseController中应用的Filter会被子类继承,如果子类又应用了和基类同样的Filter,则会不执行基类的Filter。例如上面的HomeController应用了MyFilter2,所以调用HomeController的MyFilter2,而不是BaseController的MyFilter2。 这个执行顺序还得大家好好研究才能了解的。 同时FilterAttribute还提供了一个Order的属性,用于指定Filter的执行顺序。 每一个Action过滤器都有一个 Order 属性,用来决定Action过滤器在该范围内的执行顺序。Order属性必需是0(默认值)或者更大的整数值。省略Order属性则会给该过滤器的Order值为 -1, 表明未指明顺序。任何一个在同一范围的Action过滤器Order设为 -1 的都将按不确定的顺序执行,但在此之前过滤器有一个特定的顺序(请参考上图). 当设置Order属性的值的时候,必需指定一个唯一的值。如果两个或者更多的Action过滤器具有相同的Order属性值,将会抛出一个异常。 来看一个示例: [Filter1(Order = 2)] [Filter2(Order = 3)] [Filter3(Order = 1)] public void Index() {     RenderView("Index"); } Filter的执行顺序为:Filter3 => Filter1 => Filter2. 暂时就写这么多吧,这一部分主要介绍概念。Enjoy!Post by Q.Lee.lulu。 本文的Blog程序示例代码: ActionFilter.rar

ASP.NET MVC 入门8、ModelState与数据验证

by 陈少俊 2011.5.25 09:34
ViewData有一个ModelState的属性,这是一个类型为ModelStateDictionary的ModelState类型的字典集合。在进行数据验证的时候这个属性是比较有用的。在使用Html.ValidationMessage()的时候,就是从ViewData.ModelState中检测是否有指定的KEY,如果存在,就提示错误信息。例如在前一篇文章ASP.NET MVC 入门7、Hellper与数据的提交与绑定中使用到的UpdateModel方法: 我们在View中使用Html.ValidationMessage(string modelName)来对指定的属性进行验证: Html.ValidationMessage()有几个重载: 其中ValidationSummary()是用于显示全部的验证信息的。跟ASP.NET里面的ValidationSummary验证控件差不多。 我们测试一下/Admin/Setting页面: 在用UpdateModel方法更新BlogSettings.Instance.PostsPerPage的时候,当我们如图所示填写"10d"的时候,由于PostsPerPage为整型的,所以UpdateModel方法就会出错,同时会往ViewData.ModelState添加相应的错误信息,从而Html.ValidationMessage()方法就可以从ViewData.ModelState中检测到错误并提示。同时Html.ValidationMessage()方法会为出错的属性的输入框添加一个名为"input-validation-error"的CSS类,同时后面的提示信息的CSS类名为"field-validation-error": CSS类的样式是可以由我们自己自由定义的。如上图的红色高亮显示。 好,下面我们来实现发表新随笔的功能。我们先写一个提供用户输入随笔内容的表单页面: <p> <label for="Title">标题</label> <%=Html.TextBox("Title", new { id = "Title", @class = "required" })%> <%=Html.ValidationMessage("Title")%> </p> <p> <label for="Content">内容</label> <%=Html.TextArea("Content")%> <%=Html.ValidationMessage("Content")%> </p> <p> <label for="Slug">URL地址别名(如果为空则和标题同名)</label> <%=Html.TextBox("Slug", new { id = "Slug", @class = "required" })%> <%=Html.ValidationMessage("Slug")%> </p> 然后我们对用户提交过来的数据进行保存: [AcceptVerbs("POST"), ActionName("NewPost")] public ActionResult SaveNewPost(FormCollection form) {     Post post = new Post(); try     {         UpdateModel(post, new[] { "Title", "Content", "Slug" });     } catch     { return View(post);     }     post.Save(); return ShowMsg(new List<string>() { "发表新随笔成功" }); } 由于这三个值都是字符串类型,所以如果值为空的话,UpdateModel也是不会出错的,而我们的Title和Content是不允许为空的,或者我们想我们的Slug的长度不能超过100,也就是需要有我们自己的业务规则。这时候我们或许会这样写: try {     UpdateModel(post, new[] { "Title", "Content", "Slug" }); } catch { return View(post); } if (string.IsNullOrEmpty(post.Title)) {     ViewData.ModelState.AddModelError("Title", post.Title, "标题不能为空"); } if (string.IsNullOrEmpty(post.Content)) {     ViewData.ModelState.AddModelError("Content", post.Content, "内容不能为空"); } if (!ViewData.ModelState.IsValid) { return View(post); } ViewData.ModelState提供了一个AddModelError的方法,方便我们添加验证失败的信息。我们可以如上代码这样进行对象的业务规则验证,但是一旦业务规则多了,这样的代码是非常壮观的,而且不好控制。那么我们该怎么更好的进行业务规则的验证呢?得意于BlogEngine.Net的良好架构,我们可以很轻松的完成这一点。 首先,让我们修改一下BlogEngine.Core里面BusinessBase的代码。我们前面说过,BusinessBase实现了IDataErrorInfo接口,该接口有个索引器,导致ViewData.Eval()方法调用时搜索索引器的值时返回String.Empty而使ViewData.Eval()认为是找到值了,从而失效。 我们可以将return string.Empty修改为return null。但我们这里并不需要用到这个接口,所以我们把该接口去掉,并把相应的代码注释了。然后我们再暴露一个BrokenRules的属性,用于返回当前的所有破坏性业务规则(红框部分代码为我们添加的): BusinessBase提供了一个抽象的ValidationRules方法,用于在业务类重写这个方法往里面添加验证规则(具体请看BusinessBase的Validation节)。 Validation Validation#region Validation private StringDictionary _BrokenRules = new StringDictionary(); /**//// <summary> /// 获取所有的破坏性规则。 /// 在获取前请用IsValid进行判断。 /// </summary> public StringDictionary BrokenRules { get { return _BrokenRules;     } } /**//// <summary> /// Add or remove a broken rule. /// </summary> /// <param name="propertyName">The name of the property.</param> /// <param name="errorMessage">The description of the error</param> /// <param name="isBroken">True if the validation rule is broken.</param> protected virtual void AddRule(string propertyName, string errorMessage, bool isBroken) { if (isBroken) {         _BrokenRules[propertyName] = errorMessage;     } else { if (_BrokenRules.ContainsKey(propertyName)) {             _BrokenRules.Remove(propertyName);         }     } } /**//// <summary> /// Reinforces the business rules by adding additional rules to the /// broken rules collection. /// </summary> protected abstract void ValidationRules(); /**//// <summary> /// Gets whether the object is valid or not. /// </summary> public bool IsValid { get {         ValidationRules(); return this._BrokenRules.Count == 0;     } } /**//// /// <summary> /// If the object has broken business rules, use this property to get access /// to the different validation messages. /// </summary> public virtual string ValidationMessage { get { if (!IsValid) {             StringBuilder sb = new StringBuilder(); foreach (string messages in this._BrokenRules.Values) {                 sb.AppendLine(messages);             } return sb.ToString();         } return string.Empty;     } } #endregion 我们在Post类中重写这个方法来添加验证规则: 然后我们可以在Controller的Action中很优雅的书写我们的代码来进行业务规则的验证: [AcceptVerbs("POST"), ActionName("NewPost")] public ActionResult SaveNewPost(FormCollection form) {     Post post = new Post(); try     {         UpdateModel(post, new[] { "Title", "Content", "Slug" });     } catch     { return View(post);     } if (!post.IsValid)     { foreach (string key in post.BrokenRules.Keys)         {             ViewData.ModelState.AddModelError(key, form[key], post.BrokenRules[key]);         } return View(post);     }     post.Save(); return ShowMsg(new List<string>() { "发表新随笔成功" }); } 我们注意到上面的Action中用到了一个FormCollection 的参数,这个参数系统会自动将Form提交过来的全部表单值(Request.Form)赋给它的。客户端验证可以用jQuery的验证插件来,这里就不罗嗦了。 暂时就写这么多吧,想到什么再补充。Enjoy!Post by Q.Lee.lulu。 本文的Blog程序示例代码: 4mvcBlog_8.rar

Tags:

ASP.NET MVC

ASP.NET MVC 入门7、Hellper与数据的提交与绑定

by 陈少俊 2011.5.25 09:32
本系列文章基于ASP.NET MVC Preview5. ASP.NET MVC提供了很多Hellper的方法,Hellper就是一些生成HTML代码的方法,方便我们书写HTML代码(有一部分的朋友更喜欢直接写HTML代码)。我们也可以利用.NET 3.5的扩展方法来书写我们自己的Hellper。 例如: <%=Html.ActionLink("首页","index","Home")%> 生成的HTML代码就是:<a href="/Home/Index">首页</a>。这里有一点需要注意的就是,Html.ActionLink()和Url.Link()方法生成的URL和你在Global.asax里面配置的Route的先后顺序是有关的。 具体的关于Hellper的各个方法的使用我就不详细介绍了,你可以参考重典的Asp.net Mvc Framework 系列文章。 我们来实现前面提到的Setting,用于设置Blog的基本设置。我们看一下在Views/Admin/Setting.aspx页面的代码: <p> <label for="Name">Blog的名称</label> <%=Html.TextBox("Name")%> <%=Html.ValidationMessage("Name")%> </p> <p> <label for="Description">Blog的简单描述</label> <%=Html.TextArea("Description")%> <%=Html.ValidationMessage("Description")%> </p> <p> <label for="PostsPerPage">每页显示的日志数</label> <%=Html.TextBox("PostsPerPage")%> <%=Html.ValidationMessage("PostsPerPage")%> </p> 我们的Setting Action方法是这样写的: 注意我们是return View(BlogSettings.Instance);给ViewData.Model传递了BlogSettings.Instance。然后运行一下看看: 注意到上面的TextBox都有值了么?我们使用了Html.TextBox("Name")而已,并没有指定值啊?那么这个值是怎么自动的绑定上去的呢?注意到前面在Action中我们是return View(BlogSettings.Instance);给ViewData.Model传递了BlogSettings.Instance,BlogSettings.Instance.Name和Html.TextBox("Name")的参数"Name"同名,Html.TextBox()方法在调用的时候会调用ViewData.Eval()方法,所以就自动把值绑定上去了。和前一篇文章说过的ViewData.Eval()可以使用"."表达式一样,Html.TextBox()也同样可以使用"."表达式,例如:Html.TextBox("Post.Title")也可以自动绑定ViewData.Model.Post.Title值上去。 下面我们来将值提交到Action中并保存。我们的Form使用了Post回服务器: <form id="fields" action="<%=Url.Action("Setting","Admin")%>" method="post"> 然后我们在Action中进行处理: [ActionName("Setting"), AcceptVerbs("POST")] public ActionResult SaveSetting() { //当然你可以这样取值 //string name = Request.Form["Name"];  //但我们有更简便的: try     {         UpdateModel(BlogSettings.Instance, new[] { "Name", "Description", "PostsPerPage" });     } catch     { return View(BlogSettings.Instance);     }  //在这里你可能还需要对自定义的业务逻辑进行验证      BlogSettings.Instance.Save(); return ShowMsg(new List<string>() { "修改设置成功" }); } 如上代码所示,我们可以使用UpdateModel()方法直接对Post到服务器来的Form表单的值赋给指定的对象。对于提交的数据进行业务逻辑的验证这里要讨论的还是比较多的,国外也有不少文章讨论这个的,大家可以搜索看看。 暂时就写这么多吧,想到什么再补充。Enjoy!Post by Q.Lee.lulu。 本文的Blog程序示例代码:4mvcBlog_7.rar

Tags:

ASP.NET MVC

ASP.NET MVC 入门6、TempData

by 陈少俊 2011.5.25 09:31
本系列文章基于ASP.NET MVC Preview5. ASP.NET MVC的TempData用于传输一些临时的数据,例如在各个控制器Action间传递临时的数据或者给View传递一些临时的数据,相信大家都看过“在ASP.NET页面间传值的方法有哪几种”这个面试题,在ASP.NET MVC中TempData的就是其中的一种传值方法。TempData默认是使用Session来存储临时数据的,TempData中存放的数据只一次访问中有效,一次访问完后就会删除了的。这个一次访问指的是一个请求到下一个请求,因为在下一个请求到来之后,会从Session中取出保存在里面的TempData数据并赋值给TempData,然后将数据从Session中删除。我们看一下ASP.NET MVC Preview5源码: 也就是说TempData只保存到下一个请求中,下一个请求完了之后,TempData就会被删除了。注意这里TempData使用Session来做存储的,Session是对应特定用户的,所以并不存在并发问题。如果你用数据库来做TempData的存储介质的话,必须要考虑这个情况的。至于如何自定义TempData的存储介质,你可以参考“ASP.NET MVC: 用db4o来做TempDataProvider(另附一个泛型的RedirectToAction方法)”这一篇文章。 前面说到的在我们的BaseController中有一个显示提示信息给用户的方法,这个提示信息就是临时的信息,我们可以使用TempData来实现。下面让我们来实现这个提示信息的方法: protected ActionResult ShowMsg(List<string> msgs) {     TempData["Messages"] = msgs; return RedirectToAction("Message"); }  public ActionResult Message() { return View(TempData["Messages"] as List<string>); } 因为我们的Controller都继承自这个我们自定义的BaseController,所以我么可以才Controller中这样来给用户显示提示信息: 好,这一部分就先到这里吧。Enjoy!Post by Q.Lee.lulu。 本文示例Blog的代码:4mvcBlog_5.rar

Tags:

ASP.NET MVC

ASP.NET MVC 入门5、View与ViewData

by 陈少俊 2011.5.25 09:30
本系列文章基于ASP.NET MVC Preview5. view在MVC模式中与用户进行最直接的接触,它负责数据的呈现。这里要注意一点就是,view只是负责数据的呈现,所以我们应该要尽量让view中不涉及业务逻辑的处理。 我们来添加一个Blog首页的view。在安装了ASP.NET MVC后,我们在添加新项目的时候可以看到有MVC的view模板: 注:如果你的是中文版的VS,安装完后可能会出现找不到这个模板的现象,你可以参考在中文版VS 08中安装MVC这篇文章设置一下。 其中MVC View Content Page是有母版页的。我们在Views/Home目录下添加一个MVC View Content Page,并选择我们Views/Shared目录下的Site.Master母版页: public partial class Index : ViewPage { } ASP.NET MVC默认是使用WebForm来作为view的。所以我们看到新建的aspx页面继承自ViewPage,如果使用aspx页面作为ASP.NET MVC的视图引擎,则所有的aspx页面都必须继承自ViewPage。我们再看一下ViewPage: 我们看到ViewPage继承自ASP.NET WebForm的Page页,还实现了IViewDataContainer接口,同时还提供了一些Helper类的实例。我们可以使用ViewData来从Controller中往view页面中传递数据。下面我们在HomeController中的Index Action中取出Posts列表,然后在View中显示。我们先在Controller中取出数据,前面说过,为了方便,我们会直接使用BlogEngine的Model层来作为我们这个4mvcBlog的Model。所以我们的代码如下: public ActionResult Index(int? id) {     ViewData["Title"] = BlogSettings.Instance.Name;     List<IPublishable> posts = BlogEngine.Core.Post.Posts         .ConvertAll(new Converter<Post, IPublishable>(delegate(Post p) { return p as IPublishable; })); int pageIndex = (id != null && id.HasValue && id.Value > 0) ? id.Value : 1; int pageSize = Math.Min(posts.Count, BlogSettings.Instance.PostsPerPage); if ((pageIndex - 1) * pageSize + pageSize > posts.Count)     { return ShowMsg(new List<string>() { "页码超出范围" });     }     posts = posts.GetRange((pageIndex - 1) * pageSize, pageSize);     ViewData["Posts"] = posts; //向ViewData中传数据 //这里返回View给客户端,如果不指定要返回的View的名称, //就是返回和Action同名的View, //也就是相当于return View("Index"); return View(); } 默认的WebFormView搜索View的顺序是按如下顺序搜索的: 其中{1}为ControllerName,{0}为ActionName。MasterLocationFormats为母版页的搜索顺序。 在上面的代码中我们使用ViewData["Posts"]向View页面传递数据,然后我们就可以在View中取出数据并呈现给用户,Views/Home/Index.aspx页面的部分代码如下: 如上红色框中的代码,我们可以从ViewData中取出数据,并转换为相应的类型。在这里我们发现ViewData要做一个类型的转换,其实我们可以将ViewData.Model设置为强类型,只需将我们的View页面继承自ViewPage<TModel>就可以了: 然后在Controller里面return View()的时候直接给ViewData.Model传值,如下所示: 然后在View中我们可以直接从强类型的ViewData.Model中取值: 由上面的代码我看可以看出ViewData.Model就是List<IPublishable>类型,并不需要再进行类型的转换。 ViewData还有一个Eval的方法,我们可以使用这个方法从ViewData中取值。假如我么在Action中使用return View(Post);给View传递一篇日志的数据。而Post有一个Previous的属性指向前一篇日志,则我们可以在View页面中可以这样来取值: <%= ViewData.Eval("Previous.Title")%> 但是如果使用我最后提供的示例Blog程序的代码这样在取值的时候直接在里面使用"."来取值,你会发现取不了值。因为BlogEngine里面的BusinessBase类实现了IDataErrorInfo接口,而IDataErrorInfo有一个索引器,也就是说BusinessBase有一个索引器,就因为有一个索引器,使Eval中不能用点来取值(不知道是不是bug?)。 补充:上面说到的不是Bug,是因为BusinessBase实现了IDataErrorInfo接口,该接口有个索引器,导致ViewData.Eval()方法调用时搜索索引器的值时返回String.Empty而使ViewData.Eval()认为是找到值了,从而失效。 我们可以将return string.Empty修改为return null,这样就可以了。 好,这一部分就先到这里吧。Enjoy!Post by Q.Lee.lulu。 本文示例Blog的代码:4mvcBlog_5.rar

Tags:

ASP.NET MVC

ASP.NET MVC 入门4、Controller与Action

by 陈少俊 2011.5.25 09:25
本系列文章基于ASP.NET MVC Preview5. Controller是MVC中比较重要的一部分。几乎所有的业务逻辑都是在这里进行处理的,并且从Model中取出数据。在ASP.NET MVC Preview5中,将原来的Controller类一分为二,分为了Controller类和ControllerBase类。Controller类继承自ControllerBase类,而ControllerBase实现是了IController接口。 ControllerBase实现了IController接口的Execute方法,在Route匹配到Controller之后,就会调用Execute方法来进入Controller的处理。这里还定义了一个抽象的方法ExecuteCore方法,该方法会在Execute方法的最后被调用。ControllerBase还定义了三个核心的属性。我们在后面会详细讨论TempData和ViewData。 Controller类除了继承自ControllerBase类以外,还实现了好几个Filter接口,Filter我们在后面再详细讨论。 public abstract class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter{ } Controller类还定义很多有用的方法,我们新建的Controller都必须继承自这个Controller类。例如我们新建一个AdminController: public class AdminController : Controller { } Action方法 下面谈一下在Controller中比较重要的Action方法。在ASP.NET MVC中URL都是映射到Controller中的某个Action中,然后由匹配的Action来处理我们的业务逻辑并返回view的。 Controller中的public的方法都被当作是Action方法。Action方法通常返回一个ActionResult的结果。例如我们为前面的AdminController定义一个Setting的Action方法,用于设置Blog的一些基本参数: public class AdminController : Controller { public ActionResult Setting() { throw new NotImplementedException(); } } 默认情况下,Action方法的方法名就是这个Action的Action名(Action名指的是Route中匹配Action方法的URL的那部分。例如url:Home/Index,其中Index就是Action名)。这里为什么要提到这个Action名呢?应为Action名是可以定义的,使用ActionNameAttribute来定义。请看下面的示例: public ActionResult Setting() { throw new NotImplementedException(); } [ActionName("Setting")] public ActionResult SaveSetting() { throw new NotImplementedException(); } 这两个Action方法的Action名都为"Setting",即对于url:Admin/Setting ,能同时匹配到这两个Action方法。如果一个URL同时匹配到两个Action方法的话,程序会抛出一个错误: 如果我们希望这两个Action的Action名都为Setting,Setting()就用于显示一个表单页面给用户,而SaveSetting()就用于保存用户提交过来的表单数据,我们该怎么做呢?我们可以利用AcceptVerbsAttribute来设置,这个Attribute用来定义Action方法会匹配指定的HttpMethod。例如下面的代码: [AcceptVerbs("GET")] public ActionResult Setting() { throw new NotImplementedException(); } [ActionName("Setting"), AcceptVerbs("POST")] public ActionResult SaveSetting() { throw new NotImplementedException(); } 这样,对于HttpMethod为"GET"的客户端请求,就会匹配到Setting()来显示一个表单给用户,如果用户POST回来的表单数据,则会匹配到SaveSetting()上面去,我们就可以处理用户POST过来的数据并保存到数据库。 在这里AcceptVerbsAttribute是继承自ActionSelectionAttribute的,我们也可以继承自ActionSelectionAttribute来自定义自己想要实现的功能。这个我们后面会详细讲解。如果你比较心急,可以看下Asp.net Mvc Preview 5 体验--实现ActionSelectionAttribute来判断是否为AJAX请求而选择不同的Action这篇文章。 如果你想将一个public的方法设置为不是Action方法,那么你就要为该public的方法添加NonAction的Attribute: Action方法的参数 例如我们要在AdminController中定义一个编辑日志的Action方法: public ActionResult EditPost(int? id) { throw new NotImplementedException(); } 对于URL:Admin/EditPost/2 ,上面的参数会自动被赋值为2。ASP.NET MVC在匹配Route的时候会根据Route的设置自动为Action方法的参数赋值。所以前面的id参数会被自动赋值为2的前提是,在Route配置的时候,必须指定了id参数,例如: routes.MapRoute( "Default", // Route 的名称 "{controller}/{action}/{id}", // 带有参数的URL new { controller = "Home", action = "Index", id = "" } // 设置默认的参数 ); 如果我们将Route修改为: routes.MapRoute( "Default", // Route 的名称 "{controller}/{action}/{para}", // 带有参数的URL new { controller = "Home", action = "Index", para = "" } // 设置默认的参数 ); 则前面的Action方法的参数必须修改为public ActionResult EditPost(int? para){ },使Action方法的参数和Route中定义的参数名相同,ASP.NET MVC才能自动为Action方法的参数赋值。 ActionResult Action方法返回ActionResult类型的结果。ASP.NET MVC为我们提供了几种ActionResult的实现,如下: ViewResult. 呈现视图页给客户端。由View 方法返回. RedirectToRouteResult. 重定向到另外一个Route。由RedirectToAction 和RedirectToRoute 方法返回. RedirectResult. 重定向到另外一个URL。由 Redirect 方法返回. ContentResult. 返回普通的内容。例如一段字符串。由 Content 方法返回. JsonResult. 返回JSON结果。由 Json 方法返回. EmptyResult. 如果Action必须返回空值,可以返回这个结果。Controller中没有实现的方法,可以return new EmptyResult();. 当然我们也可以自定一个我们的ActionResult返回给客户端,例如一个RssResult。可以参考Asp.Net MVC实践 - 自定义ActionResult实现Rss输出 (基于ASP.NET MVC Preview 3)这篇文章。 通常情况下,我们的Controller可能有一些相同的情况,例如我们在各个Controller中都有可能会在出错或者什么时候想要显示一条提示信息给用户,或者有一些共同的数据要呈现的。这时候,我们最好就定义一个我们自己的Controller的基类: public class BaseController : Controller { public BaseController() { } protected ActionResult ShowMsg(List<string> msgs) { throw new NotImplementedException(); } public ActionResult Message() { throw new NotImplementedException(); } } 然后,其他的Controller都继承自这个BaseController : public class AdminController : BaseController { [AcceptVerbs("GET")] public ActionResult Setting() { throw new NotImplementedException(); } [ActionName("Setting"), AcceptVerbs("POST")] public ActionResult SaveSetting() { throw new NotImplementedException(); } public ActionResult EditPost(int? id) { throw new NotImplementedException(); } } 好,时间不早了,就先到这里吧。Enjoy!Post by Q.Lee.lulu。

Tags:

ASP.NET MVC

ASP.NET MVC 入门3、Routing

by 陈少俊 2011.5.25 09:21
在一个route中,通过在大括号中放一个占位符来定义( { and } )。当解析URL的时候,符号"/"和"."被作为一个定义符来解析,而定义符之间的值则匹配到占位符中。route定义中不在大括号中的信息则作为常量值。 下面是一些示例URL: Valid route definitions Examples of matching URL {controller}/{action}/{id} /Products/show/beverages {table}/Details.aspx /Products/Details.aspx blog/{action}/{entry} /blog/show/123 {reporttype}/{year}/{month}/{day} /sales/2008/1/5 通常,我们在Global.asax文件中的Application_Start事件中添加routes,这确保routes在程序启动的时候就可用,而且也允许在你进行单元测试的时候直接调用该方法。如果你想在单元测试的时候直接调用它,注册该routes的方法必需是静态的同时有一个RouteCollection参数。 下面的示例是Global.asax中的代码,演示了添加一个包含两个URL参数action 和 categoryName的Route对象: public static void RegisterRoutes(RouteCollection routes) { //忽略对.axd文件的Route,也就是和WebForm一样直接去访问.axd文件 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Category", // Route 的名称 "Category/{action}/{categoryName}", // 带有参数的URL new { controller = "Category", action = "Index", categoryName = "4mvc" } // 设置默认的参数 ); } protected void Application_Start() { //在程序启动的时候注册我们前面定义的Route规则 RegisterRoutes(RouteTable.Routes); } 更多 文章请参考: System.Web.Routing入门及进阶 下篇 By 重典 System.Web.Routing入门及进阶 上篇 By 重典 ASP.NET MVC URL Routing 学习 By Q.Lee.lulu ASP.NET Routing (官方文档) 在这里我不打算再详细去讲解。以下只是简单的说明一下。 忽略对某类URL的Routing: //忽略对.axd文件的Route,也就是和WebForm一样直接去访问.axd文件     routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 添加约束条件,支持正则表达式。例如我们需要对id参数添加一个必须为数字的条件: routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" }, new { id = @"[\d]*" } //id必须为数字 ); 使用星号(*)匹配不确定个数的参数,这会匹配URL后面所有的剩余的参数。例如: query/{queryname}/{*queryvalues} 对于url:query/aspnetmvc/preview5/routing ,则queryvalues参数匹配的参数为 preview5/routing。 url匹配Route是根据Route的定义顺序来自上而下匹配的。例如我们定义两个Route: public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute( "Default", // Route 的名称 "{controller}/{action}/{id}", // 带有参数的URL new { controller = "Home", action = "Index", id = "" } // 设置默认的参数 ); routes.MapRoute( "Post", "Post/{id}", new { controller = "Post", action = "Index", id = "" } ); } 不知你看出上面定义的两个Route有什么问题没有?我想你看出来了,URL永远都匹配不了第二个Route,也就是名为Post的Route,因为能匹配第二个Route的url一样也能匹配第一个Route,而url匹配Route是根据Route的定义顺序来自上而下匹配的,所以URL永远都匹配不了第二个Route。所以,在定义Route的时候,要将一些特别的Route放到前面。 如果你要将ASP.NET MVC部署到IIS6下面,由于IIS6对于http://blog.51mvc.com/index这类没有扩展名的URL是不会交由ASP.NET的aspnet_isapi.dll处理的,所以你的ASP.NET MVC程序部署到IIS6的时候可能会出现404错误。你可以为你的ASP.NET MVC站点添加一个通配符: 然后点击"通配符应用程序映射"下的"插入"按钮,在弹出的对话框中如下设置: 你如果担心添加通配符会给出现性能上的问题,那么你可以修改Route为带扩展名的,这个扩展名是完全由你自己定义的,例如我们使用4mvc来做url的扩展名: routes.MapRoute( "Default", // Route 的名称 "{controller}.4mvc/{action}/{id}", // 带有参数的URL new { controller = "Home", action = "Index", id = "" } // 设置默认的参数 ); 然后再在IIS6中添加这个扩展名的映射: 然后我们访问的URL类似于:http://blog.51mvc.com/Home.4mvc/index 群上有些朋友说希望教程能根据一个示例程序来写,那样更容易他们的学习。所以这里就写一个Blog的示例程序,为了方便,Model就直接使用Blogengine的业务实体部分。在这里我们先定义这个blog的Route: BlogRoute public static void RegisterRoutes(RouteCollection routes) { //忽略对.axd文件的Route,也就是和WebForm一样直接去访问.axd文件 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Admin", "Admin/{action}", new { controller = "Admin", action = "Index" } ); routes.MapRoute( "PostById", "Post/{id}", new { controller = "Home", action = "Post", id = "" }, new { id = @"[\d]+" } ); routes.MapRoute( "PostBySlug", "Post/{slug}", new { controller = "Home", action = "Post" } ); routes.MapRoute( "Default", // Route 的名称 "{controller}/{action}/{id}", // 带有参数的URL new { controller = "Home", action = "Index", id = "" } // 设置默认的参数 ); } 暂时就学到这里吧。后面我会给出本示例Blog程序的代码。Enjoy!Post by Q.Lee.lulu。

Tags:

ASP.NET MVC

ASP.NET MVC 入门2、项目的目录结构与核心的DLL

by 陈少俊 2011.5.25 09:20
我们新建一个ASP.NET MVC的Web Application后,默认的情况下,项目的目录结构如下: App_Data :这个目录跟我们一般的ASP.NET website是一样的,用于存放数据。 Content :这个目录是建议用来存放一下资源文件的。例如CSS、JS、图片等等。当然你不愿意的话,完全可以不放到这里来。 Controllers :这个目录是建议将Controller类都放到这里来,方便管理。Controller类的命名必须以Controller结尾,例如一个名为Home的Controller则要命名为HomeController。 Models :这个目录是建议用来存放你的业务实体、数据访问层代码的类的。当然,更好的做法我觉得应该是将Models独立为一个类库。 Views :在默认情况下,所有的view文件都必须放到这个目录下来,每一个Controller对应一个子目录,而且子目录的命名必须以Controller的命名一样。例如,HomeController的view就应该放到Home子目录中。我们见到Views目录下还有一个Shared的子目录,这个子目录是用于存放一些共享的view的,例如Error.aspx和Site.Master。Controller在Views\ControllerNmae 中找不到指定的view的时候,会到Shared中去寻找。 下面我们来看一下ASP.NET MVC比较核心的DLL,见下图红框部分: System.Web.Routing :URL路由。将一个URL路由到对应的Controller上靠的就是这个。是在HttpModule里面处理的。 System.Web.Extensions :这个是ASP.NET AJAX的。 System.Web.Mvc: ASP.NET MVC最主要的程序集。在CodePlex上放出源代码的就是这个DLL。 System.Web.Abstractions :这个程序集是一些相关的基类来的。例如HttpContextBase、HttpRequestBase 等等。 Microsoft.Web.Mvc :这个程序集只要放一些MVC的特性与扩展的方法。在ASP.NET MVC beta版中,这个DLL已经被移除,但是如果你需要用到这个DLL的功能,可以到这里下载并引入就可以了。 我们完全可以在VS08中建一个一般的WebAppliction,然后引入这几个DLL,再配置一下web.config文件,那么就可以成为一个ASP.NET MVC的Application了。

Tags:

ASP.NET MVC

ASP.NET MVC 入门1、简介

by 陈少俊 2011.5.25 09:18
什么是MVC模式 MVC(Model-View-Controller,模型—视图—控制器模式)用于表示一种软件架构模式。它把软件系统分为三个基本部分:模型(Model),视图(View)和控制器(Controller)。 那么MVC模式和我们熟悉的WebForm模式有什么不同呢?他的各个部分又是怎样分工的呢? 我们先来看一下普通的WebForm模式下,我们请求一个例如http://www.51mvc.com/blog/index.aspx的URL,那么我们的WebForm程序会到网站根目录下去寻找blog目录下的index.aspx文件,然后由index.aspx页面的CodeBehind文件(.CS文件)进行逻辑处理,其中或许也包括到数据库去取出数据(其中的经过怎样的BLL到DAL这里就不谈了),然后再由index.aspx页面来呈现给用户。简单的示意图如下所示: 也就是一个URL请求的是在服务器与该URL对应路径上的物理文件(ASPX文件或其他),然后由该文件来处理这个请求并返回结果给客户端。 但是,对于MVC模式,这是怎样的一个过程呢? 我们先来建一个ASP.NET MVC的项目吧。VS2008默认是没有ASP.NET MVC的项目模板的,首先我们需要到http://www.microsoft.com/downloads/details.aspx?FamilyId=A24D1E00-CD35-4F66-BAA0-2362BDDE0766&displaylang=en去下载最新的ASP.NET MVC的安装程序,目前最新版本的Microsoft ASP.NET MVC Beta(10/15/2008)。下载安装完后,我们可以在新建项目那里找到ASP.NET MVC的项目: 注:如果你的是中文版的VS,安装完后可能会出现找不到这个模板的现象,你可以参考在中文版VS 08中安装MVC这篇文章设置一下。 建立一个ASP.NET MVC项目后,默认的项目大概如下图: 我们可以看到项目中有几个文件夹的命名和MVC(Model-View-Controller,模型—视图—控制器模式)是对应的。然后我们运行一下项目看看: 我们注意到地址栏的URL是 Home/Index,如果按照我们前面说的WebForm的模式的话,我们应该可以在我们的项目的根目录下找到Home目录,然后Home目录下有个Index的文件,但是我们并不能在根目录下找到Home这个目录。不过还是让我们在Views目录下找到了Views/Home/Index.aspx文件,我们输入这个地址运行看看: Oh,No!路径是对的,文件也存在,但为什么会是404,说找不到文件呢?如果不是直接访问存在的物理文件,那么MVC又是怎样工作的呢? 原来啊,MVC模式的工作过程是这样的: 在MVC中,客户端的所请求的URL是被映射到相应的Controller去,然后由Controller来处理业务逻辑,或许要从Model中取数据,然后再由Controller选择合适的View返回给客户端。再说回前面我们运行的ASP.NET MVC程序访问的http://localhost:2176/Home/Index这个URL,它访问的其实是HomeController中的Index这个Action,见下图: 其中public ActionResult Index()这个方法称为Controller的Action,他返回的是ActionResult的类型。一个Controller可以有很多个Action。 那么一个URL是怎样被定位到Controller中来的呢?我们先来看一下web.config文件,在web.config文件的httpModules配置节中,我们可以看到一个UrlRoutingModule: <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0 , Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 就是这个UrlRoutingModule来把URL定位到Controller中去的。而对于URL会被路由到哪一个Controller中去,这些我们是完全可以自己定义的。我们到Global.asax文件去看一下: 我们可以看到这里定义了一个名为"Default"的Route,还定义了默认的参数。默认参数的意义在于,当我们访问例如http://localhost:2176/的URL的时候,他会将不存在的参数用默认的参数补上,也就是相当于访问http://localhost:2176/Home/Index一样。 注意:我们知道在IIS中,我们访问网站的根目录的时候,如果我们不指定要访问的路径,IIS会自己根据在IIS中设置的默认文档去访问。例如我们访问http://localhost:2176/这个URL的时候,IIS会去寻找网站根目录下的Default.aspx文件(假设我们设置了IIS的默认文档为Default.aspx)。而在ASP.NET MVC中对于类似http://localhost:2176/这样的网站根目录的路径,并不会经过Route的处理,所以我们看到我们建立的ASP.NET MVC程序的根目录下有个Default.aspx文件,该文件就是用于处理前面的访问根目录的情况的。请不要删除该文件。它会将http://localhost:2176/Default.aspx交由ASP.NET MVC来处理,具体请看Default.aspx.cs文件。 我们知道了一个URL是怎样定位到相应的Controller中去的了,那么View又是怎么被返回给客户端的呢?我们从前面的截图中看到,Controller中的Action方法中有个return View()的方法。默认情况下它会返回与Action同名的view.在ASP.NET MVC默认的视图引擎(WebFormViewEngine)下,view是按如下路径访问的: /Views/{Controller}/{Action}.aspx 也就是说对于http://localhost:2176/Home/Index这个路径,在默认情况下,在Index这个Action中用return View()来返回view的时候,会去寻找/Views/Home/Index.aspx文件,如果找不到这个文件,就会去Share目录中寻找:/Views/Share/Index.aspx,如果都找不到,就会抛出找不到View的异常。return View("lulu.aspx")来指定要返回哪一个view:/Views/Home/lulu.aspx。 那么为什么前面我们直接访问Views/Home/Index.aspx这里文件的时候会出现404错误,说找不到文件呢?因为在MVC中,是不建议直接去访问View的,所以我们建立的ASP.NET MVC程序在默认情况下就在Views目录下加了一个web.config文件,内容如下: 也就是访问Views目录下的所有的文件都会由System.Web.HttpNotFoundHandler来处理,所以请不要将资源文件(CSS、JS、图片等)放到Views目录中。如果你确实要放到Views目录下的话,请修改Views/web.config文件。 至此,大家应该对MVC的工作原理有一个大概的了解了。我们就先说到这里吧。Enjoy!

Tags:

ASP.NET MVC

关于博主

博主——陈少俊

   曾经的我们并不相识,但是网络让我们联系了起来,人生本来就是这样,有许多的必然和偶然,我不能做太多,只 是分享我自己拥有的,看到的,听到的。我致力于网站开发,系统建设,推广自己,谢谢大家的关心和爱护。
现就职于汽车时尚传媒有限公司网络部,中国汽车时尚网:http://www.86che.com/
汽车时尚网分类说明:汽车时尚网分类中所有新闻来源于中国汽车时尚网
汽车时尚报分类说明:汽车时尚报分类中所有新闻均为汽车时尚报出刊报纸中所有内容。
汽车时尚报电子版从《汽车时尚报》第493期开始正式免费上线,具体新闻内容请查阅【汽车时尚报】分类。
联系电话:13551107754,(028)68116678
MSN:chen1989jun@hotmail.com

日历中查看

<<  May 2012  >>
MonTueWedThuFriSatSun
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

切换到大日历中浏览

按月阅读

最近的评论

评论 RSS

声明

本博所有网友评论不代表本博立场,版权归其作者所有。

© Copyright 2009-2010  All Rights Piao Blog
蜀ICP备09038546号

部件 Statistics 未找到.

There is an error in XML document (0, 0).X