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的更多相关文章

  1. .NET CORE学习笔记系列(1)——ASP.NET MVC Core 介绍和项目解读

    ASP.NET MVC Core 项目文件夹解读 一.项目文件夹总览 1.1.Properties——launchSettings.json 启动配置文件,你可以在项目中“Properties”文件夹 ...

  2. 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    将 ...

  3. [转帖]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 ...

  4. 简述C#中IO的应用 RabbitMQ安装笔记 一次线上问题引发的对于C#中相等判断的思考 ef和mysql使用(一) ASP.NET/MVC/Core的HTTP请求流程

    简述C#中IO的应用   在.NET Framework 中. System.IO 命名空间主要包含基于文件(和基于内存)的输入输出(I/O)服务的相关基础类库.和其他命名空间一样. System.I ...

  5. ASP.NET MVC Core的TagHelper (高级特性)

    这篇博文ASP.NET MVC Core的TagHelper(基础篇)介绍了TagHelper的基本概念和创建自定义TagHelper的方式,接着继续介绍一些新的看起来比较高级的特性.(示例代码紧接着 ...

  6. ASP.NET MVC Core的TagHelper(基础篇)

    TagHelper又是一个新的名词,它替代了自之前MVC版本的HtmlHelper,专注于在cshmlt中辅助生成html标记. 通过使用自定义的TagHelper可以提供自定义的Html属性或元素, ...

  7. ASP.NET MVC Core Starter Kit

    上一篇博文<创建.NET Core程序的Nuget Package>提到准备创建一个Nuget包,用于自动生成一个简单的ASP.NET MVC Core的示例项目.本来是打算用Nuget实 ...

  8. ASP.NET MVC/Core表单提交后台模型二级属性验证问题

    起因 这个是网友在官网论坛的提问:https://fineui.com/bbs/forum.php?mod=viewthread&tid=22237 重新问题 本着务实求真的态度,我们先来复现 ...

  9. ASP.NET/MVC/Core的HTTP请求流程

    ASP.NET HTTP管道(Pipeline)模型 1. 先讲一点,再深刻思考 一般我们都在写业务代码,优化页面,优化逻辑之间内徘徊.也许我们懂得HTTP,HTTPS的GET,POST,但是我们大部 ...

随机推荐

  1. 常用脚本--查看死锁和阻塞usp_who_lock

    USE [master] GO /****** Object: StoredProcedure [dbo].[sp_who_lock] Script Date: 02/07/2014 11:51:24 ...

  2. Linux 批量管理工具

    pssh/pscp(Python) ansible(Python) saltstack(Python) chef puppet(Ruby) fabric(Python)

  3. uwp ,win10 post json

    public static async Task<HttpResponseMessage> PostHttpstringrequest(string requesturl,string j ...

  4. Modular Arithmetic ( Arithmetic and Algebra) CGAL 4.13 -User Manual

    1 Introduction Modular arithmetic is a fundamental tool in modern algebra systems. In conjunction wi ...

  5. C#多线程编程系列(一)- 简介

    目录 系列大纲 一.前言 二.目录结构 四.章节结构 五.相关链接 系列大纲 目前只整理到第二章,线程同步,笔者后面会慢慢更新,争取能把这本书中精华的知识都分享出来. C#多线程编程系列(一)- 简介 ...

  6. iOS Keychain 跨应用

    Keychain 可以用来持久保存一些信息.通常每个应用都有自己的 Keychain Access.但有时你会需要多个应用共用一些信息.这时需要创建 Keychain Access Group. Ke ...

  7. GO语言官方中文教程!

    官方中文教程网址:https://tour.go-zh.org/basics/1 推荐理由:简洁,一句废话没有,对于初学者可以让大家快速掌握GO语言! 注意问题:如果不能访问,你懂的! 教程截图:

  8. Java中的内部类(二)成员内部类

    Java中的成员内部类(实例内部类):相当于类中的一个成员变量,下面通过一个例子来观察成员内部类的特点 public class Outer { //定义一个实例变量和一个静态变量 private i ...

  9. 【Oracle 12c】最新CUUG OCP-071考试题库(53题)

    53.(12-14) choose the best answer: Examine the command to create the BOOKS table. SQL>CREATE TABL ...

  10. 高性能缓存服务器Varnish

    一.Varnish概述 Varnish是一款高性能的.开源的反向代理服务器和缓存服务器,计算机系统的除了有内存外,还有CPU的L1.L2,甚至L3级别的缓存,Varnish的设计架构就是利用操作系统的 ...