Reputation: 10193
I am a lone developer and Pluralsight is my salvation in helping me understand things like Repository and IoC, which I am just learning. However I am finding the pattern awkward to use.
So I am using Unity IoC, and following ModelContainer
class;
public static class ModelContainer
{
private static IUnityContainer instance;
static ModelContainer()
{
instance = new UnityContainer();
}
public static IUnityContainer Instance
{
get
{
instance.RegisterType<ISCD_CallDiaryRepository, SCD_CallDiaryRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<ICompanyRepository, CompanyRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<ISubcontractorRepository, SubcontractorRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<ITradeRepository, TradeRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<IEmployeeRepository, EmployeeRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<ISubcontractorTradeRepository, SubcontractorTradeRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<ICountyRepository, CountyRepository>(new HierarchicalLifetimeManager());
instance.RegisterType<IAddressLineRepository, AddressLineRepository>(new HierarchicalLifetimeManager());
return instance;
}
}
}
allows me to instantiate the repository classes in the constructor of my controller;
public SubcontractorController(
ISubcontractorRepository subcontractorRepository,
ISubcontractorTradeRepository subcontractorTradeRepository,
ICompanyRepository companyRepository,
ISCD_CallDiaryRepository scdCallDiaryRepository,
ITradeRepository tradeRepository,
IAddressLineRepository addressLineRepository)
{
this.SubcontractorRepository = subcontractorRepository;
this.SubcontractorTradeRepository = subcontractorTradeRepository;
this.CompanyRepository = companyRepository;
this.ScdCallDiaryRepository = scdCallDiaryRepository;
this.TradeRepository = tradeRepository;
this.AddressLineRepository = addressLineRepository;
}
What I find awkward about this setup is that if I want to use the CRUD functionality in these repository classes, I have to either do the functionality in the controllers, or pass the repository instance I have created as a parameter into the model class methods. I choose the latter, controllers are not meant to do much, but it doesn't seem right to me to have to keep passing the repository parameter around.
What am I missing?
Upvotes: 3
Views: 4275
Reputation: 1837
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: 5
Reputation: 8669
In provided sample, there is SubcontractorController
controller with six dependecies, what is usually means that the controller tries to do too much at once, and could possibly violates Single responsibility principle. There are at least three way to improve situation:
Upvotes: 5
Reputation: 6239
I tried the latter as well early on in my career. It didn't end well. You end up with unexpected performance hits, and large model objects. I think the controller is the perfect place for the logic you mention. If your worried about ui and business concerns in the same class, move the business out to a service class.
Upvotes: 1