Multiple controllers, one view and one ASP.NET MVC 3 model

I want to have one model and view that are served by multiple controllers in my ASP.NET MVC 3 application.

I implement a system that interacts with the users online calendar, and I support Exchange, Google, Hotmail, Yahoo, Apple, etc ... Each of them has completely different implementations of the calendar APIs, but I can abstract it with my own model. I think that by implementing polymorphism at the controller level, I can clearly understand the various APIs and authentication issues.

I have a nice clean model and view, and so far I have implemented two controllers that prove that I can read / request / write / update both Exchange and Google: ExchangeController.cs and GoogleController.cs .

I have /Views/Calendar that contains my view code. I also have /Models/CalendarModel.cs , which includes my model.

I want to check which calendar system the user is using in my ControllerFactory . I implemented it as follows:

 public class CustomControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == typeof(CalendarController)) { if(MvcApplication.IsExchange) // hack for now return new ExchangeController(); else return new GoogleController(); } return base.GetControllerInstance(requestContext, controllerType); } } 

and in my Application_Start :

 ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory()); 

It works. If I got to http://.../Calendar , this factory code works and the right controller is created!

It worked beautifully, and I did it without realizing what I was doing. Now I think I have it, but I want to make sure that I don’t miss anything. I really spent time looking for something like that and did not find anything.

One thing that concerns me is that I decided that I could have an inheritance relationship between CalendarController and ExchangeController / GoogleController as follows:

 public class ExchangeController : CalendarController { 

But if I do this, I get:

 The current request for action 'Index' on controller type 'GoogleController' is ambiguous between the following action methods: System.Web.Mvc.ViewResult Index(System.DateTime, System.DateTime) on type Controllers.GoogleController System.Web.Mvc.ActionResult Index() on type Controllers.CalendarController 

Which drives me away because I wanted to put some common functions on the base, and now, I think, I have to use a different way.

Is this the right way to make multiple controllers for the same view / model? What else will I need to consider?

EDIT: Read more about my issues.

Based on the answers below (thanks!) I think I need to show some more codes to make sure you guys see what I'm trying to do. My model is really a data model. It starts with this:

 /// <summary> /// Represents a user calendar across a date range. /// </summary> public class Calendar { private List<Appointment> appointments = null; /// <summary> /// Date of the start of the calendar. /// </summary> public DateTime StartDate { get; set; } /// <summary> /// Date of the end of the calendar /// </summary> public DateTime EndDate { get; set; } /// <summary> /// List of all appointments on the calendar /// </summary> public List<Appointment> Appointments { get { if (appointments == null) appointments = new List<Appointment>(); return appointments; } set { } } } 

Then my controller has the following methods:

 public class ExchangeController : Controller { // // GET: /Exchange/ public ViewResult Index(DateTime startDate, DateTime endDate) { // Exchange specific gunk. The MvcApplication._service thing is a temporary hack CalendarFolder calendar = (CalendarFolder)Folder.Bind(MvcApplication._service, WellKnownFolderName.Calendar); Models.Calendar cal = new Models.Calendar(); cal.StartDate = startDate; cal.EndDate = endDate; // Copy the data from the exchange object to the model foreach (Microsoft.Exchange.WebServices.Data.Appointment exAppt in findResults.Items) { Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, exAppt.Id); Models.Appointment appt = new Models.Appointment(); appt.End = a.End; appt.Id = a.Id.ToString(); ... } return View(cal); } // // GET: /Exchange/Details/5 public ViewResult Details(string id) { ... Models.Appointment appt = new Models.Appointment(); ... return View(appt); } // // GET: /Exchange/Edit/5 public ActionResult Edit(string id) { return Details(id); } // // POST: /Exchange/Edit/5 [HttpPost] public ActionResult Edit(MileLogr.Models.Appointment appointment) { if (ModelState.IsValid) { Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(appointment.Id)); // copy stuff from the model (appointment) // to the service (a) a.Subject = appointment.Subject ... a.Update(ConflictResolutionMode.AlwaysOverwrite, SendInvitationsOrCancellationsMode.SendToNone); return RedirectToAction("Index"); } return View(appointment); } // // GET: /Exchange/Delete/5 public ActionResult Delete(string id) { return Details(id); } // // POST: /Exchange/Delete/5 [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(string id) { Microsoft.Exchange.WebServices.Data.Appointment a = Microsoft.Exchange.WebServices.Data.Appointment.Bind(MvcApplication._service, new ItemId(id)); a.Delete(DeleteMode.MoveToDeletedItems); return RedirectToAction("Index"); } 

So this is basically typical CRUD stuff. I have provided a sample from the version of ExchangeCalendar.cs. GoogleCalendar.cs is clearly similar to an implementation.

My model (Calendar) and related classes (e.g. Appointment) is what is passed from the controller for viewing. I do not want my opinion to reflect the details of using the basic online service. I don’t understand how implementing the Calendar class with an interface (or an abstract base class) will give me the polymorphism I'm looking for.

SOMEWHERE I need to choose which implementation to use based on the user.

I can either do this:

  • In my model. I do not want to do this, because then my model gets cooler using a special service code.
  • In the controller. For instance. run each controller method with something redirecting to the correct implementation.
  • Below the controller. For instance. as I suggest above with the new factory controller.

The answers below mention "service level." I think this is probably where I am off the rails. If you look at how MVC typically runs with a database, dbContext is a "service level", right? So maybe what you guys suggest is the 4th place where I can do an indirect attitude? For example, the Edit above would look something like this:

  private CalendarService svc = new CalendarService( eg Exchange or Google ); // // POST: /Calendar/Edit/5 [HttpPost] public ActionResult Edit(MileLogr.Models.Appointment appointment) { if (ModelState.IsValid) { svc.Update(appointment); return RedirectToAction("Index"); } return View(appointment); } 

Is this right to do?

Sorry this got so long, but this is the only way I know how to get enough context through ... END EDIT

+6
source share
3 answers

I think you are on a dangerous path. The controller should be as simple as possible and contain only "glue" between, for example, your level of service and models / views. Due to the movement of your common calendar abstractions and specific provider implementations from controllers, you get rid of the connection between your routes and the calendar implementation.

Edit: instead, I would use polymorphism at the service level and at the service class I would check the factory class at the level of your user database for the current user provider and create an instance of the corresponding implementation of the CalendarService class. This should eliminate the need to check the calendar provider on the controller, while keeping it simple.

What I mean by contacting the routes is that your custom URLs are what the AFAICT is currently causing. By moving with a single controller and moving complexity to the service level, you can probably just use the default MVC routes.

+7
source

I wouldn’t do that. As Jonas points out, controllers should be very simple and designed to coordinate the various “services” that are used to respond to a request. Are request streams really different from calendar to calendar? Or are these data calls necessary to capture this data.

One way to do this is to enable calendars behind the common calendar interface (or the abstract base class), and then accept the calendar into the controller through the constructor parameter.

 public interface ICalendar { // All your calendar methods } public abstract class Calendar { } public class GoogleCalendar : Calendar {} public class ExchangeCalendar : Calendar {} 

Then in your CalendarController

 public class CalendarController { public CalendarController(ICalendar calendar) {} } 

This will not work by default unless you register a dependent converter. One quick way to do this is to use NuGet to install the package that installs it. For instance:

 Install-Package Ninject.Mvc3 

I think that would be the best architecture. But suppose you disagree, let me answer your original question.

The reason you get the ambiguous exception is because you have two public Index methods that don't differ in an attribute that indicates that you need to respond to GET and one to POST. All public controller methods are action methods.

If the CalendarController not intended to be directly created (i.e. it will always be inherited), then I would make the Index method in this class protected virtual , and then redefine it in the derived class.

If the CalendarController needs to be created on its own, and other derived classes are just “flavors” of it, then you need to make the Index public virtual method, and then get each of the resulting classes to override the Index method. If they do not override it, they add another Index method (C # rules, not ours), and you need to distinguish them for the MVC radius.

+8
source

As the other answers show, you really have to reorganize your code so that you do not first need multiple controllers.

However, your controllers can inherit from your base class controller - you just need to make sure that when registering routes in Global.asax.cs you use an overload that determines what namespace the controllers and action methods should find for a given route

eg.

  routes.MapRoute(null, "{controller}/{action}", new[] { "Namespace.Of.Controllers.To.USe" }); 
0
source

Source: https://habr.com/ru/post/902498/


All Articles