changeset 50:64f19743cfd6

Initial load for Silverlight Validation
author Steven Hollidge <stevenhollidge@hotmail.com>
date Wed, 18 Apr 2012 22:28:46 +0100
parents 502f5f365649
children 02b1513f679a
files SilverlightValidation/Libs/FluentValidation.dll SilverlightValidation/Libs/System.Windows.Controls.Data.Input.dll SilverlightValidation/Libs/System.Windows.Controls.dll SilverlightValidation/SilverlightValidation.sln SilverlightValidation/SilverlightValidation/App.xaml SilverlightValidation/SilverlightValidation/App.xaml.cs SilverlightValidation/SilverlightValidation/Properties/AppManifest.xml SilverlightValidation/SilverlightValidation/Properties/AssemblyInfo.cs SilverlightValidation/SilverlightValidation/RelayCommand.cs SilverlightValidation/SilverlightValidation/SilverlightValidation.csproj SilverlightValidation/SilverlightValidation/UserModel.cs SilverlightValidation/SilverlightValidation/UserModelValidator.cs SilverlightValidation/SilverlightValidation/UserView.xaml SilverlightValidation/SilverlightValidation/UserView.xaml.cs SilverlightValidation/SilverlightValidation/UserViewModel.cs SilverlightValidation/SilverlightValidation/ViewModelBase.cs
diffstat 16 files changed, 733 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
Binary file SilverlightValidation/Libs/FluentValidation.dll has changed
Binary file SilverlightValidation/Libs/System.Windows.Controls.Data.Input.dll has changed
Binary file SilverlightValidation/Libs/System.Windows.Controls.dll has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation.sln	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SilverlightValidation", "SilverlightValidation\SilverlightValidation.csproj", "{0C1CC1FC-915A-4428-8952-CDC79EABC3F4}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{0C1CC1FC-915A-4428-8952-CDC79EABC3F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0C1CC1FC-915A-4428-8952-CDC79EABC3F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0C1CC1FC-915A-4428-8952-CDC79EABC3F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0C1CC1FC-915A-4428-8952-CDC79EABC3F4}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/App.xaml	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,3 @@
+<Application x:Class="SilverlightValidation.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" />
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/App.xaml.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,59 @@
+using System;
+using System.Windows;
+
+namespace SilverlightValidation
+{
+    public partial class App : Application
+    {
+
+        public App()
+        {
+            this.Startup += this.Application_Startup;
+            this.Exit += this.Application_Exit;
+            this.UnhandledException += this.Application_UnhandledException;
+
+            InitializeComponent();
+        }
+
+        private void Application_Startup(object sender, StartupEventArgs e)
+        {
+            this.RootVisual = new UserView();
+        }
+
+        private void Application_Exit(object sender, EventArgs e)
+        {
+
+        }
+
+        private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
+        {
+            // If the app is running outside of the debugger then report the exception using
+            // the browser's exception mechanism. On IE this will display it a yellow alert 
+            // icon in the status bar and Firefox will display a script error.
+            if (!System.Diagnostics.Debugger.IsAttached)
+            {
+
+                // NOTE: This will allow the application to continue running after an exception has been thrown
+                // but not handled. 
+                // For production applications this error handling should be replaced with something that will 
+                // report the error to the website and stop the application.
+                e.Handled = true;
+                Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
+            }
+        }
+
+        private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)
+        {
+            try
+            {
+                string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace;
+                errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n");
+
+                System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight Application " + errorMsg + "\");");
+            }
+            catch (Exception)
+            {
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/Properties/AppManifest.xml	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,6 @@
+<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+>
+    <Deployment.Parts>
+    </Deployment.Parts>
+</Deployment>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/Properties/AssemblyInfo.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SilverlightValidation")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SilverlightValidation")]
+[assembly: AssemblyCopyright("Copyright ©  2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("3f5a4c47-8fbd-4568-8728-e26ee6a51945")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers 
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/RelayCommand.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,41 @@
+using System;
+using System.Windows.Input;
+
+namespace SilverlightValidation
+{
+    public class RelayCommand : ICommand
+    {
+        public event EventHandler CanExecuteChanged;
+
+        readonly Action<object> _execute;
+        readonly Predicate<object> _canExecute;
+
+        public RelayCommand(Action<object> execute,
+                            Predicate<object> canExecute = null)
+        {
+            if (execute == null) throw new ArgumentNullException("execute");
+
+            _execute = execute;
+            _canExecute = canExecute;
+        }
+
+
+        public void UpdateCanExecuteCommand()
+        {
+            if (CanExecuteChanged != null)
+                CanExecuteChanged(this, new EventArgs());
+        }
+
+
+        public bool CanExecute(object parameter)
+        {
+            return _canExecute == null || _canExecute(parameter);
+        }
+
+
+        public void Execute(object parameter)
+        {
+            _execute(parameter);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/SilverlightValidation.csproj	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.50727</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{0C1CC1FC-915A-4428-8952-CDC79EABC3F4}</ProjectGuid>
+    <ProjectTypeGuids>{A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>SilverlightValidation</RootNamespace>
+    <AssemblyName>SilverlightValidation</AssemblyName>
+    <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
+    <TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
+    <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
+    <SilverlightApplication>true</SilverlightApplication>
+    <SupportedCultures>
+    </SupportedCultures>
+    <XapOutputs>true</XapOutputs>
+    <GenerateSilverlightManifest>true</GenerateSilverlightManifest>
+    <XapFilename>SilverlightValidation.xap</XapFilename>
+    <SilverlightManifestTemplate>Properties\AppManifest.xml</SilverlightManifestTemplate>
+    <SilverlightAppEntry>SilverlightValidation.App</SilverlightAppEntry>
+    <TestPageFileName>SilverlightValidationTestPage.html</TestPageFileName>
+    <CreateTestPage>true</CreateTestPage>
+    <ValidateXaml>true</ValidateXaml>
+    <EnableOutOfBrowser>false</EnableOutOfBrowser>
+    <OutOfBrowserSettingsFile>Properties\OutOfBrowserSettings.xml</OutOfBrowserSettingsFile>
+    <UsePlatformExtensions>false</UsePlatformExtensions>
+    <ThrowErrorsInValidation>true</ThrowErrorsInValidation>
+    <LinkedServerProject>
+    </LinkedServerProject>
+  </PropertyGroup>
+  <!-- This property group is only here to support building this project using the 
+       MSBuild 3.5 toolset. In order to work correctly with this older toolset, it needs 
+       to set the TargetFrameworkVersion to v3.5 -->
+  <PropertyGroup Condition="'$(MSBuildToolsVersion)' == '3.5'">
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>Bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;TRACE;SILVERLIGHT</DefineConstants>
+    <NoStdLib>true</NoStdLib>
+    <NoConfig>true</NoConfig>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>Bin\Release</OutputPath>
+    <DefineConstants>TRACE;SILVERLIGHT</DefineConstants>
+    <NoStdLib>true</NoStdLib>
+    <NoConfig>true</NoConfig>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="FluentValidation">
+      <HintPath>..\Libs\FluentValidation.dll</HintPath>
+    </Reference>
+    <Reference Include="mscorlib" />
+    <Reference Include="System.Windows" />
+    <Reference Include="system" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Windows.Controls, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\Libs\System.Windows.Controls.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Windows.Controls.Data.Input, Version=5.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\Libs\System.Windows.Controls.Data.Input.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Xml" />
+    <Reference Include="System.Windows.Browser" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="RelayCommand.cs" />
+    <Compile Include="UserModel.cs" />
+    <Compile Include="UserModelValidator.cs" />
+    <Compile Include="UserViewModel.cs" />
+    <Compile Include="ViewModelBase.cs" />
+    <Compile Include="UserView.xaml.cs">
+      <DependentUpon>UserView.xaml</DependentUpon>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </ApplicationDefinition>
+    <Page Include="UserView.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Properties\AppManifest.xml" />
+  </ItemGroup>
+  <ItemGroup />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\$(SilverlightVersion)\Microsoft.Silverlight.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+  <ProjectExtensions>
+    <VisualStudio>
+      <FlavorProperties GUID="{A1591282-1198-4647-A2B1-27E5FF5F6F3B}">
+        <SilverlightProjectProperties />
+      </FlavorProperties>
+    </VisualStudio>
+  </ProjectExtensions>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/UserModel.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,23 @@
+using System;
+
+namespace SilverlightValidation
+{
+    public interface ICloneable<T>
+    {
+        T Clone();
+    }
+
+    public class UserModel : IUserModel, ICloneable<UserModel>
+    {
+        public string Username { get; set; }
+        public string Email { get; set; }
+        public string Password { get; set; }
+        public DateTime DateOfBirth { get; set; }
+        public string Description { get; set; }
+
+        public UserModel Clone()
+        {
+            return (UserModel) this.MemberwiseClone(); 
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/UserModelValidator.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,32 @@
+using System;
+using FluentValidation;
+
+namespace SilverlightValidation
+{
+    public class UserModelValidator : AbstractValidator<IUserModel>
+    {
+        public UserModelValidator()
+        {
+            RuleFor(x => x.Username)
+                .NotEmpty()
+                .WithMessage("Username cannot be blank.");
+
+            RuleFor(x => x.Password)
+                .NotEmpty()
+                .WithMessage("Password cannot be blank.");
+
+            RuleFor(x => x.Email)
+                .EmailAddress()
+                .WithMessage("Email is required.");
+
+            RuleFor(x => x.DateOfBirth)
+                .Must(BeAValidDateOfBirth)
+                .WithMessage("Date of birth must be within the last 100 years.");
+        }
+
+        private bool BeAValidDateOfBirth(DateTime dateOfBirth)
+        {
+            return (dateOfBirth < DateTime.Today) && (dateOfBirth > DateTime.Today.AddYears(-100));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/UserView.xaml	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,109 @@
+<UserControl x:Class="SilverlightValidation.UserView"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
+             d:DesignHeight="350"
+             d:DesignWidth="400"
+             mc:Ignorable="d">
+
+    <Grid x:Name="LayoutRoot" Background="White">
+
+        <Grid.RowDefinitions>
+            <RowDefinition Height="30" />
+            <RowDefinition Height="30" />
+            <RowDefinition Height="30" />
+            <RowDefinition Height="30" />
+            <RowDefinition Height="30" />
+            <RowDefinition Height="30" />
+            <RowDefinition Height="50" />
+            <RowDefinition Height="120" />
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*" />
+            <ColumnDefinition Width="100" />
+            <ColumnDefinition Width="300" />
+            <ColumnDefinition Width="*" />
+        </Grid.ColumnDefinitions>
+
+        <Grid.Resources>
+            <Style TargetType="TextBlock">
+                <Setter Property="Height" Value="18" />
+                <Setter Property="FontFamily" Value="Segoe UI Light" />
+                <Setter Property="FontSize" Value="14" />
+                <Setter Property="VerticalAlignment" Value="Center" />
+            </Style>
+            <Style TargetType="TextBox">
+                <Setter Property="FontFamily" Value="Segoe UI Light" />
+                <Setter Property="FontSize" Value="14" />
+                <Setter Property="Height" Value="24" />
+                <Setter Property="VerticalAlignment" Value="Center" />
+            </Style>
+            <Style TargetType="sdk:DatePicker">
+                <Setter Property="FontSize" Value="12" />
+                <Setter Property="FontFamily" Value="Segoe UI Light" />
+                <Setter Property="Height" Value="24" />
+            </Style>
+            <Style TargetType="Button">
+                <Setter Property="Height" Value="22" />
+                <Setter Property="Margin" Value="5,0,0,0" />
+                <Setter Property="VerticalAlignment" Value="Center" />
+                <Setter Property="Width" Value="60" />
+            </Style>
+        </Grid.Resources>
+
+        <TextBlock Grid.Row="1"
+                   Grid.Column="1"
+                   Text="Username:" />
+        <TextBlock Grid.Row="2"
+                   Grid.Column="1"
+                   Text="Password:" />
+        <TextBlock Grid.Row="3"
+                   Grid.Column="1"
+                   Text="Email:" />
+        <TextBlock Grid.Row="4"
+                   Grid.Column="1"
+                   Text="Date of Birth:" />
+        <TextBlock Grid.Row="5"
+                   Grid.Column="1"
+                   Text="Description:" />
+
+        <TextBox Grid.Row="1"
+                 Grid.Column="2"
+                 Text="{Binding Username,
+                                Mode=TwoWay,
+                                NotifyOnValidationError=True}" />
+        <TextBox Grid.Row="2"
+                 Grid.Column="2"
+                 Text="{Binding Password,
+                                Mode=TwoWay,
+                                NotifyOnValidationError=True}" />
+        <TextBox Grid.Row="3"
+                 Grid.Column="2"
+                 Text="{Binding Email,
+                                Mode=TwoWay,
+                                NotifyOnValidationError=True}" />
+
+        <sdk:DatePicker Grid.Row="4"
+                        Grid.Column="2"
+                        SelectedDate="{Binding DateOfBirth,
+                                               Mode=TwoWay,
+                                               NotifyOnValidationError=True}" />
+
+        <TextBox Grid.Row="5"
+                 Grid.Column="2"
+                 Text="{Binding Description}" />
+
+        <StackPanel Grid.Row="6"
+                    Grid.Column="2"
+                    HorizontalAlignment="Right"
+                    Orientation="Horizontal">
+            <Button Command="{Binding OkCommand}" Content="OK" />
+            <Button Command="{Binding CancelCommand}" Content="Cancel" />
+        </StackPanel>
+
+        <sdk:ValidationSummary Grid.Row="7" Grid.Column="2" />
+
+    </Grid>
+</UserControl>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/UserView.xaml.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,15 @@
+using System.Windows.Browser;
+
+namespace SilverlightValidation
+{
+    public partial class UserView
+    {
+        public UserView()
+        {
+            InitializeComponent();
+            HtmlPage.Document.SetProperty("title", "Silverlight Validation");
+
+            this.DataContext = new UserViewModel(new UserModelValidator());
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/UserViewModel.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,194 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using FluentValidation;
+
+namespace SilverlightValidation
+{
+    public interface IUserModel
+    {
+        string Username { get; set; }
+        string Email { get; set; }
+        string Password { get; set; }
+        DateTime DateOfBirth { get; set; }
+        string Description { get; set; }
+    }
+
+    public interface IUserViewModel
+    {
+        ICommand OkCommand { get; set; }
+        ICommand CancelCommand { get; set; }
+    }
+
+    public class UserViewModel : ViewModelBase, IUserViewModel, IUserModel, IEditableObject
+    {
+        #region Fields
+
+        private readonly UserModelValidator _validator;
+        private UserModel _data;
+        private UserModel _backup;
+        private bool _isDirty;
+
+        #endregion
+
+        #region Constructor
+
+        public UserViewModel(UserModelValidator validator)
+        {
+            _validator = validator;
+
+            OkCommand = new RelayCommand(OkCommandExecute, OkCommandCanExecute);
+            CancelCommand = new RelayCommand(CancelCommandExecute, CancelCommandCanExecute);
+
+            ResetModels();
+
+            _backup = _data;
+        }
+
+        #endregion
+
+        private void ResetModels()
+        {
+            _backup = new UserModel();
+            _data = new UserModel();
+        }
+
+        private void RequeryAllCommandsCanExecute() 
+        {
+            
+            //this.OkCommand.();
+            //this.CancelCommand();
+        }
+
+        #region Property
+
+        private const string UsernameProperty = "Username";
+        public string Username
+        {
+            get { return _data.Username; }
+            set
+            {
+                if (_data.Username != value) { _data.Username = value; RaisePropertyChanged(UsernameProperty); }
+                ClearError(UsernameProperty);
+                var validationResult = _validator.Validate(this, UsernameProperty);
+                if (!validationResult.IsValid)
+                    validationResult.Errors.ToList().ForEach(x => SetError(UsernameProperty, x.ErrorMessage));
+            }
+        }
+
+        private const string PasswordProperty = "Password";
+        public string Password
+        {
+            get { return _data.Password; }
+            set
+            {
+                if (_data.Password != value) { _data.Password = value; RaisePropertyChanged(PasswordProperty); }
+                ClearError(PasswordProperty);
+                var validationResult = _validator.Validate(this, PasswordProperty);
+                if (!validationResult.IsValid)
+                    validationResult.Errors.ToList().ForEach(x => SetError(PasswordProperty, x.ErrorMessage));
+            }
+        }
+
+        private const string EmailProperty = "Email";
+        private string _email;
+        public string Email
+        {
+            get { return _data.Email; }
+            set
+            {
+                if (_data.Email != value) { _data.Email = value; RaisePropertyChanged(EmailProperty); }
+                ClearError(EmailProperty);
+                var validationResult = _validator.Validate(this, EmailProperty);
+                if (!validationResult.IsValid)
+                    validationResult.Errors.ToList().ForEach(x => SetError(EmailProperty, x.ErrorMessage));
+            }
+        }
+
+        private const string DateOfBirthProperty = "DateOfBirth";
+        public DateTime DateOfBirth
+        {
+            get { return _data.DateOfBirth; }
+            set
+            {
+                if (_data.DateOfBirth != value) { _data.DateOfBirth = value; RaisePropertyChanged(DateOfBirthProperty); }
+                ClearError(DateOfBirthProperty);
+                var validationResult = _validator.Validate(this, DateOfBirthProperty);
+                if (!validationResult.IsValid)
+                    validationResult.Errors.ToList().ForEach(x => SetError(DateOfBirthProperty, x.ErrorMessage));
+            }
+        }
+
+        private const string DescriptionProperty = "Description";
+        public string Description
+        {
+            get { return _data.Description; }
+            set
+            {
+                if (_data.Description != value) { _data.Description = value; RaisePropertyChanged(DescriptionProperty); }
+                ClearError(DescriptionProperty);
+                var validationResult = _validator.Validate(this, DescriptionProperty);
+                if (!validationResult.IsValid)
+                    validationResult.Errors.ToList().ForEach(x => SetError(DescriptionProperty, x.ErrorMessage));
+            }
+        }
+
+        #endregion
+
+        #region Commands
+
+        public ICommand OkCommand { get; set; }
+        public ICommand CancelCommand { get; set; }
+
+        private void OkCommandExecute(object obj)
+        {
+            MessageBox.Show("Saved to imaginary database :)");
+        }
+
+        private bool OkCommandCanExecute(object obj)
+        {
+            return !string.IsNullOrEmpty(Username);
+        }
+
+        private void CancelCommandExecute(object obj)
+        {
+
+        }
+
+        private bool CancelCommandCanExecute(object obj)
+        {
+            return !string.IsNullOrEmpty(Username);
+        }
+
+        #endregion
+
+        #region Implementation of IEditableObject
+
+        public void BeginEdit()
+        {
+            if (_isDirty) return;
+            _backup = _data;
+            _isDirty = true;
+        }
+
+        public void EndEdit()
+        {
+            if (_isDirty)
+            {
+                ResetModels();
+                _isDirty = false;
+            }
+        }
+
+        public void CancelEdit()
+        {
+            if (!_isDirty) return;
+            _data = _backup;
+            _isDirty = false;
+        }
+
+        #endregion
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SilverlightValidation/SilverlightValidation/ViewModelBase.cs	Wed Apr 18 22:28:46 2012 +0100
@@ -0,0 +1,74 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+
+namespace SilverlightValidation
+{
+    public class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
+    {
+        #region INotifyPropertyChanged method plus event
+
+        public event PropertyChangedEventHandler PropertyChanged = delegate { };
+
+        protected void RaisePropertyChanged(string propertyName)
+        {
+            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+        }
+
+        #endregion
+
+        #region IDataErrorInfo methods and helpers
+
+        private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();
+
+        public virtual bool HasErrors
+        {
+            get { return _errors.Count > 0; }
+        }
+
+        public void SetError(string propertyName, string errorMessage)
+        {
+            _errors[propertyName] = errorMessage;
+            RaisePropertyChanged(propertyName);
+        }
+
+        protected void ClearError(string propertyName)
+        {
+            _errors.Remove(propertyName);
+            RaisePropertyChanged(propertyName);
+        }
+
+        protected void ClearAllErrors()
+        {
+            var properties = _errors.Select(error => error.Key).ToList();
+
+            _errors.Clear();
+
+            foreach (string property in properties)
+                RaisePropertyChanged(property);
+        }
+
+        public string Error
+        {
+            get
+            {
+                var sb = new StringBuilder();
+
+                foreach (var item in _errors)
+                {
+                    sb.AppendLine(item.Value);
+                }
+
+                return sb.ToString();
+            }
+        }
+
+        public string this[string columnName]
+        {
+            get { return _errors.ContainsKey(columnName) ? _errors[columnName] : string.Empty; }
+        }
+
+        #endregion
+    }
+}