Validation without ViewState

by jrummell 1. June 2010 14:35

I ran into an issue today where I needed to validate a few controls without ViewState. These were drop down lists whose items were added from the returned value of a WebService.

function bindDropDownList(items, controlSelector)
{
    var itemsHtml = "<option><\/option>\n";
    $(items).each(function(index, item)
    {
        itemsHtml += "<option value='" + item.Value + "'>" + item.Text + "<\/option>\n";
    });

    $(controlSelector).html(itemsHtml);
}

This worked great, until I tried to use a RequiredFieldValidator. The client side validation actually worked, but the server side validation kept saying that there was no value selected. This is because the items added by client script aren’t persisted in ViewState, so there is no server side value. This had me stumped for a few minutes. At first I tried replacing the ASP.NET validators with a client side form submit handler, but it was going to be tricky to get the validation messages into the ValidationSummary. I thought about jQuery Validation for a moment, but I didn’t want to have to deal with two different validation frameworks (ASP.NET and jQuery).

Then it finally dawned on me. It was so simple. All that I needed to do was replace the RequiredFieldValidator with a CustomValidator and then implement both the client and server side validation methods. I also replaced the DropDownList control with a normal HTML select tag since I couldn’t access its selected value from server side code.

<select id="ddlCounty" name="ddlCounty">
    <option value="">[Select a State]</option>
</select>
<asp:CustomValidator ID="valCountyRequired" runat="server" 
    ClientValidationFunction="validateCounty"
    OnServerValidate="valCountyRequired_ServerValidate" Display="Dynamic" 
    ErrorMessage="The County is required." ValidateEmptyText="true" />

Here’s the client side handler

function validateCounty(sender, args)
{
    args.IsValid = validateRequired(countySelector);
}

function validateRequired(selector)
{
    var value = $(selector).val();
    return !String.isNullOrEmpty(value);
}

And the server side handler

protected void valCountyRequired_ServerValidate(object source, ServerValidateEventArgs args)
{
    args.IsValid = ValidateRequired(Request.Form["ddlCounty"]);
}

private static bool ValidateRequired(string value)
{
    return !String.IsNullOrEmpty(value);
}

Notice that I’m not using args.Value in either handler. That’s because I didn’t set ControlToValidate on the CustomValidator since without ViewState, it won’t be able to get the value. That’s why I’m using Request.Form[“ddlCounty”] in the server side handler to get the value.

Hope this helps!

Tags:

asp.net

Updated Scruffy BlogEngine.NET Theme

by jrummell 31. May 2010 14:47

There seems to be a lack of up to date themes in the BlogEngine.NET community lately. Initially, there were a ton of great themes available from very talented designers. But the themes haven’t kept up with new releases of the blog engine. I used a few themes here on older versions, but alas, it seems that all of the cool themes look bad in BlogEngine.NET 1.5+ so now I’m back to the Standard theme.

I’m a much better developer than designer, but I was able to update the Scruffy theme for version 1.6.1.0, which includes reCAPTCHA support. All that I had to do was replace CommentView.ascx and the comment related CSS in style.css with what’s in the Standard theme. I did this for my wife since she can’t blog without Scruffy. If I find the time, I may try updating a few more. I’m open to suggestions.

Tags: ,

asp.net

WinHost, Sub-Domains, and IIS 7 URL Rewrite

by jrummell 24. April 2010 18:31

I switched my web host from GoDaddy to WinHost this week. I only I ran into two issues. The first was how WinHost handles sub-domains. GoDaddy’s sub-domains point to a sub folder of the same name, e.g. http://sub.example.com points to \sub. You can then access sub-domain content from http://sub.example.com/abc or http://sub.example.com/sub/abc. There is no way that I could find to enforce either /abc or /sub/abc and often after arriving to the site via /abc you would be linked to /sub/abc.

WinHost doesn’t automatically redirect your sub-domains; they leave that up to the customer. They suggest using custom code on the default page to redirect based on the server name Request variable. This would be fine if users always went to that page first, but what if you already have links to http://sub.example.com/somepage.aspx? They will be broken, of course!

WinHost allows you to manage IIS7 through the Remote IIS Manager and includes the URL Rewrite Module, so I thought I’d give that a try. My first attempt was to rewrite http://john.rummell.info to /john using the information provided by Ben Powell on his blog:

<rule name="john.rummell.info" stopProcessing="true">
  <match url="(.*)" />
  <conditions>
    <add input="{HTTP_HOST}" pattern="^john.rummell.info$" />
  </conditions>
  <action type="Rewrite" url="john/{R:1}" />
</rule>

This works if you use only absolute paths, but it broke all of my relative css links and form actions. This is because even though you never see /john in the url, Request.ApplicationPath is still /john/somepage.aspx. I decided that this wasn’t right path to take. Then I tried a similar rule that would redirect instead of rewrite:

<rule name="john.rummell.info" stopProcessing="true">
    <match url="(.*)" />
    <conditions logicalGrouping="MatchAll">
        <add input="{HTTP_HOST}" pattern="^john.rummell.info$" />
        <add input="{REQUEST_URI}" negate="true" pattern="/john" />
    </conditions>
    <action type="Redirect" url="http://john.rummell.info/john/{R:1}" />
</rule>

This will redirect all requests from http://john.rummell.info to http://john.rummell.info/john. The first input is the same as the rewrite rule. The second input makes sure that the path doesn’t already start with /john. This prevents it from redirecting infinitely (/john/john/john/john etc). Its not ideal since the sub domain appears in the url twice, but at least its guaranteed to always be that way, unlike GoDaddy.

The other issue I had was that using the Facebook Developer Toolkit and reCaptcha both threw a SecurityException. This was an easy fix once I realized that I could change the trust level in IIS. Changing it to High fixed the problem. So far I’ve been very pleased!

So far I’ve been very pleased with WinHost. They have reasonable pricing, their support team has been quick to respond, and they allow you to manage your site with IIS Manager and SQL Server Management Studio. Good bye GoDaddy!

Tags: ,

asp.net

Google Chrome has Extensions

by jrummell 26. January 2010 18:14

I’ve been using Google Chrome a lot more for casual browsing lately. I continue to use Firefox with Firebug for web development tasks, but for social networking and email I use Chrome. Perhaps its because I have loaded down Firefox with too many extensions, but I can’t get over how much faster Chrome is. And the latest beta (version 4) now supports extensions. So far I’ve added five extensions and I haven’t noticed a difference in speed.

My Extensions

One Number by megazzt

Check GMail, Google Reader, Google Voice, and Google Wave. Four sources, one number.
https://chrome.google.com/extensions/detail/cfkohgkpafhkpdcnfadadcibfboapggi 

Send from Gmail (by Google) by extensions.for.gapps

Makes Gmail your default email application and provides a button to compose a Gmail message to quickly share a link via email
https://chrome.google.com/extensions/detail/pgphcomnlaojlmmcjmiddhdapjpbgeoc

Shareaholic for Google Chrome by meattle

Share, save or email any web page with your friends right from the page you are on using Twitter, Facebook, GMail, and many more!
https://chrome.google.com/extensions/detail/kbmipnjdeifmobkhgogdnomkihhgojep

Xmarks Bookmarks Sync by Xmarks Inc.

Backup and sync your bookmarks across computers and browsers. Xmarks is also available for Firefox, Safari and IE.
https://chrome.google.com/extensions/detail/ajpgkpeckebdhofmmjfgcjjiiejpodla

RSS Subscription Extension (by Google) by finnur@chromium.org

Adds one-click subscription to your toolbar.
https://chrome.google.com/extensions/detail/nlbjncdgjeocebhnmkbbbdekmmmcbfjd

Tags:

Steam Achievements: A Facebook app built with WCF and jQuery

by jrummell 23. December 2009 23:49

I’ve been wanting to create a Facebook app for a while now, but I didn’t have time or a need to fill. I took some time off over the last week and I was able to come up with both. I’m avid PC gamer and most of my games are on Valve’s Steam platform. Steam has achievements for some of its biggest newer games like Left 4 Dead and Team Fortress 2. They are much like the Xbox 360 and PS3 achievements. But unlike the Xbox and PS3, there wasn’t a Facebook app that notifies your friends with your latest achievements. There was an app that worked great for a while back, but its now broken (and still using the steamachievements url suffix!).

I’ve created an app at http://www.facebook.com/apps/application.php?id=211407042025.

I first tried an FBML Canvas app built with ASP.NET MVC and the MS Facebook SDK, but I couldn’t find a complete working example. Then I tried FBML with ASP.NET WebForms (traditional ASP.NET) but testing was super annoying since it had to be run in the context of Facebook and I couldn’t get FBJS Ajax to work. Then finally, I tried an IFrame Canvas app built with ASP.NET WebForms. I could now test easier and use jQuery instead of FBJS! For more information, see this wiki topic on IFrame vs. FBML Canvas apps.

Now that I was in familiar territory, I threw a wrench in the mix by using WCF web services instead of asmx. Not that WCF is better/worse than asmx, I had just never used them before. I had some help from Rick Strahl’s blog post called jQuery AJAX calls to a WCF REST Service. This is was no easy task for me, so I’ll list all of the details here in case anyone else is struggling. First, here is the service interface:

[ServiceContract]
public interface IAchievementService
{
    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    List<Achievement> GetAchievements(string steamUserId, int gameId);

    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    List<Game> GetGames();

    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    bool UpdateAchievements(string steamUserId);

    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    bool UpdateSteamUserId(long facebookUserId, string steamUserId);

    [OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
    bool PublishLatestAchievements(long facebookUserId, string steamUserId);
}

Note the WCF attributes: ServiceContract and OperationContract. These kind of correspond to WebService and WebMethod in an asmx web service. I say kind of because a WCF service doesn’t have to be a web service, but for this example it is. The WebInvoke attribute tells ASP.NET how the client will interact with the service. The method is POST, and both the request and the response will be json. The WrappedRequest BodyStyle is what allows you to send a json JavaScript object to a service method that gets de-serialized into parameters. If you were to omit this, you’d have to create a class for your parameters and change the method signature to accept one instance of that class. Another thing to note is that I’m returning List<T> instead of IEnumerable<T>. This is because there seems to be a serialization bug with WCF and IEnumerable<T>.

The implementation of IAchievementService is very straight forward. It is simply hands all of the heavy lifting to a manager class that uses LINQ to SQL to communicate with the database. The next part is the WCF configuration in web.config.

  <system.serviceModel>
    <services>
      <service behaviorConfiguration="SteamAchievements.Services.AchievementServiceBehavior" name="SteamAchievements.Services.AchievementService">
        <endpoint address="" binding="webHttpBinding" contract="SteamAchievements.Services.IAchievementService" behaviorConfiguration="SteamAchievements.Services.AchievementServiceBehavior" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="SteamAchievements.Services.AchievementServiceBehavior">
          <serviceMetadata httpGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="SteamAchievements.Services.AchievementServiceBehavior">
          <webHttp />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="false">
      <baseAddressPrefixFilters>
        <add prefix="http://www.rummell.info/" />
      </baseAddressPrefixFilters>
    </serviceHostingEnvironment>
  </system.serviceModel>

This is fairly standard except for a few things. I’m using the webHttp endpoint behavior to enable AJAX and I also had to add a baseAddressPrefixFilter to get it working on my web host.

Finally, here is the JavaScript that calls the WCF service methods:

var parameters = { "steamUserId": steamUserId, "gameId": gameId };
callAjax("GetAchievements", parameters, ondone);

function callAjax(method, query, ondone)
{
    var onerror = function(m)
    {
        $("#log").text(m.Message).show();
    };

    $.ajax({
        url: _serviceBase + method,
        data: JSON.stringify(query),
        type: "POST",
        processData: true,
        contentType: "application/json",
        timeout: 10000,
        dataType: "json",
        success: ondone,
        error: function(xhr)
        {
            if (!onerror)
            {
                return;
            }

            if (xhr.responseText)
            {
                try
                {
                    var err = JSON.parse(xhr.responseText);
                    if (err)
                    {
                        onerror(err);
                    }
                    else
                    {
                        onerror({ Message: "Unknown server error." });
                    }
                }
                catch (e)
                {
                    onerror({ Message: e.toString() });
                }
            }
            return;
        }
    });
}

This is basically the same snippet from Rick Strahl (see link above) that I’ve modified slightly to fit my application.

One final note on using the Facebook API from the context of a web service. The PublishLatestAchievements service method uses the API to publish achievements to the user’s profile. Since its outside the scope of a page, you can’t use Master.Api. To get around this, you’ll need to create an instance of CanvasSession:

string appKey = WebConfigurationManager.AppSettings["APIKey"];
string appSecret = WebConfigurationManager.AppSettings["Secret"];
List<Enums.ExtendedPermissions> permissions =
    new List<Enums.ExtendedPermissions> {Enums.ExtendedPermissions.publish_stream};
CanvasSession session = new IFrameCanvasSession(appKey, appSecret, permissions, false);
Api api = new Api(session);

Then you can publish to the stream:

api.Stream.Publish(description, attachment, links, null, facebookUserId);

Most samples don’t include the uid parameter (facebookUserId), but its required here since HttpSession is unavailable.

If you’d like to see more, take a look at the Google Code project.

Tags: , ,

asp.net

SQL Server Management Studio and TortoiseSVN

by jrummell 18. November 2009 23:23

At work we maintain a few SQL Server Management Studio (SSMS) solutions for our SQL views, stored procedures and functions. We also use TortoiseSVN for source control. Unfortunately, there are no SVN add-ins for SSMS and the ones for Visual Studio don’t work (VisualSVN, AnkhSVN). Its a bit frustrating that SSMS is built on the same technology as Visual Studio, but lacks so many of the features that I’ve grown accustomed to, such as the Add-in Manager.

Red Gate, however, is currently working on a add-in called SQL Source Control with a planned release in 2010. But what to do until then? Well, there is one officially supported point of extensibility in SSMS: External Tools. Here are a few that I’ve been using with TortoiseSVN lately:

Title: SVN Commit
Command: C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe
Arguments: /Command:commit /path:"$(SolutionDir)
Initial directory: $(SolutionDir)

Title: SVN Update
Command: C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe
Arguments: /Command:update /path:"$(SolutionDir)"
Initial directory: $(SolutionDir)

Title: SVN Log (Solution)
Command: C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe
Arguments: /Command:log /path:"$(SolutionDir)"
Initial directory: $(SolutionDir)

Title: SVN Log (Current Item)
Command: C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe
Arguments: /Command:log /path:"$(ItemFileName)$(ItemExt)"
Initial directory: $(ItemDir)

Title: SVN Diff
Command: C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe
Arguments: /Command:diff /path:"$(ItemFileName)$(ItemExt)"
Initial directory: $(ItemDir)

Tags: , ,

sql

Mocking Controller.User

by jrummell 17. September 2009 21:26

I’m currently working on my first ASP.NET MVC project. Naturally, I’m writing a good number of unit tests. I ran into a problem tonight with mocking Controller.User. Thankfully, someone at Stack Overflow had already asked a question about this. I took Bruno Reis’ answer:

var principal = new Moq.Mock<IPrincipal>();
// ... mock IPrincipal as you wish

var httpContext = new Moq.Mock<HttpContextBase>();
httpContext.Setup(x => x.User).Returns(principal.Object);
// ... mock other httpContext's properties, methods, as needed

var reqContext = new RequestContext(httpContext.Object, new RouteData());

// now create the controller:
var controller = new MyController();
controller.ControllerContext =
    new ControllerContext(reqContext, controller);

and I rewrote it using NUnit.Mocks and wrapped it into an implementation of HttpContextBase:

/// <summary>
/// A mock <see cref="HttpContextBase"/> that implements <see cref="HttpContextBase.User"/>.
/// </summary>
public class MockHttpContext : HttpContextBase
{
    private IPrincipal _user;

    public MockHttpContext()
    {
        DynamicMock identity = new DynamicMock(typeof (IIdentity));
        identity.ExpectAndReturn("get_Name", "testUser");

        DynamicMock user = new DynamicMock(typeof (IPrincipal));
        user.ExpectAndReturn("get_Identity", identity.MockInstance);

        _user = (IPrincipal) user.MockInstance;
    }

    public override IPrincipal User
    {
        get { return _user; }
        set { _user = value; }
    }
}

Now mocking Controller.User is as easy as this:

// create an instance of RequestContext using MockHttpContext.
RequestContext requestContext =
    new RequestContext(new MockHttpContext(), new RouteData());

// initialize the controller's ControllerContext
_controller.ControllerContext = new ControllerContext(requestContext, _controller);

Tags: ,

asp.net

Apply the same CSS class to all validators in a web project

by jrummell 16. August 2009 14:33

I recently had to add a CSS class to all validators in an ASP.NET web application.  I started with the theme’s skin file:

<asp:CompareValidator runat="server" 
    CssClass="error" />
<asp:CustomValidator runat="server" 
    CssClass="error" />
<asp:RequiredFieldValidator runat="server" 
    CssClass="error" />
<belCommon:ZipCodeValidator runat="server" 
    CssClass="error" />
<belCommon:PhoneNumberValidator runat="server" 
    CssClass="error" />

But what if I decide to use another validator down the road? I would have to remember to add it to the skin. Knowing that I was bound to forget, I sought out another method. After doing some digging, I found that ASP.NET generates a JavaScript variable called Page_Validators. This is an array of all the validator span elements on the current page. Now that I have access to the spans, I could write a script in the site’s Master Page to apply the class:

if (Page_Validators != null)
{
    for (i = 0; i < Page_Validators.length; i++)
    {
        Page_Validators[i].className = "error";
    }
}

To have it run when the page is loaded, I added it as an Sys.Application.init handler:

Sys.Application.add_init(function(sender, args)
{
    if (Page_Validators != null)
    {
        for (i = 0; i < Page_Validators.length; i++)
        {
            Page_Validators[i].className = "error";
        }
    }
});

You could also use jQuery’s document.ready handler:

$(document).ready(function()
{
    if (Page_Validators != null)
    {
        for (i = 0; i < Page_Validators.length; i++)
        {
            Page_Validators[i].className = "error";
        }
    }
});

Tags: ,

asp.net

xVal with WebForms Part 2

by jrummell 12. August 2009 22:17

Since my last post, I’ve completely rethought and re-implemented my take on xVal for WebForms. If you’re not familiar with xVal, stop now and read the tutorial. Now that you’re back, lets talk about xVal and WebForms.

Model

This is the model we’ll be using (you should recognize it from the xVal tutorial):

public class Booking
{
    [Required]
    [StringLength(15)]
    public string ClientName { get; set; }

    [Range(1, 20)]
    public int NumberOfGuests { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime ArrivalDate { get; set; }
}

Form

And here is the form:

<asp:ValidationSummary ID="valSummary" runat="server" />
<label for="txtClientName">
    Your name:</label>
<asp:TextBox ID="txtClientName" runat="server" />
<label for="txtNumberOfGuests">
    Number of guests:</label>
<asp:TextBox ID="txtNumberOfGuests" runat="server" />
<label for="txtArrivalDate">
    Arrival date:</label>
<asp:TextBox ID="txtArrivalDate" runat="server" />
<asp:Button ID="btnSubmit" runat="server" Text="Submit" OnClick="btnSubmit_Click" />

ModelValidator

My first try at validation was adding a validator control for each input field. After playing with it a bit, I decided that it would be better to have one validator for the entire model. This control defines the model’s type (ModelType), and then maps each property (PropertyName) to an input control (ControlToValidate).

<val:ModelValidator ID="valBooking" runat="server" ModelType="xVal.WebForms.Demo.Booking, xVal.WebForms.Demo">
    <ModelProperties>
        <val:ModelProperty PropertyName="ClientName" ControlToValidate="txtClientName" />
        <val:ModelProperty PropertyName="NumberOfGuests" ControlToValidate="txtNumberOfGuests" />
        <val:ModelProperty PropertyName="ArrivalDate" ControlToValidate="txtArrivalDate" />
    </ModelProperties>
</val:ModelValidator>

ControlToValidate

The biggest challenge was figuring out how to reference the input controls by given ID instead of by the elementPrefix + PropertyName convention. In other words, MVC xVal assumes that your ClientName input control ID is booking.ClientName, where booking is the elementPrefix and ClientName is the name of the property. This doesn’t work out so well with web forms and generated IDs. I got around this with the ModelProperties collection of ModelValidator. Then I updated the json formatted rule script to include each property’s control ID.

Complete Source and Demo

Get the complete source (with Bookings demo) at the xVal.WebForms project page on CodePlex.

Tags: , ,

asp.net

xVal with WebForms

by jrummell 15. July 2009 22:32

Update: See xVal with WebForms Part 2 for a better implementation.

What is xVal and why would anyone want to use it?

xVal is a validation framework for ASP.NET MVC applications. It makes it easy to link up your choice of server-side validation mechanism with your choice of client-side validation library, neatly fitting both into ASP.NET MVC architecture and conventions.

See the CodePlex page for more information.

Basically, what xVal does, is take your validation rules and perform server and client side validation based on those rules. That means you don’t have to duplicate model validation at the page level. Now you’re probably thinking, “Isn’t it for MVC?”.  It is. But I, and at least two others, would like to take advantage of xVal’s features in traditional ASP.NET WebForm projects.

Getting it to work with WebForms

I finally found some time last night to see what it would take to get xVal working in an ASP.NET Web Application Project.  After a few hours I had something. I only needed to add two classes on top of xVal, DataAnnotationsValidationRunner and ModelValidator.

public static class DataAnnotationsValidationRunner
{
    public static IEnumerable<ErrorInfo> GetErrors(object instance, string propertyName)
    {
        return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
               from attribute in prop.Attributes.OfType<ValidationAttribute>()
               where prop.Name == propertyName && !attribute.IsValid(prop.GetValue(instance))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
    }
}

This is based on the implementation in the xVal demo. I added a second parameter to GetErrors() that allows the runner to check a specific property.

 

public class ModelValidator : BaseValidator
{
    private ValidationInfo _validationInfo;

    public string ModelType
    {
        get { return (string) ViewState["ModelType"]; }
        set { ViewState["ModelType"] = value; }
    }

    public string ModelProperty
    {
        get { return (string) ViewState["ModelProperty"]; }
        set { ViewState["ModelProperty"] = value; }
    }

    protected override bool EvaluateIsValid()
    {
        Type type = Type.GetType(ModelType);

        object model = Activator.CreateInstance(type);

        IEnumerable<ErrorInfo> errors = DataAnnotationsValidationRunner.GetErrors(model, ModelProperty);

        StringBuilder errorBuilder = new StringBuilder();
        foreach (ErrorInfo error in errors)
        {
            errorBuilder.AppendLine(error.ErrorMessage);
        }

        ErrorMessage = errorBuilder.ToString();

        return ErrorMessage.Length > 0;
    }

    protected override void Render(HtmlTextWriter writer)
    {
        Type type = Type.GetType(ModelType);
        _validationInfo = new ValidationInfo(ActiveRuleProviders.GetRulesForType(type), String.Empty);

        writer.Write(_validationInfo.ToString());
    }
}

This is the ASP.NET WebForms version of <%= Html.ClientSideValidation<Booking>("booking") %>, an implementation of BaseValidator. It uses ValidationInfo for rendering the client validation script and DataAnnotationsValidationRunner for the server side validation.

You can use it like this:

public class Customer
{
    [Required, StringLength(20)]
    public string Name { get; set; }
}
<script type="text/javascript" src="xVal.AspNetNative.js"></script>
<div>
    <asp:Label ID="lblName" runat="server" AssociatedControlID="Name">Customer Name:</asp:Label>
    <asp:TextBox ID="Name" runat="server" />
    <val:ModelValidator ID="validator" runat="server" ModelType="xVal.WebForms.Test.Customer, xVal.WebForms.Test"
        ModelProperty="Name" ControlToValidate="Name" />
</div>
<asp:Button ID="btnSubmit" runat="server" Text="Submit" />

There are a few things to note here:

  • The TextBox ID must be the same as the model’s property name. This is because the xVal javascript is expecting the control’s ID to be PropertyName or prefix.PropertyName (MVC naming conventions). This could probably be fixed by modifying the client side plugins.
  • The ControlToValidate property on ModelValidator doesn’t do anything, but it’s required by any implementation of BaseValidator (it will throw an exception if you omit it). This could probably be avoided by having ModelValidator inherit from Control and implement IValidator instead.
  • xVal depends on System.Web.Mvc. It uses the TagBuilder and ModelState classes in a few places. There’s really now way around this without refactoring the MVC specific stuff into a separate assembly.

I’ll post an update when I have a chance to work on the first two items.

Tags: ,

asp.net

Powered by BlogEngine.NET 1.6.1.0
Theme by Mads Kristensen | Modified by Mooglegiant

About the author

John is a .Net Web Developer for a manufacturing company in Ohio. In his free time he enjoys web development, the outdoors, and spending time with his wife.

Ads By Google

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in  anyway.

© Copyright 2010