Зачем InitializeSimpleMembershipAttribute в приложении MVC 4

Я думаю, что мое понимание SimpleMembershipProvider составляет почти 60%, а остальное нужно узнать, как это работает внутри.

Вы можете быстро найти проблему при использовании фильтра [InitializeSimpleMembership] только в AccountController (шаблон по умолчанию). Я думаю, что везде, где вы используете Memberhsip API или WebMatrix.WebSecurity, вы должны убедиться, что этот фильтр вызывается первым.

Позже, если вы используете User.IsInRole в моем _Layout.cshtml. Вам нужно применить фильтр ко всем контроллерам, затем вы начнете регистрировать его глобально.

Однако я просто понимаю, что есть LazyInitializer.EnsureInitialized, которые заставляют инициализацию выполняться только один раз при запуске приложения.

Итак, почему SimpleMembershipInitializer (в фильтре) не находится непосредственно в Application_Start? Есть ли смысл использовать фильтр?


person CallMeLaNN    schedule 23.10.2012    source источник


Ответы (7)


Я полагаю, что в шаблоне использовался атрибут для инициализации базы данных, чтобы неаутентифицированные части сайта продолжали работать в случае сбоя инициализации.

Для большинства практических целей лучше просто сделать это в файле App_Start.

person Matt Magpayo    schedule 02.11.2012
comment
Это правильный ответ на вопрос. Почему так не отмечено? (P.S. Спасибо за информацию, очень интересно) - person Maxim Gershkovich; 16.10.2013

Если бы вы объединили InitializeSimpleMembershipAttribute с Global.asax.cs Application_Start так, чтобы SimpleMembershipProvider инициализировался без вызова каких-либо маршрутов AccountController...

... это может выглядеть примерно так: 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);
                }
            }
        }
    }
}
person Aaron Hoffman    schedule 21.02.2013
comment
в вашем ответе эта строка сбивает с толку ((IObjectContextAdapter)context).ObjectContext.CreateDatabase(); я не знаю, что заменить этой строкой. я заменил UsersContext на ApplicationDbContext. Я использую идентификатор asp.net, и я использую первый подход к данным. - person Irfan Yusanif; 29.01.2016
comment
Этот код был специально предназначен для ASP.NET MVC 4. Если вы используете более новую версию, она, скорее всего, не будет работать. Конкретную строку, о которой вы говорите, можно безопасно удалить, если базы данных и таблицы БД уже существуют. Важной строкой был вызов WebSecurity.InitializeDatabaseConnection(). - person Aaron Hoffman; 03.02.2016

Если вы планируете обеспечить глобальную работу InitializeSimpleMembershipAttribute, было бы лучше использовать способ MVC 4 в App_Start\FilterConfig.cs;

public class FilterConfig
{
  public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  {
    filters.Add(new HandleErrorAttribute());
    filters.Add(new InitializeMembershipAttribute());
  }
}

Очищает Global.asax.cs от кода, который, вероятно, должен быть инкапсулирован так же, как MVC 4 по сравнению с предыдущими версиями. Оставляет приятную чистоту:

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();
    }
}

Я также рекомендую изменить тип на AuthorizeAttribute (что он и делает), потому что методы AuthorizeAttribute выполняются до методов ActionFilterAttribute. (Это должно вызвать меньше проблем, если другие ActionFilters проверяют безопасность и разрешают производные пользовательские атрибуты AuthorizeAttributes).

[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 ...
    }
}
person Erik Philips    schedule 20.07.2013
comment
Существование InitializeMembershipAttribute на самом деле не обязательно. Я просто помещаю 2 строки кода Database.SetInitializer<UsersContext>(...) и WebSecurity.InitializeDatabaseConnection(...) в Global.asax.cs или создаю еще один App_Start\DbConfig.cs. В любом случае, пока я использую SimpleMembership, я ищу новую идентификацию ASP.NET, которая могла бы заменить сборку WebMatrix для нового членства, и API не сильно отличается. - person CallMeLaNN; 21.07.2013
comment
Вы могли бы сказать это о; маршруты, объединение, фильтры и т. д.. Инкапсуляция кода в фильтр и сохранение файла global.asax.cs в том виде, в котором он существует в MVC 4, выглядит намного чище, ИМХО. И это всего 1 строка кода. - person Erik Philips; 22.07.2013
comment
инкапсуляция кода, предназначенного для однократного запуска, не нужна и сбивает с толку. вся причина для объектов в том, что они создаются несколько раз, вся причина для методов в том, что они вызываются несколько раз. если у вас есть код, который нужно вызвать только один раз, просто поместите его в строку. см. number-none.com/blow/john_carmack_on_inlined_code.html - person Spongman; 27.04.2016
comment
Хотя я согласился с этим понятием, я лично не согласен с использованием в этом сценарии. То, что делает инициализация, не является частью HttpApplication, ее ожидаемый результат является побочным эффектом. Что касается фактического встраивания, msbuild или jit, скорее всего, в любом случае встроят его из соображений производительности. - person Erik Philips; 27.04.2016

Вдохновленный ответом Аарона, я реализовал решение, которое поддерживает чистоту Global.asax и повторно использует код, поставляемый с шаблоном.

  1. Добавьте одну строку в метод RegisterGlobalFilters в файле RegisterApp_Satrt/FilterConfig.cs.

    filters.Add(new InitializeSimpleMembershipAttribute());
  2. Добавьте конструктор по умолчанию в класс InitializeMembershipAttribute, который находится в папке Filters. Содержимое этого конструктора будет той же строкой, что и в переопределении метода OnActionExecuting. (Вот как выглядит конструктор)

    public InitializeSimpleMembershipAttribute()
        {
            // Ensure ASP.NET Simple Membership is initialized only once per app start
            LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
        }
  3. Закомментируйте (или удалите) переопределение метода OnActionExecuting.

Вот и все. Это решение дает мне два основных преимущества:

  1. Гибкость проверки вещей, связанных с членством и ролями, сразу после выполнения строки FilterConfig.RegisterGlobalFilters(GlbalFilters.Filters) в файле global.asax.

  2. Гарантирует однократное выполнение инициализации базы данных WebSecurity.


EDIT: InitializeSimpleMembershipAttribute, который я использую.

[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);
            }
        }
    }
}
person Miguel    schedule 17.08.2014
comment
Я вижу файл FilterConfig.cs в App_Start, но не вижу ни одного класса InitializeMembershipAttribute ни в одной папке Filters. Не знаю, где это. - person Nathan McKaskle; 06.06.2017
comment
@NathanMcKaskle, пожалуйста, ознакомьтесь с ответом Эрика для получения более подробной информации об атрибуте. Папка фильтров создается шаблоном MVC 4. - person Miguel; 06.06.2017
comment
Да, я MVC 6 в VS 2017, я понятия не имею, о чем он говорит. Я просто создаю этот класс в конце отдельно или? Не имею представления. - person Nathan McKaskle; 06.06.2017
comment
У него есть этот класс в конце, который неполный, просто предполагая, что я знаю, что будет после ... когда я не знаю. - person Nathan McKaskle; 06.06.2017
comment
@NathanMcKaskle только что добавил класс. Не знаю, как это будет работать в MVC6. - person Miguel; 07.06.2017
comment
Нет, если я не знаю, что в классе. Он ничего не пишет об этом классе. Это просто предполагается. Не знаю, что там должно быть. В этой штуке многое просто не сказано. Попытка решить эту проблему здесь: stackoverflow.com/questions/44395991/ - person Nathan McKaskle; 07.06.2017
comment
@NathanMcKaskle, используете ли вы поставщика аутентификации SimpleMembership в своем проекте? Если не все, что у нас есть в этом посте, не имеет отношения к тому, что вы делаете. Насколько я понимаю, MVC 6 по умолчанию использует ASP.Net.Identity. - person Miguel; 08.06.2017

Я целый день бился головой о стены, пытаясь понять, почему мой Role.GetRoleForUser не удался. Это было из-за того, что LazyInitializer не вызывался.

Итак, как сказал Мэтт, просто поместите его в App_Start, чтобы убедиться, что у вас нет проблем.

person Mircea Dogaru    schedule 24.01.2013

Я потратил много часов на эту самую проблему. но я закончил только с этим изменением:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new InitializeSimpleMembershipAttribute());

    }
}

Я случайно видел следующую ошибку

System.Web.HttpException (0x80004005): невозможно подключиться к базе данных SQL Server. ---> System.Data.SqlClient.SqlException (0x80131904): ошибка, связанная с сетью или экземпляром, при установлении соединения с SQL Server. Сервер не найден или не был доступен. Убедитесь, что имя экземпляра указано правильно и что SQL Server настроен на разрешение удаленных подключений. (поставщик: Сетевые интерфейсы SQL, ошибка: 26 — Ошибка при обнаружении указанного сервера/экземпляра)

Я заметил, что всякий раз, когда я вижу ошибку, я также вижу:

в ASP._Page_Views_Shared__Layout_cshtml.Execute() в h:\root\home\btournoux-001\www\site7\Views\Shared_Layout.cshtml:строка 5

Это следующая строка в моем _Layout.cshtml:

if (User != null && User.Identity != null && (User.IsInRole("publisher") || User.IsInRole("admin")))

Итак, чтобы протестировать мое простое решение, я поставил точку останова в своем классе InitializeSmpleMembershipAttribute на вызове SureInitialized и еще одну на первую строку в 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);

В дополнение к этим двум точкам останова я также поставил точку останова в своем _Layout.cshtml (я поместил тест для пользователя в раздел кода, чтобы добавить точку останова.

@{
    var maintenanceAccess = false;
    if (User != null && User.Identity != null && (User.IsInRole("publisher") || User.IsInRole("admin")))
   {
       maintenanceAccess = true;
   }
}

После установки точек останова я закомментировал фильтры. Добавить( new InitializSimpleMembershipAttribute() , а затем запустил приложение в Visual Studio. Я мог видеть, что я наткнулся на точку останова в _Layout.cshtml перед любой другой точкой останова. Затем Я раскомментировал эту строку и снова запустил приложение. На этот раз я увидел, что точки останова внутри класса InitializeSimpleMembershipAttribute возникают до точки останова в _Layout.cshtml. И чтобы убедиться, что все работает правильно, я вошел на свой веб-сайт, а затем увидел первая точка останова в классе InitializeSimpleMembershipAttribute (EnsureInitialized), но не вторая, чего я и ожидал.

Так вроде все работает.

Спасибо всем, кто это обнаружил!

person N8NT    schedule 23.12.2013

Фильтр InitializeSimpleMembership и его слишком сложный код предназначены для случая, когда разработчик может решить не использовать проверку подлинности с помощью форм, тогда сгенерированный шаблоном код все равно будет работать правильно. Если вы всегда будете использовать аутентификацию с помощью форм, вы можете инициализировать SimpleMembership в методе Application_Start файла Global.asax. подробные инструкции о том, как это сделать, можно найти здесь.

person Kevin Junghans    schedule 21.02.2013
comment
Я согласен с первым предложением, но как только решил использовать аутентификацию с помощью формы, я не буду рекомендовать использовать фильтр InitializeSimpleMembership в качестве новой практики для MVC4, а не устанавливать в Application_Start из-за проблемы, упомянутой в вопросе. В любом случае, хороший блог. - person CallMeLaNN; 23.02.2013