ASP.NET MVC Core的ViewComponent
MVC Core新增了ViewComponent的概念,直接强行理解为视图组件,用于在页面上显示可重用的内容,这部分内容包括逻辑和展示内容,而且定义为组件那么其必定是可以独立存在并且是高度可重用的。
其实从概念上讲,在ASP.NET的历史版本中早已有实现,从最开始的WebForm版本就提供了ascx作为用户的自定义控件,以控件的方式将独立的功能封装起来。到了后来的MVC框架,提供了partial view,但是partial view就是提供视图的重用,业务数据还是依赖于action提供。
MVC还要一个更加独立的实现方式就是用Child Action,所谓Child Action就是一个Action定义在Controller里面,然后标记一个[ChildAction]属性标记。我在早期做MVC项目的时候,感觉到Child Action的功能很不错,将一些每个页面(但不是所有页面)需要用到的小部件都做成Child Action,比如登录信息,左侧菜单栏等。一开始觉得不错,后来发现了严重性能问题结果被吊打。问题的元凶是Child Action执行的时候会把Controller的那一套生命周期的环节再执行一遍,比如OnActionExecuting,OneActionExecuted等,也就是说导致了很多无用的重复操作(因为当时OnActionExecuting也被用得很泛滥)。之后复盘分析,Child Action执行动作的时候就好比在当前ActionExcuted之后再开出一个分支去执行其他任务,导致了Controller执行过程又嵌套了一个Controller执行过程,严重违背了扁平化的思想(当时公司的主流设计思想),所以后来都没用Child Action。
简单回顾了过去版本的实现,我们来看看MVC Core的ViewComponenet。从名称定义来看,是要真的把功能数据和页面都独立出来,要不然配不上组件二字。ViewComponent独立于其所在的View页面和Action,更不会跟当前的Controller有任何的瓜葛,也就是说不是Child Action了。当然ViewComponent也可以重用父页面的数据或者从然后用自己的View。当然ViewComponent是不能独立使用的,必须在一个页面内被调用。
接下来看看ViewComponent的几种创建方式
首先准备一个项目,这里使用基于Starter kit项目项目模板创建一个用于练习的项目
(该项目模板可以在这里下载https://marketplace.visualstudio.com/items?itemName=junwenluo.ASPNETMVCCoreStarterKit)
运行后的初始界面是这样的

方式一 创建POCO View Component
POCO估计写过程序的都知道是什么东西,可以简单理解为一个比较纯粹的类,不依赖于任何外部框架或者包含附加的行为,纯粹的只用CLR的语言创建。
用POCO方式创建的ViewComponent类必须用ViewComponent结尾,这个类能定义在任何地方(只要能被引用到),这里我们新建一个ViewComponents目录,然后新建如下类
public class PocoViewComponent
{
private readonly IRepository _repository; public PocoViewComponent(IRepository repository)
{
_repository = repository;
} public string Invoke()
{
return $"{_repository.Cities.Count()} cities, "
+ $"{_repository.Cities.Sum(c => c.Population)} people";
}
}
这个类很简单,就是提供一个Invoke方法,然后返回城市的数量和总人口信息。
然后在页面中应用,我们把调用语句放在_Layout.cshtml页面中
@await Component.InvokeAsync("Poco")
将右上角的City Placeholder替换掉,那么运行之后可以看到右上角的内容输出了我们预期的内容

那么这个就是最简单的实现ViewComponent的方式,从这个简单的例子可以看到我们只需要提供一个约定的Invoke方法即可。
从这个简单的Component可以看到有以下3个优势
1 ViewComponent支持通过构造函数注入参数,也就是支持常规的依赖注入。有了依赖注入的支持,那么Component就有了自己
独立的数据来源
2 支持依赖注入意味着可以进行独立的单元测试
3 由于Component的实现的高度独立性,其可以被应用于多处,当然不会跟任何一个Controller绑定(也就不会有ChildAction带来的麻烦)
方式二 基于ViewComponentBase创建
基于POCO方式创建的ViewComponent的好处是简单,不依赖于其他类。如果基于ViewComponentBase创建,那么就有了一个上下文,毕竟也是一个基础类,总会提供一些帮助方法吧,就跟Controller一个道理。
下面是基于ViewComponent作为基类创建一个ViewComponent
public class CitySummary : ViewComponent
{
private readonly IRepository _repository; public CitySummary(IRepository repository)
{
_repository = repository;
} public string Invoke()
{
return $"{_repository.Cities.Count()} cities, "
+ $"{_repository.Cities.Sum(c => c.Population)} people";
}
}
咋一看跟用POCO的方式没有什么区别,代码拷贝过来就能用,当然输出的内容也是一致的。
当然这个例子只是证明这种实现方式,还没体现出继承了ViewComponentBase的好处。下面来了解一下这种方式的优势
1.实际项目中使用deViewComponent哪有这么简单,仅仅输出一行字符串。如果遇到需要输出很复杂的页面,那岂不是要拼凑很复杂的字符串,这样不优雅,更不用说单元测试,前后端分离这样的高大上的想法。所以ViewComponentBase就提供了一个类似Controller那样的解决方法,提供了几个内置的ResultType,然你返回到结果符合面向对象的思想,一次过满足上述的要求,主要有以下三种结果类型
a.ViewVIewComponentResult
可以理解为Controller的ViewResult,就是结果是通过一个VIew视图来展示
b.ContentViewComponentResult
类似于Controller的ContentResult,返回的是Encode之后的文本结果
c.HtmlContentViewComponentResult
这个用于返回包含Html字符串的内容,也就是说这些Html内容需要直接显示,而不是Encode之后再显示。
有了上述的理解,借助ViewComponentBase提供的一些基类方法就可以轻松实现显示一个复杂的视图,跟Controller类似。
下面我们改进一下CitySummary,改成输出一个ViewModel,并通过独立的View去定义Html内容。
public class CitySummary : ViewComponent
{
private readonly IRepository _repository; public CitySummary(IRepository repository)
{
_repository = repository;
} public IViewComponentResult Invoke()
{
return View(new CityViewModel
{
Cities = _repository.Cities.Count(),
Population = _repository.Cities.Sum(c => c.Population)
});
}
}
注意Invoke的实现代码,其中使用了View的方法。
这个View方法的实现逻辑类似Controller的View,但是寻找View页面的方式不同,其寻找页面文件的路径规则如下
/Views/{CurrentControllerName}/Components/{ComponentName}/Default.cshtml
/Views/Shared/Components/{ComponentName}/Default.cshtml
根据这规则,在View/Shared/目录下创建一个Components目录,然后再创建CitySummary目录,接着新建一个Default.cshtml页面
@model CityViewModel
<table class="table table-condensed table-bordered">
<tr>
<td>Cities:</td>
<td class="text-right">
@Model.Cities
</td>
</tr>
<tr>
<td>Population:</td>
<td class="text-right">
@Model.Population.ToString("#,###")
</td>
</tr>
</table>
尽管页面比较简单,但是比起之前拼字符串的方式更加强大了,下面是应用后右上角的变化效果

这就是使用View的方式,其他两种结果类型的使用方式跟Controller的类似。
除了调用View方法之外,通过ViewComponentBase还可以获得当前请求的上下文信息,比如路由参数。
比如读取请求id,然后加载对应Country的数据
public IViewComponentResult Invoke()
{
var target = RouteData.Values["id"] as string;
var cities =
_repository.Cities.Where(
city => target == null || Compare(city.Country, target, StringComparison.OrdinalIgnoreCase) == ).ToArray();
return View(new CityViewModel
{
Cities = cities.Count(),
Population = cities.Sum(c => c.Population)
});
}
当然也可以通过方法参数的形式传入id,比如我们可以在页面调用的时候传入id参数,那么Invoke方法可以改成如下
public IViewComponentResult Invoke(string target)
{
target = target ?? RouteData.Values["id"] as string;
var cities =
_repository.Cities.Where(
city => target == null || Compare(city.Country, target, StringComparison.OrdinalIgnoreCase) == ).ToArray();
return View(new CityViewModel
{
Cities = cities.Count(),
Population = cities.Sum(c => c.Population)
});
}
然后在界面调用的时候
@await Component.InvokeAsync("CitySummary", new { target = "USA" }),传入target参数。
上面介绍的都是同步执行的ViewComponent,接下来我们来看看支持异步操作的ViewComponent。
下面我们创建一个WeatherViewComponent,获取城市的天气,这获取天气通过异步的方式从外部获取。
在Components文件夹创建一个CityWeather文件夹,然后创建一个Default.cshtml文件,内容如下
@model string
<img src="http://@Model"/>
这个页面只是显示一个天气的图片,具体的值通过服务端返回。
然后在ViewComponents目录新建一个CityWeather类,如下
public class CityWeather : ViewComponent
{
private static readonly Regex WeatherRegex = new Regex(@"<img id=cur-weather class=mtt title="".+?"" src=""//(.+?.png)"" width=80 height=80>"); public async Task<IViewComponentResult> InvokeAsync(string country, string city)
{
city = city.Replace(" ", string.Empty);
using (var client = new HttpClient())
{
var response = await client.GetAsync($"https://www.timeanddate.com/weather/{country}/{city}");
var content = await response.Content.ReadAsStringAsync();
var match = WeatherRegex.Match(content);
var imageUrl = match.Groups[].Value;
return View("Default", imageUrl);
}
}
}
这个ViewComponent最大的特别之处是,它从外部获取城市的天气信息,这个过程使用的async的方法,异步从http下载得到内容后,解析返回当前天气的图片。
对于每一个城市我们都可以调用这个ViewComponent,在城市列表中增加一列显示当前的天气图片

最后一种创建方式:混杂在Controller中
听名字就觉得不对劲了,难道又回到ChildAction的老路。其实不是,先看看定义。
就是说将ViewComponent的Invoke方法定义在Controller中,Invoke的方法签名跟之前两种方式相同。
那么这么做的目的实际上是为了某些代码的共用,不多说先看看代码如何实现。
在HomeController加入如下方法
public IViewComponentResult Invoke() => new ViewViewComponentResult
{
ViewData = new ViewDataDictionary<IEnumerable<City>>(ViewData,
_repository.Cities)
};
这个Invoke方法就是普通的ViewComponent必须的方法,最关键是重用了这个Controller里面的_repository,当然实际代码会更有意义些。
然后给HomeController加入如下属性标记
[ViewComponent(Name = "ComboComponent")]
这里使用了ViewComponent这个属性标记在Controller上,一看就知道这是用来标记识别ViewComponent的。
接着创建视图,在Views/Shared/Components/下创建一个ComboComponent目录,并创建一个Default.cshtml文件
@model IEnumerable<City>
<table class="table table-condensed table-bordered">
<tr>
<td>Biggest City:</td>
<td>
@Model.OrderByDescending(c => c.Population).First().Name
</td>
</tr>
</table>
然后调用跟其他方式一样,按名称去Invoke
@await Component.InvokeAsync("ComboComponent")
小结
OK,以上就是ViewComponent的三种创建方式,都比较简单易懂,推荐使用方式二。
示例代码:https://github.com/shenba2014/AspDotNetCoreMvcExamples/tree/master/CustomViewComponent
ASP.NET MVC Core的ViewComponent的更多相关文章
- .NET CORE学习笔记系列(1)——ASP.NET MVC Core 介绍和项目解读
ASP.NET MVC Core 项目文件夹解读 一.项目文件夹总览 1.1.Properties——launchSettings.json 启动配置文件,你可以在项目中“Properties”文件夹 ...
- ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0
ASP.NET 5.0 将改名为 ASP.NET Core 1.0 ASP.NET MVC 6 将改名为 ASP.NET MVC Core 1.0 Entity Framework 7.0 将 ...
- [转帖]2016年时的新闻:ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0
ASP.NET Core 1.0.ASP.NET MVC Core 1.0和Entity Framework Core 1.0 http://www.cnblogs.com/webapi/p/5673 ...
- 简述C#中IO的应用 RabbitMQ安装笔记 一次线上问题引发的对于C#中相等判断的思考 ef和mysql使用(一) ASP.NET/MVC/Core的HTTP请求流程
简述C#中IO的应用 在.NET Framework 中. System.IO 命名空间主要包含基于文件(和基于内存)的输入输出(I/O)服务的相关基础类库.和其他命名空间一样. System.I ...
- ASP.NET MVC Core的TagHelper (高级特性)
这篇博文ASP.NET MVC Core的TagHelper(基础篇)介绍了TagHelper的基本概念和创建自定义TagHelper的方式,接着继续介绍一些新的看起来比较高级的特性.(示例代码紧接着 ...
- ASP.NET MVC Core的TagHelper(基础篇)
TagHelper又是一个新的名词,它替代了自之前MVC版本的HtmlHelper,专注于在cshmlt中辅助生成html标记. 通过使用自定义的TagHelper可以提供自定义的Html属性或元素, ...
- ASP.NET MVC Core Starter Kit
上一篇博文<创建.NET Core程序的Nuget Package>提到准备创建一个Nuget包,用于自动生成一个简单的ASP.NET MVC Core的示例项目.本来是打算用Nuget实 ...
- ASP.NET MVC/Core表单提交后台模型二级属性验证问题
起因 这个是网友在官网论坛的提问:https://fineui.com/bbs/forum.php?mod=viewthread&tid=22237 重新问题 本着务实求真的态度,我们先来复现 ...
- ASP.NET/MVC/Core的HTTP请求流程
ASP.NET HTTP管道(Pipeline)模型 1. 先讲一点,再深刻思考 一般我们都在写业务代码,优化页面,优化逻辑之间内徘徊.也许我们懂得HTTP,HTTPS的GET,POST,但是我们大部 ...
随机推荐
- oracle 11gr2 2.04 em 更改 hostname 后无需重建资料库的方法
1) 备份删除$ORACKE_HOME/ xxxx-sid 的EM目录:复制要创建的xxx-sid EM 名称目录: 备份删除$ORACKE_HOME/oc4j/j2ee/ xxxx-sid 的EM目 ...
- 关于.NET Core 2.0.2升级到2.1.1版本相关问题
之前,因日常任务管理比较混乱,所以自己开发了PTager任务管理系统. 当时用了.NET Core 2.0版本. 现在想修改相关功能,但.NET Core已发布到2.1.301了,也即2.1.1. 附 ...
- C#基础笔记(第二十天)
1.复习属性:保护字段的构造函数:初始化对象初始化对象:给对象的每个属性去赋值什么时候会调用构造函数:当我们new的时候面向对象中需要注意的两个关键字this 1.代表当前类的对象 2.调用自己的构造 ...
- 【VS2015】故障修复之dep6100,dep6200
问题描述:把uwp程序往手机上(或者往模拟器上)部署时,vs ide提示我错误信息dep6100和dep6200,报告说“连接不到设备”. 这可把我愁坏了,各种方法都不行,最后发现问题出在Hyper- ...
- FusionCharts的使用方法 - 公司所用的flash式的图像统计工具
我们公司一直用这个图表统计, 最近整理了一下相关文档,提供大家学习. 首先可以看看 http://www.cnblogs.com/xuhongfei/archive/2013/04/12/301688 ...
- robot framework学习笔记之十一--第三方库requests详解
一.安装 Requests 通过pip安装 pip install requests 或者,下载代码后安装: $ git clone git://github.com/kennethreitz/req ...
- bad interpreter: Text file busy
刚才运行test_mysql.py文件的时候 报了个这样的错.上网查了下,链接在这里:http://www.cnblogs.com/kerrycode/p/4038934.html 于是我就把第一行的 ...
- 爬虫4:re库
一. 常见匹配模式 模式 描述 \w 匹配字母数字及下划线 \W 匹配非字母数字下划线 \s 匹配任意空白字符,等价于 [\t\n\r\f]. \S 匹配任意非空字符 \d 匹配任意数字, ...
- Nginx+Tomcat负载均衡群集
一.Nginx负载均衡原理 目前很多大型网站都应用Nginx服务器作为后端网站程序的反向代理及负载均衡器,提升整个站点的负载并发能力 Nginx负载均衡是通过反向代理实现的 二.部署Tomcat 本案 ...
- 2016级算法期末模拟练习赛-B.AlvinZH的青春记忆I
1083 AlvinZH的青春记忆I 思路 中等题,动态规划. 简化题意,一个环上取数,数不可相邻,取取得数之和最大值. 环不好表示,可以解开变成一列数,那么答案应为下列两种情况较大者. ①:取第一个 ...