tags:

views:

315

answers:

2

In my application I have different pages: Contact Us, About Us, Home

They all have the same base elements that needs to populate the view:

  • Page Title
  • Meta Description
  • User Information

However on each page, they have some elements that are different:

Contact Us

  • Contact Information Model
  • Contact Form Model

About Us

  • Extended User Information Model

Home

  • Home Page Text Property

They are all routed to the same Controller Action Method because most of the functionality is similar other than populating the "extra" information dependent on page type.

So far I have done something where:

PageDetailViewData pageDetailViewData = new PageDetailViewData {Title = title, Desc = desc....}

and following this I have:

                switch ((PageType)page.PageType)
            {
                case (PageType.Contact):
                    return View("ContactUsDetails", pageDetailViewData);
                default:
                    return View(pageDetailViewData);
            }

The question is how do I populate the "extra" information? I am not sure if I am going about doing this the right way. Any insight to better structure the logic flow would be appreciated.

+1  A: 

The title of your question almost gives you the answer. You can use some form of polymorphism to accomplish this. You could define a base class with the shared properties, or alternatively an interface like this:

public interface ICommonPage
{
    string Title { get; }
    string MetaDescription { get; }
    string UserInformation { get; }
}

Then define three strongly typed ViewModel classes that all implement this interface (or derive from the base class):

  • ContactUsViewModel : ICommonPage
  • AboutUsViewModel : ICommonPage
  • HomeViewModel : ICommonPage

On each of those ViewModel classes, you add the extra properties that you need for those Views.

In your Controller Action, you will need to switch on PageType to select the correct ViewModel and populate it with data.

You will also need to creat three different Views (.aspx) that are strongly typed to each ViewModel class.

If you have shared rendering for the common data, you can extract that into a strongly typed UserControl (.ascx) that is typed to ICommonPage.

Mark Seemann
What is the best way to instantiate the Derived View Model? What is the best way to populate the base information? Should I abstract the population into a method?
TimLeung
In general it is best if you can keep the ViewModels passive and make it the responsibility of the Controller (or another class) to populate the data. This is also the place where you will need to select a the correct ViewModel type.
Mark Seemann
+2  A: 

Hi Tim,

The answer of using interfaces to imply some commonality between your view models is certainly going to help to answer some of the points in your questions.

I would however ask how wise it is to "refactor" your Action to support multiple views of differing data structures.

MVC controller actions typically represent the minimum amount of code required to gather the specific data required to generate the intended view. It's not completely uncommon for a single action to return different views of the same model data (Html view or Mobile view for example) but by varying both the structure of the data and view that will generated you introduce a few problems.

In particular you violate common best practices like the Single Responsibility Principle and make your code much more complicated to test - and Pain free testing and TDD are part of the big win with ASP.Net MVC after all.

Personally I would have a separate Action.

As far as your view models are concerned, how would you do it if this was a database? You would have separate queries for separate data right?

A user's profile information would be queried separately from the page meta data information. This would be done for a number of reasons that could include the ability to cache certain parts of the data but not others for example.

So with the above suggestions your code might look like this (Warning: this code wasn't written in Visual Studio and is probably full of syntax issues):

public interface IMetaDataViewModel
{
    PageMetaData MetaData{get; set;}
}
public class HomeViewModel : IMetaDataViewModel
{
    public PageMetaData MetaData{get; set;}
    public string HomePageText{get; set;}
}
//other view models go here....

public class CommonPagesController : Controller
{
    private MetaDataProvider _metaProvider = new MetaDataProvider();
    private PageDataProvider _pageDataProvider = new PageDataProvider();
    private ContactDataProvider _contactDataProvider = new ContactDataProvider();

    public ActionResult Home()
    {
        var viewModel = new HomeViewModel
        {
            MetaData = _metaProvider.GetPageMeta();
            HomePageText = _pageDataProvider.GetPageData();
        };
        return View(viewModel);
    }
    public ActionResult Contact()
    {
        var viewModel = new ContactViewModel
        {
            MetaData = _metaProvider.GetPageMeta();
            ContactFormData = _contactDataProvider.GetData();
        };
        return View(viewModel);
    }
    //you get the picture...
}

There are several ways you could also refactor out the generation of the view model code but thats one possible pattern.

I appreciate that this answer does have a certain amount of opinion in it but I would consider having separate actions to be best practice.

Hope that helps.

Gavin Osborn
Thanks for the detailed response. The reason everything is routed to the same Action Handler is because the "Action" being passed in is dynamic and the Page Type is only determined when I query the database. for example: site.com/ABC site.com/contact-ABC... I only know which View to pull dependent on the key following the domain.
TimLeung
Perhaps the abstraction that you need is not at the controller level but perhaps at the Controller Factory level?If you want to persist down the strongly typed view route (usually a good thing) then you are going to have to write custom code to populate each view model.It sounds though that you are writing a CMS style application. It's possibly the one time I would actively debate the advantages of a strongly typed view. I don't have a view on that yet... but it's worth considering.
Gavin Osborn