I. Introduction▲
Les données avec lesquelles nous travaillons dans des applications métier sont précieuses. Nous avons besoin de les protéger, parfois en suivant de près qui accède et met à jour les données et d'autres fois en empêchant activement l'accès aux données par des tiers dans lesquels on n'a pas confiance.
Le Web est de plus en plus un lieu qui contient des données personnelles - les applications vous « connaissent » la plupart du temps, ce qui permet aux utilisateurs d'avoir des paramètres personnalisés qui seront partagés par toutes leurs applications.
Dans cet exemple, je vais partir de l'application toujours plus populaire SuperEmployees et l'améliorer pour avoir plus de détails sur l'authentification et la personnalisation.
Vous pouvez voir la série complète ici .
Cette démo nécessite les éléments suivants (tout est 100% gratuit) :
II. L'authentification de base▲
Commençons par voir comment nous assurer que seuls les utilisateurs authentifiés peuvent accéder aux données et tenir un log très simple de qui a eu accès aux données.
À partir de l'exemple original, examinons la possibilité d'ajouter l'authentification à la méthode GetSuperEmployees() sur DomainService dans le projet serveur.
[RequiresAuthentication]
public
IQueryable<
SuperEmployee>
GetSuperEmployees
(
)
{
Une fois que nous ajoutons l'attribut RequiresAuthentication, le système fera en sorte que seuls les appels provenant d'utilisateurs authentifiés puissent passer. Cela signifie que nous pouvons ensuite faire très simplement des actions telles que le Loger qui accède aux données et quand :
[RequiresAuthentication]
public
IQueryable<
SuperEmployee>
GetSuperEmployees
(
)
{
File.
AppendAllText
(
@"C:\users\brada\desktop\userslog.txt"
,
String.
Format
(
"{0}: {1} {2}"
,
DateTime.
Now,
ServiceContext.
User.
Identity.
Name,
Environment.
NewLine));
Vous pouvez utiliser une bibliothèque telle que Log4net pour une solution de log complète.
Maintenant, lorsque nous exécutons l'application, plus aucun résultat n'est retourné.
Nous avons besoin de nous connecter pour voir des résultats… Heureusement, le Template Business Application livré avec .NET RIA Services inclut le support idéal pour cela.
Cliquez sur connexion
Remarquez qu'ici, nous pourrions ne pas utiliser l'authentification Windows pour utiliser a la place la sécurité NTLM, les deux façons fonctionnent très bien.
Maintenant, nous allons nous inscrire :
et nous pouvons créer un nouvel utilisateur directement depuis le client Silverlight.
Notez que si vous souhaitez personnaliser le look and feel d'un de ces dialogues, il est facile de le faire depuis Views\LoginControl.xaml, Views\LoginWindow.xaml.
Et si vous voulez contrôler le côté serveur sur la façon dont ceux-ci sont mis en œuvre, vous le pouvez, en regardant dans le projet serveur sous la rubrique Services\AuthenticationService.cs and UserRegistrationService.cs.
Par défaut, cela va à l'encontre du système de rôles et de membres d'ASP.NET, mais vous pouvez les personnaliser comme vous le voulez en substituant simplement ces méthodes-là.
Maintenant, nous avons juste besoin de réagir à l'événement de connexion.
Dans ce cas, je vais tout simplement recharger les données lorsque l'utilisateur se connecte. Les lignes 10 à 13 permettent de s'inscrire à l'événement de connexion, puis recharge les données comme, cette fois-ci, un utilisateur authentifié.
public
Home
(
)
{
InitializeComponent
(
);
var
context =
dds.
DomainContext as
SuperEmployeeDomainContext;
originFilterBox.
ItemsSource =
context.
Origins;
context.
Load
(
context.
GetOriginsQuery
(
));
RiaContext.
Current.
Authentication.
LoggedIn +=
(
s,
e) =>
{
if
(
dds !=
null
) dds.
Load
(
);
};
}
Et remarquez que maintenant, le client sait qui je suis :
Et le serveur le sait tout autant… Si vous allez voir le fichier journal que nous créons dans le DomainService nous verrons :
Donc, c'est cool, mais je pense que nous pouvons faire un peu mieux au niveau de l'expérience utilisateur du client. Après tout, je ne reçois aucune erreur pour me dire que je dois ouvrir une session pour voir les données.
Tout d'abord, nous allons suivre les meilleures pratiques pour gérer l'événement DDS.LoadedData et montrer simplement toutes les erreurs qui sont retournées.
<
riaControls:
DomainDataSource x:
Name=
"dds"
AutoLoad=
"True"
QueryName=
"GetSuperEmployeesQuery"
LoadedData=
"dds_LoadedData"
LoadSize=
"20"
>
Ensuite, la mise en œuvre est très simple :
private
void
dds_LoadedData
(
object
sender,
LoadedDataEventArgs e)
{
if
(
e.
Error !=
null
)
{
var
win =
new
ErrorWindow
(
e.
Error);
win.
Show
(
);
}
}
Maintenant, quand nous exécutons cette application, nous obtenons cette erreur :
C'est utile, peut-être pour un développeur, mais pour un utilisateur final, nous voulons peut-être quelque chose de plus explicite.
La première étape est de ne même pas faire la demande si l'utilisateur n'est pas authentifié. Nous savons cela depuis le client, cela va donc être très facile à faire.
D'abord, inscrivez-vous pour l'événement DDS.DataLoading afin de capturer le chargement avant qu'il ne se produise.
<
riaControls:
DomainDataSource x:
Name=
"dds"
AutoLoad=
"True"
QueryName=
"GetSuperEmployeesQuery"
LoadedData=
"dds_LoadedData"
LoadingData=
"dds_LoadingData"
LoadSize=
"20"
>
Puis nous allons simplement annuler le chargement si l'utilisateur n'est pas authentifié.
private
void
dds_LoadingData
(
object
sender,
LoadingDataEventArgs e)
{
e.
Cancel =
!
RiaContext.
Current.
User.
IsAuthenticated;
}
Maintenant, nous allons fournir un autre moyen de dire à l'utilisateur qu'il a besoin de se connecter. Nous allons tout simplement ajouter un peu de texte et le rendre visible uniquement lorsque l'utilisateur n'est pas authentifié.
<
TextBlock Text=
"Data is only available to authenticated users"
Foreground=
"Red"
DataContext=
"{StaticResource RiaContext}"
Visibility=
"{Binding Path=User.IsAuthenticated, Converter={StaticResource VisibilityConverter}}"
>
</
TextBlock>
La mise en œuvre de la valeur de conversion est assez simple.
public
class
VisibilityConverter :
IValueConverter
{
public
object
Convert
(
object
value
,
Type targetType,
object
parameter,
CultureInfo culture)
{
bool
visibility =
(
bool
)value
;
return
visibility ?
Visibility.
Collapsed :
Visibility.
Visible;
}
public
object
ConvertBack
(
object
value
,
Type targetType,
object
parameter,
CultureInfo culture)
{
Visibility visibility =
(
Visibility)value
;
return
(
visibility !=
Visibility.
Visible);
}
}
Maintenant, quand nous exécutons l'application, nous obtenons une belle interface :
Puis, quand nous nous connectons, c'est très joli.
Nous pouvons même faire un peu mieux en aidant les utilisateurs à se connecter facilement à partir d'ici :
<
TextBlock Text=
"Data is only available to authenticated users. Please click here to log in."
Foreground=
"Red"
DataContext=
"{StaticResource RiaContext}"
Visibility=
"{Binding Path=User.IsAuthenticated, Converter={StaticResource VisibilityConverter}}"
MouseLeftButtonUp=
"TextBlock_MouseLeftButtonUp"
>
</
TextBlock>
private
void
TextBlock_MouseLeftButtonUp
(
object
sender,
MouseButtonEventArgs e)
{
new
LoginWindow
(
).
Show
(
);
}
Ce que nous avons montré dans cette section est à quel point il est facile de configurer l'authentification pour les données et de créer une super expérience utilisateur sur le client.
III. Personnalisation▲
Maintenant que nous avons les bases de l'authentification en place, nous allons voir comment nous pouvons fournir une expérience personnalisée. Pour de nombreuses applications, les utilisateurs passent beaucoup de temps dans l'application, nous voulons qu'ils se sentent à l'aise et qu'ils contrôlent leur environnement. Avant tout, nous allons créer un réglage utilisateur pour la couleur de fond de l'application. Chaque utilisateur peut avoir une valeur différente qui devrait la suivre quelle que soit la machine sur laquelle il exécute l'application.
Commençons par la définition d'une propriété de profil dans le fichier web.config.
<
profile enabled=
"true"
>
<
properties>
<
add
name=
"PageBackgroundColor"
defaultValue=
"White"
/>
</
properties>
</
profile>
Ensuite, nous allons ajouter une propriété au fichier AuthenticationService.cs sur le serveur.
public
class
User :
UserBase
{
public
string
PageBackgroundColor {
get
;
set
;
}
}
Maintenant, nous pouvons tout simplement accéder à cette propriété sur le client. D'abord nous allons définir une page pour définir cette valeur. Dans MyFirstPage.xaml, ajoutons quelques contrôles :
<
StackPanel Orientation=
"Horizontal"
>
<
TextBlock Text=
"Enter background color: "
/>
<
TextBox x:
Name=
"colorTextBox"
KeyDown=
"colorTextBox_KeyDown"
Width=
"100"
/>
<
Button Content=
"Save"
Click=
"Button_Click"
/>
</
StackPanel>
<
TextBlock x:
Name=
"saveStatus"
/>
Nous pouvons gérer le clic sur le bouton comme suit :
private
void
Button_Click
(
object
sender,
RoutedEventArgs e)
{
string
colorString =
this
.
colorTextBox.
Text.
Trim
(
).
ToLower
(
);
colorString =
colorString.
Substring
(
0
,
1
).
ToUpper
(
) +
colorString.
Substring
(
1
,
colorString.
Length -
1
);
RiaContext.
Current.
User.
PageBackgroundColor =
colorString;
this
.
saveStatus.
Text =
"setting saving.."
;
RiaContext.
Current.
Authentication.
SaveUser
((
o) =>
{
this
.
saveStatus.
Text =
"setting saved"
;
},
null
);
}
private
void
colorTextBox_KeyDown
(
object
sender,
KeyEventArgs e)
{
this
.
saveStatus.
Text =
""
;
}
Notez qu'aux lignes 3-4 nous normalisons le nom de chaîne de la couleur de sorte qu'il soit « XAML compliant ». Puis à la ligne 5 nous affectons la valeur à mettons en place dans la propriété User.PageBackgroundColor. Enfin, dans les lignes 6-9, nous ne faisons que donner des indications à l'utilisateur pendant que nous sauvons cette valeur sur le serveur.
Bien sûr, cela ne fonctionnera que si l'utilisateur est déjà connecté, alors cette fois, soyons proactifs et encourageons l'utilisateur à se connecter quand il accède à la page pour la première fois.
protected
override
void
OnNavigatedTo
(
NavigationEventArgs e)
{
if
(!
RiaContext.
Current.
User.
IsAuthenticated)
{
new
LoginWindow
(
).
Show
(
);
}
}
La dernière étape est ici de prendre en compte cette valeur quand elle est fournie. Cela s'avère assez facile dans ce cas. Il suffit d'aller dansMainPage.xaml et de lier la couleur de fond du LayoutRoot à cette valeur.
<
Grid x:
Name=
"LayoutRoot"
Style=
"{StaticResource LayoutRootGridStyle}"
DataContext=
"{StaticResource RiaContext}"
Background=
"{Binding Path=User.PageBackgroundColor}"
>
Puis, quand nous nous connectons :
Et si nous changeons la couleur en bleu :
Et remarquez que le changement de couleur affecte l'application entière.
Et si j'accède à l'application depuis une autre machine, sur un autre navigateur, ma configuration est préservée. Avant de nous connecter, nous obtenons la valeur par défaut :
mais quand nous nous connectons… nos paramètres apparaissent.
Maintenant, comme c'est un paramètre utilisateur, si je crée un nouvel utilisateur « Glenn » et que je définis sa couleur d'arrière-plan en rose :
cela n'affecte pas la couleur de fond pour Darb…
OK, la couleur de fond c'est rigolo, mais ce qui pourrait être encore plus utile est de stocker la façon dont j'ai quitté l'application. Cela garantit que lorsque j'accède à l'application dans le futur, le contexte de mon travail est préservé.
Donc, nous allons ajouter quelques champs en plus à notre profil.
<
profile enabled=
"true"
>
<
properties>
<
add
name=
"PageBackgroundColor"
defaultValue=
"White"
/>
<
add
name=
"SortOrder"
type=
"Int32"
defaultValue=
"0"
/>
<
add
name=
"SortProperty"
defaultValue=
"Name"
/>
<
add
name=
"OriginFilter"
defaultValue=
""
/>
</
properties>
</
profile>
Puis mettre à jour la classe utilisateur.
public
class
User :
UserBase
{
public
string
PageBackgroundColor {
get
;
set
;
}
public
int
SortOrder {
get
;
set
;
}
public
string
SortProperty {
get
;
set
;
}
public
string
OriginFilter {
get
;
set
;
}
}
Nous avons besoin de définir l'interface utilisateur en fonction des paramètres de l'utilisateur.
void
LoadUserState
(
)
{
var
user =
RiaContext.
Current.
User;
if
(
user.
OriginFilter !=
null
)
originFilterBox.
Text =
user.
OriginFilter;
else
originFilterBox.
Text =
string
.
Empty;
if
(
user.
SortProperty !=
null
)
{
dds.
SortDescriptors.
Add
(
new
SortDescriptor
(
user.
SortProperty,
(
SortDirection)user.
SortOrder));
}
}
Et nous avons besoin d'y faire appel lorsque l'on accède à la page…
protected
override
void
OnNavigatedTo
(
NavigationEventArgs e)
{
LoadUserState
(
);
Et lorsque l'utilisateur ouvre une session.
RiaContext.
Current.
Authentication.
LoggedIn +=
(
s,
e) =>
{
User user =
RiaContext.
Current.
User;
if
(
dds !=
null
)
{
dds.
Load
(
);
LoadUserState
(
);
}
};
Ensuite nous avons besoin de stocker les valeurs sur le serveur au bon moment. Cette méthode SaveUserState récupère les bonnes valeurs depuis l'interface utilisateur et les sauvegarde si les valeurs ont changé.
string
lastSave;
void
SaveUserState
(
)
{
User user =
RiaContext.
Current.
User;
if
(!
user.
IsAuthenticated) return
;
var
order =
dds.
SortDescriptors.
LastOrDefault
(
);
if
(
order !=
null
)
{
user.
SortProperty =
order.
PropertyPath.
Value.
ToString
(
);
user.
SortOrder =
(
int
)order.
Direction;
}
user.
OriginFilter =
this
.
originFilterBox.
Text;
if
(
lastSave !=
user.
SortProperty +
user.
SortOrder +
user.
OriginFilter)
{
RiaContext.
Current.
Authentication.
SaveUser
(
);
lastSave =
user.
SortProperty +
user.
SortOrder +
user.
OriginFilter;
}
}
Nous avons besoin d'appeler cette méthode lorsque l'utilisateur navigue vers l'extérieur.
protected
override
void
OnNavigatedFrom
(
NavigationEventArgs e)
{
SaveUserState
(
);
}
Et, périodiquement, nous vérifions si nous avons besoin d'enregistrer les modifications sur le serveur. Nous avons donc ajouté cela dans le constructeur de formes :
Timer =
new
DispatcherTimer
(
);
Timer.
Interval =
TimeSpan.
FromSeconds
(
10
);
Timer.
Tick +=
(
o,
e) =>
SaveUserState
(
);
Timer.
Start
(
);
Maintenant, quand nous exécutons l'application et qu'on configure l'ordre de tri et un filtre
puis qu'on se déconnecte.
Lorsque nous nous connectons à nouveau (depuis une autre machine) nous voyons que l'application nous renvoie là où nous nous sommes quittés.
Nous avons vu dans cette section comment personnaliser l'expérience utilisateur sur la base des préférences de celui-ci.
IV. Interface d'administration▲
Dans cette dernière section, penchons-nous sur la façon de construire une interface d'administration. Ce que nous voulons faire est de fournir une page qui permet aux administrateurs de voir tous les utilisateurs et de modifier leurs paramètres de profil.
Tout d'abord, allons dans AuthenticationService ajouter des méthodes qui vont renvoyer tous les utilisateurs. Nous devons être sûrs que seuls les utilisateurs ayant le rôle d'administrateur peuvent accéder à ce service.
[EnableClientAccess]
public
class
AuthenticationService :
AuthenticationBase<
User>
{
[RequiresRoles(
"Admin"
)]
public
IEnumerable<
User>
GetAllUsers
(
)
{
return
Membership.
GetAllUsers
(
).
Cast<
MembershipUser>(
).
Select
(
mu =>
this
.
GetUserForMembershipUser
(
mu));
}
private
User GetUserForMembershipUser
(
MembershipUser membershipUser)
{
return
this
.
GetAuthenticatedUser
(
new
GenericPrincipal
(
new
GenericIdentity
(
membershipUser.
UserName),
new
string
[
0
]
));
}
Maintenant, ajoutons une interface utilisateur en Silverlight pour utiliser ces infos. Nous allons créer une nouvelle page appelée « Admin ». La première chose que nous voulons faire est de demander à l'utilisateur de se connecter s'il n'est pas déjà identifié en tant qu'utilisateur ayant un rôle d'administrateur.
protected
override
void
OnNavigatedTo
(
NavigationEventArgs e)
{
if
(!
RiaContext.
Current.
User.
Roles.
Contains
(
"Admin"
))
{
new
LoginWindow
(
).
Show
(
);
RiaContext.
Current.
Authentication.
LoggedIn +=
(
s,
ev) =>
{
if
(
dds !=
null
) dds.
Load
(
);
};
}
}
Ensuite, nous définissons un DomainDataSource pour accéder à AuthenticationService.
<
riaControls:
DomainDataSource x:
Name=
"dds"
AutoLoad=
"True"
QueryName=
"GetAllUsersQuery"
LoadSize=
"20"
>
<
riaControls:
DomainDataSource.
DomainContext>
<
App:
AuthenticationContext/>
</
riaControls:
DomainDataSource.
DomainContext>
</
riaControls:
DomainDataSource>
Puis nous définissons des interfaces graphiques simples pour travailler avec les données.
<
activity:
Activity IsActive=
"{Binding IsBusy, ElementName=dds}"
>
<
StackPanel>
<
dataControls:
DataForm x:
Name=
"dataForm1"
Height=
"393"
Width=
"331"
VerticalAlignment=
"Top"
Header=
"User Data"
ItemsSource=
"{Binding Data, ElementName=dds}"
HorizontalAlignment=
"Left"
>
</
dataControls:
DataForm>
<
StackPanel Orientation=
"Horizontal"
Margin=
"0,5,0,0"
>
<
Button Content=
"Submit"
Width=
"105"
Height=
"28"
Click=
"SubmitButton_Click"
/>
</
StackPanel>
</
StackPanel>
</
activity:
Activity>
Maintenant, nous lançons l'application… On se connecte, mais on ne voit pas toutes les données, pourquoi?
Well, the user we created is not an Admin.
Eh bien, l'utilisateur que nous avons créé n'est pas un administrateur. Pour en faire une administration, accédons à l'outil Web Admin et ajoutons-le au rôle « Admin ».
Sélectionnons « Sécurité ».
Puis, sous les rôles, ajoutons un nouveau rôle pour « Admin »
et sous Utilisateurs, « Gestionnaire des utilisateurs », ici vous pouvez facilement ajouter votre utilisateur au rôle.
Maintenant, quand j'ouvre une session et que j'accède à la page d'administration, je peux accéder à l'ensemble des paramètres utilisateur.
Cette section vous a montré comment construire une interface d'administration pour vos applications.
J'espère que vous avez trouvé cet article utile pour prendre en charge l'authentification et la personnalisation des services RIA. Encore une fois, vous pouvez télécharger les fichiers démo complète ou consultez la série complète ici.
V. Conclusion▲
Cet article conclut la série. Pour retrouver les autres articles de la série, rendez-vous sur la page récapitulative.
VI. Remerciements▲
Je tiens ici à remercier Brad Abrams de nous avoir autorisés à traduire son article.
Je remercie également jacques_jean pour sa relecture et ses propositions.