WCF的登录服务+WPF登录窗体实现
现将一个基于WCF的分布式程序的登录实现( 源码下载)描述如下:
1.部署需求
- 数据库部署在一台专用服务器上,要实现数据和服务的远程调用.
- 客户端与服务器不属于同一计算机.
基于以上要求,考虑到以后便于扩展,考虑使用 WCF 技术实现程序的分布式开发.
2.登录界面
3.服务搭建
解决方案目前考虑6个项目.其中和服务相关的3个项目.
3.1 JIS.IService 项目
主要定义各种服务契约.该项目主要是一些接口.通过定义 [ServiceContract] [OperationContract]特性,指定服务契约和契约方法.
如:登录服务中,需要首先为界面用户提供用户数据,和进行登录方法
namespace JIS.IService
{
[ServiceContract]
public interface ILoginService
{
[OperationContract]
bool Login(string loginName, string pwd);
[OperationContract]
List<User> GetUsers();
}
}
3.2 JIS.Service 项目
该项目为服务契约提供服务实现
using JIS.IService;
using JIS.Utilities.DBComm;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using JIS.Model;
using System.Configuration;
namespace JIS.Service
{
/// <summary>
/// 登陆服务
/// </summary>
public class LoginService : System.MarshalByRefObject, ILoginService
{
public LoginService()
{
//从配置文件中读取数据库连接字符串
Database.SqlConStr = ConfigurationManager.ConnectionStrings["SqlConStr"].ConnectionString;
}
public List<User> GetUsers()
{
string sql = "select * from UserTb";
DataTable table = Database.ExecSqlReturnDataTable(sql);
List<User> userList = new List<User>();
foreach (DataRow row in table.Rows)
{
var user = new User();
user.LoginName = row["LoginName"].ToString();
user.UserName = row["UserName"].ToString();
user.PassWord = row["PassWord"].ToString();
userList.Add(user);
}
return userList;
}
/// <summary>
/// 登录
/// </summary>
/// <param name="loginName"></param>
/// <param name="pwd"></param>
/// <returns></returns>
public bool Login(string loginName, string pwd)
{
string sql = "select * from UserTb where LoginName=@user and Password=@pwd";
SqlParameter[] ps = {
new SqlParameter("@user",loginName),
new SqlParameter("@pwd",pwd)
};
DataTable table = Database.ExecSqlReturnDataTable(sql, ps);
return table.Rows.Count>0;
}
}
}
3.3 JIS.Host 项目
主要是为服务提供主机服务,是服务的宿主程序.这里用控制台简单实现.
step 1:配置服务
App.config配置最顺手配置如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<
<connectionStrings>
<add name="SqlConStr" connectionString=".\DY;uid=sa;pwd=888;database=RMSDb"/>
</connectionStrings>
<system.serviceModel>
<services>
<service name="JIS.Service.LoginService" behaviorConfiguration="MyTypeBehaviors">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8001/LoginService"/>
</baseAddresses>
</host>
<!-- 终结点 -->
<endpoint address="" binding="basicHttpBinding" contract="JIS.IService.ILoginService" ></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyTypeBehaviors" >
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
具体对应关系见下图
step 2:宿主程序
详见Main方法
using System;
using System.Collections.Generic;
using System.ServiceModel;
namespace JISHost
{
class Program
{
static void Main(string[] args)
{
//创建提供服务的主机
ServiceHost host = new ServiceHost(typeof(JIS.Service.LoginService));
host.Open();
//读取服务基地址
IReadOnlyCollection<Uri> uris = host.BaseAddresses;
foreach (var u in uris)
{
Console.WriteLine($"{u.AbsoluteUri} 服务已经启动....");
}
Console.Read();
}
}
}
step 3:运行服务
生成Hosts项目,将生成如下文件,以管理员身份运行JISHost.exe,将启动服务
这时,如果在浏览器地址栏中输入http://localhost:8001/LoginService ,将显示如下信息,这里也给出了客户端调用服务的方法.
4. 客户端Client实现
基本框架流程就如上述所现.
6.WPF客户端登录实现
建立JIS.Client项目,主要代码如下:
6.1 App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
// base.OnStartup(e);
WLogin wLogin = new WLogin();
var s = wLogin.ShowDialog();
}
}
6.1 登录窗体WLogin.xaml
//WLogin.xaml
<local:BaseWindow x:Class="JIS.Views.WLogin"
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:local="clr-namespace:JIS.Views"
xmlns:vm="clr-namespace:JIS.ViewModels"
mc:Ignorable="d"
Title="登录" Height="300" Width="400" WindowStyle="ToolWindow"
WindowStartupLocation="CenterScreen" Background="#FFF6F6F6" ResizeMode="NoResize">
<StackPanel Margin="5">
<Image Grid.Row="0" Grid.ColumnSpan="4" Source="/JIS;component/Resources/Images/Login_Top.png" Margin="20 5 20 0"></Image>
<GroupBox Margin="20 5 20 5" Header="登录"
BorderBrush="OliveDrab" BorderThickness="1"
FocusManager.FocusedElement="{Binding ElementName=pwd}" >
<Grid Width="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" >用户(U):</Label>
<Label Grid.Row="1" >密码(P):</Label>
<ComboBox Grid.Row="0" Grid.Column="1" Margin="4"
ItemsSource="{Binding UserList}"
DisplayMemberPath="UserName"
SelectedValuePath="LoginName"
SelectedIndex="0"
SelectedItem="{Binding SelectedUserItem,Mode=OneWayToSource}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" />
<PasswordBox x:Name="pwd" Grid.Row="1" Grid.Column="1" Margin="4"
local:PasswordBoxHelper.Attach="True"
local:PasswordBoxHelper.Password="{Binding PassWord, Mode=TwoWay}"
HorizontalContentAlignment="Center" />
</Grid>
</GroupBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5 5 20 5">
<Button x:Name="btnReister" Margin="2 2 65 2">申请账号</Button>
<Button x:Name="btnLogin" Margin="2" Command="{Binding LoginCommand}" >登录(_L)</Button>
<Button x:Name="btnExit" Margin="2" Click="BtnExit_Click">退出(_E)</Button>
</StackPanel>
</StackPanel>
</local:BaseWindow>
6.2 WLoginViewModel
using JIS.Model;
using JIS.Views;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace JIS.ViewModels
{
public class WLoginViewModel : NotificationObject
{
//加载登陆服务
LoginService.LoginServiceClient serviceClient = new LoginService.LoginServiceClient();
#region 委托事件
public event EventHandler<LoginTipsEventArgs> LoginHandler;
protected virtual void OnLoginHandler(LoginTipsEventArgs e)
{
this.LoginHandler?.Invoke(this, e);
}
#endregion
#region 构造函数
public WLoginViewModel()
{
_UserList = serviceClient.GetUsers();
}
#endregion
#region 封装字段
private List<User> _UserList;
public List<User> UserList
{
get { return this._UserList; }
set
{
this._UserList = value;
RaisePropertyChanged(() => UserList);
}
}
private string _PassWord;
public string PassWord
{
get { return this._PassWord; }
set
{
this._PassWord = value;
RaisePropertyChanged(() => PassWord);
}
}
private User _SelectedUserItem;
public User SelectedUserItem
{
get { return this._SelectedUserItem; }
set
{
this._SelectedUserItem = value;
RaisePropertyChanged(() => SelectedUserItem);
}
}
private bool? _DialogResult;
public bool? DialogResult
{
get { return this._DialogResult; }
set
{
this._DialogResult = value;
RaisePropertyChanged(() => DialogResult);
}
}
#endregion
#region 封装命令
private ICommand _LoginCommand;
public ICommand LoginCommand
{
get
{
if (this._LoginCommand == null)
this._LoginCommand = new RelayCommandBase(LoginAction);
return this._LoginCommand;
}
}
#endregion
#region 命令实现
private void LoginAction(object obj)
{
if (serviceClient.Login(SelectedUserItem.LoginName, _PassWord))
OnLoginHandler(new LoginTipsEventArgs(LoginState.Success, "登陆成功!"));
else
OnLoginHandler(new LoginTipsEventArgs(LoginState.Success, "登陆失败!"));
}
#endregion
}
}
6.3 WLogin.xaml.cs
WLoginViewModel 绑定,并实现LoginHandler
using JIS.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace JIS.Views
{
/// <summary>
/// WLogin.xaml 的交互逻辑
/// </summary>
public partial class WLogin : BaseWindow
{
public WLogin()
{
InitializeComponent();
WLoginViewModel login = new WLoginViewModel();
login.LoginHandler += Login_LoginHandler;
this.DataContext = login;
}
private void Login_LoginHandler(object sender, LoginTipsEventArgs e)
{
if (e.loginState == LoginState.Fail)
{
MessageBox.Show(e.Tips);
return;
}
else
{
MainWindow mainWindow = new MainWindow();
this.Close();
mainWindow.Show();
}
}
private void BtnExit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}
6.4 PasswordBoxHelper
WPF的PasswordBox控件的Password属性不是依赖属性,无法直接进行数据绑定,为使其在MVVM模式中正常使用,增加附加属性
using System.Windows;
using System.Windows.Controls;
namespace JIS.Views
{
//WPF的PasswordBox控件的Password属性不是依赖属性,无法直接进行数据绑定,为使其在MVVM模式中正常使用,可以为PasswordBox增加一个助手类
public static class PasswordBoxHelper
{
public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached(
"Password",
typeof(string),
typeof(PasswordBoxHelper),
new FrameworkPropertyMetadata(string.Empty, OnPasswordPropertyChanged));
public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached(
"Attach",
typeof(bool),
typeof(PasswordBoxHelper),
new PropertyMetadata(false, Attach));
private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached(
"IsUpdating",
typeof(bool),
typeof(PasswordBoxHelper));
public static void SetAttach(DependencyObject dp, bool value)
{
dp.SetValue(AttachProperty, value);
}
public static bool GetAttach(DependencyObject dp)
{
return (bool)dp.GetValue(AttachProperty);
}
public static string GetPassword(DependencyObject dp)
{
return (string)dp.GetValue(PasswordProperty);
}
public static void SetPassword(DependencyObject dp, string value)
{
dp.SetValue(PasswordProperty, value);
}
private static bool GetIsUpdating(DependencyObject dp)
{
return (bool)dp.GetValue(IsUpdatingProperty);
}
private static void SetIsUpdating(DependencyObject dp, bool value)
{
dp.SetValue(IsUpdatingProperty, value);
}
private static void OnPasswordPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
passwordBox.PasswordChanged -= PasswordChanged;
if (!(bool)GetIsUpdating(passwordBox))
{
passwordBox.Password = (string)e.NewValue;
}
passwordBox.PasswordChanged += PasswordChanged;
}
private static void Attach(
DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
if (passwordBox == null)
return;
if ((bool)e.OldValue)
{
passwordBox.PasswordChanged -= PasswordChanged;
}
if ((bool)e.NewValue)
{
passwordBox.PasswordChanged += PasswordChanged;
}
}
private static void PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
SetIsUpdating(passwordBox, true);
SetPassword(passwordBox, passwordBox.Password);
SetIsUpdating(passwordBox, false);
}
}
}