Dependency Injection when I am constructing the object - asp.net-mvc

I am creating a custom ActionResult for my controllers because I've noticed a lot of repeated code that could be made reusable. It looks something like this:
public ExtendedViewResult<T> : ActionResult
{
protected T Model { get; set; }
protected IModelExtender<T> Extender { get; set; }
public ExtendedActionResult(T model, IModelExtender<T> extender)
{
this.Model = model;
this.Extender = extender;
}
}
public class BaseController : Controller
{
public ExtendedViewResult<T> ExtendedView<T>(T model)
{
// I need to create the result here, but how?
var result = new ExtendedViewResult<T>(model, ???????);
return result;
}
}
The problem I am having is that I'm not sure how to construct my ExtendedViewResult object. Since the interface is generic I want to use Dependency Injection to get the proper object, but I'm not sure how to do that since I'm constructing the object myself.
I am using Ninject and Ninject.MVC3 and the default Nuget package creates a Bootstrapper class for me, and when I access the Bootstrapper.Kernel property I get the following warning:
Ninject.Web.Mvc.Bootstrapper.Kernel is obsolete. Do not use Ninject as Service Locator.
If I'm not supposed to access the kernel directly, then how can I change my code so that I can get the appropriate concrete class?
EDIT
Here is the ninject bootstrapper code. The only method I added is the GetInstance()
public static class NinjectMVC3
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestModule));
DynamicModuleUtility.RegisterModule(typeof(HttpApplicationInitializationModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
// I ADDED THIS CODE, EVERYTHING ELSE IS AUTO-GENERATED
// BY THE NUGET PACKAGE
public static T GetInstance<T>()
{
return bootstrapper.Kernel.Get<T>();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
RegisterServices(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
}
}

Sorry this will be dry on actual code - I've not used ninject much, but other DI containers have this solution, and I'm sure ninject will have this construct as well.
The issue is that you are constructing the object itself. When you're really using a DI container and following IoC, anytime you see the keyword new should be a red flag - and using the container as a service locator is a yellow one.
So how do we get rid of the 'new', since you need a new object? The answer is to have your BaseController take a dependency on a factory that can create an ExtendedViewResult. In Autofac (my container of choice), that would be as simple as having a Func<ExtendedViewResult> injected. I would be surprised if Ninject doesn't have the same. In fact, looks like it does - this ninject wiki page points to this blog post on Ninject.Extensions.Factory.
So that means your code for the Controller could look like this:
public class ConcreteController : BaseController
{
private Func<Foo,ExtendedViewResult<Foo>> _factory;
public BaseController(Func<Foo,ExtendedViewResult<Foo>> factory)
{
_factory = factory;
}
public ExtendedViewResult<Foo> Method(Foo model)
{
var result = _factory(model);
return result;
}
}
If you REALLY want to have a generic method do the creation in your base class, then you will probably need to go the explicit factory interface route from the blog post linked above. With this style code though, you would not need it in most cases, and your controllers explicitly declare the dependencies they need.

You should be using an abstract factory anytime your objects want to create other objects. The abstract factory itself can injected. I've asked a similar question here: Abstract factories when using dependency injection frameworks
It really shouldn't make much of a difference that your factory is generic though.

Related

Resolve constructor argument with parameter from base class

I have a custom ASP.NET MVC controller that retrieves operations from the user service. I want to pass the operations property to the scenario service using dependency injection.
public abstract class BaseController : Controller {
protected IUserService userService;
public OperationWrapper operations { get; private set; }
public BaseController(IUserService userService) {
this.userService = userService;
this.operations = userService.GetOperations(HttpContext.Current.User.Identity.Name);
}
}
public abstract class ScenarioController : BaseController {
protected IScenarioService scenarioService;
public ScenarioController(IScenarioService scenarioService, IUserService userService)
: base(userService) {
this.scenarioService = scenarioService;
}
}
public class ScenarioService : IScenarioService {
private OperationWrapper operations;
public ScenarioService(OperationWrapper operations) {
this.repo = repo;
this.operations = operations;
}
}
Here is my Windsor installer.
public class Installer : IWindsorInstaller {
public void Install(IWindsorContainer container, IConfigurationStore store) {
container.Register(Classes.FromThisAssembly()
.BasedOn<IController>());
container.Register(Classes.FromThisAssembly()
.Where(x => x.Name.EndsWith("Service"))
.WithService.DefaultInterfaces()
.LifestyleTransient());
}
}
I pretty sure I've done something similar with Ninject a couple of years back. What do I need to add to the installer in order to make this work? Is it even possible?
There are a few of options here:
1. Use LifeStylePerWebRequest() and UsingFactoryMethod()
First, you could register an OperationWrapper as LifestylePerWebRequest() and inject it into both the BaseController and ScenarioService. Windsor will let you register the dependency with a factory method for creating it, which can in turn call other services which have been registered.
container.Register(Component.For<OperationWrapper>()
.LifestylePerWebRequest()
.UsingFactoryMethod(kernel =>
{
var userService = kernel.Resolve<IUserService>();
try
{
return userService.GetOperations(
HttpContext.Current.User.Identity.Name);
}
finally
{
kernel.ReleaseComponent(userService);
}
}));
So, every time Windsor is asked for an OperationWrapper, it will run that call against an instance if IUserService, giving it the Name of the current User. By binding the lifestyle to LifestylePerWebRequest(), you can verify that each request will get its own instance of the OperationWrapper and it won't bleed across requests.
(The only edge case you'd run into is one where a user becomes authenticated mid-request and the OperationWrapper needs to be adjusted as a result. If that's a normal-path use case, this may need some re-thinking.)
Then, modify your base controller to take that registered object in as a dependency:
public abstract class BaseController : Controller {
protected IUserService userService;
protected OperationWrapper operations;
public BaseController(IUserService userService, OperationWrapper operations) {
this.userService = userService;
this.operations = operations;
}
}
2. Use Method Injection
It looks like OperationWrapper is some sort of context object, and those can sometimes be injected into the method instead of into the constructor.
For instance, if your method was:
int GetTransactionId() { /* use OperationWrapper property */ }
You could just modify the signature to look like:
int GetTransactionId(OperationWrapper operations) { /* use arg */ }
In this situation, it makes sense to use it if a small-ish subset of your service's methods use that dependency. If the majority (or totality) of methods need it, then you should probably go a different route.
3. Don't use DI for OperationWrapper at all
In situations where you have a highly-stateful contextual object (which it seems like your OperationWrapper is), it frequently just makes sense to have a property whose value gets passed around. Since the object is based on some current thread state and is accessible from everywhere in any subclassed Controller, it may be right to just keep the pattern you have.
If you can't answer the question "What am I unable to do with OperationWrapper now that DI is going to solve for me?" with anything but "use the pattern/container," this may be the option for this particular situation.
You should set dependency resolver in Application_Start method of global.asax
System.Web.MVC.DependencyResolver.SetResolver(your windsor resolver)
Create a class that inherits from DefaultControllerFactory. Something like this will do:
public class WindsorControllerFactory : DefaultControllerFactory
{
public WindsorControllerFactory(IKernel kernel)
{
_kernel = kernel;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(
404,
String.Format(
CultureInfo.CurrentCulture,
"The controller for path '{0}' was not found or does not implement IController.",
requestContext.HttpContext.Request.Path
)
);
}
return (IController)_kernel.Resolve(controllerType);
}
public override void ReleaseController(IController controller)
{
Kernel.ReleaseComponent(controller);
}
private readonly IKernel _kernel;
private IKernel Kernel
{
get { return _kernel; }
}
}
In the Application_Start method of your MvcApplication class add the following:
var container = new WindsorContainer();
container.Install(FromAssembly.This());
ControllerBuilder.Current.SetControllerFactory(
new WindsorControllerFactory(container.Kernel)
);
This should work with your existing installer and get you to the point where Windsor will start resolving your dependencies for you. You might have to fill-in a few gaps, but you'll get the point.
I've borrowed heavily from: https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-intro.md
Be wary of using IDependencyResolver as it doesn't make provision for releasing what's resolved.

Can I define custom attributes for proxy type in Castle Windsor

I have a class that I proxy it with Castle Dynamic Proxy. I want to add some custom Attributes to proxy methods (which is not defined in proxied class). Is this possible.
I want this because I want to generate ASP.NET Web API layer for my application's Service Layer. I proxied services (with inheriting from ApiController and additional IMyService interfaces), it works great but I want to add WebAPI specific attributes to this newly created Dynamic class, thus Web API framework can read them.
EDIT:
I want to explain detailed if someone want to know what I want actually.
public interface IMyService
{
IEnumerable<MyEntity> GetAll();
}
public class MyServiceImpl : IMyService
{
public IEnumerable<MyEntity> GetAll()
{
return new List<MyEntity>(); //TODO: Get from database!
}
}
public class MyServiceApiController : ApiController,IMyService
{
private readonly IMyService _myService;
public MyServiceApiController(IMyService myService)
{
_myService = myService;
}
public IEnumerable<MyEntity> GetAll()
{
return _myService.GetAll();
}
}
Think that I have a IMyService which is implemented by MyServiceImpl. And I want to make a Api controller to be able to use this service from web.
But as you see, api controller is just a proxy for real service. So, why I should write it? I can dynamically create it using castle windsor.
This is my idea and almost done it in my new project (https://github.com/hikalkan/aspnetboilerplate). But what if I need to add some attribute (such as Authorize) to GetAll method of the api controller. I cant directly add since there is no such a class, it's castle dynamic proxy.
So, beside this problem. I want to know if it's possible to add a attribute to a method of a synamic proxy class.
See again that project
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/55
I also want to know how, so that I can define RoutePrefix on IService and Route on Action.
Fortunately, I finally know how to define custom attributes for proxy.
public class CustomProxyFactory : DefaultProxyFactory
{
#region Overrides of DefaultProxyFactory
protected override void CustomizeOptions(ProxyGenerationOptions options, IKernel kernel, ComponentModel model, object[] arguments)
{
var attributeBuilder = new CustomAttributeBuilder(typeof(DescriptionAttribute).GetConstructor(new[] { typeof(string) }), new object[] { "CustomizeOptions" });
options.AdditionalAttributes.Add(attributeBuilder);
}
#endregion
}
/// <summary>
/// 用户信息服务
/// </summary>
[Description("IUserInfoService")]
public interface IUserInfoService
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Description("IUserInfoService.GetUserInfo")]
UserInfo GetUserInfo([Description("IUserInfoService.GetUserInfo name")] string name);
}
/// <summary>
/// 用户信息服务
/// </summary>
[Description("UserInfoService")]
public class UserInfoService : IUserInfoService
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Description("UserInfoService.GetUserInfo")]
public virtual UserInfo GetUserInfo([Description("UserInfoService.GetUserInfo name")] string name)
{
return new UserInfo { Name = name };
}
}
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
[TestFixture]
public class AttributeTests
{
/// <summary>
/// Reference to the Castle Windsor Container.
/// </summary>
public IWindsorContainer IocContainer { get; private set; }
[SetUp]
public void Initialize()
{
IocContainer = new WindsorContainer();
IocContainer.Kernel.ProxyFactory = new CustomProxyFactory();
IocContainer.Register(
Component.For<UserInfoService>()
.Proxy
.AdditionalInterfaces(typeof(IUserInfoService))
.LifestyleTransient()
);
}
/// <summary>
///
/// </summary>
[Test]
public void GetAttributeTest()
{
var userInfoService = IocContainer.Resolve<UserInfoService>();
Assert.IsNotNull(userInfoService);
var type = userInfoService.GetType();
Assert.IsTrue(type != typeof(UserInfoService));
var attribute = type.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
var method = type.GetMethod("GetUserInfo");
attribute = method.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
var parameter = method.GetParameters().First();
attribute = parameter.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
}
}
#hikalkan I faced the same problem and I was looking for a solution as well. The best I could encounter was
How to add an attribute to a property at runtime
Proxy the controller (in my case a dynamic controller) with a new wrapper that set those attributes in the class itself and its methods..

Ninject + WebApi + Custom Membership Provider + Property Injection

I have a repository that i use to access user info IAccountCore and I have a custom membership provider that I've implemented as well. I have property injection setup in the membership provider but it seems that they're never injected. I've followed all the examples on the net to no avail.
I have the ninject bootstrapper + web activator firing the kernel.Inject(Membership.Provider) in the post startup method but the properties are always null. Here are some code snippets:
Bootstrapper in WebApi
[assembly: WebActivator.PostApplicationStartMethod(typeof(API.App_Start.NinjectWebCommon), "RegisterMembership")]
Method being called from above:
public static void RegisterMembership()
{
//bootstrapper.Kernel.Inject(Membership.Provider);
bootstrapper.Kernel.Load(new MembershipNinjectModule());
}
Ninject Module (in a different assembly/project):
public class MembershipNinjectModule : NinjectModule
{
/// <summary>
/// Loads the module into the kernel.
/// </summary>
public override void Load()
{
var kernel = Kernel;
kernel.Inject(Membership.Provider);
}
}
Property in custom membership provider:
[Inject]
public IAccountCore AccountCore {get;set;}
Bindings in another ninject module:
public class NinjectConfigurator : NinjectModule
{
public override void Load()
{
var kernel = Kernel
kernel.Bind<IAccountCore>().To<AccountCore>().InTransientScope();
}
}
Code that loads above module in the Ninject boot strapper:
private static void RegisterServices(IKernel kernel)
{
kernel.Load(new NinjectConfigurator());
}
Try to add on the constructor of your custom membership provider this line :
public MyCustomMembershipProvider()
{
NinjectWebCommon.Kernel.Inject(this);
}
On my project, I used an custom membership provider with Ninject, and I just have this line more than you.
Hope it helps

How can I do this? I need to call a Service Class but I need to pass in parameters

I need to call something in my application start to start my quartz.net scheduler.
The problem is that I need to pass in a repository into my service layer that normally is done with ninject and dependency injection.
//global.aspx
public class MvcApplication : System.Web.HttpApplication
{
private readonly IScheduledRemindersService scheduledRemindersService;
public MvcApplication(IScheduledRemindersService
scheduledRemindersService)
{
this.scheduledRemindersService = scheduledRemindersService;
}
protected void Application_Start()
{
//other default stuff here like mvc routers.
scheduledRemindersService.RemindersSchedule();
}
}
private readonly IReminderRepo reminderRepo;
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
}
private readonly IReminderRepo reminderRepo;
public ScheduledRemindersService(IReminderRepo reminderRepo)
{
this.reminderRepo = reminderRepo;
}
I have NHibernate set to so when It seems IReminderRepo it shoudl bind it and in IReminderRepo I have
private readonly ISession session;
public ReminderRepo(ISession session)
{
this.session = session;
}
This will also get automatically binded through nhibernate.
This won't work though as the global.aspx only allows no argument constructors.
So how can inject the right classes to these interfaces? Especially the nhibernate session that is the most important thing I need.
Edit
public class NhibernateSessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var sessionFactory = new NhibernateSessionFactory();
return sessionFactory.GetSessionFactory();
}
}
public class NhibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>().ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
}
}
// in the global.aspx
protected IKernel CreateKernel()
{
var modules = new INinjectModule[]
{
new NhibernateModule(),
new ServiceModule(),
new RepoModule(),
};
return new StandardKernel(modules);
}
// in RepoModule()
Bind<IReminderRepo>().To<ReminderRepo>();
// in serviceModule
Bind<IScheduledRemindersService>().To<ScheduledRemindersService>()
We use a method like this in our Global.asax.cs file for exactly this purpose (it gets called from Application_Start:
private static void SetupScheduling(IKernel kernel)
{
var scheduler = SchedulerUtil.Scheduler;
scheduler.JobFactory = kernel.Get<IJobFactory>();
scheduler.Start();
}
And we have IJobFactory bound to the following class:
public class QuartzJobFactory : IJobFactory
{
private readonly IObjectFactory<IJob> _jobFactory;
private readonly LogService _logService;
public QuartzJobFactory(IObjectFactory<IJob> jobFactory,
LogService logService)
{
_jobFactory = jobFactory;
_logService = logService;
}
/// <summary>
/// Called by the scheduler at the time of the trigger firing, in order to
/// produce a <see cref="T:Quartz.IJob"/> instance on which to call Execute.
/// </summary>
/// <remarks>
/// <p>It should be extremely rare for this method to throw an exception -
/// basically only the the case where there is no way at all to instantiate
/// and prepare the Job for execution. When the exception is thrown, the
/// Scheduler will move all triggers associated with the Job into the
/// <see cref="F:Quartz.TriggerState.Error"/> state, which will require human
/// intervention (e.g. an application restart after fixing whatever
/// configuration problem led to the issue wih instantiating the Job.
/// </p>
/// </remarks>
/// <param name="bundle">The TriggerFiredBundle from which the <see cref="T:Quartz.JobDetail"/>
/// and other info relating to the trigger firing can be obtained.
/// </param><throws>SchedulerException if there is a problem instantiating the Job. </throws>
/// <returns>
/// the newly instantiated Job
/// </returns>
public IJob NewJob(TriggerFiredBundle bundle)
{
Type jobType;
try
{
Require.ThatArgument(bundle != null);
Require.ThatArgument(bundle.JobDetail != null);
jobType = bundle.JobDetail.JobType;
}
catch (Exception e)
{
// This shouldn't ever happen, but if it does I want to know about it.
_logService.LogCritical(() => e.ToString());
throw;
}
try
{
return _jobFactory.GetObject(jobType);
}
catch (Exception e)
{
_logService.LogCritical(() => "An exception was thrown while creating job of type {0}: {1}"
.With(jobType, e));
throw;
}
}
}
And IObjectFactory is a simple interface that just abstracts away the Kernel so we're not depending on Ninject everywhere:
/// <summary>
/// Similar to IFactory, but this factory produces types based on a dynamic
/// <see cref="Type"/> object. If the given Type object is not of the given
/// "T" type, an exception will be thrown.
/// </summary>
/// <typeparam name="T">A parent-level type representing what sort of values
/// are expected. If the type could be anything, try using <see cref="object"/>
/// </typeparam>
public interface IObjectFactory<out T>
{
T GetObject(Type type);
}
IObjectFactory is then bound to a class like this:
/// <summary>
/// This implementation of the generic <see cref="IFactory{T}"/> and
/// <see cref="IObjectFactory{T}"/> classes uses Ninject to supply instances of
/// the given type. It should not be used explicitly, but will rather be used
/// by the DI framework itself, to provide instances to services that depend on
/// IFactory objects.
/// This implementation takes the injection context as a constructor argument, so that
/// it can reuse elements of the context when it is asked to supply an instance
/// of a type.
/// In order for this to work, you will need to define bindings from <see cref="IFactory{T}"/>
/// and <see cref="IObjectFactory{T}"/> to this class, as well as a binding from
/// <see cref="IContext"/> to a method or factory that returns the current binding
/// context.
/// </summary>
/// <typeparam name="T">The Type of the service to be produced by the Get method.</typeparam>
public class InjectionFactory<T> : IFactory<T>, IObjectFactory<T>
{
private readonly IKernel _kernel;
private readonly IParameter[] _contextParameters;
/// <summary>
/// Constructs an InjectionFactory
/// </summary>
/// <param name="injectionContext">The context in which this injection is taking place.</param>
public InjectionFactory(IContext injectionContext)
{
_contextParameters = injectionContext.Parameters
.Where(p => p.ShouldInherit).ToArray();
_kernel = injectionContext.Kernel;
}
public T Get()
{
try
{
return _kernel.Get<T>(_contextParameters.ToArray());
}
catch (Exception e)
{
throw e.Wrap(() => "An error occurred while attempting to instantiate an object of type <{0}>".With(typeof(T)));
}
}
public T GetObject(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
if (!typeof (T).IsAssignableFrom(type))
{
throw new InvalidCastException(type.FullName + " is not a child type of " + typeof (T).FullName);
}
try
{
return (T)_kernel.Get(type, _contextParameters);
}
catch (Exception e)
{
throw e.Wrap(() => "An error occurred while attempting to instantiate an object of type <{0}>".With(typeof(T)));
}
}
}
... using a binding that looks like this:
Bind(typeof (IObjectFactory<>)).To(typeof (InjectionFactory<>));
// Injection factories ask for the injection context.
Bind(typeof (IContext)).ToMethod(c => c.Request.ParentContext);
So the overall effect is that we use the Ninject kernel to create the IJobFactory, which uses constructor injection to take an IObjectFactory<IJob>, which is invoked to produce any IJobs that Quartz requires. Those job classes can therefore use constructor-based injection, and Ninject is indirectly instantiating them.
i don't think you have understood IoC very well. When you say the ISession will get auto binded through NHibernate - what are you thinking happens? NHibernate won't manage that for you. Session lifestyle management is your domain.
I don't think there is enough code to really help you. the service as well as the repository would need to be managed by your IoC to be able to inject one into the other - unless you have a direct reference to the IoC - which is not recommended.
You should get the instance of IScheduledRemindersService from your IoC container. The container should take charge of injecting all of the dependencies for you as it creates/resolves the instance.
I'm not familiar with NInject so I can't tell you how it should be done, but you should do something along the lines of:
protected void Application_Start()
{
// 1- register IoC container
// 2- resolve instance of IScheduledRemindersService
// 3- invoke RemindersService() method on instance
}

Ninject.MVC3, Pass DependencyResolver to service-layer?

In a MVC3-application with Ninject.MVC 2.2.0.3 (after merge), instead of injecting repostories directly into controllers I'm trying to make a service-layer that contain the businesslogic and inject the repostories there. I pass the ninject-DependencyResolver to the service-layer as a dynamic object (since I don't want to reference mvc nor ninject there). Then I call GetService on it to get repositories with the bindings and lifetimes I specify in NinjectHttpApplicationModule. EDIT: In short, it failed.
How can the IoC-container be passed to the service-layer in this case? (Different approaches are also very welcome.)
EDIT: Here is an example to illustrate how I understand the answer and comments.
I should avoid the service locator (anti-)pattern and instead use dependency injection. So lets say I want to create an admin-site for Products and Categories in Northwind. I create models, repositories, services, controllers and views according to the table-definitions. The services call directly to the repositories at this point, no logic there. I have pillars of functionality and the views show raw data. These bindings are configured for NinjectMVC3:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ICategoryRepository>().To<CategoryRepository>();
kernel.Bind<IProductRepository>().To<ProductRepository>();
}
Repository-instances are created by ninject via two layers of constructor injection, in the ProductController:
private readonly ProductsService _productsService;
public ProductController(ProductsService productsService)
{
// Trimmed for this post: nullchecks with throw ArgumentNullException
_productsService = productsService;
}
and ProductsService:
protected readonly IProductRepository _productRepository;
public ProductsService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
I have no need to decouple the services for now but have prepared for mocking the db.
To show a dropdown of categories in Product/Edit I make a ViewModel that holds the categories in addition to the Product:
public class ProductViewModel
{
public Product Product { get; set; }
public IEnumerable<Category> Categories { get; set; }
}
The ProductsService now needs a CategoriesRepository to create it.
private readonly ICategoryRepository _categoryRepository;
// Changed constructor to take the additional repository
public ProductsServiceEx(IProductRepository productRepository,
ICategoryRepository categoryRepository)
{
_productRepository = productRepository;
_categoryRepository = categoryRepository;
}
public ProductViewModel GetProductViewModel(int id)
{
return new ProductViewModel
{
Product = _productRepository.GetById(id),
Categories = _categoryRepository.GetAll().ToArray(),
};
}
I change the GET Edit-action to return View(_productsService.GetProductViewModel(id)); and the Edit-view to show a dropdown:
#model Northwind.BLL.ProductViewModel
...
#Html.DropDownListFor(pvm => pvm.Product.CategoryId, Model.Categories
.Select(c => new SelectListItem{Text = c.Name, Value = c.Id.ToString(), Selected = c.Id == Model.Product.CategoryId}))
One small problem with this, and the reason I went astray with Service Locator, is that none of the other action-methods in ProductController need the categories-repository. I feel it's a waste and not logical to create it unless needed. Am I missing something?
You don't need to pass the object around you can do something like this
// global.aspx
protected void Application_Start()
{
// Hook our DI stuff when application starts
SetupDependencyInjection();
}
public void SetupDependencyInjection()
{
// Tell ASP.NET MVC 3 to use our Ninject DI Container
DependencyResolver.SetResolver(new NinjectDependencyResolver(CreateKernel()));
}
protected IKernel CreateKernel()
{
var modules = new INinjectModule[]
{
new NhibernateModule(),
new ServiceModule(),
new RepoModule()
};
return new StandardKernel(modules);
}
So in this one I setup all the ninject stuff. I make a kernal with 3 files to split up all my binding so it is easy to find.
In my service layer class you just pass in the interfaces you want. This service class is in it's own project folder where I keep all my service layer classes and has no reference to the ninject library.
// service.cs
private readonly IRepo repo;
// constructor
public Service(IRepo repo)
{
this.repo = repo;
}
This is how my ServiceModule looks like(what is created in the global.aspx)
// ServiceModule()
public class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<IRepo>().To<Repo>();
}
}
Seee how I bind the interface to the repo. Now every time it see that interface it will automatically bind the the Repo class to it. So you don't need to pass the object around or anything.
You don't need worry about importing .dll into your service layer. For instance I have my service classes in their own project file and everything you see above(expect the service class of course) is in my webui project(where my views and global.aspx is).
Ninject does not care if the service is in a different project since I guess it is being referenced in the webui project.
Edit
Forgot to give you the NinjectDependecyResolver
public class NinjectDependencyResolver : IDependencyResolver
{
private readonly IResolutionRoot resolutionRoot;
public NinjectDependencyResolver(IResolutionRoot kernel)
{
resolutionRoot = kernel;
}
public object GetService(Type serviceType)
{
return resolutionRoot.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return resolutionRoot.GetAll(serviceType);
}
}

Resources