Проблемы с аутентификацией после публикации приложения MVC 4 в Azure

У меня есть базовый сайт ASP.NET MVC 4, который я размещаю на сайтах Azure. Аутентификация - это проверка подлинности с помощью форм и не была настроена на основе шаблона по умолчанию. Каждый раз, когда я публикуюсь, когда я повторно посещаю свой сайт, он просто зависает с очень долгим таймаутом (возможно, несколько минут), прежде чем, наконец, показать мне сообщение об ошибке. Я могу восстановиться, удалив файлы cookie для сайта в моем браузере и перезагрузив.

Первоначально проблема заключалась в попытке получить доступ к страницам, требующим аутентификации, но затем я добавил это в свой общий _Layout.cshtml:

@if (User.IsInRole("Admin"))
{
    <li>@Html.ActionLink("Admin", "Index", "Admin")</li>
}

что теперь означает, что никакие страницы не будут доступны после новой публикации, и поэтому я даже не могу щелкнуть ссылку выхода, что было еще одним способом, которым я мог решить проблему.

Я что-то настроил неправильно? Хотя у меня есть обходной путь, который я могу использовать сам, это не принесет пользы пользователям сайта после того, как я опубликую обновление.

РЕДАКТИРОВАТЬ: из журналов ELMAH кажется, что проверка подлинности форм пытается создать базу данных SQL Express, когда я вызываю IsInRole. Я не понимаю, зачем он это делал, поскольку моя проверка подлинности с помощью форм настроена на использование моей базы данных SQL Azure.

System.Web.HttpException (0x80004005): Unable to connect to SQL Server database. ---> System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
   at System.Data.SqlClient.TdsParser.Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, Boolean ignoreSniOpenTimeout, Int64 timerExpire, Boolean encrypt, Boolean trustServerCert, Boolean integratedSecurity, Boolean withFailover)
   at System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, Boolean ignoreSniOpenTimeout, TimeoutTimer timeout, SqlConnection owningObject, Boolean withFailover)
   at System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, Boolean redirectedUserInstance, SqlConnection owningObject, SqlConnectionString connectionOptions, TimeoutTimer timeout)
   at System.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(SqlConnection owningObject, TimeoutTimer timeout, SqlConnectionString connectionOptions, String newPassword, Boolean redirectedUserInstance)
   at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, Object providerInfo, String newPassword, SqlConnection owningObject, Boolean redirectedUserInstance)
   at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection)
   at System.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup)
   at System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
   at System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
   at System.Data.SqlClient.SqlConnection.Open()
   at System.Web.Management.SqlServices.GetSqlConnection(String server, String user, String password, Boolean trusted, String connectionString)
ClientConnectionId:00000000-0000-0000-0000-000000000000
   at System.Web.Management.SqlServices.GetSqlConnection(String server, String user, String password, Boolean trusted, String connectionString)
   at System.Web.Management.SqlServices.SetupApplicationServices(String server, String user, String password, Boolean trusted, String connectionString, String database, String dbFileName, SqlFeatures features, Boolean install)
   at System.Web.Management.SqlServices.Install(String database, String dbFileName, String connectionString)
   at System.Web.DataAccess.SqlConnectionHelper.CreateMdfFile(String fullFileName, String dataDir, String connectionString)
   at System.Web.DataAccess.SqlConnectionHelper.EnsureSqlExpressDBFile(String connectionString)
   at System.Web.DataAccess.SqlConnectionHelper.GetConnection(String connectionString, Boolean revertImpersonation)
   at System.Web.Security.SqlRoleProvider.GetRolesForUser(String username)
   at WebMatrix.WebData.SimpleRoleProvider.GetRolesForUser(String username)
   at System.Web.Security.RolePrincipal.IsInRole(String role)

person Mark Heath    schedule 18.10.2012    source источник
comment
Марк Я уже упоминал этот вопрос в своем аналогичном здесь ... stackoverflow.com/questions/24149044/, и если ваш ответ ниже работает, действительно нужно подтолкнуть щедрость к себе ...   -  person hawbsl    schedule 17.06.2014


Ответы (3)


Попробовав десятки разных предложений из разных сообщений в блогах, я нашел решение. Добавление атрибута InitialiseSimpleMembership к моему домашнему контроллеру решает проблему.

[InitializeSimpleMembership]
public class HomeController : Controller

После внесения этого изменения мне удалось без проблем опубликовать несколько успешных публикаций. Я подозреваю, что причина в том, что следующая строка кода в конструкторе InitializeSimpleMembershipAttribute должна выполняться до того, как будут выполнены какие-либо вызовы User.IsInRole:

WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

Думаю, лучше всего было бы запустить InitializeSimpleMembership в Application_Start.

person Mark Heath    schedule 20.10.2012
comment
Если это сработает, возможно, вам нужно явно подключить поставщиков в файле web.config. См. blog.longle.net/2012/09/25/ - person RickAndMSFT; 23.10.2012
comment
спасибо @RickAndMSFT, в какой-то момент я исследовал редактирование поставщиков членства и ролей в моем файле Web.Config, но получить информацию о том, каким должен быть синтаксис, было чрезвычайно сложно. - person Mark Heath; 23.10.2012
comment
Я столкнулся с аналогичной проблемой - сообщение об ошибке было Ошибка, связанная с сетью или конкретным экземпляром, при установке соединения с SQL Server. Сервер не найден или не был доступен. Убедитесь, что имя экземпляра правильное и что SQL Server настроен на разрешение удаленных подключений. (поставщик: Сетевые интерфейсы SQL, ошибка: 26 - Ошибка при обнаружении указанного сервера / экземпляра). - person Will; 20.12.2012
comment
Учетные данные для подключения к базе данных были определенно правильными, поскольку они уже подключались к базе данных. После добавления атрибута InitializeSimpleMembership к каждому контроллеру он работает. Похоже, что есть ошибка при создании подключений, если у контроллера нет атрибута InitializeSimpleMembership. Это начинает терпеть неудачу только через некоторое время простоя после входа в систему (возможно, из-за тайм-аута входа в систему?). - person Will; 20.12.2012
comment
У меня была эта проблема, и я подумал, что это связано с временными ошибками сбоя SQL Azure. Это сводило меня с ума в течение 3 дней, пока я не нашел этот ТАК-пост. Если кто-то еще получает ошибку 26, и вы знаете, что ваша строка подключения хороша, я рекомендую предложение Марка. - person aknatn; 24.12.2012

Вот tutorial, который вы можете выполнить настройку базы данных SQL при развертывании приложения в Windows Azure. Вы должны настроить правильную строку подключения в своем web.config, которая по умолчанию указывает на локальную базу данных SQL Express, когда вы создаете новое приложение ASP.NET MVC 4 с использованием Интернет-шаблона.

Строка подключения к SQL Azure будет выглядеть примерно так:

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=tcp:#server#.database.windows.net,1433;Initial Catalog=#DBName#;User ID=UserName#@#server#;Password=#password#;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
person Darin Dimitrov    schedule 20.10.2012
comment
Спасибо, но с моей строкой подключения проблем нет. Как только я удалю файлы cookie в своем браузере, сайт будет работать нормально. Здесь есть все данные, все логины и роли пользователей. Это просто первый раз после публикации, когда произошла ошибка. - person Mark Heath; 20.10.2012
comment
Что ж, тогда я действительно должен понять, в чем может быть проблема. Попробуйте следовать руководству, на которое я ссылаюсь. - person Darin Dimitrov; 20.10.2012
comment
да, я пробовал это, и это побудило меня установить это: nuget.org/packages/Microsoft .AspNet.Providers.LocalDB, но это все сломало, вызвав ошибку, обсуждаемую в этом потоке social.msdn.microsoft.com/forums/pl-pl/windowsazuredevelopment/ - person Mark Heath; 20.10.2012
comment
Visual Studio 2012 / MVC 4 по умолчанию использует LocalDB, а не SQL Express. Какая у вас строка подключения? Цитируемое руководство подходит для развертывания БД, но не БД членства. - person RickAndMSFT; 21.10.2012
comment
Моя веб-конфигурация для локальной разработки действительно использует LocalDB, но профиль публикации, который вы загружаете с портала Azure, включает преобразование, которое изменяет вашу строку подключения на строку SQL Azure при публикации. Это работает отлично - все мои данные, пользователи и роли присутствуют и верны в моей базе данных SQL Azure, и как только я удаляю файлы cookie в своем браузере, мой сайт работает отлично. Я могу подключиться к своему лазурному веб-пространству по FTP и изучить файл Web.config, и нигде нет упоминания о localdb. - person Mark Heath; 21.10.2012

Хотя на этот вопрос был дан ответ, я подумал, что могу быстро поделиться своим опытом решения той же проблемы. Мой случай немного отличается от случая Марка.

Итак, у меня был атрибут InitializeSimpleMembership на всех моих контроллерах (на самом деле он был у меня на моем базовом контроллере, от которого унаследованы все мои контроллеры), однако я все еще испытывал ту же проблему. Теперь в моем базовом контроллере я также переопределил метод Initialize, чтобы настроить некоторую контекстную информацию для нашего приложения. Частью этой настройки контекста является проверка того, находится ли текущий пользователь в определенной роли, поэтому метод IsUserInRole был вызван из этого метода Initialize, и все это до того, как будет вызван какой-либо метод действия.

Теперь, если взглянуть на класс InitializeSimpleMembership, можно заметить, что инициализация фактически выполняется в методе OnActionExecuting (т.е. InitializeSimpleMembership наследуется от ActionFilterAttribute):

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

В моем случае инициализация членства происходила слишком поздно ... то есть мне нужно, чтобы простое членство было инициализировано, прежде чем я сделаю свой вызов IsUserInRole в замещенном методе Initialize моего базового контроллера.

Решение для меня было относительно простым: я полностью удалил атрибут InitializeSimpleMembership и поместил его логику прямо в свой базовый контроллер, чтобы я мог вызвать его из моего метода Initialize, примерно так:

public class BaseController : Controller
{
    private static SimpleMembershipInitializer _initializer;
    private static object _initializerLock = new object();
    private static bool _isInitialized;

    private class SimpleMembershipInitializer
    {
        public SimpleMembershipInitializer()
        {
            try
            {
                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);
            }
        }
    }

    ...

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);
        LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
        SetupControllerContext(); // my function that calls 'IsUserInRole'
    }
}

Теперь я полагаю, что, вероятно, следует реорганизовать это и поместить его в метод Application_Start(), как предлагал Марк, но вы поняли идею :). Я просто хотел объяснить свой опыт на тот случай, если кто-то делает что-то подобное в замещаемом Initialize методе своего контроллера.

person sammy34    schedule 10.02.2013