web-dev-qa-db-fra.com

Pourquoi attribuer InitializeSimpleMembership dans l'application MVC 4

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?

24
CallMeLaNN

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.

21
Matt Magpayo

Si vous fusionniez la InitializeSimpleMembershipAttribute dans le Global.asax.csApplication_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);
                }
            }
        }
    }
}
18
Aaron Hoffman

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 ...
    }
}
5
Erik Philips

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.

  1. Ajouter une ligne à la méthode RegisterGlobalFilters dans RegisterApp_Satrt/FilterConfig.cs

    filters.Add (new InitializeSimpleMembershipAttribute ());
  2. 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); .____.]}
  3. Mettez en commentaire (ou supprimez) le remplacement de la méthode OnActionExecuting.

Et c'est tout. Cette solution me donne deux avantages principaux:

  1. 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.

  2. 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);
            }
        }
    }
}
3
Mike

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.

2
Mircea Dogaru

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! 

2
N8NT

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 .

0
Kevin Junghans