ASP.NET MVC: LessThan and GreaterThan Validation Attributes


ASP.NET MVC ships with a handy Compare attribute that allows you to compare two inputs and display a validation message if they do not match. This is great for sign up pages where a password needs to be entered twice and we need to ensure that they are equal but aside from that, I haven’t found any other use for the Compare attribute. What I have found that I have needed more are LessThan and GreaterThan attributes that allow you to compare two inputs and ensure that one is less than or greater than the other. The ASP.NET MVC Framework doesn’t not come loaded with such attributes so using the Compare attribute as a template, I have created them.

You can download a project here that has the complete source code and test pages for these attributes.

We will start first with the validation attribute:

public class NumericLessThanAttribute : ValidationAttribute, IClientValidatable
{
    private const string lessThanErrorMessage = "{0} must be less than {1}.";
    private const string lessThanOrEqualToErrorMessage = "{0} must be less than or equal to {1}.";                

    public string OtherProperty { get; private set; }

    private bool allowEquality;

    public bool AllowEquality
    {
        get { return this.allowEquality; }
        set
        {
            this.allowEquality = value;
                
            // Set the error message based on whether or not
            // equality is allowed
            this.ErrorMessage = (value ? lessThanOrEqualToErrorMessage : lessThanErrorMessage);
        }
    }        

    public NumericLessThanAttribute(string otherProperty)
        : base(lessThanErrorMessage)
    {
        if (otherProperty == null) { throw new ArgumentNullException("otherProperty"); }
        this.OtherProperty = otherProperty;            
    }        

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, this.OtherProperty);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);   
            
        if (otherPropertyInfo == null)
        {
            return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "Could not find a property named {0}.", OtherProperty));
        }

        object otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);

        decimal decValue;
        decimal decOtherPropertyValue;

        // Check to ensure the validating property is numeric
        if (!decimal.TryParse(value.ToString(), out decValue))
        {
            return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "{0} is not a numeric value.", validationContext.DisplayName));
        }

        // Check to ensure the other property is numeric
        if (!decimal.TryParse(otherPropertyValue.ToString(), out decOtherPropertyValue))
        {
            return new ValidationResult(String.Format(CultureInfo.CurrentCulture, "{0} is not a numeric value.", OtherProperty));
        }

        // Check for equality
        if (AllowEquality && decValue == decOtherPropertyValue)
        {
            return null;
        }
        // Check to see if the value is greater than the other property value
        else if (decValue > decOtherPropertyValue)
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }            

        return null;
    }

    public static string FormatPropertyForClientValidation(string property)
    {
        if (property == null)
        {
            throw new ArgumentException("Value cannot be null or empty.", "property");
        }
        return "*." + property;
    }       

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {           
        yield return new ModelClientValidationNumericLessThanRule(FormatErrorMessage(metadata.DisplayName), FormatPropertyForClientValidation(this.OtherProperty), this.AllowEquality);
    }
}

Like the compare attribute, this attribute will compare against another value on the form. The other property name is passed into the attributes constructor. There is also another named parameter, AllowEquality, which allows you to specify whether or not the value being validated can be equal to the ‘other’ property. The IsValid method is pretty straightforward and compares the two values to see if they are valid.

The last method, GetClientValidationRules creates a ModelClientValidationNumericLessThanRule class defined below:

public class ModelClientValidationNumericLessThanRule : ModelClientValidationRule
{
    public ModelClientValidationNumericLessThanRule(string errorMessage, object other, bool allowEquality)
    {
        ErrorMessage = errorMessage;
        ValidationType = "numericlessthan";
        ValidationParameters["other"] = other;
        ValidationParameters["allowequality"] = allowEquality;
    }
}

This class specifies the client validation type and parameters that will be loaded into the data attributes of the input on the html page. Here we have specified that the jQuery client validation type has a name of ‘numericlessthan’ and that it will accept to parameters values named ‘other’ and ‘allowEquality’.

Now that we have created these two classes, we can now generate a model to test the validation:

public class NumericLessThanViewModel
{
    public decimal MaxValue { get; set; }

    [NumericLessThan("MaxValue", AllowEquality = true)]
    [Display(Name="Value")]
    public decimal Value { get; set; }
}

For this to work we need at least two properties on the model; one that specifies the maximum value and another that will be used for the user input. On the user input property, add the NumericLessThan attribute and specify the name of the ‘other’ property to which it will be compared and whether or not equality is allowed. The ‘other’ value will usually be loaded as a hidden field in the form.

At this point just the server side validation has been setup. We need to add a javascript file as well to enable client side validation.

jQuery.validator.addMethod('numericlessthan', function (value, element, params) {
    var otherValue = $(params.element).val();

    return isNaN(value) && isNaN(otherValue) || (params.allowequality === 'True' ? parseFloat(value) <= parseFloat(otherValue) : parseFloat(value) < parseFloat(otherValue));
}, '');

jQuery.validator.unobtrusive.adapters.add('numericlessthan', ['other', 'allowequality'], function (options) {
    var prefix = options.element.name.substr(0, options.element.name.lastIndexOf('.') + 1),
    other = options.params.other,
    fullOtherName = appendModelPrefix(other, prefix),
    element = $(options.form).find(':input[name=' + fullOtherName + ']')[0];

    options.rules['numericlessthan'] = { allowequality: options.params.allowequality, element: element };
    if (options.message) {
        options.messages['numericlessthan'] = options.message;
    }
});

function appendModelPrefix(value, prefix) {
    if (value.indexOf('*.') === 0) {
        value = value.replace('*.', prefix);
    }
    return value;
}

The first method in the code above adds the actual method that is called when validating the input. In it we check to see if both values are numbers and depending on whether or not we specified to allow equality, we check to ensure that the user input is less than or equal to the other value.

The second method adds the rule to the set of jQuery validation adapters and supplies the wiring up of the parameters that will be supplied to the validation method.

And that is it. The ASP.NET MVC framework and the jQuery validation libraries will take care of the rest.

In the downloadable project above I have included a NumericGreaterThan attribute as well but as you can image, the code is almost identical to the LessThan attribute so I will not be going over it here.

ASP.NET: HttpRequestValidationException on HTTP Post method


Today I developed an aspx page that another company we are integrated with would use to send us some XML data. They transfer the data by performing an HTTP Post to a web page hosted on our servers. To do this I simply read the data posted in the Page_Load event like so:

protected void Page_Load(object sender, EventArgs e)
{
    try
    {                
        if (Request.HttpMethod.ToUpper().Equals("POST"))
        {                 
            StreamReader reader = new StreamReader(Request.InputStream);                    
            string xml = reader.ReadToEnd();
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);                   
                                                 
           ...
        }           
    }
    catch (Exception ex)
    {
        // Handle exception
    }
}

To test my code I created a simple html form and posted to the page and all worked fantastically. I figured even thought information I was posting from the form wasn’t XML, the concept was the same and all should work accordingly. I moved the code out to our production server but it wouldn’t work for the life of me. Nothing seemed to be working, the Page_Load event didn’t even get fired. I ran Wireshark and I saw the HTTP Post come in but nothing was ever done with it. I then took a look at the Windows Application log files and found the problem. The following exception was being thrown before the Page_Load event event fired:

Exception information: 
    Exception type: HttpRequestValidationException 
    Exception message: A potentially dangerous Request.Form value was detected from the client (<?xml version=""1.0" ?><?adf version="1.0" ..."). 

I had forgotten that the .NET Framework does its own validation on all requests and since the post contained XML which could look like a potentially harmful script, it would not let it through. A great security feature but unfortunately the Page_Load event wasn’t even being fired and thus I couldn’t see the exception right off the bat.

To fix this all you need to do is disable request validation for the page using the following page directive.

<%@ Page Language="C#" ValidateRequest="false" ...%>

Obviously if you do this you should explicitly check all inputs and ensure no one can do anything malicious.

ASP.NET: HTTP Post Enabled Web Service


Today I had to create a web service that allowed another company to send information to us on a regular basis and they requested that they be able to send the information by using an HTTP Post rather than a SOAP request. I created the web service and tested it locally using a simple form and all went smoothly but when I deployed it to our production server, I was not able to perform and HTTP Post to the service. Turns out that be default, ASP.NET web services are only able to be called using an HTTP Post when it is on the local machine.

If you need to enable HTTP Post calls to your web service in a production environment, simply add an entry for HttpPost to the system.web -> webServices -> protocols node of the web.config.

Similarly, if you do not want to support HTTP Post calls, just add a remove entry for HttpPost in the same location.

This change can also made in the machine.config file if you want global results.

Posted in ASP.NET. Tags: . 4 Comments »

ASP.NET AJAX ModalPopup to Confirm Delete


As mentioned in my last post I have been working with the ASP.NET AJAX Toolkit lately and more specifically the ModalPopupExtender control. On one of the pages I have a DataGridView with a delete ImageButton and I wanted to use the ModalPopup control to confirm that the user really wanted to delete the row from the DataGridView. This question has been asked many times on many different forums but the most common response seems to be, “That is completely overkill. Just use the confirm() javascript method”. While this may be a true statement, the confirm() method has some drawbacks one of which is that you can’t customize the window to the look and feel of your website.

Confirming an action is a very common scenario, one that will occur on many pages and even multiple times on one page. Such a situation obviously calls for a custom control and that is exactly what I did. After creating the custom control, which will be explained shortly, adding a confirmation box is accomplished by inserting the following markup into your page:

<enso:ConfirmBox runat="server" 
     Title="Confirm Delete" 
     Message="Are you sure you want to delete this product key?" 
     TargetControlId="btnDeleteProductKey"  />

This one simple line will create a confirmation box that can be completely customized to fit the look and feel of your site as shown below.

Now for the explanation.

To create a new user control:

  1. In the Solution Explorer of Visual Studio right click the project name, select Add, and click New Item…
  2. Select Web User Control
  3. Enter ConfirmBox.ascx in the Name textbox
  4. Copy and paste the following code into the ConfirmBox.ascx window.
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ConfirmBox.ascx.cs" Inherits="TestWebApp.ConfirmBox" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="asp" %>

<script runat="server">
    public string Title { set { this.lblTitle.Text = value; } }
    public string Message { set { this.lblMessage.Text = value; } }
    public string TargetControlId { set { this.popupConfirmBox.TargetControlID = this.btnConfirm.TargetControlID = value; } }
    public int Width { set { this.panelConfirmBox.Width = Unit.Pixel(value); } }

    protected void Page_Load(object sender, EventArgs e)
    {
        this.btnPopupClose.OnClientClick = "$find('" + popupConfirmBox.ClientID + "').hide();";
    }
</script>
  
<asp:Panel ID="panelConfirmBox" runat="server" CssClass="modalPopup" Width="400px" Style="display:none;">
    <h5><asp:Label ID="lblTitle" runat="server" Text="[Title]"></asp:Label></h5>
    <div class="closeButton">
        <asp:LinkButton ID="btnPopupClose" runat="server" >x</asp:LinkButton>
    </div>    
    <div class="clearBoth"></div>
    
    <br />  
    <div class="modalMessage">
        <asp:Label ID="lblMessage" runat="server" Text="[Message]"></asp:Label>
    </div>
    <br />
    
    <div class="modalButtons">
        <asp:Button ID="btnYes" runat="server" Text="Yes" /> &nbsp; <asp:Button ID="btnNo" runat="server" Text="No" />
    </div>
</asp:Panel>                    
<asp:ModalPopupExtender Id="popupConfirmBox" runat="server" PopupControlID="panelConfirmBox" BackgroundCssClass="modalBackground" CancelControlID="btnNo" OkControlId="btnYes" ></asp:ModalPopupExtender>
<asp:ConfirmButtonExtender ID="btnConfirm" runat="server" DisplayModalPopupID="popupConfirmBox"> </asp:ConfirmButtonExtender>

You will need to change the Inherits property in the first line of the file to match the name of your application. Additionally, you will need to make sure you already have a reference to the AjaxControlToolkit.dll in your project.

Property Definitions:

  • Title – Sets the title of the confirmation box
  • Message – Sets the message of the confirmation box
  • TargetControlId – Sets the id of the control that should display the confirmation box when clicked
  • Width – Sets the width confirmation box

The only non-appearance property is the TargetControlId which as in all ASP.NET AJAX Toolkit controls is the id of the control that is to be extended. In our case, setting this property will set the TargetControlId property of both the ModalPopupExtender and the ConfirmButtonExtender. For these to work in conjunction, they must both have the same value for the TargetControlId property.

If you read my previous post, ASP.NET AJAX Toolkit ModalPopup Tips, I mentioned a few tricks I learned while using the ModalPopupExtender; one of which was how to add multiple cancel buttons. This can be accomplished with a bit of client-side javascript placed in the OnClientClick event of a LinkButton. In the Page_Load event of the control we are adding this javascript to the ‘x’ in the top right corner of the window. Note that we are accessing the ClientID property of the ModualPopupExtender as when the page is rendered, the control will no longer have an Id of ‘popupConfirmationBox’ but rather something like ”ctl00_contentMain_dataGridViewProductKeys_ctl02_ctl01_popupConfirmBox’.

There is nothing fancy to the configuration of the ModalPopupExtender. We simply set the panel that will appear when the TargetControlId is clicked and the cancel and ok control ids.

Next we introduce another ASP.NET AJAX Toolkit control called the ConfirmButtonExtender. As the name indicates, it is used to confirm an action taken by the user. The plain vanilla version of this control simply uses the javascript function confirm() as discussed earlier in this post. But, the developers were nice enough to allow us to specify a ModalPopup control to be displayed instead. We simply set the DisplayModalPopupID property to the Id of our ModalPopupExtender to let the control know to display the ModalPopup instead.

Before we can start using this control on any page we desire, we need to make a simple modification to the web.config to ensure the control is visible throughout the site. Open your web.config and find the pages section which is a child of the system.web node. Add something similar to the last line that appears in the controls section below. Note that the tagPrefix and tagName can be anything you desire but just make them something you can remember as they will be used to identify your control.

<system.web>
    <pages>
      <controls>
        <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add tagPrefix="enso" src="~/ConfirmBox.ascx" tagName="ConfirmBox"  />
      </controls>
    </pages>
</system.web>

Save the web.config file and build your project to make the control visible while using intellisense. Now navigate to the page on which you want to place the ConfirmBox control. In my case I wanted to add it to a DataGridView delete button. To do this I added a TemplateField to the DataGridView using the following markup.

<asp:TemplateField ShowHeader="false">
     <ItemTemplate>
          <asp:ImageButton ID="btnDeleteProductKey" runat="server" CommandName="Delete" ImageUrl="~/Styles/images/X.png" />
          <enso:ConfirmBox runat="server" Title="Confirm Delete" Message="Are you sure you want to delete this product key?" TargetControlId="btnDeleteProductKey"  />
     </ItemTemplate>
</asp:TemplateField>

Lastly you will need to make sure you have a ScriptManager on the page somewhere and a link to the stylesheet used to format your confirmation box. Click here for the stylesheet and here for the image used in my implementation.

Simply add the markup for the new control you created, specify the Title, Message, and TargetControlId and you are done! You have now implemented a confirmation box that you can placee anywhere on any page without having to duplicate code over and over!

ASP.NET AJAX Toolkit ModalPopup Tips


Today I was working with the ModalPopup control provided by Microsoft in the ASP.NET AJAX Toolkit. This control allows you to create a popup box that removes the users ability to interact with the underlying page until the box is closed. Below is a screenshot from my implementation which was used in a customer managment web application used to create product keys.

Joe Stagner provides a great introductory video tutorial on this control here which got me off to a great start but I soon ran into my first problem.

If you setup the control exactly as described in the video and run your application in IE8, the background opacity is completely ignored and the entire window changes to an opaque gray. Unfortunately, IE8 does not support the opacity css property even though all other browsers do. After many complaints, Microsoft provided a work around that allows you to set the opacity of the page using a proprietary property named ‘-ms-filter’. In order to get the correct opacity in all browsers (IE8, 7, 6, Chrome, FireFox, etc), your CSS class must be in the following order:

.modalBackground
{        
    background-color: Gray;    
    -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";   /* IE 8 Compatibility */
    filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);       /* IE 7 Compatibility */
    opacity: 0.5;    /* Everyone else */
}

My second issue was that when you click the OK button in the ModalPopup the control is designed to execute a javascript function but I needed to execute a server-side function in the code-behind file. To fix this, all you need to do is simply remove the OKControlID property from the ModalPopupExtender control. After doing this, the code-behind click event will be executed when the user clicks the OK button.

<asp:ModalPopupExtender 
     BehaviorID="popupAddProductKeys" 
     runat="server" 
     TargetControlID="btnAddProductKeys" 
     PopupControlID="panelAddProductKeys" 
     BackgroundCssClass="modalBackground"
     CancelControlID="btnProductKeysCancel">

Unfortunately, fixing this second issue caused another. In all non-IE browsers, clicking the OK button would not close the popup window. To fix this, two things need be done. First, set the BehaviorID property in the ModalPopupExtender control. Second, in the Button control, include the following javascript in the OnClientClick event.

<asp:Button 
     ID="btnProductKeysOk" 
     runat="server" 
     Text="OK" 
     OnClientClick="$find('popupAddProductKeys').hide()" 
     onclick="btnProductKeysOk_Click" />

The included javascript will find the ModalPopup control and then call its hide function and thus hide it from view. This trick can be used to add multiple close buttons on the popup. For instance, I added a LinkButton with the text of ‘x’ in the to right corner to allow the user another way to close the popup. To close the popup when clicked, simply place the same javascript as shown above in the OnClientClick event.

<asp:LinkButton 
     ID="btnPopupClose" 
     runat="server" 
     OnClientClick="$find('popupAddProductKeys').hide()">x</asp:LinkButton>
Follow

Get every new post delivered to your Inbox.

Join 69 other followers