Reputation: 6451
I'm using Unity framework for IoC in an MVC application, and I populate a UnityContainer using the LoadConfiguration method (which reads the interface/type mappings from the configuration file).
What is the best method to share this container among different controllers in my application?
If I use a public property on the Application (Global.asax) should I be worried about thread synchronization?
Upvotes: 1
Views: 996
Reputation: 1837
I agree with Zabavsky on injecting services. Here is what I do:
I break my MVC apps into several different projects.
I also have a set of classes packaged up into NuGet packages that I can add to my app if/when needed, namely (for this example):
My controllers do nothing except get the view models from the services layer to send to the views and then receive the data upon post from the views and send it off to the services layer for validation and saving back to the repository.
Here is a basic example:
This is the view model that will be used in this example:
public class CreateFocusViewModel
{
public int CareerPlanningFormID { get; set; }
public int PerformanceYear { get; set; }
public IList<FocusModel> Focuses { get; set; }
public string ResultsMeasuresFocusComments { get; set; }
public byte MaximumFocusesAllowed { get; set; }
}
public class FocusModel
{
public int FocusID { get; set; }
public string FocusText { get; set; }
public bool IsPendingDeletion { get; set; }
}
Sample Controller with GET and POST action methods:
public class CPFController : Controller
{
private readonly ICareerPlanningFormService careerPlanningFormService;
public CPFController(ICareerPlanningFormService careerPlanningFormService)
{
this.careerPlanningFormService = careerPlanningFormService;
}
[HttpGet]
public ViewResult CreateFocus(int careerPlanningFormID)
{
var model = this.careerPlanningFormService.BuildCreateFocusViewModel(careerPlanningFormID);
return this.View(model);
}
[HttpPost]
public ActionResult CreateFocus(int careerPlanningFormID, string button)
{
var model = this.careerPlanningFormService.BuildCreateFocusViewModel(careerPlanningFormID);
this.TryUpdateModel(model);
switch (button)
{
case ButtonSubmitValues.Next:
case ButtonSubmitValues.Save:
case ButtonSubmitValues.SaveAndClose:
{
if (this.ModelState.IsValid)
{
try
{
this.careerPlanningFormService.SaveFocusData(model);
}
catch (ModelStateException<CreateFocusViewModel> mse)
{
mse.ApplyTo(this.ModelState);
}
}
if (!this.ModelState.IsValid)
{
this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
return this.View(model);
}
break;
}
default:
throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
}
switch (button)
{
case ButtonSubmitValues.Next:
return this.RedirectToActionFor<CPFController>(c => c.SelectCompetencies(model.CareerPlanningFormID));
case ButtonSubmitValues.Save:
this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
return this.RedirectToActionFor<CPFController>(c => c.CreateFocus(model.CareerPlanningFormID));
case ButtonSubmitValues.SaveAndClose:
default:
return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
}
}
}
Service layer where the ViewModel is built and data validated/saved:
public class CareerPlanningFormService : ICareerPlanningFormService
{
private readonly IAppNameRepository repository;
private readonly IPrincipal currentUser;
public CareerPlanningFormService(IAppNameRepository repository, IPrincipal currentUser)
{
this.repository = repository;
this.currentUser = currentUser;
}
public CreateFocusViewModel BuildCreateFocusViewModel(int careerPlanningFormID)
{
var cpf = this.repository.GetCareerPlanningFormByID(careerPlanningFormID);
// create the model using cpf
var model = new CreateFocusViewModel
{
CareerPlanningFormID = cpf.CareerPlanningFormID,
PerformanceYear = cpf.PerformanceYearID,
ResultsMeasuresFocusComments = cpf.ResultsMeasuresFocusComments,
MaximumFocusesAllowed = cpf.PerformanceYear.MaximumCareerPlanningFormFocusesAllowed
// etc., etc...
};
return model;
}
public void SaveFocusData(CreateFocusViewModel model)
{
// validate the model
this.ValidateCreateFocusViewModel(model);
// get the current state of the CPF
var cpf = this.repository.GetCareerPlanningFormByID(model.CareerPlanningFormID);
// bunch of code saving focus data here...
// update the ResultsMeasuresFocusComments
cpf.ResultsMeasuresFocusComments = string.IsNullOrWhiteSpace(model.ResultsMeasuresFocusComments) ? null : model.ResultsMeasuresFocusComments.Trim();
// commit the changes
this.repository.Commit();
}
private void ValidateCreateFocusViewModel(CreateFocusViewModel model)
{
var errors = new ModelStateException<CreateFocusViewModel>();
{
var focusesNotPendingDeletion = model.Focuses.Where(f => f.IsPendingDeletion == false);
// verify that at least one of the focuses (not pending deletion) has a value
{
var validFocuses = focusesNotPendingDeletion.Where(f => !string.IsNullOrWhiteSpace(f.FocusText)).ToList();
if (!validFocuses.Any())
{
var index = model.Focuses.IndexOf(model.Focuses.Where(f => f.IsPendingDeletion == false).First());
errors.AddPropertyError(m => m.Focuses[index].FocusText, Resources.ErrorMsg_CPF_OneFocusRequired);
}
}
// verify that each of the focuses (not pending deletion) length is <= 100
{
var focusesTooLong = focusesNotPendingDeletion.Where(f => f.FocusText != null && f.FocusText.Length > 100).ToList();
if (focusesTooLong.Any())
{
focusesTooLong.ToList().ForEach(f =>
{
var index = model.Focuses.IndexOf(f);
errors.AddPropertyError(m => m.Focuses[index].FocusText, Resources.ErrorMsg_CPF_FocusMaxLength);
});
}
}
}
errors.CheckAndThrow();
}
}
Repository class:
public class AppNameRepository : QueryRepository, IAppNameRepository
{
public AppNameRepository(IGenericRepository repository)
: base(repository)
{
}
public CareerPlanningForm GetCareerPlanningFormByID(int careerPlanningFormID)
{
return this.Repository.Get<CareerPlanningForm>().Where(cpf => cpf.CareerPlanningFormID == careerPlanningFormID).Single();
}
}
Repository interface:
public interface IAppNameRepository : IRepository
{
CareerPlanningForm GetCareerPlanningFormByID(int careerPlanningFormID);
}
Classes from the CompanyName.Data common library:
public abstract class QueryRepository : IRepository
{
protected readonly IGenericRepository Repository;
protected QueryRepository(IGenericRepository repository)
{
this.Repository = repository;
}
public void Remove<T>(T item) where T : class
{
this.Repository.Remove(item);
}
public void Add<T>(T item) where T : class
{
this.Repository.Add(item);
}
public void Commit()
{
this.Repository.Commit();
}
public void Refresh(object entity)
{
this.Repository.Refresh(entity);
}
}
public interface IGenericRepository : IRepository
{
IQueryable<T> Get<T>() where T : class;
}
public interface IRepository
{
void Remove<T>(T item) where T : class;
void Add<T>(T item) where T : class;
void Commit();
void Refresh(object entity);
}
I have both LinqToSQL and E.F., here is the setup for LinqToSQL:
internal sealed class LinqToSqlRepository : IGenericRepository
{
private readonly DataContext dc;
public LinqToSqlRepository(DataContext dc)
{
this.dc = dc;
}
public IQueryable<T> Get<T>() where T : class
{
return this.dc.GetTable<T>();
}
public void Remove<T>(T item) where T : class
{
this.dc.GetTable<T>().DeleteOnSubmit(item);
}
public void Add<T>(T item) where T : class
{
this.dc.GetTable<T>().InsertOnSubmit(item);
}
public void Commit()
{
this.dc.SubmitChanges();
}
public void Refresh(object entity)
{
this.dc.Refresh(RefreshMode.OverwriteCurrentValues, entity);
}
}
This is in the CompanyName.Data common library as well. It is has methods to either register LinqToSQL or EntityFramework
public static class UnityContainerExtensions
{
public static IUnityContainer RegisterEntityFrameworkClasses<TDbContext>(this IUnityContainer container, string nameOrConnectionString) where TDbContext : DbContext
{
var constructor = typeof(TDbContext).GetConstructor(new Type[] { typeof(string) });
container.RegisterType<DbContext>(new HierarchicalLifetimeManager(), new InjectionFactory(c => constructor.Invoke(new object[] { nameOrConnectionString })));
container.RegisterType<IGenericRepository, EntityFrameworkRepository>();
return container;
}
public static IUnityContainer RegisterLinqToSqlClasses<TDataContext>(this IUnityContainer container, string connectionString) where TDataContext : DataContext
{
var constructor = typeof(TDataContext).GetConstructor(new Type[] { typeof(string) });
container.RegisterType<DataContext>(new HierarchicalLifetimeManager(), new InjectionFactory(c => constructor.Invoke(new object[] { connectionString })));
container.RegisterType<IGenericRepository, LinqToSqlRepository>();
return container;
}
}
In the CompanyName.Utilities library:
public interface IUnityBootstrap
{
IUnityContainer Configure(IUnityContainer container);
}
Unity bootstrapping in AppName.Data
public class UnityBootstrap : IUnityBootstrap
{
public IUnityContainer Configure(IUnityContainer container)
{
var config = container.Resolve<IAppNameConfiguration>();
return container.RegisterLinqToSqlClasses<AppNameDataContext>(config.AppNameConnectionString)
.RegisterType<IAppNameRepository, AppNameRepository>();
}
}
Unity bootstrapping in AppName.Services
public class UnityBootstrap : IUnityBootstrap
{
public IUnityContainer Configure(IUnityContainer container)
{
new CompanyName.Security.UnityBootstrap().Configure(container);
new AppName.Data.UnityBootstrap().Configure(container);
container.RegisterSecureServices<AuthorizationRulesEngine>(typeof(UnityBootstrap).Assembly);
return container.RegisterType<ICareerPlanningFormService, CareerPlanningFormService>()
.RegisterType<IStaffService, StaffService>();
}
}
Unity bootstrapping in AppName.Web
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Standard MVC setup
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
// Application configuration
var container = new UnityContainer();
new CompanyName.Mvc.UnityBootstrap().Configure(container);
new AppName.Configuration.UnityBootstrap().Configure(container);
new AppName.Data.UnityBootstrap().Configure(container);
new AppName.Services.UnityBootstrap().Configure(container);
// Default MVC model binder is pretty weak with collections
ModelBinders.Binders.DefaultBinder = new DefaultGraphModelBinder();
}
protected void Application_Error()
{
HttpApplicationEventHandler.OnError(this.Context);
}
protected void Application_EndRequest()
{
HttpApplicationEventHandler.OnEndRequest(this.Context);
}
}
Upvotes: 1