Почему InitializeSimpleMembershipAttribute в приложении MVC 4
Я думаю, что мое понимание на SimpleMembershipProvider
составляет почти 60%, а остальные узнают, как он работает внутри.
Вы можете быстро найти проблему при использовании фильтра [InitializeSimpleMembership]
только в AccountController (шаблон по умолчанию). Я думаю, что везде, где вы используете API-интерфейс Memberhsip или WebMatrix.WebSecurity
, вам нужно сначала убедиться, что этот фильтр нужно вызвать.
Позже, если вы используете User.IsInRole
в моем _Layout.cshtml
. Вам необходимо применить фильтр ко всем контроллерам, после чего вы начнете его регистрировать в глобальном масштабе.
Однако я просто понимаю, что есть LazyInitializer.EnsureInitialized
, которые делают инициализацию выполняемой только один раз за начало приложения.
Итак, почему SimpleMembershipInitializer
(в фильтре) не находится непосредственно в Application_Start?
Есть ли причина использовать фильтр?
Ответы
Ответ 1
Я считаю, что шаблон использовал атрибут для инициализации базы данных, чтобы не аутентифицированные части сайта все равно работали, если инициализация завершилась неудачно.
Для большинства практических целей лучше всего это сделать в App_Start.
Ответ 2
Если вы должны были объединить 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);
}
}
}
}
}
Ответ 3
Если вы планируете, чтобы 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 ...
}
}
Ответ 4
Вдохновленный ответом Аарона, я реализовал решение, которое сохраняет Global.asax в чистоте и повторяет код, который поставляется с шаблоном.
-
Добавьте одну строку в метод RegisterGlobalFilters в RegisterApp_Satrt/FilterConfig.cs
filters.Add(new InitializeSimpleMembershipAttribute());
-
Добавьте конструктор по умолчанию в класс 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);
}
-
Комментарий (или удаление) переопределения метода OnActionExecuting.
И что это. Это решение дает мне два основных преимущества:
-
Гибкость проверки элементов, связанных с членством и ролями сразу после строки FilterConfig.RegisterGlobalFilters(GlbalFilters.Filters)
, выполняется на global.asax.
-
Обеспечивает, что инициализация базы данных WebSecurity выполняется только один раз.
РЕДАКТИРОВАТЬ: 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);
}
}
}
}
Ответ 5
Я постучал головой о стены на день, пытаясь понять, почему моя роль. GetRoleForUser не удалась. Это произошло из-за того, что LazyInitializer не получил вызов.
Итак, как сказал Мэтт, просто поставьте его в App_Start, чтобы убедиться, что у вас нет проблем.
Ответ 6
Я потратил много часов на эту самую проблему. но я закончил с этим изменением:
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 на вызов EnsureInitialized, а другой - в первой строке в 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;
}
}
После того, как я установил точки останова, я должен был закомментировать фильтры .Add(new InitializSimpleMembershipAttribute(), а затем запустил приложение в Visual Studio. Я видел, что я ударил точку останова в _Layout.cshtml перед любым другим точка останова. Затем я раскоментировал эту строку и снова запустил приложение. На этот раз я увидел точки останова внутри класса InitializeSimpleMembershipAttribute до точки останова в _Layout.cshtml. И, чтобы быть уверенным, что он работает правильно, я вошел в систему на своем веб-сайте и затем увидел первую точку останова в классе InitializeSimpleMembershipAttribute (EnsureInitialized), но не второй - это то, что я ожидал.
Итак, все работает.
Спасибо всем, кто это открыл!
Ответ 7
Причиной для фильтра InitializeSimpleMembership и его чрезмерно сложного кода является тот случай, когда разработчик может решить не использовать проверку подлинности на основе форм, тогда сгенерированный шаблон будет по-прежнему работать правильно. Если вы всегда будете использовать проверку подлинности форм, вы можете инициализировать SimpleMembership в методе Application_Start для Global.asax. подробные инструкции о том, как это сделать здесь.