Asked by:
Extending Page Class To Associate Domain Models With Web Form

Question
-
User-2006244509 posted
Introduction
In asp.net MVC we are able to use strongly typed view pages that gives much flexibility in design as well as data presentation and data retrieval on form submission. But in traditional asp.net applications we are not using 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 back the data 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 forum. If you find any glaring mistake 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 is inheriting from the ViewPage<T> class instead of Page class in System.Web.UI. ViewPage<T> is an extension of Page class and it is accepting a type parameter of the domain model object which you want to associate with the page. 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.
Implementation of the VewPage class:
namespace System.Web.UI { using System.Reflection; 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 ViewPage class OnInit method of the Page class is overridden to recollect the data from 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); } </t>
HtmlHelper Class
HtmlHelper class accepts a type parameter of the your domain model object, letting you to render appropriate HTML input controls as well as input validation controls for your model object. HtmlHelper class constructor takes two parameters. One is your domain model instance and the other is ModelStateValidator instance, I will explain it soon.
Implementation of the HtmlHelper class:
namespace System.Web.UI { using System; using System.Text; using System.Reflection; using System.ComponentModel; using System.Collections.Generic; using System.Linq.Expressions; 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>> expression, 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 TextBoxFor method of HtmlHelper class. If you are familiar with MVC Html helper methods, it will be easy to understand TextBoxFor method. TextBoxFor method takes the model class property by an expression parameter. Html attributes 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
GetPropertyName method retrieving the property name from 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
ModelStateValidator class validates the use inputs with DataAnnotation rules applied on each property in the domain model object. ModelStateValidator is inherited from a Generic Dictionary. Constructor argument will be your domain model instance to validate.
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 ViewPage Class
To consume the web page extension feature, first we need to create domain model type. Here I am creating a model type namely Category, having three properties.
public class Category { public int Id { get; set; } [Required()] public string Name { get; set; } public int ParentId { get; set; } }
Next inherit our code behind class from VewPage<T> base class instead of System.Web.UI.Page class.
public partial class _Default : ViewPage<Category>
Next adding input controls to the page using 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 simply you can access the model object like this.
protected void btnSubmit_Click(object sender, EventArgs e) { Category category = this.Model; }
Source Code
I have similar posting in Code Project. Source code can be downloaded from there.
Source code contains few more input controls and usage.
Friday, January 28, 2011 9:27 PM
All replies
-
User1682618242 posted
Please format your post. It is very hard to read but I followed the one on CodeProject.
If I understood corectly this is for WebForms and not MVC and you're trying to have Html.Helpers and strong typed Pages in ASP.NET WebForms...Am I correct?
This is a MVC forum so you'll probably find more answers in the Web Forms forum (http://forums.asp.net/18.aspx).
Saturday, January 29, 2011 3:02 AM -
User-2006244509 posted
Thank you for pointing out. I surely have misplaced my article. It was intended for ASP.NET forum.
Saturday, January 29, 2011 9:57 PM