Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Web Page Extensions to Associate Domain Models With Web Forms

0.00/5 (No votes)
29 Jan 2011 2  
An extension type associates an ASPX view page with a domain model object.

Introduction

In ASP.NET MVC, we are able to use strongly typed view pages that give much flexibility in design as well as data presentation and data retrieval on form submission. But in traditional ASP.NET applications, we do not use strongly typed models, even when we are using domain model objects and generic lists; those are not associated with our .aspx pages. Our page is totally unaware about which model object it is using, so we have to do an extra task to present data in input controls or in read only format and retrieve the data back to its original format while submitting the form.

I am trying to reduce this overhead by making an extension class and a set of input controls. I am not sure whether it will help someone or not. Anyway, I am sharing my thoughts with you.

This is my first posting in this site. If you find any glaring mistakes in the code, please let me know and I will try to fix those.

ViewPage Base Class

In contrast to the traditional way, the code-behind class inherits from the ViewPage<T> class instead of the Page class in System.Web.UI. ViewPage<T> is an extension of the Page class and it accepts a type parameter of the domain model object which you want to associate with the page. The ViewPage class mainly contains three properties:

  • Model - Get or set the domain model instance which you want to operate on.
  • Html - Returns an HTML helper class which helps you to render input controls.
  • ModelState - Helps to validate the user input with the DataAnnotation rules applied.

Here is the implementation of the ViewPage class:

public abstract class ViewPage<T> : Page
{
    ModelStateValidator<T> modelState;
    
    protected virtual T Model
    {
        get;
        set;
    }
    
    protected virtual HtmlHelper<T> Html
    {
        get
        {
            return new HtmlHelper<T>(Model, ModelState);
        }
    }
    
    protected virtual ModelStateValidator<T> ModelState
    {
        get
        {
            if (modelState == null)
            {
                modelState = new ModelStateValidator<T>(Model);
            }
            return modelState;
        }
    }
    
    ......................
}

In the ViewPage class, the OnInit method of the Page class is overridden to recollect the data from the Request key collection if the request is a postback.

protected override void OnInit(EventArgs e)
{
  T model = Activator.CreateInstance<T>();

  if (this.IsPostBack)
  {
       string modelName = typeof(T).Name;
       HttpRequest request = HttpContext.Current.Request;
            
       foreach (PropertyInfo property in typeof(T).GetProperties())
       {
            if (request[modelName + "_" + property.Name] != null)
            {
                property.SetValue(model, Convert.ChangeType(
                  request[modelName + "_" + property.Name], 
                  property.PropertyType), null);
            }
        }
    }
    Model = model;
    base.OnInit(e);
}

HtmlHelper Class

The HtmlHelper class accepts a type parameter of your domain model object, letting you to render the appropriate HTML input controls as well as the input validation controls for your model object. The HtmlHelper class constructor takes two parameters. One is your domain model instance and the other is the ModelStateValidator instance; I will explain this soon.

Here is the implementation of the HtmlHelper class:

public class HtmlHelper<T>
{
    T model;
    ModelStateValidator<T> modelState;

    public HtmlHelper(T model, ModelStateValidator<T> modelState)
    {   
        this.model = model;
        this.modelState = modelState;
    } 

    public string TextBoxFor(Expression<Func<T, object>> 
                  modelProperty, Object htmlAttributes)
    {
        ..............
    }

    private string GetPropertyName(Expression<Func<T, object>> expression)
    {
        .............
    }     
}

TextBoxFor Method

HtmlHelper helps to render popular input controls; here I am illustrating how an input type text will be rendered with the TextBoxFor method of the HtmlHelper class. If you are familiar with MVC HTML helper methods, it will be easy to understand the TextBoxFor method. The TextBoxFor method takes the model class property by an expression parameter. The htmlAttributes parameter can be any attribute supported by the input control.

public string TextBoxFor(Expression<Func<T, object>> modelProperty, Object htmlAttributes)
{
    string property = GetPropertyName(modelProperty);  
    string modelName = typeof(T).Name;
            
    Object value = typeof(T).GetProperty(property).GetValue(model, null);
            
    StringBuilder control = new StringBuilder(String.Format("<input type={0}text{0} ", '"'));
    control.Append(String.Format("name={0}" + modelName + "_" + property + "{0} ", '"'));
    
    foreach (PropertyInfo _property in htmlAttributes.GetType().GetProperties())
    {
        control.Append(String.Format(_property.Name + "={0}" + 
           _property.GetValue(htmlAttributes, null) + "{0} ", '"'));
    }

    control.Append(String.Format("value={0}" + Convert.ToString(value) + "{0} />", '"'));
    return control.ToString();
}

GetPropertyName Method

The GetPropertyName method retrieves the property name from the expression:

private string GetPropertyName(Expression<Func<T, object>> expression)
{
    MemberExpression memberExp = null;
    if (expression.Body.NodeType == ExpressionType.Convert)
    {
        memberExp = ((UnaryExpression)expression.Body).Operand as MemberExpression;
    }
    else if (expression.Body.NodeType == ExpressionType.MemberAccess)
    {
        memberExp = expression.Body as MemberExpression;
    }
    return (memberExp != null) ? memberExp.Member.Name : "";
}

ModelStateValidtor class

The ModelStateValidator class validates the user input with the DataAnnotation rules applied on each property in the domain model object. ModelStateValidator is inherited from a Generic Dictionary. The constructor argument will be your domain model instance to validate.

Here is the implementation of the ModelStateValidator class:

public class ModelStateValidator<T> : Dictionary<String, String>
{
    T model;
    RequiredAttribute required;
    
    public ModelStateValidator(T model)
    {
        this.model = model;
    }
    
    public virtual bool IsValid
    {
        ..........
    }
}

IsValid Property

The IsValid property of the ModelStateValidator checks whether the model instance is valid as per validation rules applied or not. Here I am trying to illustrate how it validates the RequiredAttribute of a model property.

public virtual bool IsValid
{
    get
    {
        bool isValid = true;
        foreach (PropertyInfo property in typeof(T).GetProperties())
        {
            if (property.GetCustomAttributes(
                  typeof(RequiredAttribute), false).Length > 0)
            {
                required = (RequiredAttribute)property.GetCustomAttributes(
                            typeof(RequiredAttribute), false)[0];
                if (!required.IsValid(property.GetValue(model, null)))
                {
                    isValid = false;
                    this.Add(property.Name, String.IsNullOrEmpty(required.ErrorMessage) ? 
                             property.Name + " is required." : required.ErrorMessage);
                }
            }
        return isValid;
    }
}

Consuming Web Page Extensions

To consume the web page extension feature, first we need to create a domain model type. Here I am creating a model type Category having three properties:

public class Category
{
    public int Id { get; set; }
    [Required()]
    public string Name { get; set; }
    public int ParentId { get; set; }
}

We next inherit our code-behind class from the ViewPage<T> base class instead of the System.Web.UI.Page class:

public partial class _Default : ViewPage<Category>

Next, we add the input controls to the page using the HTML helper class.

<%= Html.TextBoxFor(x => x.Id)%> <br />     
<%= Html.TextBoxFor(x => x.Name)%> <br /> 
<%= Html.TextBoxFor(x => x.ParentId)%> <br />   
     
<asp:Button ID="btnSubmit" runat="server" Text="Submit" 
     onclick="btnSubmit_Click" />

In the button click event, you can simply access the model object like this:

model.jpg

Source

The included source contains a few more input controls and their usage.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here