代码改变世界

Expert C# 2008 Business Objects 第19章 WPF用户界面 NO.2

2009-07-07 09:33  sapajou  阅读(681)  评论(0编辑  收藏  举报

2.3 应用程序配置

应用程序需要通过应用程序配置文件提供一些基本的配置信息。在客户端应用程序配置文件中,你即可以提供连接字符串使得应用程序可以直接与数据库交互,你也可以配置数据门户与远程应用服务器通信。我在第15章完成通道适配器的实现时讨论了这个基本概念。回忆一下,数据门户支持几种通道,包括WCF、远程调用、企业服务和Web Services。如果这些没有适合你的需求的你也可以创建自己的通道。

在第1章,我讨论了伴随着各种不同的物理n层配置所产生的在性能、可扩展性、容错和安全方面的权衡。对一个智能客户端用户界面最具扩展性的解决方案是使用一个应用服务器来宿主数据访问层,性能最好的解决方案是将数据门户运行在客户端的本地进程中。在本章中,我将先展示如何将数据门户运行在本地,然后使用WCF通道运行在远程。

配置是由应用程序配置文件来控制的,在Visual Studio项目中,该文件被命名为App.config。

注:命名App.config文件是很重要的,Visual Studio 2008会自动将该文件拷贝到适当的bin目录下,将名字修改为一程序相匹配。在本例中,当该文件被拷贝到bin目录下的时候,名字被修改为PTWin.exe.config。这个动作每次在Visual Studio中构建项目时发生。

2.3.1验证

CSLA.NET通过配置文件来控制身份验证。

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<appSettings>

<add key="CslaAuthentication" value="Csla" />

<add key="CslaPropertyChangedMode" value="Xaml" />

</appSettings>

</configuration>

这里CslaAuthentication键值指定了使用自定义身份验证。第8章实现的PTPrincipal和PTIdentity类专为支持自定义的身份验证,本章的UI代码也将使用自定义的身份验证。

如果你想使用Windows身份验证,将配置修改成下面这样:

<add key="CslaAuthentication" value="Windows" />

当然,这种修改需要代码也要改动,首先要从ProjectTracker.Library中移除PTPrincipal和PTIdentity类,因为不需要它们了。本章中实现的登录/注销功能也不需要了。特别地,登录窗体和显示窗体的代码也要从UI项目中移除。

这里的CslaPropertyChangedMode键值使CSLA.NET使用XAML模型来从所有的业务对象中触发PropertyChanged事件,回忆下第7章和第10章,WPF/XAML数据绑定中的PropertyChanged事件必须以与Windows Forms数据绑定不同的方式触发,因为两种数据绑定基础结构处理事件的方式有些不同。

对于WPF应用程序来说这个值的设定是很重要的,否则,数据绑定就不会基于业务对象的变动正确的更新UI。

2.3.2本地数据门户

配置文件也控制应用程序如何使用数据门户,为了让客户端应用程序直接与数据库交互,使用下面的配置(将“your connection string”修改成你的数据库连接字符串):

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<appSettings>

<add key="CslaAuthentication" value="Csla" />

<add key="CslaPropertyChangedMode" value="Xaml" />

</appSettings>

<connectionStrings>

<add name="PTracker" connectionString="your connection string"

providerName="System.Data.SqlClient" />

<add name="Security" connectionString="your connection string"

providerName="System.Data.SqlClient" />

</connectionStrings>

</configuration>

因为本地代理(LocalProxy)是数据门户的CslaDataPortalProxy设置的默认值,实际的数据门户配置是不需要的,所以配置文件中仅有的设置是控制身份验证和提供数据库连接字符串。

2.3.3远程数据门户

为了使数据门户使用应用服务器并用WCF通道进行通信,使用如下的配置:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

<appSettings>

<add key="CslaAuthentication" value="Csla" />

<add key="CslaPropertyChangedMode" value="Xaml" />

<add key="CslaDataPortalProxy"

value="Csla.DataPortalClient.WcfProxy, Csla"/>

</appSettings>

<connectionStrings>

</connectionStrings>

<system.serviceModel>

<client>

<endpoint name="WcfDataPortal"

address="http://localhost:4147/WcfHost/WcfPortal.svc"

binding="wsHttpBinding"

contract="Csla.Server.Hosts.IWcfPortal" />

</client>

</system.serviceModel>

</configuration>

为使用WCF配置中的关键行用粗体显示。当然,你需要将localhost:4147修改为安装数据门户宿主的应用服务器的名字,WcfHost也需要改为应用服务器上虚拟目录的名字。

在使用这个配置前,你必须创建并配置WCF宿主的虚拟目录,我将在下一节展示如何做。

实现应用程序的配置最重要的事情是数据门户可以在不用改动任何UI和业务对象代码的情况下从本地切换到远程(使用任何一种网络通道)。

2.3.4配置数据门户服务

当使用远程数据门户时,客户端与应用服务器进行通信。很明显,这意味着一个应用服务器必须存在并被正确的配置。当客户端数据门户被配置为使用WCF时,你必须提供一个正确配置的WCF应用服务器。

注:数据门户被配置为本地模式时数据门户服务器是不需要的。在那种情况下,服务端组件运行在客户端进程中,不使用应用服务器也不需要应用服务器。

在下载的ProjectTracker中WcfHost Web站点是WCF应用服务器宿主在IIS中的一个例子。你也可以选择将WCF服务器宿主在一个自定义的Windows服务中,宿主在一个EXE程序中,或者宿主在WAS中。这些不同的宿主在许多方面与IIS非常类似,但这超出了本书的范围。

WcfHost Web站点很简单,因为它依赖于CSLA.NET提供的已经存在的功能,这在第15章已经讨论过。表19-4列出了站点的关键要素:

表19-4 WcfHost站点的关键要素

要素

描述

WcfPortal.svc

WCF终结点文件,引用Csla.Server.Hosts.WcfPortal类

bin

标准的Web bin文件夹,包含了Csla.dll,ProjectTracker.Library.dll和业务组件库需要的其他程序集;重要的是你的业务程序集和Csla.dll要在这个文件夹中出现

web.config

标准web.config文件,包含了一个system.serviceModel来为数据门户配置WCF终结点

任何包含表19-4中元素的站点都能作为WCF数据门户宿主。web.config文件必须包含WCF配置节,为数据门户定义终结点,例如:

<system.serviceModel>

<services>

<service name="Csla.Server.Hosts.WcfPortal">

<endpoint address=""

contract="Csla.Server.Hosts.IWcfPortal"

binding="wsHttpBinding"/>

</service>

</services>

</system.serviceModel>

WCF服务由它们的地址、绑定和契约定义。

对于宿主在IIS中的WCF服务来说,地址由IIS和svc文件名定义,因此不需要在web.config文件中指定。像这里显示的那样,你可以提供一个“”address或者根本不用address。如果你使用诸如WAS或是自定义Windows服务这样的宿主技术,你可能需要指定服务地址。

就CSLA.NET数据门户终结点来说,契约是固定的,必须是Csla.Server.Hosts.IWcfPortal。像第15章讨论的那样,契约由数据门户内实现的接口来定义,那与些值没什么不同。

绑定可以是任何同步WCF绑定。唯一被数据门户强加的需求是WCF绑定必须是同步的;除此之外,任何绑定都是可以的。你可以选择使用HTTP、TCP、命名管道或其他的绑定。你可以选择使用SSL、X.509证书或是其他的加密和验证形式来配置绑定。WCF的所有特性都供你任意使用。

要记得你的业务对象的数据访问代码会运行在应用服务上,这意味着应用服务器的web.config文件必须要定义数据访问代码所需要的连接字符串。

<connectionStrings>

<add name="PTracker" connectionString="your connection string"

providerName="System.Data.SqlClient" />

<add name="Security" connectionString="your connection string"

providerName="System.Data.SqlClient" />

</connectionStrings>

这与你使用本地数据门户配置时放在客户端的app.config文件中的配置是相同的,但现在这些值必须配置在服务器上,因为数据访问代码将在服务器上执行。

CslaAuthentication的值必须在客户端和服务器保持一致。只要你保证在两端都作修改,你也可能把值改为Windows。如果你把值改为Windows,你必须确保WCF宿主Web站点被配置为需要Windows身份验证和模拟调用用户,因为在那种情况下,CSLA.NET会仅使用.NET提供的值。

到这里你应该清楚了如何配置应用程序,如何为本地(2层)或远程(3层)操作配置数据门户。

2.4 PTWpf项目配置

用户界面应用程序可以在ProjectTracker解决方案中找到。项目被命名为PTWpf并且引用了ProjectTracker.Library项目,还有Csla.dll。ProjectTracker.Library是一个项目引用,而Csla.dll是一个文件引用。

当使用CSLA.NET框架构建应用程序时,最好对框架程序集建立一个文件引用,而在用户界面和业务程序集间使用项目引用。这使得整体调试更加容易,因为这有助于防止对于CSLA.NET框架项目的意外修改,同时允许对用户界面代码和业务对象代码不固定的修改。

让我们继续WPF用户界面的创建。首先,我会讨论MainForm的布局和设计,然后涵盖用户登录和注销的过程。

在公共代码之外,我会详细讨论维护角色和项目数据的过程。在那你会更好的理解如何创建查找对话框,还有基于grid的窗体以及明细窗体。

2.5 主窗体

MainForm是应用程序的核心,它提供了导航并且宿主显示给用户的用户控件。它协调整个应用程序的流程。

像你在本章前面的图19-1中看到的,应用程序有一个图形化的观感,这是在MainForm中通过使用一系列渐变填充的矩形和路径对象来实现的。所有这些图形元素包含在名为BackgroundGrid的Grid中,你可以在下载的代码中看到这是如何实现的,代码的下载你可以在Apress网站www.apress.com/book/view/1430210192 的Source Code/Download区域或www.lhotka.net/cslanet/download.aspx 下找到。本章中我的焦点不是WPF中图形布局的概念,我将聚焦在导航、数据绑定以及与业务对象的交互。

2.5.1导航

名字为LayoutRoot的Grid包含了实际的窗体内容,这个网格定义两列,左边的包含了导航控件,右边的包含了本章前面讨论的contentArea控件。

导航区的第一节都被包含在一个Expander中,该控件包含了一列Hyperlink控件,例如:

<Expander IsExpanded="True" Header="Projects" >

<ListBox>

<ListBoxItem>

<Hyperlink Name="ShowProjectListButton"

Click="ShowProjectList">Show list</Hyperlink>

</ListBoxItem>

<ListBoxItem>

<Hyperlink Name="NewProjectButton"

Click="NewProject">Add new</Hyperlink>

</ListBoxItem>

<ListBoxItem>

<Hyperlink Name="CloseProjectButton"

Click="CloseProject">Close project</Hyperlink>

</ListBoxItem>

</ListBox>

</Expander>

每个Hyperlink控件有一个Click事件处理器,该事件处理器在窗体的code-behind中实现,每个事件处理器负责实现用户请求的行为。例如,NewProject()事件处理器看起来像下面这样:

private void NewProject(object sender, EventArgs e)

{

try

{

ProjectEdit frm = new ProjectEdit(Guid.Empty);

ShowControl(frm);

}

catch (System.Security.SecurityException ex)

{

MessageBox.Show(ex.ToString());

}

}

创建一个新项目就是将ProjectEdit窗体显示给用户。你可通过用空的Guid值(指示一个新的项目要被添加)创建一个ProjectEdit的新实例并且调用ShowControl()方法来在MainForm的内容区显示窗体来完成这个工作。任何的安全异常会被捕获并在一个对话框中显示给用户。另一个例子是CloseProject()处理器。

private void CloseProject(object sender, RoutedEventArgs e)

{

ProjectSelect frm = new ProjectSelect();

bool result = (bool)frm.ShowDialog();

if (result)

{

Guid id = frm.ProjectId;

ProjectCloser.CloseProject(id);

MessageBox.Show("Project closed",

"Close project", MessageBoxButton.OK, MessageBoxImage.Information);

}

}

这个方法有点复杂,因为它要给用户显示ProjectSelect对话框以让用户选择哪个项目要被关闭,如果用户选择了一个项目,ProjectCloser业务类(命令对象类型)会关闭指定的项目。这是导航链接不显示一个新的编辑窗体给用户而且执行一个行为的例子。

所有导航项的Click事件处理器都以一种与这里展示的方式类似的方式工作。如果你愿意,你可以将Click事件路由到其他类而不是在窗体自身中处理-可能是一个负责实现用户单击每一项所触发的行为的控制器类或是表现类。

在任何情况下,代码都与我这里展示的很类似。不管你的用户界面设计模式,你都需要一些代码负责实现被请求的用户界面行为,并且通过利用业务对象中已有的功能这样做。

2.5.2登录和注销

MainForm中实现的最后一点公共的功能允许用户登录应用程序或是注销。认识到ProjectTracker应用程序中允许未经授权的或是来宾用户查看某些数据是很重要的,这些用户甚至能在未登录的情况下与应用程序进行交互。

当应用程序第一次加载、当用户单击菜单上的登录按钮时登录过程被触发。在这两种情况下,LogInOut()方法被调用来处理实际的登录/注销行为。

void LogInOut(object sender, EventArgs e)

{

if (Csla.ApplicationContext.User.Identity.IsAuthenticated)

{

ProjectTracker.Library.Security.PTPrincipal.Logout();

CurrentUser.Text = "Not logged in";

LoginButtonText.Text = "Log in";

}

else

{

Login frm = new Login();

frm.ShowDialog();

if (frm.Result)

{

string username = frm.UsernameTextBox.Text;

string password = frm.PasswordTextBox.Password;

ProjectTracker.Library.Security.PTPrincipal.Login(

username, password);

}

if (!Csla.ApplicationContext.User.Identity.IsAuthenticated)

{

ProjectTracker.Library.Security.PTPrincipal.Logout();

CurrentUser.Text = "Not logged in";

LoginButtonText.Text = "Log in";

}

else

{

CurrentUser.Text =

string.Format("Logged in as {0}",

Csla.ApplicationContext.User.Identity.Name);

LoginButtonText.Text = "Log out";

}

}

ApplyAuthorization();

IRefresh p = _currentControl as IRefresh;

if (p != null)

p.Refresh();

}

如果当前主体是通过身份验证的,下面的代码将注销那个用户:

if (Csla.ApplicationContext.User.Identity.IsAuthenticated)

{

ProjectTracker.Library.Security.PTPrincipal.Logout();

CurrentUser.Text = "Not logged in";

LoginButtonText.Text = "Log in";

}

相反,如果当前主体是没有经过身份验证的,下面的代码将给用户显示登录对话框使得用户能输入自己的用户名和密码:

Login frm = new Login();

frm.ShowDialog();

登录对话框窗体是一个模式窗体,提示用户输入凭证并将凭证值提供给调用代码。

回到LogInOut()方法,如果用户点击了对话框上的登录按钮,用户名和密码的值会传递给PTPrincipal.Login()方法来验证凭证。

string username = frm.UsernameTextBox.Text;

string password = frm.PasswordTextBox.Password;

ProjectTracker.Library.Security.PTPrincipal.Login(

username, password);

结果就是Csla.ApplicationContext.User要么是一个通过验证的PTPrincipal,要么是一个未通过验证的UnauthenticatedPrincipal。然后主体对象的状态将被用来决定用户是否登录了。

if (!Csla.ApplicationContext.User.Identity.IsAuthenticated)

{

ProjectTracker.Library.Security.PTPrincipal.Logout();

CurrentUser.Text = "Not logged in";

LoginButtonText.Text = "Log in";

}

else

{

CurrentUser.Text =

string.Format("Logged in as {0}",

Csla.ApplicationContext.User.Identity.Name);

LoginButtonText.Text = "Log out";

}

如果用户通过了身份验证,按钮的文本将变成“注销”,用户的名字被显示在窗体上,否则,按钮的文本将变成“登录”,表示用户没有登录系统的文本也会显示出来。

不管在哪种情况下,ApplyAuthorization()方法都会被调用使得MainForm能基于用户的标识更新显示。然后,当前的用户控件(如果存在)将得到主体变动的通知。

ApplyAuthorization();

IRefresh p = _currentControl as IRefresh;

if (p != null)

p.Refresh();

每个用户控件负责处理这个事件并做出适当的响应。回忆一下,EditForm基类定义ApplyAuthorization()方法,因此每个用户控件在需要时都可以覆写这个方法。

MainForm中的ApplyAuthorization()方法负责启用或禁用导航项。

private void ApplyAuthorization()

{

this.NewProjectButton.IsEnabled =

Csla.Security.AuthorizationRules.CanCreateObject(typeof(Project));

this.CloseProjectButton.IsEnabled =

Csla.Security.AuthorizationRules.CanEditObject(typeof(Project));

this.NewResourceButton.IsEnabled =

Csla.Security.AuthorizationRules.CanCreateObject(typeof(Resource));

}

注意实际的授权检查是如何被委托给CSLA.NET提供的每类型授权方法。我在第12章中讨论的这些方法被具体实现了以应用在这样场景中。这里的思想就是MainForm不了解特定的用户或角色是否被授权来添加项目对象,但Project类自身了解,MainForm只是简单的询问授权子系统当前用户是否被授权了。

最终的结果就是很好的关注分离:MainForm关注启用或禁用控件的用户界面细节,而实际的规则被包含在业务逻辑层中。