MvcSiteMapProvider中各个项目的可见性?

时间:2015-04-20 15:22:01

标签: c# asp.net-mvc session caching mvcsitemapprovider

如果当前会话IP位于以色列,我想隐藏菜单中的某个页面。这是我尝试过的,但事实上菜单项并没有出现在任何地方。
我测试了GeoIP提供商,它似乎正在工作,我做错了什么?

这里是我如何创建菜单以及如何跳过菜单中我不想要的项目:

public class PagesDynamicNodeProvider
  : DynamicNodeProviderBase
{
  private static readonly Guid KeyGuid = Guid.NewGuid();
  private const string IsraelOnlyItemsPageKey = "publications-in-hebrew";

  public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode siteMapNode)
  {
    using (var context = new Context())
    { 
      var pages = context.Pages
                    .Include(p => p.Language)
                    .Where(p => p.IsPublished)
                    .OrderBy(p => p.SortOrder)
                    .ThenByDescending(p => p.PublishDate)
                    .ToArray();

      foreach (var page in pages)
      {


        //*********************************************************
        //Is it the right way to 'hide' the page in current session
        if (page.MenuKey == IsraelOnlyItemsPageKey && !Constants.IsIsraeliIp)
          continue;

        var node = new DynamicNode(
          key: page.MenuKey,
          parentKey: page.MenuParentKey,
          title: page.MenuTitle,
          description: page.Title,
          controller: "Home",
          action: "Page");          

        node.RouteValues.Add("id", page.PageId);
        node.RouteValues.Add("pagetitle", page.MenuKey);

        yield return node;
      }
    }
  }
}

以下是我如何确定和缓存IP是否来自以色列的信息:

private const string IsIsraeliIpCacheKey = "5522EDE1-0E22-4FDE-A664-7A5A594D3992";
private static bool? _IsIsraeliIp;
/// <summary>
/// Gets a value indicating wheather the current request IP is from Israel
/// </summary>
public static bool IsIsraeliIp
{
  get
  {
    if (!_IsIsraeliIp.HasValue)
    {
      var value = HttpContext.Current.Session[IsIsraeliIpCacheKey];
      if (value != null)
        _IsIsraeliIp = (bool)value;
      else
        HttpContext.Current.Session[IsIsraeliIpCacheKey] = _IsIsraeliIp = GetIsIsraelIpFromServer() == true;
    }
    return _IsIsraeliIp.Value;
  }
}

private static readonly Func<string, string> FormatIpWithGeoIpServerAddress = (ip) => @"http://www.telize.com/geoip/" + ip;
private static bool? GetIsIsraelIpFromServer()
{
  var ip = HttpContext.Current.Request.UserHostAddress;
  var address = FormatIpWithGeoIpServerAddress(ip);
  string jsonResult = null;
  using (var client = new WebClient())
  {
    try
    {
      jsonResult = client.DownloadString(address);
    }
    catch
    {
      return null;
    }
  }

  if (jsonResult != null)
  {
    var obj = JObject.Parse(jsonResult);
    var countryCode = obj["country_code"];

    if (countryCode != null)
      return string.Equals(countryCode.Value<string>(), "IL", StringComparison.OrdinalIgnoreCase);
  }
  return null;
}
  1. DynamicNodeProvider是否已缓存?如果是的话,也许这就是造成这个问题的原因?如何在每个会话中进行缓存,以便每个会话都有其特定的菜单?
  2. 每个会话缓存IP是否正确?
  3. 有关追踪此问题的其他任何提示吗?

2 个答案:

答案 0 :(得分:1)

我不确定您使用的是哪个版本的MVCSiteMapProvider,但最新版本是非常可扩展的,因为它允许使用内部/外部DI(依赖注入)。

在您的情况下,通过将滑动缓存过期设置为会话超时,可以轻松地为每个会话配置缓存。

Link

// Setup cache
SmartInstance<CacheDetails> cacheDetails;

this.For<System.Runtime.Caching.ObjectCache>()
    .Use(s => System.Runtime.Caching.MemoryCache.Default);

this.For<ICacheProvider<ISiteMap>>().Use<RuntimeCacheProvider<ISiteMap>>();

var cacheDependency =
    this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
        .Ctor<string>("fileName").Is(absoluteFileName);

cacheDetails =
    this.For<ICacheDetails>().Use<CacheDetails>()
        .Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
        .Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
        .Ctor<ICacheDependency>().Is(cacheDependency);

如果您使用的是旧版本,则可以尝试在GetCacheDescription

中实施IDynamicNodeProvider方法
public interface IDynamicNodeProvider
{
  IEnumerable<DynamicNode> GetDynamicNodeCollection();
  CacheDescription GetCacheDescription();
}

以下是CacheDescription结构的详细信息。  Link

答案 1 :(得分:1)

您的链接没有出现在任何地方的原因是因为SiteMap被缓存并在所有用户之间共享。无论构建缓存的用户请求的状态是什么,所有用户都会看到。

但是,如果没有缓存,查找节点层次结构的性能对于每个请求来说都非常昂贵。通常,支持使用每个SiteMap会话的方法(使用外部DI),但不建议出于性能和可伸缩性的原因。

建议的方法是始终将每个用户的所有预期节点加载到SiteMap的缓存中(或者通过forcing a match伪造它)。然后使用以下方法之一来适当地显示和/或隐藏节点。<​​/ p>

  1. Security Trimming
  2. 内置或custom visibility providers
  3. 自定义HTML帮助程序模板(位于/Views/Shared/DisplayTemplates/文件夹中)
  4. 自定义HTML帮助程序
  5. 最好将SiteMap视为分层数据库。您只需设置数据结构,该数据结构适用于应用程序的每个用户。然后,针对可以根据需要过滤的共享数据(SiteMap对象)进行按请求查询。

    当然,如果以上选项都不包含您的使用案例,请answer my open question as to why anyone would want to cache per user,因为它几乎违背了制作网站地图的目的。

    在这种情况下,您可以设置可见性提供程序来进行过滤。

    public class IsrealVisibilityProvider : SiteMapNodeVisibilityProviderBase
    {
        public override bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
        {
            return Constants.IsIsraeliIp;
        }
    }
    

    然后从DynamicNodeProvider中删除条件逻辑,并将可见性提供程序添加到适用的每个节点。

    public class PagesDynamicNodeProvider
      : DynamicNodeProviderBase
    {
        private const string IsraelOnlyItemsPageKey = "publications-in-hebrew";
    
        public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode siteMapNode)
        {
            using (var context = new Context())
            { 
                var pages = context.Pages
                            .Include(p => p.Language)
                            .Where(p => p.IsPublished)
                            .OrderBy(p => p.SortOrder)
                            .ThenByDescending(p => p.PublishDate)
                            .ToArray();
    
                foreach (var page in pages)
                {
                    var node = new DynamicNode(
                      key: page.MenuKey,
                      parentKey: page.MenuParentKey,
                      title: page.MenuTitle,
                      description: page.Title,
                      controller: "Home",
                      action: "Page");          
    
                    // Add the visibility provider to each node that has the condition you want to check
                    if (page.MenuKey == IsraelOnlyItemsPageKey)
                    {
                        node.VisibilityProvider = typeof(IsraelVisibilityProvider).AssemblyQualifiedName;
                    }
                    node.RouteValues.Add("id", page.PageId);
                    node.RouteValues.Add("pagetitle", page.MenuKey);
    
                    yield return node;
                }
            }
        }
    }
    

    对于更复杂的可见性方案,您可能希望创建一个父可见性提供程序,该提供程序根据您自己的自定义逻辑调用子可见性提供程序,然后将父可见性提供程序设置为web.config中的默认提供程序。

    <add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MyNamespace.ParentVisibilityProvider, MyAssembly"/>
    

    或者,使用外部DI,您可以在SiteMapNodeVisibilityProviderStrategy的构造函数中设置默认值。

    // Visibility Providers
    this.For<ISiteMapNodeVisibilityProviderStrategy>().Use<SiteMapNodeVisibilityProviderStrategy>()
        .Ctor<string>("defaultProviderName").Is("MyNamespace.ParentVisibilityProvider, MyAssembly");