Last night I started having a bit of a play with the Azure blob storage system. The first thing to do is install the SDK and get the development storage and development fabric setup. The SDK gives you the ability to go File –> New –> Cloud Project, I selected the web role for my demo (the other options include a stand alone worker, or combined web and worker roles). From here I included the StorageClient project from the Azure SDK samples, this has a bunch of wrapper classes that are helpful. Then I added some appSettings entries that contain URL’s for the development tools: 1: <appSettings> 2: <add key = "AccountName" value="devstoreaccount1"/> 3: <add key = "AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/> 4: <add key="BlobStorageEndpoint" value="http://127.0.0.1:10000/"/> 5: <add key="QueueStorageEndpoint" value="http://127.0.0.1:10001"/> 6: <add key="TableStorageEndpoint" value="http://127.0.0.1:10002"/> 7: </appSettings>
The only thing that I was interested in was putting stuff into the blob storage, so I put together a simple asp.net page that contained an upload file control and a button.
I wired up the button event with the following code:
1: protected void btnTest_Click(object sender, EventArgs e) 2: { 3: StorageAccountInfo blobAccount = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration(); 4: 5: BlobStorage blobStorage = BlobStorage.Create(blobAccount); 6: blobStorage.RetryPolicy = RetryPolicies.RetryN(1, TimeSpan.FromMilliseconds(100)); 7: 8: BlobContainer container = blobStorage.GetBlobContainer(Guid.NewGuid().ToString()); 9: 10: NameValueCollection containerMetadata = new NameValueCollection(); 11: containerMetadata.Add("Name", "TestContainer"); 12: container.CreateContainer(containerMetadata, ContainerAccessControl.Public); 13: 14: string blobName = Path.GetFileName(testFile.PostedFile.FileName); 15: BlobProperties properties = new BlobProperties(blobName); 16: container.CreateBlob(properties, new BlobContents(testFile.PostedFile.InputStream), true); 17: }
To prove that the uploaded file was inserted into the blob, I used the CloudDrive project that is also provided by the Azure SDK, this project provides some PowerShell magic to allow you to mount the blob storage as a drive (the drive is blob:) so in the command window I could do the following:
This just lists all the containers that have been created and then the blob’s that live inside those containers.
Now the interesting part is that we can use the browser to also view our blob:
The MSDN pages give us the details, but basically you browse to http://127.0.0.1/devstoreaccount/<container>/<blobname>
Of course you’ll only be able to browse like this unauthenticated if your container is publically viewable, these attributes were set above with the ContainerAccessControl.Public parameter on the CreateContainer method call.
I previously blogged about using the BDC with MOSS user profiles and how to set that whole process up. Well I thought that I might write a little about the BDC application definition file (ADF) that is imported into MOSS and is used by the BDC to generate the meta-data and to ultimately connect to the data source. An ADF file contains metadata describing entities and methods to populate those entities. These are the methods that are of interest to us, all have nice relevant names: IDEnumerator – These methods can perform filtering and can be passed parameters, the idea is that it returns an ID (and a timestamp if possible), as it’s name implies it is used to enumerate all the ID’s (or primary keys). In the context of the profile import, if your key is say, an Active Directory email address, then the IDEnumerator should return the email address field. Specific Finder – This method accepts an ID and returns just the information related to that ID. You’ll probably create a number of these using different filter descriptors. Now that you have an idea of what the methods are and how they operate you can design web services that are low friction for the BDC. You’ll need a web service that returns a list of ID’s (for the IDEnumerator), you’ll need a second webservice that accepts the same ID’s that were returned by the first method, this second method will comprise your Specific Finder methods. Also don’t create trouble for yourself by building webservices that accept a large number of parameters, you’ll regret it, just keep it simple. The guys that developed the fantastic BDC Meta-Man product also have the same advice. I’ve provided the SQL create statement and ADF file here>.
I’ve previously posted a demo that made use of a tag suggestion web part, as you type the web part will make an ajax call which will return the tags that match the current input. The user can click on the suggestion tag and it will populate the textbox, multiple tags can be entered into the textbox.  The most interesting part of this web part is the server side call, which I’ve implemented as a HttpHandler in the Tags.ashx file: public class Handler : IHttpHandler {
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text";
I’ve made use of the System.Web.Extensions JavaScriptSerializer to render the string array of tags to JSON:
1: List<string> tagList = new List<string>(); 2: 3: //add all the tags to a collection, JSON serialize the list 4: 5: System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); 6: 7: context.Response.Write(serializer.Serialize(tagList.ToArray())); 8:
I’ve put this handler in the /_vti_bin/ directory of SharePoint which maps to the ISAPI folder under the 12 hive.
I’ve used the same code to generate the tags as I did with the tag cloud web part, so once again the generation of these tags won’t scale to large lists, this is just an example of how to implement a JQuery based Tag Suggestion web part.
The source code for this web part can be found here.
I recently had a project where I needed to bind some business objects to a SPGridView in a web part, which on its own is trivial, but I also needed to create the columns for the SPGridView on the fly, since a number of business objects could be rendered in this control. The main problem was the naming of these columns, if I just bound them directly to the Grid, I would get the ugly naming of my properties (I say it’s ugly, it’s actually pretty awesome if your a developer, but those pesky end users like pretty names). The sample grid looks like this by default: So what I came up with isn’t exactly new or innovative, I created a custom attribute called Alias: public class AliasAttribute : System.Attribute
{
protected string[] aliasName;
public AliasAttribute(params string[] alias)
{
this.aliasName = alias;
}
public string[] Alias
{
get { return aliasName; }
set { aliasName = value; }
}
}
This attribute gets applied to each property on your business object with a pretty name:
[Alias("Internet URL")]
public string InternetAddress
{
get { return internetAddress; }
set { internetAddress = value; }
}
Now the code that performs the binding looks for properties with a custom attribute and pulls out the alias as the HeaderText:
BoundField newBoundField = new BoundField();
newBoundField.HeaderText = Helpers.GetPropertyAlias(field.Trim());
newBoundField.DataField = field.Trim();
grid.Columns.Add(newBoundField);
public static string GetPropertyAlias(string propertyName)
{
//Change the next line for your business object ..
PropertyInfo[] propInfos = typeof(BusinessObject).GetProperties();
foreach (PropertyInfo propInfo in propInfos)
{
if (propInfo.Name.ToUpper().Equals(propertyName.ToUpper()))
{
object[] attribs = propInfo.GetCustomAttributes(typeof(AliasAttribute), true);
foreach (object attrib in attribs)
{
if (attrib is AliasAttribute)
{
AliasAttribute alias = (AliasAttribute)attrib;
//return the alias
return alias.Alias[0];
}
}
}
}
//if we don't find the custom attribute, just return the same value we passed in
return propertyName;
}
The end result is a nice pretty grid:
A sample project can be found here.
I found this fantastic post today that I just had to comment on: | In a SharePoint entry form the user can select multiple values from a list. This should include an “All” option so in a long list the user can select or de-select all values with one click. | The idea is to use JQuery to perform a select 'All' on all the checkboxes in a list. DannyE posted this JavaScript: $(”.ms-RadioText[title=’All’] :checkbox”).click(function(){
var otherids = (this.id).substring(0, (this.id).length-2 );
$(”input[id^=’”+otherids+”‘]”).attr( “checked” , (this.checked)?”checked”:”" );
});
It looks for all the items with the ms-RadioText class that also have a Title attribute that equals 'All', it adds a click handler that will then toggle the checked status of the checkbox, its extremely cool, way better than the latest block buster movie ...
Anyway, that's all well and good, I'm not adding much value reposting his stuff, so I'd just like to improve on his deployment recommendation.
Instead of adding a script reference to the master page and then inserting a content editor region to paste the JavaScript in, I would suggest using the JQuery Script Manager:
So the idea is to use SharePoint Designer to register the control with the JavaScript. Note here to use the ScriptTemplate you'll have to grab my latest script manager control from here.
<cc1:jQueryManager ID="JQueryManager1" runat="server">
<ScriptTemplate>
$(".ms-RadioText[title='All'] :checkbox").click(function(){
var otherids = (this.id).substring(0, (this.id).length-2 );
$("input[id^='"+otherids+"']").attr( "checked" , (this.checked)?"checked":"" );});
</ScriptTemplate>
</cc1:jQueryManager>
This will insert the JQuery Script Manager and declaratively add the JavaScript code to the page. This way you don't need to worry about the script resources or having multiple client side load functions.
How can you not love JQuery after seeing the power of the above code? it's just that awesome!
I've previously posted a demo that illustrated a tag cloud web part, I've now posted the code. First let me warn you that the method I've used to get the tag list will not scale, so please disregard the TagBuilder method, you'll need to implement that yourself. I've based this tag cloud control loosely on the tag cloud control from codeproject, I've refactored most of it, but have kept the maths behind generating a weighting. Using the control is pretty simple, you just need to measure the tags and then pass in a collection of CloudItems: //tagList is a dictionary with the tag and count
Dictionary<string, int> tagList = new Dictionary<string, int>();
foreach (string tag in tagList.Keys)
{
CloudItem cloudItem = new CloudItem();
cloudItem.Text = tag;
cloudItem.Weight = tagList[tag];
cloudItem.Href = "/SearchCenter/Pages/Results.aspx?k=tags:" + tag;
Items.Add(cloudItem);
}
There are a number of ways to perform the measurement, if you were to count the number of times a tagged resource was accessed, you would end up with a heat map of popular items. Alternatively you could simply count up the number of times a tag has been used (I think this is the most common use, I've done it this way in my demo). In any case the exact implementation details have been left up to you.
The web part that I'm presenting today is more of a building block, it doesn't do much on its own. It makes use of the JQuery Corners Plugin. To set the corner types you can modify the Corner Type setting: The above example will cause the following JavaScript to be output: jQuery('cornerDiv').corner('tr 25px');
This web part makes use of the JQuery Script Manager that I've posted about previously.
As usual the source can be found here.
A screencast of the control in action can also be found here.
I've posted a couple of articles on JQuery and it's use in SharePoint and also provided some sample web parts. Recently I was putting together these web parts into a single project and I ran into a problem. Each web part was adding the JQuery script resources to the page as if it was the only control on the page, for example the ImageCarousel and the Tag Suggestion web parts both had code that needed to be run on the JQuery load event, this problem would also crop up when multiple instances of the same web part were added to the page. What was outputted to the page was something like: $(document).ready(function(){
//function call for webpart1
}
$(document).ready(function(){
//function call for webpart2
}
$(document).ready(function(){
//function call for webpart2 instance 2
}
So I started developing a Script Manager control that I could add to the page that would output the JQuery load event just once, but with all the calls for the page inside this event. As with anything programming related these days, I found what I was looking for at http://codeplex.com/JQueryScriptManager/ I've changed a bit of the implementation, but the basic intent has remained the same. Hopefully I'll be able to get these changes updated to codeplex.
Looking at the revised code for the ImageCarousel web part:
jQueryManager jqueryManager = null;
protected override void OnInit(EventArgs e)
{
jqueryManager = jQueryManager.GetCurrent(Page);
if (jqueryManager == null)
{
jqueryManager = new jQueryManager();
Page.Controls.Add(jqueryManager);
}
Page.ClientScript.RegisterClientScriptInclude("JCarousel", Page.ClientScript.GetWebResourceUrl(this.GetType(),
"JQueryWebParts.js.jquery.jcarousel.pack.js"));
base.OnLoad(e);
}
Now inside the PageInit we check for the presence of a JQueryManager script control, if we don't find one, we add it to the controls collection. Just like the ASP.NET Ajax ScriptManager control, there can only be one per page.
Now to add code that is globally scoped which also resides in the same script block where our OnLoad event lives:
jqueryManager.RegisterScript("-- javascript code without <script> blocks");
//example:
jqueryManager.RegisterScript("var someItems = [5,4,3]");
To get javascript to run when the page is loaded (this will bundle calls into a single load event):
jqueryManager.ReadyFunctions.Add(new RegisterStartFunction("jQuery('#carousel').jcarousel();"));
Use the ReadyFunctions collection which takes a RegisterStartFunction object as a parameter.
Internally the JQuery script manager control is performing the following:
StringBuilder Start = new StringBuilder();
if (Scripts != null)
{
foreach (RegisterScriptBlock r in Scripts)
Start.Append(r.ScriptBlock + Environment.NewLine);
}
if (ReadyFunctions != null)
{
Start.Append("$(document).ready(function(){");
foreach (RegisterStartFunction r in ReadyFunctions)
Start.Append(r.FunctionName + Environment.NewLine);
Start.Append("});\n\n");
}
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "JQuery", Start.ToString(), true);
The source code can be downloaded here. This includes the updated JQuery Script Manager and the ImageCarousel web part that I've previously posted about.
I think this approach will provide a nice platform to continue building web parts based on JQuery Plugins, have fun.
One of my favourite features of SQL Server 2008 is the Merge or 'upsert' Statement. Lets say we have an ETL process that prepares a data stage table, without the merge statement you might first delete all the records from the staging table and then insert them all again. If you have large data sets this can become quite time consuming and well frankly it doesn't seem very elegant. Another alternative is to use a cursor to iterate over each record and look for changes, you might also have data that has a timestamp which might help you write a set based operation, both of these approaches are less than ideal. Well as of SQL 2008 you can use the Merge Statement: Lets say you have a simple table like: CREATE TABLE People
(
ID INT PRIMARY KEY,
Name Varchar(50) NOT NULL,
Position Varchar(50) NOT NULL,
Age INT NOT NULL
);
Assuming our StagedPeople table is the same structure the merge statement would look like:
MERGE INTO People
USING StagedPeople
ON People.ID = StagedPeople.ID
WHEN MATCHED THEN
-- if we find a match, then perform an update
UPDATE SET
Age = StagedPeople.Age,
Name = StagedPeople.Name,
Position = StagedPeople.Position
WHEN NOT MATCHED THEN
-- if we don't find a match, then insert a new record
INSERT (ID, Name, Position, Age)
VALUES (StagedPeople.keycol, StagedPeople.Name, StagedPeople.Position, StagedPeople.Age)
WHEN SOURCE NOT MATCHED THEN
-- we could delete the record, since it's not found in the Staged table
DELETE;
OUTPUT $Action
The $action variable returns a varchar(20) of the operation that has taken place, i.e. 'INSERT', 'UPDATE', 'DELETE'
The operations and full reference can be found here.
It seems like a pretty fundamental operation, glad it only took till 2008 for us to have it :)
This week I picked up this simple tip for spatial enabled applications. Just say you have an existing database that has the latitude and longitude stored as separate columns such as a Latitude and Longitude as you would like to make these columns available so that spatial operations can be performed on them. Create a view on the table with the Lat, Long as parameters to the Geography::Point function which creates a spatial point. Your table might look like: The following SQL will do the trick to convert the points to spatial column: CREATE VIEW SpatialView
AS
-- Create a view over a table without a spatial column
SELECT Name, geography::Point(Latitude, Longitude, 4326) as Location FROM UnSpatialTable
So now you have a spatial column that you can include in your spatial queries such as, finding the distance between points:
DECLARE @location GEOGRAPHY
SELECT @Location = Location from SpatialView where name = 'test'
SELECT @Location.STDistance(Location) as DistanceToOther from SpatialView
Creating a buffer around a point:
SELECT Location.STBuffer(20) as BufferedLocation from SpatialView where Name = 'test'
This approach might be useful if you collecting data on a mobile device running the compact edition of SQL which doesn't have any spatial features. You can collect your data as lat,long values and then do the spatial manipulation via the view. Don't forget to think about spatial indexes!
Recently I posted about using JQuery in SharePoint web parts in this post I wrapped up the JCarousel plugin. Instead of requiring the JCarousel.js file to be included into the master page, I instead made the web part inject the HTML to cause the JavaScript file to be loaded. The first step was to include the packed version of jquery.jcarousel.pack.js into my project: Notice the Embedded Resource setting for the build action. Now if we add the following code to our assembly.cs file: [assembly: System.Web.UI.WebResource("SPImageCarousel.jquery.jcarousel.pack.js", "text/javascript")]
The above line now makes our embedded resource available for use in our web part which is done by the following code:
protected override void OnInit(EventArgs e)
{
Page.ClientScript.RegisterClientScriptInclude("JCarousel", Page.ClientScript.GetWebResourceUrl(this.GetType(), "SPImageCarousel.jquery.jcarousel.pack.js"));
base.OnLoad(e);
}
This code will ensure that the script is only added to the page once since the key "JCarousel" is the first parameter, remember that more than one web part can be added to a page at the same time, so we need to consider this as we develop our web parts. OnInit is the perfect event to call RegisterClientScriptInclude it's nice and early in the page lifecycle.
So hopefully you'll find this a nice and easy way to include your JavaScript resources, it does have the disadvantage of requiring a recompile to include a later version of the JavaScript file, so your mileage may vary if this is a concern.
Better still if your using .NET 3.5 Service Pack 1, you could make use of the CompsiteScript element of the script manager to combine the javascript files so the browser makes fewer calls to the server.
I've mentioned before that I'm partial to JQuery, I consider it to be the Jessica Alba of JavaScript. With all the work that has been done creating awesome JQuery based UI components, it seems like such a shame that us SharePoint developers aren't making more use of them. So I present to you my first JQuery based web part, the Image Carousel: Based on JCarousel, this web part presents the contents of a picture library in a carousel that can be scrolled by the navigation buttons. The web part simply imports the jcarousel.js file, the web part then injects a JavaScript array of the images that reside in the selected picture library along with some javascript that initialises the carousel. The code to find the SharePoint picture library list is trivial: SPSite site = SPContext.GetContext(HttpContext.Current).Site;
SPWeb web = site.OpenWeb();
foreach (SPList list in web.Lists)
{
if (list.BaseTemplate == SPListTemplateType.PictureLibrary)
{
//grab the images from this list
jsArray.Append(ProcessList(list));
}
}
The web part makes use of custom toolpart for the selection of the picture list:
I think this provides a good template and starting point to start putting more of those JQuery plugins to use inside SharePoint
A small screencast of the control in action can be found here.
The end effect is some exceptional functionality for little development effort, which is really what JQuery is all about.
Some of the things that you need to think about when integrating:
- Make sure that the injected div tag that will be used at the host has a unique id, remember that more than one webpart can be added to a page, if you hard code the html to have specific ID's, you will run into problems.
- Make sure the JavaScript gets injected into the right part of the page, also just like the above point, ensure that each injected bits of code have a unique name, so that multiple webparts on the same page don't cause problems.
- Use the packed version of the component, SharePoint is bloated enough already, use the smallest possible footprint you can.
The source code can be found here.
In my first post dissecting the portal connection code, I showed the following code: PortalConfig portalConfig =
(PortalConfig)curSite.WebApplication.Farm.GetObject
(
new Guid(curSite.WebApplication.Properties[PROP_KEY].ToString())
);
Notice the line: curSite.WebApplication.Properties[PROP_KEY].ToString()
The properties collection is scoped at the web application level. So if someone was to build a tool that called the Clear() method on this collection it would cause issues inside the Portal Connection tool.
So my point is to use the web application properties with other applications in mind, only clear your own state.
If your profile properties includes the organisation hierarchy, you may have noticed that when an AD account is deleted or deactivated the manager of that person is sent an email notifying the manager that the site is scheduled for deletion and that they now have permissions to the mysite so they can clean it up. Of course the MySite will only be deleted if the Automatic Site Deletion is enabled. Be aware that if the AD account is deleted and Automatic Site Deletion is enabled, the manager won't have access because the site will be deleted.  Lets say you work in an organisation that wants to suppress this email (I know it sounds silly), how would you go about that? The first thing to understand is the process that caused that email to be sent in the first place. The first step is the profile import will detect if the AD account has been deleted or disabled and will change the bDeleted flag to 1 in the UserProfile_Full table of the SSP database. The next step of the process is handled by the My Site Cleanup Job:  If you disable this job then you will prevent the notification email being sent. However you will still need to have a process in place to clean out the mysites and the profiles. The crawler will still find the profiles and index them (so your people searches will include outdated people). The MySites will still be around taking up disk space. One potential solution is to call the profile stored procedure manually: DECLARE @UserID uniqueidentifier
DECLARE @NTName nvarchar(400)
DECLARE @SID varbinary(512)
DECLARE userCursor CURSOR FOR
SELECT Userid, NTName, SID from userprofile _full where bdeleted = 1
OPEN userCursor
FETCH NEXT FROM userCursor INTO @UserID, @NTName, @SID
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC profile_RemoveUser @UserID, @NTName, @SID
FETCH NEXT FROM userCursor INTO @UserID, @NTName, @SID
END
CLOSE userCursor
DEALLOCATE userCursor
In a previous post I introduced the Automatic Portal Connection tool, which can be used to automatically setup the portal connection on new and existing sites. I thought I would take a peek at how I've made this happen: Firstly I'd like to introduce the SPPersistedObject: The description MSDN uses is: | The SPPersistedObject class provides a base class for all administration objects. It serializes all fields marked with the Persisted attribute to XML and writes the XML blob to the configuration database. The SPPersistedObject class contains code to serialize all its members that are base types, other persisted objects, and collections of persisted objects. Configuration data that is stored in persisted objects is automatically made available to every process on every server in the farm. | The Portal Connection tool has a class called PortalConfig which derives from SPPersistedObject this class holds the configuration information such as the portal name and portal URL: /// <summary>
/// Simple persisted object that stores the portal connection name and the portal connection url
/// </summary>
public class PortalConfig : SPPersistedObject
{
[Persisted]
private string portalName = string.Empty;
public string PortalName
{
get { return portalName; }
set { portalName = value; }
}
[Persisted]
private string portalUrl = string.Empty;
public string PortalUrl
{
get { return portalUrl; }
set { portalUrl = value; }
}
public PortalConfig()
{
}
public PortalConfig(string name, SPPersistedObject parent, Guid guid)
: base(name, parent, guid)
{
}
}
SPPersistedObject is a very handy class, especially for configuration type scenario that is used in this solution, a method that makes use of this object is:
/// <summary>
/// Simple method to persist our settings to the site
/// </summary>
/// <param name="curWeb"></param>
/// <param name="curSite"></param>
public static void ApplyPortalConnectionSettings(SPWeb curWeb, SPSite curSite)
{
PortalConfig portalConfig = (PortalConfig)curSite.WebApplication.Farm.GetObject(
new Guid(curSite.WebApplication.Properties[PROP_KEY].ToString())
);
if (curWeb.IsRootWeb)
{
curSite.PortalName = portalConfig.PortalName;
curSite.PortalUrl = portalConfig.PortalUrl;
}
curWeb.Update();
}
This object is filled out and persisted by the central admin page, then a feature staple gets this information when the site is created and applies it, but I'll expand on this in future posts.
The portal connection is pretty useful, say you have a web application for team sites and have self service site creation turned on you would probably like the portal connection to be set to your portal homepage. Just as a refresher the portal connection is the link at the top of your site: I've put together a tool that will give you the ability to set the portal connection for new sites and also for newly created sites. Once the solution has been deployed and the features activated, from central admin you will see a new link: Following this link you will be presented with the following page: From here you can select the web application to work with and you can set the portal name and URL. By selecting the 'Apply To Existing Sites' checkbox, you will set portal connections to all the sites on that web application. The SharePoint solution can be downloaded here, I'll be releasing the code soon, I want to release it to Codeplex, I just need to set it up first. Here is a small screencast showing the tool in use Edit: I've now setup the Codeplex project: http://codeplex.com/SPPortalConnection
JQuery is my all time favorite JavaScript library, it's so simple to use and incredibly powerful, but if you include all the plugins that are available, I just don't think that you would find a better tool for client side scripting. My favorite validation plugin is the 'Validation Plugin', let me give you an example of how it is used: Lets say you have a signup form, you want the user to select a user name, a password with confirmation and an email address, assume all of this is inside a form tag named 'signup'. All of the HTML input element ids are 'Password', 'ConfirmPassword', 'Email'. The JavaScript would be: <script type="text/javascript">
$().ready(function() {
$("#signup").validate({
rules: {
UserName: {
required: true,
minLength: 2,
remote: "/Account/CheckUserName"
},
Password: {
required: true,
minLength: 2
},
ConfirmPassword: {
required: true,
minLength: 5,
equalTo: "#Password"
},
Email:{
required: true,
email: true,
remote: "/Account/CheckEmail"
}
},
messages: {
UserName: {
required: "Please enter a username",
minLength: "Your username must consist of at least 2 characters",
remote: jQuery.format("{0} is already in use")
},
Password: {
required: "Please enter a password",
minLength: "Your password must consist of at least 2 characters"
},
ConfirmPassword: {
required: "Please provide a password",
minLength: "Your password must be at least 2 characters long",
equalTo: "Please enter the same password as above"
},
Email: {
required: "Please enter your email address",
|