什么叫上下文?

在你设计一个方法的时候,无法直接从方法参数或实例成员(字段或属性)获得的所有信息都是上下文。例如:

  • 当前用户是谁?
  • 刚才提供操作的数据库连接实例从哪里拿到?
  • 这个方法从哪个 View 或者哪个 Controller 调用的?

当然,在方法体中获得上下文最终还是要靠方法参数或实例成员。

在MVC中有大量的上下文信息,例如:

  • ControllerContext
  • ViewContext
  • ModelBindingContext
  • ExceptionContext
  • ActionExcutingContext
  • ActionExcutedContext
  • AuthorizationContext
  • ResultExcutingContext
  • ResultExcutedContext

这些上下文通过单一的参数提供了丰富的运行时信息。

实体上下文放到哪里?

除了MVC的上下文外,还有一个重要的上下文就是 ADO.NET EF的实体上下文,通常派生自System.Data.Objects.ObjectContext,都是由IDE自动生成的。这个上下文承载了数据库连接,需要通过IDisposable来释放连接。多数情况下,我们这样使用:

using(MyEntities context = new MyEntities())
{
…… 在这里写入代码
}

如果在一次页面生命周期内只使用一次实体上下文这样处理是非常合适的,但是事实上不都是这样。更多的时候可能需要临时对实体进行一个小的访问,例如获得一个当前用户的显示名,通过这种方式访问就代价太大了。

我们知道,这个上下文可以存放到HttpContext里。在HttpContext的所有容器中,只有Items是最合适的,因为这个属性的存续期在后台页面对象释放后就结束了。当然,被释放时也不会执行IDisposable的Dispose方法。我们仍然需要在Global.asax中捕捉EndRequest事件。但是奇妙的是:在ASP.NET MVC Application中不能使用event方式来捕捉,只能手工写Application_EndRequest方法

什么是一次Model、二次Model和Form Model?

Model一共分为三种:

  • 直接数据库实体映射实例,如Product(产品)
  • 为View的呈现提供服务的包装对象,如ProductInfo(产品信息)
  • 为Post回传提供服务的包装对象,如ProductForm(产品属性值)

第一种类型Model的特点是非常浓缩,几乎没有冗余,通过复杂的关系进行组合,通常需要通过多个不同类型的实例进行组合来表达一个完整的有意义的场景。例如,一个产品信息可能包含产品名称、产品类别、该产品所有的规格型号以及每种规格型号的参数、单价等。虽然ADO.NET EF提供了获取组合属性的能力,但不能处理多层次,并且不能对加载过程进行控制。所以,需要专门定义一些Model对这一组Model进行包装。如果把原始的模型称做“一次Model”,则可以把这个包装对象称做“二次Model”。

页面上收集到的Form信息,通过三种方式传递到Controller(以登录为例):

  • 每个信息项一个参数:public ActionResult Login(string userName, string password){…}
  • 一个单一的名值对参数:public ActionResult Login(FormCollection formCollection){…}
  • 一个单一的包装对象:public ActionResult Login(LoginInfo info){…}

第一种方式不利于重构。当需要加入一个参数时,必须修改Action的签名。而且也无法令Controller把值传递到View。第二种方式不利于设计时纠错,因为FormCollection中的值不是强类型的。所以,我们通常都会采用第三种方式。虽然ADO.NET EF对象可以直接作为Form Model,并且有BindAttribute对属性与Form值进行定制化的绑定,但是不够灵活,如果一个Form组合跨多个一次Model类型,则根本无法处理。所以我们有必要专门定义一个Model给View使用。我们不妨称之为“Form Model”。

业务逻辑放到什么地方?

MVC是一种“古老”的设计模式,提供了非常自然的分层方式,这也是为什么利于单元测试的原因。除了MVC这些“主层”以外,BLL可以算是一个“亚层”。那么,我们把BLL放到什么地方最合适?

BLL需要完全可见Model层,同时也需要一些上下文信息。例如,我们至少从我们刚才描述的论题中发现,需要从HttpContext的Items中获得实体上下文。有些时候,我们还需要将用户的一些登录信息缓存到HttpContext中,如果用户的登录信息非常复杂的话,仅仅依靠HttpContext.User.Identity.Name每次去抓取未必很合算。我的习惯是把和这个用户相关的信息组合到一个大的Model对象中,并把这个对象的实例 存放到HttpContext.Cache中。如果有任何变化,释放这个Cache项即可。

所以,对于业务逻辑的位置你可以有两个选择:

  • 放到 Model 下,再建立一个“上下文提供器接口”,由 Model 借助上下文来独立处理。
  • 放到 Controller 下,直接使用 Controller 提供的上下文来进行处理。

第一种方式不依赖Controller,解耦彻底,非常灵活,更易于测试。但是需要付出一定的成本,调用栈会稍深一点,还需要劳神处理到Controller与BLLContext间的关系。第二种方式解耦不够彻底,但非常简捷,比较适合 Controller 与 Model 不必彻底解耦的小型项目。有意思的是:ASP.NET MVC Application模板所生成的AccountController采用的就是第二种方式。

ADO.NET EF仅影响ASP.NET MVC的Model层。在Model层中除了EDMX自动生成的一次Model外,我们还需要建立大量的二次Model和Form Model。当然,从提升内聚度考虑,所有的业务逻辑方法都在这些Model中定义,特别是,可以利用partial类和扩展方法这两种手段加入业务逻辑。

ASP.NET MVC + ADO.NET EF 项目实战(一):应用程序布局设计的更多相关文章

  1. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  2. ASP.NET MVC 5 with EF 6 上传文件

        参考   ASP.NET MVC 5 with EF 6 - Working With Files Rename, Resize, Upload Image (ASP.NET MVC) ASP ...

  3. Asp.net mvc web api 在项目中的实际应用

    Asp.net mvc web api 在项目中的实际应用 前言:以下只是记录本人在项目中的应用,而web api在数据传输方面有多种实现方式,具体可根据实际情况而定! 1:数据传输前的加密,以下用到 ...

  4. 简读《ASP.NET Core技术内幕与项目实战》之3:配置

    特别说明:1.本系列内容主要基于杨中科老师的书籍<ASP.NET Core技术内幕与项目实战>及配套的B站视频视频教程,同时会增加极少部分的小知识点2.本系列教程主要目的是提炼知识点,追求 ...

  5. 快读《ASP.NET Core技术内幕与项目实战》EFCore2.5:集合查询原理揭秘(IQueryable和IEnumerable)

    本节内容,涉及4.6(P116-P130).主要NuGet包:如前述章节 一.LINQ和EFCore的集合查询扩展方法的区别 1.LINQ和EFCore中的集合查询扩展方法,虽然命名和使用完全一样,都 ...

  6. 《ASP.NET Core技术内幕与项目实战》精简集-目录

    本系列是杨中科2022年最新作品<ASP.NET Core技术内幕与项目实战>及B站配套视频(强插点赞)的精简集,是一个读书笔记.总结和提炼了主要知识点,遵守代码优先原则,以利于快速复习和 ...

  7. ASP.NET MVC开发:Web项目开发必备知识点

    最近加班加点完成一个Web项目,使用Asp.net MVC开发.很久以前接触的Asp.net开发还是Aspx形式,什么Razor引擎,什么MVC还是这次开发才明白,可以算是新手. 对新手而言,那进行A ...

  8. 解析ASP.NET Mvc开发之EF延迟加载

    目录: 1)从明源动力到创新工场这一路走来 2)解析ASP.NET WebForm和Mvc开发的区别 3)解析ASP.NET Mvc开发之查询数据实例 ------------------------ ...

  9. ASP.NET MVC 入门2、项目的目录结构与核心的DLL

    我们新建一个ASP.NET MVC的Web Application后,默认的情况下,项目的目录结构如下: App_Data :这个目录跟我们一般的ASP.NET website是一样的,用于存放数据. ...

随机推荐

  1. Android源码和内核源码的下载,编译和执行

    笔者依据罗升阳老师的<Android 系统源码情景分析>一书,尝试下载,编译和执行Android源码和内核源码.但可能是软件源"被墙"或版本号更新的原因.期间遇到诸多问 ...

  2. 【Hadoop】Hadoop DataNode节点超时时间设置

    hadoop datanode节点超时时间设置 datanode进程死亡或者网络故障造成datanode无法与namenode通信,namenode不会立即把该节点判定为死亡,要经过一段时间,这段时间 ...

  3. 介绍下Shell中的${}、##和%%使用范例

    假设定义了一个变量为:代码如下:file=/dir1/dir2/dir3/my.file.txt可以用${ }分别替换得到不同的值:${file#*/}:删掉第一个 / 及其左边的字符串:dir1/d ...

  4. IQueryable接口与IEnumberable 区别

    总结一下: IEnumerable<T> 泛型类在调用自己的SKip 和 Take 等扩展方法之前数据就已经加载在本地内存里了,而IQueryable<T> 是将Skip ,t ...

  5. 配置Linux实现静态路由

    配置Linux实现静态路由 背景和原理 路由器的功能是实现一个网段到另一个网段之间的通信,路由分为静态路由.动态路由. 默认路由和直连路由.静态路由是手工指定的,使用静态路由的好处是网络安全保密性高. ...

  6. 如何让mysql的自动递增的字段重新从1开始呢?(

    数据库表自动递增字段在用过一段时间后清空,还是继续从清空后的自动编号开始.如何才能让这个字段自动从1开始自动递增呢? 下面两个方法偶都试过,很好用: 1 清空所有数据,将自增去掉,存盘,在加上自增,存 ...

  7. 函数传参,改变Div任意属性的值&&图片列表:鼠标移入/移出改变图片透明度

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. MPTCP 源码分析(一) MPTCP的三次握手

    简述:      MPTCP依然按照正常的TCP进行三次握手,只是在握手过程中增加了MPTCP特有的信息.   建立过程      三次握手过程如下图所示: 左边客户端发送的第一个SYN包携带有客户端 ...

  9. Windows服务器SYSTEM权限Webshell无法添加3389账户情况突破总结

    转自:http://bbs.blackbap.org/thread-2331-1-1.html 近好多Silic的朋友在Windows下SYSTEM权限的php webshell下添加账户,但是却无法 ...

  10. C++ 设置文件最近修改时间

    利用VS开发C++项目,经常发现修改系统时间后,每次编译过程会变得很慢,其原因就是当你把系统时间调到未来的一个时间点,然后有意或者无意编辑过一些代码文件,那么这些文件的时间戳就停留在未来. 当你把系统 ...