Monday, April 7, 2014

Showing different RAZOR Views based on the Model type in ASP.Net MVC

Problem

In real time, there will be scenarios where we need to display different model classes differently in the Views. Normally, if the controller is aware of the Model type and knows the corresponding View, it can return the proper ViewResult object via its Action() methods. But what should we do, if we have an GenericBusinessObject ie an Entity which have its properties in a dictionary or enclosed in XElement and we need to switch the View based on the type of Generic Entity?

My current project have an offline processing feature which is controlled via queues. When somebody needs to process a long running task they put their request into the queue and the queue monitor mechanism dequeue the requests whenever there is space to process that request. This is a general framework where the track developer needs to write code for queuing the request which accepts the parameter in the form of xml. The parameter decide how the processing server process that particular request. The track developer also needs to write code for the actual processing by implementing certain interfaces. Simply saying the queuing system just makes the processing delayed until the processing servers have enough capacity to process the request and the details about how to process and related logic is decoupled from the queue framework as the operation detail is passed as xml.

This system also has monitoring web application as well where the production engineers can see how many queue items are waiting in the queue, how many failed etc...The web site also have provision to see the queue request parameter xml which is viewable only to the application support engineers. A trained application support engineer can identify, what a particular queue request is intended for, by analysing the queue requests parameter. This monitoring site is implemented using ASP.Net MVC 4 with a page to see all queue requests. Things were easy until we get a requirement to display different pages based on the queue request type, so that the application support engineers don't need to analyse the xml to know what is the intention of the queue request.

To understand the issue in a simple scenario, lets take Person object with 2 properties. PersonType and Properties. Definition is given below.

    public class Person
    {
        [Display(Name = "First Name")]
        public string PersonType { getset; }
        public XElement Properties { getset; }
    }

The Properties is kept as xml as it is having dynamic schema or in simple words variable number of properties. If the PersonType is Employee the Properties can include FirstName & Company.For Student type it can have FirstName,School & Marks. 
The MVC site is supposed to call a web service to get person  object list and display all the unique person types from the obtained person list. When the user clicks on the PersonType link it should display Person objects corresponding to the person type. If the type is known such as Student display 3 columns and if its unknown just show the Person type. The URL might look like as below
site.com/Person/List?personType=Employee
or
site.com/Person/List?personType=Student

Challenges

  • If we just return the model from Action method, it cannot render as it don't know the View corresponding to the Model.
  • If we put if/switch condition based on the PersonType in the List.cshtml and render the <Table> accordingly the List.cshtml will grow to large size.
  • We can have same if/switch based on PersonType and render partial views which will limit the size of the List.cshtml. But again the partial view need to have logic for parsing the XML and display accordingly.

Solution

The better solution, I could think of is "having an abstraction for parsing the xml to create concrete model classes and give to View for just display" instead of view working on Model to find out what is relevant. So the logic of parsing xml is done in the Action and it returns typed model class object to the corresponding views. When we say abstraction, its just and interface which returns the typed objects and the corresponding view name. The implemented classes will have the logic of parsing the xml to convert to typed objects. Only consideration is about having a base class for view model classes which are returned from the interface methods or properties.

It is not the perfect solution. We can argue on merits of having partial views v/s this provider mechanism. But its worth looking at this provider mechanism as well. Below is the relevant code for this approach.

    public class ViewModelBase
    {
    }
//Typed class for student.It will differ for employee
    public class StudentViewModel:ViewModelBase
    {
        public IEnumerable<StudentModel> Students { getset; }
    }
    public class StudentModel
    {
        [Display(Name = "First Name")]
        public string FirstName { getset; }
        [Display(Name = "School")]
        public string School { getset; }
        [Display(Name = "Marks")]
        public string Marks { getset; }
    }
    public class DefaultViewModel : ViewModelBase
    {
        //Reuse the Person itself instead of DefaultModel
        public IEnumerable<Person> Persons { getset; }
    }
/////////////////Classes related to provider model.
    public interface IViewModelProvider
    {
        ViewModelBase GetViewModel(IList<Person> persons);
        string ViewName { get; }
    }
    internal partial class StudentViewModelProvider : IViewModelProvider
    {
        ViewModelBase IViewModelProvider.GetViewModel(IList<Models.Person> persons)
        {
            StudentViewModel studVMList = new StudentViewModel() { };
            studVMList.Students = persons.Select(p=>new StudentModel(){
                FirstName = p.Properties.Attribute("FirstName").Value,
                School = p.Properties.Attribute("School").Value,
                Marks = p.Properties.Attribute("Marks").Value
            });
            return studVMList;
        }
        string IViewModelProvider.ViewName
        {
            get { return "Student"; }
        }
    }
    public class DefaultViewModelProvider : IViewModelProvider
    {
        ViewModelBase IViewModelProvider.GetViewModel(IList<Models.Person> persons)
        {
            DefaultViewModel personVMList = new DefaultViewModel() { };
            personVMList.Persons = persons;
            return personVMList;
        }
        string IViewModelProvider.ViewName
        {
            get { return "Index"; }
        }
    }
/////////////MVC Specific
    public class PersonController : Controller
    {
        public ActionResult List(string personType)
        {
            IEnumerable<Person> persons = DataRepository.GetPersonsByType(personType);
            IViewModelProvider provider = ViewModelProviderFactory.GetProvider(personType);
            ViewModelBase viewModel = provider.GetViewModel(persons.ToList());
            return View(provider.ViewName, viewModel);
        }
    }

Hope this is clear. Sample can be downloaded from the below location.

No comments: