Ok, there are so many results in Google when you search for this topic; NerdDinner,CodeClimber,CodeProject etc.. but they all seems to be not working as expected! Either they give error during build or runtime! What is the right way to implement Unity 2.0 in ASP.Net MVC 2? I am simply not able to get this working!
Your help and thoughts are highly appreciated. Thanks!
Try writing a simple controller factory using Unity which is capable of resolving the controller instances:
public class UnityControllerFactory : DefaultControllerFactory
{
private readonly IUnityContainer _container;
public UnityControllerFactory(IUnityContainer container)
{
_container = container;
}
protected override IController GetControllerInstance(
RequestContext requestContext,
Type controllerType
)
{
if (controllerType == null)
{
throw new ArgumentNullException("controllerType");
}
if (!typeof(IController).IsAssignableFrom(controllerType))
{
throw new ArgumentException("Type requested is not a controller", "controllerType");
}
return _container.Resolve(controllerType) as IController;
}
}
and then hook it up in the Application_Start event in Global.asax:
protected void Application_Start()
{
...
var container = new UnityContainer();
// TODO: Configure the container here with your controllers
var factory = new UnityControllerFactory(container);
ControllerBuilder.Current.SetControllerFactory(factory);
}
This is my version of the UnityControllerFactory. It uses reflection to get the controllers from the calling assembly and register them in the container.
using System;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
namespace WebProveidorsMVC.DI.ControllerFactories
{
public class UnityControllerFactory : DefaultControllerFactory
{
private readonly IUnityContainer _container;
public UnityControllerFactory()
{
_container=new UnityContainer();
((UnityConfigurationSection) ConfigurationManager.GetSection("unity")).Configure(_container);
var controllerTypes = from t in Assembly.GetCallingAssembly().GetTypes()
where typeof(IController).IsAssignableFrom(t)
select t;
foreach (var t in controllerTypes)
_container.RegisterType(t, t.FullName);
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType != null)
return (IController)_container.Resolve(controllerType);
return null;
}
public override void ReleaseController(IController controller)
{
_container.Teardown(controller);
base.ReleaseController(controller);
}
}
}
Then in my ApplicationStart method I register it like this:
ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory());
Related
Ok, after searching Google, here and several ASP/MVC forums I am bound to have to ask what the hell I am doing wrong here.
I have a good start to my application, an ok understanding of DI, IoC and am using the Repository, Service and UnitOfWork patterns. When I attempt to load a controller that needs the DI from Unity, it's as if unity is not resolving any of the registered items, or that I have done it poorly. From all the examples I can see for this version (not the version that creates the Bootstrap.cs file that is then called from Global.asax) I am doing what others have done with no love from Unity.
My core question is: Have I setup/configured Unity to inject the items into the controller constructor as needed or not. If I have, any ideas why it's not working like examples I have seen?
I keep getting the error that the AssetController needs to have a parameterless public constructor. If I add one, then it uses it without the DI and if I don't add one, then it yells about not having it.
Thanks, code below.
UnityConfig.cs
namespace CARS.web.App_Start
{
/// <summary>
/// Specifies the Unity configuration for the main container.
/// </summary>
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
/// <summary>
/// Gets the configured Unity container.
/// </summary>
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
/// <summary>Registers the type mappings with the Unity container.</summary>
/// <param name="container">The unity container to configure.</param>
/// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to
/// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your types here
// container.RegisterType<IProductRepository, ProductRepository>();
container.RegisterType<IDataContext, CARSDEMOContext>(new PerRequestLifetimeManager())
.RegisterType<IAssetService, AssetService>()
.RegisterType<IUnitOfWork, UnitOfWork>()
.RegisterType<IRepository<Asset>, Repository<Asset>>();
//.RegisterType<AssetController>(new InjectionConstructor(typeof(IAssetService), typeof(IUnitOfWork)));
}
}
}
AssetController.cs (constructor portion where I am doing the injection params)
namespace CARS.web.Controllers
{
public class AssetController : Controller
{
private readonly IAssetService _assetService;
private readonly IUnitOfWork _unitOfWork;
public AssetController(IAssetService assetService, IUnitOfWork unitOfWork)
{
_assetService = assetService;
_unitOfWork = unitOfWork;
}
//other methods for CRUD etc stripped for brevity
}
}
IAssetService.cs (first param is the assetService )
namespace CARS.service
{
public interface IAssetService : IService<Asset>
{
Task<IEnumerable<Asset>> GetAsync();
Task<Asset> FindAsync(Guid id);
Asset Add(Asset asset);
Asset Update(Asset asset);
void Remove(Guid id);
}
}
AssetService.cs (concrete implementation for IAssetService interaction)
namespace CARS.service
{
public class AssetService : Service<Asset>, IAssetService
{
private readonly IRepositoryAsync<Asset> _repository;
public AssetService(IRepositoryAsync<Asset> repository) : base(repository)
{
_repository = repository;
}
public Task<IEnumerable<Asset>> GetAsync()
{
//return _repository.Query().SelectAsync();
return _repository.Query().SelectAsync();
}
public Task<Asset> FindAsync(Guid id)
{
return _repository.FindAsync(id);
}
public Asset Add(Asset asset)
{
_repository.Insert(asset);
return asset;
}
public Asset Update(Asset asset)
{
_repository.Update(asset);
return asset;
}
public void Remove(Guid id)
{
_repository.Delete(id);
}
}
}
IUnitOfWork.cs (this is from Long Le's Generic UofW and Repository - http://genericunitofworkandrepositories.codeplex.com/)
namespace Repository.Pattern.UnitOfWork
{
public interface IUnitOfWork : IDisposable
{
int SaveChanges();
Task<int> SaveChangesAsync();
void Dispose(bool disposing);
IRepository<TEntity> Repository<TEntity>() where TEntity : IObjectState;
void BeginTransaction();
bool Commit();
void Rollback();
}
}
UnitOfWork.cs (again from Long Le's framework)
namespace Repository.Pattern.Ef6
{
public class UnitOfWork : IUnitOfWork, IUnitOfWorkAsync
{
#region Private Fields
private readonly IDataContextAsync _dataContext;
private bool _disposed;
private ObjectContext _objectContext;
private Dictionary<string, object> _repositories;
private DbTransaction _transaction;
#endregion Private Fields
#region Constuctor/Dispose
public UnitOfWork(IDataContextAsync dataContext)
{
_dataContext = dataContext;
}
public void Dispose()
{
if (_objectContext != null && _objectContext.Connection.State == ConnectionState.Open)
_objectContext.Connection.Close();
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
_dataContext.Dispose();
_disposed = true;
}
#endregion Constuctor/Dispose
public int SaveChanges()
{
return _dataContext.SaveChanges();
}
public IRepository<TEntity> Repository<TEntity>() where TEntity : IObjectState
{
return RepositoryAsync<TEntity>();
}
public Task<int> SaveChangesAsync()
{
return _dataContext.SaveChangesAsync();
}
public Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
return _dataContext.SaveChangesAsync(cancellationToken);
}
public IRepositoryAsync<TEntity> RepositoryAsync<TEntity>() where TEntity : IObjectState
{
if (_repositories == null)
_repositories = new Dictionary<string, object>();
var type = typeof (TEntity).Name;
if (_repositories.ContainsKey(type))
return (IRepositoryAsync<TEntity>) _repositories[type];
var repositoryType = typeof (Repository<>);
_repositories.Add(type, Activator.CreateInstance(repositoryType.MakeGenericType(typeof (TEntity)), _dataContext, this));
return (IRepositoryAsync<TEntity>) _repositories[type];
}
#region Unit of Work Transactions
public void BeginTransaction()
{
_objectContext = ((IObjectContextAdapter) _dataContext).ObjectContext;
if (_objectContext.Connection.State != ConnectionState.Open)
{
_objectContext.Connection.Open();
_transaction = _objectContext.Connection.BeginTransaction();
}
}
public bool Commit()
{
_transaction.Commit();
return true;
}
public void Rollback()
{
_transaction.Rollback();
((DataContext)_dataContext).SyncObjectsStatePostCommit();
}
#endregion
// Uncomment, if rather have IRepositoryAsync<TEntity> IoC vs. Reflection Activation
//public IRepositoryAsync<TEntity> RepositoryAsync<TEntity>() where TEntity : EntityBase
//{
// return ServiceLocator.Current.GetInstance<IRepositoryAsync<TEntity>>();
//}
}
}
Updated to include the SetResolver info from UnityMvcActivator.cs
using System.Linq;
using System.Web.Mvc;
using Microsoft.Practices.Unity.Mvc;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(CARS.web.App_Start.UnityWebActivator), "Start")]
namespace CARS.web.App_Start
{
/// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
public static class UnityWebActivator
{
/// <summary>Integrates Unity when the application starts.</summary>
public static void Start()
{
var container = UnityConfig.GetConfiguredContainer();
FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
// TODO: Uncomment if you want to use PerRequestLifetimeManager
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}
}
}
I have read/tried the following info/data and nothing has fixed it:
The type IUserStore`1 does not have an accessible constructor
How to add MVC 5 authentication to Unity IoC?
Types not resolving with Unity [MVC 5]
I have ready where one must write a ControllerFactory for Unity to be able to do this, but that seems quite a bit of work when all the examples I have found simply have the config registered, and the injection apparently happening on the controllers and other classes as need.
And finally the error:
The following server error was encountered:
An error occurred when trying to create a controller of type 'CARS.web.Controllers.AssetController'. Make sure that the controller has a parameterless public constructor.Details are:
at System.Web.Mvc.DefaultControllerFactory.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) at System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) at System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionSte p.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Thanks
You need to set the DependencyResolver. I cant see the code in the example you provided where this is done.
Once setting up your UnityContainer and registering your types, you need to set the System.Web.MVC.DependencyResolver.
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
You may have missed (like I did) to register the async types in UnityConfig.cs
Check if you have this:
container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager())
.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager())
.RegisterType<IDataContextAsync, SomeDBContext>(new PerRequestLifetimeManager())
.RegisterType<IDataContext, SomeDBContext>(new PerRequestLifetimeManager())
.RegisterType<ISomeService, SomeService>(new PerRequestLifetimeManager())
.RegisterType<IRepositoryAsync<Some>, Repository<Some>>(new PerRequestLifetimeManager());
1:
Uncomment in file UnityMvcActivator this line:
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
2:
Basic registration with Unity + UnitOfwork + Repository
this must exists in file: UnityConfig:
container.RegisterType<IDataContextAsync, KlussendirectContext>(new PerRequestLifetimeManager());
container.RegisterType<IDataContext, KlussendirectContext>(new PerRequestLifetimeManager());
container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager());
3:
When you use Dependency Injection in a controller(api) then a parameter less constructor should exists:
e.g.
public MyNameController() : base(){}
If followed those steps, it should work (Edit, Insert, Delete, etc..)
This is my controller
public class SuggestionController : ApiController
{
public ISuggestionRepository Repository { get; private set; }
public SuggestionController(ISuggestionRepository repository)
{
this.Repository = repository;
}
// to post suggestion
[HttpPost]
[ActionName("PostSuggestion")]
public HttpResponseMessage PostSuggestion(Suggestion suggestion)
{
var answerCorrect = this.Repository.CreateSuggestion(suggestion);
if (answerCorrect == true)
return Request.CreateResponse(HttpStatusCode.OK);
else
return Request.CreateResponse(HttpStatusCode.Conflict);
}
}
and this is my RegisterServices method in NinjectWebCommon.cs
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ICompetitionRepository>().To(typeof(CompetitionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Competition>());
kernel.Bind<ISubmissionRepository>().To(typeof(SubmissionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Submission>());
kernel.Bind<IUserRepository>().To(typeof(UserRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<User>());
kernel.Bind<ISuggestionRepository>().To(typeof(SuggestionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Suggestion>());
}
But am getting an exception that my suggestion controller does not have a default constructor and its showing a 500 internal server when am hitting the controller from a client app
I know that we get the exception of controller not having default constructor if the ninject dependency is not working properly but the below is another controller i have implemeneted similar to suggestion controller and its working absolutely fine.
public IUserRepository Repository { get; private set; }
public SSOController(IUserRepository repository)
{
this.Repository = repository;
}
[HttpPost]
[ActionName("PostUser")]
public HttpResponseMessage PostUser([FromBody]string id)
{
var accessToken = id;
var client = new FacebookClient(accessToken);
dynamic result = client.Get("me", new { fields = "name,email" });
string name = result.name;
string email = result.email;
var existingUser = this.Repository.FindByUserIdentity(name);
if (existingUser == null)
{
var newUser = new User
{
Username = name,
Email = email,
};
var success = this.Repository.CreateAccount(newUser);
if (!success)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
//return created status code as we created the user
return Request.CreateResponse<User>(HttpStatusCode.Created, newUser);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
}
I have no idea where am going wrong. Please let me know if u have any suggestions.
EDIT:
my Global.asax
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
AuthConfig.RegisterAuth();
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy =
IncludeErrorDetailPolicy.Always;
}
Dependency resolver am using
// Provides a Ninject implementation of IDependencyScope
// which resolves services using the Ninject container.
public class NinjectDependencyScope : IDependencyScope
{
IResolutionRoot resolver;
public NinjectDependencyScope(IResolutionRoot resolver)
{
this.resolver = resolver;
}
public object GetService(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.GetAll(serviceType);
}
public void Dispose()
{
IDisposable disposable = resolver as IDisposable;
if (disposable != null)
disposable.Dispose();
resolver = null;
}
}
// This class is the resolver, but it is also the global scope
// so we derive from NinjectScope.
public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
IKernel kernel;
public NinjectDependencyResolver(IKernel kernel)
: base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}
and calling it in CreateKernel() method in NinjectWebCommon
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
// Install our Ninject-based IDependencyResolver into the Web API config
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
Suggestion Repository
public class SuggestionRepository : Repository<Suggestion>, ISuggestionRepository
{
public SuggestionRepository(IServiceContext<Suggestion> servicecontext)
: base(servicecontext)
{ }
public bool CreateSuggestion(Suggestion suggestion)
{
this.ServiceContext.Create(suggestion);
this.ServiceContext.Save();
return true;
}
}
ISuggestionRepository
public interface ISuggestionRepository
{
bool CreateSuggestion(Suggestion suggestion);
}
Repository
public abstract class Repository<T>
{
public IServiceContext<T> ServiceContext { get; private set; }
public Repository(IServiceContext<T> serviceContext)
{
this.ServiceContext = serviceContext;
}
}
IserviceContext
public interface IServiceContext<T>
{
IQueryable<T> QueryableEntities { get; }
void Create(T entity);
void Update(T entity);
void Delete(T entity);
void Save();
}
Since you're using WebApi, you will need to use the WebApi extension for Ninject. Unfortunately, the current Ninject.WebApi nuget package is out of date, and doesn't work with the released version of WebApi.
Temporarily, until Remo gets around to updating Ninject.WebApi to the release version, you can use Ninject.WebApi-RC http://nuget.org/packages/Ninject.Web.WebApi-RC
http://www.eyecatch.no/blog/2012/06/using-ninject-with-webapi-rc/
EDIT:
To recap the information discussed in comments, Here are the recommendations:
1) Use Ninject.MVC3 and Ninject.Web.WebApi (but use Ninject.Web.WebApi-RC until the official is updated) as discussed above. Do not use a custom DependencyResolver, and let Ninject.Web.Mvc and .WebApi do their job.
2) Change your bindings to this:
kernel.Bind<ICompetitionRepository>().To<CompetitionRepository>();
... similar bindings
3) Add a generic binding for your ServiceContext
kernel.Bind(typeof(IServiceContext<>)).To(typeof(InMemoryDataContext<>));
I think the problem is you're using the ApiController.
Controllers and apiControllers are using a different dependancy injection container.
Both of them however expose the same methods.
If the working controller is inheriting the Controller class then that's your cause.
For a work around take a look at
this topic
I have faced the same issue.
This is how I rectified:
I created a WebContainerManager which is just a static wrapper around container.
Static container wrappers useful when you don't control instantiation and can't rely on injection - e.g. action filter attributes
public static class WebContainerManager
{
public static IKernel GetContainer()
{
var resolver = GlobalConfiguration.Configuration.DependencyResolver as NinjectDependencyResolver;
if (resolver != null)
{
return resolver.Container;
}
throw new InvalidOperationException("NinjectDependencyResolver not being used as the MVC dependency resolver");
}
public static T Get<T>()
{
return GetContainer().Get<T>();
}
}
Inside your controller, call your empty constructor like this with no parameters:
public SuggestionController() : this(WebContainerManager.Get<ISuggestionRepository>())
{
}
This should work.
This technique i got from the book on MVC4 by Jamie Kurtz #jakurtz.
You probably need to do some dependency injection so you can inject the ISuggestionRepository parameter on your SuggestionController constructor. To do that you need to override methods in the DefaultControllerFactory class to customize the creation of controllers. Since you are using NInject, you can have something like:
public class NInjectControllerFactory : DefaultControllerFactory
{
private IKernel kernel = new StandardKernel(new CustomModule());
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return controllerType == null ? null : (IController)kernel.Get(controllerType);
}
public class CustomModule : NinjectModule
{
public override void Load()
{
this.Bind<ICompetitionRepository>().To(typeof(CompetitionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Competition>());
this.Bind<ISubmissionRepository>().To(typeof(SubmissionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Submission>());
this.Bind<IUserRepository>().To(typeof(UserRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<User>());
this.Bind<ISuggestionRepository>().To(typeof(SuggestionRepository))
.WithConstructorArgument("serviceContext", new InMemoryDataContext<Suggestion>());
}
}
}
Then in your Global.asax.cs, you can add a line to swap out the controller factory
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NInjectControllerFactory());
}
In Global.asax file, I have the following code.
config.DependencyResolver = new IoCContainer(unity);
In IOCContainer, it is showing as "Namespace not found". What Namespace we should include here. I already included Microsoft.Practices.Unity.
You are missing the following classes. This took me a while to find aswell.
Found the below code reference Here
class ScopeContainer : IDependencyScope
{
protected IUnityContainer container;
public ScopeContainer(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
public object GetService(Type serviceType)
{
if (container.IsRegistered(serviceType))
{
return container.Resolve(serviceType);
}
else
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (container.IsRegistered(serviceType))
{
return container.ResolveAll(serviceType);
}
else
{
return new List<object>();
}
}
public void Dispose()
{
container.Dispose();
}
}
class IoCContainer : ScopeContainer, IDependencyResolver
{
public IoCContainer(IUnityContainer container)
: base(container)
{
}
public IDependencyScope BeginScope()
{
var child = container.CreateChildContainer();
return new ScopeContainer(child);
}
}
'Namespace not found'?
Look at your "using" directives at the very top of the file and check if any of them is undelined with red color. Maybe you have some typo? Or maybe you have forgotten to "add reference" to some library?
You can install the Nuget package Unity.Mvc3 and then use the following code:
var container = new UnityContainer();
container.RegisterType<IFoo, Foo>();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Which version of EntLib are you using? Until 4.1, it had UnityContainer and not IoCContainer.
Edit: What is that unity object you are passing? You may have to post some code. Meanwhile, here's a tutorial that can help you out.
In mvc asp.net, I can override a factory to create my controllers, and so put a reference to my IOC just here. Doing So every interface needed by the constructor of my controllers will be feeded by my IOC .
Is there some common way to do it using Silverlight?
At the moment I only found to use the kernel of Ninject everywhere :
public partial class MyUserControlSL
{
public MyUserControlSL()
{
DataContext = new MyViewModel(Kernel.Get<IMyRepository>());
InitializeComponent();
}
}
eg using StructureMap and MVC:
public class ControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
IController result = null;
try
{
if (controllerType != null)
{
result = ObjectFactory.GetInstance(controllerType)
as Controller;
}
else
{
return base.GetControllerInstance(
requestContext, controllerType);
}
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(
ObjectFactory.WhatDoIHave());
throw;
}
return result;
}
}
public AController(IServiceA serviceA)
{
if (serviceA == null)
{
throw new Exception("IServiceA cannot be null");
}
_ServiceA = serviceA;
}
public ServiceA(IRepositoryA repository)
{
if (repository == null)
{
throw new Exception(
"the repository IRepositoryA cannot be null");
}
_Repository = repository;
}
Thanks for your help, please ask if it is not clear..
In Silverlight you should use a bootstrapper at composition root to wire up your entire object graph. It could be the Application class app.xml.cs and look similar to
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}
In general this should be sufficant but if you need a separate Factory class for your views, take a look at
Keeping the DI-container usage in the composition root in Silverlight and MVVM.
For Silverlight you may use PRISM framework with custom IoC container.
Autofac has built in support for silverlight: http://weblogs.asp.net/dwahlin/archive/2010/01/03/using-autofac-as-an-ioc-container-in-silverlight-applications.aspx
I am trying to use the approach from this article, but I am missing something - I am currently getting an error within WindsorControllerFactory.GetControllerInstance when it tries to resolve IActionInvoker since WindsorActionInvoker has a dependency on IWindsorContainer.
Given that WindsorControllerFactory already has a reference to IWindsorContainer, could I pass that reference in? If so - how? The only examples I have found are about passing value types as constructor parameters, not reference types.
I'm feeling I'm missing something obvious...
Current setup as follows:
Within Global.asax Application_Start I call the following method:
protected virtual IWindsorContainer InitializeServiceLocator()
{
IWindsorContainer container = new WindsorContainer();
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));
container.RegisterControllers(typeof(HomeController).Assembly);
ComponentRegistrar.AddComponentsTo(container);
return container;
}
ComponentRegistrar:
public static void AddComponentsTo(IWindsorContainer container)
{
//add other components.....
container.AddComponentLifeStyle<IActionInvoker, WindsorActionInvoker>(LifestyleType.PerWebRequest);
}
WindsorActionInvoker:
public class WindsorActionInvoker : ControllerActionInvoker, IActionInvoker
{
readonly IWindsorContainer container;
public WindsorActionInvoker(IWindsorContainer container)
{
this.container = container;
}
protected override ActionExecutedContext InvokeActionMethodWithFilters(
ControllerContext controllerContext,
IList<IActionFilter> filters,
ActionDescriptor actionDescriptor,
IDictionary<string, object> parameters)
{
foreach (IActionFilter actionFilter in filters)
{
container.Kernel.InjectProperties(actionFilter);
}
return base.InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
}
}
WindsorControllerFactory:
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IWindsorContainer container;
public WindsorControllerFactory(IWindsorContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
public override void ReleaseController(IController controller)
{
var disposable = controller as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
this.container.Release(controller);
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
return base.GetControllerInstance(requestContext, controllerType);
}
var controller = this.container.Resolve(controllerType) as Controller;
if (controller != null)
{
controller.ActionInvoker = this.container.Resolve<IActionInvoker>(this.container);
}
return controller;
}
}
Update
I missed a subtlety: I was trying to use this behaviour for the following:
public class CustomAuthorize : AuthorizeAttribute {...}
which doesn't implement IActionFilter.
Added the following to WindsorActionInvoker:
protected override AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext, IList<IAuthorizationFilter> filters, ActionDescriptor actionDescriptor)
{
foreach (IAuthorizationFilter authorizeFilter in filters)
{
this.kernel.InjectProperties(authorizeFilter);
}
return base.InvokeAuthorizationFilters(controllerContext, filters, actionDescriptor);
}
This now works as required. Thanks to Cristiano since it was analysis of his kindly provided code which put me on the right path.
Global.asax
private static void bootstrapContainer()
{
container = new WindsorContainer()
.Install(FromAssembly.This());
var controllerFactory = new WindsorControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
}
Installer / filling container
public class ControllersInstaller : IWindsorInstaller
{
#region IWindsorInstaller Members
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Component.For<WpRegistration.Web.Filters.AgencyAuthorize>().LifeStyle.Transient);
container.Register(Component.For<IActionInvoker>().ImplementedBy<WindsorExtensions.Mvc.WindsorActionInvoker>().LifeStyle.Transient);
container.Register(AllTypes.FromThisAssembly()
.BasedOn<IController>()
.If(Component.IsInSameNamespaceAs<HomeController>())
.If(t => t.Name.EndsWith("Controller"))
.Configure((c => c.LifeStyle.Transient)));
}
#endregion
}
WindsorControllerFactory
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.MicroKernel;
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IKernel kernel;
public WindsorControllerFactory(IKernel kernel)
{
this.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.", requestContext.HttpContext.Request.Path));
}
IController iController = (IController)kernel.Resolve(controllerType);
// new code
if (iController is Controller)
{
((Controller)iController).ActionInvoker = kernel.Resolve<IActionInvoker>();
}
return iController;
}
}
WindsorActionInvoker
namespace WindsorExtensions.Mvc
{
public class WindsorActionInvoker : ControllerActionInvoker
{
readonly IKernel kernel;
public WindsorActionInvoker(IKernel kernel)
{
this.kernel = kernel;
}
protected override ActionExecutedContext InvokeActionMethodWithFilters(
ControllerContext controllerContext
, IList<IActionFilter> filters
, ActionDescriptor actionDescriptor
, IDictionary<string, object> parameters)
{
foreach (IActionFilter actionFilter in filters)
{
kernel.InjectProperties(actionFilter);
}
return base.InvokeActionMethodWithFilters(controllerContext, filters, actionDescriptor, parameters);
}
}
public static class WindsorExtension
{
public static void InjectProperties(this IKernel kernel, object target)
{
var type = target.GetType();
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (property.CanWrite && kernel.HasComponent(property.PropertyType))
{
var value = kernel.Resolve(property.PropertyType);
try { property.SetValue(target, value, null); }
catch (Exception ex)
{
var message = string.Format("Error setting property {0} on type {1}, See inner exception for more information.", property.Name, type.FullName);
throw new ComponentActivatorException(message, ex);
}
}
}
}
}
}
AgencyAuthorizeAttribute
namespace WpRegistration.Web.Filters
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class AgencyAuthorize : ActionFilterAttribute
{
CurrentUserService _currentUserSvc;
public AgencyAuthorize() { }
public CurrentUserService Service
{
get { return _currentUserSvc; }
set
{
this._currentUserSvc=value;
}
}