Comment tirer parti du pipeline intégré IIS 7.0
par Mike Volodarsky
IIS 6.0 et versions précédentes ont permis le développement de composants d’application .NET via la plateforme ASP.NET. ASP.NET intégré à IIS via une extension ISAPI et a exposé son propre modèle de traitement des demandes et d’application. Cela a effectivement exposé deux pipelines de serveur distincts, un pour les filtres ISAPI natifs et les composants d’extension, et un autre pour les composants d’application managée. Les composants ASP.NET s’exécutent entièrement à l’intérieur de la bulle d’extension ASP.NET ISAPI et uniquement pour les requêtes mappées à ASP.NET dans la configuration de la carte de script IIS.
IIS 7.0 et versions ultérieures intègre le runtime ASP.NET avec le serveur web principal, fournissant un pipeline de traitement des demandes unifié, exposé aux composants natifs et managés, appelés modules. Les nombreux avantages de l’intégration sont :
- Autoriser les services fournis par les modules natifs et gérés pour s’appliquer à toutes les requêtes, quel que soit le gestionnaire. Par exemple, les formulaires d’authentification gérés peuvent être utilisés pour tout le contenu, y compris les pages ASP, les CGIs et les fichiers statiques.
- Permettre aux composants ASP.NET de fournir des fonctionnalités qui n’étaient pas disponibles précédemment en raison de leur placement dans le pipeline de serveur. Par exemple, un module géré fournissant des fonctionnalités de réécriture de requête peut réécrire la demande avant tout traitement du serveur, y compris l’authentification.
- Un seul espace pour implémenter, configurer, surveiller et prendre en charge les fonctionnalités du serveur, telles que la configuration de mappage de module unique et de gestionnaire, la configuration des erreurs personnalisées uniques, la configuration d’autorisation d’URL unique.
Cet article explique comment les applications ASP.NET peuvent tirer parti du mode intégré dans IIS 7.0 et versions ultérieures, et illustre les tâches suivantes :
- Activation/désactivation des modules au niveau de chaque application.
- Ajout de modules d’application managée au serveur et leur activation pour s’appliquer à tous les types de demandes.
- Ajout de gestionnaires managés.
En savoir plus sur la création de modules IIS 7.0 et versions ultérieures dans le développement de modules et de gestionnaires IIS 7.0 et versions ultérieures avec .NET Framework.
Consultez également le blog, http://www.mvolo.com/pour plus d’informations sur la prise en charge du mode intégré et le développement de modules IIS qui tirent parti de l’intégration ASP.NET dans IIS 7.0 et versions ultérieures. Là, téléchargez un certain nombre de ces modules, y compris les demandes de redirection vers votre application avec le moduleHttpRedirection, les listes de répertoires de recherche pour votre site web IIS avec DirectoryListingModuleet l’affichage de jolies icônes de fichier dans vos applications ASP.NET avec IconHandler.
Prérequis
Pour suivre les étapes décrites dans ce document, les fonctionnalités IIS 7.0 et ultérieures suivantes doivent être installées.
ASP.NET
Installez ASP.NET via le Panneau de configuration Windows Vista. Sélectionnez « Programmes et fonctionnalités » : « Activer ou désactiver les fonctionnalités Windows ». « ASP.NET » sous « Internet Information Services » – « World Wide Web Services » – « Fonctionnalités de développement d’applications »
Si vous disposez d’une build Windows Server® 2008, ouvrez « Gestionnaire de serveur » - « Rôles », puis sélectionnez « Serveur web (IIS) ». Cliquez sur "Ajouter des services de rôle". Sous « Développement d’applications », cochez « ASP.NET ».
ASP classique
Nous voulons montrer comment les modules ASP.NET fonctionnent désormais avec tout le contenu et pas seulement ASP.NET pages. Installez donc ASP classique via le Panneau de configuration Windows Vista. Sélectionnez « Programmes » : « Activer ou désactiver les fonctionnalités Windows ». « ASP.NET » sous « Internet Information Services » – « World Wide Web Services » – « Fonctionnalités de développement d’applications ».
Si vous disposez d’une build Windows Server 2008, ouvrez « Gestionnaire de serveur » - « Rôles », puis sélectionnez « Serveur web (IIS) ». Cliquez sur "Ajouter des services de rôle". Sous « Développement d’applications », cochez « ASP ».
Ajouter l'authentification par formulaire à votre application
Dans le cadre de cette tâche, nous allons activer l’authentification basée sur les formulaires ASP.NET pour l’application. Dans la tâche suivante, nous allons activer le module d’authentification par formulaire pour toutes les demandes adressées à votre application, quel que soit le type de contenu.
Tout d’abord, configurez l’authentification par formulaire comme vous le feriez pour une application ASP.NET normale.
Création d’un exemple de page
Pour illustrer la fonctionnalité, nous ajoutons une page default.aspx au répertoire racine web. Ouvrez le Bloc-notes (pour vous assurer que vous avez accès au répertoire wwwroot ci-dessous, vous devez exécuter en tant qu’administrateur- cliquez avec le bouton droit sur Programmes\Accessoires\Bloc-notes, puis cliquez sur « Exécuter en tant qu’administrateur »), puis créez le fichier suivant : %systemdrive%\inetpub\wwwroot\default.aspx
. Collez les lignes suivantes :
<%=Datetime.Now%>
<BR>
Login Name: <asp:LoginName runat="server"/>
Toutes les default.aspx affichent l’heure actuelle et le nom de l’utilisateur connecté. Nous utilisons cette page ultérieurement pour afficher l’authentification par formulaire en action.
Configuration des règles d’authentification et de contrôle d’accès par formulaire
À présent, pour protéger default.aspx avec l’authentification par formulaire. Créez un fichier web.config dans le %systemdrive%\inetpub\wwwroot
répertoire et ajoutez la configuration indiquée ci-dessous :
<configuration>
<system.web>
<!--membership provider entry goes here-->
<authorization>
<deny users="?"/>
<allow users="*"/>
</authorization>
<authentication mode="Forms"/>
</system.web>
</configuration>
Cette configuration définit le mode d’authentification ASP.NET pour utiliser l’authentification basée sur les formulaires et ajoute des paramètres d’autorisation pour contrôler l’accès à l’application. Ces paramètres refusent l’accès aux utilisateurs anonymes ( ?) et autorisent uniquement les utilisateurs authentifiés (*).
Implémentation d’un fournisseur d’appartenances
Étape 1 : Nous devons fournir un magasin d’authentification sur lequel les informations d’identification de l’utilisateur seront vérifiées. Pour illustrer l’intégration approfondie entre ASP.NET et IIS 7.0 et versions ultérieures, nous utilisons notre propre fournisseur d’appartenance XML (vous pouvez également utiliser le fournisseur d’appartenance SQL Server par défaut si SQL Server est installé).
Ajoutez l’entrée suivante juste après l’élément de configuration initiale <>/<system.web> dans le fichier web.config :
<membership defaultProvider="AspNetReadOnlyXmlMembershipProvider">
<providers>
<add name="AspNetReadOnlyXmlMembershipProvider" type="AspNetReadOnlyXmlMembershipProvider" description="Read-only XML membership provider" xmlFileName="~/App_Data/MembershipUsers.xml"/>
</providers>
</membership>
Étape 2 : Une fois l’entrée de configuration ajoutée, vous devez enregistrer le code du fournisseur d’appartenance fourni dans l’Annexe en tant que XmlMembershipProvider.cs dans votre %systemdrive%\inetpub\wwwroot\App_Code
annuaire. Si ce répertoire n’existe pas, vous devez le créer.
Remarque
Si vous utilisez le Bloc-notes, veillez à définir Enregistrer sous : Tous les fichiers pour empêcher l’enregistrement du fichier en tant que XmlMembershipProvider.cs.txt.
Étape 3 : Tout ce qui reste est le magasin d’informations d’identification réel. Enregistrez l’extrait de code xml ci-dessous en tant que fichier MembershipUsers.xml dans le %systemdrive%\inetpub\wwwroot\App_Data
répertoire.
Remarque
Si vous utilisez le Bloc-notes, veillez à définir Enregistrer sous : Tous les fichiers pour empêcher l’enregistrement du fichier en tant que MembershipUsers.xml.txt.
<Users>
<User>
<UserName>Bob</UserName>
<Password>contoso!</Password>
<Email>bob@contoso.com</Email>
</User>
<User>
<UserName>Alice</UserName>
<Password>contoso!</Password>
<Email>alice@contoso.com</Email>
</User>
</Users>
Si le répertoire App_Data n’existe pas, vous devez le créer.
Remarque
En raison des modifications de sécurité dans Windows Server 2003 et Windows Vista SP1, vous ne pouvez plus utiliser l’outil d’administration IIS pour créer des comptes d’utilisateur d’appartenance pour les fournisseurs d’appartenance non gaCed.
Une fois cette tâche terminée, accédez à l’outil Administration IIS et ajoutez ou supprimez des utilisateurs pour votre application. Démarrez « INETMGR » à partir de la commande « Exécuter... » Menu. Ouvrez les connexions « + » dans l’arborescence à gauche jusqu’à ce que le « site web par défaut » s’affiche. Sélectionnez « Site web par défaut », puis accédez à droite, puis cliquez sur la catégorie « Sécurité ». Les fonctionnalités restantes affichent les utilisateurs « .NET ». Cliquez sur Utilisateurs .NET et ajoutez un ou plusieurs comptes d’utilisateur de votre choix.
Recherchez les utilisateurs nouvellement créés dans MembershipUsers.xml.
Création d'un formulaire de connexion
Pour utiliser l’authentification par formulaire, nous devons créer une page de connexion. Ouvrez le Bloc-notes (Pour vous assurer que vous avez accès au répertoire wwwroot ci-dessous, vous devez exécuter en tant qu’administrateur en cliquant avec le bouton droit sur Programmes\Accessoires\Bloc-notes, puis en cliquant sur « Exécuter en tant qu’administrateur »), puis créer le fichier login.aspx dans le %systemdrive%\inetpub\wwwroot
répertoire. Remarque : veillez à définir Enregistrer sous : tous les fichiers pour empêcher l’enregistrement du fichier en tant que login.aspx.txt. Collez les lignes suivantes :
<%@ Page language="c#" %>
<form id="Form1" runat="server">
<asp:LoginStatus runat="server" />
<asp:Login runat="server" />
</form>
Il s’agit de la page de connexion vers laquelle vous êtes redirigé lorsque vos règles d’autorisation refusent l’accès à une ressource particulière.
Test
Ouvrez une fenêtre Internet Explorer et demandez http://localhost/default.aspx
. Vous voyez que vous êtes redirigé vers login.aspx, car initialement votre n’a pas été authentifié et que nous avons refusé l’accès aux utilisateurs non authentifiés précédemment. Si vous vous connectez avec l’une des paires nom d’utilisateur/mot de passe spécifiées dans MembershipUsers.xml, vous êtes redirigé vers default.aspx page initialement demandée. Cette page affiche ensuite l’heure actuelle et l’identité de l’utilisateur avec laquelle vous avez authentifié.
À ce stade, nous avons déployé une solution d’authentification personnalisée à l’aide de l’authentification par formulaire, des contrôles de connexion et de l’appartenance. Cette fonctionnalité n’est pas nouvelle dans IIS 7.0 ou version ultérieure : elle a été disponible depuis ASP.NET 2.0 sur les versions antérieures d’IIS.
Toutefois, le problème est que seul le contenu géré par ASP.NET est protégé.
Si vous fermez et rouvrez la fenêtre du navigateur et demandez http://localhost/iisstart.htm
, vous n’êtes pas invité à entrer les informations d’identification. ASP.NET ne participe pas à une demande de fichier statique comme iisstart.htm. Par conséquent, elle ne peut pas la protéger avec l’authentification par formulaire. Vous voyez le même comportement avec les pages ASP classiques, les programmes CGI, PHP ou Perl. L’authentification par formulaire est une fonctionnalité ASP.NET et n’est simplement pas disponible pendant les demandes adressées à ces ressources.
Activation de l’authentification par formulaire pour l’ensemble de l’application
Dans cette tâche, nous allons éliminer la limitation des ASP.NET sur les versions précédentes et activer la fonctionnalité d’authentification par formulaire et d’autorisation d’URL ASP.NET pour l’ensemble de l’application.
Pour tirer parti de l’intégration de ASP.NET, notre application doit être configurée pour s’exécuter en mode intégré. Le mode d’intégration ASP.NET est configurable par pool d’applications, ce qui permet aux applications ASP.NET dans différents modes d’être hébergées côte à côte sur le même serveur. Le pool d’applications par défaut dans lequel notre application utilise déjà le mode intégré par défaut. Nous n’avons donc rien à faire ici.
Pourquoi n’avons-nous pas connu les avantages du mode intégré lorsque nous avons essayé d’accéder à la page statique précédemment ? La réponse se trouve dans les paramètres par défaut de tous les modules ASP.NET fournis avec IIS 7.0 et versions ultérieures.
Tirer parti du pipeline intégré
La configuration par défaut de tous les modules gérés qui sont fournis avec IIS 7.0 et versions ultérieures, y compris les modules d’authentification par formulaire et d’autorisation d’URL, utilise une condition préalable pour que ces modules s’appliquent uniquement au contenu géré par un gestionnaire (ASP.NET). Cela s’effectue pour des raisons de compatibilité descendante.
En supprimant la condition préalable, nous effectuons l’exécution du module géré souhaité pour toutes les demandes adressées à l’application, quel que soit le contenu. Cela est nécessaire pour protéger nos fichiers statiques et tout autre contenu d’application avec l’authentification basée sur les formulaires.
Pour ce faire, ouvrez le fichier web.config de l’application situé dans le %systemdrive%\inetpub\wwwroot
répertoire, puis collez les lignes suivantes immédiatement sous le premier <élément de configuration> :
<system.webServer>
<modules>
<remove name="FormsAuthenticationModule" />
<add name="FormsAuthenticationModule" type="System.Web.Security.FormsAuthenticationModule" />
<remove name="UrlAuthorization" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<remove name="DefaultAuthentication" />
<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" />
</modules>
</system.webServer>
Cette configuration ajoute à nouveau les éléments du module sans condition préalable, ce qui leur permet de s’exécuter pour toutes les demandes adressées à l’application.
Test
Fermez toutes les instances d’Internet Explorer afin que les informations d’identification entrées avant ne soient plus mises en cache. Ouvrez Internet Explorer et effectuez une demande à l’application à l’URL suivante :
http://localhost/iisstart.htm
Vous êtes redirigé vers la page login.aspx pour vous connecter.
Connectez-vous avec une paire nom d’utilisateur/mot de passe utilisée précédemment. Lorsque vous vous connectez correctement, vous êtes redirigé vers la ressource d’origine, qui affiche la page d’accueil IIS.
Remarque
Même si vous avez demandé un fichier statique, le module d’authentification par formulaire managé et le module d’autorisation d’URL ont fourni leurs services pour protéger votre ressource.
Pour illustrer cela encore plus loin, nous ajoutons une page ASP classique et nous la protégeons avec l’authentification par formulaire.
Ouvrez le Bloc-notes (pour vous assurer que vous avez accès au répertoire wwwroot ci-dessous, vous devez exécuter en tant qu’administrateur- cliquez avec le bouton droit sur Programmes\Accessoires\Bloc-notes, puis cliquez sur « Exécuter en tant qu’administrateur »), puis créer un fichier page.asp dans votre %systemdrive%\inetpub\wwwroot
répertoire.
Remarque
Si vous utilisez le Bloc-notes, veillez à définir Enregistrer sous : Tous les fichiers pour empêcher l’enregistrement du fichier en tant que page.asp.txt. Collez les lignes ci-dessous :
<%
for each s in Request.ServerVariables
Response.Write s & ": "&Request.ServerVariables(s) & VbCrLf
next
%>
Fermez à nouveau toutes les instances d’Internet Explorer ; sinon, vos informations d’identification sont toujours mises en cache et demandées http://localhost/page.asp
. Vous êtes de nouveau redirigé vers la page de connexion, et après l’authentification réussie, affichez la page ASP.
Félicitations : vous avez correctement ajouté des services managés au serveur, en les activant pour toutes les demandes adressées au serveur, quel que soit le gestionnaire !
Résumé
Cette procédure pas à pas a montré comment le mode intégré ASP.NET peut être utilisé pour rendre les fonctionnalités ASP.NET puissantes disponibles non seulement pour ASP.NET pages, mais pour l’ensemble de l’application.
Plus important encore, vous pouvez maintenant créer de nouveaux modules gérés à l’aide des API familières ASP.NET 2.0 qui ont la capacité d’exécuter pour tout le contenu de l’application et de fournir un ensemble amélioré de services de traitement des demandes à votre application.
N’hésitez pas à consulter le blog, https://www.mvolo.com/, pour plus d’informations sur la prise en charge du mode intégré et sur le développement de modules IIS qui tirent profit de l’intégration ASP.NET dans IIS 7 et versions ultérieures. Vous pouvez également y télécharger un certain nombre de ces modules, notamment les demandes de redirection vers votre application avec le module HttpRedirection, des listes bien présentées de répertoires de recherche pour votre site web IIS avec DirectoryListingModuleet afficher de jolies icônes de fichier dans vos applications ASP.NET avec IconHandler.
Annexe
Ce fournisseur d’appartenances est basé sur l’exemple de fournisseur d’appartenance XML situé dans ces fournisseurs d’appartenances.
Pour utiliser ce fournisseur d’appartenances, enregistrez le code en tant que XmlMembershipProvider.cs dans votre %systemdrive%\inetpub\wwwroot\App\_Code
annuaire. Si ce répertoire n’existe pas, vous devrez le créer. Remarque : veillez à définir Enregistrer sous : tous les fichiers, si vous utilisez le Bloc-notes, pour empêcher l’enregistrement du fichier en tant que XmlMembershipProvider.cs.txt.
Remarque
Cet exemple de fournisseur d’appartenances est uniquement destiné à cette démonstration. Elle n’est pas conforme aux meilleures pratiques et aux exigences de sécurité d’un fournisseur d’appartenances de production, y compris le stockage de mots de passe en toute sécurité et l’audit des actions utilisateur. N’utilisez pas ce fournisseur d’appartenances dans votre application !
using System;
using System.Xml;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Web.Security;
using System.Web.Hosting;
using System.Web.Management;
using System.Security.Permissions;
using System.Web;
public class AspNetReadOnlyXmlMembershipProvider : MembershipProvider
{
private Dictionary<string, MembershipUser> _Users;
private string _XmlFileName;
// MembershipProvider Properties
public override string ApplicationName
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override bool EnablePasswordRetrieval
{
get { return false; }
}
public override bool EnablePasswordReset
{
get { return false; }
}
public override int MaxInvalidPasswordAttempts
{
get { throw new NotSupportedException(); }
}
public override int MinRequiredNonAlphanumericCharacters
{
get { throw new NotSupportedException(); }
}
public override int MinRequiredPasswordLength
{
get { throw new NotSupportedException(); }
}
public override int PasswordAttemptWindow
{
get { throw new NotSupportedException(); }
}
public override MembershipPasswordFormat PasswordFormat
{
get { throw new NotSupportedException(); }
}
public override string PasswordStrengthRegularExpression
{
get { throw new NotSupportedException(); }
}
public override bool RequiresQuestionAndAnswer
{
get { return false; }
}
public override bool RequiresUniqueEmail
{
get { throw new NotSupportedException(); }
}
// MembershipProvider Methods
public override void Initialize(string name,
NameValueCollection config)
{
// Verify that config isn't null
if (config == null)
throw new ArgumentNullException("config");
// Assign the provider a default name if it doesn't have one
if (String.IsNullOrEmpty(name))
name = "ReadOnlyXmlMembershipProvider";
// Add a default "description" attribute to config if the
// attribute doesn't exist or is empty
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description",
"Read-only XML membership provider");
}
// Call the base class's Initialize method
base.Initialize(name, config);
// Initialize _XmlFileName and make sure the path
// is app-relative
string path = config["xmlFileName"];
if (String.IsNullOrEmpty(path))
path = "~/App_Data/MembershipUsers.xml";
if (!VirtualPathUtility.IsAppRelative(path))
throw new ArgumentException
("xmlFileName must be app-relative");
string fullyQualifiedPath = VirtualPathUtility.Combine
(VirtualPathUtility.AppendTrailingSlash
(HttpRuntime.AppDomainAppVirtualPath), path);
_XmlFileName = HostingEnvironment.MapPath(fullyQualifiedPath);
config.Remove("xmlFileName");
// Make sure we have permission to read the XML data source and
// throw an exception if we don't
FileIOPermission permission =
new FileIOPermission(FileIOPermissionAccess.Read,
_XmlFileName);
permission.Demand();
// Throw an exception if unrecognized attributes remain
if (config.Count > 0)
{
string attr = config.GetKey(0);
if (!String.IsNullOrEmpty(attr))
throw new ProviderException
("Unrecognized attribute: " + attr);
}
}
public override bool ValidateUser(string username, string password)
{
// Validate input parameters
if (String.IsNullOrEmpty(username) ||
String.IsNullOrEmpty(password))
return false;
// Make sure the data source has been loaded
ReadMembershipDataStore();
// Validate the user name and password
MembershipUser user;
if (_Users.TryGetValue(username, out user))
{
if (user.Comment == password) // Case-sensitive
{
return true;
}
}
return false;
}
public override MembershipUser GetUser(string username,
bool userIsOnline)
{
// Note: This implementation ignores userIsOnline
// Validate input parameters
if (String.IsNullOrEmpty(username))
return null;
// Make sure the data source has been loaded
ReadMembershipDataStore();
// Retrieve the user from the data source
MembershipUser user;
if (_Users.TryGetValue(username, out user))
return user;
return null;
}
public override MembershipUserCollection GetAllUsers(int pageIndex,
int pageSize, out int totalRecords)
{
// Note: This implementation ignores pageIndex and pageSize,
// and it doesn't sort the MembershipUser objects returned
// Make sure the data source has been loaded
ReadMembershipDataStore();
MembershipUserCollection users =
new MembershipUserCollection();
foreach (KeyValuePair<string, MembershipUser> pair in _Users)
users.Add(pair.Value);
totalRecords = users.Count;
return users;
}
public override int GetNumberOfUsersOnline()
{
throw new NotSupportedException();
}
public override bool ChangePassword(string username,
string oldPassword, string newPassword)
{
throw new NotSupportedException();
}
public override bool
ChangePasswordQuestionAndAnswer(string username,
string password, string newPasswordQuestion,
string newPasswordAnswer)
{
throw new NotSupportedException();
}
public override MembershipUser CreateUser(string username,
string password, string email, string passwordQuestion,
string passwordAnswer, bool isApproved, object providerUserKey,
out MembershipCreateStatus status)
{
throw new NotSupportedException();
}
public override bool DeleteUser(string username,
bool deleteAllRelatedData)
{
throw new NotSupportedException();
}
public override MembershipUserCollection
FindUsersByEmail(string emailToMatch, int pageIndex,
int pageSize, out int totalRecords)
{
throw new NotSupportedException();
}
public override MembershipUserCollection
FindUsersByName(string usernameToMatch, int pageIndex,
int pageSize, out int totalRecords)
{
throw new NotSupportedException();
}
public override string GetPassword(string username, string answer)
{
throw new NotSupportedException();
}
public override MembershipUser GetUser(object providerUserKey,
bool userIsOnline)
{
throw new NotSupportedException();
}
public override string GetUserNameByEmail(string email)
{
throw new NotSupportedException();
}
public override string ResetPassword(string username,
string answer)
{
throw new NotSupportedException();
}
public override bool UnlockUser(string userName)
{
throw new NotSupportedException();
}
public override void UpdateUser(MembershipUser user)
{
throw new NotSupportedException();
}
// Helper method
private void ReadMembershipDataStore()
{
lock (this)
{
if (_Users == null)
{
_Users = new Dictionary<string, MembershipUser>
(16, StringComparer.InvariantCultureIgnoreCase);
XmlDocument doc = new XmlDocument();
doc.Load(_XmlFileName);
XmlNodeList nodes = doc.GetElementsByTagName("User");
foreach (XmlNode node in nodes)
{
MembershipUser user = new MembershipUser(
Name, // Provider name
node["UserName"].InnerText, // Username
null, // providerUserKey
node["Email"].InnerText, // Email
String.Empty, // passwordQuestion
node["Password"].InnerText, // Comment
true, // isApproved
false, // isLockedOut
DateTime.Now, // creationDate
DateTime.Now, // lastLoginDate
DateTime.Now, // lastActivityDate
DateTime.Now, // lastPasswordChangedDate
new DateTime(1980, 1, 1) // lastLockoutDate
);
_Users.Add(user.UserName, user);
}
}
}
}
}