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 Providers because they provide the controller with specialized functions or data as needed.