Как перестать звонить провайдеру Sitemap IsAccessibleToUser каждый пост обратно
Мы используем securityTrimming на нашем веб-сайте ASP.NET и используем карту сайта для отображения / скрытия меню. Но проблема в том, что каждый пост возвращается в этот класс и проходит через метод IsAccessibleToUser.
Поскольку мы используем активные группы каталогов, это действительно проблема производительности. (Я уже кеширую группы (к которым принадлежит пользователь), когда поступает первый вызов, когда получают группы из AD, но все еще требуется время для выполнения этого метода.
Было бы замечательно, если бы кто-то предложил мне другие способы улучшить производительность этого метода или не вызывать этот метод для каждого поста обратно. На данный момент и, насколько я понимаю, этот метод автоматически выполняется из карты сайта и меню.
Web.config:
<siteMap defaultProvider="CustomSiteMapProvider" enabled="true">
<providers>
<clear/>
<add siteMapFile="Web.sitemap" name="CustomSiteMapProvider" type="xxx.CustomSiteMapProvider"
description="Default SiteMap provider." securityTrimmingEnabled="true"/>
</providers>
</siteMap>
Файл класса..
public class CustomSiteMapProvider : System.Web.XmlSiteMapProvider
{
public override bool IsAccessibleToUser(System.Web.HttpContext context, System.Web.SiteMapNode node)
{
// return true false depend on user has access to menu or not.
// return UserIsInRole(string role, string userName);
}
}
Вот как мы получаем роли из AD и кешируем их. (Я получил основу этого кода из другой статьи)
public class SecurityHelpler2 : WindowsTokenRoleProvider
{
/// <summary>
/// Retrieve the list of roles (Windows Groups) that a user is a member of
/// </summary>
/// <remarks>
/// Note that we are checking only against each system role because calling:
/// base.GetRolesForUser(username);
/// Is very slow if the user is in a lot of AD groups
/// </remarks>
/// <param name="username">The user to check membership for</param>
/// <returns>String array containing the names of the roles the user is a member of</returns>
public override string[] GetRolesForUser(string username)
{
// contain the list of roles that the user is a member of
List<string> roles = null;
// Create unique cache key for the user
string key = username.RemoveBackSlash();
// Get cache for current session
Cache cache = HttpContext.Current.Cache;
// Obtain cached roles for the user
if (cache[key] != null)
{
roles = new List<string>(cache[key] as string[]);
}
// is the list of roles for the user in the cache?
if (roles == null)
{
// create list for roles
roles = new List<string>();
Dictionary<string, string> groupNames = new Dictionary<string, string>();
// check the groups are available in cache
if (cache[Common.XXX_SEC_GROUPS] != null)
{
groupNames = new Dictionary<string, string>(cache[Common.XXX_SEC_GROUPS] as Dictionary<string, string>);
}
else
{
// if groups are not available in the cache get again
// here we are getting the valid group from web config
// also add to the cache inside this method
groupNames = Utility.GetRetailSecurityGroups();
}
// For each role, determine if the user is a member of that role
foreach (KeyValuePair<String,String> entry in groupNames)
{
if (base.IsUserInRole(username, entry.Value))
{
roles.Add(entry.Value);
}
}
// Cache the roles for 1 hour
cache.Insert(key, roles.ToArray(), null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
}
// Return list of roles for the user
return roles.ToArray();
}
}
}
И, наконец, я вызываю следующий метод из метода IsAccessibleToUser.
/// <summary>
/// Get the usr role from the cache and check the role exists
/// </summary>
/// <param name="role"></param>
/// <param name="userName"></param>
/// <returns>return true if the user is in role</returns>
public static bool UserIsInRole(string role, string userName)
{
// contains the list of roles that the user is a member of
List<string> roles = null;
// Get cache for current session
Cache cache = HttpContext.Current.Cache;
string key = userName.RemoveBackSlash();
// Obtain cached roles for the user
if (cache[key] != null)
{
roles = new List<string>(cache[key] as string[]);
}
else
{
// if the cache is null call the method and get the roles.
roles = new List<string>(new SecurityHelpler2().GetRolesForUser(userName) as string[]);
}
if (roles.Count > 0)
{
return roles.Contains(role);
}
return false;
}
2 ответа
По дизайну SiteMapProvider
, IsAccessibleToUser
всегда будет называться Если бы он не вызывал его, он должен был бы кэшировать результаты предыдущего вызова. SiteMapProvider
не могу решить, если в вашем случае правильно кешировать результаты или нет. Это ваше решение. Любое необходимое кэширование должно быть внутри вашей реализации.
Я считаю, что функция, из которой вы выбираете данные из Active Directory, находится в SecurityHelpler2().GetRolesForUser
Любой вызов этой функции будет довольно медленным. Остальная часть кода, из которого вы извлекаете данные из кэша, должна быть довольно быстрой.
Поскольку ваш кэш действителен только в течение 1 часа, каждый час одно попадание пользователя будет очень медленным.
Если вы уже знаете пользователей вашего сайта (и это не очень большое число), чтобы ускорить его, вы можете предварительно загрузить Cache
для всех пользователей. Для активных пользователей, скользящий срок действия будет лучше. Таким образом, у пользователя будут одинаковые роли, пока они не станут активными. При следующем входе в систему новые роли будут загружаться из Active Directory.
Я бы порекомендовал реализовать кастом IAclModule
вместо переопределения провайдера. В этом модуле вы можете написать любую логику для IsAccessibleToUser
метод. Тот же результат, только более элегантный.
Смотрите пример здесь: https://github.com/maartenba/MvcSiteMapProvider/wiki/Security-Trimming