views:

144

answers:

3

Hi, I can't think of a "best" way of handling the following situation - basically, I have a bunch of stored objects that inherit from a base type and would like to be able to retrieve one from storage, find its subtype (perhaps via "if(x is y)") and then act accordingly - some using a shared implementation, others with dedicated logic and views.

I guess [stripped down] this would look a little like:

/vehicle/details/1234

- Views
  - Vehicle
    - Details.aspx

abstract class Vehicle{
  public int ID{ get; }
}
class Motorbike : Vehicle{
  //whatever  
}
class Car : Vehicle{
  public int NoOfDoors{ get; }
}

class VehicleController : Controller{
  VehicleRepository _vehicleRepository; //injected, etc
  public ActionResult Details(int id){
    var vehicle = _vehicleRepository.Get(id);
    //we can now figure out what subtype the vehicle is
    //and can respond accordingly
  }
}

Now, if we weren't worried about future extension and maintenance and all that, we could go down the dark path and implement something like the following - which would function just fine, but would no doubt be[come] an absolute nightmare.

- Views
  - Vehicle
    - Details.aspx
    - CarDetails.aspx

public ActionResult Details(int id){
  var vehicle = _vehicleRepository.Get(id);
  return (vehicle is Car) ? DetailsView((Car)vehicle) : DetailsView(vehicle);
}
private ActionResult DetailsView(Car car){
  var crashTestResults = GetCrashTestResults(car);
  var carData = new CarDetailsViewData(car, crashTestResults);
  return View("CarDetails", carData);
}
private ActionResult DetailsView(Vehicle vehicle){
  var vehicleData = new VehicleDetailsViewData(car, crashTestResults);
  return View("Details", vehicleData);
}

Another mechanism would be to use subfolders at the view layer - which would keep the code reasonably clean, but doesn't work for my situation since I want a custom action method too...

- Views
  - Vehicle
    - Car
      - Details.aspx
    - Motorbike
      - Details.aspx

public ActionResult Details(int id){
  var vehicle = _vehicleRepository.Get(id);
  return View(vehicle.GetType().Name + "\Details", vehicle);
}

Ideally, the solution would be a base controller and dedicated controllers with overrides where required - but since we have to pull the object from storage before we could determine that ideal controller, I can't figure out how to make that work...

My current ideas generally fall over the first hurdle of having the "VehicleController" know far too much about what those subtype overrides are so any ideas would be appreciated.

Cheers.

A: 

Two solutions I can see, depending which would lead to less duplication:

1) Have Vehicle include abstract methods GetViewName and GetViewData, which allows you to have multiple views but your controller doesn't need to know about them.

2) Have Vehicle include an abstract GetViewData method which returns an object containing all of the ViewData for that class. The ViewData would then implement interfaces and your single view can condition out sections of HTML based on if (ViewData is IHasCrashTestData) or something like that.

In most cases, I would suggest option 1 is more extensible.

pdr
Cheers pdr. I can see where you're going with them, but that would require my models to have intimate knowledge of the views in my app and would prefer not to go down that route.
chrisb
Ok, I can see what your saying. Personally, I see no problem with the view model knowing the name of the view, as opposed to my domain model which shouldn't. And the view model can't help but know about the data required to display the model, that's its purpose. If you don't like the view name in the view model, you could isolate that tranlation out into a service. Or you can use the URL, which is fine, but wasn't your question.
pdr
Might be going a bit mad, but I thought you meant having virtual methods on the "Vehicle" class which could then be overridden by subtypes e.g. car.GetDetailsViewData() - granted the implementation could be abstracted into a service, but its still doing something that I'd prefer kept in the controller - could you elaborate please as I'm probably missing something? Cheers.
chrisb
That is what I'm suggesting, but under the assumption that Vehicle is View Model rather than Data Model - I tend to keep the two quite separate: this is what it looks like when I store it, this is what it looks like on this page.
pdr
A: 

Why you want to create new controller for all vehicle types. In Database N-M assocation will be simply and flexible.

VehicleTypes
    ->Id
    ->Name

VehicleTypeVariables
    ->Id
    ->Name

Vehicles
    ->Id
    ->VehicleTypeId

VehicleVariables
    ->Id
    ->VehicleId
    ->VehicleTypeVariableId
    ->Value
cem
Thanks, but I'd like to stick with subtypes - the object itself isn't just a property bag and needs a bunch of custom logic associated with each.
chrisb
A: 

I could be wrong, but it really looks to me like you're just trying to ask the question of how to render a view appropriate to the given subtype (e.g. Motorcycle or Boat or whatever).

The controller logic, aside from that different view stuff, doesn't look like it needs to be different' for each vehicle type. Are you actually trying to change the program flow based on the vehicle type, or is the controller mostly about handling the CRUD operations?

Assuming that you're just trying to change the rendered view, then have a look at MVC 2's DisplayFor and EditorFor functionaliity. That should allow you to pass down just the model type and then render an appropriate view (iirc, haven't played with it much).

If that doesn't work quite teh way you want, or if you don't want to use MVC 2 since it's just RC still, not RTM, then your next best bet is to override the ViewEngine (probably subclassing your current one) to change the logic of how views are looked up to cover either your folder-based scenario above or whatever scheme you want to apply.

Paul
Some of the subtypes will definitely need to perform different operations per subtype, so the differentiation needs to happen at the controller layer. The views may have completely different characteristics (my example being crash test data) and so just passing that core object along wouldn't be enough; as it would be missing the value-add stuff :)
chrisb
If you're using MVC2 you can account for the view changes using DisplayTemplates.I don't see any examples in yoru post of different actual behavior, just different views. Can you elaborate on what behavior might be different?
Paul