我们最终将我们的VS 2008 .NET MVC解决方案转换为VS 2012(耶!),但发现了一个令人不安的问题。 Web应用程序内存不足。经过数小时的调查,我们发现了这一点:
我们的控制器的自定义属性用于验证用户是否具有相关角色以访问该功能。它看起来像这样:
[ScreenAccessAuthorize(Roles = "RoleName.Access"), Prefs]
public class SampleController : SampleBaseController
我们使用覆盖方法将角色与我们系统的其他可能角色名称连接起来。它看起来像这样:
public class ScreenAccessAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
...
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
string r = "Admin," + Roles + ".Admin," + Roles + ".Access," + Roles;
Roles = r;
return base.AuthorizeCore(httpContext);
}
}
VS 2008项目中的此代码没有问题(针对3.5 Framework),但出于某种原因,VS 2012项目(针对4.5 Framework)在覆盖功能中耗尽内存。
第一次运行属性时(2012年),角色的值为" RoleName.Access"并且r的值是" Admin,RoleName.Admin,RoleName.Access,RoleName.Access",但是后来对属性覆盖方法的调用似乎连接了Roles值,直到应用程序内存不足。 / p>
进一步调查(2012年)显示,如果我们通过访问不同的页面来更改控制器,则Roles值似乎会重置,但如果我们不断选择在同一控制器中调用方法的功能,则角色的值将会增长,直到我们的内存耗尽了。这是MVC4中的一种奇怪的缓存吗?为什么属性中的角色值没有重置?
[ScreenAccessAuthorize(Roles = "RoleName.Access"),Prefs]
我们看到Roles是System.Web.Mvc的一个属性,它具有与MVC3到MVC4不同的版本号,这是我们所知道的唯一区别。
有人能想到我们为什么遇到这个问题吗?
如果您需要更多解释,请与我们联系。
答案 0 :(得分:1)
除了你应该在构造函数中这样做的事实,而不是在AuthorizeCore中,是的..有角色缓存,虽然我不确定它何时被添加。
这里的问题似乎更像是控制器本身没有被破坏和重新创建。你有没有机会做依赖注射?您使用的是自定义控制器工厂吗?我看那儿。
您可以尝试在web.config中禁用角色缓存。
<roleManager ... cacheRolesInCookie="false"...>
答案 1 :(得分:1)
答案隐藏在MVC代码的深处。该问题源于默认过滤器提供程序,即为控制器和操作构建过滤器实例的FilterAttributeFilterProvider
,默认情况下允许缓存过滤器实例。
这意味着虽然默认控制器工厂将始终创建控制器的新实例,但过滤器提供程序不必须创建过滤器的新实例,而是允许重用缓存实例
解决方法之一是在构造函数中重新映射字符串
public class ScreenAccessAuthorize : AuthorizeAttribute
{
public ScreenAccessAuthorize( string Roles )
{
int v = 0;
string r = "Admin," + Roles + ".Admin," + Roles + ".Access," + Roles;
this.Roles = r;
}
protected override void HandleUnauthorizedRequest(
AuthorizationContext filterContext )
{
}
protected override bool AuthorizeCore( HttpContextBase httpContext )
{
return base.AuthorizeCore( httpContext );
}
}
但正如您所看到的,这需要以明确的方式提供参数
[ScreenAccessAuthorize("RoleName.Access")
而不是
[ScreenAccessAuthorize(Roles = "RoleName.Access"), Prefs]
在调用构造函数>之后完成赋值(因此构造函数中的参数值为null
。
另一个解决方案是关闭过滤器实例缓存,方法是将过滤器提供程序替换为不会缓存实例的新过滤器提供程序。这可以在global.asax
protected void Application_Start()
{
// remove the one that caches instances by default
FilterProviders.Providers.Remove(
FilterProviders.Providers.Where(
p => p is FilterAttributeFilterProvider ).FirstOrDefault() );
// replace with one that doesn't
FilterProviders.Providers.Add( new FilterAttributeFilterProvider( false ) );
}