ASP.NET MVC Core的ViewComponent

转:https://www.cnblogs.com/shenba/p/6629212.html

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

运行后的初始界面是这样的

image

方式一 创建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替换掉,那么运行之后可以看到右上角的内容输出了我们预期的内容

image

那么这个就是最简单的实现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>

复制代码

尽管页面比较简单,但是比起之前拼字符串的方式更加强大了,下面是应用后右上角的变化效果

image

这就是使用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) == 0).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) == 0).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[1].Value;
            return View("Default", imageUrl);
        }
    }
}

复制代码

这个ViewComponent最大的特别之处是,它从外部获取城市的天气信息,这个过程使用的async的方法,异步从http下载得到内容后,解析返回当前天气的图片。

对于每一个城市我们都可以调用这个ViewComponent,在城市列表中增加一列显示当前的天气图片

image

 

最后一种创建方式:混杂在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

原文地址:https://www.cnblogs.com/feihusurfer/p/14198085.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


数组的定义 Dim MyArray MyArray = Array(1‚5‚123‚12‚98) 可扩展数组 Dim MyArray() for i = 0 to 10
\'参数: \'code:要检测的代码 \'leixing:html或者ubb \'nopic:代码没有图片时默认值
演示效果: 代码下载: 点击下载
环境:winxp sp2 ,mysql5.0.18,mysql odbc 3.51 driver 表采用 myisam引擎。access 2003  不同的地方: 
其实说起AJAX的初级应用是非常简单的,通俗的说就是客户端(javascript)与服务端(asp或php等)脚本语言的数据交互。
<% ’判断文件名是否合法 Function isFilename(aFilename)  Dim sErrorStr,iNameLength,i  isFilename=TRUE
在调用的时候加入判断就行了. {aspcms:navlist type=0 } {if:[navlist:i]<6} < li><a href=\"[navlist:link]\" target=\"_top\">[navlist:name]</a> </li>
导航栏调用 {aspcms:navlist type=0}     <a href=\"[navlist:link]\">[navlist:name]</a>
1.引入外部文件: {aspcms:template src=infobar.html} 2.二级下拉菜单 <ul class=\"nav\">
downpic.asp页面:  <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
Cookies是数据包,可以让网页具有记忆功能,在某台电脑上记忆一定的信息。Cookies的工作原理是,第一次由服务器端写入到客户端的系统中。以后每次访问这个网页,都是先由客户端将Cookies发送到服务器端,再由服务器端
很简单,在需要调用的地方用这种模式 {aspcms:content sort={aspcms:sortid} num=17 order=isrecommend}
网站系统使用ACCESS数据库时,查询时怎么比较日期和时间呢?为什么常常比较出来却是错误的呢?比如早的日期比迟的日期大?
str1=\"1235,12,23,34,123,21,56,74,1232\" str2=\"12\" 问题:如何判断str2是否存在str1中,要求准确找出12,不能找出str1中的1235、123、1232
实例为最新版本的kindeditor 4.1.5. 主要程序: <% Const sFileExt=\"jpg|gif|bmp|png\" Function ReplaceRemoteUrl(sHTML,sSaveFilePath,sFileExt)
用ASP实现搜索引擎的功能是一件很方便的事,可是,如何实现类似3721的智能搜索呢?比如,当在搜索条件框内输入“中国人民”时,自动从中提取“中国”、“人民”等关键字并在数据库内进行搜索。看完本文后,你就可以发
首先感谢ASPCMS官网注册用户xing0203的辛苦付出!一下为久忆YK网络转载原创作者xing0203的文章内容!为了让小白更加清楚的体验替换过程,久忆YK对原文稍作了修改!
数据库连接: <% set conn=server.createobject(\"adodb.connection\") conn.open \"driver={microsoft access driver (*.mdb)};dbq=\"&server.mappath(\"数据库名\")
第1步:修改plugins下的image/image.js 找到\'<input type=\"button\" class=\"ke-upload-button\" value=\"\' + lang.upload + \'\" />\',
asp函数: <% Const sFileExt=\"jpg|gif|bmp|png\" Function ReplaceRemoteUrl(sHTML,sSaveFilePath,sFileExt)