开始尝试MVC模式的使用,将相关的理解和使用在此记录
——————————————————————————————————————————————
相关博客推荐,感谢写出这些博客的前辈们·:
——————————————————————————————————————————————
初步认识:
概念:
MVC即Model、View、Controller即模型、视图、控制器。是一种软件设计典范。
它采用了将业务逻辑、数据、界面 三者相分离的方法来组织代码。
将众多的业务逻辑聚集在一个部件内,一旦需要对用户交互和界面进行修改,则不需要重新编写业务逻辑,从而达到减少编码的时间。
比起传统ASP.net的winforms方式,MVC显著优点在于更能理解分层逻辑,实现了Controller代码的单独管理。winform也并不是不能实现分层逻辑,只不过比MVC模式处理要多一些代码,另外在一些简单的项目中比MVC更为简洁。
详述:
一个简单的mvc例子就是在windows资源管理器浏览文件夹时的显示方法。
这里文件的内容没有改变,改变的是文件的显示方法,实现了MV相分离。
而在网页中:
V【view视图】,是指用户看到并与之交互的界面。譬如由html元素组成的网页界面,或者软件的客户端界面。MVC好处之一在于能为应用程序处理很多不同的视图。而在这些视图中并没有真正的的逻辑和操作处理,只是作为输出数据并允许用户操纵的方法。
M【model模型】,是指模型表示业务规则,用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。在mvc中,模型拥有最多的处理任务,同时也有对数据直接访问的权力,例如对数据库的访问。但是被模型所返回的数据是中立的,模型与数据格式无关,这个模型能为多个视图提供数据,减少了代码重复性。
C【controller控制器】,是指控制器接受用户的输入并调用模型和视图去完成用户需求,它本身不输出任何东西和做任何处理。它起到不同层面间的组织作用,用于控制应用程序的流程,它只是接受请求并界定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
结合两图可以看出各个部分的关系,虽然各种语言和各种框架对MVC定义各有不同,但每个部分都是分工明确、界定清晰的。
几个常见的MVC应用举例:
1、最典型的MVC就是jsp+servlet+javabean模式。
- JavaBean作为模型,既可以作为数据模型来封装业务数据,又可以作为业务逻辑模型来包含应用的业务操作。其中,数据模型用来存储或传递业务数据,而业务逻辑模型接收到控制器传过来的模型更新请求后,执行特定的业务逻辑处理,然后返回相应的执行结果。
- JSP作为表现层,负责提供页面为用户展示数据,提供相应的表单(Form)来用于用户的请求,并在适当的时候(点击按钮)向控制器发出请求来请求模型进行更新。
- Serlvet作为控制器,用来接收用户提交的请求,然后获取请求中的数据,将之转换为业务模型需要的数据模型,然后调用业务模型相应的业务方法进行更新,同时根据业务执行结果来选择要返回的视图。
2、Struts2框架:
- Struts2是基于MVC的轻量级的web应用框架。Struts2的应用范围是Web应用,注重将Web应用领域的日常工作和常见问题抽象化,提供一个平台帮助快速的完成Web应用开发。基于Struts2开发的Web应用自然就能实现MVC,Struts2着力于在MVC的各个部分为开发提供相应帮助。
3、ASP.NET的mvc:
- 可以直接新建一个MVC项目,会自动生成相关的文件。
深入理解:
参考深入理解MVC
MVC与三层架构的混淆
这是在MVC之前红极一时的一种软件设计模式。在MVC出现后逐步消失。
三层架构,和MVC相似的一个地方在于它也是分成三层:UI层表示用户界面,BLL层表示业务逻辑,DAL层表示数据访问。
直观看去,这两种模式似乎如出一辙,改一下各层次的名称几乎就是MVC。
然而两者绝对不能混淆。如果按着三层架构的思路来写MVC,出来的只能是一个什么都不像的、愚蠢的编码方式。
实际上,三层架构的核心思想是面向接口编程、各层次之间的解耦和可替代性。MVC并没有这个概念。二者要解决的问题截然不同,所以以MVC为基础写出来的三层架构也不会具备三层架构的核心要义。
以下叙述的MVC可以应用到任何软件中,也适合WEB框架下的开发。
MVC的本质原理
MVC要实现的目标是将软件用户界面和业务逻辑分离以使代码可扩展性、可复用性、可维护性、灵活性加强。
View层是界面,Model层是业务逻辑,Controller层用来调度View层和Model层,将用户界面和业务逻辑合理的组织在一起,起粘合剂的效果。所以Controller中的内容能少则少,这样才能提供最大的灵活性。
比方说,有一个View会提交数据给Model进行处理以实现具体的行为,View通常不会直接提交数据给Model,它会先把数据提交给Controller,然后Controller再将数据转发给Model。假如此时程序业务逻辑的处理方式有变化,那么只需要在Controller中将原来的Model换成新实现的Model就可以了,控制器的作用就是这么简单, 用来将不同的View和不同的Model组织在一起,顺便替双方传递消息,仅此而已。
MVC的正确使用方式
在使用MVC模式中,很多人喜欢将业务逻辑放在Controller中,这是完全错误的做法。这个大家也知道,但是为什么呢?
当写程序时,经常要进行业务逻辑的重复使用。
如果业务逻辑写到控制器内,惟一的重用方法就是将该控制器提升到父类之中,通过继承来实现代码的复用。但这样去做会带出一个面向对象设计原则的重要问题:【接口隔离】
【接口隔离】
- 通俗来说,接口隔离就是当一个类要继承另一个类时,如果被继承类中有继承类不需要的方法或者属性,就不要去实现这个类。
- 如果非要实现这个继承,那也需要从被继承的类中提取一个只包含需要功能的新类型,然后再去继承这个新类型才是正确的做法。
- 简单的说,就是不要去继承那些用不到的事物。
继续回到论述,当我们通过继承父控制器的方式来复用业务逻辑时,就会出现为了重用一个方法而继承一堆用不到的方法,会让代码越来越无法理解子类快速膨胀,有时甚至对新继承类有害。
这里引出到另一个关于继承的条件问题。
- 只有满足IS-A关系时,才可以使用继承,所谓IS-A关系,即“是一个”,A IS-A B,意味着B是A的父类。此时,继承是对已有类的一个改造,需要对全部的内容进行应有的重写。
- 使用组合是所谓的HAS-A关系,即“有一个”,A HAS-A B意思是说,B是A的部分,有明显的“整体-部分”区别。
- 少用继承,多用组合
再看mvc的模式。
各Model之间是可以相互调用的, Controller也可以无障碍的调用Model,因此将业务逻辑放在Model中可以灵活的使用组合的方式复用代码。
而Controller之间是不可以相互调用的,要复用代码只能将代码提升至父类,通过继承实现,显然这种做法既不正确,也不灵活,因此完全不提倡。
综上所述,仅仅只是代码复用这一点,也足以将“厚Controller,薄Model”这种不健康的MVC思想打入十八层地狱。
MVC与设计模式
众所周知,GoF总结过23个设计模式,这23个设计模式是某些特定的编程问题的特效药,这是业内公认的。
MVC是一种模式,但却在GoF总结出来的这个23个设计模式之外,确切的说它不是一种设计模式,它是多种设计模式的组合,并不仅仅只是一个单独的一个模式。
组成MVC的三个模式分别是组合模式、策咯模式、观察者模式,MVC在软件开发中发挥的威力,最终离不开这三个模式的默契配合。 那些崇尚设计模式无用论的程序员,请了解只要你们使用MVC,就离不开设计模式。
这里本人并不十分理解,仅仅将原文的总结列举在下方,需要的话,请点原文了解。
- View层,单独实现了组合模式
- Model层和View层,实现了观察者模式
- View层和Controller层,实现了策咯模式
MVC就是将这三个设计模式在一起用了,将这三个设计模式弄明白,MVC将毫无神秘感可言。如果不了解这三个设计模式去学习MVC,那不管怎么学总归是一知半解,用的时候也难免不会出想问题。
ASP.NET MVC 5个人实验:
ASP.NET MVC 5 系列教程,该系列教程,从一个web网站示例开始讲解,全文最终完成了一个管理影片的小系统,非常适合新手入门ASP.NET MVC 5 (新增、删除、查询、更新) ,并由此开始开发工作。感谢大佬们的翻译和奉献,对我起到了极大的帮助!
值得一提,本练习逐步深入,因此前期会看到在控制器中行使了视图职能之类的错误行为,伴随学习深入,我将逐步修改。
1、控制器
新增操作:直接对解决方案资源管理器中对“Controllers”右键添加,就可以新增一个控制器类了。
添加成功后我们会发现除了“Controllers”文件夹内出现了新增的控制器类外,在views文件夹内也出现了对应的文件夹,这个我们先放过。
URL路由逻辑:
先将自动生成的Index()方法进行修改,并添加一个新的方法Welcome:
public string Index()
{
return "This is my <b>default</b> action...";
}
public string Welcome(string name,int ID=1)
{
return "This is the Welcome action method...";
}
现在我们浏览地址:http://localhost:xxxx/HelloWorld(以及/Welcome),我们可以看到返回的字符串。
ASP.NET MVC的默认URL路由逻辑使用这样的格式来判定哪些代码以便调用:
/[Controller]/[ActionName]/[Parameters]
当然,如果对这个默认逻辑不满意,你也可以在App_Start/RouteConfig.cs 文件内通过配置URL路由解析规则:
public static void RegisterRoutes(RouteCollection routes)
{//默认的RouteConfig.cs文件爱你
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new{controller = "Home",action="Index",id =UrlParameter.Optional}
);
}
- 第一部分的URL确定哪个控制器类会被执行。因此 /HelloWorld映射到HelloWorldController控制器类。
- 第二部分的URL确定要执行控制器类中的哪个操作方法。因此 /HelloWorld/Index,会使得
HelloWorldController
控制器类的Index 方法被执行。请注意,我们只需要浏览 /HelloWorld路径,默认情况下会调用Index方法。如果没有明确的指定操作方法,Index方法会默认的被控制器类调用。 - 第三部分的URL段(Parameters参数)是路由数据。
现在来深入对于第三部分URL段的理解:
将之前的welcome方法进行2种修改:
//1
public string Welcome(string name, int numTimes = 1) {
return HttpUtility.HtmlEncode("Hello " + name + ", NumTimes is: " + numTimes);
}
//2
public string Welcome(string name, int ID = 1)
{
return HttpUtility.HtmlEncode("Hello " + name + ", ID: " + ID);
}
第1种时,我们使用【http://localhost:xxxx/HelloWorld/Welcome?name=Scott&numtimes=4】访问。
第2种时,我们使用【 http://localhost:xxx/HelloWorld/Welcome/3?name=Rick】访问。
观察二者的效果。
(
值得一提,这里使用了 HttpServerUtility.HtmlEncode 来保护应用从malacious输入的(也就是JavaScript). 即对不安全字符串安全保护的一种措施。
相关链接:点击这里。
)
第一种仅仅将URL中的query string映射到您方法中的参数,而第二种则是使用了第三段URL的路由路径。
我们试着对第一种welcome方法使用【http://localhost:xxx/HelloWorld/Welcome/3?name=Rick】(第二种方式)访问,却惊讶的发现最后返回的NumTimes仅仅是1.
这是为什么?
我们重新打开之前的RouteConfig.cs 文件。然后发现这里面的Parameters参数默认是“ID”。
url: "{controller}/{action}/{name}/{id}"
如果我们在这里把这个id改成“numTimes”,再以第二种方式访问,就可以带来同样的效果。
但现在的两个参数之间没有分开,这样一来并不是我们所谓的第三段URL,如果我们想要分开该怎么做?
还是RouteConfig.cs 文件,我们添加如下内容(在routes.MapRoute后再添加一个routes.MapRoute):
routes.MapRoute(
name:"Hello",
url: "{controller}/{action}/{name}/{id}"
);
这个时候使用【/localhost:XXX/HelloWorld/Welcome/Scott/3.】访问,就实现了用“/”来间隔参数的目的。
关于参数缺省访问的相关内容,将在后续进行。
2、视图
新增操作:
在创建试图视图模板文件前,我们需要用到Razor视图引擎(Razor view engine)
Razor视图模板文件使用.cshtml文件扩展名.
1、之前我们已经创建过一个控制器类,这里结合视图的时候,要对控制器类的index方法进行修改,使它返回一个view对象。
public ActionResult Index()
{
return View();
}
///这里的Index方法使用一个视图模板来生成一个HTML返回给浏览器。
///返回对象除去ActionResult外,也可以是从它处继承的子类。
2、对我们先前所创建的Views\HelloWorld 文件夹上,右键单击添加-MVC 5 View Page with (Layout Razor) 【带有布局的MVC5视图页】,名称为Index,“选择布局页(Select a Layout Page)”对话框中,选择shared下的缺省“_Layout.cshtml”,并单击”确定“。
3、此时,我们就可以看到在解决方案资源管理器中的MvcMovie\View\HelloWorld\Index.cshtml文件。
4、在文件中,添加如下:
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@{
ViewBag.Title = "Movie List";
}
<h2>My Movie List</h2>
<p>Hello from our View Template!</p>
5、在解决方案资源管理器,找到Index.cshtml文件,右键单击并选择“在浏览器中查看”。同样的,也可以直接运行后访问http://localhost:xxxx/HelloWorld“,因为此时的控制器中的indexf方法并没有很多工作,仅仅执行了return view().,这个方法指定使用一个视图模板文件来Render返回给浏览器的HTML。因为您没有明确指定使用那个视图模板文件,ASP.NET MVC会默认使用\Views\HelloWorld文件夹下的Index.cshtml视图文件
修改视图和布局页面:
在这里,我把提及的三个文件的名称和地址都放出来。
1、_Layout.cshtml文件。/Views/Shared文件夹。布局页面(Layout page)/布局模板。
2、index.cshtml文件。/views/HelloWorld文件夹。视图模板(?),被控制器所返回的html页面时根据它生成的。
3、_ViewStart.cshtml 文件。Views/文件夹最下方。
我们首先要搞清楚三者之间的关系。
1是模板页,你根据它所创建的页面都会采用它的布局(包括标题等信息。)
2是你所创建的页面文件,你可以设置了ViewBag
对象的属性,从而将参数传给布局模板中。
3文件定义我们使用到的所有视图的通用布局,在其他页面没有设置布局的情况下,会采用这个文件所约定的布局。
此外还要注意Index.cshtml视图模板中的内容是如何合并到_Layout.cshtml模板,从而形成一个完整的HTML返回到客户端浏览器的。使用布局模板页面,可以很容易进行一个修改并应用到所有页面。
3、将数据从控制器传给视图。
这一节其实可以说是数据库和数据模型之前的一个铺垫。
我们从上文可以知道,控制器可以把数据传给模型(model),也可以传给视图(view)。
控制器类将响应请求来的URL。控制器类是给您写代码来处理传入请求的地方,并从数据库中检索数据,并最终决定什么类型的返回结果会发送回浏览器。视图模板可以被控制器用来产生格式化过的HTML从而返回给浏览器。
同样从上文可知:控制器负责给任何数据或者对象提供一个必需的视图模板,用这个视图模板来Render返回给浏览器的HTML。最佳做法是:一个视图模板应该永远不会执行业务逻辑或者直接和数据库进行交互。相应的,一个视图模板应该只和控制器所提供的数据进行交互。维持这种"隔离关系"可以帮助,保持代码的干净、测试性和更易维护。
所以,我们从控制器中将这些参数,放入到一个ViewBag
对象中,然后让视图模板可以访问这个对象。
ViewBag
是一个动态的对象,这意味着在您没有给ViewBag
放置属性时,它没有任何属性,您可以把任何您想放置的对象放入到 ViewBag
对象中。
以下为一个例子:
HelloWorldController.cs文件中,对welcome方法进行一个修改:
public ActionResult Index()
{
return View();
}
public ActionResult Welcome(string name, int numTimes = 1)
{
ViewBag.Message = "Hello " + name;
ViewBag.NumTimes = numTimes;
return View();
}
根据上一节,创建一个“_Layout.cshtml”为布局模板的Welcome.cshtml文件。
文件的内容:对得到的viewbag内的内容进行一个循环(参数决定)输出。
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@{
ViewBag.Title = "Welcome";
}
<h2>My Movie List</h2>
<ul>
@for (int i = 0; i < ViewBag.NumTimes; i++)
{
<li>@ViewBag.Message</li>
}
</ul>
运行后,进入这个URL:http://localhost:xx/HelloWorld/Welcome?name=Scott&numtimes=4
此时,模型绑定(model binder) 使得数据从URL传递给控制器。控制器将数据装入到ViewBag
对象中,通过该对象传递给视图。然后视图为用户生成显示所需的HTML。
4、添加模型(Model)
还是我们的案例。
对model文件夹右键添加-class(最下方)
其中代码如下:
using System;
using System.Data.Entity;
namespace MVCtest1.Models
{
//1
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
//2
public class MovieDBContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
}
}
一个命名空间中,我们放入了两个类。
Movie
类:表示数据库中的电影。 Movie
对象的每个实例将对应数据库表的一行, Movie
类的每个属性将对应表的一列。
MovieDBContext
类:MovieDBContext
类代表Entity Framework的电影数据库类,这个类负责在数据库中获取,存储,更新,处理 Movie
类的实例。
值得一提,DbContext
和DbSet需要添加:
using System.Data.Entity;
如果您发现“未能找到类型或命名空间名称“DbContext”这个错误,则说明您VS中少了 Entity Framework,EntityFramework是.net framework 3.5中的东西,当前项目可能是更高的版本……所以,在工具-NuGut包管理器-控制管理台,在下方输入指令:【PM> Install-Package EntityFramework】即可。
5、创建链接字符串,并使用sqlserverDB……
这个我直接掠过了,需要可以看原文……
6、从控制器访问数据模型