MVC深入理解

Posted IT技术分享社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVC深入理解相关的知识,希望对你有一定的参考价值。

软件开发

MVC是目前WEB开发的主流技术/模式,应用广泛。可很多同学受原始MVC概念的影响并没有真正理解MVC的确切含义;或者套用原始的MVC概念却与现实的编程对应不起来;或者能够参照范例比葫芦画瓢写代码,但遇到复杂问题却不知从何入手。那么ASP.NET MVC中,MVC到底是什么含义,M、V、C三者之间到底是什么关系,遇到复杂交互问题该如何基于最新MVC的原理进行分析?

1.M、V、C的含义

先顾名思义:M:Model,模型;V:View,视图;C:Controller,控制器。

M:Model,模型。这个M是理解MVC机制的关键。但M这个概念却很模糊,含义不明。是领域模型(Domain Model),代表业务实体与逻辑?是实体模型(Entity Model),代表ORM实体对象?还是视图模型(View Model,简称VM)?答案是视图模型!是视图内容的一种抽象或映射。

V:View,视图。很好理解,就是UI界面,用户的交互接口,对应的代码就是一系列html元素。视图的作用是:(1)接收用户动作;(2)呈现处理结果。呈现的数据来自哪里?视图模型对象!

C:Controller,控制器。控制器是个“框”,每个控制器包含一系列Action函数,每个Action(动作)代表一个HTTP请求(动作)与响应。每个Action函数的作用是:(1)接收HTTP请求;(2)返回请求处理结果。其进行业务处理或调用独立的业务层进行业务处理。返回什么结果?“视图+视图模型”的组合!这样View才能呈现用户在浏览器中看到的内容。那么“控制器”控什么?控“请求与响应”的关联,控“视图与视图模型”的关联。当然这些关联都由MVC框架底层支撑实现,控制器只是这些关联的呈现者。

M-V-C确切的应该叫ViewModel-View-Controller,或者ViewModel-View-Action。

MVC发展到现在,已经不是原始的模样了。现在的Web应用环境与1979年提出MVC时的软件环境已经是天壤之别,M、V、C的概念和关系已经极大演变。所以,我们不能再用原始的MVC概念套用理解现今的MVC模式,ASP.NET MVC采用的是MVC的新变体Model2模式,它也是Spring MVC所采用的模式。下面,我们再从典型的ASP.NET MVC代码中分析下M、V、C的含义与关系。

2.从典型代码中分析M、V、C的关系

一个超级简单的用户信息编辑DEMO,界面如下图:

MVC深入理解

(1)模型文件User.cs。具体代码如下:

namespace MvcDemo.Models

{

public class User

{

public int ID { set; get; }

public string Name { set; get; }

public string Address { set; get; }

}

}

这只是一个简单的POCO(Plain Old C# Object)类,定义了User这个模型。为什么要定义这个模型?因为视图需要。下面看视图文件。

(2)视图文件UserEdit.cshtml。其存储在Views/User/UserEdit.cshtml,具体代码如下:

@model MvcDemo.Models.User

<h2>用户信息编辑</h2>

@using (Html.BeginForm())

{

<p>

ID:@Html.DisplayFor(m => m.ID)

@Html.HiddenFor(m => m.ID)

</p>

<p>姓名:@Html.TextBoxFor(m => m.Name)</p>

<input type="submit" value="保存" />

}

这个文件定义了浏览器要呈现的界面元素,通过“保存”按钮可实现与用户的交互。

浏览器呈现界面内容需要数据,数据来自于模型对象。@model标签指定了当前视图对应的数据的样子(数据的定义),即视图对应的模型User。这样,我们假定有了一个User对象m,就可以把m的数据呈现在界面上。

模型User(类)是基于视图UserEdit.cshtml的。视图展示的需求,决定了User要长成什么样子,需要定义什么内容。视图/界面代表的是应用需求,我们所做的都是要基于应用需求的。

总结:视图决定模型,模型是基于视图的模型,所以应该叫视图模型(ViewModel)。

(3)控制器文件UserController.cs。具体代码如下:

using System.Web.Mvc;

using MvcDemo.Models;

namespace MvcDemo.Controllers

{

public class UserController : Controller

{

[HttpGet]

public ActionResult UserEdit(int id)

{

var user = getUserById(id);

return View(user);

}

[HttpPost]

public ActionResult UserEdit(User user)

{

updateUser(user);

return View(user);

}

}

文件中UserController类包含两个Action函数:

第1个Action函数UserEdit(int id),其处理HttpGet请求,请求的URL类似为http://www.xxx.com/User/UserEdit?id=2,问号后边的参数id被自动映射为Action函数的参数id。针对该请求,Action函数获取用户id=2的User对象并呈现到界面。

第2个Action函数UserEdit(User user),其处理HttpPost请求,当用户点击“保存”按钮时FORM表单内容被提交,触发了一个HttpPost请求,请求的URL同上,类似为http://www.xxx.com/User/UserEdit?id=2,请求提交的表单内容被自动映射为Action函数的参数user,即参数user的属性值被提交的表单内容自动按属性名称填充。针对该请求,即Action函数保存提交的用户信息并依旧呈现该信息到界面。

(1)先说HTTP请求。HTTP请求一般要包含三个要素:请求方式(Get/Post)、请求URL、请求参数(在URL中明传或在表单中暗传),Cookie等暂切不论。上述代码中,标签“[HttpGet]、[HttpPost]”区分不同的HTTP请求类型。根据ASP.NET MVC约定胜于配置的原则,URL “http://www.xxx.com/User/UserEdit?id=2”中“/User/”指明MVC框架要寻找“UserController”, “/UserEdit”则指明MVC框架要寻找“UserEdit”函数来处理对应的请求。即URL确定了要调用的Controller类及其中的Action函数名称。请求的参数被自动映射为Action函数的参数,特别是在第2个Action函数UserEdit(User user)中被映射为视图模型对象。其实,第1个Action函数UserEdit(int id)也可写为:

[HttpGet]

public ActionResult UserEdit(User user)

{

var userObj = getUserById(user.ID);

return View(userObj);

}

也就是说,我们都可以把请求参数映射为一个视图模型对象,只不过这个对象属性值的填充是否完整要看请求参数的内容,当然我们也不一定非要填充完整,满足程序处理需要即可。

那么,ASP.NET MVC的HTTP请求过程可以抽象表示为下图,请求由View发送给对应的Action,发送的内容则为ViewModel对象。ViewModel对象则成为Action函数的输入参数。

MVC深入理解

(2)再说HTTP响应。HTTP响应的内容一般为一个HTML文档,其被浏览器解析显示为页面。我们看到View文件UserEdit.cshtml实质上是一个HTML模板文件,其获得相应的视图模型对象user后,就会被填充为一个正规的HTML文档。再看Action函数,其明确了请求对应的响应视图:Action函数UserEdit的名称与视图UserEdit.cshtml的去后缀名称是对应一致的,控制器UserController的去后缀名称与Views下的User目录名称是对应一致的。其作用是控制器中的Action函数处理完用户请求后,将使用同名的视图呈现响应结果。另外,Action函数还明确了视图要呈现的数据内容(视图模型对象)。其中最后一行代码“return View(user)”,指明了当前视图要呈现的数据对象为user。

那么,ASP.NET MVC的HTTP响应过程可以抽象表示为下图,响应由Action发送给对应的View,发送的内容则为ViewModel对象。MVC框架底层实现将View文件和ViewModel对象结合为一个正规的HTML文档发送到用户的浏览器。

总结:控制器的作用其实就是将“请求-响应”、“视图(文件)-视图模型(对象)”对应关联起来!

将上面两幅图合并起来,如下图,则抽象的表达了View、ViewModel、Action、Controller之间的关系。即View与Action之间,通过交换ViewModel对象实现交互——实现请求接收与结果响应。

可见,M、V、C三者相互作用的核心是处理视图交互问题,所以Dino在《Microsoft.NET企业级应用架构设计》和Martin Fowler在《企业应用架构模式》中均把MVC归为Web表现层的模式。

3.理解MVC的关键——M

前边我们说了M是理解MVC机制的关键,这个M是View Model,是基于视图的,是对视图内容的抽象。

此M不是Domain Model,它不处理业务逻辑,它应该保持尽可能的简单,一般为POCO类。而业务逻辑由控制器负责处理或其调用独立的业务层进行处理。认为M是处理业务逻辑的误解,通常是将对原始MVC概念的认识,生搬硬套到了现在MVC的新变体Model2模式上。

此M也不是来自数据库ORM的Entity Model,因为没有数据库、没有ORM实体模型,MVC已然有效工作,MVC跟数据库没有直接关系。但很多时候,我们直接使用Entity Model作为视图模型,是因为它往往恰好满足视图需要,但在概念上我们还是要明确:Entity Model此时被作为视图模型使用。

数据库是对企业应用类软件需求的高度抽象,因为通常由经验丰富的高级程序员或架构师一次性在编程实现前设计,导致很多程序员的思维都是从数据库开始,觉得一切(包括视图模型)都从数据库导出的。而且使用Visual Studio创建MVC项目时,Models文件下也会自动创建基于EF的ORM实体模型,更使很多人产生了混淆和误解,觉得基于数据库的实体模型就是MVC中的M。其实,我们编程时思维应该从软件需求开始,软件需求导出界面原型,界面原型导出视图,视图导出视图模型(对象),视图模型导出业务逻辑,业务逻辑导出数据库设计。Code First其实就是这样一种“结果倒推过程”——“需求倒推数据库设计”的思维模式。DB First模式只不过是高级程序员凭借丰富的经验高度压缩了“需求倒推数据库设计”的思维过程。合理的思维应该是“结果倒推”思维,需求是软件的结果,需求驱动着软件开发的一系列过程。按照“结果倒推”的思维,视图呈现的需求导出了对视图模型的定义。所以,ASP.NET MVC中M是基于视图的需要而产生,其应为View Model。

4.复杂交互问题的解决

那么,我们在ASP.NET MVC编程开发中应依据视图来设计视图模型,特别是在处理复杂的有多种交互的视图时,我们应仔细思考如何对视图界面进行抽象,然后设计出合理的视图模型。

我总结为“看着视图设计视图模型”,眼睛盯着视图界面,脑子里把视图想象为一个对象,页面上的元素/数据看作对象的成员。实质就是将视图中的元素对应抽象为POCO类的属性,属性不仅是简单的变量也可以是个小型对象。视图上的每一次交互请求,都将ViewModel对象作为参数传递给对应的Action函数。把一个视图界面看作一个对象,也就不怕复杂了。

心中有对象,世界就简单了。


以上是关于MVC深入理解的主要内容,如果未能解决你的问题,请参考以下文章

深入理解MVC的生命周期

七天学会ASP.NET MVC ——深入理解ASP.NET MVC

深入理解MySQL的间隙锁

三、深入理解OkHttp:连接处理-ConnectIntercepter

深入理解 ASP.NET MVC 上的 async/await

深入理解 MVC .net 中的延迟加载和处理错误