如何以及在何处判断 ViewComponent 是否已在视图中被调用 x 次?

How and Where to tell if a ViewComponent has been invoked x times in a view?

我有一个 ViewComponent,我只需要调用两次!我如何以及在哪里可以知道调用计数?

目前我可以使用会话,但我不喜欢在 mvc 应用程序中使用会话!我怎样才能做到这一点?

 namespace Partials.Components
 {
     public class MyComponent : ViewComponent
     {
         public IViewComponentResult Invoke()
         {
             Session["invoked"]=(int)Session["invoked"]+1;
             var model = new{
                        Website="Stack Overflow",
                        Url="www.http://whosebug.com"
                       };
             return View("_MyComponent ", model);
         }
     }
 }

在我看来

 @Component.Invoke("MyComponent")
 <span>Invoked ViewComponent <span>@Session["invoked"]</span>  times</span>

您可以使用临时数据。它只持续到下一个请求。

TempData["invoked"]=(int)TempData["invoked"]+1;

查看:

<span>Invoked ViewComponent <span>@TempData["invoked"]</span>  times</span>

注意:TempData 在幕后使用会话。

您可以使用 HttpContext.Items,它的优点是不使用会话。这些项目根据请求存储和共享,这也适合您的 objective.

在您的 viewComponent 中,您可以 add/retrieve 一个项目,如 this.Context.Items["MyComponentInvocationCount"]。只要计数大于 2,您就可以 return 空内容 return Content(String.Empty).

您可以将其与扩展方法结合使用,这样您就可以从 class:

外部获取计数
[ViewComponent(Name = "MyComponent")]
public class MyViewComponent : ViewComponent
{
    internal static readonly string ContextItemName = "InvocationCount";

    public IViewComponentResult Invoke()
    {
        this.InvocationCount = this.InvocationCount + 1;
        if (this.InvocationCount > 2) return Content(String.Empty);

        //return your content here
        return Content("Can be invoked");
    }

    private int InvocationCount
    {
        get
        {
            return this.Context.InvocationCount();
        }
        set
        {
            this.Context.Items[ContextItemName] = value;
        }
    }
}

public static class MyViewComponentExtensions
{
    public static int InvocationCount(this HttpContext context)
    {
        var count = context.Items[MyViewComponent.ContextItemName];
        return count == null ? 0 : (int)count;
    }
}

然后您可以在视图中使用它,如下所示:

@Component.Invoke("MyComponent")
<span>Invoked ViewComponent <span>@Context.InvocationCount()</span>  times</span>

如果您在一个视图中添加上述行 3 次,您将看到第三行没有添加任何内容。


编辑 - 使用 ViewComponentInvoker

我一直在探索如何通过添加自定义 ViewComponentInvoker.

来实现此功能

我首先添加了一个可用于装饰 ViewComponents 的新属性,以便将它们限制为每个请求的特定调用次数:

public class PerRequestInvocationLimitAttribute: Attribute
{
    public int PerRequestInvocationLimit { get; set; }
}

然后您将像往常一样创建视图组件,唯一的变化是添加此属性:

[PerRequestInvocationLimit(PerRequestInvocationLimit = 2)]
public class MyViewComponent : ViewComponent
{
    //implementation of view component
}

然后我们可以创建一个自定义 IViewComponentInvoker 来装饰 DefaultViewComponentInvoker

  • 此自定义视图组件调用程序将跟踪 在当前请求中调用视图组件的次数。
  • 调用具有新属性的视图组件时,它只会 如果调用次数低于限制,则真正调用它。

实现此视图组件调用程序如下所示:

public class LimitedPerRequestViewComponentInvoker : IViewComponentInvoker
{
    private readonly IViewComponentInvoker _defaultViewComponentInvoker;
    public LimitedPerRequestViewComponentInvoker(IViewComponentInvoker defaultViewComponentInvoker)
    {
        this._defaultViewComponentInvoker = defaultViewComponentInvoker;
    }

    public void Invoke(ViewComponentContext context)
    {            
        if (!CanInvokeViewComponent(context)) return;
        this._defaultViewComponentInvoker.Invoke(context);
    }

    public Task InvokeAsync(ViewComponentContext context)
    {
        if (!CanInvokeViewComponent(context)) return Task.WhenAll();
        return this._defaultViewComponentInvoker.InvokeAsync(context);
    }

    private bool CanInvokeViewComponent(ViewComponentContext context)
    {
        // 1. Increase invocation count
        var increasedCount = context.ViewContext.HttpContext.IncreaseInvocationCount(
                                                                context.ViewComponentDescriptor.ShortName);

        // 2. check if there is any limit for this viewComponent, if over the limit then return false
        var limitAttribute = context.ViewComponentDescriptor.Type
                                        .GetCustomAttributes(true)
                                        .OfType<PerRequestInvocationLimitAttribute>()
                                        .FirstOrDefault();
        if (limitAttribute != null && limitAttribute.PerRequestInvocationLimit < increasedCount)
        {
            return false;
        }

        // 3. There is no limit set or the limit has not been reached yet
        return true;
    }
}

它使用一些扩展方法 set/get 来自 HttpContext.Items 的调用计数(您也可以在视图中使用它来获取视图组件被调用的次数)

public static class ViewComponentExtensions
{
    public static int InvocationCount(this HttpContext context, string viewComponentName)
    {
        var count = context.Items[GetHttpContextItemsName(viewComponentName)];
        return count == null ? 0 : (int)count;
    }

    internal static int IncreaseInvocationCount(this HttpContext context, string viewComponentName)
    {
        var count = context.InvocationCount(viewComponentName);
        context.Items[GetHttpContextItemsName(viewComponentName)] = ++count;
        return count;
    }

    private static string GetHttpContextItemsName(string viewComponentName)
    {
        return string.Format("InvocationCount-{0}", viewComponentName);
    }
}

最后一部分是创建一个新的 IViewComponentInvokerFactory 替换 default one,因此它创建了一个新的自定义视图组件调用程序的实例,而不是默认的调用程序。您还需要在 Startup.cs:

上注册
public class MyViewComponentInvokerFactory : IViewComponentInvokerFactory
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ITypeActivatorCache _typeActivatorCache;
    private readonly IViewComponentActivator _viewComponentActivator;

    public MyViewComponentInvokerFactory(IServiceProvider serviceProvider, ITypeActivatorCache typeActivatorCache, IViewComponentActivator viewComponentActivator)
    {
        _serviceProvider = serviceProvider;
        _typeActivatorCache = typeActivatorCache;
        _viewComponentActivator = viewComponentActivator;
    }

    public IViewComponentInvoker CreateInstance(ViewComponentDescriptor viewComponentDescriptor, object[] args)
    {
        return new LimitedPerRequestViewComponentInvoker(
            new DefaultViewComponentInvoker(_serviceProvider, _typeActivatorCache, _viewComponentActivator));
    }
}

//Configure the ViewComponentInvokerFactory in Startup.ConfigureServices
services.AddTransient<IViewComponentInvokerFactory, MyViewComponentInvokerFactory>();

所有这些都准备就绪后,您可以使用视图组件 3 次,您将看到它如何只渲染两次:

@Component.Invoke("MyComponent")
<span>Invoked ViewComponent <span>@Context.InvocationCount("MyComponent")</span>  times</span>

出于以下几个原因,我更喜欢此解决方案:

  • 它基于新的mvc框架提供的钩子。
  • 除了添加设置调用限制的属性外,不需要更改您的视图组件。
  • 异步调用视图组件时有效