A Sample Silverlight 4 Application Using MEF, MVVM, and WCF RIA Services - Part 1
Article Series
This article is part one of a series on developing a Silverlight business application using MEF, MVVM Light, and WCF RIA Services.

- Part 1 - Introduction, Installation, and General Application Design Topics
- Part 2 - MVVM Light Topics
- Part 3 - Custom Authentication, Reset Password and User Maintenance
Contents
- Introduction
- Requirements
- Installation
- Architecture
- Custom Controls for Layout
- Dynamic Theming
- Next Steps
- History
Introduction
This sample application is the result of my initiative to learn Silverlight and WCF RIA Services. With my background of using WPF and MVVM for the past several years, I found that there is a lack of sample LOB applications that can combine the latest Silverlight enhancements with MVVM. This three part article series is my effort at creating such a sample. The choice of an issue tracking application comes from David Poll's PDC09 talk, and the design architecture is from Shawn Wildermuth's blog posts.
The main features of this issue tracking application are:
- Login screen provides custom authentication and password reset based on security question and answer.
- My Profile screen is for updating user information, password, security questions and answers.
- User Maintenance screen is only available to Admin users, and lets Admin user add/delete/update users.
- New Issue screen is for creating new issues (bugs, work items, spec defects, etc.).
- My Issues screen is for tracking all active and resolved issues assigned to a user.
- All Issues screen is for tracking all issues (Open, Active, Pending or Resolved).
- Bug Report screen provides a summary of bug trend, bug count and the functionality to print the summary.
- Four different Themes are available and can be applied dynamically at any time.
Requirements
In order to build the sample application, you need:
- Microsoft Visual Studio 2010
- Microsoft Silverlight 4 Tools for Visual Studio 2010
- Silverlight 4 Toolkit April 2010 (included in the sample solution)
- MVVM Light Toolkit V3 SP1 (included in the sample solution)
Installation
After downloading the setup package to a location on your local disk, we need to complete the following steps:
1. Installing the IssueVision Sample Database
To install the sample database, please run SqlServer_IssueVision_Schema.sql and SqlServer_IssueVision_InitialDataLoad.sql included in the setup package zip file. SqlServer_IssueVision_Schema.sql creates the database schema and database user IVUser
; SqlServer_IssueVision_InitialDataLoad.sql loads all the data needed to run this application, including the initial application user ID user1 and Admin user ID admin1 with passwords all set as P@ssword1234.
2. Installing the Web Setup Package
After the database setup, run setup.exe also included in the setup package zip file. This will install the IssueVision for Silverlight website.
When done installing the website, we can access the Silverlight application as follows:
Architecture
1. Solution Structure
Inside the sample solution file, projects are further organized into either client folder or server folder. Client folder includes all the projects that will be compiled into file IssueVision.Client.xap, and server folder consists of all the projects that will eventually run inside a web server environment.
For the projects inside the server folder:
IssueVision.Web
project is the main startup project. It includes the startup page Default.aspx, and Silverlight application package IssueVision.Client.xap.IssueVision.Data.Web
project is the server-side data access layer. It receives requests from clients, accesses the database through database userIVUser
, and returns the results back. The major components of this project includeIssueVision
Entity data model, and all relatedDomainService
classes.
For the projects inside the client folder:
IssueVision.Data
project has a WCF RIA Services link toIssueVision.Data.Web
, and therefore, hosts the generated client-side proxy code and shared source code. This project also includes all the client-side only partial classes that do not need to be duplicated on the server side.IssueVision.Common
project, as the name suggests, includes all the common interface classes and helper classes shared among other client projects.IssueVision.Model
project defines the Model of MVVM, and it has the following three model classes:AuthenticationModel
PasswordResetModel
IssueVisionModel
IssueVision.ViewModel
project is theViewModel
part of MVVM, and includes all nineViewModel
classes.IssueVison.Client
is the main client-side project, and is also the View of MVVM that hosts all the UI logic.
From the solution structure above, we should notice that MVVM provides good separation of concerns between UI and business logic in order to make those UIs easier to maintain by developers and designers. Next, let's visit the Model
, ViewModel
, and View
classes in more detail.
2. IssueVisionModel Class
We will discuss classes AuthenticationModel
and PasswordResetModel
in part 3, for now, let's focus on class IssueVisionModel
, the main Model (of MVVM) class for this application. IssueVisionModel
is based on the following interface IIssueVisionModel
:
public interface IIssueVisionModel : INotifyPropertyChanged
{
void GetIssueTypesAsync();
event EventHandler<EntityResultsArgs<IssueType>> GetIssueTypesComplete;
void GetPlatformsAsync();
event EventHandler<EntityResultsArgs<Platform>> GetPlatformsComplete;
void GetResolutionsAsync();
event EventHandler<EntityResultsArgs<Resolution>> GetResolutionsComplete;
void GetStatusesAsync();
event EventHandler<EntityResultsArgs<Status>> GetStatusesComplete;
void GetSubStatusesAsync();
event EventHandler<EntityResultsArgs<SubStatus>> GetSubStatusesComplete;
void GetUsersAsync();
event EventHandler<EntityResultsArgs<User>> GetUsersComplete;
void GetCurrentUserAsync();
event EventHandler<EntityResultsArgs<User>> GetCurrentUserComplete;
void GetSecurityQuestionsAsync();
event EventHandler<EntityResultsArgs<SecurityQuestion>> GetSecurityQuestionsComplete;
void GetMyIssuesAsync();
event EventHandler<EntityResultsArgs<Issue>> GetMyIssuesComplete;
void GetAllIssuesAsync();
event EventHandler<EntityResultsArgs<Issue>> GetAllIssuesComplete;
void GetAllUnresolvedIssuesAsync();
event EventHandler<EntityResultsArgs<Issue>> GetAllUnresolvedIssuesComplete;
void GetActiveBugCountByMonthAsync(Int32 numberOfMonth);
event EventHandler<InvokeOperationEventArgs> GetActiveBugCountByMonthComplete;
void GetResolvedBugCountByMonthAsync(Int32 numberOfMonth);
event EventHandler<InvokeOperationEventArgs> GetResolvedBugCountByMonthComplete;
void GetActiveBugCountByPriorityAsync();
event EventHandler<InvokeOperationEventArgs> GetActiveBugCountByPriorityComplete;
Issue AddNewIssue();
void RemoveAttribute(IssueVision.Data.Web.Attribute attribute);
void RemoveFile(IssueVision.Data.Web.File file);
User AddNewUser();
void RemoveUser(IssueVision.Data.Web.User user);
void SaveChangesAsync();
event EventHandler<SubmitOperationEventArgs> SaveChangesComplete;
void RejectChanges();
Boolean HasChanges { get; }
Boolean IsBusy { get; }
}
We define a separate model class and do not use the data context class itself as the model because the model is best expressed as a set of properties and operations that retrieve, add, delete and update data. This makes the model easier to maintain and test. Additionally, as Shawn mentioned in his blog, "creating a custom model allows us to isolate what transport layer we're using so we can change it or even have several data providers specifying data for our model".
Next, let's look at how a retrieve method in IssueVisionModel
class is actually implemented:
public void GetIssueTypesAsync()
{
PerformQuery<IssueType>(Context.GetIssueTypesQuery(), GetIssueTypesComplete);
}
GetIssueTypeAsync()
calls private
method PerformQuery()
and passes in an EntityQuery GetIssueTypesQuery()
and an event GetIssueTypesComplete
. When the retrieve call is done, event GetIssueTypesComplete
will fire and pass back the result set or any error message if something goes wrong. In fact, almost all retrieve methods are as simple as calling the PerformQuery()
method defined below:
private void PerformQuery<T>(EntityQuery<T> qry,
EventHandler<EntityResultsArgs<T>> evt) where T : Entity
{
Context.Load<T>(qry, LoadBehavior.RefreshCurrent, r =>
{
if (evt != null)
{
try
{
if (r.HasError)
{
evt(this, new EntityResultsArgs<T>(r.Error));
r.MarkErrorAsHandled();
}
else
{
evt(this, new EntityResultsArgs<T>(r.Entities));
}
}
catch (Exception ex)
{
evt(this, new EntityResultsArgs<T>(ex));
}
}
}, null);
}
Also, the model class exports itself to the ViewModel
classes by using MEF Export attribute on the class as follows:
[Export(typeof(IIssueVisionModel))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class IssueVisionModel : IIssueVisionModel
3. The ViewModel Classes
Most of the ViewModel
classes include six regions: Private Data Members region, Constructor region, Public Properties region, Public Commands region, ICleanup Interface region and Private Methods region. The Public Properties and Public Commands regions expose all the necessary properties and commands to its View class. And, the Constructor sets up event handling, sets initial values for any private
data, and registers AppMessages
needed inside ViewModel
class. Here is an example:
[ImportingConstructor]
public IssueEditorViewModel(IIssueVisionModel issueVisionModel)
{
_issueVisionModel = issueVisionModel;
// Set up event handling
_issueVisionModel.GetIssueTypesComplete += new EventHandler<EntityResultsArgs
<IssueType>>(_issueVisionModel_GetIssueTypesComplete);
_issueVisionModel.GetPlatformsComplete += new EventHandler
<EntityResultsArgs<Platform>>(_issueVisionModel_GetPlatformsComplete);
_issueVisionModel.GetResolutionsComplete += new EventHandler
<EntityResultsArgs<Resolution>>(_issueVisionModel_GetResolutionsComplete);
_issueVisionModel.GetStatusesComplete += new EventHandler
<EntityResultsArgs<Status>>(_issueVisionModel_GetStatusesComplete);
_issueVisionModel.GetSubStatusesComplete += new EventHandler
<EntityResultsArgs<SubStatus>>(_issueVisionModel_GetSubStatusesComplete);
_issueVisionModel.GetUsersComplete += new EventHandler
<EntityResultsArgs<User>>(_issueVisionModel_GetUsersComplete);
// load issue type entries
_issueVisionModel.GetIssueTypesAsync();
// load platform entries
_issueVisionModel.GetPlatformsAsync();
//load resolution entries
_issueVisionModel.GetResolutionsAsync();
// load status entries
_issueVisionModel.GetStatusesAsync();
// load substatus entries
_issueVisionModel.GetSubStatusesAsync();
// load user entries
_issueVisionModel.GetUsersAsync();
// register for EditIssueMessage
AppMessages.EditIssueMessage.Register(this, OnEditIssueMessage);
// register for SubmitChangesMessage
AppMessages.SubmitChangesMessage.Register(this, OnSubmitChangesMessage);
// register for CancelChangesMessage
AppMessages.CancelChangesMessage.Register(this, OnCancelChangesMessage);
}
We can see from the code above that the ViewModel
class gets an object that implements IIssueVisionModel
interface by using the ImportingConstructor
attribute which tells MEF to supply the discovered model class into the ViewModel
. In turn, all the ViewModel
classes export themselves like the following:
[Export(ViewModelTypes.IssueEditorViewModel)]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class IssueEditorViewModel : ViewModelBase
4. The View Classes and Code-behind Files
Each View
class finds its ViewModel
object through the Import attribute, and in the constructor of each View
class, we call the function CompositionInitializer.SatisfyImports(this)
. This function instructs MEF at runtime to fulfill a chain of dependencies, which in turn creates all the Model
and ViewModel
objects required. The beauty of using MEF is that we can keep these projects loosely coupled. In fact, projects IssueVision.Model
, IssueVision.ViewModel
, and IssueVison.Client
do not need a reference to the other two projects to compile successfully. Project IssueVison.Client
has a reference to the other two projects only because we need to add them into the output IssueVision.Client.xap file.
In the same constructor, we also register AppMessages
the View
class will handle. The IssueEditor
class below is a good sample:
public partial class IssueEditor : UserControl, ICleanup
{
public IssueEditor()
{
InitializeComponent();
if (!ViewModelBase.IsInDesignModeStatic)
{
// Use MEF To load the View Model
CompositionInitializer.SatisfyImports(this);
}
// register for ReadOnlyIssueMessage
AppMessages.ReadOnlyIssueMessage.Register(this, OnReadOnlyIssueMessage);
// register for OpenFileMessage
AppMessages.OpenFileMessage.Register(this, OnOpenFileMessage);
// register for SaveFileMessage
AppMessages.SaveFileMessage.Register(this, OnSaveFileMessage);
}
[Import(ViewModelTypes.IssueEditorViewModel)]
public object ViewModel
{
set
{
DataContext = value;
}
}
.........
}
Within the code-behind files, we define all the UI-related logic like event handlers to dynamically enable/disable a button or AppMessages
that display an error message when something goes wrong. As long as the code is related to the UI logic, it is perfectly OK to add them into the code-behind file, like the following:
private void userNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
// dynamically enable/disable error message
if (!string.IsNullOrWhiteSpace(this.loginScreenErrorMessageTextBox.Text))
this.loginScreenErrorMessageTextBox.Text = string.Empty;
// dynamically enable/disable login button
this.loginButton.IsEnabled =
!(string.IsNullOrWhiteSpace(this.userNameTextBox.Text) ||
string.IsNullOrWhiteSpace(this.passwordPasswordBox.Password));
}
private void passwordPasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
// dynamically enable/disable error message
if (!string.IsNullOrWhiteSpace(this.loginScreenErrorMessageTextBox.Text))
this.loginScreenErrorMessageTextBox.Text = string.Empty;
// dynamically enable/disable login button
this.loginButton.IsEnabled =
!(string.IsNullOrWhiteSpace(this.userNameTextBox.Text) ||
string.IsNullOrWhiteSpace(this.passwordPasswordBox.Password));
}
Custom Controls for Layout
Custom controls FlipControl
and MainPageControl
defined in project IssueVision.Common
are used as the basis for screen layout.
FlipControl
is used in LoginForm.xaml, which hosts both login screen and password reset screen. Switching the dependency property IsFlipped
will toggle between these two screens with animations defined inside the VisualStateManager
.
Similarly, MainPageControl
is used in MainPage.xaml, and divides the whole screen into title content, login/logout menu contents, login page content, and main page content. Dependency property IsLoggedIn
switches between the login page and main page.
Using these layout custom controls could be considered as another application of separation of concerns. The screen layout styles along with animations defined in VisualStateManager
is encapsulated by itself. As long as they provide the same functionality, we can easily change them, let's say creating a new animation, without affecting any View
classes defined in project IssueVision.Client
.
Dynamic Theming
There are four different themes defined in this application, and they are BureauBlue
, ExpressionLight
, ShinyBlue
, and TwilightBlue
. Each theme is included in project IssueVision.Client
as a ResourceDictionary
, which defines all the styles for built-in controls as well as styles for custom controls built specifically for this sample. They are in the Assets folder shown below:
When we want to dynamically change themes, ChangeThemmeCommand
will get called, and following is the source code:
private RelayCommand<string> _changeThemeCommand = null;
public RelayCommand<string> ChangeThemeCommand
{
get
{
if (_changeThemeCommand == null)
{
_changeThemeCommand = new RelayCommand<string>(
g => this.OnChangeThemeCommand(g),
g =>
{
var themeResource = Application.GetResourceStream
(new Uri("/IssueVision.Client;component/Assets/" +
g, UriKind.Relative));
return themeResource != null;
});
}
return _changeThemeCommand;
}
}
private void OnChangeThemeCommand(String g)
{
try
{
if (g == "BureauBlue.xaml" || g == "ExpressionLight.xaml" ||
g == "ShinyBlue.xaml" || g == "TwilightBlue.xaml")
{
// remove the old one
Application.Current.Resources.MergedDictionaries.RemoveAt
(Application.Current.Resources.MergedDictionaries.Count - 1);
// find and add the new one
var themeResource = Application.GetResourceStream(new Uri
("/IssueVision.Client;component/Assets/" +
g, UriKind.Relative));
ResourceDictionary rd = (ResourceDictionary)(XamlReader.Load
(new StreamReader(themeResource.Stream).ReadToEnd()));
Application.Current.Resources.MergedDictionaries.Add(rd);
// notify the change
if (g == "BureauBlue.xaml")
{
IsBureauBlueTheme = true;
IsExpressionLightTheme = false;
IsShinyBlueTheme = false;
IsTwilightBlueTheme = false;
}
else if (g == "ExpressionLight.xaml")
{
IsBureauBlueTheme = false;
IsExpressionLightTheme = true;
IsShinyBlueTheme = false;
IsTwilightBlueTheme = false;
}
else if (g == "ShinyBlue.xaml")
{
IsBureauBlueTheme = false;
IsExpressionLightTheme = false;
IsShinyBlueTheme = true;
IsTwilightBlueTheme = false;
}
else if (g == "TwilightBlue.xaml")
{
IsBureauBlueTheme = false;
IsExpressionLightTheme = false;
IsShinyBlueTheme = false;
IsTwilightBlueTheme = true;
}
}
}
catch (Exception ex)
{
// notify user if there is any error
AppMessages.RaiseErrorMessage.Send(ex);
}
}
I like the flexibility of using ResourceDictionary
directly for dynamic theming because we can easily modify them any time if there is a bug found or any enhancements is needed. Also, we have the option to define our own styles for any custom controls as follows:
<ResourceDictionary>
.........
<!--IssueVision Specific Styles-->
<LinearGradientBrush x:Key="IssueVisionBackgroundBrush" EndPoint="1,0.5"
StartPoint="0,0.5">
<GradientStop Color="#FFBFDBFF" Offset="0"/>
<GradientStop Color="#FFA6C2E5" Offset="1"/>
</LinearGradientBrush>
<!--common:MainPageControl-->
<Style TargetType="common:MainPageControl">
<Setter Property="Background"
Value="{StaticResource IssueVisionBackgroundBrush}"/>
</Style>
<!--common:FlipControl-->
<Style TargetType="common:FlipControl">
<Setter Property="Background"
Value="{StaticResource IssueVisionBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="DarkBlue"/>
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
</ResourceDictionary>
Next Steps
In this article, we visited how the application is installed, as well as design architecture, layout custom controls and dynamic theming. In part 2, we will go through the topics of how MVVM Light Toolkit is used: namely, RelayCommand
, Messenger
, EventToCommand
, and ICleanup
.
I hope you find this article useful, and please rate and/or leave feedback below. Thank you!
History
- May 2010 - Initial release
- July 2010 - Minor update based on feedback
- November 2010 - Update to support VS2010 Express Edition
Post Comment
uYMrAg I wish too learn evven more things about it!
7l0K76 I\ ave been looking for something that does all those things you just mentioned. Can you recommend a good one?
aMvi0g Thanks , I ave recently been searching for information approximately this subject for a long
from your to sperm, the. The tooth life and suffering were causes must people of of as treatments to possible.