Introduction
The purpose of this project is to demonstrate both proper generation of a random password (or large randomized data set) as well as showcase the usage
of the incredibly useful Model-View-ViewModel architecture. MVVM is a pattern that affords separation of concerns, i.e., the user interface logic and
the business logic are not interspersed. Instead, a class (the view-model) sits in between the UI code (view) and the business logic (model). WPF utilizes additional features, namely data binding, to facilitate the implementation of MVVM in a WPF application.
Background
To understand all the aspects of this article, it behooves the reader to have a strong understanding of core C# concepts, including collection classes, thread
synchronization,
and delegates and events. Moderate understanding of WPF and XAML is also beneficial.
To expedite development, the use of the ever useful WPF MVVM project template, by Ofir Shemesh, is also advised. It is available via the Extension Manager
in Visual Studio 2010. Version 3.0 of the WPF MVVM project template was used to produce the folder and code structure for this project.
Using the Code
The code in this project is meant to be as self-explanatory as possible, given
a strong understanding of general C# concepts and a willingness to learn WPF, XAML, and MVVM.
The project delineated in this article is self-contained. Downloading the source code and running it will produce a window that looks like the following:
The project folder and file organization is structured as per the illustration below.
MVVM Components at a High Level
You will notice that there is one view, one view-model, and two models. The MainWindow
view encapsulates the
XAML code and the code behind (though the latter is essentially
blank, meaning all view rendering is done via markup). The MainWindowViewModel
inherits from a simple base class that inherits from NotificationObject
to facilitate notifications flowing to and from the view and models. The two model classes, RandPasswordModel
and StatsModel
, handle the holding
of in-memory data about the state of the underlying business objects for the random password generator settings, generated randomized data, as well as the statistics gleaned
from the generated password/data strings. These statistics can be used to assess the randomness of the generated data.
The XAML
Constructing the View (again, all in XAML) made heavy use of the designer as well as direct edits of the XAML code. It is your preference as to whether you wish to rely
on the designer or code by hand.
Crucially, you will notice the use of the view-model and the converters (more on converters later) in the window resources section of the XAML.
<Window.Resources>
<localVM:MainWindowViewModel x:Key="Windows1ViewModel" />
<localConverters:CharArrayToStrConverter x:Key="CharArrayToStrConverter" />
<localConverters:StrListToStrConverter x:Key="StrListToStrConverter" />
<localConverters:DictionaryToText x:Key="DictionaryToText" />
</Window.Resources>
These elements provide the view's link to the view-model and to converters used later in data conversion, as data flows to and from the view and model.
The capacity to bind WPF controls to both data and to commands gives us the ability to perform actions and to receive data. The following illustrates the XAML syntax to associate
a button click with a command and to receive text back from the business layer.
<Button Content="Create passwords" Height="23" Name="button1"
Width="233" HorizontalAlignment="Left" Command="{Binding GeneratePassesCommand}" />
...
<TextBox Height="300" Name="textBox3" Width="Auto"
HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Text="{Binding Model.GeneratedPass, Mode=OneWay, Converter={StaticResource StrListToStrConverter} }" />
In the preceding example, you will note that the button is associated with a command, the GeneratePassesCommand
.
That command flows to a same-named method in the view-model that calls the relevant method in the model. Once generated, the
TextBox
control
receives the generated randomized data set, converts that result to something that can be bound to the
TextBox
control (via the StrListToStrConverter
),
and is then rendered as the text in the control.
The View-Model - taking a look at the GeneratePassesCommand
GeneratePassesCommand
is of type System.Windows.Input.ICommand
and is declared as follows:
public ICommand GeneratePassesCommand { get { return new DelegateCommand(GeneratePasswords); } }
GeneratePassesCommand
encapsulates an Action
type that, in this scenario, calls the private GeneratePasswords
method. See method code below, in full.
private void GeneratePasswords()
{
AppStatus = "Generating passwords...";
AsyncCallHelper.RunAync(() =>
{
List<string> passes = new List<string>(Model.NumPasses);
Parallel.For(0, Model.NumPasses, i =>
{
int seed = RandomGenHelper.GetCryptographicallyRandomInt32();
Random r = new Random(seed);
string pass = RandomGenHelper.GeneratePassword(r, Model.Length, Model.AllowableChars);
lock (passes)
{
passes.Add(pass);
}
} );
Model.GeneratedPass = passes;
StatsModel = new StatsModel(Model.GeneratedPass);
AppStatus = string.Format("{0} passwords generated", Model.NumPasses);
} );
}
Back in the view, and by virtue of the fact that the TextBox
control previously described is bound to the GeneratedPass
property in the model,
the generated passwords (or randomized data) will be fed back into the control after generation and upon execution of the property setter,
called by this line: Model.GeneratedPass = passes;
.
It is also worth mentioning the use of helper methods that can be used elsewhere, including across models. For example, the entire block
in the GeneratePasswords()
method above is executed via a .NET Task
via the call to RunAsync
.
For purposes of illustration, let's take a look at that code.
public class AsyncCallHelper
{
static public void RunAync(Action action)
{
Task.Factory.StartNew(action);
}
}
In fact, the entire class is just a few lines! The asynchronous helper method utilizes the exceptionally helpful TPL, task parallel library,
to execute as an asynchronous operation. By utilizing this technique, we are following
the recommended guidelines for increasing the responsiveness
of the application, as the execution of the expensive business logic is done outside the UI thread.
The Model - A closer look at RandPasswordModel
The model's GeneratedPass
is just a regular property, with one caveat. Once that property is set, a notification must be sent so that the view can be updated.
private List<string> _GeneratedPass;
public List<string> GeneratedPass
{
get { return this._GeneratedPass; }
set
{
if (this._GeneratedPass != value)
{
this._GeneratedPass = value;
RaisePropertyChanged(() => GeneratedPass);
}
}
}
Recall that the base class of both models in this project inherit from NotificationObject
. Once RaisePropertyChanged
is called in the setter of a property of the model, the PropertyChangedEventHandler
event is fired, alerting the underlying view's
(bound) control that a property change has occurred.
Before we leave this topic, you will recall in the View-Model section above that we dived into the GeneratePasswords
method.
If you look over that method again, you will see that the logic to fetch the random seed and the randomized data are done via calls to helper methods
in the RandomGenHelper
class. Let's take a look at this code and examine it.
public static class RandomGenHelper
{
static public Int32 GetCryptographicallyRandomInt32()
{
byte[] randomBytes = new byte[4];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomBytes);
Int32 randomInt = BitConverter.ToInt32(randomBytes, 0);
return randomInt;
}
static public string GeneratePassword(Random r, int length, char[] allowableChars)
{
StringBuilder passwordBuilder = new StringBuilder((int)length);
for (int i = 0; i < length; i++)
{
int nextInt = r.Next(allowableChars.Length);
char c = allowableChars[nextInt];
passwordBuilder.Append(c);
}
return passwordBuilder.ToString();
}
}
Above, you will see the call to GetCryptographicallyRandomInt32
. This method utilizes the System.Security.Cryptography
namespace.
Here, we are using a well known library to aid in the generation of highly random 32-bit integers. These cryptographically random numeric seeds are fed
into the Random
class, which is then used by the GeneratePassword
method above to generate random passwords (i.e., a randomized data set),
using the character set specified by the user (with a reasonable default one provided). This is the crux of the code from a business logic standpoint.
Converters - When and how to use them
A converter will need to be used in cases where data coming from or routed to the model must be translated to be understood. In the case of the GeneratedPass
property,
we can use a simple converter to translate the List<string>
to a regular string. Note that since the binding is one way, we only need to convert
in one direction: from the model and to the control. Let's take a look at the StrListToStrConverter
class in full.
public class StrListToStrConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
List<string> passes = (List<string>)value;
string ret=string.Join(Environment.NewLine, passes.ToArray<string>() );
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And there you have it, a conversion of a list of strings to a regular string to allow for binding to the TextBox
control.
Conclusion
In this article, we have covered the high level concepts of the MVVM pattern implemented in the context of a WPF desktop application in C# for .NET 4.0,
as well as ancillary topics including the task parallel library for parallelized work and asynchronous operations, the use of
ICommand
objects for command delegation,
and the use of the cryptography library for random number seeding.
Points of interest
I thoroughly enjoyed writing this application to demonstrate the power and flexibility of WPF coupled with MVVM. It is my hope that others find it equally useful and inspiring.
History
Version 1.0 - Operational MVVM application.