转载请注明出处:http://surfsky.cnblogs.com
MVC
参考 http://msdn.microsoft.com/zh-cn/dd408813.aspx
MVC 的基本思想
·Module : 模型。可以看做实体类,可用各种技术实现,例如Microsoft Entity Framework、NHibernate等
·View : 视图。可以与某个实体类关联,此时视图则负责展示该实体类
·Controller : 控制器。接受请求,获取模型数据,并指派指定的视图展示之
MVC 是一种网页框架,jsp、php、asp.net都可以用到,呃asp...这个好像不能
MVC 是一种很好的分离实现,将用户操作、数据和视图很好的分离开来,便于协同开发,也便于测试
MVC 现阶段最糟糕的缺点在于控件太少,若要完成内置ajax功能的MVC控件则更是少之又少
MVC 3 快出来了,razor视图引擎也快出来了,我觉得可以学习一下了,于是就有了这篇编程参考......
目录结构
Scripts : js 文件
Content : css, image 文件
Controllers : 控制器目录
HomeController.cs : Home 控制器,对应的视图在Views/Home/目录下
Models : 模型目录,里面放实体类代码
Views : 视图目录
Home : Home目录,对应HomeController.cs
Create.aspx : Home视图,对应HomeController.Create方法
Index.aspx : Home视图,对应HomeController.Index方法
Shared : 共享的一些视图
web.config : 该文件禁止了直接访问该目录下的视图。所有的请求都由路由以及控制器控制
Default.aspx : 默认页
global.asax : 在该文件中写了路由规则
web.config : 网站配置文件
------------------------------------------------------
Route(路由)
aspnet mvc中大量采用路由来重写路径
------------------------------------------------------
MVC模型的 URL
·URL默认模式为:/控制器/方法名/参数。和页面不是对等的,不会显示例如aspx 之类的文件扩展名
·除了IIS 7.0集成模式外,在iis下部署必须
(1)将.*映射到C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll
(2)关闭选项:“检查文件是否存在(Verify that file exists)”
参考: http://msdn.microsoft.com/zh-cn/dd320350.aspx
·在Web.config 中启用路由:
system.web.httpModules 部分
system.web.httpHandlers 部分
system.webserver.modules 部分
system.webserver.handlers 部分
注意不要删除这些部分,因为没有这些部分路由就无法工作
默认路由(推荐路由)
在Application_Start中对路由进行控制
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // 路由名称
"{controller}/{action}/{id}", // 路由模式
new {controller = "Home", action = "Index", id = ""} // 默认路由
);
}
路由示例
/Product/Details/3 解析为:
Controller = ProductController
Action = Details
Id = 3
执行代码(路由器使用反射):ProductController.Detail(3)
/Employee 解析为:
Controller = EmployeeController
Action = Index
Id = ""
执行代码:EmployeeController.Index(null)
/ 解析为:
Controller = HomeController
Action = Index
Id = ""
执行代码:EmployeeController.Index(null)
注:若无法更改iis配置,也可以使用另外一种映射方案
{controller}.aspx/{action}/{id}
可以映射如下链接:
/Home.aspx/Index
/Product.aspx/Details/3
/Product.aspx
控制器扩展名要改成aspx
确保所有的页面链接都包含.aspx 扩展名(或直接使用 Html.ActionLink()函数)
详细请参考:http://msdn.microsoft.com/zh-cn/dd320350.aspx
自定义路由
routes.MapRoute(
"blog",
"Archive/{entryDate}",
new {controller = "Archive", action = "Entry"}
);
则路径:/Archive/2010-01-01 会映射到 ArchiveController.Entry(DateTime entryDate)方法
------------------------------------------------------
Module(模型)
可以看成是实体类,是传递给视图的强类型对象。
------------------------------------------------------
例如:
public class Product
{
public int ProductID {get; set;}
public string ProductName {get; set;}
public Decimal? UnitPrice {get; set;}
public bool Discontinued {get; set;}
public virtual Category Category {get; set;}
}
public class Category
{
public int CategoryId {get; set;}
public string CategoryName {get; set;}
public string Description {get; set;}
public byte[] Picture {get; set;}
public virtual ICollection
}
public class Northwind : DbContext
{
public DbSet
public Dbset
}
调用方法
Northwind northwind = new Northwind();
var products = from p in northwind.Products
where p.Discontinued == false
select p;
注:推荐使用《EntityFramework 4 代码优先》方案
直接写类即可,可自动生成数据库表,适合快速建模(现在还是ctp版本)
http://blog.joycode.com/scottgu/archive/2010/09/06/116066.joy
------------------------------------------------------
Controller(控制器)
控制器用于响应用户需求,生成相应的模型并传递给指定视图令其输出
------------------------------------------------------
Controller 基本成员
上下文
HttpContext :
Request :
Response :
Server :
Session :
Url :
User : 用户认证信息(IPricipal对象)
数据载体
ViewData : 视图数据
ModelData : 模型数据
TempData : 临时数据
RouteData : 路由数据
ModelState : 提交上来的模型数据是否验证正确
其它
ActionInvoker :
TempDataProvider : 临时数据提供者
方法
UpdateModel(T) : 根据提交上来的视图数据来更新模型数据
控制器代码结构
public class HomeController : Controller
{
//...若干返回类型为ActionResult的方法
public ActionResult Create()
{
return View();
}
}
ActionResult 类别(以下类型都是ActionResult的子类)
类型 方法 说明
---- ---- ----
ViewResult View() 将视图呈现为网页。
PartialViewResult PartialView() 呈现分部视图,该分部视图定义可呈现在另一视图内的某视图的一部分。
RedirectResult Redirect() 使用其 URL 重定向到另一操作方法。
RedirectToRouteResult RedirectToAction(),RedirectToRoute() 重定向到另一操作方法。
ContentResult Content() 返回用户定义的内容类型。
JsonResult Json() 返回序列化的 JSON 对象。
JavaScriptResult JavaScript() 返回可在客户端上执行的脚本。
FileResult File() 返回要写入响应中的二进制输出。
EmptyResult (无) 表示在操作方法必须返回 null 结果 (void) 的情况下所使用的返回值。
直接输出文本
方法返回值为ContentResult
public ContentResult Demo()
{
return Content("Hello World!");
}
若返回类型不是ActionResult,其结果也会被自动封装在 ContentResult 中
public DateTime Time()
{
return DateTime.Now;
}
返回与方法名相同的视图(/HOME/Create)
public ActionResult Create()
{
return View();
}
返回指定的视图(名称为“Fred”)
public ActionResult Other()
{
return View(“Fred”);
}
用ViewData将数据传递给视图
public ActionResult Details()
{
ViewData["message"] = "Hello World!";
return View();
}
<%=Html.Encode(ViewData["message"])%>
<%:ViewData["message"]%>
@ViewData["message"]
用TempData将数据传递给视图
public ActionResult TempDataWay(int id)
{
Book book = bookRepository.GetBook(id);
TempData["Countries"] = new SelectList(PhoneValidator.Countries, book.Country);
return View(book);
}
TempData 与 ViewData的区别在于TempData在RedirectAction后仍然可以使用
具体可查看:http://www.cnblogs.com/zhuqil/archive/2010/08/03/Passing-Data-from-Controllers-to-View.html
将模型传递给视图
将实体对象传递给视图
public ActionResult Details(int Id)
{
var product = new Product(Id, "Laptop");
return View("Details", product);
}
var product = ViewData.Model;
@Model.Id
将数据dinners实体集合传递给视图
public ActionResult Index()
{
var dinners = from d in NerdDinners
where d.EventDate > DateTime.Now
select d;
return View(dinners.ToList());
}
foreach (Dinner d in (IEnumerable)ViewData.Model)
视图参数(可用为空对象或者默认参数实现)
// 返回Detail视图(/HOME/Detail/n)
// 或者 public ActionResult Details(int Id=0)
public ActionResult Details(int? id)
{
if (id == null)
return RedirectToAction("Index");
return View();
}
处理Post消息
// 使用FormCollection参数
[HttpPost]
public ActionResult ViewDataWay(int id, FormCollection collection)
{
Book book = bookRepository.GetBook(id); // 获取原数据
UpdateModel
bookRepository.Save(book); // 保存新数据
return RedirectToAction("Details", new {id=id}); // 跳到详细视图
}
// 使用ModelWrapper
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
渲染片段(类似控件)
Html.RenderPartial("MovieTemplate", m);
-------------------------------------
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MovieTemplate.ascx.cs" Inherits="System.Web.Mvc.ViewUserControl
<%=ViewData.Model.Id%>
<%=Html.Encode(ViewData.Model.Title)%>
<%=ViewData.Model.DateReleased.ToString("D")%>
渲染其它action结果(Child Action)
<%= Html.Action("LogOnWidget", "Account") %>
-------------------------------------
public ViewResult LogOnWidget()
{
...
return View(...);
}
-------------------------------------
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="AccountProfile.Controllers"%>
<%if (Request.IsAuthenticated) {%>
Welcome <%= Html.Encode(Page.User.Identity.Name) %>!
[ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> | <%= Html.ActionLink("Profile", "Show", "Profile", new RouteValueDictionary(new { username = Html.Encode(Page.User.Identity.Name) }), null)%> ]
<%}else {%>
[ <%= Html.ActionLink("Log On", "LogOn", "Account") %> ]
<%}%>
输出json文本
[HttpPost]
public ActionResult SearchByLocation(float latitude, float longitude)
{
var dinners = dinnerRepository.FindByLocation(latitude, longitude);
var jsonDinners = from dinner in dinners.AsEnumerable()
select JsonDinnerFromDinner(dinner);
return Json(jsonDinners.ToList());
}
输出文件
file
var path = Server.MapPath("~/Files/鹤冲天.zip");
var name = Path.GetFileName(path);
return File(path, "application/x-zip-compressed", Url.Encode(name));
byte[]
byte[] data = Encoding.UTF8.GetBytes("欢迎访问 鹤冲天 的博客 http://www.cnblogs.com/ldp615/");
return File(data, "text/plain", "welcome.txt");
stream
var path = Server.MapPath("~/Files/鹤冲天.zip");
var fileStream = new FileStream(path, FileMode.Open);
var zipInputStream = new ZipInputStream(fileStream);
var entry = zipInputStream.GetNextEntry();
return File(zipInputStream, "application/pdf", Url.Encode(entry.Name));
------------------------------
var stream = new WebClient().OpenRead("http://files.cnblogs.com/ldp615/Mvc_TextBoxFor.rar");
return File(stream, "application/x-zip-compressed", "Mvc_TextBoxFor.rar");
输出缓存(适合于直接输出文本或者图像的场合)
在controll中指定
[OutputCache(VaryByParam = "none", Duration = 300)]
public ActionResult RSS()
{
var dinners = dinnerRepository.FindUpcomingDinners();
if (dinners == null)
return View("NotFound");
return new RssResult(dinners.ToList(), "Upcoming Nerd Dinners");
}
在View窗口声明
<%@ OutputCache Duration="#ofseconds"
Location="Any | Client | Downstream | Server | None |
ServerAndClient "
Shared="True | False"
VaryByControl="controlname"
VaryByCustom="browser | customstring"
VaryByHeader="headers"
VaryByParam="parametername"
VaryByContentEncoding="encodings"
CacheProfile="cache profile name | ''"
NoStore="true | false"
SqlDependency="database/table name pair | CommandNotification"
%>
限制必须是Ajax访问
public ActionResult Discover(string identifier)
{
if (!this.Request.IsAjaxRequest()) {
throw new InvalidOperationException();
}
...
}
自定义ActionResult
FileNotFoundResult
public class FileNotFoundResult : ActionResult
{
public string Message { get; set;}
public override void ExecuteResult(ControllerContext context) {
throw new HttpException(404, Message);
}
}
if (id == null)
return new FileNotFoundResult { Message = "No Dinner found due to invalid dinner id" };
RssResult
public class RssResult : FileResult
{
public List
public string Title { get; set; }
private Uri currentUrl;
public RssResult() : base("application/rss+xml") { }
public RssResult(List
{
this.Dinners = dinners;
this.Title = title;
}
public override void ExecuteResult(ControllerContext context)
{
currentUrl = context.RequestContext.HttpContext.Request.Url;
base.ExecuteResult(context);
}
protected override void WriteFile(System.Web.HttpResponseBase response)
{
var items = new List
foreach (Dinner d in this.Dinners)
{
string contentString = String.Format("{0} with {1} on {2:MMM dd, yyyy} at {3}. Where: {4}, {5}",
d.Description, d.HostedBy, d.EventDate, d.EventDate.ToShortTimeString(), d.Address, d.Country);
var item = new SyndicationItem(
title: d.Title,
content: contentString,
itemAlternateLink: new Uri("http://nrddnr.com/" + d.DinnerID),
id: "http://nrddnr.com/" + d.DinnerID,
lastUpdatedTime: d.EventDate.ToUniversalTime()
);
item.PublishDate = d.EventDate.ToUniversalTime();
item.Summary = new TextSyndicationContent(contentString, TextSyndicationContentKind.Plaintext);
items.Add(item);
}
SyndicationFeed feed = new SyndicationFeed(
this.Title,
this.Title, /* Using Title also as Description */
currentUrl,
items);
Rss20FeedFormatter formatter = new Rss20FeedFormatter(feed);
using (XmlWriter writer = XmlWriter.Create(response.Output))
{
formatter.WriteTo(writer);
}
}
}
未处理action
protected override void HandleUnknownAction(string actionName)
{
throw new HttpException(404, "Action not found");
}
特性标签(过滤器)
[HttpPost]:该方法只接受post请求
[Authorize]:过滤器提供了一种声明的方式来控制对Controller或Action方法的访问权限,它允许你表示用户必须已经登录,或者要求他们必须是某个特定的用户或是某个特定的安全角色才能访问。这个过滤器可以用于任何类型的认证方式(包括基于Windows以及Forms的认证),还提供了自动将匿名用户转向到登录页面的支持。
[ChildActionOnly]:只能由RenderAction方法调用
验证输入参数
[HttpPost]
public ActionResult Exp1(Models.UserModel user)
{
//如果错误,调用ModelState的AddModelError方法,第一个参数需要输入出错的字段名
if (user.Name.Length > 20)
ModelState.AddModelError("Name", "名字不得超过20个字符");
//判断ModelState中是否有错误
if (ModelState.IsValid)
return RedirectToAction("Index");
else
return View(user);
}
------------------------------------------------------
View(视图)
用于展示。可与具体实体类模型相关联
------------------------------------------------------
ViewPage : Page
主要的一些特殊成员
Ajax
Html
Model
ViewData
TempData
ViewContext
Url
看到这个父类Page,是不是觉得有点笨重了?
个人很喜欢razor引擎,它的父类应该不是ViewPage
aspx 视图引擎
<%=d.Title %> (<%=d.EventDate%>)
<%}%>
<%foreach(var d in Model){ %>
<%=Html.ActionLink("Create New Dinner", "Create")%>
razor 视图引擎
Upcoming Diners
- @d.Title (@d.EventDate)
}
@foreach(var d in Model){
@Html.ActionLink("Create New Dinner", "Create")
关于razor请参考razor引擎相关章节
强类型页面视图
<%@ View Language="C#" Inherits="System.Web.Mvc.ViewPage
Model.xxx
用户控件视图
LoginStatus.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="NerdDinner" %>
<%if (Request.IsAuthenticated) {%>
Welcome <%: ((NerdIdentity)Page.User.Identity).FriendlyName %>!
[ <%: Html.ActionLink("Log Off", "LogOff", "Account") %> ]
<%}else {%>
[ <%: Html.ActionLink("Log On", "LogOn", new { controller = "Account", returnUrl = HttpContext.Current.Request.RawUrl }) %> ]
<%}%>
DateTime.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl
<%: Html.TextBox("", String.Format("{0:yyyy-MM-dd HH:mm}", Model)) %>
CountryDropDown.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl
<%: Html.DropDownList("", new SelectList(NerdDinner.Helpers.PhoneValidator.Countries, Model)) %>
Alert.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
public ActionResult Test()
{
ViewData["JSAlert"] = "保存成功";
ViewData["JSHref"] = "保存成功";
return PartialView("JS");
}
------------------------------------------------------
HelperClass(视图中使用的辅助类)
------------------------------------------------------
同winform相比,只有简单的aspnet控件可用(如repeater),复杂的有postback逻辑的控件都不能用了
同asp相比,似乎mvc视图又回到asp的标签年代,代码和逻辑看起来仍是混杂的,又要自己手工拼凑tag了,那和asp有什么区别呢?
(1)mvc视图大量使用helper模式的类,如类HtmlHelper、UrlHelper,为快速创建form控件提供辅助,如:
Html.BeginForm()
Html.ValidateSummary(true)
Html.LabelFor(dinner => dinner.Title)
Html.TextBoxFor(dinner => dinner.Title)
Html.ValidateMessageFor(dinner => dinner.Title)
(2)以及种种mvc模式下的控件(一般由三方控件商提供),如telerik的开源mvc控件,如:
Html.Telerik().DatePicker()
.Name("DatePicker")
.MinDate(Model.MinDate.Value)
.MaxDate(Model.MaxDate.Value)
.Value(Model.SelectedDate.Value)
.ShowButton(Model.ShowButton.Value)
一般说来,mvc的控件具有以下特点:
·无viewstate数据,不保持控件状态,刷新页面后就还原了
·以helper代码的方式提供控件,而非传统的tag。如:@Html.Grid(....);
·post操作用ajax来实现,对应到某个controller method
(3)mvc视图可以接受contoller传递进来的数据,封装在ViewData和Model中
HtmlHelper
普通方法
Html.BeginForm() : using (Html.BeginForm()) {...}
Html.EndForm() : Html.EndForm()
Html.ActionLink() : Html.ActionLink("Find Dinner", "Index", "Home")
Html.CheckBox() : <%: Html.CheckBox("rememberMe") %>
Html.DropDownList() : Html.DropDownList("", new SelectList(new[]{"France", "Germany", "US"}))
Html.Hidden() : Html.Hidden("ReturnUrl", Request.QueryString["ReturnUrl"], new { id = "ReturnUrl" })
Html.ListBox() : listbox
Html.Password() : Html.Password("password")
Html.RadioButton() : radio
Html.TextArea() : text area
Html.TextBox() : Html.TextBox("Email")
Html.ValidationSummary() : Html.ValidationSummary("请输出正确的数据")
Html.ValidateMessage() : Html.ValidateMessage("password")
-----------------------------------------------
SelectList
// 枚举转化为SelectList
public static SelectList ToSelectList
{
var values = from TEnum e in Enum.GetValues(typeof(TEnum))
select new { ID = e, Name = e.ToString() };
return new SelectList(values, "Id", "Name", enumObj);
}
强类型方法(会检索实体类的特性,做出智能反馈)
Html.DisplayFor() :
Html.LabelFor() : Html.LabelFor(c=>c.Name)。获取特性DisplayName
Html.ValidationMessageFor : Html.ValidationMesssageFor(c=>c.Name)。获取验证相关的特性
Html.EditorFor() : Html.EditorFor(c=>c.Name)。会根据类型输出合适的元素,对于复杂对象还会生成控件组。
...
----------------------------------------------
注:EditorFor
会根据类型输出合适的元素
可指定复杂对象使用的partial 控件
创建Views/Shared/EditorTemplates/CountryDropDown.ascx 控件
用代码指定编辑器:Html.EditorFor(c=>c.County, "CountryDropDown")
用UIHint指定编辑器:[UIHint("CountryDropDown")]public stirng Country{get; set;}
UrlHelper
string Action(...)
string Content(string contentPath)
string Encode(string url)
string GenerateContentUrl(...)
string GenerateUrl(...)
string RouteUrl(...)
扩展HtmlHelper方法
public static class LabelExtensions
{
public static string Label(this HtmlHelper helper, string target, string text)
{
return String.Format("", target, text);
}
}
@Html.Label("firstName", "First Name:")
自定义helper(LabelHelper)
public class LabelHelper
{
public static string Label(string target, string text)
{
return String.Format("", target, text);
}
}
@LabelHelper.Label("firstName", "First Name:")
------------------------------------------------------
Validation(数据验证)
数据验证跨越模型和视图,故在此单独提出
------------------------------------------------------
(1)带验证信息的实体类示例(具体请查看实体框架代码优先模式相关章节)
引入dll:System.ComponentModel.DataAnnotation
public class Dinner
{
public int DinerID {get; set;}
[Required(ErrorMessage="Please Enter a dinner title")]
[StringLength(20, ErrorMessage="Title is to long")]
public string Title {get; set;}
[Required(ErrorMessage="Please enter the date of dinner")]
public DateTime EventDate {get; set;}
[Required(ErrorMessage="Please enter your email address")]
[RegularExpression(".+\\@.+\\..+", ErrorMessage="Please enter a valid email address")]
public string HostedBy {get; set;}
public virtual ICollection
}
或者分离模式
[MetadataType(typeof(UserMetaData))]
public partial class User {...}
public class UserMetaData
{
[Required(ErrorMessage = "名字为空")]
[StringLength(10, ErrorMessage = "名字长度不得超过10个字符")]
public string Name { get; set; }
[Required(ErrorMessage = "密码为空")]
[StringLength(20, ErrorMessage = "密码长度不得超过20个字符")]
public string Password { get; set; }
[Required(ErrorMessage = "帐号为空")]
[StringLength(10, ErrorMessage = "帐号长度不得超过10个字符")]
public string Passport { get; set; }
}
(2)在视图中验证
<% Html.EnableClientValidation(); %>
<%: Html.ValidationSummary("Please correct the errors and try again.") %>
Html.ValidateMessage("password")
Html.ValidateMessageFor(d => d.Title) // 根据模型中的特性来自动生成客户端验证代码
(3)在控制器中验证
if (ModelState.IsValid) {}
附录:自定义验证特性
// 必须比当前日期早(适合于出生日期)
public class BeforeTodaysDateAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
return true;
DateTime result;
if (DateTime.TryParse(value.ToString(), out result))
{
if (result < DateTime.Now)
return true;
}
return false;
}
}
------------------------------------------------------
UnitTest(单元测试)
MVC模式将模型和视图进行了完全的分离,极大的方便了测试
MVC的单元测试一般针对Controller进行测试
------------------------------------------------------
测试类
[TestClass]
public class ProductControllerTest
{
}
测试控制器返回的视图是否正确
[TestMethod]
public void TestDetailsView()
{
var controller = new ProductController();
var result = controller.Details(2) as ViewResult;
Assert.AreEqual("Details", result.ViewName);
}
测试控制器传递给视图的实体数据是否正确
public ActionResult Details(int Id)
{
var product = new Product(Id, "Laptop");
return View("Details", product);
}
[TestMethod]
public void TestDetailsViewData()
{
var controller = new ProductController();
var result = controller.Details(2) as ViewResult;
var product = (Product) result.ViewData.Model;
Assert.AreEqual("Laptop", product.Name);
}
测试控制器动作是否正确
public ActionResult Details(int Id)
{
if (Id < 1)
return RedirectToAction("Index");
var product = new Product(Id, "Laptop");
return View("Details", product);
}
[TestMethod]
public void TestDetailsRedirect()
{
var controller = new ProductController();
var result = (RedirectToRouteResult) controller.Details(-1);
Assert.AreEqual("Index", result.Values["action"]);
}