欢迎访问 生活随笔!

凯发k8官方网

当前位置: 凯发k8官方网 > 编程语言 > c/c >内容正文

c/c

(四) view/model 全解(mvc) -凯发k8官方网

发布时间:2024/10/8 c/c 0 豆豆
凯发k8官方网 收集整理的这篇文章主要介绍了 (四) view/model 全解(mvc) 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

转自:http://www.cnblogs.com/zhangziqiu/archive/2009/03/18/aspnet-mvc-4.html

 

一.摘要

本文讲解在action中向view传递model的几种方式.以及view获取model以后如何编写显示逻辑.还详细的介绍了asp.net mvc框架提供的html helper类的使用及如何为html helper类添加自定义扩展方法.

上一篇文章中我们学习了controller处理一次请求的全过程.在controller的action中, 会传递数据给view,还会通知view对象开始显示.所以model是在action中获取的, 并由action传递给view. view对象接到action通知后会使用自己的显示逻辑展示页面.

下面首先让我们学习如何将model传递给view对象.

三.传递数据给view

在mvc中,model对象是指包含了数据的模型. controller将model传递给view以后, view对象中不应该做任何的业务逻辑处理, 仅仅根据model对象做一些显示逻辑的处理.

传递model对象时, 我们有两种选择:

1.传递一个弱类型的集合, 即成员为object类型的集合,  在view中需要将每个成员转换成我们需要的类型,比如int, string,自定义类型等.

2.传递强类型对象, 这些类型是我们自定义的. 在view中直接使用我们传递的强类型对象, 不需要再转换类型.

如果让我们自己设计一个mvc框架, 我们也会想到上面两种实现方式,接下来看看在asp.net mvc中的实现.

1.传递弱类型的集合

(1) 如何传递

asp.net mvc框架定义了viewcontext类, 直译后是"view上下文", 其中保存和view有关的所有数据, 其中model对象也封装在了此类型中.

viewcontext对象包含三个属性:

  • iview view
  • viewdatadictionary viewdata
  • tempdatadictionary tempdata

其中viewdata集合和tempdata集合都是用来保存model对象的.在一个controller的action中, 我们可以用如下方式为这两个集合赋值:

/// /// 传递弱类型model的action示例 /// /// viewresult public actionresult weaktypeddemo() { viewdata["model"] = "weak type data in viewdata"; tempdata["model"] = "weak type data in tempdata"; return view("weaktypeddemo"); }

 

在页面中, 是用如下方式使用这两个集合:

<div> <% = viewdata["model"] %><br /> <% = tempdata["model"] %><br /> div>

 

(2) 传递过程

请注意action中的赋值语句实际上操作的是controller类的viewdata和tempdata属性, 此时并没有任何的数据传递.上一篇文章中我们已经学到, return view语句会返回一个viewresult对象, 并且接下来要执行viewresult的executeresult方法. controller的view方法会将controller类的viewdata和tempdata属性值传递给viewresult,代码如下:

protected internal virtual viewresult view(iview view, object model) { if (model != null) { viewdata.model = model; } return new viewresult { view = view, viewdata = viewdata, tempdata = tempdata }; }

然后在viewresult中根据viewdata和tempdata构建viewcontext对象:

public override void executeresult(controllercontext context) { //... viewcontext viewcontext = new viewcontext(context, view, viewdata, tempdata); view.render(viewcontext, context.httpcontext.response.output); //... }

 

viewcontext对象最终会传递给viewpage, 也就是说viewdata和tempdata集合传递到了viewpage. 我这里简化了最后的传递流程, 实际上viewdata对象并不是通过viewcontext传递到viewpage中的, viewpage上的viewdata是一个单独的属性, 并没有像tempdata一样其实访问的是viewcontext.tempdata. 这么做容易产生奇异, 本类viewcontext是一个很好理解职责十分清晰的类. 作为使用者的我们暂时可以忽略这点不足, 因为如此实现viewdata完全是为了下面使用强类型对象.

(3)viewdata和tempdata的区别

虽然viewdata和tempdata都可以传递弱类型数据,但是两者的使用是有区别的:

  • viewdata的生命周期和view相同, 只对当前view有效.
  • tempdata保存在session中, controller每次执行请求的时候会从session中获取tempdata并删除session, 获取完tempdata数据后虽然保存在内部的字典对象中,但是tempdata集合的每个条目访问一次后就从字典表中删除. 也就是说tempdata的数据至多只能经过一次controller传递.

(4) tempdata的实现

tempdata的类型是tempdatadictionary, 和一般的字典表没有明显的不同, tempdata的生命周期是由controll决定的.

在所有controll的基类controllerbase中, 创建了类型为tempdatadictionary的tempdata属性.

在controllerbase的派生类controller中, 重写了executecore()方法:

protected override void executecore() { tempdata.load(controllercontext, tempdataprovider); try { string actionname = routedata.getrequiredstring("action"); if (!actioninvoker.invokeaction(controllercontext, actionname)) { handleunknownaction(actionname); } } finally { tempdata.save(controllercontext, tempdataprovider); } }

注意其中的tempdata.load和tempdata.save语句.

tempdatadictionary.load用来从controllercontext总读取tempdata的数据.

tempdatadictionary.save方法将发生了变化的tempdata数据保存到controllercontext中.

这两个方法都需要传递itempdataprovider实例负责具体的读取和保存操作. 在controll中默认的tempdataprovider是sessionstatetempdataprovider, 也就是说读取和保存都使用session. 我们也可以扩展自己的tempdataprovider, 可以将临时数据保存在任何地方.比如制作aspnetcachetempdataprovider使用机器本地缓存来保存tempdata.

为何tempdata只能够在controll中传递一次? 因为sessionstatetempdataprovider.loadtempdata方法(在tempdatadictionary.load中调用)在从controllercontext的session中读取了tempdata数据后, 会清空session:

public virtual idictionary<string, object> loadtempdata(controllercontext controllercontext) { httpcontextbase httpcontext = controllercontext.httpcontext; if (httpcontext.session == null) { throw new invalidoperationexception(mvcresources.sessionstatetempdataprovider_sessionstatedisabled); } dictionary<string, object> tempdatadictionary = httpcontext.session[tempdatasessionstatekey] as dictionary<string, object>; if (tempdatadictionary != null) { // if we got it from session, remove it so that no other request gets it httpcontext.session.remove(tempdatasessionstatekey); return tempdatadictionary; } else { return new dictionary<string, object>(stringcomparer.ordinalignorecase); } }

注意上面加粗的部分. 一旦读取成功, 就会删除tempdata的session.

再回忆一下controll的executecore方法,  是在controll的execute方法中调用的, 是每一次controll一定会执行的方法, 所以我们得出了"只能在controll之间传递一次"的结论.

 

 

2.传递强类型对象

我在系统中建立了一个模型类:strongtypeddemodto

从名字可以看出, 这个模型类是数据传输时使用的(data transfer object).而且是我为一个view单独创建的.

添加一个传递强类型model的action,使用如下代码:

public actionresult strongtypeddemo() { strongtypeddemodto model = new strongtypeddemodto() { username="ziqiu.zhang", userpassword="123456" }; return view(model); }

使用了controller.view()方法的一个重载, 将model对象传递给view对象.下面省略此对象的传输过程, 先让我们来看看如何在view中使用此对象.

在创建一个view时, 会弹出下面的弹出框:

勾选"create a strongly-typed view"即表示要创建一个强类型的view, 在"view data class"中选择我们的数据模型类.

在"view content"中如下选项:

这里是选择我们的view的"模板", 不同的模板会生成不同的view页面代码. 虽然这些代码不一定满足我们需要, 但是有时候的确能节省一些时间,尤其是在写文章做demo的时候. 比如我们的view是添加数据使用的,那就选择"create".如果是显示一条数据用的, 就选择"detail".

以选择detail为例, 自动生成了下列代码:

<%@ page language="c#" inherits="system.web.mvc.viewpage" %> ... <body> <fieldset> <legend>fieldslegend> <p> username: <%= html.encode(model.username) %> p> <p> userpassword: <%= html.encode(model.userpassword) %> p> fieldset> <p> <%=html.actionlink("edit", "edit", new { /* id=model.primarykey */ }) %> | <%=html.actionlink("back to list", "index") %> p> body> ...

页面的model属性就是一个强类型对象, 在这里就是strongtypeddemodto类实例.page页面指令可以看出, 这里的页面继承自viewpage类, 而不是viewpage, 用t已经确定为strongtypeddemodto类型, 所以model的类型就是strongtypeddemodto.

3.传递数据的思考

使用强类型数据要优于弱类型数据, 老赵也曾提出过. 强类型有太多的好处, 智能提示, 语意明确, 提高性能,编译时发现错误等等.

所以在实际应用中, 我们应该传递强类型的数据.

目前asp.net mvc还存在一些不足. 将页面类型化,  导致了只能传递一种数据类型实例到页面上. 而且内部代码的实现上并不十分完美.尤其是虽然我们已经知道传递的是strongtypeddemodto类型, 页面的model属性也是strongtypeddemodto类型, 但是仍然需要进行强制类型转换, 原因是controller的view(object model)方法重载接收的是一个object类型.

还有, 为每个view建立一个模型类, 也是一件繁琐的工作. 也许我们的业务模型中的两个类组合在一起就是view页面需要的数据, 但是却不得不建立一个类将其封装起来.模型类的膨胀也是需要控制一个事情. 尤其是对于互谅网应用而非企业内部的系统, 页面往往会有成百上千个.而且复用较少.

当然目前来说既然要使用强类型的model, 我提出一些组织model类型的实践方法.下面是我项目中的model类型组织结构:

这里model是一个文件夹, 稍大一些的系统可以建立一个model项目. 另外我们需要建立一个dto文件夹, 用来区分model的类型. mvc中的model应该对应dto文件夹中的类.在dto中按照controller再建立文件夹, 因为action和view大部分都是按照controller组织的, 所以model也应该按照controller组织.

在controller文件夹里放置具体的model类. 其实两个controller文件夹中可以同相同的类名称, 我们通过命名空间区分同名的model类:

namespace demorc.models.dto.transfermodelcontroller { /// /// action为strongtypeddemo的数据传输模型 /// public class strongtypeddemodto { /// /// 用户名 /// public string username { get; set; } /// /// 用户密码 /// public string userpassword { get; set; } } }

使用时也要通过带有controller的命名空间使用比如dto.transfermodelcontroller.strongtypeddemodto, 或者建立一些自己的约定.

view对象获取了model以后, 我们可以通过两种方式使用数据:

1.使用内嵌代码

熟悉asp,php等页面语言的人都会很熟悉这种直接在页面上书写代码的方式.但是在view中应该只书写和显示逻辑有关的代码,而不要增加任何的业务逻辑代码.

假设我们创建了下面这个action,为viewdata添加了三条记录:

/// /// action示例:使用内嵌代码输出viewdata /// /// public actionresult showmodelwithinsidecodedemo() { viewdata["k1"] = @""; return view("showmodelwithinsidecode"); }

 

 

在showmodelwithinsidecode中, 我们可以通过内嵌代码的方式, 遍历viewdata集合:

<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>使用内嵌代码输出viewdatatitle> <% foreach(keyvaluepair<string, object> item in viewdata ) {%> <% = item.value %> <% } %> head> <body> <div> <div>此页面运行的脚本代码为:div> <fieldset> <% foreach(keyvaluepair<string, object> item in viewdata ) {%> <% = html.encode(item.value) %> <br /> <% } %> fieldset> div> body> html>

页面上遍历了两遍viewdata,第一次是作为脚本输出的, 第二次由于进行了html编码,所以将作为文字显示在页面上.

使用这种方式, 我们可以美工做好的html页面的动态部分, 使用<% %>的形式转化为编码区域, 通过程序控制输出.由于只剩下显示逻辑要处理,所以这种开发方式往往要比codebehind的编码方式更有效率, 维护起来一目了然.

最让我高兴是使用这种方式后,我们终于可以只使用html控件了.虽然asp.net webfrom编程模型中自带了很多服务器控件, 功能很好很强大, 但是那终究是别人开发的控件, 这些控件是可以随意变化的, 而且实现原理也对使用者封闭. 使用原始的页面模型和html控件将使我们真正的做程序的主人.而且不会因为明天服务器控件换了个用法就要更新知识, 要知道几乎所有的html控件几乎是被所有浏览器支持且不会轻易改变的.

2.使用服务器控件

注意虽然我们同样可以在asp.net mvc中使用服务器端控件, 但是在mvc中这并不是一个好的使用方式.建议不要使用.

要使用服务器端控件, 我们就需要在后台代码中为控件绑定数据. asp.net mvc框架提供的添加一个view对象的方法已经不能创建后台代码, 也就是说已经摒弃了这种方式.但是我们仍然可以自己添加.

首先创建一个带有后台代码的(.cs文件),一般的web form页面(aspx页面),然后修改页面的继承关系, 改为继承自viewpage:

public partial class showmodelwithcontrol : system.web.mvc.viewpage

 

在页面上放置一个repeater控件用来显示数据:

<body> <form id="form1" runat="server"> <div> <asp:repeater id="rptview" runat="server"> <itemtemplate> <%# ((keyvaluepair<string, object>)container.dataitem).value %><br /> itemtemplate> asp:repeater> div> form> body>

在page_load方法中, 为repeater绑定数据:

public partial class showmodelwithcontrol : system.web.mvc.viewpage { protected void page_load(object sender, eventargs e) { rptview.datasource = viewdata; rptview.databind(); } }

在controller中创建action, 为viewdata赋值:

/// /// action示例:使用服务器控件输出viewdata /// /// public actionresult showmodelwithcontroldemo() { viewdata["k1"] = @"this"; viewdata["k2"] = @"is"; viewdata["k3"] = @"a"; viewdata["k4"] = @"page"; return view("showmodelwithcontrol"); }

运行结果:

再次强调,  在asp.net mvc中我们应该尽量避免使用这种方式.

3.使用 html helper 类生成html控件

html helper类是asp.net mvc框架中提供的生成html控件代码的类. 本质上与第一种方式一样, 只是我们可以不必手工书写html代码,而是借助html helper类中的方法帮助我们生成html控件代码.

同时,我们也可以使用扩展方法为html helper类添加自定义的生成控件的方法.

html helper类的大部分方法都是返回一个html控件的完整字符串, 所以可以直接在需要调用的地方使用<% =html.actionlink() %>的形式输出字符串.

(1)asp.net mvc中的htmlhelper类

在viewpage中提供了html属性, 这就是一个htmlhelper类的实例. asp.net mvc框架自带了下面这些方法:

  • html.actionlink()
  • html.beginform()
  • html.checkbox()
  • html.dropdownlist()
  • html.endform()
  • html.hidden()
  • html.listbox()
  • html.password()
  • html.radiobutton()
  • html.textarea()
  • html.textbox()

上面列举的常用的htmlhelper类的方法,并不是完整列表.

下面的例子演示如何使用htmlhelper类:

<div> <% using (html.beginform()) { %> <label style="width:60px;display:inline-block;">用户名:label> <% =html.textbox("username", "ziqiu.zhang", new { style="width:200px;" })%> <br /><br /> <label style="width:60px;display:inline-block;">密码:label> <% =html.password("psssword", "123456", new { style = "width:200px;" })%> <% }%> div>

上面的代码使用html.beginform输出一个表单对象, 并在表单对象中添加了两个input, 一个使用html.textbox输出, 另一个使用html.password输出,区别是html.password输出的input控件的type类型为password.效果如图:

(2)扩展html helper类

我们可以自己扩展htmlhelper类, 为htmlhelper类添加新的扩展方法, 从而实现更多的功能.

在项目中建立extensions文件夹, 在其中创建spanextensions.cs文件.源代码如下:

using system; using system.collections.generic; using system.linq; using system.web; using system.web.mvc; namespace system.web.mvc { public static class spanextensions { public static string span(this htmlhelper helper,string id, string text) { return string.format(@""{0}"">{1}", id, text); } } }

上面的代码我们为htmlhelper类添加了一个span()方法, 能够返回一个span的完整html字符串.

因为命名空间是system.web.mvc,所以页面使用的时候不需要再做修改,visual studio会自动识别出来:

请大家一定要注意命名空间, 如果不使用system.web.mvc命名空间, 那么一定要在页面上引用你的扩展方法所在的命名空间, 否则我们的扩展方法将不会被识别.

接下来在页面上可以使用我们的扩展方法:

<div> <% =html.span("textspan", "使用自定义的span方法扩展htmlhelper类生成的span") %> div>

 

(3) 使用tagbuilder类创建扩展方法

上面自定义的span()方法十分简单, 但是有时候我们要构造具有复杂结构的html元素, 如果用字符串拼接的方法就有些笨拙.

asp.net mvc框架提供了一个帮助我们构造html元素的类:tagbuilder

tagbuilder类有如下方法帮助我们构建html控件字符串:

方法名称用途
addcssclass()添加class=””属性
generateid()添加id,  会将id名称中的"."替换为idattributedotreplacement 属性值的字符.默认替换成"_"
mergeattribute()添加一个属性,有多种重载方法.
setinnertext() 设置标签内容, 如果标签中没有再嵌套标签,则与设置innerhtml 属性获得的效果相同.
tostring() 输出html标签的字符串, 带有一个参数的重载可以设置标签的输出形式为以下枚举值:
  • tagrendermode.normal -- 有开始和结束标签
  • tagrendermode.starttag -- 只有开始标签
  • tagrendermode.endtag -- 只有结尾标签
  • tagrendermode.selfclosing -- 单标签形式,如

同时一个tagbuilder还有下列关键属性:

属性名称用途
attributes tag的所有属性
idattributedotreplacement 添加id时替换"."的目标字符
innerhtml tag的内部html内容
tagname html标签名, tagbuilder只有带一个参数-tagname的构造函数.所以tagname是必填属性


下面在添加一个自定义的htmlhelper类扩展方法,同样是输出一个标签:

public static string span(this htmlhelper helper, string id, string text, string css, object htmlattributes) { //创意某一个tag的tagbuilder var builder = new tagbuilder("span"); //创建id,注意要先设置idattributedotreplacement属性后再执行generateid方法. builder.idattributedotreplacement = "-"; builder.generateid(id); //添加属性 builder.mergeattributes(new routevaluedictionary(htmlattributes)); //添加样式 builder.addcssclass(css); //或者用下面这句的形式也可以: builder.mergeattribute("class", css); //添加内容,以下两种方式均可 //builder.innerhtml = text; builder.setinnertext(text); //输出控件 return builder.tostring(tagrendermode.normal); }

在页面上,调用这个方法:

<% =html.span("span.test", "使用tagbuilder帮助构建扩展方法", "colorred", new { style="font-size:15px;" })%>

生成的html代码为:

<span id="span-test" class="colorred" style="font-size: 15px;">使用tagbuilder帮助构建扩展方法span>


注意已经将id中的"."替换为了"-"字符.

五.总结

本来打算在本文中直接将viewengine的使用也加进来, 但是感觉本文已经写了很长的内容, (虽然不多,但是很长......)所以将viewengine作为下一章单独介绍.

前些天 scott guthrie's的博客上放出了"asp.net mvc免费教程", 里面介绍了创建一名为"nerddinner"项目的全过程, 使用linq asp.net mvc, 但是其中对于技术细节没有详细介绍(和本系列文章比较一下就能明显感觉出来), 下面提供本书的pdf文件下载地址以及源代码下载地址:

免费下载pdf版本下载应用源码 单元测试

 

源代码是英文版本,  其实我最近有做一个中文的"nerd dinner"的想法, 但是因为要写文章而且还要工作已经让我焦头烂额, 写文章真的是一件体力活.如果有人同样有兴趣翻译代码, 我可以提供域名和服务器空间.

 

转载于:https://www.cnblogs.com/jams742003/archive/2009/11/04/1595932.html

总结

以上是凯发k8官方网为你收集整理的(四) view/model 全解(mvc)的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得凯发k8官方网网站内容还不错,欢迎将凯发k8官方网推荐给好友。

  • 上一篇:
  • 下一篇:
网站地图