MVC 简单框架起步-分层(二)
框架当然离不开分层,最开始写 Hello Word 第一个程序,很简单,完全不需要其他,直接Console.Write() 一下就好。后来需要实现一个小小的9*9乘法表,直接在一个方法在里面实现,可当我们实现一个业务的时候,可能要个100行左右的代码量,将此这个业务拆分成几个方法块,然后进行组合调用,这样做的目的也是解耦,为了以后需求变更时更好的拓展,分层也是这个目的,常见的一般时 数据访问层DLL,业务数据层BLL,模型层Model,UI层,有时候还会有一个帮助层,和接口层,这些层之间关系如下
这种实现方式看起来很基础,感觉和 最开始 拆分方法很类似,用的时候 New 一下,不过是方法放在同一个类库下,而分层是在一个新的类库中,另外要把类库生成 dll 添加进来。综合来看依赖性依旧很强,所以需要进行调整,采用控制反转的方式来解除强依赖关系,这里采用AutoFac 的技术来实现,调整后的框架结构如下:
如上,接入 AutoFac (IOC) 后,就不必进行大量的New 操作,需要那个方法,直接通过接口向容器拿就好。只需要将 BLL,DLL层的接口继承 IDependency接口,然后在全局文件Global中 进行注册。对于用惯了 New 方式的人来说,代码简直优美的不行,当然对于IOC 控制反转 的工具有很多,像 微软自带的 MEF,和其他的第三方工具 AutoFac ,Unity、Spring.NET、StructureMap 等等,这里主推AutoFac 实现简单,好理解,效率高,我之前有用过微软自带的 MEF,也就那样,直到尝试了AutoFac,果然主流的东西还是更香一些。
关于AutoFac 如何使用,这里做一个简要的说明,网上很多文章,说的不明不白,浅藏辄止,举几个简单的例子,感觉很简单,实际在项目使用的时候疑惑的地方很多。
1. 如何批量注册IBLL 和IDLL层
2. 同一个接口被多个类继承并实现,调用的时候如何区分
3. 网上大多是Web层 直接通过容器调用DLL层,那么当web调用BLL层,BLL去调用DLL层时,BLL如何关联DLL层
这些问题对于熟悉容器的人来说可能不值一提,但对于小白来说,很是纠结,一度迷惑了我很久。试想带着很多疑问去学习,或者说是不理解。这就是一根坎,总有一些路要独自一个人走,能走多远,走多快全靠自己领悟,如果没有领路人真的是费劲。这里记录一下
1. 实际项目中,不可能挨个接口去注册,不现实,这里只需要定义一个公共接口类,让IBLL层,IDLL层 均继承此接口,然后再全局文件Global.asax 中注册此公共接口即可
#region AtuoFac 注册 Assembly[] assemblies = Directory.GetFiles(AppDomain.CurrentDomain.RelativeSearchPath, "*.dll").Select(Assembly.LoadFrom).ToArray(); //创建autofac管理注册类的容器实例 var builder = new ContainerBuilder(); //注册所有实现了 IDependency 接口的类型 Type baseType = typeof(IDependency); builder.RegisterAssemblyTypes(assemblies) //.Where(type => baseType.IsAssignableFrom(type) && !type.IsAbstract || type.GetCustomAttribute(typeof(ServiceAttribute)) != null) .Where(t => baseType.IsAssignableFrom(t) || baseType.IsAssignableFrom(t) && !t.IsAbstract) .AsSelf().AsImplementedInterfaces() .PropertiesAutowired().InstancePerLifetimeScope(); //使用Autofac提供的RegisterControllers扩展方法来对程序集中所有的Controller一次性的完成注册 builder.RegisterControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired(); builder.RegisterFilterProvider(); //生成具体的实例 var container = builder.Build(); //下面就是使用MVC的扩展 更改了MVC中的注入方式. DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); #endregion
2. 当同一个接口被多个类继承并实现时,按照常规,用接口对象去调用时,默认是后一个被注册的实现类覆盖前一个实现类。如果想区分开,可以采用 Attribute 特性的方式给实现类分别加上不同的名称,写一个根据特性名找对应实现类的公共方法,调用此方法即可
3. 对于Web 层直接调用 DLL 层来说,很简单,直接添加接口对象,然后再构造函数中,赋值接口对象即可。当通过BLL 层来实现此方式时,需要做一个转变,我们先在BaseLL 层的基类中定义BaseDLL 为null
public class BaseBLL<T> : IBaseBLL<T> where T : class, new() { //定义BasaDLL为 null protected IBaseDLL<T> BaseDLL = null; public int Add(T model) { return BaseDLL.Add(model); } }
然后在BLL业务实现类中的构造函数中对 该业务实现类对应的 数据访问类接口对象进行赋值,以及该业务实现类的基类进行赋值,具体赋值操作如下
public class MenuBLL:BaseBLL<Menu>,IMenuBLL { IMenuDLL MenuDLL { set; get; } public MenuBLL(IMenuDLL dLL) { BaseDLL = dLL; MenuDLL = dLL; } }
如此,需要调用函数时候,用同样的方式在控制器所在的构造函数中进行赋值,便可以通过容器任意调用BLL层中的函数
public class HomeController : Controller { public IMenuBLL MenuBLL { set; get; } public HomeController(IStudentBLL bLL) { MenuBLL = menuBLL; } ..... }