Note: I can't bountify this question yet (it's too new), but I will reward a good answer with 50 points, and a great answer with 100 (when possible).
I need to incorporate DI into my Web API project. I currently have the expected Model and Controller folders/classes, along with corresponding Repository classes.
That seemed to work well for awhile, but now I need to use DI with the Controllers so that I can pass an Interface type to the Controllers' constructor.
I'm struggling with just how to implement this; that is, how to incorporate the DI "extravaganza" into my existing Model/Controller/Repository structure. I have example DI code, but I don't know just how it should be applied to my project.
Perhaps some code is in order to try to make this clear. I will show a simple sample of what I've got, followed by the DI code I'd like to somehow incorporate into it / with it.
Here is the existing Model/Controller/Repository code:
MODEL
public class Department
{
public int Id { get; set; }
public int AccountId { get; set; }
public string Name { get; set; }
}
CONTROLLER
public class DepartmentsController : ApiController
{
private readonly IDepartmentRepository _deptsRepository;
public DepartmentsController(IDepartmentRepository deptsRepository)
{
if (deptsRepository == null)
{
throw new ArgumentNullException("deptsRepository is null");
}
_deptsRepository = deptsRepository;
}
public int GetCountOfDepartmentRecords()
{
return _deptsRepository.Get();
}
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch)
{
return _deptsRepository.Get(ID, CountToFetch);
}
public void PostDepartment(int accountid, string name)
{
_deptsRepository.PostDepartment(accountid, name);
}
public HttpResponseMessage Post(Department department)
{
// Based on code 2/3 down http://www.codeproject.com/Articles/344078/ASP-NET-WebAPI-Getting-Started-with-MVC4-and-WebAP?msg=4727042#xx4727042xx
department = _deptsRepository.Add(department);
var response = Request.CreateResponse<Department>(HttpStatusCode.Created, department);
string uri = Url.Route(null, new { id = department.Id });
response.Headers.Location = new Uri(Request.RequestUri, uri);
return response;
}
REPOSITORY
public class DepartmentRepository : IDepartmentRepository
{
private readonly List<Department> departments = new List<Department>();
public DepartmentRepository()
{
using (var conn = new OleDbConnection(
@"Provider=Microsoft.ACE.OLEDB.12.0;User ID=BlaBlaBla...
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT td_department_accounts.dept_no,
IIF(ISNULL(t_accounts.name),'No Name provided',t_accounts.name) AS name
FROM t_accounts INNER JOIN td_department_accounts ON
t_accounts.account_no = td_department_accounts.account_no ORDER BY
td_department_accounts.dept_no";
cmd.CommandType = CommandType.Text;
conn.Open();
int i = 1;
using (OleDbDataReader oleDbD8aReader = cmd.ExecuteReader())
{
while (oleDbD8aReader != null && oleDbD8aReader.Read())
{
int deptNum = oleDbD8aReader.GetInt16(0);
string deptName = oleDbD8aReader.GetString(1);
Add(new Department { Id = i, AccountId = deptNum, Name,
deptName });
i++;
}
}
}
}
}
public int Get()
{
return departments.Count;
}
private Department Get(int ID) // called by Delete()
{
return departments.First(d => d.Id == ID);
}
public IEnumerable<Department> Get(int ID, int CountToFetch)
{
return departments.Where(i => i.Id > ID).Take(CountToFetch);
}
public Department Add(Department dept)
{
if (dept == null)
{
throw new ArgumentNullException("Department arg was null");
}
// This is called internally, so need to disregard Id vals that already exist
if (dept.Id <= 0)
{
int maxId = departments.Max(d => d.Id);
dept.Id = maxId + 1;
}
if (departments != null) departments.Add(dept);
return dept;
}
public void PostDepartment(int accountid, string name)
{
int maxId = departments.Max(d => d.Id);
Department dept = new Department();
dept.Id = maxId + 1;
dept.AccountId = accountid;
dept.Name = name;
departments.Add(dept);
}
public void Post(Department department)
{
int maxId = departments.Max(d => d.Id);
department.Id = maxId + 1;
departments.Add(department);
}
public void Put(Department department)
{
int index = departments.ToList().FindIndex(p => p.Id == department.Id);
departments[index] = department;
}
public void Put(int id, Department department)
{
int index = departments.ToList().FindIndex(p => p.Id == id);
departments[index] = department;
}
public void Delete(int id)
{
Department dept = Get(id);
departments.Remove(dept);
}
And now here is the DI code that I want to incorporate
Classes in the DIInstallers folder:
IDepartmentProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace HandheldServer.DIInstallers
{
public interface IDepartmentProvider
{
// These are the methods that are in the sample example IAuthProvider interface; I don't know what I need yet, though...
//bool Authenticate(string username, string password, bool createPersistentCookie);
//void SignOut();
}
}
DepartmentProvider.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace HandheldServer.DIInstallers
{
public class DepartmentProvider : IDepartmentProvider
{
// TODO: Implement methods in IDepartmentProvider, once they have been added
}
}
DepartmentProviderInstaller.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
namespace HandheldServer.DIInstallers
{
public class DepartmentProviderInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn(typeof(IDepartmentProvider))
.WithServiceAllInterfaces());
// If I declare/implement more interface types (other than IDepartmentProvider), I assume there would be another container.Register() call for each of them?
}
}
}
Classes in the DIPlumbing folder:
WindsorCompositionRoot.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Castle.Windsor;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
namespace HandheldServer.DIPlumbing
{
public class WindsorCompositionRoot : IHttpControllerActivator
{
private readonly IWindsorContainer container;
public WindsorCompositionRoot(IWindsorContainer container)
{
this.container = container;
}
public IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
var controller =
(IHttpController)this.container.Resolve(controllerType);
request.RegisterForDispose(
new Release(
() => this.container.Release(controller)));
return controller;
}
private class Release : IDisposable
{
private readonly Action release;
public Release(Action release)
{
this.release = release;
}
public void Dispose()
{
this.release();
}
}
}
}
WindsorControllerFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.MicroKernel;
namespace HandheldServer.DIPlumbing
{
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));
}
return (IController)kernel.Resolve(controllerType);
}
}
}
The Global.asax.cs file
using System;
using System.Reflection;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using Castle.Windsor;
using Castle.Windsor.Installer;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.Http.Dispatcher;
using HandheldServer.DIPlumbing;
namespace HandheldServer
{
public class WebApiApplication : System.Web.HttpApplication
{
private static IWindsorContainer container;
protected void Application_Start()
{
BootstrapContainer();
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
// Code that runs when an unhandled error occurs
void Application_Error(object sender, EventArgs e)
{
// Get the exception object.
Exception exc = Server.GetLastError();
log.Error(exc.Message);
// Clear the error from the server
Server.ClearError();
}
private static void BootstrapContainer()
{
container = new WindsorContainer().Install(FromAssembly.This());
var controllerFactory = new WindsorControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator), new WindsorCompositionRoot(container));
}
protected void Application_End()
{
container.Dispose();
}
}
}
So, I think I've basically got the code I need, but how to fold the DI code into my previous (Model/Controller/Repository) code is the part that has me stumped.
You can simply use WebApiContrib.IoC.CastleWindsor (Nuget).
This test should give you an idea of how to use it.