MvcSiteMapProvider 中单个项目的可见性?

Visibility of individual items in 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. 关于追踪问题的任何其他提示?

我不确定您使用的是哪个版本的 MVCSiteMapProvider,但最新版本的扩展性很强,因为它允许使用 internal/external 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);

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

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

这里是 CacheDescription 结构的详细信息。 Link

您的 link 没有出现在任何地方的原因是站点地图被缓存并在所有用户之间共享。无论构建缓存的用户请求的状态如何,您的所有用户都会看到。

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

推荐的方法是始终将每个用户的所有预期节点加载到 SiteMap 的缓存中(或通过 forcing a match 伪造)。然后使用以下方法之一显示 and/or 适当隐藏节点。

  1. Security Trimming
  2. 内置或custom visibility providers
  3. 自定义 HTML 助手模板(在 /Views/Shared/DisplayTemplates/ 文件夹中)
  4. 自定义 HTML 助手

最好将 SiteMap 视为分层数据库。您只需设置数据结构,该数据结构适用于应用程序的每个用户。然后针对可根据需要过滤的共享数据(SiteMap 对象)进行按请求查询。

当然,如果上述 none 选项涵盖您的用例,请 answer my open question as to why anyone would want to cache per user,因为它几乎违背了制作 站点 [=50= 的目的] 地图。

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

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