Wednesday, April 13, 2011

UI Validation with Silverlight 4

I am currently designing components of the application architecture for a LOB web based MVVM Silverlight 4 application that will contain many forms so obviously I will need a solid, simple way to do UI validation. I want to leverage out-of-the-box Silverlight validation. And the validation will need to run business rule logic in another tier.

There are a few ways to go about this with Silverlight 4:

  • Throw exceptions in your property setters
  • Implement IDataErrorInfo
  • Implement INotifyDataErrorInfo

With Silverlight 4 came the new interface INotifyDataErrorInfo. So why another interface?

Well, INotifyDataErrorInfo newly supports multiple validation errors per property.

Ok, let's see it in action.

We create a base view model class which implements INotifyDataErrorInfo and we might as well implement INotifyPropertyChanged too.

public class ViewModelBase : INotifyDataErrorInfo, INotifyPropertyChanged

The idea is that view models register methods that validate a particular property and when a property is set, ViewModelBase will know to fire validation methods registered for that property. It then collects up validation errors and presents them to the UI.

As an example I've created a simple PersonViewModel that has an age and a first name.

So first we derive our class from ViewModelBase.

public class PersonViewModel : ViewModelBase

Then in the constructor or somewhere during initialisation we register our validation methods against our properties.

In this example I've created one validation method for Age and two methods for First Name.

protected void ValidateAge()

{

if (Age < 18 || Age > 55)

throw new Exception("Age must be between 18 and 55");

}

protected void ValidateFirstNameLength()

{

if(FirstName.Length < 3)

throw new Exception("First name must be at least 3 characters long.");

}

protected void ValidateFirstNameCharacters()

{

foreach (char c in FirstName.ToCharArray())

if (Char.IsDigit(c))

throw new Exception("First name cannot contain digits.");

}

To register our validation methods we call ViewModelBase.RegisterForValidation() passing in the property and the validation method.

public PersonViewModel()

{

RegisterForValidation("Age", ValidateAge);

RegisterForValidation("FirstName", ValidateFirstNameLength);

RegisterForValidation("FirstName", ValidateFirstNameCharacters);

}

protected void RegisterForValidation(string propertyName, Action validationMethod)

{

if (!_registeredValidationMethods.Keys.Contains<string>(propertyName))

_registeredValidationMethods.Add(propertyName, new List<Action>() { validationMethod });

else

if (!_registeredValidationMethods[propertyName].Contains(validationMethod))

_registeredValidationMethods[propertyName].Add(validationMethod);

}

In the setters for our properties we call the SetValue() method on ViewModelBase.

private int _age = 0;

public int Age

{

get

{

return _age;

}

set

{

SetValue<int>("Age", value, ref _age);

}

}

SetValue() performs validation, sets the value of the underlying field and notifies the UI the property has changed.

public void SetValue(string propertyName, T newValue, ref T underlyingField)

{

//set value

underlyingField = newValue;

//validate

ValidateProperty(propertyName);

//notify

RaisePropertyChangedEvent(propertyName);

}

So let's see it in action. Setting the Age to 4 and removing focus from the age text box causes our validation to fire and fail:

Correcting the age satisfies the validation logic and the call out disappears:

Changing the name to less than 3 chars works as expected:

And so does including digits in the name:

What about a scenario where the first name fails both validation rules?

In this example, the viewmodel knows about both failed rules but it only displays one callout.

Silverlight doesnt know how to handle multiple errors per property at the UI with this implementation.

we need to add some kind of validation summary control that displays all errors.

With one simple line of code we can add a validation summary control in XAML and it all works out of the box.

xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"

<sdk:ValidationSummary>sdk:ValidationSummary>

And here it is in action:

Monday, April 19, 2010

Sharepoint lists Web service

Recently I had a requirement to get data from a project management product and push it into a sharepoint list on a set interval.
The steps involved were:

1. Delete all items from current sharepoint list

  1. Get data from project management software
  2. Do some mapping and push into sharepoint

SSIS was going to be the vehicle to run the code I just had to figure out how to talk to sharepoint.

This was the first time I had attempted to do this so I relied mostly on Google to show me the way but it wasnt so straight forward. There are a few niggles to be aware of.

My approach was:

  1. Get All current sharepoint list items (for the IDs) - GetListItems
  2. Delete each one from sharepoint (Using the IDs) - UpdateListItems
  3. Get new data rows
  4. Push each one to sharepoint - UpdateListItems

Before we get started there's a few things we need to know:

  • Url to sharepoint lists web service

    eg. http://someLocationSomewhere/SCP2010/_vti_bin/lists.asmx - add this as a web reference to your project.
  • List GUID - From here Go to the list settings page. Right click on "Title, description and navigation" and copy the URL. Paste that into notepad and copy everything after "List="in the string. That's your URL Encoded GUID for the list
  • View GUID- You can google this yourself. You can get it either by url hack or web service calls

Ok, first thing, how to get list items?

Lists list = new Lists()

{

Url = ServiceUrl,

Credentials = System.Net.CredentialCache.DefaultCredentials

};

XmlNode resultsNode = list.GetListItems(ListGUID, ViewGUID, null, null, null, null, null);

ServiceUrl, ListGUID and ViewGUID are class properties.
The above code will give you an XmlNode containing your list items. View the InnerXml in VS. You can iterate them using standard Xml node iteration techniques.

foreach (XmlNode outerNode in resultsNode.ChildNodes)

{

if (outerNode.NodeType.Equals(System.Xml.XmlNodeType.Element))

{

foreach (XmlNode node in outerNode.ChildNodes)

{

if (node.NodeType.Equals(System.Xml.XmlNodeType.Element))

{

XmlNode idNode = node.Attributes.GetNamedItem(IDFieldName);

try

{

DeleteListItem(idNode.InnerText);

Dts.Log("Deleted ID: " + idNode.InnerText, 997, null);

}

catch (Exception ex)

{

Dts.Log("An error occurred during DeleteListItem: " + ex.Message, 998, null);

if (!ContinueOnError)

throw ex;

}

}

}

}

}


The important bit in this code is IDFieldName. This attribute has the ID of the item we want to delete.

public string IDFieldName

{

get

{

return "ows_ID";

}

}

How to Delete a list item?

Now we have the ID of the list item, we can delete it.

I created a method that takes the ID and does the delete using UpdateListItems()

private void DeleteListItem(string itemId)

{

XmlDocument xmlDoc = new System.Xml.XmlDocument();

XmlElement elBatch = xmlDoc.CreateElement("Batch");

elBatch.SetAttribute("ViewName", ViewGUID);

elBatch.InnerXml = string.Format("{0}", itemId);

Lists list = new Lists()

{

Credentials = System.Net.CredentialCache.DefaultCredentials,

Url = ServiceUrl

};

XmlNode ndReturn = list.UpdateListItems(ListGUID, elBatch);

if (!ndReturn.InnerText.Equals(SuccessResult))

throw new ApplicationException(ndReturn.InnerText);

}


I used the returned XmlNode.InnerText to test for Success. The sharepoint web service returns

"0x00000000"
when it succeeds.

public string SuccessResult

{

get

{

return "0x00000000";

}

}



Now the final step, How to Add List Items?

I created a method called AddListItem(). Pretty much the same code as DeleteListItems() except we build the xml stub with our new data and pass it to AddListItem() to be submitted to sharepoint via UpdateListItems().

private void AddListItem(string spAddItemXML)

{

XmlDocument xmlDoc = new System.Xml.XmlDocument();

XmlElement elBatch = xmlDoc.CreateElement("Batch");

elBatch.SetAttribute("OnError", "Continue");

elBatch.SetAttribute("ViewName", ViewGUID);

elBatch.InnerXml = spAddItemXML;

Lists list = new Lists()

{

Url = ServiceUrl,

Credentials = System.Net.CredentialCache.DefaultCredentials

};

XmlNode ndReturn = list.UpdateListItems(ListGUID, elBatch);

if (!ndReturn.InnerText.Equals(SuccessResult))

throw new ApplicationException(ndReturn.InnerText);

}


The xml stub you pass in to this method will depend on the structure of your list but the basic xml structure is:

Container: <Method ID="MyId" Cmd="New">
Row: <Field Name="MyFieldName">myFieldValueField>

Thats pretty much it, remember to always specify the Url even though the web service reference already knows it.

Wednesday, March 31, 2010

Bids4biz launch coming soon

We're getting closer to launch for bids4biz. I dont want to say a specific date right now but we do have a date set.

Dev has gone well, Ive been able to maximise code reuse without getting lost in abstraction. Theres a few other things I would like to implement like a "one click button user control" that uses javascript to disable the button after the first click and displays a throbber gif and perhaps even changes the button text to "Loading..." or "Please wait". Ill have to expose ValidationGroup, Text and the click event.

I still have a few issues with some of my user controls.
I have a UC which has a ModalPopup with login controls with validation. If I have multiple of these UC on the same page they fire each others validation which is not intended behaviour. Ive tried setting ValidationGroup to be unique (random generated string) on UC Init but still fires other instances validation. Will have to figure this one out soon.


Tuesday, January 26, 2010

Plinqo for bids4biz

We've been developing an exciting new website called Bids4biz.com.
I wont go into detail about the actual site yet (until we are closer to launch) but I will say a bit about my experience with PLINQO .

What is PLINQO?

Basically it's what LINQ to SQL should've been.
PLINQO is an ORM built on top of LINQ to SQL implemented as Codesmith templates so you'll need a copy of Codesmith to use it.

Why I chose PLINQO

Before I begun the project I had a few requirements in mind when deciding what framework to use for my business and data access layers:
  • Code should be easily generated from Database schema
  • Changes to my database schema should easily propagate to my code through code re-generation
  • The code Generation tool should be simple to use
  • Documentation should be readily available
  • There should exist some implementation in the business layer objects to enforce business rules
  • Performance should be good (for DB transactions)
  • Objects should all have strongly typed properties.
  • Should not be overly complex
  • Learning curve not too steep
I looked at CSLA, nHibernate, Doodads, building from the ground up and of course PLINQO.
PLINQO by far fit my criteria above.

All I had to worry about was designing the DB then after specifying a few settings (with help from the video tutorials which were very easy to follow) then Codesmith generated all my classes and dbml from within Visual Studio.
If I make a change to my database, after 2-3 clicks, my code is updated to match.

I only needed to spend 30mins learning how it worked before I could jump right in and generate some code.


Development is still going and so is my learning experience with PLINQO but so far it has been an excellent choice.

Friday, February 27, 2009

[CompressionExtension] attribute with IIS compression

IIS has a feature that allows all outgoing responses to be compressed (usually zip). With content such as html and xml the benefit of doing this is much smaller packets back to the client since these types of documents can usually be compressed very well.
This may caus e you some issues when you are working with web services which are deployed on an IIS compression enabled server.
To get around this, you have to decompress the soap message before it gets consumed by your local proxy class.
There is an article here http://mastercsharp.com/article.aspx?ArticleID=86&TopicID=16 that explains the implementation and has the download.
What I needed to do in my project was import the reference to the above assembly and add the [CompressionExtension] attribute to every web service method on my proxy class.
The only annoyance is every time you update your web reference you have to re-add the using and all the attributes.
The good thing is I can now use IIS compression with my web services.

Wednesday, July 9, 2008

Designing a batch system with.net 2.0

A common requirement for larger scale systems is to have some kind of batch processing system. Batch processing is beneficial when you have certain tasks that consume alot of resources and the tasks are not urgent. An example might be sending out an email notification to subscribers or printing invoices.
There are a few things to keep in mind when designing a batch system:
  • You should always use a sufficient amount of logging to help you resolve issues.
  • Designing a good BatchJob base class will make things alot easier for future development.
  • Batch system events should be visible.
  • The Batch System should be easy to re-configure (dont hard code stuff you shouldnt).
  • Build provisions for batch job failiures and time outs.
  • Dispose of resources properly.

I'll write a bit about how I implemented a batch system I just finished using c#.net and an asp.net 2.0 application to provide visibilty of batch system events.

Sunday, July 6, 2008

File upload manager Web User Control in ASP.net


Here is some code for a File upload manager control.
This control can:


  • Upload multiple files

  • Remove any uploaded files

  • Check for duplicate uploads

  • Reduces risk of concurrent file access issues by assigning a sessionId sub-folder under the upload directory.

  • Remembers uploaded files over post backs.

All you have to do is assign the FileUploadDirectory property. This will be a full local path on the local machine.


Note. I have written this control with the intention of creating a re-usable dll, to see how to do this check my blog here: http://maoriitguy.blogspot.com/2008/07/creating-ascx-library.html


Create a new file in VS named FileUploadManager.ascx and paste this code:



<%@ Control Language="C#" ClassName="DKCS.FileUploadManager" %>
<script runat="server"> private string _fileUploadDirectory;
public ListItemCollection UploadFiles { get { return CheckBoxList1.Items; } }
public string Title { get { return Label1.Text; } set { Label1.Text = value; } }
public string FileUploadDirectory { get { return HiddenFieldUploadDir.Value; } set { if (!System.IO.Directory.Exists(value)) ThrowNotImplementedException();
_fileUploadDirectory = value + "/" + Session.SessionID;
try { System.IO.Directory.CreateDirectory(_fileUploadDirectory); } catch { ThrowNotImplementedException(); }
HiddenFieldUploadDir.Value = _fileUploadDirectory; } }
protected void Button2_Click(object sender, EventArgs e) { if (FileUpload1.FileName == null FileUpload1.FileName.Equals("")) return;
if (FileUploadDirectory == null FileUploadDirectory.Equals("")) { ThrowNotImplementedException(); }
//check File exists foreach (ListItem item in CheckBoxList1.Items) { if (item.Text.Equals(FileUpload1.FileName)) return; } string localFilePath = FileUploadDirectory + "/" + FileUpload1.FileName; FileUpload1.SaveAs(localFilePath);
CheckBoxList1.Items.Add(new ListItem(FileUpload1.FileName, localFilePath)); Button1.Enabled = true; }
private void ThrowNotImplementedException() { throw new NotImplementedException("The FileUploadManager.FileUploadDirectory property must be set to a absolute directory path on the local server. Ensure the asp.net application has permissions to read and write to the upload directory."); }
protected void Button1_Click(object sender, EventArgs e) { ListItemCollection temp = new ListItemCollection();
foreach (ListItem item in CheckBoxList1.Items) { if (item.Selected) { System.IO.File.Delete(item.Value); temp.Add(item); } }
foreach (ListItem item in temp) { CheckBoxList1.Items.Remove(item); }
if (CheckBoxList1.Items.Count == 0) { Button1.Enabled = false; } }
</script>
<div> <table> <tr> <td align="center" colspan="2"> <asp:Label ID="Label1" runat="server" Text="Upload Files"></asp:Label></td> </tr> <tr> <td> <asp:FileUpload ID="FileUpload1" runat="server" Width="300px" Height="24px" /></td> <td> <asp:Button ID="Button2" runat="server" Text="Upload" OnClick="Button2_Click" Width="80px" /></td> </tr> <tr> <td colspan="2"> <asp:CheckBoxList ID="CheckBoxList1" runat="server"> </asp:CheckBoxList> <asp:HiddenField ID="HiddenFieldUploadDir" runat="server" /> </td> </tr> <tr> <td align="center" colspan="2"> <asp:Button ID="Button1" runat="server" Text="Remove Selected" Enabled="False" OnClick="Button1_Click" /></td> </tr> </table></div>




Here is a screen of it inside a WebPartZone: