一、技术架构介绍
基于.Net的多层解决方案,web框架采用了微软的ASP.NET MVC 3(视图引擎为Razor),数据层采用Entity Framework 4.0。界面组件方面使用了Telerik for MVC版本,JS框架依然是强大的JQuery。 系统提供了中、英两套语言版本,采用微软官方推荐的多语言解决方案,该方案很容易扩展其他语言版本。
二、MVC
个人感觉ASP.NET MVC 3相比2和1,并没有带来革命性的变化,但一些小的改进还是相当不错的,也提高了不少开发人员的效率。
以下列表显示了我们用到的一些ASP.NET MVC的特性(包括ASP.NET MVC 3但不限于3)
1. 基于layout的页面结构,一个首页可以拆分成如下: 包含title、head、body、以及公用JS、CSS文件的最顶层layout。 包含菜单导航的分部视图(Partial View) 包含主页面内容的View。 主页面里又可以包含其他公共的分部视图,或为了层次更清晰而人为拆分的逻辑部分视图(设计合理的部分视图有利于提高代码层次的清晰程度,但过多的使用也会降低系统性能,毕竟多了一些IO操作)。
2. 通过使用ViewResult、ActionResult、PartialResult、JsonResult输出不同的内容。
3. 新增了Dynamic特性,配合Razor可以很方便的在视图上使用控制器提供的数据。
4. MVC Chart组件可以很方便的输出比较专业的图表。
5. 控制器基类提供了很多重写方法,可以较为方便的进行一些全局的处理,例如:异常、方法级别的注入等。
6. unobtrusive js,通过服务器端ModelMetadata和客户端的unobtrusive js,可以做到前后台的统一验证,大大减少开发量。
三、Razor
采用了微软最新的Razor(模板引擎语言)配合MVC3进行开发,新一代的视图引擎Razor以简洁的“@”开头的写法,对强类型的智能感知做的也非常不错(不过在JavaScript代码部分的智能感知还是有些问题)。 相比ASPX视图引擎,Razor还是胜在简洁上,也有第三方的NVelocity模板引擎,虽然语法也很简洁,但智能感知方面就做的比较差了,因此,开发ASP.NET MVC 3,Razor还是首选。 至于性能上的比较,没有实际分析过,可能会比ASPX略逊一些。
总结Razor的优点如下:
1. 代码流畅简洁。
2. 与HTML结合很顺畅,不需要单独处理大括号。
3. 代码高亮显示。
4. 很方便的输出单行文本(@:)或多行文本()。
四、Entity Framework
数据层采用微软提供的ORM框架Entity Framework,并配合Linq To Entity,能够以简洁的linq语句或lambda表达式,来代替复杂冗长的SQL语句。 总体上来讲EF相比Linq To SQL,并没有特别大的不同,其中结合Validation类来对Model进行MetadataType的标记,来实现数据的校验,感觉是一个比较不错的方式。更能体现代码单一职责原则,我们不必要在控制器里写大量的校验逻辑。
EF的edmx文件在从数据库更新的时候,容易出现更新不成功的情况,这时候则需要将实体删除并从数据库新增就可以解决这个问题。 对于EF的DBContext,在不同层传递的时候,应该尽量一致,个人更倾向于实例化对象的方式,而且比较好的实践方式是: 在业务层持有一个全局的数据层对象引用,在控制器里使用实例化的业务层对象对数据库进行读写操作。 在项目的前期,因为大量使用了数据层或业务层的静态方法,以至于在业务逻辑相对简单的系统管理模块出现了大量的bug,主要的原因就是数据上下文使用混乱,前后不一致,应该说是我们的一个教训。 需要谨慎处理延迟加载,以降低性能方面的影响。什么时候需要延迟加载、什么时候不需要延迟加载,感觉还是跟具体的业务逻辑有关,不能一概而论。 数据对象的使用也是要考虑的一方面,尤其是在前台更改了某些数据属性之后,在同一个上下文里进行数据保存操作后,可能会出现你所不期望的修改。尤其是在复杂的业务计算类库上面,一个对象经过重重传递,很容易出这个问题,并且不太好查。
总结:要明确的知道数据对象的使用范围。
五、AOP
对于ASP.NET MVC 3来讲,天生就是支持AOP的,框架自带的ActionFilterAttribute基类提供了四个重载方法,
如下所示:
1. public virtual void OnActionExecuted(ActionExecutedContext filterContext) 该方法在Action执行之后触发。
2. public virtual void OnActionExecuting(ActionExecutingContext filterContext) 该方法在Action执行之前触发。
3. public virtual void OnResultExecuted(ResultExecutedContext filterContext) 该方法在Result渲染完之后触发。
4. public virtual void OnResultExecuting(ResultExecutingContext filterContext) 该方法在Result渲染之前触发。
通过这四个重载方法,可以非常方便的将代码逻辑织入到Action的生命周期内。 对于我们这个系统,实现了如下几个方面的织入:
1. 功能权限控制。
2. 数据权限控制。
3. 项目状态检查。
4. 操作日志。
AOP带来的好处也是显而易见的,减少代码量、降低业务逻辑和非业务逻辑代码的耦合度、便于维护。
六、Telerik
为了提高开发效率和复用代码,在界面上也大量的使用了Telerik控件for ASP.NET MVC版本。
主要有如下应用:
DatePicker,日期选择组件,其中可以设置可以选取的范围是亮点,但其本身绑定的一些事件也是有利有弊(例如Onblur之后的自动设置日期值),客户端不能灵活的控制。
Window,仿模态窗口,弹窗利器。
Grid,表格组件,功能很强大,但有些特殊场景还是很别扭。同样是客户端控制程度不够。
TreeView,树组件,功能也很强大,很好用。
总结:有一套好的可复用组件确实能大大提高开发效率,但前提是要对其有较好的把控能力,否则容易适得其反。
七、多语言解决方案
采用微软官方推荐的资源文件做法,我们这个系统有中、英文两个版本,则在所有的显示字段、交互提示等地方,全部使用了资源文件标签。 其原理就是根据用户选择的语言种类(存到cookie),动态设置当前线程的语言环境,剩下的事情微软已经帮我们做好了,实现起来很简单,推荐使用。
关键代码:
HttpCookie hk = Utils.Language.GetLangCookie();
string
langName =
""
;
if
(hk !=
null
)
{
langName = hk.Value;
}
else
{ langName =
"zh-cn"
; }
CultureInfo cultureInfo =
new
System.Globalization.CultureInfo(langName);<br>System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; <br>System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
|
八、其他:log4net、NPOI
同时也采用了一些其他优秀的开源框架: 使用log4net来记录异常日志和一些操作日志,使用npoi来进行excel操作。这两个框架都是从JAVA平台移植过来,不得不说,JAVA社区在开源框架方面的研究要胜于.Net。
九、未来考虑
引入Ioc框架:为了降低业务层与数据层、控制器之间的耦合,使代码更灵活以及更容易扩展和维护,考虑后续引入IOC框架(例如:unity、spring.net、castle windsor等)
采用独立缓存服务器:一直以来,缓存都是提升系统性能最廉价的解决方案,目前我们需要缓存的数据量较小,直接使用.Net自带缓存进行缓存处理,例如:组织机构、功能菜单等,虽然.NET自带的缓存功能很强大,但毕竟和app server共用内存,且处于同一应用程序域内,如果在缓存使用较多的情况下,性能不是很理想。未来考虑使用分布式缓存,例如:memcache、redis等优秀的第三方缓存框架。
更好的用户体验,主要体现在优化部分操作流程和页面操作体验。
WCF重构部分服务以支撑更多不同类型的客户端。
微软正式发布支持Visual Studio 2008的Enterprise Library 4.0
MVC中对controller的抽象
将所有controller的公共属性和方法提取出来,放到一个controller基类里,共其它controller使用,这无疑不是一个好的选择,以下是我的项目中controller层的抽象,分享一下代码:
/// <summary>
/// Controller基类
/// </summary>
public abstract class ControllerBase : Controller
{
#region Fields
#endregion
#region Constructor
#endregion
#region Properties
/// <summary>
/// 通用信息(一般由service层返回的)
/// </summary>
protected Entity.VMessage VMessage { get; set; }
#endregion
#region Protected Consts
/// <summary>
/// 每页显示的记录数
/// </summary>
protected const int PAGESIZE = 10;
#endregion
#region Protected virtual Properties
/// <summary>
/// 查询器属性
/// </summary>
protected virtual Entity.VPredication vp { get; set; }
/// <summary>
/// 分页结构属性
/// </summary>
protected virtual Entity.PagingParam pp { get; set; }
#endregion
#region Protected virtual Methods
/// <summary>
/// 操作后,显示的提示成功或失败的信息
/// author:zzl
/// </summary>
/// <param name="msg">提示信息,操作成功或操作失败</param>
/// <param name="url">点击返回列表后,所进入的地址</param>
/// <returns></returns>
protected virtual string DisplayMsg(string msg, string url)
{
string message = string.Format("<div style='color:red;font-size:14px;background:#f4fafb;border:#bbdde5 1px solid;margin:3px;padding:3px'>{0},<a href='{1}'>返回列表</a></div>", msg, url);
return message;
}
protected virtual string DisplayMsg(string msg)
{
string message = string.Format("<div style='color:red;font-size:14px;background:#f4fafb;border:#bbdde5 1px solid;margin:3px;padding:3px'>{0}</div>", msg);
return message;
}
#endregion
#endregion
}