Сейчас мы расширим наши классы так, чтобы они использовали не аутентификацию по умолчанию, а аутентификацию, в которой пользователи берутся из базы данных.
Для этого переопределим несколько методов в класса AuthenticationService.
Нам нужно переопределить методы ValidateUser и GetAuthenticatedUser. ValidateUser должен вернуть true, если пользователь ввел правильные login и пароль.
GetAuthenticatedUser должен вернуть объект типа User (тот, который создан автоматически).
AuthenticationService.cs
namespace MyApplication.Web
{
using System.Security.Authentication;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.ServiceModel.DomainServices.Server.ApplicationServices;
using System.Threading;
using System.Security.Principal;
using HLUser = MyApplication.BLL.User;
using MyApplication.BLL;
using MyApplication.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
/// </summary>
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User>
{
protected override bool ValidateUser(string username, string password)
{
return false;
}
protected override User GetAuthenticatedUser(IPrincipal pricipal)
{
return null;
}
}
}
Пока мы не может вернуть ничего разумного. Расширим наши классы User. Добавим несколько новых свойств, которые нам пригодятся в дальнейшем.
User.cs
namespace MyApplication.Web
{
using System.Runtime.Serialization;
using System.ServiceModel.DomainServices.Server.ApplicationServices;
using System.Collections.Generic;
/// <summary>
/// Class containing information about the authenticated user.
/// </summary>
public partial class User : UserBase
{
//// NOTE: Profile properties can be added for use in Silverlight application.
//// To enable profiles, edit the appropriate section of web.config file.
////
//// public string MyProfileProperty { get; set; }
/// <summary>
/// Gets and sets the friendly name of the user.
/// </summary>
public string FriendlyName { get; set; }
public bool IsAdmin { get; internal set; }
public bool HasProjects { get; internal set; }
public bool HasManagedProjects { get; internal set; }
public int ID { get; set; }
public List<int> ManagedProjects { get; set; }
}
}
User.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyApplication.BLL
{
public partial class User
{
public static bool Authenticate(string userName, string password)
{
using (var ctx = new MyApplicationEntities())
{
var user = ctx.Users.Where(p => p.Login == userName).FirstOrDefault();
if (user != null && Hasher.VerifyMd5Hash(password, user.Password))
{
return true;
}
}
return false;
}
public static bool IsUserExist(string userName)
{
using (var ctx = new MyApplicationEntities())
{
var user = ctx.Users.Where(p => p.Login == userName).FirstOrDefault();
if (user != null)
{
return true;
}
}
return false;
}
public static string GetUserName(string userName)
{
using (var ctx = new MyApplicationEntities())
{
var user = ctx.Users.Where(p => p.Login == userName).FirstOrDefault();
if (user != null)
{
return user.Name;
}
}
return null;
}
public static User GetUserByLogin(string userName)
{
using (var ctx = new MyApplicationEntities())
{
var user = ctx.Users.Where(p => p.Login == userName).FirstOrDefault();
if (user != null)
{
return user;
}
}
return null;
}
public int GetProjectsCount()
{
using (var ctx = new MyApplicationEntities())
{
return ctx.UserProjects.Where(p => p.UserID == ID).Count();
}
}
public int GetManagedProjectsCount()
{
using (var ctx = new MyApplicationEntities())
{
return ctx.UserProjects.Include("ProjectRole").Where(p => p.UserID == ID && p.ProjectRole.Name == "Manager").Count();
}
}
public List<int> GetManagedProjects()
{
using (var ctx = new MyApplicationEntities())
{
return (from pu in ctx.UserProjects.Include("ProjectRole")
where pu.UserID == ID && pu.ProjectRole.Name == "Manager"
select pu.ProjectID).ToList();
}
}
public static string GetProjectRole(int userId, int projectId)
{
using (var ctx = new MyApplicationEntities())
{
return (from up in ctx.UserProjects.Include("ProjectRole")
where up.UserID == userId && up.ProjectID == projectId
select up.ProjectRole.Name).FirstOrDefault();
}
}
}
}
User.shared.cs
using MyApplication.BLL;
namespace MyApplication.Web
{
/// <summary>
/// Partial class extending the User type that adds shared properties and methods
/// that will be available both to the server app and the client app
/// </summary>
public partial class User
{
/// <summary>
/// Returns the user display name, which by default is its Friendly Name,
/// and if that is not set, its User Name
/// </summary>
public string DisplayName
{
get
{
if (!string.IsNullOrEmpty(this.FriendlyName))
{
return this.FriendlyName;
}
else
{
return this.Name;
}
}
}
}
}
Теперь, когда нужные свойства добавлены можно переходить к реализации класса AuthenticationService. Стоит обратить внимание на то, что сейчас мы можем не заполнять поля класса User в методе GetAuthenticatedUser. С другой стороны, нужно понимать, что, поскольку Silverlight – клиентская технология, нам придется подгружать все недостающие данные асинхронно во время работы приложения.
Я не хочу этого делать, так что я заполню все поля сразу. Из-за этого появится следующая проблема – данные в полях User будут действительны до момента нового вызова GetAuthenticatedUser, то есть до момента нового входа пользователя в систему.
AuthenticationService.cs
namespace MyApplication.Web
{
using System.Security.Authentication;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.ServiceModel.DomainServices.Server.ApplicationServices;
using System.Threading;
using System.Security.Principal;
using HLUser = MyApplication.BLL.User;
using MyApplication.BLL;
using MyApplication.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
/// </summary>
[EnableClientAccess]
public class AuthenticationService : AuthenticationBase<User>
{
protected override bool ValidateUser(string username, string password)
{
return HLUser.Authenticate(username, password);
}
protected override User GetAuthenticatedUser(IPrincipal pricipal)
{
User user = null;
if (HLUser.IsUserExist(pricipal.Identity.Name))
{
user = new User();
var dbUser = HLUser.GetUserByLogin(pricipal.Identity.Name);
if (dbUser != null)
{
user.Name = dbUser.Name;
user.IsAdmin = dbUser.IsAdmin;
user.HasProjects = dbUser.GetProjectsCount() > 0;
user.HasManagedProjects = dbUser.GetManagedProjectsCount() > 0;
user.ID = dbUser.ID;
user.ManagedProjects = dbUser.GetManagedProjects();
}
}
return user;
}
}
}
Поскольку сейчас мы будем использовать таблицу в базе данных для аутентификации пользователей, стоит отключить возможность регистрации пользователей, которая автоматически доступна при создании нового проекта.
Для этого изменим страницу LoginForm следующим образом:
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApplication.Controls"
xmlns:login="clr-namespace:MyApplication.LoginUI"
x:Class="MyApplication.LoginUI.LoginForm"
KeyDown="LoginForm_KeyDown"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=login:LoginInfo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:BusyIndicator x:Name="busyIndicator" BusyContent="{Binding Path=ApplicationStrings.BusyIndicatorLoggingIn, Source={StaticResource ResourceWrapper}}"
IsBusy="{Binding IsLoggingIn}">
<StackPanel Orientation="Vertical">
<local:CustomDataForm x:Name="loginForm"
Padding="10,0,10,0"
CurrentItem="{Binding}"
IsEnabled="{Binding IsLoggingIn, Converter={StaticResource NotOperatorValueConverter}}"
AutoEdit="True" CommandButtonsVisibility="None" HeaderVisibility="Collapsed"
AutoGeneratingField="LoginForm_AutoGeneratingField"
Style="{StaticResource LoginDataFormStyle}" />
</StackPanel>
</local:BusyIndicator>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
<Button x:Name="loginButton" Content="{Binding Path=ApplicationStrings.OKButton, Source={StaticResource ResourceWrapper}}" Click="LoginButton_Click" Style="{StaticResource RegisterLoginButtonStyle}" IsEnabled="{Binding Path=CanLogIn}" />
<Button x:Name="loginCancel" Content="{Binding Path=ApplicationStrings.CancelButton, Source={StaticResource ResourceWrapper}}" Click="CancelButton_Click" Style="{StaticResource RegisterLoginButtonStyle}" />
</StackPanel>
<!--<StackPanel Grid.Row="1" Grid.Column="0" Style="{StaticResource RegisterLoginLinkPanelStyle}">
<TextBlock Text="{Binding Path=ApplicationStrings.NotRegisteredYetLabel, Source={StaticResource ResourceWrapper}}" Style="{StaticResource CommentStyle}"/>
<HyperlinkButton x:Name="registerNow" Content="{Binding Path=ApplicationStrings.RegisterNowButton, Source={StaticResource ResourceWrapper}}" Click="RegisterNow_Click" IsEnabled="{Binding IsLoggingIn, Converter={StaticResource NotOperatorValueConverter}}" />
</StackPanel>-->
</Grid>
</StackPanel>
Теперь мы можем использовать все поля, которые были описаны в классе User в нашем Silverlight приложении.
Также имеет смысл подписаться на события LoggedIn и LoggedOut на странице Main, чтобы можно было упралять пользовательским интерфейсом.
Например, администратор может видеть больше страниц, чем все другие пользователи, можно реализовать это следующим образом:
MainPage.xaml.cs
namespace MyApplication
{
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using MyApplication.LoginUI;
using System;
/// <summary>
/// <see cref="UserControl"/> class providing the main UI for the application.
/// </summary>
public partial class MainPage : UserControl
{
/// <summary>
/// Creates a new <see cref="MainPage"/> instance.
/// </summary>
public MainPage()
{
InitializeComponent();
this.loginContainer.Child = new LoginStatus();
WebContext.Current.Authentication.LoggedIn += new System.EventHandler<System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs>(Authentication_LoggedIn);
WebContext.Current.Authentication.LoggedOut += new System.EventHandler<System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs>(Authentication_LoggedOut);
}
void Authentication_LoggedOut(object sender, System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs e)
{
// скрыть станицы, недоступные для незарегистрированных пользователей
this.ContentFrame.Navigate(new Uri("/Home", UriKind.Relative));
}
void Authentication_LoggedIn(object sender, System.ServiceModel.DomainServices.Client.ApplicationServices.AuthenticationEventArgs e)
{
if (WebContext.Current.User.IsAdmin)
{
// показывать больше страниц
}
}
/// <summary>
/// After the Frame navigates, ensure the <see cref="HyperlinkButton"/> representing the current page is selected
/// </summary>
private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
{
foreach (UIElement child in LinksStackPanel.Children)
{
HyperlinkButton hb = child as HyperlinkButton;
if (hb != null && hb.NavigateUri != null)
{
if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
{
VisualStateManager.GoToState(hb, "ActiveLink", true);
}
else
{
VisualStateManager.GoToState(hb, "InactiveLink", true);
}
}
}
}
/// <summary>
/// If an error occurs during navigation, show an error window
/// </summary>
private void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
e.Handled = true;
ErrorWindow.CreateNew(e.Exception);
}
}
}
Дальше мы сделаем страницу создания нового пользователя, а также добавления пользователей к проектам.
Здравствуйте!
ОтветитьУдалитьНачал разбираться с аутентификацией в Silverlight по вашим статьям.
По умолчанию бизнес приложение SL создается со структурой из 2-х проектов MyApplication и MyApplication.Web. Не пойму что такое MyApplication.BLL, и что там содержится, можите объяснить подробней?
При создании Silverlight проекта вы можете выбрать метод для размещения приложения Silverlight. Есть два метода: 1) Использовать новый отдельный веб-сайт ASP.NET или проект веб-приложения ASP.NET для размещения приложения Silverlight 2)Не использовать новый отдельный веб-сайт ASP.NET. Вместо этого будет создана тестовая страница HTML для размещения данного приложения.
УдалитьЕсли вы выберите первый способ, то у вас создадутся два проекта. Первый проект - это Silverlight проект, который компилируется в .xap файл, а второй проект это и будет ASP сайт, на страничке которого будет запускаться ваш xap файл. ASP проект как раз будет компилироваться в MyApplication.BLL, это сборка вашего сайта, где находятся скомпилированные страницы сайта.