Click here to Skip to main content
Click here to Skip to main content

A ContactLOB line-of-business application sample - Login: Silverlight, MVVM, WCF RIA Services

By , 20 Feb 2012
Rate this:
Please Sign up or sign in to vote.

Login Page

Introduction

Some said: "Silverlight is dead". Others added: "Silverlight + Desktop = WinRT". I started working on my Silverlight project long before Microsoft announced its plans on Silverlight. I think that having experience in using Silverlight can be very useful, and I'm sure it will come in handy in Microsoft technologies to come.

However, while working on my Silverlight application, I came across some issues. I spent a lot of time searching the Internet for solutions. In order to save you some time, I decided to share these solutions.

I'm not inventing the wheel here. Most of the code that you will see in my article was found online. I just put it all together. I'll do my best to provide appropriate links to the original sources of the code. If I miss something, let me know and I'll update my article accordingly. I apologize in advance for any inconvenience related to the missed links.

I'm going to use the Problem-Design-Solution approach in my article. I think that it’s a great fit for this article, and I hope that it will make for an easier reading.

One more thing: English is not my native language, so I apologize for any grammatical and/or stylistic quirks in advance.

So let's get started.

Prerequisites

In the ContactLOB project I use the following:

  • Visual Studio 2010
  • Silverlight 5
  • Prism 4.0
  • WCF RIA Services

I'd like to give you a little tip on Prism 4.0 that was released before Silverlight 5 did - recompile the Prism 4.0 libraries using Silverlight 5 references.

Problem

One of the problems I came across is the way the user is going to log in to your application. There are a lot of ways to do it.

One of the scenarios is to show a login page on an application startup which will navigate the user to a main page upon receiving their credentials. So if the users run your application, they will see a login page like the one in Figure 1.

Figure 1

Figure 1.

After a successful login you'll navigate the user to a main page like the one in Figure 2.

Figure 2

Figure 2.

If the user gets to the main page, they can't get back to the login page using the browser’s back button - the back button is disabled.

In Silverlight you must provide one and only one main page as an entry point of the application:

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new LoginPage();
}

So you need to replace the login page with the main page somehow:

private void Application_Startup(object sender, StartupEventArgs e)
{
    // this.RootVisual = new LoginPage();
    this.RootVisual = new MainPage();
}

The problem is that you can only assign a page to the RootVisual once. If you try to assign to the RootVisual more than once, nothing will happen. For further reference let's call this problem the Navigation Problem.

The next problem is that you might want to use the Form Authentication like this one, to authenticate the user but you don't know how to authenticate the user on the web server side using your own database. Let's call this problem the Authentication Problem.

Another problem is that you might want to encrypt a user password so it won't be sent over the Internet as plain text. One of the best ways to encrypt a user password is to use the MD5 hash. Unfortunately Microsoft does not provide libraries to get the MD5 hash on the Silverlight client side. Let's call this problem the Encryption Problem.

Design

Let's address the problems described above one by one.

To solve the Navigation Problem, I use the Prism regions. In the Prism documentation it's called View-Based Navigation.

To solve the Authentication Problem, I use a class inherited from the AuthenticationBase class. The class has a couple of overridden functions that can make a difference.

To solve the Encryption Problem, I use the MD5 hash. The procedure here is very simple. You encrypt the user password and send the MD5 hash to the web server. You don't need to decrypt the user password on the server side. You just need to compare encrypted password with the known MD5 hash. If there is a match, you can authorize the user. The trick is getting the MD5 hash.

Solution

I'll walk you through the process of creating a Silverlight application and applying the design described above.

  1. Start Visual Studio 2010 and click New Project...
  2. In the New Project form select the Silverlight Application project template and type in ContactLOB as a project name:
  3. Image1_1.png

    Click the OK button.

  4. In the New Silverlight Application form, check the Enable WCF RIA Services checkbox and click the OK button:
  5. Image2_1.png

  6. Launch the project to build it. You'll have fewer problems later.
  7. Add the Base, ViewModels and Views folders to the ContactDB project.
  8. Add the Prism library references:
  9. Go to the Mark Harris's post and download the code that provides the MD5 hash.
  10. Add the file to the project.

    Note: You don’t need to download the source code for the ContactLOB project attached to the article - it's already there.

  11. Add the ViewModelBase.cs file to the Base folder.
  12. Insert the code into the file:
  13. using System.ComponentModel;
    namespace ContactLOB.Base
    {
        public class ViewModelBase : INotifyPropertyChanged
        {
            #region INotifyPropertyChanged
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void OnPropertyChanged(string propertyName)
            {
                if (null != PropertyChanged)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            #endregion
        }
    }

    The MVVM design pattern is not the goal of this article. So the implementation of the INotifyPropertyChanged interface is very simple.

  14. Add the LoginViewModel.cs file to the ViewModels project folder and insert the code:
  15. using System.ComponentModel;
    using System.ServiceModel.DomainServices.Client.ApplicationServices;
    using System.Text;
    using System.Windows.Input;
    using ContactLOB.Base;
    using FlowGroup.Crypto;
    using Microsoft.Practices.Prism.Commands;
    using Microsoft.Practices.Prism.Regions;
    using Microsoft.Practices.ServiceLocation;
    using Microsoft.Practices.Unity;
    
    namespace ContactLOB.ViewModels
    {
        public class LoginViewModel : ViewModelBase
        {
            private AuthenticationService authService;
    
            public LoginViewModel()
            {
                LoginCommand = new DelegateCommand(ClickLogin);
    
                if (!DesignerProperties.IsInDesignTool)
                {
                    authService = WebContext.Current.Authentication;
                }
            }
    
            public ICommand LoginCommand { get; private set; }
    
            private string userName;
    
            public string UserName
            {
                get
                {
                    return userName;
                }
                set
                {
                    userName = value;
                    OnPropertyChanged("UserName");
                }
            }
    
            private string password;
    
            public string Password
            {
                get
                {
                    return password;
                }
                set
                {
                    password = value;
                    OnPropertyChanged("Password");
                }
            }
    
            internal void ClickLogin()
            {
                if (authService == null) return;
    
                LoginParameters loginParams = new LoginParameters(UserName, 
                       GetPasswordHash(Password));
                var loginOperation = authService.Login(loginParams,
                    (loginOp) =>
                    {
                        if (loginOp.LoginSuccess)
                        {
                            GoToMainPage();
                        }
                        else if (loginOp.HasError)
                        {
                            loginOp.MarkErrorAsHandled();
                        }
                    },
                    null);
            }
    
            private string GetPasswordHash(string password)
            {
                UTF8Encoding encoder = new UTF8Encoding();
                byte[] arr = encoder.GetBytes(password);
    
                MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
                byte[] md5arr = md5.ComputeHash(arr);
                return BytesToHexString(md5arr);
            }
    
            private string BytesToHexString(byte[] value)
            {
                StringBuilder sb = new StringBuilder(value.Length * 2);
                foreach (byte b in value)
                {
                    sb.AppendFormat("{0:x2}", b);
                }
                return sb.ToString();
            }
    
            private void GoToMainPage()
            {
                IRegionManager regionManager = 
                          ServiceLocator.Current.GetInstance<IRegionManager>();
                if (regionManager == null) return;
    
                IUnityContainer container = 
                          ServiceLocator.Current.GetInstance<iunitycontainer>();
                if (container == null) return;
    
                IRegion mainRegion = regionManager.Regions["MainRegion"];
                if (mainRegion == null) return;
    
                // Check to see if we need to create an instance of the view.
                MainPage view = mainRegion.GetView("MainPage") as MainPage;
                if (view == null)
                {
                    // Create a new instance of the MainPage using the Unity container.
                    view = container.Resolve<mainpage>();
    
                    // Add the view to the main region.
                    mainRegion.Add(view, "MainPage");
                    // Activate the view.
                    mainRegion.Activate(view);
                }
                else
                {
                    // The view has already been added to the region so just activate it.
                    mainRegion.Activate(view);
                }
            }
        }
    }

    Rebuild the project just in case.

    The main part of the code above is the GoToMainPage function. The logic is pretty straightforward. Using the Region Manager we check if the MainPage view is already created, and if not, create one and activate it. When we activate the MainPage view, the user will be navigated to the MainPage.

  16. Add the LoginView user control to the Views folder and insert the following code:
  17. <UserControl 
    x:Class="ContactLOB.Views.LoginView"
    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:vm="clr-namespace:ContactLOB.ViewModels"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.DataContext>
    <vm:LoginViewModel />
    </UserControl.DataContext>
    <Grid>
    <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition Height="Auto"/>
    <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Row="1" Grid.Column="1">
    <Border BorderThickness="2" CornerRadius="4" BorderBrush="Black">
    <Grid Margin="5">
    <Grid.RowDefinitions>
    <RowDefinition Height="10" />
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="10"/>
    <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <TextBlock Text="User Name" Grid.Row="1" VerticalAlignment="Center" />
    <TextBox FontSize="12" Margin="8" Grid.Column="1" Width="150" Grid.Row="1" 
    Text="{Binding Path=UserName, Mode=TwoWay, NotifyOnValidationError=True, 
    TargetNullValue=''}" 
    VerticalAlignment="Center"/>
    <TextBlock Text="Password" Grid.Row="2" VerticalAlignment="Center" />
    <PasswordBox FontSize="12" Margin="8" Grid.Column="1" Width="150" Grid.Row="2" 
    Password="{Binding Path=Password, Mode=TwoWay, NotifyOnValidationError=True, TargetNullValue=''}" 
    VerticalAlignment="Center"/>
    <Button Content="Login" Grid.Column="1" Grid.Row="4" HorizontalAlignment="Left" Margin="8" 
    Command="{Binding Path=LoginCommand}" Width="80" />
    </Grid>
    </Border>
    </StackPanel>
    </Grid>
    </UserControl>
    
  18. Add the ShellView user control to the Views folder and insert the following code:
  19. <usercontrol 
      x:class="ContactLOB.Views.ShellView" 
      d:designwidth="400" 
      d:designheight="300" 
      mc:ignorable="d" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:prism="http://www.codeplex.com/prism">
    <contentcontrol horizontalcontentalignment="Stretch" 
      verticalcontentalignment="Stretch" 
      prism:regionmanager.regionname="MainRegion" x:name="MainRegion">
    </contentcontrol> 
    </usercontrol>
    
  20. Add the following code to the MainPage.xaml so you can display the user info when the user gets there:
  21. <grid>
        <textblock x:name="txtWelcome" text="This is the Main Page">
        </textblock>
    </grid>
  22. Add the code to the MainPage.xaml.cs to display the user info:
  23. using System.Windows.Controls;
    using ContactLOB.Web.Services;
    
    namespace ContactLOB
    {
        public partial class MainPage : UserControl
        {
            public MainPage()
            {
                InitializeComponent();
    
                this.Loaded += (s, e) =>
                {
                    WebUser usr = WebContext.Current.User;
                    this.txtWelcome.Text = string.Format("Welcome {0} {1}",
                        usr.FirstName, usr.LastName);
                };
            }
        }
    }
  24. Add the Bootstrapper.cs file to the ContactLOB project and insert the code:
  25. using System.Windows;
    using ContactLOB.Views;
    using Microsoft.Practices.Prism.Regions;
    using Microsoft.Practices.Prism.UnityExtensions;
    using Microsoft.Practices.Unity;
    
    namespace ContactLOB
    {
        public class Bootstrapper : UnityBootstrapper
        {
            protected override DependencyObject CreateShell()
            {
                // Use the container to create an instance of the shell.
                ShellView view = this.Container.TryResolve<ShellView>();
    
                // Set it as the root visual for the application.
                Application.Current.RootVisual = view;
    
                return view;
            }
    
            protected override void InitializeShell()
            {
                base.InitializeShell();
    
                IRegionManager regionManager = RegionManager.GetRegionManager(Shell);
                if (regionManager == null) return;
    
                // Create a new instance of the LoginView using the Unity container.
                var view = this.Container.Resolve<loginview>();
                if (view == null) return;
                // Add the view to the main region.
                regionManager.Regions["MainRegion"].Add(view, "LoginView");
            }
        }
    }
  26. Open the App.xaml.cs file and add the code:
  27. public App()
    {
        this.Startup += this.Application_Startup;
        this.Exit += this.Application_Exit;
        this.UnhandledException += this.Application_UnhandledException;
    
        InitializeComponent();
    
        // Create a WebContext and add it to the ApplicationLifetimeObjects
        // collection.  This will then be available as WebContext.Current.
        WebContext webContext = new WebContext();
        webContext.Authentication = new FormsAuthentication() 
          { DomainContext = new AuthenticationDomainContext() };
        this.ApplicationLifetimeObjects.Add(webContext);
    }
    
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        new Bootstrapper().Run();
    }

    Don't worry about the code line with AuthenticationDomainContext. It will be fixed when we're done with the server part of the application.

  28. Now go to the ContactLOB.Web project and add the following references:
    • System.ServiceModel.DomainServices.Server
    • System.ServiceModel.DomainServices.Hosting
    • System.Security.
  29. Add the Services folder to the ContactLOB.Web project and add the AuthenticationDomainService.cs file to the folder. Add this code to the file:
  30. using System.Security.Principal;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server.ApplicationServices;
    
    namespace ContactLOB.Web.Services
    {
        /// <summary>
        /// RIA Services DomainService responsible for authenticating users when
        /// they try to log on to the application.
        ///
        /// Most of the functionality is already provided by the base class
        /// AuthenticationBase
        /// 
        [EnableClientAccess]
        public class AuthenticationDomainService : AuthenticationBase<webuser>
        {
            protected override WebUser GetAuthenticatedUser(IPrincipal principal)
            {
                // TODO: Add code to retreive user info from database
    
                WebUser user = new WebUser();
                user.Name = principal.Identity.Name;
                user.FirstName = "Bill";
                user.LastName = "Gates";
    
                return user;
            }
    
            protected override bool ValidateUser(string userName, string password)
            {
                // TODO: Add code to check user credentials against database
                string usrName = "demo";
                string pswHash = "fe01ce2a7fbac8fafaed7c982a04e229";
                return (usrName.Equals(userName) && pswHash.Equals(password));
    
            }
        }
    
        public class WebUser : UserBase
        {
            public string UserId { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public bool IsAdmin { get; set; }
        }
    }

    You can find the more comprehensive sample of the AuthenticationBase usage here.

    I've included the WebUser class to demonstrate how to pass some of the user info from the server to the client. User info provided by the WebUser class is used in the Main Page (see item 14).

  31. 19. To make all this work you have to modify the web.config file:
  32. <?xml version="1.0"?>
    <!--
    For more information on how to configure your ASP.NET application, please visit
    http://go.microsoft.com/fwlink/?LinkId=169433
    -->
    <configuration>
    <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
    <add name="DomainServiceModule" preCondition="managedHandler" 
       type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
             System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
             Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </modules>
    <validation validateIntegratedModeConfiguration="false"/>
    </system.webServer>
    <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpModules>
    <add name="DomainServiceModule" 
      type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, 
            System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, 
            Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </httpModules>
    <authentication mode="Forms"> <forms name=".ContactLOB_ASPXAUTH" /> </authentication> 
    </system.web> 
    <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
    </system.serviceModel>
    </configuration>
    

    Notice the authentication tag in the web.config. It indicates that we're using the Form Authentication.

  33. Rebuild the ContactLOB.Web project and then the ContactLOB project - in this order. The order is important here. Launch the application and enter the user name demo and the password demo. If you put it in properly, you'll be redirected to the MainPage.

Summary

You now know how to provide the user login in the line-of-business applications, use the MD5 hash to encrypt the user password, use the Prism Region Manager to navigate from the LoginPage to the MainPage and also how to use the Form Authentication to authenticate the user. The Problem is solved.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

BrizkCRM
BrizkCRM
United States United States
You can contact me using my website: BrizkCRM

Comments and Discussions

 
Questionthanks Pinmemberwakazz4-Nov-13 4:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 20 Feb 2012
Article Copyright 2012 by BrizkCRM
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid