Generating ASP.NET MVC View Controls According to Xml Configurations

April 23, 2012

Generating ASP.NET MVC View Controls According to Xml Configurations

Generating ASP.NET MVC View Controls According to Xml ConfigurationsLast week I was consulting about ASP.NET MVC at a customer. One of the customer project requirements is to generate forms according to Xml configuration files. In this post I’ll offer an end-to-end solution to this requirement. This solution can be rafactored to use databases or any other data sources in order to achieve the same results.

Setting The Environment

Create a new default internet ASP.NET MVC 3 web application:

The Solution

Pay attention to the folders I added such as Classes and ViewModels. The Classes folder will be used to store classes that I use in the application. The ViewModels folder will be used to store the view models that I’ll use in the solution.

The Xml File

Here is an example for the Xml file I’m going to use in the solution:

<?xml version="1.0" encoding="utf-8" ?>
<views>
  <view id="home">
    <controls>
      <control>
        <type name="Label">
          <property name="ID" value="lbl1"/>
          <property name="Value" value="lbl1"/>
        </type>
      </control>
      <control>
        <type name="TextBox">
          <property name="ID" value="txt1"/>
          <property name="Value" value="txt1"/>
        </type>
      </control>
    </controls>
  </view>
</views>

The Xml will be located in the App_Data directory. In the Xml, a view node represents a single view that holds some controls to render. Every control has a type (for example Label) and each type has properties that define the type. As you can understand this data structure can also be implemented inside a database or other storage types. Since the customer needed Xml representation I use an Xml file.

First Step – Creating the ViewModels for the Controls

The first thing to do is to create a base class to represent a control inside a view.

public abstract class ControlViewModel
{
  public abstract string Type { get; }
  public string Label { get; set; }
  public string ID { get; set; }
  public string Name { get; set; }
}

The ControlViewModel include the control type, the control id, the control name and a label string associated with the control. If you need other properties which are repeated in all your controls you can add them.

After we have the base class lets implement some concrete classes such as TextBox and Label:

public class TextBoxViewModel : ControlViewModel
{
  public override string Type
  {
    get
    {
      return "TextBox";
    }
  }
  public string Value { get; set; }
}
 
 
public class LabelViewModel : ControlViewModel
{
  public override string Type
  {
    get
    {
      return "Label";
    }
  }
 
  public string Value { get; set; }
}

In the concrete classes, I implemented the control type and added more properties that are used in that control.

Now that we have the concrete classes we can add a ViewModel that will hold all the list of controls for a view:

public class DefaultControlsViewModel
{
  public List<ControlViewModel> Controls { get; set; }
 
  public DefaultControlsViewModel()
  {
    Controls = new List<ControlViewModel>();
  }
}

The DefaultControlsViewModel includes a list of all the controls to render. If you like to add more model properties to the ViewModel you can inherit from this class and add them.

Second Step – Creating the ControlsContext to Read the Xml File

After we have the ViewModels, we will create a class to read the Xml file (or read from your data source if you have chosen another data source). I have created a ControlsContext class to read the Xml file and to store a DefaultControlsViewModel for each view in the Xml. Here is the ControlsContext implementation:

public class ControlsContext
{
  private static Dictionary<string, DefaultControlsViewModel> _dynamicViewModel;
 
  public Dictionary<string, DefaultControlsViewModel> DynamicViewModel
  {
    get
    {
      return _dynamicViewModel;
    }
  }
 
  static ControlsContext()
  {      
    Init();
  }
 
  private static void Init()
  {
    _dynamicViewModel = new Dictionary<string, DefaultControlsViewModel>();
    XDocument doc = GetXmlDocument();
    CreateDynamicViewModel(doc);
  }
 
  private static void CreateDynamicViewModel(XDocument doc)
  {
    var viewNodes = from node in doc.Document.Descendants()
                    where node.Name == "view"
                    select node;
 
    foreach (var element in viewNodes)
    {
      AddControlType(element);
    }
  }
 
  private static void AddControlType(XElement element)
  {
    var controlTypes = from node in element.Descendants()
                       where node.Name == "type"
                       select node;
 
    DefaultControlsViewModel viewModel = new DefaultControlsViewModel();
    foreach (var type in controlTypes)
    {
      var id = (from idNode in type.Descendants("property")
                where idNode.Attribute("name").Value == "ID"
                select idNode.Attribute("value").Value).FirstOrDefault();
 
      var value = (from valueNode in type.Descendants("property")
                   where valueNode.Attribute("name").Value == "Value"
                   select valueNode.Attribute("value").Value).FirstOrDefault();
 
      var obj = Activator.CreateInstance("DynamicControlsCreation", "DynamicControlsCreation.ViewModels." + type.FirstAttribute.Value + "ViewModel").Unwrap();
      var control = (ControlViewModel)obj;
      control.ID = id;
      control.Name = id;
      control.GetType().GetProperty("Value").SetValue(control, value, null);
      viewModel.Controls.Add(control);
    }
    _dynamicViewModel.Add(element.FirstAttribute.Value, viewModel);
  }
 
  private static XDocument GetXmlDocument()
  {
    string path = HttpContext.Current.Server.MapPath("~/App_Data/Controls.xml");
    XDocument doc = XDocument.Load(path);
    return doc;
  }
}

Some things to notice:

  • You can create this class as singleton since we will want only one instance for it.
  • I use the HttpContext.Current.Server.MapPath("~/App_Data/Controls.xml") statement to get the path for the App_Data folder.
  • I use LINQ to Xml to read the Xml file content.
  • In order to create and fill the control view models I use the Activator.CreateInstance("DynamicControlsCreation", "DynamicControlsCreation.ViewModels." + type.FirstAttribute.Value + "ViewModel")

    statement.

Third Step – Implement the HomeController Functionality

Now that we have the ControlsContext and the control ViewModel classes we can implement the controller functionality.

public class HomeController : Controller
{
  public ActionResult Index()
  {
    ControlsContext context = new ControlsContext();
    var model = context.DynamicViewModel["home"];
    return View(model);
  }
 
  [HttpPost]
  public ActionResult Index(DefaultControlsViewModel model)
  {
    return RedirectToAction("Submitted");
  }
 
  public ActionResult Submitted()
  {
    return View();
  }
 
  public ActionResult About()
  {
    return View();
  }
}

As you can see, I use the default generated ASP.NET MVC HomeController and implement the Index method and an Index method to handle HTTP posts. The reason for the second method will be explained in the last step.

Forth Step – Creating the Views and the Template Views for Each Control

We set all the environment but now we want to create our dynamic generated views. Here is the Index view:

@{
    ViewBag.Title = "Home Page";
}
@model DynamicControlsCreation.ViewModels.DefaultControlsViewModel 
<p>
    @using (Html.BeginForm())
    {
        for (int i = 0; i < Model.Controls.Count; i++)
        {         
        <div>
            @Html.HiddenFor(x => x.Controls[i].Type)
            @Html.HiddenFor(x => x.Controls[i].Name)
            @Html.EditorFor(x => x.Controls[i])
        </div>       
        } 
        <input type="submit" value="Submit" /> 
    }
</p>

As you can see, I iterate on all the controls in the DefaultControlsViewModel and write the relevant controls in the view. I use the HTML.EditorFor method to render the relevant view for the relevant control.

In the Views –> Shared folder, I added an EditorTemplates folder and put the relevant view templates for the controls view model (which will be used we you call the HTML.EditorFor method):

@model DynamicControlsCreation.ViewModels.LabelViewModel
<div>
    @Html.LabelFor(x => x.Value, Model.Label)
    @Html.LabelFor(x => x.Value)
</div>

and

@model DynamicControlsCreation.ViewModels.TextBoxViewModel
<div>
    @Html.LabelFor(x => x.Value, Model.Label)
    @Html.TextBoxFor(x => x.Value)
</div>

The last view that I added is the Submitted view:

@{
    ViewBag.Title = "Submitted";
}
 
<h2>Submitted</h2>

The Views folder will look like:

Views Folder

Last Step – Create the ControlsModelBinder

If you’ll try to run the application everything will work except for the post to the Index method which is created when you click the submit button. The reason it won’t work is that ASP.NET MVC doesn’t know how to create the instances for the concrete control classes. This error can be solved by using a a model binder. If you aren’t familiar with ASP.NET MVC model binders you can read the following post. Here is the model binder to instantiate the relevant ControlViewModel class:

public class ControlsModelBinder : DefaultModelBinder
{
  protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
  {
    var type = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type");
    object model = Activator.CreateInstance("DynamicControlsCreation", "DynamicControlsCreation.ViewModels." + type.AttemptedValue + "ViewModel").Unwrap();
    bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
    return model;
  }
}

You will have to register that binder in the Global.asax file in the Application_Start event:

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
 
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
 
  ModelBinders.Binders.Add(typeof(ControlViewModel), new ControlsModelBinder()); 
}

This is it.

Now you can run the application and see it working including the submit button in the homepage.

You can download the full code example from here.

Summary

In the post, I’ve created an end-to-end solution to handle dynamic controls creation in a view according to an Xml configuration file.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

6 comments

  1. Dotnet TechyApril 25, 2012 ב 10:02

    Great article

    We can also submit our .net related article links on http://www.dotnettechy.com to improve traffic

    This is kind of social networking for dotnet professionls only

    Reply
  2. MonicaFebruary 28, 2014 ב 12:50

    Hi,
    I am trying to add a dropdownlist dynamically. Can you please explain how should the xml file look like and also how can i bind the model to the values in the list.

    Reply
    1. Gil Fink
      Gil FinkMarch 2, 2014 ב 13:16

      Hi Monica,

      Here is a direction to solve your problem:

      You can add the following class in the ControlViewModel to represent the drop down:

      public class DropDownViewModel : ControlViewModel
      {
      public override string Type
      {
      get
      {
      return "DropDown";
      }
      }

      public string Value { get; set; }
      }

      You can add a new EditorTemplate to represent the drop down itself:

      @model DynamicControlsCreation.ViewModels.DropDownViewModel

      @{
      var items = new List();
      foreach (var item in Model.Value.Split(','))
      {
      items.Add(new SelectListItem()
      {
      Text = item,
      Value = item
      });
      }
      }
      @Html.LabelFor(x => x.Value, Model.Label)
      @Html.DropDownList(Model.Name, items)

      The last thing is to add to the Controls.xml the following code:

      Pay attention that I put the list that is going to be shown in the drop down in the value property of the Value node.

      I hope it will help you to add the implementation you need.

      Gil

      Reply
  3. NkMarch 4, 2014 ב 15:05

    Thanks for giving the editor template code.

    but i stuck in validations, how to do validation in Editor Template because its generate dynamically?

    Want validations using Data Annotations in MVC3. But its generating dynamically and control name doesn’t created as per Entity class created.

    What to do to doing validations? Is there any method to validate multiple dynamic fields?

    Reply
  4. Jiny1July 22, 2014 ב 12:25

    Hi,
    Its been long since you posted this but appreciate if you answer this.
    I am able to create controls using this method.But when we override the ” Createmodel” of defaultmodelbinder and get the type, the type is null.May I know what does this line mean “var type = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + “.Type”);” specifically what does appending “.Type” mean.

    NOTE: I am using Entityframework to load data from DB unlike the XML file used by you

    Thanks,
    Jiny1

    Reply
    1. Jiny1July 22, 2014 ב 12:54

      Well, I got the answer.Its the type of control that we are creating at runtime.It gets its value from hiddenfor.type in the view

      Thanks

      Reply