【问题标题】:Visibility of individual items in MvcSiteMapProvider?MvcSiteMapProvider 中单个项目的可见性?
【发布时间】:2015-06-27 10:03:34
【问题描述】:

如果当前会话 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. 关于追查问题的任何其他提示?

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

    最好将 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");
    

    【讨论】:

    • 您好,感谢您的全面回复。可见性提供者听起来是最好的解决方案。不仅如此,IP 是否为以色列 IP 已经在每个会话中缓存,这使得它变得更好。我现在还了解到我可以通过编程方式使缓存失效,因此我将增加缓存持续时间并在菜单更改时手动重置它。无论如何上面的代码不起作用,它无法找到可见性提供者。
    • 这是我得到的错误:“找不到名为 'MyNamespace.IsraelVisibilityProvider, MyAssembly' 的可见性提供程序实例。检查您的 DI 配置以确保具有此名称的可见性提供程序实例存在并且配置正确。 "
    • 您可以改用node.VisibilityProvider = typeof(IsraelVisibilityProvider).AssemblyQualifiedName 来确保字符串正确。或者,如果您导入命名空间MvcSiteMapProvider.Reflection,您可以使用扩展方法typeof(IsraelVisibilityProvider).ShortAssemblyQualifiedName()
    • 感谢您的努力,我使用了那个前任。方法还是不行。
    • 我添加了一个问题here。您可以通过订阅问题来跟踪它。
    【解决方案2】:

    我不确定您使用的是哪个版本的 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);
    

    如果你使用的是旧版本,你可以尝试在IDynamicNodeProvider中实现GetCacheDescription方法

    public interface IDynamicNodeProvider
    {
      IEnumerable<DynamicNode> GetDynamicNodeCollection();
      CacheDescription GetCacheDescription();
    }
    

    这里是CacheDescription结构的细节。 Link

    【讨论】:

    • 感谢您的回复,但这是一个旧网站,没有启用 DI。有没有更简单的方法来实现这一点?我不想过多地干扰这个网站,所以我希望尽可能减少我的更改。有没有办法缓存每个会话的站点地图?
    • 它来自 Nuget。当前版本是 4.6.3。接口IDynamicNodeProvider好像没有GetCacheDescription的方法。
    • 我找到了 this 帖子,这听起来像是我想要的,但我想避免在该项目中启用 DI。
    • 对于 4.6.3 版本,代码更改最少的最佳选择是使用内部 DI 并遵循第一个选项。
    • 1) 泛型类型 T 在引用的帖子中指的是什么? 2) 项目安装了 CommonServiceLocator 包,有帮助吗?注入代码应该放在哪里?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多