65.9K
CodeProject 正在变化。 阅读更多。
Home

一个 ContactLOB 业务应用程序示例 - 登录:Silverlight, MVVM, WCF RIA Services

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (7投票s)

2012年2月20日

CPOL

6分钟阅读

viewsIcon

37999

downloadIcon

1124

一个 Silverlight LOB 应用程序教程。

Login Page

引言

有人说:“Silverlight 已死”。 另一些人补充说:“Silverlight + 桌面 = WinRT”。 我在微软公布 Silverlight 计划之前很久就开始开发我的 Silverlight 项目了。我认为使用 Silverlight 的经验非常有价值,而且我确信它将在微软未来的技术中派上用场。

然而,在我开发 Silverlight 应用程序的过程中,我遇到了一些问题。我花了大量时间在网上搜索解决方案。为了节省您一些时间,我决定分享这些解决方案。

我并没有发明轮子。我在文章中看到的大部分代码都是在网上找到的。我只是把它们整理在一起。我会尽力提供指向原始代码来源的适当链接。如果我遗漏了什么,请告诉我,我会相应地更新我的文章。我提前为遗漏链接可能带来的任何不便道歉。

我将在我的文章中使用“问题-设计-解决方案”的方法。我认为它非常适合这篇文章,而且我希望它能让阅读更容易。

还有一件事:英语不是我的母语,所以我提前为任何语法和/或风格上的奇怪之处道歉。

那么,让我们开始吧。

必备组件

在 ContactLOB 项目中,我使用了以下技术:

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

我想给您一个关于 Prism 4.0 的小提示,它是在 Silverlight 5 发布之前发布的 - 使用 Silverlight 5 引用重新编译 Prism 4.0 库。

问题

我遇到的一个问题是用户将如何登录您的应用程序。有很多方法可以做到这一点。

其中一种场景是在应用程序启动时显示登录页面,该页面在接收到用户凭据后会将用户导航到主页面。因此,如果用户运行您的应用程序,他们将看到如图 1 所示的登录页面。

Figure 1

图1。

成功登录后,您将把用户导航到如图 2 所示的主页面。

Figure 2

图2。

如果用户进入主页面,他们无法使用浏览器的后退按钮返回登录页面 - 后退按钮被禁用。

在 Silverlight 中,您必须提供一个且仅一个主页面作为应用程序的入口点。

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

所以您需要以某种方式用主页面替换登录页面。

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

问题在于,您只能为 RootVisual 分配一次页面。如果您尝试为 RootVisual 分配多次,则什么也不会发生。为了方便参考,我们将此问题称为“导航问题”。

下一个问题是,您可能希望使用表单身份验证,如这个,来验证用户,但您不知道如何在服务器端使用您自己的数据库来验证用户。我们将此问题称为“身份验证问题”。

另一个问题是,您可能希望加密用户密码,这样它就不会以明文形式通过 Internet 发送。加密用户密码的最佳方法之一是使用 MD5 哈希。不幸的是,微软没有提供在 Silverlight 客户端上获取 MD5 哈希的库。我们将此问题称为“加密问题”。

设计

让我们一个一个地解决上述问题。

为了解决“导航问题”,我使用了 Prism 的区域。在 Prism 文档中,这被称为“基于视图的导航”。

为了解决“身份验证问题”,我使用了一个继承自 `AuthenticationBase` 类的类。该类有几个可以产生差异的重写函数。

为了解决“加密问题”,我使用了 MD5 哈希。这里的过程非常简单。您加密用户密码并将 MD5 哈希发送到 Web 服务器。您无需在服务器端解密用户密码。您只需要将加密后的密码与已知的 MD5 哈希进行比较。如果匹配,则可以授权用户。诀窍在于获取 MD5 哈希。

解决方案

我将引导您完成创建 Silverlight 应用程序并应用上述设计的过程。

  1. 启动 Visual Studio 2010,然后单击“新建项目...”
  2. 在“新建项目”窗体中,选择“Silverlight 应用程序”项目模板,并将项目名称键入为 ContactLOB。
  3. Image1_1.png

    单击“确定”按钮。

  4. 在“新建 Silverlight 应用程序”窗体中,勾选“启用 WCF RIA Services”复选框,然后单击“确定”按钮。
  5. Image2_1.png

  6. 构建项目。这样可以减少以后出现的问题。
  7. 将 Base、ViewModels 和 Views 文件夹添加到 ContactDB 项目。
  8. 添加 Prism 库引用。
  9. 转到 Mark Harris 的帖子,下载提供 MD5 哈希的代码。
  10. 将文件添加到项目中。

    注意:您无需下载文章附加的 ContactLOB 项目的源代码 - 它已经在那里了。

  11. 将 `ViewModelBase.cs` 文件添加到 Base 文件夹。
  12. 将代码插入到文件中。
  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
        }
    }

    MVVM 设计模式不是本文的目标。因此,`INotifyPropertyChanged` 接口的实现非常简单。

  14. 将 `LoginViewModel.cs` 文件添加到 ViewModels 项目文件夹,并插入代码。
  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);
                }
            }
        }
    }

    重新生成项目以防万一。

    上面代码的主要部分是 `GoToMainPage` 函数。逻辑非常直接。使用 Region Manager,我们检查 `MainPage` 视图是否已创建,如果没有,则创建一个并激活它。当我们激活 `MainPage` 视图时,用户将被导航到 `MainPage`。

  16. 将 `LoginView` 用户控件添加到 Views 文件夹,并插入以下代码。
  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. 将 `ShellView` 用户控件添加到 Views 文件夹,并插入以下代码。
  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. 将以下代码添加到 MainPage.xaml,以便当用户到达那里时可以显示用户信息。
  21. <grid>
        <textblock x:name="txtWelcome" text="This is the Main Page">
        </textblock>
    </grid>
  22. 将代码添加到 `MainPage.xaml.cs` 以显示用户信息。
  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. 将 `Bootstrapper.cs` 文件添加到 ContactLOB 项目,并插入代码。
  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. 打开 `App.xaml.cs` 文件并添加代码。
  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();
    }

    不用担心 `AuthenticationDomainContext` 处的代码行。完成应用程序的服务器部分后,它将得到修复。

  28. 现在转到 ContactLOB.Web 项目并添加以下引用:
    • System.ServiceModel.DomainServices.Server
    • System.ServiceModel.DomainServices.Hosting
    • System.Security。
  29. 将 Services 文件夹添加到 ContactLOB.Web 项目,并将 `AuthenticationDomainService.cs` 文件添加到该文件夹。将以下代码添加到文件中:
  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; }
        }
    }

    您可以在此处找到 `AuthenticationBase` 用法的更全面的示例。

    我包含了 `WebUser` 类,以演示如何将一些用户信息从服务器传递到客户端。`WebUser` 类提供的用户信息用于主页面(参见第 14 项)。

  31. 19. 要使这一切生效,您必须修改 `web.config` 文件。
  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>
    

    请注意 `web.config` 中的 `authentication` 标签。它表明我们正在使用表单身份验证。

  33. 按此顺序(先 ContactLOB.Web,再 ContactLOB)重新生成 ContactLOB.Web 项目,然后是 ContactLOB 项目。顺序在这里很重要。启动应用程序,输入用户名 `demo` 和密码 `demo`。如果输入正确,您将被重定向到 MainPage。

摘要

现在您知道如何在业务应用程序中提供用户登录、如何使用 MD5 哈希加密用户密码、如何使用 Prism Region Manager 从 LoginPage 导航到 MainPage,以及如何使用表单身份验证来验证用户。问题已解决。

© . All rights reserved.