Creating a session ASP.NET MVC action filter
使用ASP.NET MVC动作过滤器(action filter)创建会话
通常,一个unit of work可以简洁地映射到单个的controller action. 下面介绍在ASP.NET MVC应用程序中如何通过创建一个action filter来管理我们的NHibernate会话.
准备
为NHibernate创建一个ASP.NET MVC应用程序,步骤如下:
1. 创建一个ASP.NET MVC应用程序.
2. 添加引用:NHibernate.dll, NHibernate.ByteCode.Castle.dll, log4net.dll和Eg.Core项目.
3. 在web.config文件中,添加NHibernate和log4net的设置节点.可以参考第二章中的Configuring NHibernate with App.config小节的示例
4. 将current_session_context_class属性设置为web .
5. 在Global.asax中, 创建一个名为SessionFactory的静态属性.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static ISessionFactory SessionFactory { get; private set; }
6. I在Application_Start方法中,添加下述代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
log4net.Config.XmlConfigurator.Configure(); var nhConfig = new Configuration().Configure(); SessionFactory = nhConfig.BuildSessionFactory();
步骤
1. 添加NHibernateSessionAttribute类,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public class NHibernateSessionAttribute : ActionFilterAttribute { public NHibernateSessionAttribute() { Order = 100; } protected ISessionFactory sessionFactory { get { return MvcApplication.SessionFactory; } } public override void OnActionExecuting( ActionExecutingContext filterContext) { var session = sessionFactory.OpenSession(); CurrentSessionContext.Bind(session); } public override void OnActionExecuted( ActionExecutedContext filterContext) { var session = CurrentSessionContext.Unbind(sessionFactory); session.Close(); } }
2. 为controller actions布置特性,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[NHibernateSession] public ActionResult Index() { return View(DataAccessLayer.GetBooks()); }
3. 创建一个虚拟数据访问层,代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System.Collections.Generic; namespace ActionFilterExample { public static class DataAccessLayer { public static IEnumerable<Eg.Core.Book> GetBooks() { var session = MvcApplication.SessionFactory .GetCurrentSession(); using (var tx = session.BeginTransaction()) { var books = session.QueryOver<Eg.Core.Book>() .List(); tx.Commit(); return books; } } } }
4. 在Views文件夹下创建一个名为Book的文件夹.
5. 在Book文件夹下,添加一个view,设置如下图所示:
6. 打开SQL Server Management Studio, 连接NHCookbook数据库,运行下面的SQL代码来创建一些book数据:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
USE NHCookbook INSERT INTO Product VALUES ( NEWID(), 'Eg.Core.Book', 0, 'NHibernate 3 Cookbook', 'Bridging the gap between database and .NET Application', 45.99, null, 'Jason Dentler', '3043' ) INSERT INTO Product VALUES ( NEWID(), 'Eg.Core.Book', 0, 'NHibernate 2 Beginner's Guide', 'Rapidly retrieve data from your database into .NET objects', 45.99, null, 'Aaron Cure', '978-1-847198-90-7' )
7. 编译运行.页面如下:
原理
这个示例的理念和本章开头部分的session-per-request小节的示例是很相像的.我们使用了应用session-per-request模式的NHibernate上下文会话.
在controller的action:Index()执行之前, ASP.NET MVC会先运行筛选器的OnActionExecuting方法.在OnActionExecuting中,通过NHibernate的上下文会话特性筛选器会打开一个会话并将她绑定到这个web请求.
类似地,在Index()返回前,ASP.NET MVC会运行筛选器的OnActionExecuted方法.在该方法中筛选器会解除会话的绑定并将会话关闭.然后交由ASP.NET MVC处理返回的结果.在本示例中, 它呈现一个视图来显示一个书籍列表.
action filter的Order属性决定了action filter的执行顺序. 对于执行中的事件,所有没有显式指定执行顺序的action filter会先被执行,然后那些有指定顺序的action filter才会被执行,执行顺序是:从0开始,按升序排列.对于执行完毕的事件,action filter按相反的顺序执行.本质上,就是将action filter压栈,后进先出.这样就有了一个确定的顺序, 因此我们可以将它和有更高Order值的session-dependent filters结合起来使用.
扩展
NHibernate要求每一个数据库的交互操作都要应用NHibernate事务,无论是她是会话中方法的直接调用,还是触发延迟加载的action.通过使用这种实现,想要在事务处理中捕获延迟加载的调用时很难的.但在下个小节中我们会看到曙光,在一个单独的action filter中适当地结合使用会话和事务处理,会在其他地方实现controller action中的延迟加载.
请确保action加载了view所需的所有数据.action的结果(这个示例中是一个view)返回后该会话不再是打开的.
提示
因为会话已经关闭,因此如果一个view试图访问一个延迟加载的集合,而该集合尚未被controller action加载,就会抛出LazyInitializationException.
即使有更宽松的实现,也不应从view直接访问数据库,因为view通常是动态的并且难以测试.
View 模式
为了规避这样那样的问题,许多ASP.NET MVC应用程序使用view模式.为每个view定义一个类,该类只包含该view所需的数据. 她被认为是controller和view之间的数据传输对象.与其写几页将数据从实体复制到view模式的管道代码,不如使用一个开源项目:AutoMapper. 结合使用action filter属性,事情会变得非常简单.在Jimmy Bogard的博客上有个很不错的示例,网址是:http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx.
请注意AutoMapper属性上的Order属性.为了实现从实体到view模式转换的延迟加载,应该将Order设置为比会话属性更高的值.这将确保AutoMapper时会话是打开的.