Je pense que ma compréhension de SimpleMembershipProvider
est de près de 60% et le reste consiste à savoir comment cela fonctionne en interne.
Vous pouvez rapidement trouver un problème lorsque vous utilisez le filtre [InitializeSimpleMembership]
uniquement dans AccountController (le modèle par défaut). Je pense que partout où vous utilisez Memberhsip API ou WebMatrix.WebSecurity
, vous devez vous assurer que ce filtre doit être appelé en premier.
Plus tard, si vous utilisez User.IsInRole
dans mon _Layout.cshtml
. Vous devez appliquer le filtre à tous les contrôleurs, puis vous commencez à l'enregistrer de manière globale.
Cependant, je viens de me rendre compte qu'il existe LazyInitializer.EnsureInitialized
qui fait que l'initialisation n'est effectuée qu'une fois par démarrage de l'application.
Alors pourquoi la SimpleMembershipInitializer
(dans le filtre) n’est pas directement dans Application_Start? Y at-il une raison pour utiliser le filtre?
Je crois que le modèle a utilisé un attribut pour l'initialisation de la base de données, de sorte que les parties non authentifiées du site continuent de fonctionner si l'initialisation échouait.
Dans la plupart des cas, il est préférable d’effectuer cette opération dans App_Start.
Si vous fusionniez la InitializeSimpleMembershipAttribute
dans le Global.asax.cs
Application_Start
de sorte que la SimpleMembershipProvider
soit initialisée sans qu'aucune route AccountController
ne soit appelée ...
... cela pourrait ressembler à quelque chose comme ceci: http://aaron-hoffman.blogspot.com/2013/02/aspnet-mvc-4-membership-users-passwords.html
// The using below is needed for "UsersContext" - it will be relative to your project namespace
using MvcApplication1.Models;
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Threading;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using WebMatrix.WebData;
namespace MvcApplication1
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.Microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.Microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
}
Si vous avez l’intention de vous assurer que la variable InitializeSimpleMembershipAttribute
est exécutée globalement, il serait préférable d’utiliser la méthode MVC 4 dans le code App_Start\FilterConfig.cs
;
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new InitializeMembershipAttribute());
}
}
Conserve le fichier Global.asax.cs propre du code qui devrait probablement être encapsulé de la même manière que MVC 4 par rapport aux versions précédentes. Laisse une belle propre:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}
Je recommande également de changer le type en un AuthorizeAttribute (ce qui est vraiment ce qu'il fait) car les méthodes AuthorizeAttribute sont exécutées avant les méthodes ActionFilterAttribute. (Cela devrait produire moins de problèmes si d'autres ActionFilters vérifient la sécurité et autorise les autorisations personnalisées dérivées).
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public class InitializeMembershipAttribute : AuthorizeAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer,
ref _isInitialized,
ref _initializerLock);
base.OnAuthorization(filterContext);
}
private class SimpleMembershipInitializer ...
}
}
Inspirée de la réponse d'Aaron, j'ai mis en place une solution qui garde Global.asax propre et réutilise le code fourni avec le modèle.
Ajouter une ligne à la méthode RegisterGlobalFilters dans RegisterApp_Satrt/FilterConfig.cs
filters.Add (new InitializeSimpleMembershipAttribute ());
Ajoutez un constructeur par défaut à la classe InitializeMembershipAttribute qui se trouve dans le dossier Filters. Le contenu de ce constructeur sera la même ligne que celle utilisée pour le remplacement de la méthode OnActionExecuting. (Voici à quoi ressemble le constructeur)
public InitializeSimpleMembershipAttribute () { // Assurez-vous que l'adhésion simple à ASP.NET n'est initialisée qu'une fois par application LazyInitializer.EnsureInitialized (ref _initializer, ref _isInitialized, ref _initializerLock); .____.]}
Mettez en commentaire (ou supprimez) le remplacement de la méthode OnActionExecuting.
Et c'est tout. Cette solution me donne deux avantages principaux:
La possibilité de vérifier les éléments liés aux membres et aux rôles immédiatement après la ligne FilterConfig.RegisterGlobalFilters(GlbalFilters.Filters)
est exécutée sur global.asax.
Garantit que l'initialisation de la base de données WebSecurity est exécutée une seule fois.
EDIT: l'attribut InitializeSimpleMembershipAttribute que j'utilise.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
{
private static SimpleMembershipInitializer _initializer;
private static object _initializerLock = new object();
private static bool _isInitialized;
public InitializeSimpleMembershipAttribute()
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
//public override void OnActionExecuting(ActionExecutingContext filterContext)
//{
// // Ensure ASP.NET Simple Membership is initialized only once per app start
// LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
//}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<UsersContext>(null);
try
{
using (var context = new UsersContext())
{
if (!context.Database.Exists())
{
// Create the SimpleMembership database without Entity Framework migration schema
((IObjectContextAdapter)context).ObjectContext.CreateDatabase();
}
}
WebSecurity.InitializeDatabaseConnection("Database_Connection_String_Name", "Users", "UserId", "UserName", autoCreateTables: true);
}
catch (Exception ex)
{
throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.Microsoft.com/fwlink/?LinkId=256588", ex);
}
}
}
}
Pendant une journée, je me suis cogné la tête contre les murs pour tenter de comprendre pourquoi mon rôle Role.GetRoleForUser a échoué. C'est parce que LazyInitializer n'a pas été appelé.
Ainsi, comme Matt l'a dit, insérez-le simplement dans App_Start pour vous assurer que vous n'avez aucun problème.
J'ai passé de nombreuses heures sur ce problème même. mais je me suis retrouvé avec juste ce changement:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new InitializeSimpleMembershipAttribute());
}
}
J'avais vu au hasard l'erreur suivante
System.Web.HttpException (0x80004005): Impossible de se connecter à la base de données SQL Server. ---> System.Data.SqlClient.SqlException (0x80131904): une erreur liée au réseau ou spécifique à une instance s'est produite lors de l'établissement d'une connexion à SQL Server. Le serveur est introuvable ou inaccessible. Vérifiez que le nom de l'instance est correct et que SQL Server est configuré pour autoriser les connexions à distance. (fournisseur: Interfaces réseau SQL, erreur: 26 - Erreur lors de la localisation du serveur/instance spécifiée)
J'ai remarqué que chaque fois que je verrais l'erreur, je verrais aussi:
à ASP._Page_Views_Shared__Layout_cshtml.Execute () dans h:\root\home\btournoux-001\www\site7\Views\Shared_Layout.cshtml: ligne 5
Cela se trouve être la ligne suivante dans mon _Layout.cshtml:
if (User != null && User.Identity != null && (User.IsInRole("publisher") || User.IsInRole("admin")))
Ainsi, afin de tester ma solution simple, j'ai placé un point d'arrêt dans ma classe InitializeSmpleMembershipAttribute lors de l'appel EnsureInitialized et un autre à la première ligne dans SimpleMembershipInitializer.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Ensure ASP.NET Simple Membership is initialized only once per app start
LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
}
private class SimpleMembershipInitializer
{
public SimpleMembershipInitializer()
{
Database.SetInitializer<DataContext>(null);
En plus de ces 2 points d'arrêt, je mets également un point d'arrêt dans mon _Layout.cshtml (je mets le test pour l'utilisateur dans une section de code afin de pouvoir ajouter le point d'arrêt.
@{
var maintenanceAccess = false;
if (User != null && User.Identity != null && (User.IsInRole("publisher") || User.IsInRole("admin")))
{
maintenanceAccess = true;
}
}
Après avoir placé les points d'arrêt, j'ai commenté les filtres.Ajoutez (nouvelle InitializSimpleMembershipAttribute (), puis démarrez l'application dans Visual Studio. Je pouvais voir que j'avais atteint le point d'arrêt dans _Layout.cshtml avant tout autre point d'arrêt. Ensuite, J'ai annulé la mise en commentaire de cette ligne et exécuté à nouveau l'application. Cette fois, j'ai vu les points d'arrêt de la classe InitializeSimpleMembershipAttribute se produire avant le point d'arrêt dans le fichier _Layout.cshtml. premier point d'arrêt de la classe InitializeSimpleMembershipAttribute (EnsureInitialized) mais pas le second - ce à quoi je m'attendais.
Donc, tout semble fonctionner.
Merci à tous ceux qui ont découvert cela!
La raison du filtre InitializeSimpleMembership et de son code excessivement complexe est le cas où un développeur pourrait décider de ne pas utiliser l'authentification par formulaire, le code généré par le modèle fonctionnant toujours correctement. Si vous utilisez toujours l'authentification par formulaire, vous pouvez initialiser SimpleMembership dans la méthode Application_Start du fichier Global.asax. Il y a des instructions détaillées sur la procédure à suivre ici .