我们有一个ASP.NET MVC 5网站,当我们使用Visual Studio在本地运行时,它可以正常工作。在本地运行时,网站连接到两个SQL Express数据库(AspNetUsers和我们的内容数据库)。我们使用Entity Framework连接到内容数据库。
我们将网站部署到Azure,并将2个数据库迁移到SQL-Azure数据库。
以下是web.config中的连接字符串
<connectionStrings>
<add name="AspNetUsersDatabase" connectionString="Data Source=tcp:ZZZ.database.windows.net,
1433;Initial Catalog=ZZZ;User ID=ZZZ@ZZZ;Password=ZZZ;"
providerName="System.Data.SqlClient" />
<add name="Group5DatabaseConnectionString" connectionString="Data Source=YYY.database.windows.net,
1433;Initial Catalog=YYY;User ID=YYY@YYY;Password=YYY;"
providerName="System.Data.SqlClient" />
<add name="Group5DatabaseEntities" connectionString="metadata=res://*/Models.Group5Model.csdl|
res://*/Models.Group5Model.ssdl|res://*/Models.Group5Model.msl;provider=System.Data.SqlClient;
provider connection string="data source=XXX.database.windows.net,1433;
InitialCatalog=XXX;UserID=XXX@XXX;Password=XXX;MultipleActiveResultSets=True;
App=EntityFramework""providerName="System.Data.EntityClient" />
</connectionStrings>
它们似乎正确地链接到网站,因为web.config中的连接字符串与Azure中的连接字符串正确匹配,如in this answer所述,并且网站上的大多数视图都可正常工作。
但是,在尝试打开其中两个视图时,会出现以下错误:
'/'应用程序中的服务器错误。
建立与SQL Server的连接时发生与网络相关或特定于实例的错误。服务器未找到或无法访问。验证实例名称是否正确,以及SQL Server是否配置为允许远程连接。 (提供程序:SQL网络接口,错误:26 - 查找指定的服务器/实例时出错)
描述:执行当前Web请求期间发生未处理的异常。 请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息。
SQLExpress数据库文件自动创建错误:
连接字符串使用应用程序的App_Data目录中的数据库位置指定本地Sql Server Express实例。
提供程序尝试自动创建应用程序服务数据库,因为提供程序确定数据库不存在。要成功检查应用程序服务数据库是否存在并自动创建应用程序服务数据库,必须满足以下配置要求:
如果应用程序在Windows 7或Windows Server 2008R2上运行,则需要执行特殊配置步骤才能自动创建提供程序数据库。有关更多信息,请访问:http://go.microsoft.com/fwlink/?LinkId=160102。如果应用程序的App_Data目录尚不存在,则Web服务器帐户必须具有对应用程序目录的读写权限。这是必要的,因为Web服务器帐户将自动创建App_Data目录(如果它尚不存在)。
如果应用程序的App_Data目录已存在,则Web服务器帐户仅需要对应用程序的App_Data目录的读写访问权限。这是必要的,因为Web服务器帐户将尝试验证应用程序的App_Data目录中是否已存在Sql Server Express数据库。从Web服务器帐户撤消对App_Data目录的读取访问将阻止提供程序正确确定Sql Server Express数据库是否已存在。当提供程序尝试创建现有数据库的副本时,这将导致错误。需要写访问权限,因为在创建新数据库时会使用Web服务器帐户的凭据。
必须在计算机上安装Sql Server Express。 Web服务器帐户的进程标识必须具有本地用户配置文件。有关如何为计算机和域帐户创建本地用户配置文件的详细信息,请参阅自述文档。
似乎以下行可能在错误消息中最重要:
连接字符串使用应用程序的App_Data目录中的数据库位置指定本地Sql Server Express实例。
两个不起作用的视图都是实例化AccountController
并在其中使用以下方法:
[Authorize]
public class AccountController : Controller
{
// ...
public string FindUserArea(string id)
{
int locationId = FindUserLocationId(id);
Location location = db.Locations.FirstOrDefault(p => p.LocationID == locationId);
return (string) (location.Suburb.SuburbName + " > "
+ location.Suburb.Region.RegionName + " > "
+ location.Suburb.Region.Province.ProvinceName);
}
// ...
}
我们首先怀疑在AccountController
中有一个错误,但另一个使用上述方法的视图显示正常。然后我们认为可能仍然存在与InitializeSimpleMembership
相关的错误的可能性,因为这出现在几个问题中(我们不是100%确定SimpleMembership
的意义/效果)。我们跟随this question的领先,但成功添加InitializeSimpleMembership
基于建议in this article没有解决错误。
此外,在注册两个用户之一时会出现相同的错误,但是AspNetUser
Azure数据库仍然会更新,并且一旦刷新了新用户的站点登录就可以了。
这看起来很奇怪,当我从实体框架上下文和标准连接的混合中建立SQL连接时,我看到了其他奇怪的行为,但是如果你只有一个单独的数据库连接字符串可能不是问题。
我遇到的其他东西,但主要是由于死锁和超时问题,EF DbExecutionStrategy可以根据各种错误重新尝试调用,包括瞬态连接问题,这可能是尝试,请看这些链接:
http://www.codeproject.com/Tips/758469/Implementing-Connection-Resiliency-with-Entity-Fra
在系统地将错误精确定位到它的根之后,我们发现问题起源于基于角色的[Authorize]
属性,与this stack中的问题完全相同。
显然,连接到SQL-Azure时,幕后的User.IsInRole()
存在一些问题。虽然这通常可以通过启用简单成员资格来解决 - 在我们的例子中我们无法轻易做到这一点,因为简单成员资格要求AspNetUser
Id是一个int而不是一个字符串(这对于Identity Model来说实现起来相当棘手)因为Id固有地需要是一个字符串而使用。
最初我们没有接受SimpleMembership的问题,因为下面的行在App_Start中没有及早发生,我们只是意识到一旦我们把它放到全局中就会出现问题。
WebSecurity.InitializeDatabaseFile("MySecurity.sdf", "Users", "UserID", "Username", true);
所以我们最终编写了自己的授权方法,如下所示:
if (User.Identity.GetUserId() == null)
{
return RedirectToAction("Login", "Account");
}
else
{
var c = new AccountController();
string UserRole = c.FindUserRoleById(User.Identity.GetUserId().ToString());
if (UserRole != "DesiredUserRole")
{
return RedirectToAction("Login", "Account");
}
else
{
// Desired User stuff
}
}
它叫FindUserRoleById
:
public string FindUserRoleById(string id)
{
var user = UserManager.FindById(id);
var userroles = user.Roles;
string userrole = "";
foreach (var x in userroles)
{
if (userrole != "Admin")
{
if (x.Role.Name.ToString() == "Admin")
{
userrole = "Admin";
}
else if (x.Role.Name.ToString() == "User2")
{
userrole = "User2";
}
else if (x.Role.Name.ToString() == "User3")
{
userrole = "User3";
}
}
}
return userrole;
}
显然这不像[Authorize]
属性的简单角色检查那么光滑,但它在这种情况下做了诀窍并且工作得很好。
如果有人有更好的解决方案,我仍然会感兴趣。
在我的情况下,这是一个与authorize和isinrole()方法相关的问题。解决它必须将以下行添加到web.config:
<system.webServer>
...
<modules>
...
<remove name="RoleManager" />
..
</modules>