I'm not sure if I'll answer all of your questions, but here goes...
I use a BaseController
as well, but I don't do what your example does. Here's how my code looks like (my application also uses DI for the constructors...):
public class BaseController : Controller {
private readonly IProvider AddressProvider = null;
private readonly IProvider EmailProvider = null;
private readonly IProvider PhoneProvider = null;
[Inject] // Using Ninject for DI
public BaseController(
AddressProvider AddressProvider,
EmailProvider EmailProvider,
PhoneProvider PhoneProvider) {
this.AddressProvider = AddressProvider;
this.EmailProvider = EmailProvider;
this.PhoneProvider = PhoneProvider;
}
}
And here's my AdministrationController
which inherits from BaseController
:
public class AdministrationController : BaseController {
private readonly CustomerProvider CustomerProvider = null;
private readonly EmployeeProvider EmployeeProvider = null;
[Inject]
public AdministrationController(
CustomerProvider CustomerProvider,
EmployeeProvider EmployeeProvider,
AddressProvider AddressProvider,
EmailProvider EmailProvider,
PhoneProvider PhoneProvider) : base(AddressProvider, EmailProvider, PhoneProvider) {
this.CustomerProvider = CustomerProvider;
this.EmployeeProvider = EmployeeProvider;
}
}
My AdministrationController
only cares about CustomerProvider
and EmployeeProvider
and it passes AddressProvider
, EmailProvider
and PhoneProvider
to the BaseController
.
AddressProvider
, EmailProvider
and PhoneProvider
are in the BaseController
because I consider Address
, Email
and Phone
to be low-level objects. My reason for that is because they can be linked to Customer
, Employee
or anything else as far as the database is concerned. So, instead of having multiple methods for Customer
or Employee
to interact with each of their objects, I just have one. For example:
public class BaseController : Controller {
// GET: /Addresses/{AddressId}/Delete
public void DeleteAddress(
int AddressId) {
this.AddressProvider.DeleteAndSave(AddressId);
Response.Redirect(Request.UrlReferrer.AbsoluteUri);
}
// GET: /Emails/{EmailId}/Delete
public void DeleteEmail(
int EmaildId) {
this.EmailProvider.DeleteAndSave(EmailId);
Response.Redirect(Request.UrlReferrer.AbsoluteUri);
}
// GET: /Phones/{PhoneId}/Delete
public void DeletePhone(
int PhoneId) {
this.PhoneProvider.DeleteAndSave(PhoneId);
Response.Redirect(Request.UrlReferrer.AbsoluteUri);
}
}
And with that I take care of my low-level objects. Keep in mind, in my application I have additional methods to further manipulate those objects as needed.
Now, in my AdministrationController
I'm working with CustomerProvider
and EmployeeProvider
. These are more specialized because I consider Customer
and Employee
to be high-level objects. That being said, their providers do a bit more work than Delete. For example they also provide the view models used by the views (durp...):
public class AdministrationController : BaseController {
public ActionResult Customer(
int CustomerId) {
return this.View(this.CustomerProvider.GetView(CustomerId));
}
public AciontResult Customers() {
return this.Veiw(this.CustomerProvider.GetAllView(CustomerId));
}
public ActionResult CustomerAddresses(
int CustomerId,
Address Address) {
if (ModelState.IsValid) {
this.CustomerProvider.AddAddressAndSave(CustomerId, Address);
};
return this.RedirectToAction("Customer", new {
CustomerId = CustomerId
});
}
public ActionResult Employee(
int EmployeeId) {
return this.View(this.EmployeeProvider.GetView(EmployeeId));
}
public ActionResult Employees() {
return this.View(this.EmployeeProvider.GetAllView());
// OR
// return this.View(this.EmployeeProvider.GetActiveView());
// OR
// return this.Veiw(this.EmployeeProvider.GetInactiveView());
// ETC...
// All of these return the exact same object, just filled with different data
}
public RedirectToRouteResult EmployeeAddresses(
int EmployeeId,
Address Address) {
if (ModelState.IsValid) {
this.EmployeeProvider.AddAddressAndSave(EmployeeId, Address);
// I also have AddAddress in case I want to queue up a couple of tasks
// before I commit all changes to the data context.
};
return this.RedirectToAction("Employee", new {
EmployeeId = EmployeeId
});
}
}
Is it best practices to have only one repository per controller?
I'm going to say no because your repositories will only work for the object they're instanced for. You can't have (well, you can, but that's just bad...) a repository that handles an Address
, Email
and Phone
all at once because you'll have to specialize it just for it to work the way you need it.
My AddressProvider
, EmailProvider
and PhoneProvider
are all essentially the same because they implement IProvider
, however they each instance a generic repository (Repository<T>
) for the object they're working with.
Furthermore, you're controller shouldn't be interacting with the repositories directly, but indirectly through the providers.
My CustomerProvider
and EmployeeProvider
each instance specialized repositories for Customer
and Employee
(CustomerRepository
, EmployeeRepository
), but they also instance other repositories they'll need when they for example construct the view models. For example, they'll instance a StateRepository
which is Repository<State>
or PhoneTypesRepository
which is Repository<PhoneType>
, and they'll use those repositories to pass additional objects/collections to the view to build up forms with drop downs or whatever. They'll also instance other providers to further help with building the view model such as CookieProvider
which they use to get the currently active Cookie
and pass it to the view model.
All in all it's a mesh of independent/generic providers or repositories which are combined to accomplish a specialized task.
I hope this sheds some light for you through an alternative way of writing code, or at the least I hope it just helps you understand a little bit better.
P.S. In case you're wondering what Provider
is, most all other people choose to call them Service
, but to me that word is misused, so I just call them Provider
s because they provide the controller with specialized functions or data as needed.