做了几个项目,有接触过MVC,不过不是用微软定义的那套MVC的框架,是别人写的一个很简单的MVC框架。因此对于微软的那个MVC框架,无论几点零版本的。鄙人还不会用。近日从 lulu Studio 的系列博文 《ASP.NET MVC 入门系列教程》从头学起。
先对项目简单了解一下
在上图中可以明显的看到MVC那三个玩意了:代表M的模型(Model),代表V的视图(View),代表C的控制器(Controller)。就是下图这个经典的品字形图,了解过MVC的肯定见过。
图上原本有的一些文字就不列出来的,但光看这些东西还是对MVC一头雾水的。至少请求的过程还不知道是什么回事。那就回到还没用上MVC的WebForm说起,比较比较。
在WebForm时,浏览器往服务器发出请求时这样的,发一个形如 http://localhost:4419/UI/Index.aspx的URL,服务器收到请求之后,就直接从根目录下找到UI文件夹下的Index.aspx文件,经过Index.aspx.cs的相关处理(这处理有大有小,有的作一般的判断,有的连接数据库,还有其他的不列举了)之后,生成了一个页面返回给浏览器。大致如下图
对于在MVC模式下则不相同了,首先,你若是用回上面那个请求的URL,即便是路径是正确的,也是获取不到期望得到的内容,它会返回一个"无法找到资源"的404错误。这是因为MVC框架的处理机制不同了。在MVC下多了个路由的机制,从浏览器发来的URL要符合路由的规格,经过路由的解析选择相应的控制器,调用控制器里的Action,做完相关处理之后,返回一个视图给浏览器。大致入下图。
其实在项目中会有一个或多个控制器,它们都存放在Controller文件夹里面,以Controller作主文件名(也是类名)的后缀,Action其实是控制器里面的方法,Action是有一个或多个,当然这里的模型和视图都是有一个或多个,在一个Action里头调用的模型数量按需求定,>=0个。返回的视图只能是一个。
这里的路由设置放在了Global.asax文件的RegisterRoutes方法里。
1 public static void RegisterRoutes(RouteCollection routes)
2 {
3 routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
4
5
6
7 routes.MapRoute(
8 "Default", // 路由名称
9 "{controller}/{action}/{id}", // 带有参数的 URL
10 new { controller = "System", action = "Index", id = UrlParameter.Optional } // 参数默认值
11 );
12
13 }
我这里设置了默认的控制器是System,就是Controller夹里的SystemController。从请求的URL来看,System/Index,是System控制器下的Index Action,也就是SystemController类下面的Index方法。
1 public class SystemController : Controller
2 {
3 //
4 // GET: /Index/
5
6 public ActionResult Index()
7 {
8 return View("Index");
9 }
10
11 //.........Other Actions
12 }
Action方法可以加上一些Attribute,例如HttpGet和HttpPost等,以限制在Get或者Post请求下才能访问这个Action,Action还可以带参数,不过带了参数就要设置好相关的路由,否则进到方法里参数会为空的。每个Action方法都要返回一些东西,最简单的就是"View();"就是返回与当前Action同名的视图。当前是Index方法,那单纯return View(); 就是返回一个Index.aspx页面,如果要指定其他页面,可以想上面那样调用View方法的另一个重载,字符串的内容就是视图的名称。在这里return的还可以有其他东西:
- ViewResult. 呈现视图页给客户端。由View
- RedirectToRouteResult. 重定向到另外一个Route。由RedirectToAction 和RedirectToRoute
- RedirectResult. 重定向到另外一个URL。由 Redirect
- ContentResult. 返回普通的内容。例如一段字符串。由 Content
- JsonResult. 返回JSON结果。由 Json
- EmptyResult. 如果Action必须返回空值,可以返回这个结果。Controller中没有实现的方法,可以return new EmptyResult();
上面列出的都是继承一个抽象类ActionResult,因此我们还可以自己定义一种ActionResult,但那方面鄙人还没涉及到。
以上则把一些MVC的基础知识介绍了一下,下面则通过一些实例来介绍其他方面,由于鄙人还是行入门了没几天,有说错的还得请各位大侠指出;用的方法太笨拙的还请得各位前辈指点。
登陆
首先则是实现一个简单的登陆功能。
用到了AdminModel,主要是验证密码,但是账号密码是写死的。
AdminModel
1 public class AdminModel
2 {
3 public static bool Login(string no, string pw)
4 {
5 if (no == "admin" && pw == "123456")
6 return true;
7 return false;
8 }
9 }
10
11 public class AdminData
12 {
13 public int AdminID { get; set; }
14
15 public string AdminNo { get; set; }
16
17 public string AdminName { get; set; }
18
19 public string AdminPassword { get; set; }
20
21 }
定义的模型文件里存放了两个类,秉承了以往开发三层架构项目时用到的实体类,这里也用上了,Data那个的则是实体类,与文件名同名的那个是处理一些逻辑的,比如登陆。
控制器里的Action方法定义如下,拥有两个参数,一个是账号id,另一个是密码password,在Action里定义调用了Model的方法验证账号密码的准确性,如果正确的就跳转到MainView视图,
用了RedirectToAction重定向到MainView的那个Action里,如果密码不正确就逗留在原本的Index视图里。
1 public ActionResult Login(string id, string password)
2 {
3 if ( AdminModel.Login(id, password)) return RedirectToAction("MainView");
4 return View("Index");
5 }
这样的需要重新定义一个路由
1 routes.MapRoute("Login", "System/Login/{id}/{password}", new { controller="System", action="Login" });
URL的默认形式是"{controller}/{action}/{id}",控制器和行为都已经填上了System和Login了。
在视图中原本想随便拖几个控件罢了,可是拖的button的事件用不了,就是由于MVC与WebForm的处理机制不同,以前WebForm单击了按钮,回传给服务器,在生成新的html之前会在aspx.cs文件作一些处理,包括了事件响应。可现在MVC不同了,所有请求都到路由,再到控制器上的行为,即使在视图的文件aspx上直接加C#的代码都是行不通的。代码是这样的
<div style=" margin-left:auto; margin-right:auto; padding-top:64px; width:427px; height:186px; background-color:#33CCFF ">
<div style="margin-left:auto; margin-right:auto; width:208px; ">
账号:
<input type="text" id="txtID" />
</div>
<div style="margin-left:auto; margin-right:auto;width:208px">
密码:
<input type="password" id="txtPW"/>
</div>
<div style="margin-left:auto; margin-right:auto;width:208px">
<input type="button" id="btnReset" value="Reset" class="btnClass" onclick="btnReset_Click()" />
<input type="button" id="btnLogin" value="Login" class="btnClass" onclick="btnLogin_Click()" />
<script type="text/javascript">
function btnReset_Click() {
$("#txtID,#txtPW").val("");
}
function btnLogin_Click() {
var id = $("#txtID").val();
var pw = $("#txtPW").val();
location = "System/Login/" + id + "/" + pw;
}
</script>
</div>
</div>
这里只是用回原本html里的表单元素,验证账号密码时直接把location改了就是了。附上界面的图片
表格绑定
登陆成功之后跳转到的是主界面,主界面上只有一个表格,列举的是客户信息用的是ViewData来把信息从行为方法传递到视图上。由于以往用的web控件都用不了,只能用别的办法做一个网格表出来,用了单值绑定。别的方法可以是js动态构造一个出来,或者用插件。在这里就尝试一下这种没用过的方式。
<script type="text/javascript">
$(function () {
var time = new Date();
if (time.getTime() < 12) $("#time").text("上午好");
else if (time.getTime() < 18) $("#time").text("下午好");
else $("#time").text("晚上好");
});
</script>
<div>
<div id="header">
<%= ViewData["Admin"] %>
,
<label id="time"></label>
</div>
<table border="1" cellpadding="0" cellspacing="2">
<tr>
<td class="hidden">id</td>
<td>编号</td>
<td>姓名</td>
<td>性别</td>
<td>出生年月</td>
<td></td>
</tr>
<% List<MVCDemo.Models.UserData> dataSouce = ViewData["userList"] as List<MVCDemo.Models.UserData>; %>
<%if(dataSouce!=null) %>
<% foreach (MVCDemo.Models.UserData user in dataSouce)
{ %>
<tr>
<td class="hidden">
<%=user.UserID %>
</td>
<td>
<%=user.UserNo %>
</td>
<td>
<%=user.UserName %>
</td>
<td>
<%=user.UserSex?"男":"女" %>
</td>
<td>
<%= user.UserBirth==new DateTime()?"--":user.UserBirth.ToString("yyyy-MM-dd") %>
</td>
<td>
<a href="/Customer/CustomerDetail/<%= user.UserID %>">查看</a>
</td>
</tr>
<% } %>
</table>
</div>
附上界面的图片
控制器的代码和模型的代码依次列出。
1 public ActionResult MainView()
2 {
3 List<UserData> list = UserModel.GetUserList();
4 ViewData["Admin"]="admin";
5 ViewData["userList"] = list;
6 return View();
7 }
UserModel
1 public class UserModel
2 {
3 private static List<UserData> list=new List<UserData>()
4 {
5 new UserData(){ UserID=1, UserName="Tom", UserNo="USR001", UserPassword="123456", UserSex=true,UserBirth=new DateTime(1990,1,1)},
6 new UserData(){UserID=2,UserName="Tim" , UserNo="USR002", UserPassword="234567", UserSex=true,UserBirth=new DateTime(1992,2,1)},
7 new UserData(){UserID=3,UserName="Mary" , UserNo="USR003", UserPassword="243567", UserSex=false,UserBirth=new DateTime(1993,4,5)},
8 new UserData(){UserID=4,UserName="Kay" , UserNo="USR004", UserPassword="234547", UserSex=false,UserBirth=new DateTime(1996,5,4)},
9 new UserData(){UserID=5,UserName="Ben" , UserNo="USR005", UserPassword="234567", UserSex=true}
10 };
11
12 public static bool InitPassword(int id)
13 {
14 UserData user = list.Where(c => c.UserID == id).FirstOrDefault();
15 if(user==null)return false;
16 user.UserPassword = "123456";
17 return true;
18 }
19
20 public static List<UserData> GetUserList()
21 {
22 return list;
23 }
24
25 public static UserData GetUserDetail(int id)
26 {
27 UserData user = list.Where(c => c.UserID == id).FirstOrDefault();
28 return user;
29 }
30
31 public static bool UpdateUser(UserData user)
32 {
33 UserData ent = list.Where(c => c.UserID == user.UserID).FirstOrDefault();
34 if (ent == null) return false;
35 ent.UserName = user.UserName;
36 ent.UserSex = user.UserSex;
37 ent.UserBirth = user.UserBirth;
38 return true;
39 }
40 }
41
42 public class UserData
43 {
44 public int UserID { get; set; }
45
46 public string UserNo { get; set; }
47
48 public string UserName { get; set; }
49
50 public string UserPassword { get; set; }
51
52 public bool UserSex { get; set; }
53
54 public DateTime UserBirth { get; set; }
55 }
控制器的行为方法里头,用了ViewData。在UserModel里面,用了一个List来存放用户列表,免了用数据库读取数据库那部分的繁琐操作。
查看编辑客户
上面的例子中,每一行的末尾都有一个"查看"的超链接,点击了这个超链接就可以进入每个客户的详情页面。这个详情页面包括了三个功能:初始化密码,保存更改和返回。这些按钮都是直接用表单元素,通过js更改location的方式来引致触发器的相应的行为处理。其他的文本框就应用了MVC提供的helper的方法来生成html方法。视图的代码如下
<script type="text/javascript">
$(function () {
if ('<%= Convert.ToBoolean( TempData["Result"]) %>' == "True")
alert("Init password success!");
if ('<%= Convert.ToBoolean( TempData["editRes"]) %>' == "True")
alert("Update Success!");
});
</script>
<form id="form1" action='/Customer/UpdateCustomer/<%= ViewData.Eval("user.UserID") %>' method="post">
<div>
编号:<%= Html.Label(ViewData.Eval("user.UserNo").ToString()) %>
<br />
姓名:<%= Html.TextBox("UserName") %>
<br />
性别:<%--<%= Convert.ToBoolean( ViewData.Eval("user.UserSex"))?"男":"女" %>--%>
<%= Html.RadioButton("UserSex",true, Convert.ToBoolean(ViewData.Eval("user.UserSex")), new {@name="sex" })%>男
<%= Html.RadioButton("UserSex",false, !Convert.ToBoolean(ViewData.Eval("user.UserSex")), new {@name="sex" })%>女
<br />
出生年月:<%= Html.TextBox("UserBirth", Convert.ToDateTime( ViewData.Eval("user.UserBirth")).ToString("yyyy-MM-dd")) %>
<br />
<input type="button" id="btnInitPW" value="InitPassword" onclick="btnInitPW_Click()" />
<input type="button" id="btnSave" value="SaveData" onclick="btnSave_Click()" />
<input type="button" id="btnBack" value="Back" onclick="btnBack_Click()" />
<script type="text/javascript">
function btnInitPW_Click() {
location = '/Customer/ResetPassword/<%= ViewData.Eval("user.UserID") %>';
}
function btnSave_Click() {
form1.submit();
}
function btnBack_Click() {
location = "/System/MainView";
}
</script>
</div>
</form>
附上界面的图片
由于这里要用到数据的提交,用了个表单,而且所有编辑的框都是用helper生成的,name属性应该不用自己填了。以上这部分的代码觉得有几个地方不对劲,但是资历尚浅还不知道如何改进,第一是表单提交的URL里头有用到单值绑定;第二是保存用户信息和初始化密码这两个更改操作之后返回的信息是使用了TempData把操作结果返回给视图,在视图上通过js处理把结果呈现给客户;第三是绑定数据时有些纠结,行为方法里面return View()时附带了要绑定的对象,在这里就是user这个对象,如果要获取user的某个属性作一些处理,好比上面的UserSex和UserBirth,单纯用helper生成表单元素时绑定行不通,鄙人只好再多用一个ViewData。 希望各位有什么意见或者建议多提点一下。
下面则是控制器的代码
1 public ActionResult ResetPassword(int id)
2 {
3 bool result= UserModel.InitPassword(id);
4 TempData["Result"]=result;
5 return Redirect("/Customer/CustomerDetail/" + id);
6 }
7
8 public ActionResult UpdateCustomer(int id)
9 {
10 bool updateResult = true;
11 UserData user = UserModel.GetUserDetail(id);
12 try
13 {
14
15 UpdateModel(user, new string[] { "UserNo", "UserName", "UserSex", "UserBirth" });
16 updateResult= UserModel.UpdateUser(user);
17 }
18 catch
19 {
20 updateResult = false;
21 }
22 TempData["editRes"] = updateResult;
23 ViewData["user"] = user;
24 return View("CustomerDetail",user);
25 }
要获取表单提交过来的数据,用15行的代码就行了,当然可以用更原始的方式Request.Form[""]获取。
好了,这篇博文介绍到此,作为MVC的初次涉足,做到这样知道还很不像样,以后多加探究。大年初四
代码下载MVCDemo.rar