Manage Multiple Config Files in Visual Studio

This post is a quick tip on how to manage multiple app.config or web.config files in Visual Studio. Consider the typical development environment for a project where we have development / QA / Production. Each environment has its own database and other settings. We need a way to be able to run the app but use the pertinent config settings. This technique allows for us to create config files for each environment but still have a clean way to use the respective config.

In short it comes down to leveraging pre-compile events and the configuration manager within Visual Studio.

Check it out as it is a real nice time saver.

http://www.hanselman.com/blog/CommentView.aspx?guid=93bfa4b3-44cd-4681-b70e-f4a2b0386466

JQuery UI and CDN

Stumbled across this this article recently. Apparently Google is not only hosting the JQuery libraries but now also the JQuery UI libraries and CSS files.

http://encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/

SQL – The Constant in a Changing World

I was recently reading Sam Gentile’s Blog focusing on data.

http://samgentile.com/Web/linq/focusing-on-data-ndash-try-3/

This got me to thinking… SQL seems to be the one constant in an ever evolving technology space. I have seen and used all sorts of languages over my career. The languages all had features that seemed to “wrap” SQL. Similarly we see frameworks introduced that try to make our access to data simpler. Things like LINQ to SQL, ORM tools etc.

So how is it that SQL has stayed consistent all these years? And, if it is consistent, why do we try to keep solving the data access problem? Is it even a problem?

Maybe it’s due to the fact that the problem domain that SQL solves is bounded so there is no need for evolution. I do know this..there has been many a time I have been saved by keeping data access in the database. Maybe saved isn’t the right word but rather able to support change more easily. I still am a big fan of stored procedures. I like the ORM tools that can generate code for my stored procedures.

How about everyone else?

HTTP Request LifeCyle

Someone asked me the other day. “What the heck happens after I type in a web address in my browser and hit submit?”

My initial thought was “yeah, good question”. We take this for granted so much; It is so common place that we forget about all the details under the hood. Kind of like when I turn on the shower in the morning how does the water come out? Where does the water come from.

Now, I know what is happening under the hood but went searching around a bit to just refresh my mental model.

I found this little excerpt on superuser.com (side note..that stackoverlow is such a great resource! Man I wish I came up with that idea.)

I thought this excerpt did a nice job in explaining the entire transaction in layman’s terms. In any case here it is. A nice refreshing look at something we take for granted.

Browser: “Ok, so, I have a user requesting this address: www.cnn.com. I figure since there are no slashes or anything, this is a direct request of a main page. There was also no protocol or port defined, so I’ll assume it’s HTTP and going to port 80… oh well, first things first. Hey DNS, pal, wake up! Where is this www.cnn.com hiding at?”

DNS: “Right… wait a sec, I’ll ask the ISP servers. Ok, it looks like 157.166.226.25.”

Browser: “Ok. Internet Protocol Suite, your turn! Call 157.166.226.25, please. Send them this HTTP header. It’s asking for the basic structure and content of their main page so I know what else to fetch… oh well, not that you’d care about this I guess. “

TCP/IP: “What do you mean my turn? Like I wasn’t just working my back off right there for the DNS? God, what does it take to get a bit of appreciation here…”

Browser: …

TCP/IP: “Yeah, yeah… Connecting… I’ll just ask the gateway to forward it. You know, it isn’t all that easy, I’ll have to divide your pretty request there into multiple parts so it reaches the end, and assemble any stuff they send back from all the thousands of packages I get… ah, right, you don’t care. Figures.”

Meanwhile, at the CNN headquarters, a message finally ends up at the door of the Web Server.

CNN Web Server: “Nzhôô! A customer! He wants news! The Front Page! How about it?”

CNN Server Side Script Engine: “Right, will do! Front page, right?”

CNN Database Server: “Yey! Work for me! What content do you need?”

CNN Server Side Script Engine: “… um, sorry DB, I have a copy of front page right here in my cache, no need to compile anything. But hey, take this user ID and store it, I’ll send it to the customer too, so we know who we’re talking to later on.”

CNN Database Server: “Yey!”

Back at the user’s computer…

TCP/IP: “Ooookay, here comes the reply. Oh boy, why do I have a feeling this’ll be a big one…”

Browser: “Uh, wow… this has all sorts of javascript code… bunch of images, couple of forms… Right, this’ll take a while to render. Better get to it. Hey, IP system, there’s a bunch more stuff you’ll need to get. Let’s see I need a few stylesheets from i.cdn.turner.com – via HTTP and ask for the file /cnn/.element/css/2.0/common.css. And then get some of those scripts at i.cdn.turner.com too, I’m counting six so far…”

TCP/IP: “I get the picture. Just give me the server addresses and all that. And wrap that file stuff within the HTTP request, I don’t want to deal with it.”

DNS: “Checking the i.cdn.turner.com… hey, bit of trivia, it’s actually called cdn.cnn.com.c.footprint.net. IP is 4.23.41.126″

Browser: “Sure, sure… wait a sec, this’ll take a few nsec to process, I’m trying to understand all this script…”

TCP/IP: “Hey, here’s the CSS you asked for. Oh, and… yeah, those additional scripts also just came back.”

Browser: “Whew, there’s more… some sort of video ad!”

TCP/IP: “Oh boy, what fun that sounds like…”

Browser: “There’s all sorts of images too! And this CSS looks a bit nasty… right, so if that part goes there, and has this line at the top… how on earth would that fit anymore… no, I’ll have to stretch this a bit to make it… Oh, but that other CSS file overrides that rule… Well, this one ain’t going to be an easy piece to render, that’s for sure!”

TCP/IP: “Ok, ok, stop distracting me for a sec, there’s a lot to do here still.”

Browser: “User, here’s a small progress report for you. Sorry, this all might take a few secs, there’s like 140 different elements to load, and going at 16 so far.”

One or two seconds later…

TCP/IP: “Ok, that should be all. Hey, listen… sorry I snapped at you earlier, you managing there? This sure seems like quite the load for you too.”

Browser: “Phew, yeah, it’s all these websites nowdays, they sure don’t make it easy for you. Well, I’ll manage. It’s what I’m here for.”

TCP/IP: “I guess it’s quite heavy for all of us these days… oh, stop gloating there DNS!”

Browser: “Hey user! The website’s ready – go get your news!”

WPF vs. WPF XBAP vs. Silverlight vs. ASP.NET MVC

A nice small comparison of when to use WPF / WPF XBAP / Silverlight / ASP.NET

WPF vs WPF XBAP vs Silverlight

Shared via AddThis

SQL Server Grant User Rights

This is a small proc that will parse out the grant statements on user defined stored procedures, views and tables. This is a quick way to give a user rights in one quick sweep of the db. It can achieve this by leveraging the information stored in the meta data tables with SQL server.

Example syntax:

exec GrantRights ‘SomeUser’.

Here is the proc.

ALTER PROCEDURE [dbo].[GrantRights]
@username varchar(100)
AS
BEGIN
DECLARE @sqlstatement varchar(1000)
DECLARE @procname varchar(1000)
DECLARE @tablename varchar(1000)

–DO THE STORED PROCEDURES
declare proccursor cursor forward_only
for Select name from sysobjects WHERE xtype = ‘P’
open proccursor

while (1=1)
begin
fetch next from proccursor into @procname
if @@fetch_status 0
break;
SET @sqlstatement = ‘GRANT CONTROL, EXECUTE, TAKE OWNERSHIP, VIEW DEFINITION ON ‘ + @procname + ‘ TO ‘ + @username
print @sqlstatement
exec ( @sqlstatement )
end
close proccursor
deallocate proccursor

—NOW GO AND DO THE TABLES
declare tablecursor cursor forward_only
for Select name from sysobjects WHERE xtype = ‘U’
open tablecursor

while (1=1)
begin
fetch next from tablecursor into @tablename
if @@fetch_status 0
break;
SET @sqlstatement = ‘GRANT INSERT, UPDATE, DELETE, SELECT ON ‘ + @tablename + ‘ TO ‘ + @username
print @sqlstatement
exec ( @sqlstatement )
end
close tablecursor
deallocate tablecursor

END

ASP.NET MVC and JQUERY Validation Part II

This is a continuation of an earlier post about a nice framework that ties JQuery validation and the MS Validation Application block

See the solution here

http://www.codeplex.com/aspmvcvalidation.

Now what I wanted to do was extend this framework so I could generate the required client side regex validation methods based upon the tagging of class attributes.  Do note that the currenframework does support RegExValidators but with a dependency.  The dependency is that there is an external js file which defines the custom client side regex validation methods.  For example a method defined in an external file like:

$.validator.addMethod('ValidZip',
function (value) {
return /^((\d{5}-\d{4})|(\d{5})|([A-Z]\d[A-Z]\s\d[A-Z]\d))$/.test(value);},
'Please enter a valid.');

When we have an external file that defines this custom method,  we can tag our attributes like the following and the framework will work.

[RegularExpressionValidatorAttribute(
MessageTemplate = "Invalid zip code",
ClientFunctionName = "ValidZip",
Pattern = "Insert your Regex here")]
public string Zip { get; set; }

When this tag runs through the framework it will generate the following code for us.

$("#Zip").rules("add", {
	ValidZip: true
	messages: {
		ValidZip: "Please enter a valid zip."
	}
});
^((\d{5}-\d{4})|(\d{5})|([A-Z]\d[A-Z]\s\d[A-Z]\d))

This is all well and good but it assumes we have a custom method defined in an external file for the ValidZip RegEx check.

What I wanted to do was generate the methods and the rules based on the tag.  The reason being is we already have the regex in the attribute tag and the JQuery code required to implement the custom client  method is predictable.  The benefit of generating the custom method is that I don't need to concern myself with keeping an external js file in sync with my attribute tags.  Simply put the framework will do that for me.

In order to accomplish this I created another validator called ClientRegExValidator.  What I do is decorate my attribute with something like the following

[ClientRegExValidator(@"/^[a-zA-Z0-9]+$/", "Password must contain only letters or numbers", "Password")]
public string Password { get; set; }

From here both the client side method and the rule definition will be generated by the JQueryGenerator class.  Here is the changes I made to the framework.

ClientRegExValidatorAttribute class.

using System;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

namespace Mvc.Validation.Validators
{
[AttributeUsage(AttributeTargets.Property
| AttributeTargets.Field
| AttributeTargets.Method
| AttributeTargets.Class
| AttributeTargets.Parameter,
AllowMultiple = false,
Inherited = false)]
public sealed class ClientRegExValidatorAttribute : ValueValidatorAttribute
{
string _ClientPattern;
string _FieldName;
string _MessageTemplate;

public ClientRegExValidatorAttribute(string MessageTemplate)
: base()
{
this._MessageTemplate = MessageTemplate;
}

public ClientRegExValidatorAttribute(string Pattern, string MessageTemplate, string FieldName)
: base()
{

this._FieldName = FieldName;
this._ClientPattern = Pattern;
this._MessageTemplate = MessageTemplate;
}

public string ClientPattern {
get
{
return this._ClientPattern;
}
set
{
}
}

public string FieldName {
get
{
return this._FieldName;
}
set
{
}
}

public string MessageTemplate
{
get
{
return this._MessageTemplate;
}
set
{
}
}

protected override Validator DoCreateValidator(Type targetType)
{
return new ClientRegExValidator( this._ClientPattern,this._MessageTemplate, this._FieldName);
}
}
}

ClientRegExValidator class

using System;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

namespace Mvc.Validation.Validators
{
    ///
    /// Logs an error if the string to validate is  or empty.
    ///
    public class ClientRegExValidator : ValueValidator
    {
        private string _Pattern;
        private string _FieldName;
        public ClientRegExValidator()
			: this(null,null,null)
		{ }

         public ClientRegExValidator(string Pattern, string messageTemplate, string FieldName)
             : base(messageTemplate, null, false)
		{
            this._FieldName = FieldName;
            this._Pattern = Pattern;
         }

		protected override void DoValidate(object objectToValidate,
			object currentTarget,
			string key,
			ValidationResults validationResults)
        {
            string stringValue = null;
            if (objectToValidate != null && !(objectToValidate is string))
            {
                stringValue = Convert.ToString(objectToValidate);
            }
            else
            {
                stringValue = (string)objectToValidate;
            }

		}

        protected override string DefaultNonNegatedMessageTemplate
        {
            get { return "Failed validation"; }
        }

        protected override string DefaultNegatedMessageTemplate
        {
            get { return null; }
        }
    }
}

JQueryCodeGenerator class

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using Mvc.Validation.Validators;

namespace Mvc.Validation
{
    ///
    /// Class to generate corresponding client-side validation code for an VAB-annotated
    /// type.
    ///
    /// The generated JavaScript code targets the jQuery validation plugin:
    /// http://bassistance.de/jquery-plugins/jquery-plugin-validation/.
    ///
    internal sealed class JQueryCodeGenerator : IValidationCodeGenerator
    {
        #region Implementing IValidationCodeGenerator
            StringBuilder customFunctions = new StringBuilder();

        public string GenerateCode(string formName, bool ignoreMissingElements, string FunctionName)
        {
            var body = GenerateScriptBody(ignoreMissingElements);
            return GenerateScriptWrapper(body, formName, FunctionName);
        }
        #endregion

        #region Private Members
        ///
        /// Generates the body of the script by inspecting all properties and their
        /// attributes.
        ///
        private static string GenerateScriptBody(bool ignoreMissingElements)
        {
            var script = new StringBuilder();
            var props = TypeDescriptor.GetProperties(typeof(T));
            var functions = new StringBuilder();
            foreach (PropertyDescriptor prop in props)
            {
                var rules = GetRulesOfProperty(prop);
                var propertyScript = GenerateScriptForRules(prop, rules, ignoreMissingElements);
                script.Append(propertyScript).AppendLine().AppendLine();
                var functionScript = GenerateFunctionsForRules(prop, rules, ignoreMissingElements);
                functions.Append(functionScript);
            }
            script.Append(functions.ToString());
            return script.ToString();
        }

         private static string GetCustomFunctions(BaseValidationAttribute rule)
         {

             StringBuilder aFunction = new StringBuilder();
             if (rule is ClientRegExValidatorAttribute)
             {
                 ClientRegExValidatorAttribute localrule;
                 localrule = (ClientRegExValidatorAttribute)rule;
                 aFunction.Append("$.validator.addMethod('" + localrule.FieldName + "', function (value) { ");
                 aFunction.Append("return " + localrule.ClientPattern + ".test(value); ");
                 aFunction.Append("}, '" + rule.MessageTemplate + "');");
                 return aFunction.ToString();
             }
             return "";
             //throw new NotSupportedException("Not supported attribute of type " + rule.GetType());
         }
        ///
        /// Generates the wrapper and initialization JS code for the validation.
        ///
        private static string GenerateScriptWrapper(string bodyScript, string formName, string FunctionName)
        {
            var script = new StringBuilder();

            script.Append("function " + FunctionName + "(){ \n");
            if (!string.IsNullOrEmpty(formName))
            {
                //script.AppendFormat("$(\"#{0}\").validate( { errorLabelContainer: $(\"#{0}\" div.error });", formName).AppendLine();
                script.Append("$(\"#" + formName + "\").validate( { errorLabelContainer: \"#" + formName + "_ErrorMessages\", wrapper: \"li\" });\n");
            }
            script.AppendLine().Append(bodyScript);
            script.Append("}\n");
            return string.Format("",
                script, Environment.NewLine);
        }

        ///
        /// Retrieves all the validation attributes for the specified
        ///
        private static IList GetRulesOfProperty(MemberDescriptor prop)
        {
            return prop.Attributes.OfType().ToList();
        }

        ///
        /// Generates the validation script for the specified
        ///
        private static string GenerateFunctionsForRules(MemberDescriptor prop,
            IList rules, bool ignoreMissingElements)
        {
            if (rules.Count == 0)
                return string.Empty;

            var functionScripts = new StringBuilder();
            for (var i = 0; i < rules.Count; i++)
            {
                string aFunction = GetCustomFunctions(rules[i]);
                functionScripts.Append(aFunction);
                functionScripts.AppendLine();
            }
            return functionScripts.ToString();
        }

        ///
        /// Generates the validation script for the specified
        ///
        private static string GenerateScriptForRules(MemberDescriptor prop,
            IList rules, bool ignoreMissingElements)
        {
            if (rules.Count == 0)
                return string.Empty;

            var rulesStr = new StringBuilder();
            var messagesStr = new StringBuilder();

            if (ignoreMissingElements)
            {
                rulesStr.AppendFormat("if($(\"#{0}\").length > 0) {1}",
                               prop.Name, "{").AppendLine();
            }

            rulesStr.AppendFormat("$(\"#{0}\").rules(\"add\", {1}",
                               prop.Name, "{").AppendLine();

            messagesStr.AppendFormat("\tmessages: {0}", "{").AppendLine();

            for (var i = 0; i < rules.Count; i++)
            {
                ScriptInfo scriptInfo = GetRuleScript(rules[i]);
                rulesStr.AppendFormat("\t{0},", scriptInfo.RuleScript).AppendLine();
                messagesStr.AppendFormat("\t\t{0}", scriptInfo.MessageScript);

                if (i < rules.Count - 1)
                {
                    messagesStr.Append(",");
                }
                messagesStr.AppendLine();
            }
            messagesStr.AppendFormat("\t{0}", "}").AppendLine();
            rulesStr.Append(messagesStr.ToString());
            rulesStr.AppendFormat("{0});", "}");

            if (ignoreMissingElements)
            {
                rulesStr.AppendFormat("{0}", "}");
            }
            return rulesStr.ToString();
        }

        private static ScriptInfo GetRuleScript(BaseValidationAttribute rule)
        {

             if (rule is BaseValueValidatorAttribute)
            {
                var customRule = rule as BaseValueValidatorAttribute;
                var ruleName = customRule.ClientFunctionName;
                return new ScriptInfo
                {
                    RuleScript = ruleName + ": true",
                    MessageScript = string.Format("{0}: \"{1}\"", ruleName, customRule.FormattedMessageTemplate)
                };
            }

            if (rule is NotNullOrEmptyValidatorAttribute ||
                rule is NotNullValidatorAttribute)
            {
                return new ScriptInfo
                    {
                        RuleScript = "required: true",
                        MessageScript = string.Format("required: \"{0}\"", rule.MessageTemplate)
                    };
            }

            if (rule is EmailValidatorAttribute)
            {
                return new ScriptInfo
                {
                    RuleScript = "email: true",
                    MessageScript = string.Format("email: \"{0}\"", rule.MessageTemplate)
                };
            }

            if (rule is StringLengthValidatorAttribute)
            {
                // lowerBound and upperBound fields are not visible
                // thus need to use reflection to retrieve them
                var lowerBound = GetField(rule, "lowerBound");
                var upperBound = GetField(rule, "upperBound");

                // To understand the magic numbers 3 & 5, refer here
                // http://msdn.microsoft.com/en-us/library/cc511854.aspx
                var errorMessage = rule.MessageTemplate
                    .Replace("{3}", lowerBound.ToString())
                    .Replace("{5}", upperBound.ToString());

                return new ScriptInfo
                    {
                        RuleScript = string.Format("minlength: \"{0}\", maxlength : \"{1}\"",
                                                  lowerBound, upperBound),
                        MessageScript = string.Format("minlength: \"{0}\", maxlength : \"{0}\"",
                                                  errorMessage)
                    };
            }

            if (rule is RangeValidatorAttribute)
            {
                // lowerBound and upperBound fields are not visible
                // thus need to use reflection to retrieve them
                var lowerBound = GetField(rule, "lowerBound");
                var upperBound = GetField(rule, "upperBound");
                var errorMessage = rule.MessageTemplate;
                return new ScriptInfo
                    {
                        RuleScript = string.Format("rangelength: [{0}, {1}]",
                                                  lowerBound, upperBound),
                        MessageScript = string.Format("required: \"{0}\"", errorMessage)
                    };
            }

            if (rule is ClientRegExValidatorAttribute)
            {
                ClientRegExValidatorAttribute localrule;
                localrule = (ClientRegExValidatorAttribute)rule;

                return new ScriptInfo
                {
                    RuleScript = localrule.FieldName + ": true",
                    MessageScript = string.Format("{0}: \"{1}\"", localrule.FieldName, localrule.MessageTemplate)
                };

            }

            if (rule is PropertyComparisonValidatorAttribute)
            {
                var propToCompare = GetField(rule, "propertyToCompare");
                var propDescriptor = GetProperty(propToCompare);
                var propOperator = GetField(rule, "comparisonOperator");
                if (propOperator == ComparisonOperator.Equal)
                {
                    return new ScriptInfo
                    {
                        RuleScript = string.Format("equalTo: \"#{0}\"", GetClientID(propDescriptor)),
                        MessageScript = string.Format("equalTo: \"{0}\"", rule.MessageTemplate)
                    };
                }
            }

            throw new NotSupportedException("Not supported attribute of type " + rule.GetType());
        }

        private static T GetField(object obj, string fieldName)
        {
            var fieldInfo = obj.GetType().GetField(fieldName,
                BindingFlags.Instance | BindingFlags.NonPublic);
            return (T)fieldInfo.GetValue(obj);
        }

        private static PropertyDescriptor GetProperty(string name)
        {
            var props = TypeDescriptor.GetProperties(typeof(T));
            return props.Find(name, false);
        }

        private static string GetClientID(MemberDescriptor prop)
        {
            var clientIdList = prop.Attributes.OfType().ToList();
            return clientIdList.Count == 0
                ? prop.Name
                : clientIdList[0].ClientId;
        }

        private class ScriptInfo
        {
            public string RuleScript { get; set; }
            public string MessageScript { get; set; }
        }

        #endregion
    }
}

So with this technique we can get our custom RegEx client validation methods set up on the fly.  This removes the dependency on any external js files. Please note that this custom validator will only enforce client side validation. This is by design and the reason why I prefixed the validator name with "Client".

JQuery fadeIn/Out IE display issue

Have you run into an issue with text not displaying correctly in IE when using animation effects like fadeIn, fadeOut etc?

Here is a nice post on how to fix the issue. One thing to note, adding a background-color to the div is an easy fix and works well. Unfortunately sometimes we can’t add a background color if we make use of background images and other css items.

This little snippet will do the trick

$(‘#somediv’).fadeOut(’slow’, function() {

if(jQuery.browser.msie)

this.style.removeAttribute(‘filter’);

});

Here is the full post

http://blog.bmn.name/2008/03/jquery-fadeinfadeout-ie-cleartype-glitch/

ASP.NET MVC and JQUERY Validation

Recently I have been using this nice library that leverages the MS Validation Application Block along with the JQuery Validation plugin.

http://www.codeplex.com/aspmvcvalidation.  Really nice solution.

I wanted to extend this a bit by leveraging the remote feature in the JQuery validation block.  This was for cases like when we need to check  if a login is already taken.  In short I wanted a mechanism to emit out the JQuery validation that can call our server side validation method.  The JQuery validation plugin has a nice hook for us in the remote keyword.

To make this happen I added the following classes to the MVC.Validation Library.

using System;

using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

namespace Mvc.Validation.Validators
{
/// <summary>
/// Logs an error if the string to validate is <see langword=”null”/> or empty.
/// </summary>
public class RemoteValidator : ValueValidator
{
private string _RemoteURL;
public RemoteValidator()
: this(null)
{ }

public RemoteValidator(string messageTemplate)
: base(messageTemplate, null, false)
{ }

public RemoteValidator(string messageTemplate, string RemoteURL)
: base(messageTemplate, null, false)
{
this._RemoteURL = RemoteURL;
}

protected override void DoValidate(object objectToValidate,
object currentTarget,
string key,
ValidationResults validationResults)
{
string stringValue = null;
if (objectToValidate != null && !(objectToValidate is string))
{
stringValue = Convert.ToString(objectToValidate);
}
else
{
stringValue = (string)objectToValidate;
}
if (string.IsNullOrEmpty(stringValue))
{
LogValidationResult(validationResults, GetMessage(objectToValidate, key), currentTarget, key);
}
}

protected override string DefaultNonNegatedMessageTemplate
{
get { return “Element must not be null or empty”; }
}

protected override string DefaultNegatedMessageTemplate
{
get { return null; }
}
}
}

and

using System;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

namespace Mvc.Validation.Validators
{
[AttributeUsage(AttributeTargets.Property
| AttributeTargets.Field
| AttributeTargets.Method
| AttributeTargets.Class
| AttributeTargets.Parameter,
AllowMultiple = false,
Inherited = false)]
public sealed class RemoteValidatorAttribute : ValueValidatorAttribute
{
string _RemoteURL;
string _MessageTemplate;

public RemoteValidatorAttribute(string MessageTemplate)
: base()
{
this._MessageTemplate = MessageTemplate;
}

public RemoteValidatorAttribute(string RemoteURL, string MessageTemplate)
: base()
{
this._RemoteURL = RemoteURL;
this._MessageTemplate = MessageTemplate;
}

public string URL {
get
{
return this._RemoteURL;
}
set
{
}
}

public string MessageTemplate
{
get
{
return this._MessageTemplate;
}
set
{
}
}

protected override Validator DoCreateValidator(Type targetType)
{
return new RemoteValidator(this._MessageTemplate, this._RemoteURL);
}
}
}

When combined with this validation library, we get the following javascript code emitted to our page.  This example validates that an EmailAddresss is not already taken.  It does so by calling our Controller called /ServerValidation/IsValidLogin.

$("#EmailAddress").rules("add", {
	required: true,
	remote: "/ServerValidation/IsValidLogin",
	messages: {
		required: "Email Address is Required.",
		remote: "Login is already in use."
	}
});

Nice Solution and thanks for the library buunguyen

Warn about content change on web page

OK this is a pretty slick solution. I am going to defer the full solution to someone that I work with.  I thought is was  worth mention/remembering here because it is slick, and useful.

The Requirement: Warn a user that tries to navigate away from a page that content has changed on the page (of course if they changed anything)

The solution: JQuery goodness.

In essence the solution revolves around JQuery, adding events to input fields and using CSS classes to track state.

In a nutshell..

For any input field on a form, wire up a change event. On the change add a CSS style that indicates something changed.. On submit check if any fields on a form have CSS style that indicates if something changed. If they do warn the user.

Here is the implementation

http://robfuller.blogspot.com/2009/07/automatically-determine-if-there-is-any.html

Nice solution, elegant, and easy to implement.