Orchard提供了依赖注入机制,并且框架的实现也离不开依赖注入如模块管理、日志、事件等。在前一篇中提到在Global.asax中定义的HostInitialization创建了Autofac的IoC容器。
1 private static IOrchardHost HostInitialization(HttpApplication application) { 2 var host = OrchardStarter.CreateHost(MvcSingletons); 3 4 host.Initialize(); 5 6 // initialize shells to speed up the first dynamic query 7 host.BeginRequest(); 8 host.EndRequest(); 9 10 return host;11 }
其中OrchardStarter是位于Orchard.Framework项目中Environment目录下的类型
1 public static IOrchardHost CreateHost(Actionregistrations) {2 var container = CreateHostContainer(registrations);3 return container.Resolve ();4 }
以下是容器创建的全量代码:
1 public static IContainer CreateHostContainer(Actionregistrations) { 2 ExtensionLocations extensionLocations = new ExtensionLocations(); 3 4 var builder = new ContainerBuilder(); 5 // Application paths and parameters 6 builder.RegisterInstance(extensionLocations); 7 8 builder.RegisterModule(new CollectionOrderModule()); 9 builder.RegisterModule(new LoggingModule()); 10 builder.RegisterModule(new EventsModule()); 11 builder.RegisterModule(new CacheModule()); 12 13 // a single default host implementation is needed for bootstrapping a web app domain 14 builder.RegisterType ().As ().SingleInstance(); 15 builder.RegisterType ().As ().SingleInstance(); 16 builder.RegisterType ().As ().SingleInstance(); 17 builder.RegisterType ().As ().SingleInstance(); 18 builder.RegisterType ().As ().SingleInstance(); 19 builder.RegisterType ().As ().SingleInstance(); 20 builder.RegisterType ().As ().Named (typeof(IShellSettingsManagerEventHandler).Name).SingleInstance(); 21 builder.RegisterType ().As ().SingleInstance(); 22 builder.RegisterType ().As ().SingleInstance(); 23 builder.RegisterType ().As ().SingleInstance(); 24 builder.RegisterType ().As ().SingleInstance(); 25 builder.RegisterType ().As ().SingleInstance(); 26 builder.RegisterType ().As ().SingleInstance(); 27 builder.RegisterType ().As ().SingleInstance(); 28 builder.RegisterType ().As ().SingleInstance(); 29 builder.RegisterType ().As ().SingleInstance(); 30 builder.RegisterType ().As ().SingleInstance(); 31 builder.RegisterType ().As ().InstancePerDependency(); 32 builder.RegisterType ().As ().SingleInstance(); 33 builder.RegisterType ().As ().SingleInstance(); 34 builder.RegisterType ().As ().SingleInstance(); 35 //builder.RegisterType ().As ().SingleInstance(); 36 37 RegisterVolatileProvider (builder); 38 RegisterVolatileProvider (builder); 39 RegisterVolatileProvider (builder); 40 RegisterVolatileProvider (builder); 41 RegisterVolatileProvider (builder); 42 RegisterVolatileProvider (builder); 43 RegisterVolatileProvider (builder); 44 RegisterVolatileProvider (builder); 45 RegisterVolatileProvider (builder); 46 47 builder.RegisterType ().As ().As () 48 .Named (typeof(IShellSettingsManagerEventHandler).Name) 49 .Named (typeof(IShellDescriptorManagerEventHandler).Name) 50 .SingleInstance(); 51 { 52 builder.RegisterType ().As ().SingleInstance(); 53 54 builder.RegisterType ().As ().SingleInstance(); 55 { 56 builder.RegisterType ().As ().SingleInstance(); 57 58 builder.RegisterType ().As ().SingleInstance(); 59 { 60 builder.RegisterType ().As ().SingleInstance(); 61 builder.RegisterType ().As ().SingleInstance(); 62 builder.RegisterType ().As ().SingleInstance(); 63 builder.RegisterType ().As ().SingleInstance(); 64 { 65 builder.RegisterType ().As ().SingleInstance(); 66 builder.RegisterType ().As ().SingleInstance() 67 .WithParameter(new NamedParameter("paths", extensionLocations.ModuleLocations)); 68 builder.RegisterType ().As ().SingleInstance() 69 .WithParameter(new NamedParameter("paths", extensionLocations.CoreLocations)); 70 builder.RegisterType ().As ().SingleInstance() 71 .WithParameter(new NamedParameter("paths", extensionLocations.ThemeLocations)); 72 73 builder.RegisterType ().As ().SingleInstance(); 74 builder.RegisterType ().As ().SingleInstance(); 75 builder.RegisterType ().As ().SingleInstance(); 76 builder.RegisterType ().As ().SingleInstance(); 77 builder.RegisterType ().As ().SingleInstance(); 78 } 79 } 80 81 builder.RegisterType ().As ().SingleInstance(); 82 } 83 84 builder.RegisterType ().As ().SingleInstance(); 85 } 86 87 builder.RegisterType ().As ().SingleInstance(); 88 builder.RegisterType ().As ().InstancePerMatchingLifetimeScope("shell"); 89 builder.RegisterType ().As ().InstancePerMatchingLifetimeScope("shell"); 90 91 registrations(builder); 92 93 var autofacSection = ConfigurationManager.GetSection(ConfigurationSettingsReaderConstants.DefaultSectionName); 94 if (autofacSection != null) 95 builder.RegisterModule(new ConfigurationSettingsReader()); 96 97 var optionalHostConfig = HostingEnvironment.MapPath("~/Config/Host.config"); 98 if (File.Exists(optionalHostConfig)) 99 builder.RegisterModule(new ConfigurationSettingsReader(ConfigurationSettingsReaderConstants.DefaultSectionName, optionalHostConfig));100 101 var optionalComponentsConfig = HostingEnvironment.MapPath("~/Config/HostComponents.config");102 if (File.Exists(optionalComponentsConfig))103 builder.RegisterModule(new HostComponentsConfigModule(optionalComponentsConfig));104 105 var container = builder.Build();106 107 //108 // Register Virtual Path Providers109 //110 if (HostingEnvironment.IsHosted) {111 foreach (var vpp in container.Resolve >()) {112 HostingEnvironment.RegisterVirtualPathProvider(vpp.Instance);113 }114 }115 116 ControllerBuilder.Current.SetControllerFactory(new OrchardControllerFactory());117 FilterProviders.Providers.Add(new OrchardFilterProvider());118 119 GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new DefaultOrchardWebApiHttpControllerSelector(GlobalConfiguration.Configuration));120 GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new DefaultOrchardWebApiHttpControllerActivator(GlobalConfiguration.Configuration));121 GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);122 123 GlobalConfiguration.Configuration.Filters.Add(new OrchardApiActionFilterDispatcher());124 GlobalConfiguration.Configuration.Filters.Add(new OrchardApiExceptionFilterDispatcher());125 GlobalConfiguration.Configuration.Filters.Add(new OrchardApiAuthorizationFilterDispatcher());126 127 ViewEngines.Engines.Clear();128 ViewEngines.Engines.Add(new ThemeAwareViewEngineShim());129 130 var hostContainer = new DefaultOrchardHostContainer(container);131 //MvcServiceLocator.SetCurrent(hostContainer);132 OrchardHostContainerRegistry.RegisterHostContainer(hostContainer);133 134 // Register localized data annotations135 ModelValidatorProviders.Providers.Clear();136 ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider());137 138 return container;139 }
代码很长,但是可以分为以下几个部分:
- 基础组件:日志、事件、缓存
- 环境相关:路径、程序集、编译器,Http上下文
- 异常处理:异常策略
- 拓展相关:拓展目录、拓展依赖、拓展管理。
- 多租户相关:Shell表、Shell启动。
- 拓展的依赖注入:拓展模块中的自定义注入、Host及HostComponents配置文件的注入。
- MVC相关:ControllerFactory替换,过滤器加载
- WebAPI相关:Selector、Activator替换,DependencyResolver替换、过滤器加载
- ViewEngine:使用ThemeAwareViewEngineShim。
- Model相关:使用LocalizedModelValidatorProvider作为Model验证器。
相对特殊的地方有:
- 日志的自动属性注入:
Orchard使用Autofac实现了日志的属性自动注入,具体代码如下:
1 protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) { 2 var implementationType = registration.Activator.LimitType; 3 4 // build an array of actions on this type to assign loggers to member properties 5 var injectors = BuildLoggerInjectors(implementationType).ToArray(); 6 7 // if there are no logger properties, there's no reason to hook the activated event 8 if (!injectors.Any()) 9 return;10 11 // otherwise, whan an instance of this component is activated, inject the loggers on the instance12 registration.Activated += (s, e) => {13 foreach (var injector in injectors)14 injector(e.Context, e.Instance);15 };16 }17 18 private IEnumerable> BuildLoggerInjectors(Type componentType) {19 // Look for settable properties of type "ILogger"20 var loggerProperties = componentType21 .GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)22 .Select(p => new {23 PropertyInfo = p,24 p.PropertyType,25 IndexParameters = p.GetIndexParameters(),26 Accessors = p.GetAccessors(false)27 })28 .Where(x => x.PropertyType == typeof(ILogger)) // must be a logger29 .Where(x => x.IndexParameters.Count() == 0) // must not be an indexer30 .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)); //must have get/set, or only set31 32 // Return an array of actions that resolve a logger and assign the property33 foreach (var entry in loggerProperties) {34 var propertyInfo = entry.PropertyInfo;35 36 yield return (ctx, instance) => {37 string component = componentType.ToString();38 if (component != instance.GetType().ToString()) {39 return;40 }41 var logger = _loggerCache.GetOrAdd(component, key => ctx.Resolve (new TypedParameter(typeof(Type), componentType)));42 propertyInfo.SetValue(instance, logger, null);43 };44 }45 }
其实它的核心就是注册时通过对象类型查找是否有一个类型为Ilogger的可读写属性,拿出来赋值。
2. 通过动态代理实现的事件机制:
在.Net中提到事件就会想到委托,但是Orchard的事件比较特殊,它将事件定义为一个接口(实现IEventHandler的接口),对于生产者(事件发布者)它仅需要通过Orchard的容器注入一个相应接口类型即可,而观察者(事件的真实处理器)则仅需要实现相应接口即可(其余框架会自动处理)。
对.Net的委托不了解的可参考张子阳老师的博客:
Orchard事件的使用如下(使用张子阳老师例子改了一下):
1 public interface IBoilHandler : IEventHandler 2 { 3 void OnTemperatureChanged(int temperature); 4 } 5 6 // 热水器 7 public class Heater 8 { 9 private int temperature;10 private IBoilHandler _boilhandler;11 12 public Heater(IBoilHandler boilhandler)13 {14 _boilhandler = boilhandler;15 }16 17 // 烧水18 public void BoilWater()19 {20 for (int i = 0; i <= 100; i++)21 {22 temperature = i;23 24 if (temperature > 95)25 {26 if (_boilhandler != null)27 { //如果有对象注册28 _boilhandler.OnTemperatureChanged(temperature); //调用所有注册对象的方法29 }30 }31 }32 }33 }34 35 // 警报器36 public class Alarm : IBoilHandler37 {38 public void OnTemperatureChanged(int temperature)39 {40 Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", temperature);41 }42 }43 44 // 显示器45 public class Display : IBoilHandler46 {47 public void OnTemperatureChanged(int temperature)48 {49 Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", temperature);50 }51 }
那么这是如何实现的呢?
两个要点:
1. 在注册服务时将所有通过继承IEventhandler的子接口(如上面代码的IBoilHandler)通过动态代理,将相应接口的方法执行全部拦截并转到EventBus上,调用Notify方法,其参数为当前接口名称+方法名称和相应的参数。--生产者
1 public class EventsRegistrationSource : IRegistrationSource { 2 private readonly DefaultProxyBuilder _proxyBuilder; 3 4 public EventsRegistrationSource() { 5 _proxyBuilder = new DefaultProxyBuilder(); 6 } 7 8 public bool IsAdapterForIndividualComponents { 9 get { return false; }10 }11 12 public IEnumerableRegistrationsFor(Service service, Func > registrationAccessor) {13 var serviceWithType = service as IServiceWithType;14 if (serviceWithType == null)15 yield break;16 17 var serviceType = serviceWithType.ServiceType;18 if (!serviceType.IsInterface || !typeof(IEventHandler).IsAssignableFrom(serviceType) || serviceType == typeof(IEventHandler))19 yield break;20 21 var interfaceProxyType = _proxyBuilder.CreateInterfaceProxyTypeWithoutTarget(22 serviceType,23 new Type[0],24 ProxyGenerationOptions.Default);25 26 27 var rb = RegistrationBuilder28 .ForDelegate((ctx, parameters) => {29 var interceptors = new IInterceptor[] { new EventsInterceptor(ctx.Resolve ()) };30 var args = new object[] { interceptors, null };31 return Activator.CreateInstance(interfaceProxyType, args);32 })33 .As(service);34 35 yield return rb.CreateRegistration();36 }37 }38 39 public class EventsInterceptor : IInterceptor {40 private readonly IEventBus _eventBus;41 private static readonly ConcurrentDictionary _enumerableOfTypeTDictionary = new ConcurrentDictionary ();42 43 public EventsInterceptor(IEventBus eventBus) {44 _eventBus = eventBus;45 }46 47 public void Intercept(IInvocation invocation) {48 var interfaceName = invocation.Method.DeclaringType.Name;49 var methodName = invocation.Method.Name;50 51 var data = invocation.Method.GetParameters()52 .Select((parameter, index) => new { parameter.Name, Value = invocation.Arguments[index] })53 .ToDictionary(kv => kv.Name, kv => kv.Value);54 55 var results = _eventBus.Notify(interfaceName + "." + methodName, data);56 57 invocation.ReturnValue = Adjust(results, invocation.Method.ReturnType);58 }59 60 public static object Adjust(IEnumerable results, Type returnType) {61 if (returnType == typeof(void) ||62 results == null ||63 results.GetType() == returnType) {64 return results;65 }66 67 // acquire method:68 // static IEnumerable IEnumerable.OfType (this IEnumerable source)69 // where T is from returnType's IEnumerable 70 var enumerableOfTypeT = _enumerableOfTypeTDictionary.GetOrAdd( returnType, type => typeof(Enumerable).GetGenericMethod("OfType", type.GetGenericArguments(), new[] { typeof(IEnumerable) }, typeof(IEnumerable<>)));71 return enumerableOfTypeT.Invoke(null, new[] { results });72 }73 }
上面代码有个重点就是:
1 if (!serviceType.IsInterface || !typeof(IEventHandler).IsAssignableFrom(serviceType) || serviceType == typeof(IEventHandler))2 yield break;
如果不是接口、如果类型不是IEventHandler的子类型(子接口)且当前类型就是IEventHandler,满足这三个接口就跳出。正过来说就是:
- 类型一定是接口
- 一定是继承IEventHandler的接口
- 一定不是IEventHandler本身
2. 把所有实现了IEventHandler的实现全部解析到EventBus的_eventHandlers字段中。 --消费者
换句话说就是,当应用程序初始化的时候EventBus里面的处理器都已经被加载了,但真正触发事件时,仅仅是通过拦截器将相应接口的方法转到了Notify方法上,进而去通知相应的Handlers执行。
下面代码位于ShellContainerFactory.cs文件中
1 if (typeof(IEventHandler).IsAssignableFrom(item.Type)) { 2 var interfaces = item.Type.GetInterfaces(); 3 foreach (var interfaceType in interfaces) { 4 5 // register named instance for each interface, for efficient filtering inside event bus 6 // IEventHandler derived classes only 7 if (interfaceType.GetInterface(typeof (IEventHandler).Name) != null) { 8 registration = registration.Named(interfaceType.Name); 9 }10 }11 }
3. ICacheManager的注入:
ICacheManager的特殊之处就在于依赖它的对象在通过构造注入ICacheManager会将当前对象作为参数来实例化ICacheManager。
1 protected override void AttachToComponentRegistration(Autofac.Core.IComponentRegistry componentRegistry, Autofac.Core.IComponentRegistration registration) { 2 var needsCacheManager = registration.Activator.LimitType 3 .GetConstructors() 4 .Any(x => x.GetParameters() 5 .Any(xx => xx.ParameterType == typeof(ICacheManager))); 6 7 if (needsCacheManager) { 8 registration.Preparing += (sender, e) => { 9 var parameter = new TypedParameter(10 typeof(ICacheManager),11 e.Context.Resolve(new TypedParameter(typeof(Type), registration.Activator.LimitType)));12 e.Parameters = e.Parameters.Concat(new[] { parameter });13 };14 }15 }
小结:
本章主要是分析了Orchard中IoC容器的创建以及基础设施的注册,其中对Log、Event和CacheManager进行了特殊处理,并且处理方式均是使用Autofac提供的一系列特性,动态的对注册的组件进行额外操作,另外在Orchard中它还针对Shell级别创建了一个子容器,所以后续将对Orchard的依赖注入进一步分析。
参考: