Thursday, 19 April 2012

Silverlight 5 Validation

Moving forward with .NET 4.5 the validation story will be played out through the INotifyDataErrorInfo interface.

http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifydataerrorinfo(v=vs.110).aspx

Here is an example of a Silverlight 5 grid and form using INDEI for its validation:

This demo can also be accessed online here.

Tricks

This example shows the following tricks when validating user input in Silverlight:

  • MVVM with independent validation rules allowing for contextual validation with injection into view model via the constructor
  • No validation is applied when first loading up the form
  • Validation is applied when each control is updated, with form wide validation being applied when the user clicks the Ok button
  • DatePicker control is loaded with no date and also prevents user textual input along with its validation
  • Fluent Validation uses length, email and regex rules to validate properties
  • View model implements INotifyPropertyChanged, INotifyDataErrorInfo and IEditableObject interfaces
  • INotifyPropertyChanged and INotifyDataErrorInfo implementations are stored within an abstract view model base class
  • Model implements bespoke generic ICloneable interface
  • Cancel (by pressing escape) on grid reverts the data and controls back to original state
  • RelayCommand has been used for the command buttons

Class diagram

image

Styles

This example explicitly contains all styles used in this solution, within the app.xaml file.

Screenshots

Description viewer

Used to show if a field is required and/or any of other relevant info, when the user hovers over the little circle with the i for information to the right of the control.

image

Validation Summary

Lists all validation errors on the grid and form.

image

image

Validation Tooltips

image

image

image

image

Validator code

using System;
using FluentValidation;

namespace SilverlightValidation
{
public class UserModelValidator : AbstractValidator<IUserModel>
{
public UserModelValidator()
{
RuleFor(x => x.Username)
.Length(3, 8)
.WithMessage("Must be between 3-8 characters.");

RuleFor(x => x.Password)
.Matches(@"^\w*(?=\w*\d)(?=\w*[a-z])(?=\w*[A-Z])\w*$")
.WithMessage("Must contain lower, upper and numeric chars.");

RuleFor(x => x.Email)
.EmailAddress()
.WithMessage("A valid email address is required.");

RuleFor(x => x.DateOfBirth)
.Must(BeAValidDateOfBirth)
.WithMessage("Must be within 100 years of today.");
}

private bool BeAValidDateOfBirth(DateTime? dateOfBirth)
{
if (dateOfBirth == null) return false;
if (dateOfBirth.Value > DateTime.Today || dateOfBirth < DateTime.Today.AddYears(-100))
return false;
return true;
}
}
}


ViewModelBase code

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace SilverlightValidation
{
public class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
#region INotifyPropertyChanged method plus event

public event PropertyChangedEventHandler PropertyChanged = delegate { };

protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

#endregion

#region INotifyDataErrorInfo methods and helpers

private readonly Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

public void SetError(string propertyName, string errorMessage)
{
if (!_errors.ContainsKey(propertyName))
_errors.Add(propertyName, new List<string> { errorMessage });

RaiseErrorsChanged(propertyName);
}

protected void ClearError(string propertyName)
{
if (_errors.ContainsKey(propertyName))
_errors.Remove(propertyName);

RaiseErrorsChanged(propertyName);
}

protected void ClearAllErrors()
{
var errors = _errors.Select(error => error.Key).ToList();

foreach (var propertyName in errors)
ClearError(propertyName);
}

public void RaiseErrorsChanged(string propertyName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };

public IEnumerable GetErrors(string propertyName)
{
return _errors.ContainsKey(propertyName)
? _errors[propertyName]
: null;
}

public bool HasErrors
{
get { return _errors.Count > 0; }
}

#endregion
}
}


UserListViewModel code

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Input;
using SilverlightValidation.Commands;
using SilverlightValidation.Models;
using SilverlightValidation.Validators;
using SilverlightValidation.Views;
using GalaSoft.MvvmLight.Messaging;
using SilverlightValidation.Messages;

namespace SilverlightValidation.ViewModels
{
public class UserListViewModel
{
UserView window;

public UserListViewModel(IList<UserModel> models, UserModelValidator validator)
{
Data = new ObservableCollection<UserViewModel>();

foreach (var model in models)
Data.Add(new UserViewModel(model, validator));

AddCommand = new RelayCommand(AddCommandExecute);
DeleteCommand = new RelayCommand(DeleteCommandExecute);

Messenger.Default.Register<UserViewResponseMessage>(this, UserViewResponseMessageReceived);
}

private void UserViewResponseMessageReceived(UserViewResponseMessage userViewResponseMessage)
{
if (userViewResponseMessage.UserViewModel != null)
Data.Add(userViewResponseMessage.UserViewModel);
window.Close();
}

#region Properties

public ObservableCollection<UserViewModel> Data { get; set; }

public UserViewModel SelectedItem { get; set; }

#endregion

#region Commands

public ICommand AddCommand { get; set; }
public ICommand DeleteCommand { get; set; }

private void AddCommandExecute(object obj)
{
window = new UserView();
window.Show();
}

private void DeleteCommandExecute(object obj)
{
if (SelectedItem!=null)
Data.Remove(SelectedItem);
}

#endregion
}
}


UserViewModel

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using FluentValidation;
using SilverlightValidation.Interfaces;
using SilverlightValidation.Validators;
using SilverlightValidation.Models;
using SilverlightValidation.Commands;
using GalaSoft.MvvmLight.Messaging;
using SilverlightValidation.Messages;

namespace SilverlightValidation.ViewModels
{
public class UserViewModel : ViewModelBase, IUserModel, IEditableObject
{
#region Fields

private readonly UserModelValidator _validator;
private UserModel _data;
private UserModel _backup;

#endregion

#region Constructor

public UserViewModel(UserModel model, UserModelValidator validator)
{
_validator = validator;
_data = model;
_backup = model.Clone();

OkCommand = new RelayCommand(OkCommandExecute);
CancelCommand = new RelayCommand(CancelCommandExecute);
}

#endregion

#region Methods

private void SetProperties(IUserModel source)
{
_data.Username = source.Username;
_data.Password = source.Password;
_data.Email = source.Email;
_data.DateOfBirth = source.DateOfBirth;
_data.Description = source.Description;
}

#endregion

#region Properties

private const string UsernameProperty = "Username";
public string Username
{
get { return _data.Username; }
set
{
if (_data.Username != value)
{
_data.Username = value;
RaisePropertyChanged(UsernameProperty);
IsChanged = true;
}

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);
IsChanged = true;
}

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";
public string Email
{
get { return _data.Email; }
set
{
if (_data.Email != value)
{
_data.Email = value;
RaisePropertyChanged(EmailProperty);
IsChanged = true;
}

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);
IsChanged = true;
}

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);
IsChanged = true;
}

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)
{
RefreshToViewErrors();

if (IsChanged && !HasErrors)
{
// save here
Messenger.Default.Send<UserViewResponseMessage>(
new UserViewResponseMessage() { UserViewModel = this });
}
}

// in case user hasn't touched the form
private void RefreshToViewErrors()
{
Username = _data.Username;
Password = _data.Password;
Email = _data.Email;
DateOfBirth = _data.DateOfBirth;
}

private void CancelCommandExecute(object obj)
{
Messenger.Default.Send<UserViewResponseMessage>(
new UserViewResponseMessage() { UserViewModel = null });
}

#endregion

private void ResetFormData()
{
SetProperties(_backup);
ClearAllErrors();
IsChanged = false;
}

public bool IsChanged { get; private set; }

#region IEditableObject for datagrid

private bool inEdit;
public void BeginEdit()
{
if (inEdit) return;
inEdit = true;
}

public void CancelEdit()
{
if (!inEdit) return;
inEdit = false;
ResetFormData();
}

public void EndEdit()
{
if (!inEdit) return;
}

#endregion
}
}


Model code

using System;
using System.ComponentModel;

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 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 static UserModel Create()
{
return new UserModel() { Username = "", Email = "", Password = "", DateOfBirth = null, Description = "" };
}

public UserModel Clone()
{
return (UserModel) this.MemberwiseClone();
}
}
}

UserListView code

<UserControl x:Class="SilverlightValidation.Views.UserListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
d:DesignHeight="400"
d:DesignWidth="725"
mc:Ignorable="d">

<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="40" />
<RowDefinition Height="300" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="725" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<StackPanel Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Width="60"
Command="{Binding AddCommand}"
Content="Add"
Style="{StaticResource ButtonStyle}" />
<Button Width="60"
Command="{Binding DeleteCommand}"
Content="Delete"
Style="{StaticResource ButtonStyle}" />
</StackPanel>

<controls:DataGrid Grid.Row="2"
Grid.Column="1"
AutoGenerateColumns="False"
ItemsSource="{Binding Data}"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Width="125"
Binding="{Binding Username,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}"
Header="Username" />
<controls:DataGridTemplateColumn Width="125" Header="Password">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<PasswordBox Password="{Binding Password, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
<controls:DataGridTextColumn Width="150"
Binding="{Binding Email,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}"
Header="Email" />

<controls:DataGridTemplateColumn Width="150" Header="Date of Birth">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<sdk:DatePicker KeyDown="DatePicker_KeyDown" SelectedDate="{Binding DateOfBirth, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
<controls:DataGridTextColumn Width="150"
Binding="{Binding Description,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}"
Header="Description" />
</controls:DataGrid.Columns>
</controls:DataGrid>
</Grid>
</UserControl>


UserView code

<c:ChildWindow x:Class="SilverlightValidation.Views.UserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:p="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
Title="Add User"
Width="500"
Height="400">

<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="30" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>

<TextBlock Grid.Row="1"
Grid.Column="1"
Style="{StaticResource LabelStyle}"
Text="Username:" />

<TextBox x:Name="tbUsername"
Grid.Row="1"
Grid.Column="2"
Style="{StaticResource TextBoxStyle}"
Text="{Binding Username,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}" />

<sdk:DescriptionViewer Grid.Row="1"
Grid.Column="3"
Width="20"
Description="Required"
Target="{Binding ElementName=tbUsername}" />

<TextBlock Grid.Row="2"
Grid.Column="1"
Style="{StaticResource LabelStyle}"
Text="Password:" />

<PasswordBox x:Name="tbPassword"
Grid.Row="2"
Grid.Column="2"
Password="{Binding Password,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}"
Style="{StaticResource PasswordBoxStyle}" />

<sdk:DescriptionViewer Grid.Row="2"
Grid.Column="3"
Width="20"
Description="Required"
Target="{Binding ElementName=tbPassword}" />

<TextBlock Grid.Row="3"
Grid.Column="1"
Style="{StaticResource LabelStyle}"
Text="Email:" />

<TextBox x:Name="tbEmail"
Grid.Row="3"
Grid.Column="2"
Style="{StaticResource TextBoxStyle}"
Text="{Binding Email,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}" />

<sdk:DescriptionViewer Grid.Row="3"
Grid.Column="3"
Width="20"
Description="Required"
Target="{Binding ElementName=tbEmail}" />

<TextBlock Grid.Row="4"
Grid.Column="1"
Style="{StaticResource LabelStyle}"
Text="Date of Birth:" />

<sdk:DatePicker x:Name="dpDateOfBirth"
Grid.Row="4"
Grid.Column="2"
KeyDown="DatePicker_KeyDown"
SelectedDate="{Binding DateOfBirth,
Mode=TwoWay,
ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True}"
Style="{StaticResource DatePickerStyle}" />
<sdk:DescriptionViewer Grid.Row="4"
Grid.Column="3"
Width="20"
Description="Required"
Target="{Binding ElementName=dpDateOfBirth}" />

<TextBlock x:Name="tbDescription"
Grid.Row="5"
Grid.Column="1"
Style="{StaticResource LabelStyle}"
Text="Description:" />

<TextBox Grid.Row="5"
Grid.Column="2"
Style="{StaticResource TextBoxStyle}"
Text="{Binding Description}" />
<StackPanel Grid.Row="6"
Grid.Column="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button Command="{Binding OkCommand}"
Content="OK"
Style="{StaticResource ButtonStyle}" />
<Button Command="{Binding CancelCommand}"
Content="Cancel"
Style="{StaticResource ButtonStyle}" />
</StackPanel>

<sdk:ValidationSummary Grid.Row="7"
Grid.Column="1"
Grid.ColumnSpan="2"
Style="{StaticResource ValidationSummaryStyle}" />

</Grid>
</c:ChildWindow>

Download the source


http://stevenhollidge.com/blog-source-code/SilverlightValidation.zip

Wednesday, 18 April 2012

Installing Linux VM on Windows

1. Download and install VM Player (you’ll need to create an account and register):

https://my.vmware.com/web/vmware/info/slug/desktop_end_user_computing/vmware_player/4_0

2. Download and install Ubuntu:

http://www.ubuntu.com/download/ubuntu/download

Debugger Tricks

DebuggerDisplay Attribute

image

DebuggerHidden Attribute

If you Step-Into line 10 the debugger will ignore your request and step over.  Useful for preventing tedious code debugging steps.

using System.Diagnostics;

namespace DebuggerTricks
{
class Program
{
static void Main(string[] args)
{
var calculator = new Calculator(1, 2);
var result = calculator.Add();
}

public class Calculator
{
public int a { get; set; }
public int b { get; set; }

public Calculator(int a, int b)
{
this.a = a;
this.b = b;
}

[DebuggerHidden]
public int Add()
{
return a + b;
}
}
}
}

MOQ Samples

MOQ like RhinoMocks uses Castle Dynamic Proxy under the covers to generate “objects” from classes or interfaces.

Here is a quick look at the MOQ Store sample contained within the source: http://code.google.com/p/moq/

Stories

image

Class Diagram

image

Tests

ShouldSetViewCategories

[Test]
public void ShouldSetViewCategories()
{
// Arrange
var catalog = new Mock<ICatalogService>();
var view = new Mock<IProductsView>();

// Act
var presenter = new ProductsPresenter(catalog.Object, view.Object);

// Assert
view.Verify(v => v.SetCategories(It.IsAny<IEnumerable<Category>>()));
}

Shows how Moq generates a real object from an interface, stored in the mock object property
“Verify” extension method from Moq verifies that the expression was called on the mock object
It.IsAny<T>() is generating a default dummy value for the SetCategories, just to check it can be called


ShouldCategorySelectionSetProducts

[Test]
public void ShouldCategorySelectionSetProducts()
{
// Arrange
var catalog = new Mock<ICatalogService>();
var view = new Mock<IProductsView>();
var presenter = new ProductsPresenter(catalog.Object, view.Object);

// Act
view.Raise(
v => v.CategorySelected += null,
new CategoryEventArgs(new Category { Id = 1 }));

// Assert
view.Verify(v => v.SetProducts(It.IsAny<IEnumerable<Product>>()));
}

Shows an event being raised on a mocked object


ShouldPlaceOrderIfEnoughInventory

[Test]
public void ShouldPlaceOrderIfEnoughInventory()
{
// Arrange
var catalog = new Mock<ICatalogService>();
var view = new Mock<IProductsView>();
var presenter = new ProductsPresenter(catalog.Object, view.Object);
var order = new Order
{
Product = new Product { Id = 1 },
Quantity = 5
};

catalog
.Setup(c => c.HasInventory(1, 5))
.Returns(true);

// Act
presenter.PlaceOrder(order);

// Assert
Assert.IsTrue(order.Filled);
catalog.Verify(c => c.HasInventory(1, 5));
}

“Setups” the result of True for the mock object if method HasInventory(1, 5) is called.
NOTE:  presenter.PlaceOrder(order) internally calls HasInventory(1, 5) to set the order.Filled property


ShouldNotPlaceOrderIfNotEnoughInventory

[Test]
public void ShouldNotPlaceOrderIfNotEnoughInventory()
{
// Arrange
var catalog = new Mock<ICatalogService>();
var view = new Mock<IProductsView>();
var presenter = new ProductsPresenter(catalog.Object, view.Object);
var order = new Order
{
Product = new Product { Id = 1 },
Quantity = 5
};

catalog
.Setup(c => c.HasInventory(1, 5))
.Returns(false);

// Act
presenter.PlaceOrder(order);

// Assert
Assert.IsFalse(order.Filled);
catalog.Verify(c => c.HasInventory(1, 5));
}

As per the previous test notes.


ShouldNotPlaceOrderIfFailsToRemove

[Test]
public void ShouldNotPlaceOrderIfFailsToRemove()
{
// Arrange
var catalog = new Mock<ICatalogService>();
var view = new Mock<IProductsView>();
var presenter = new ProductsPresenter(catalog.Object, view.Object);
var order = new Order
{
Product = new Product { Id = 1 },
Quantity = 5
};

catalog
.Setup(c => c.HasInventory(1, 5))
.Returns(true);
catalog
.Setup(c => c.Remove(1, 5))
.Throws<InvalidOperationException>();

// Act
presenter.PlaceOrder(order);

// Assert
Assert.IsFalse(order.Filled);
catalog.Verify(c => c.HasInventory(1, 5));
catalog.Verify(c => c.Remove(1, 5));
}

Source Code


http://stevenhollidge.com/blog-source-code/Moq-StoreSample.zip

Monday, 16 April 2012

RavenDb Quick Start

image

Introduction

Continuing the NoSQL trend in recent years RavenDb is a .NET LINQ enabled document storage engine with super fast text search capabilities thanks to Lucene.NET.  It also enables sharding out of the box which is a great feature for scalability.

It exposes a HTTP REST interface, which gives it far greater flexibility when scaling - no nasty firewalls getting in the way.

The most compelling reason from my point of view for using RavenDb over SQL Server is the ability to store a complete document (read .NET object model) without having to hit multiple tables.  SQL Server file IO can be crippling when dealing with large sets of data spread across huge tables. 

For example, if you had a customer with addresses, orders and payments in SQL you would have to access at least 4 tables (probably more) whereas in RavenDb the complete customer entity is stored as one document with one ID value.  One fast lookup and the data, which is stored internally within RavenDb as JSON, is rehydrated into the full .NET object.

Download

From the http://ravendb.net/download website you can download the latest build and/or source.

Or you can use Nuget directly from Visual Studio:

Client and Server package (recommended) http://nuget.org/packages/RavenDB
Embedded package http://nuget.org/packages/RavenDB-Embedded

 

Deployment

You have four main options for running RavenDb:

  • As a service under its own web server
  1. Download the build file from the website and extract the zip
  2. Go to the Server directory
  3. Execute the following command on the command line: Raven.Server.exe /install
    Note: Raven may ask you for administrator privileges while installing the service.  Configuration including port number is taken from Raven.Server.exe.config (defaults to port 8080).
  • Under IIS

http://ravendb.net/docs/server/deployment/as-iis-application

  • Embedded within your own application

http://ravendb.net/docs/server/deployment/embedded

  • Within a Command Prompt window, perhaps for development work with manual start up
    1. Download the build file from the website and extract the zip
    2. Run the Start.cmd batch file in the root directory.

Warning

IMPORTANT: To save any potential headaches, make sure you use the same build numbers for client and server!

RavenDB Management Studio

From the browser you can use the Management Studio, a great little tool written in Silverlight.  It enables you to write indexes (the map/reduce equivalent of queries in SQL), view/edit the JSON documents directly, run backups, import/export data or view the logs.

image

By default the Management Studio comes with a button you can press to load a sample Albums database.

The following examples are based on samples taken from the RavenDb source code.

Writing and Reading data from .NET

using System;
using Raven.Client.Document;

namespace Raven.Sample
{
class Program
{
static void Main(string[] args)
{
var documentStore1 = new DocumentStore
{ Url = "http://localhost:8080" }.Initialize();

using (var session1 = documentStore1.OpenSession())
{
session1.Store(new User { Id = "users/ayende", Name = "Ayende" });
session1.SaveChanges();
}

using (var session1 = documentStore1.OpenSession())
{
Console.WriteLine(session1.Load<User>("users/ayende").Name);
}

Console.WriteLine("Wrote and read one document to 8080");
}
}

public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
}



Sharding Example from RavenDb Source

//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="Hibernating Rhinos LTD">
// Copyright (c) Hibernating Rhinos LTD. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Raven.Client.Document;
using Raven.Client.Shard;
using Raven.Client;

namespace Raven.Sample.ShardClient
{
class Program
{
static void Main()
{
var shards = new Dictionary<string, IDocumentStore>
{
{"Asia", new DocumentStore {Url = "http://localhost:8080"}},
{"Middle-East", new DocumentStore {Url = "http://localhost:8081"}},
{"America", new DocumentStore {Url = "http://localhost:8082"}},
};

var shardStrategy = new ShardStrategy(shards)
.ShardingOn<Company>(x => x.Region)
.ShardingOn<Invoice>(x => x.CompanyId);

using (var documentStore = new ShardedDocumentStore(shardStrategy).Initialize())
{
new InvoicesAmountByDate().Execute(documentStore);

using (var session = documentStore.OpenSession())
{
var asian = new Company { Name = "Company 1", Region = "Asia" };
session.Store(asian);
var middleEastern = new Company { Name = "Company 2", Region = "Middle-East" };
session.Store(middleEastern);
var american = new Company { Name = "Company 3", Region = "America" };
session.Store(american);

session.Store(new Invoice { CompanyId = american.Id, Amount = 3, IssuedAt = DateTime.Today.AddDays(-1) });
session.Store(new Invoice { CompanyId = asian.Id, Amount = 5, IssuedAt = DateTime.Today.AddDays(-1) });
session.Store(new Invoice { CompanyId = middleEastern.Id, Amount = 12, IssuedAt = DateTime.Today });
session.SaveChanges();
}


using (var session = documentStore.OpenSession())
{
var reduceResults = session.Query<InvoicesAmountByDate.ReduceResult, InvoicesAmountByDate>()
.ToList();

foreach (var reduceResult in reduceResults)
{
string dateStr = reduceResult.IssuedAt.ToString("MMM dd, yyyy", CultureInfo.InvariantCulture);
Console.WriteLine("{0}: {1}", dateStr, reduceResult.Amount);
}
Console.WriteLine();
}
}
}

}
}



Caching

using (session.Advanced.DocumentStore.AggressivelyCacheFor(TimeSpan.FromMinutes(5)))
{
session.Load<User>("users/1");
}

Authentication


RavenDB supports Windows and OAuth security models.  You can also add support for custom users within RavenDb by implementing IAuthenticateClient.


By default, Windows anonymous access is enabled for GET only access. This can be amended in the Raven.Server.exe.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Raven/Port" value="*"/>
<add key="Raven/DataDir" value="~\Data"/>
<add key="Raven/AnonymousAccess" value="Get"/>
</appSettings>
<runtime>
<loadFromRemoteSources enabled="true"/>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Analyzers"/>
</assemblyBinding>
</runtime>
</configuration>


The full set of AnonymousAccess values are:  Get, All and None.

Friday, 13 April 2012

ServiceStack: REST with ProtoBuf

Service Stack

Service Stack makes services easy! 

You can download their excellent software and examples from the website:  http://www.servicestack.net/

This blog post combines three of their examples into one project: 

  • Web Service
  • REST Services
  • ProtoBuf plugin

Web Services in 5 easy Steps

1. Create an empty ASP.NET Web Application

new-web

2. Add ServiceStack via Nuget

servicestack-via-nuget

3. Create your DTO, Response and Service

// DTO
public class Hello
{
public string Name { get; set; }
}

// Response
public class HelloResponse
{
public string Result { get; set; }
}

//Service
public class HelloService : IService<Hello>
{
public object Execute(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}

4. Add your Global.asax


Notice typeof(AppHost).Assembly on line 11.  This allows us to pass in multiple assemblies that contain our services.  In this case we’re just working within the one assembly.

using System;
using System.Web;
using Funq;
using ServiceStack.Demo.WebService;
using ServiceStack.WebHost.Endpoints;

namespace ServiceStack.Demo
{
public class AppHost : AppHostBase
{
public AppHost() : base("ServiceStack makes services easy!", typeof(AppHost).Assembly) { }

public override void Configure(Container container)
{
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name}");
}
}

public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
new AppHost().Init();
}
}
}

5. Update your Web.Config


Notice line 6: <location path=”servicestack”>  This creates a virtual directory that will contain your services, in this example we’ve called it servicestack but it could be called anything we like.

<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<location path="servicestack">
<system.web>
<httpHandlers>
<add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*"/>
</httpHandlers>
</system.web>

<!-- Required for IIS 7.0 -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add path="*" name="ServiceStack.Factory" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
</handlers>
</system.webServer>
</location>

</configuration>


That’s it, ready to run!


image


Don’t panic, remember we set a virtual directory to hold all our services (see step 5)?

All we need to do is navigate to the servicestack folder.

Out of the box you get a nice metadata landing page, which lists all your hosted services:


image


If we run the default web service URL we see a nice HTML rendered presentation of our data.


image


Or we can append the format of our choice to the URL:


image


For Google Chrome to be able to render JSON I use the following plugin:


https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc 


image


?format=JSV would return us the following data:


image


JSV is a compact version of JSON, which by default Google Chrome cannot display in the browser.


NOTE TO CLIENTS: When using other clients to your services you can set the HTTP Accept header on the request to determine which response format you’ll receive.


image


I’m using RestClient-Tool to test my services: http://code.google.com/a/eclipselabs.org/p/restclient-tool/


Rest Services


Two simples steps:


1. Add your Services

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using ServiceStack.Common.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;

namespace ServiceStack.Demo.Rest
{
[Description("GET or DELETE a single movie by Id. Use POST to create a new Movie and PUT to update it")]
public class Movie
{
public Movie()
{
this.Genres = new List<string>();
}

public int Id { get; set; }
public string Title { get; set; }
public decimal Rating { get; set; }
public string Director { get; set; }
public DateTime ReleaseDate { get; set; }
public List<string> Genres { get; set; }
}

public class MovieResponse
{
public Movie Movie { get; set; }
}

public class MovieService : RestServiceBase<Movie>
{
/// GET /movies/{Id}
public override object OnGet(Movie movie)
{
// normally you would return a movie from the db
return new MovieResponse { Movie = movie };
}

/// POST /movies
///
/// returns HTTP Response =>
/// 201 Created
/// Location: http://localhost/ServiceStack.MovieRest/movies/{newMovieId}
///
/// {newMovie DTO in [xml|json|jsv|etc]}
public override object OnPost(Movie movie)
{
// insert a movie into your database... returns the new Id = 999
var newId = 999;
var newMovie = new MovieResponse { Movie = new Movie() { Id = newId } };

return new HttpResult(newMovie)
{
StatusCode = HttpStatusCode.Created,
Headers = {{ HttpHeaders.Location, this.RequestContext.AbsoluteUri.WithTrailingSlash() + newId }}
};
}

/// PUT /movies/{id}
public override object OnPut(Movie movie)
{
// save to db
return null;
}

/// DELETE /movies/{Id}
public override object OnDelete(Movie request)
{
// delete from db
return null;
}
}

[Description("Find movies by genre, or all movies if no genre is provided")]
public class Movies
{
public string Genre { get; set; }
}

public class MoviesResponse
{
public List<Movie> Movies { get; set; }
}

public class MoviesService : RestServiceBase<Movies>
{
/// GET /movies
/// GET /movies/genres/{Genre}
public override object OnGet(Movies request)
{
return new MoviesResponse { Movies = new List<Movie>() { new Movie() { Id=10 }} };
}
}
}

2. Add your routes to the Global.asax

public override void Configure(Container container)
{
Routes
.Add<Hello>("/hello")
.Add<Hello>("/hello/{Name}");

Routes
.Add<Movie>("/movies", "POST,PUT,DELETE")
.Add<Movie>("/movies/{Id}")
.Add<Movies>("/movies")
.Add<Movies>("/movies/genres/{Genre}");
}

Run the app!


Our services have been detected.


image


image


Adding ProtoBuf for Raw Performance


1. Add the plugin via Nuget


protobuf


Which adds WebActivator framework which runs code before the start of application in the ASP.NET pipeline.


ProtoBuf_AppStart is added and will run to handle the configuration.


2. Add Attributes to your DTOs


ProtoBuf requires attributes to explain the de/serialization order for the properties.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Runtime.Serialization;
using ServiceStack.Common.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;

namespace ServiceStack.Demo.Rest
{
[DataContract]
[Description("GET or DELETE a single movie by Id. Use POST to create a new Movie and PUT to update it")]
public class Movie
{
public Movie()
{
this.Genres = new List<string>();
}

[DataMember(Order = 1)] public int Id { get; set; }
[DataMember(Order = 2)] public string Title { get; set; }
[DataMember(Order = 3)] public decimal Rating { get; set; }
[DataMember(Order = 4)] public string Director { get; set; }
[DataMember(Order = 5)] public DateTime ReleaseDate { get; set; }
[DataMember(Order = 6)] public List<string> Genres { get; set; }
}

[DataContract]
public class MovieResponse
{
[DataMember(Order = 0)]
public Movie Movie { get; set; }
}

public class MovieService : RestServiceBase<Movie>
{
/// GET /movies/{Id}
public override object OnGet(Movie movie)
{
// normally you would return a movie from the db
return new MovieResponse { Movie = movie };
}

/// POST /movies
///
/// returns HTTP Response =>
/// 201 Created
/// Location: http://localhost/ServiceStack.MovieRest/movies/{newMovieId}
///
/// {newMovie DTO in [xml|json|jsv|etc]}
public override object OnPost(Movie movie)
{
// insert a movie into your database... returns the new Id = 999
var newId = 999;
var newMovie = new MovieResponse { Movie = new Movie() { Id = newId } };

return new HttpResult(newMovie)
{
StatusCode = HttpStatusCode.Created,
Headers = {{ HttpHeaders.Location, this.RequestContext.AbsoluteUri.WithTrailingSlash() + newId }}
};
}

/// PUT /movies/{id}
public override object OnPut(Movie movie)
{
// save to db
return null;
}

/// DELETE /movies/{Id}
public override object OnDelete(Movie request)
{
// delete from db
return null;
}
}

[DataContract]
[Description("Find movies by genre, or all movies if no genre is provided")]
public class Movies
{
[DataMember(Order = 1)]
public string Genre { get; set; }
}

[DataContract]
public class MoviesResponse
{
[DataMember(Order=1)]
public List<Movie> Movies { get; set; }
}


public class MoviesService : RestServiceBase<Movies>
{
/// GET /movies
/// GET /movies/genres/{Genre}
public override object OnGet(Movies request)
{
return new MoviesResponse { Movies = new List<Movie>() { new Movie() { Id=10 }} };
}
}
}

Ready to run!


Notice how the X-PROTOBUF column has been added.


image


image


image


image


Framework tests for ServiceStack,Plugins.ProtoBuf can be found here:



Blog Source

http://stevenhollidge.com/blog-source-code/ServiceStack.Demo.zip

Redis on Windows

Server

Description Link
Redis service, runtime executable, client and installer https://github.com/rgl/redis/downloads
Redis runtime executable https://github.com/dmajkic/redis/downloads

Note: Redis on Windows should be used for development only as it’s unsupported.

Client

Project Description Link
ServiceStack.Redis Lightning quick! https://github.com/ServiceStack/ServiceStack.Redis
BookSleeve Provides async support http://code.google.com/p/booksleeve/

 

26/04/2012  UPDATE

Microsoft have released an open source windows version:  https://github.com/MSOpenTech/redis

http://blogs.msdn.com/b/interoperability/archive/2012/04/26/here-s-to-the-first-release-from-ms-open-tech-redis-on-windows.aspx?utm_source=WillyDev.NET.Rss&utm_medium=twitter

Thursday, 12 April 2012

RX Frequency Cheat Sheet

Ok, so you’re down with observables and you get the use of where and select statements to restrict and project your observables/events.  Now you want to filter on a time frequency.

Here’s a quick way to visually plan out your pipeline with RX.

Basic Subscribe

The basics, you want to know about every single event:

image

var observable = Enumerable.Range(1, 1000000).ToObservable();
using (observable.Subscribe(Console.WriteLine))

Sample


You want to get the latest event every X seconds.


image

var observable = Enumerable.Range(1, 1000000).ToObservable();
using (observable
.Sample(TimeSpan.FromSeconds(1))
.Subscribe(Console.WriteLine))

Regulate (custom extension method)


Slow down the events to a manageable speed without losing any events.

image

Use the extension method provided by John Rayner


http://sharpfellows.com/post/Rx-Controlling-frequency-of-events.aspx

var observable = Enumerable.Range(1, 1000000).ToObservable();
using (observable
.Regulate(TimeSpan.FromSeconds(1))
.Subscribe(Console.WriteLine))

Buffer


Receive all events every X seconds.


image

IObservable<long> observable = Observable.Interval(TimeSpan.FromMilliseconds(250));
using (observable
.Buffer(TimeSpan.FromSeconds(1))
.Subscribe(x => { foreach (var item in x) Console.WriteLine(item); }))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}

 


Here’s a few other useful tricks:


Merge


Merge two event streams into one.


image

IObservable<int> observable1 = (new[] { 1, 3, 5, 7, 9 }).ToObservable();
IObservable<int> observable2 = (new[] { 2, 4, 6, 8, 10 }).ToObservable();

using (observable1
.Merge(observable2)
.Subscribe(Console.WriteLine))

Or like this:


image


ConCat


Concatenates two streams.


image

IObservable<int> observable1 = (new[] { 1, 3, 5, 7, 9 }).ToObservable();
IObservable<int> observable2 = (new[] { 2, 4, 6, 8, 10 }).ToObservable();

using (observable1
.Concat(observable2)
.Subscribe(Console.WriteLine))

Zip


From two streams, takes an event from each, applies a selector logic and returns an output.


image


IObservable<int> observable1 = (new[] {1, 3, 5, 7, 9}).ToObservable();
IObservable<int> observable2 = (new[] {2, 4, 6, 8, 10}).ToObservable();

using (observable1
.Zip(observable2, (x, y) => x > y ? x : y)
.Subscribe(Console.WriteLine))

Distinct Until Changed


Only receive events that are different from the previous event.


image

IObservable<int> observable = (new[] { 1, 1, 1, 2, 2, 3, 4, 4 })
.ToObservable();
using (observable
.DistinctUntilChanged()
.Subscribe(Console.WriteLine))

Interval


Fires the event every specified duration.


image

IObservable<long> observable = Observable.Interval(TimeSpan.FromSeconds(1));

using (observable.Subscribe(Console.WriteLine))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}

Delay


Delays the start of the event stream, preserving the interval between events.

image
IObservable<long> observable = Observable.Interval(TimeSpan.FromSeconds(0.5));

using (observable
.Delay(TimeSpan.FromSeconds(1))
.Subscribe(Console.WriteLine))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}

TimeInterval


Wraps the event, exposing the event as it’s value and an Interval property.  The interval is the amount of time since the previous event.


image

var observable = Observable.Interval(TimeSpan.FromMilliseconds(750)).TimeInterval();

using (observable.Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Interval)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}

Timestamp


Wraps the event, exposing the event as it’s value and a timestamp property.


image

var observable = Observable.Interval(TimeSpan.FromSeconds(1)).Timestamp();

using (observable.Subscribe(
x => Console.WriteLine("{0}: {1}", x.Value, x.Timestamp)))
{
Console.WriteLine("Press any key to unsubscribe");
Console.ReadKey();
}

Throttle


Holds back on receiving events until the throttle duration has elapsed between two events.


image

var dictionarySuggest = userInput
.Throttle(TimeSpan.FromMilliseconds(250))
.SelectMany(input => serverCall(input));

Monday, 9 April 2012

Custom Tooltip and Popup

Like with everything else in WPF you can override the style for tooltips.

Standard Tooltip

image_thumb3

Custom Tooltip

image_thumb1[1]

<Window.Resources>
<Style x:Key="CustomTooltip" TargetType="{x:Type ToolTip}">
<Setter Property="HorizontalOffset" Value="50" />
<Setter Property="VerticalOffset" Value="-50" />
<Setter Property="Background" Value="Beige" />
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="18" />
<Setter Property="FontFamily" Value="Segoe UI" />
</Style>
</Window.Resources>

<!-- the following code lives inside the control you want to tooltip -->
<Grid.ToolTip>
<ToolTip Style="{StaticResource CustomTooltip}">
<TextBlock>Custom tooltip</TextBlock>
</ToolTip>
</Grid.ToolTip>

This custom tooltip rather boringly just features a textblock but you can customise with any fonts and colours and include any combination of UI controls such as images, animations or even video.


Source:  http://stevenhollidge.com/blog-source-code/WrappingListbox-CustomTooltip.zip


Custom Tooltip with Shape (Path)


image_thumb1

<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="HorizontalOffset" Value="0" />
<Setter Property="VerticalOffset" Value="-75" />
<Setter Property="Background" Value="GhostWhite" />
<Setter Property="Foreground" Value="Gray" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Canvas Width="200" Height="100">
<Path x:Name="Container"
Canvas.Left="0"
Canvas.Top="0"
Margin="20"
Data="M 0,40 L15,50 15,80 150,80 150,0 15,0 15,30"
Fill="{TemplateBinding Background}"
Stroke="Black">
<Path.Effect>
<DropShadowEffect BlurRadius="10"
Opacity="0.5"
ShadowDepth="4" />
</Path.Effect>
</Path>
<TextBlock Canvas.Left="50"
Canvas.Top="28"
Width="100"
Height="65"
Text="{TemplateBinding Content}"
TextWrapping="Wrapwithoverflow" />
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Source:  http://stevenhollidge.com/blog-source-code/WrappingListbox-CustomTooltip-WithPath.zip


Popup


image_thumb[1]

<Popup x:Name="PopupInfo"
AllowsTransparency="True"
HorizontalOffset="-10"
IsOpen="{Binding ElementName=backgroundGrid,
Path=IsMouseOver,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}"
VerticalOffset="-30"
Placement="Right">

<Canvas Width="200" Height="100">
<Path x:Name="Container"
Canvas.Left="0"
Canvas.Top="0"
Margin="20"
Data="M 0,40 L15,50 15,80 150,80 150,0 15,0 15,30"
Fill="Beige"
Stroke="Black">
<Path.Effect>
<DropShadowEffect BlurRadius="10"
Opacity="0.5"
ShadowDepth="4" />
</Path.Effect>
</Path>
<TextBlock Canvas.Left="50"
Canvas.Top="28"
Width="100"
Height="65"
Text="Popup with text...."
TextWrapping="Wrapwithoverflow" />

</Canvas>
</Popup>

Source: http://stevenhollidge.com/blog-source-code/wpf-popup.zip