Skip to content

Instantly share code, notes, and snippets.

@sheastrickland
Created April 11, 2011 22:33
Show Gist options
  • Save sheastrickland/914515 to your computer and use it in GitHub Desktop.
Save sheastrickland/914515 to your computer and use it in GitHub Desktop.
Windsor MvcFacility - Early hacks
using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
namespace VMS.Domain.WindsorMvcFacility
{
public class ControllerContextHost
{
private ControllerBase controller;
public void SetController(ControllerBase controller)
{
if (this.controller != null)
{
return;
//An exception cannot be thrown here because child request actions will fail.
//throw new InvalidOperationException("Controller was already set!");
}
this.controller = controller;
}
public ControllerContext GetContext()
{
if (controller == null)
{
return null;
}
return controller.ControllerContext;
}
}
public class RequestContextHost
{
private RequestContext context;
public void SetContext(RequestContext context)
{
if (this.context != null)
{
//An exception cannot be thrown here because child request actions will fail.
return;
}
this.context = context;
}
public RequestContext GetContext()
{
if (context == null)
{
throw new InvalidOperationException("Context was not set!");
}
return context;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using VMS.Domain.Extensions;
namespace VMS.Domain.WindsorMvcFacility
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class ModelBinderForTypesAttribute : Attribute
{
public ModelBinderForTypesAttribute(params Type[] targetTypes)
{
if (targetTypes == null)
throw new ArgumentNullException("targetTypes");
TargetTypes = targetTypes;
}
public ModelBinderForTypesAttribute(Type targetType)
{
if (targetType == null)
throw new ArgumentNullException("targetType");
TargetTypes = new[] {targetType};
}
public IEnumerable<Type> TargetTypes { get; set; }
public static IEnumerable<Type> ExtractTypesFor(IModelBinder modelBinder)
{
return modelBinder.GetType().IsDecoratedWith<ModelBinderForTypesAttribute>()
? modelBinder.GetType().GetAttribute<ModelBinderForTypesAttribute>().TargetTypes
: Enumerable.Empty<Type>();
}
}
}
using System;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.MicroKernel.Facilities;
using Castle.MicroKernel.Registration;
namespace VMS.Domain.WindsorMvcFacility
{
public class MvcFacility : AbstractFacility
{
private Assembly _bindersAssembly;
private Assembly _controllersAssembly;
protected override void Init()
{
TransientScopedInWebRequestLifestyleModule.Init(Kernel);
DependencyResolver.SetResolver(new WindsorDependencyResolver(Kernel));
RegisterAspNetContextImpl();
RegisterControllersImpl();
RegisterModelBindersImpl();
}
public MvcFacility RegisterControllers()
{
_controllersAssembly = Assembly.GetCallingAssembly();
return this;
}
public MvcFacility RegisterModelBinders()
{
_bindersAssembly = Assembly.GetCallingAssembly();
return this;
}
private void RegisterModelBindersImpl()
{
if (_bindersAssembly == null) return;
Kernel.Register(AllTypes.FromAssembly(_bindersAssembly)
.BasedOn<IModelBinder>()
.WithService.AllInterfaces()
.Configure(c => c.LifeStyle.PerWebRequest));
//MVC will hold the references to this component in a static dictionary, so lifetime is a moot point
Kernel.Register(Component.For<IModelBinderProvider>().ImplementedBy<WindsorModelBinderProvider>());
}
private void RegisterControllersImpl()
{
if (_controllersAssembly == null) return;
Kernel.Register(Component.For<ControllerContextHost>().LifeStyle.PerWebRequest,
Component.For<Func<ControllerContext>>()
.UsingFactoryMethod<Func<ControllerContext>>(k => k.Resolve<ControllerContextHost>().GetContext)
.LifeStyle.Transient,
AllTypes.FromAssembly(_controllersAssembly)
.BasedOn<IController>()
.If(t => t.Namespace.EndsWith(".Controllers"))
.If(t => t.Name.EndsWith("Controller"))
.Configure((c => c.LifeStyle.Transient
.OnCreate((k, v) => k.Resolve<ControllerContextHost>()
.SetController(v as ControllerBase)))));
Kernel.Register(Component.For<IControllerFactory>().ImplementedBy<WindsorControllerFactory>().LifeStyle.PerWebRequest);
}
private void RegisterAspNetContextImpl()
{
Kernel.Register(Component.For<RequestContextHost>().LifeStyle.PerWebRequest,
Component.For<RequestContext>()
.UsingFactoryMethod(k => k.Resolve<RequestContextHost>().GetContext())
.LifeStyle.Transient,
Component.For<HttpContextBase>()
.UsingFactoryMethod(k => k.Resolve<RequestContext>().HttpContext)
.LifeStyle.Transient,
Component.For<HttpRequestBase>()
.UsingFactoryMethod(k => k.Resolve<HttpContextBase>().Request)
.LifeStyle.Transient,
Component.For<HttpServerUtilityBase>()
.UsingFactoryMethod(k => k.Resolve<HttpContextBase>().Server)
.LifeStyle.Transient,
Component.For<UrlHelper>()
.UsingFactoryMethod(k => new UrlHelper(k.Resolve<RequestContext>(), RouteTable.Routes))
.LifeStyle.Transient);
}
}
}
using Castle.MicroKernel.Context;
using Castle.MicroKernel.Lifestyle;
namespace VMS.Domain.WindsorMvcFacility
{
/// <summary>
/// This lifetime should be used for any transient components manually resolved within MVC
/// </summary>
/// <remarks>These are tracked and released via <see cref="TransientScopedInWebRequestLifestyleModule"/></remarks>
public class TransientScopedInWebRequestLifestyleManager : AbstractLifestyleManager
{
public override object Resolve(CreationContext context)
{
var instance = base.Resolve(context);
TransientScopedInWebRequestLifestyleModule.RegisterForEviction(instance);
return instance;
}
public override bool Release(object instance)
{
TransientScopedInWebRequestLifestyleModule.Evict(instance);
return base.Release(instance);
}
public override void Dispose()
{
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Castle.MicroKernel;
namespace VMS.Domain.WindsorMvcFacility
{
/// <summary>
/// This module tracks any manually resolved transient components, where MVC doe not provide any explicit release hooks
/// </summary>
public class TransientScopedInWebRequestLifestyleModule : IHttpModule
{
private const string Key = "trolls_and_goblins";
public static IKernel Kernel;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.EndRequest += ApplicationEndRequest;
}
public static void Init(IKernel kernel)
{
Kernel = kernel;
}
protected void ApplicationEndRequest(object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var candidates = (IEnumerable<object>)application.Context.Items[Key];
if (candidates == null) return;
foreach (var instance in candidates.Reverse())
Kernel.ReleaseComponent(instance);
application.Context.Items.Remove(Key);
}
public static void RegisterForEviction(object instance)
{
var context = HttpContext.Current;
var list = (List<object>)context.Items[Key];
if (list == null)
{
list = new List<object>();
context.Items[Key] = list;
}
list.Add(instance);
}
public static void Evict(object instance)
{
var context = HttpContext.Current;
var list = (List<object>)context.Items[Key];
if (list != null)
{
list.Remove(instance);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Castle.MicroKernel;
namespace VMS.Domain.WindsorMvcFacility
{
public class WindsorActionInvoker : ControllerActionInvoker
{
private readonly IKernel _kernel;
public WindsorActionInvoker(IKernel kernel)
{
_kernel = kernel;
}
private void ExecuteFiltersFromWindsor(AuthorizationContext filterContext)
{
if (_kernel.HasComponent(typeof(IAuthorizationFilter)) == false)
return;
var resolved = _kernel.ResolveRegisteredHandlers<IAuthorizationFilter>();
foreach (var filter in resolved)
{
try
{
filter.OnAuthorization(filterContext);
}
finally
{
_kernel.ReleaseComponent(filter);
}
}
}
private void ExecuteFiltersFromWindsor(ExceptionContext filterContext)
{
var filter = _kernel.Resolve<IExceptionFilter>();
try
{
filter.OnException(filterContext);
}
finally
{
_kernel.ReleaseComponent(filter);
}
}
protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
var result = _kernel.HasComponent(parameterDescriptor.ParameterType)
? _kernel.Resolve(parameterDescriptor.ParameterType)
: base.GetParameterValue(controllerContext, parameterDescriptor);
return result;
}
protected override ExceptionContext InvokeExceptionFilters(ControllerContext controllerContext,
IList<IExceptionFilter> filters,
Exception exception)
{
var filterContext = new ExceptionContext(controllerContext, exception);
ExecuteFiltersFromWindsor(filterContext);
foreach (var filter in filters)
{
filter.OnException(filterContext);
if (filterContext.Result != null)
{
return filterContext;
}
}
return filterContext;
}
protected override AuthorizationContext InvokeAuthorizationFilters(
ControllerContext controllerContext,
IList<IAuthorizationFilter> filters,
ActionDescriptor actionDescriptor)
{
var filterContext = new AuthorizationContext(controllerContext, actionDescriptor);
ExecuteFiltersFromWindsor(filterContext);
foreach (var filter in filters)
{
filter.OnAuthorization(filterContext);
if (filterContext.Result != null)
{
return filterContext;
}
}
return filterContext;
}
protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
if (_kernel.HasComponent(typeof(IActionFilter)))
{
var resolved = _kernel.ResolveRegisteredHandlers<IActionFilter>();
foreach (var filter in resolved)
{
filters.ActionFilters.Add(filter);
}
}
if (_kernel.HasComponent(typeof(IResultFilter)))
{
var resolved = _kernel.ResolveRegisteredHandlers<IResultFilter>();
foreach (var filter in resolved)
{
filters.ResultFilters.Add(filter);
}
}
return filters;
}
}
}
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.MicroKernel;
namespace VMS.Domain.WindsorMvcFacility
{
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IKernel _kernel;
public WindsorControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
public override void ReleaseController(IController controller)
{
_kernel.ReleaseComponent(controller);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
throw new HttpException(404,
string.Format(
"The controller for path '{0}' could not be found or it does not implement IController.",
requestContext.HttpContext.Request.Path));
var host = _kernel.Resolve<RequestContextHost>();
host.SetContext(requestContext);
var controller = (IController)_kernel.Resolve(controllerType);
if (controller is Controller && _kernel.HasComponent(typeof(ITempDataProvider)))
((Controller) controller).TempDataProvider = _kernel.Resolve<ITempDataProvider>();
return controller;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Castle.MicroKernel;
namespace VMS.Domain.WindsorMvcFacility
{
public class WindsorDependencyResolver :System.Web.Mvc.IDependencyResolver
{
private readonly IKernel _kernel;
public WindsorDependencyResolver(IKernel kernel)
{
_kernel = kernel;
}
public object GetService(Type serviceType)
{
#if DEBUG
System.Diagnostics.Debugger.Log(0, "GetService", string.Format("{0:T}-{1}\n", DateTime.Now, serviceType));
#endif
return _kernel.HasComponent(serviceType) ? _kernel.Resolve(serviceType) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
#if DEBUG
System.Diagnostics.Debugger.Log(0, "GetServices", string.Format("{0:T}-{1}\n", DateTime.Now, serviceType));
#endif
return _kernel.ResolveRegisteredHandlers(serviceType).OfType<object>();
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using Castle.MicroKernel;
using Castle.MicroKernel.Context;
using Castle.MicroKernel.Handlers;
namespace VMS.Domain.WindsorMvcFacility
{
public static class WindsorExtensions
{
/// <summary>
/// Returns all the valid component instances by the service type and have been explicitly registered
/// </summary>
/// <remarks> Based on the original ResolveAll()</remarks>
/// <see cref="https://github.com/castleproject/Castle.Windsor/blob/2.5.3/src/Castle.Windsor/MicroKernel/DefaultKernel_Resolve.cs#L261"/>
public static Array ResolveRegisteredHandlers(this IKernel container, Type service)
{
var resolved = new Dictionary<IHandler, object>();
foreach (var handler in container.GetHandlers(service))
{
var actualHandler = handler;
if (handler is ForwardingHandler)
{
actualHandler = ((ForwardingHandler)handler).Target;
}
if (resolved.ContainsKey(actualHandler))
{
continue;
}
var component = handler.TryResolve(CreationContext.Empty);
if (component != null)
{
resolved.Add(actualHandler, component);
}
}
var components = Array.CreateInstance(service, resolved.Count);
((ICollection)resolved.Values).CopyTo(components, 0);
return components;
}
/// <summary>
/// Returns component instances that implement TService and have been explicitly registered
/// </summary>
/// <typeparam name="TService"></typeparam>
/// <returns></returns>
public static TService[] ResolveRegisteredHandlers<TService>(this IKernel container)
{
return (TService[])ResolveRegisteredHandlers(container, typeof(TService));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace VMS.Domain.WindsorMvcFacility
{
/// <summary>
/// This is in essence a "static" class as when Mvc resolves the instance, it will live in a static collection
/// </summary>
public class WindsorModelBinderProvider : IModelBinderProvider, IDisposable
{
private readonly Func<IEnumerable<IModelBinder>> _modelBinderFactory;
/// <summary>
/// Ensure all components injected are factories, as these will in essence be static
/// </summary>
/// <param name="modelBinderFactory"></param>
public WindsorModelBinderProvider(Func<IEnumerable<IModelBinder>> modelBinderFactory)
{
_modelBinderFactory = modelBinderFactory;
}
public void Dispose()
{
}
public IModelBinder GetBinder(Type modelType)
{
return _modelBinderFactory()
.FirstOrDefault(binder => ModelBinderForTypesAttribute.ExtractTypesFor(binder)
.Any(modelType.IsAssignableFrom));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment