Springe zum Inhalt

Mind-driven development

Matthias Jauernig — Software development, architecture and design

That's me

Categories

  • Business (1)
  • General (6)
  • Management (1)
    • Ideas (1)
  • Technology (95)
    • .NET (48)
      • C# (7)
      • Code Contracts (15)
      • F# (1)
      • Web (10)
        • ASP.NET (1)
        • ASP.NET MVC (5)
        • Silverlight (6)
      • Windows (2)
        • WPF (1)
      • Windows Phone (6)
    • Development (21)
      • Architecture (3)
      • Design patterns (9)
      • Software Design (8)
        • DbC (7)
        • TDD (7)
      • User Interfaces (1)
    • Microsoft (25)
      • Cloud Computing (23)
        • Live Services (19)
          • Live Framework (16)
          • Live Mesh (14)
        • Windows Azure (3)
      • Windows (2)
    • Visual Studio (4)
    • Windows 8 (10)
      • Metro (1)
      • WinRT (10)

Last articles

  • WinRT XAML Validation library: UI Controls for Validation
  • WinRT XAML Validation library: Manual Validation
  • WinRT XAML Validation library: Implicit and Explicit Validation Execution
  • WinRT XAML Validation library: Async Validation Attributes
  • WinRT XAML Validation library: Property-Group Validation Attributes

Last comments

  • SDX Privatbilanz für Windows 8.1: Eingabevalidierung - SDX AG bei WinRT XAML Validation library: Manual Validation
  • Wird Windows Azure Erfolg haben? - SDX AG bei Will Windows Azure succeed?
  • Elias Neumann bei Explicit Property Memento
  • Alex bei C#: Efficient retrieval of Expression values
  • c# - Impuro viene chiamato il metodo per readonly campo bei Code Contracts #5: Method purity

Tools

Can't live without ReSharper

Can't live without NDepend

Schlagwort: data annotations

ASP.NET MVC Quick Tip: Check Data Annotations from code

During the last weeks I got some insights on ASP.NET MVC 2. Personally I like the programming model of MVC in contrast to WebForms, although the productivity first seems to be lower in common data-driven scenarios. One of the most advertised features of MVC 2 is the support of Data Annotations for adding validators and further information right to your model via attributes. Data Annotations have their origins in ASP.NET Dynamic Data and are defined in the System.ComponentModel.DataAnnotations namespace that ships with .NET 4.0. Microsoft is promoting Data Annotations even further and beside MVC and Dynamic Data they can be used in Silverlight, too.

Data Annotations offer many capabilities for model validation to your application. There are predefined validators for common constraints: RangeAttribute, RegularExpressionAttribute, RequiredAttribute and StringLengthAttribute. Those attributes can be automatically checked on the client-side (via Javascript) as well. Furthermore there is a CustomValidationAttribute which allows you to define custom validation logic. Personally I feel a bit ambivalent on Data Annotations. Beside of validation logic you are able to define UI-relevant information on your model which I can’t encourage if done on the core domain model. On the other side you are able to extend your model with Data Annotations on the UI layer… But I don’t want to discuss the usage of Data Annotations here. There are scenarios where they are a perfect match and there are other cases where you want to do validation on your own…

Simple example

If you use Data Annotations for validation you get great tool support in ASP.NET MVC 2 and other UI technologies. Imagine the following model class Product:

public class Product
{
    [Required(ErrorMessage="ProductID is a required field")]
    public int ProductID { get; set; }

    [Required(ErrorMessage = "ProductName is a required field")]
    [StringLength(40, ErrorMessage = "ProductName can only contain up to 40 characters")]
    public string ProductName { get; set; }

    [StringLength(20, ErrorMessage = "QuantityPerUnit can only contain up to 20 characters")]
    public string QuantityPerUnit { get; set; }

    [Range(0, (double)decimal.MaxValue, ErrorMessage = "UnitPrice must be a valid positive currency")]
    public decimal? UnitPrice { get; set; }
}

Entities of this model class should be editable through the following strongly-typed view Edit.aspx:

<%@ Page Title="Edit Product" Language="C#"  Inherits="System.Web.Mvc.ViewPage<Product>" %>

<h2>Edit Product</h2>

<% using (Html.BeginForm()) {%>
<%: Html.ValidationSummary(true) %>

<fieldset style="padding:10px;">
<legend>Product Fields model.ProductName) %>
</div>
<div class="editor-field">
    <%: Html.TextBoxFor(model => model.ProductName) %>
    <%: Html.ValidationMessageFor(model => model.ProductName) %>
</div>

<div class="editor-label" style="margin-top:10px;">
    <%: Html.LabelFor(model => model.QuantityPerUnit) %>
</div>
<div class="editor-field">
    <%: Html.TextBoxFor(model => model.QuantityPerUnit) %>
    <%: Html.ValidationMessageFor(model => model.QuantityPerUnit) %>
</div>

<div class="editor-label" style="margin-top:10px;">
    <%: Html.LabelFor(model => model.UnitPrice) %>
</div>
<div class="editor-field">
    <%: Html.TextBoxFor(model => model.UnitPrice, String.Format("{0:F}", Model.UnitPrice)) %>
    <%: Html.ValidationMessageFor(model => model.UnitPrice) %>
</div>

<p style="margin-top:10px;">
    <input type="submit" value="Save" />
</p>
</fieldset>
<% } %>

When posting the form in this view the following action Edit() on the ProductController is invoked:

[HttpPost]
public ActionResult Edit(Products product)
{
    if (!ModelState.IsValid)
        return View(product);

    // ...
}

And here’s where magic comes into play. When posting the form the Data Annotations on the Product class are checked automatically. If there are any validation errors ModelState.IsValid is set to false and ModelState itself will contain the error messages. Those error messages automatically are displayed in the UI through the Html.ValidationMessageFor() helpers as shown below:
ASP.NET MVC 2 - Validation with Data Annotations

Everything’s fine?

Now you could think that everything’s fine with this solution, right? But that’s not the case! Data Annotations have an important and not very obvious shortcoming which can lead to serious data consistency problems. The problem: Data Annotations are checked during the model binding phase based on the posted form values and not on the bound model entity. That means only the Data Annotations on the posted form values are checked, but not other properties which are perhaps defined on the model class, but missing in the form values. Imagine for example some bad guy who visits your page and edits a Product. Before posting the form he manipulates the DOM of the page and removes some form values. Those values will not be checked on server-side and thus the (invalid) Product will be saved to the DB or some operations will be done on it. Got the point?

So what can we do? We can manually check the defined validation Data Annotations in our controller action on the model class after the model binding procedure. Therefore we can for example develop a custom action filter attribute (inspired from here):

public class ModelValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // get the controller for access to the ModelState dictionary
        var controller = filterContext.Controller as Controller;
        if(controller != null)
        {
            var modelState = controller.ModelState;

            // get entities that could have validation attributes
            foreach (var entity in filterContext.ActionParameters.Values.Where(o => o != null))
            {
                // get metadata attribute
                MetadataTypeAttribute metadataTypeAttribute =
                    entity.GetType().GetCustomAttributes(typeof(MetadataTypeAttribute), true)
                        .FirstOrDefault() as MetadataTypeAttribute;

                Type attributedType = (metadataTypeAttribute != null)
                    ? metadataTypeAttribute.MetadataClassType
                    : entity.GetType();

                // get all properties of entity class and possibly defined metadata class
                var attributedTypeProperties = TypeDescriptor.GetProperties(attributedType)
                    .Cast<PropertyDescriptor>();
                var entityProperties = TypeDescriptor.GetProperties(entity.GetType())
                    .Cast<PropertyDescriptor>();

                // get errors from all validation attributes of entity and metadata class
                var errors = from attributedTypeProperty in attributedTypeProperties
                             join entityProperty in entityProperties
                             on attributedTypeProperty.Name equals entityProperty.Name
                             from attribute in attributedTypeProperty.Attributes.OfType<ValidationAttribute>()
                             where !attribute.IsValid(entityProperty.GetValue(entity))
                             select new KeyValuePair<string, string>(attributedTypeProperty.Name,
                                 attribute.FormatErrorMessage(string.Empty));

                // add errors to ModelState dictionary
                foreach (var error in errors)
                    if (!modelState.ContainsKey(error.Key))
                        modelState.AddModelError(error.Key, error.Value);
            }
        }

        base.OnActionExecuting(filterContext);
    }
}

This attribute takes all validation attributes into account which are defined on the model class itself or on an associated metadata class. The attribute adds all validation errors to the ModelState dictionary of the controller which have not been added before and which are defined in the ErrorMessage or the Error Resource of the validation attribute. Note that your controller has to derive from the Controller class and not from ControllerBase, but that’s the default setting and should not be a problem.

Now we only have to add this attribute to our controller action and validation will be done on the bound model class:

[HttpPost]
[ModelValidation]
public ActionResult EditProduct(Products product)
{
    if (!ModelState.IsValid)
        return View(product);

    // ...
}

Conclusion

This post has shown how you can check defined validation Data Annotations from your code and why it’s important in ASP.NET MVC 2 to do so. Of course you don’t have to define a custom action filter attribute for this task and can do this task by a custom validation helper in your business logic if you like. Once again this story told me one thing: don’t rely on technical solutions and question their background behavior all the time…

kick it on DotNetKicks.com

Veröffentlicht am 2010/05/022010/07/10Kategorien ASP.NET MVCTags actionfilterattribute, ASP.NET Dynamic Data, ASP.NET MVC, custom validation, data annotations, data annotations model binder, model, model binding, model metadata, Silverlight, validation, validation from code7 Kommentare zu ASP.NET MVC Quick Tip: Check Data Annotations from code
Stolz präsentiert von WordPress