ASP.NET Core MVC6 中 ViewComponent 视图组件使用方法

—— ASP.NET Core 踩坑日常(二)

momo314相同方式共享非商业用途署名转载

背景介绍

在ASP.NET5 RC1 MVC6中,微软为我们提供了一种叫做ViewComponent视图组件的东西来替代Web Forms中的用户控件,而在MVC6之前我们只能用Partial View来实现类似的功能。

可是。。。当我把项目升级为ASP.NET Core 1.0 RC2的时候。。。我突然发现这玩意在报错。。。

按照ASP.NET5中的使用方式,我们只需要

  1. 提供一个继承自Microsoft.AspNetCore.Mvc.ViewComponent的类,实现一个返回值为IViewComponentResultInvoke或者InvokeAsync方法;
  2. 提供一个ViewComponent视图;
  3. 在其他View中调用该ViewComponent

即可。

然后在实际使用中,又经常继承一个BaseViewComponent以提供一些通用的功能,如下:

/// <summary>
/// 视图组件基类
/// <summary>
public class BaseViewComponent : ViewComponent
{
    protected IConfiguration _config { get; set; }

    protected ILoggerFactory _loggerFactory { get; set; }

    protected ILogger logger { get; set; }

    public BaseViewComponent(IConfiguration config, ILoggerFactory loggerFactory)
    {
        _config = config;
        _loggerFactory = loggerFactory;
        logger = _loggerFactory.CreateLogger(GetType().FullName);
    }
}

/// <summary>
/// 视图组件类: 推荐作者
/// <summary>
[ViewComponent(Name = "RecommendedAuthors")]
public class RecommendedAuthorsViewComponent : BaseViewComponent
{
    public RecommendedAuthorsViewComponent(IConfiguration config, ILoggerFactory loggerFactory) :
        base(container, config, loggerFactory)
    {

    }

    /// <summary>
    /// 获取推荐作者列表
    /// <summary>
    public IViewComponentResult Invoke(int count)
    {
        var list = new List<RecommendedAuthor>(count);
        return View(list);
    }
}

然后,只需要在视图中调用这个ViewComponet就好了:

@Component.Invoke("RecommendedAuthors", 10/*参数*/)

在 ASP.NET Core 1.0 RC2 中的用法虽然有小小的变化,但大同小异,主要的变化目前看来就两点:

  1. 取消了IviewComponentHelper的同步方法Invoke,现在只保留了异步方法InvokeAsync。
  2. 参数的传入有些调整,不再以 params object[] 的形式传入,现在不管Invoke方法有多少个参数(即使只有一个),都需要将其组装为一个对象 new {ParamName1 = ParamValue1, ParamName2 = ParamValue2, ...}这种。

背景介绍完毕。。。


可是,当我升级到 ASP.NET Core 1.0 RC2 的时候:


"IviewComponentHelper"未包含"Invoke"的定义

"IviewComponentHelper"未包含"Invoke"的定义"IviewComponentHelper"未包含"Invoke"的定义

"IViewComponentHellper"未包含"Invoke"的定义,并且找不到可接受第一个"IViewComponentHellper"类型参数的扩展方法"Invoke"(是否缺少using指定或程序集引用?)

怎么会找不到呢?ASP.NET5时代人家都用的好好的。没错!确实找不到了,去github上一看,现在只剩下InvokeAsync方法了,不过好在:

如果在指定的ViewComponent上未找到异步的InvokeAsync方法时,@Component.InvokeAsync方法会自动调用Invoke方法。

这意味着我们在上面写好的的 RecommendedAuthorsViewComponent 类并不需要再实现一个InvokeAsync方法,有Invoke方法就足够了。

所以,我们将视图中的调用修改为:

@await Component.InvokeAsync("RecommendedAuthors", new { count = 10 })

嗯,好了,看起来貌似没什么问题了,也不报错了,那我们来运行一下吧 (○’ω’○)

在'BaseViewComponent'基类中找不到'Invoke' 或'InvokeAsync' 方法

在'BaseViewComponent'基类中找不到'Invoke' 或'InvokeAsync' 方法在'BaseViewComponent'基类中找不到'Invoke' 或'InvokeAsync' 方法

Connection id "0HKS8H7FF0NKM": An unhandled exception was thrown by the application. System.InvalidOperationException: Could not find an 'Invoke' or 'InvokeAsync' method for the view component 'GloriousAge.Web.Common.Base.BaseViewComponent'.

看吧,我就知道没那么顺利 ㄟ( ▔, ▔ )ㄏ

可是,这玩意为啥会从我的基类上去找Invoke方法呢?明明都由第一个参数指定了让他调用子类RecommendedAuthors的呀。。。

个人猜测:这个方法应该会查找直接继承Microsoft.AspNetCore.Mvc.ViewComponent的第一级子类,将其实例化(因为我们声明的Invoke方法并不是static)之后,寻找并调用Invoke/InvokeAsync方法,所以才会出现这种情况。

如果上面猜想成立的话,那继续猜想:如果我们为我们的BaseViewComponent基类加上 abstract 关键字,使其不能实例化,是不是就能找到正确的类呢?

/// <summary>
/// 视图组件基类
/// <summary>
public abstract class BaseViewComponent : ViewComponent
{
    // ...
}

事实证明,果然,这样就可以了!!

但是,这并不能证明上面的猜想是正确的,如果哪位知道具体原理还麻烦评论里说下,在此多谢!


至此,问题已经基本解决。下面是探寻以上解决方法时遇到的另一个小问题,记录一下,提醒自己不要再犯类似的低级错误。与君共勉。

ViewComponent 无法等待方法组 问题

探寻以上问题的解决方法时,曾试过不少方式,包括使用IviewComponentHelper的泛型方法Component.InvokeAsync<TComponent>(object arguments):

@await Component.InvokeAsync<RecommendedAuthorsViewComponent>(new { count = 10 })

因为上面说了在没给基类添加abstract修饰之前,Component.InvokeAsync(string name, object arguments)这个非泛型方法的第一个参数虽然指定了使用子类,但是并没有什么效果,所以想要尝试使用这个泛型方法来指定ViewComponent类。

看起来貌似没什么问题,然而:

ViewComponent 无法等待方法组ViewComponent 无法等待方法组

宇宙第一IDE Visual Studio给出的提示也是挺怪异的:

无法等待“方法组”。

为什么无法等待方法组?这个方法组有什么关系? 其实并不是,大家如果仔细看上面的截图就会发现,其中TComponent是以类似字符串的红色显示的,而非类型的浅蓝色。这说明什么?明显VS并没有把他当成类型来处理,而是把他识别成了一个HTML标签,也就是说:VS认为,你的C#代码部分只有:

@await Component.InvokeAsync

所以,报出这个奇怪的无法等待方法组的提示也就不奇怪了。

那么怎么处理呢?其实也是很简单:

@(await Component.InvokeAsync<RecommendedAuthorsViewComponent>(new { count = 10 }))
✎﹏ 本文来自于 momo314和他们家的猫,文章原创,转载请注明作者并保留原文链接。