首页 > 编程语言 >ASP.NET Core – View Component

ASP.NET Core – View Component

时间:2023-08-10 23:46:20浏览次数:40  
标签:Core ASP name component Component ViewComponent NET class View

前言

以前写过 Asp.net core 学习笔记 ( ViewComponent 组件 ), 这篇作为翻新版.

 

参考

Docs – View components in ASP.NET Core

Don't replace your View Components with Razor Components (Razor Component 无法替代 View Component)

 

介绍

View Component 是 Partial View 的升级版本. 区别就是它多了一个 .cs 可以写逻辑.

 

Overview

View Component 是由一个 class (.cs) 和一个 View (.cshtml) 组成的.

Component 有名字, View 也有名字, 在某个 Page/View 中想使用 Component 就通过名字召唤它

然后 Component 又会去找寻它的 View 做渲染.

ASP.NET Core 有一套命名机制去声明是否一个 class 属于 View Component, 也有一套机制去寻找 Component 的 View.

我呢是很讨厌这些机制的, 因为它们设计的不直观. 经常莫名其妙的找不到, 需要死背它那不直观的设计才能用好. 但幸好我们可以通过一些 best practice 去规定使用的方式, 这样就不会老是搞错了.

 

Create View Component Class

两个点需要注意,

第一点是如何告诉 ASP.NET Core 这个 class 是一个 ViewComponent 

第二点是这个 Component 的名字是什么.

第一种: 继承 ViewComponent class

public class CallToAction : ViewComponent

class name CallToAction 自动成为 component name, 如果 class name ends with "ViewComponent" 则会被忽略

比如: CallToActionViewComponent 最终的 component name 依然是 CallToAction.

第二种: class name ends with "ViewComponent"

public class CallToActionViewComponent

component name = CallToAction

例外

由于第二种方式是通过命名, 这就导致会出现一些例外. 比如

[NonViewComponent]
public class ReviewComponent

这时需要通过 NonViewComponentAttribute 声明这不是一个 ViewComponent

第三种: 通过 ViewComponentAttribute

[ViewComponent(Name = "CallToAction")]
public class CallToAction

这也是唯一一个自定义 component name 的方式, 如果没有定义, 那么它依然是通过 class name auto become component name.

如果 Name = "CallToActionViewComponent" 那么最终就是 "CallToActionViewComponent" ViewComponent 只有在 class name auto become component name 时会被忽视.

Best Practice

上面这些方式是可以混用的哦, 但是搞那么多方除了乱以外没有任何好处.

我的建议:

1. 通过继承 ViewComponent 来让 ASP.NET Core 知道这个 class 是 View Component, 继承的好处是可以用 build-in 的功能, 比如 return View()

2. class name 命名规范最好是 CallToActionViewComponent, 最终 component name 是 CallToAction

Implement Invoke Method

ViewComponent class 必须实现 Invoke 或 InvokeAsync 方法, 不然会报错

很奇葩的地方是它并不是 interface of ViewComponent class 哦, 所以不是 override method, 它就是一个你要自己知道的方法. 也只有在 runtime 会报错. 设计的真烂.

复制代码
public class CallToActionViewComponent : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        return View();
    }
}
复制代码

 

View (.cshtml) 查询

下面 3 个是默认查询的地方.

Component name 上面提到了, view name 默认是 "Default" (不要问我为什么不是 Index)

所以上面 Component 对应的 View Localtion 是

Views/Shared/Components/CallToAction/Default.cshtml

可以通过 options 添加更多匹配的路径.

{0} 就是最终的 view name 

查看 ViewViewComponentResult.cs 源码, 可以看到它 hardcore 了 format "Components/{0}/{1}", 0 = component name, 1 = view name.

然后通过 viewEngine 去找. 而 Options 的 ViewLocationFormats 是添加给 viewEngine 用的. 但是无论如何你都避不开它 hardcode 的 "Components/{0}/{1}"

viewEngine 的所有 format 可以通过 RazorViewEngineOptions 查看

var razorViewEngineOptions = app.Services.GetRequiredService<IOptions<RazorViewEngineOptions>>().Value;

下面是 MVC, RazorPages 的所有默认 format

它的玩法是定义一些 format, 然后 viewEngine.FindView 的时候会把 parameter 丢进去成为最终的路径.

ViewLocationExpanders 是一个动态 generate format 的方式. 几年前有写过一篇, 大概长这样:

context 可以看到什么 Page 调用了这个 Component, 然后 viewName 就是 hardcode 的 component format.

viewLocations 是默认的 3 个 formats.

虽然 ViewLocationExpanders 比 ViewLocationFormats dynamic 多一些, 但是很多时候还是不够用的, 因为它也拿不到 component 的 physical file path.

比如, 我的 folder 结构是 MyViewComponent > MyViewComponent.cs + Index.cshtml

同一个 folder 里面有 component class 和 view

由于我拿不到 component 的 physical 路径, 也就没有办法设定成 "{MyViewComponent.cs 路径}Index.cshtml"

MVC 其实也是同样的局限. Router 找到 Controller 以后, Controller 也只能通过 ControllerName 找到 View 没办法依据 physical file path.

Best Practice

我的建议是直接写绝对路径, 或者固定好 component 的 folder 位置. 然后写一个 ViewLocationExpanders 去扩展它.

复制代码
public class PageTitleSectionComponent : ViewComponent
{
    public IViewComponentResult Invoke(PageTitleSectionComponentViewModel viewModel)
    {
        return View(
            $"~/Web/Shared/Component/PageTitleSection/Index.cshtml", viewModel
        );
    }
}
复制代码

 

调用

有 3 种调用方式

@await Component.InvokeAsync("CallToAction", new { myName = "test1" })
@await Component.InvokeAsync(typeof(CallToActionViewComponent)) <!-- 推荐 -->
<vc:call-to-action my-name="test2"></vc:call-to-action>

一个通过 component name (string), 一个用 type, 一个用 tag helper (记得要 @addTagHelper 哦)

也是一样, 搞一堆来乱而已. 我个人的建议是使用 Type 的方式. 这样可以避开 PascalCase convert kebab-case 的问题.

想传入 parameter 就通过匿名对象, 它这个方式也是很烂, 类型在 compile 时无法检测.

public IViewComponentResult Invoke(string myName = "abc")

另外还有一些奇葩

如果是 string 要传变量的话, 记得加上 @. 其它类似就不需要 (统一加上会比较好)

Passing HTML Template

想 passing HTML template 也是可以, 类似 Angular 的 ng-template

定义 Templated Razor delegates

复制代码
@{
  Func<string, object> content =
        @<div>
          <p>@item</p>
        </div>
  ;
}
@await Component.InvokeAsync(typeof(CallToActionViewComponent), new { content = content })
复制代码

Func<string, object> string 是 @item 的类型.

把委托当普通参数传进去 View Component.

复制代码
public class CallToActionViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(Func<string, object> content)
    {
        return View(content);
    }
}
复制代码

在通过 ViewModel 传给 View.

最后在 View 执行就可以了

@model Func<string, object>

@Model("item1")
<h1>Hello World</h1>

提醒:

1. Templated Razor delegates 只能一个 tag, 如果想传入多个 tag 可以用 List<Func<string, object>> 来装, 然后传进去, 虽然很麻烦但是勉强能用, 或者 wrap 一层 div 也是可以破, 只是对 HTML 结构有点乱而已, 有个相关的 Issue 但没人理了

2. 委托最多传 1 个参数 , 像这样 Func<string, int, object> 2个参数是不 ok 的, 会报错 error CS1593: Delegate 'Func<string, int, object>' does not take 1 arguments

因为 @item 只有一个丫. 所以要嘛传入一个对象, 或者 Tuple 也可以.

public Func<(string Value, int Number), object> BodyTemplate // view model

BodyTemplate = @<div>@item.Value - @item.Number</div> // template

@Model.BodyTemplate(("string value", 11)) // generate template

另外最少也要传一个, 可以像下面这样传 null

复制代码
@{
  Func<dynamic?, object> value =
        @<h1>Hello World</h1>
  ;
}
<div class="text-center">
  @value(null)
</div>
复制代码

3. dynamic 的对象不可以不一致

会出现这样的 error

Passing HTML Content 

参考 :

Stack Overflow – How to pass csHtml to ViewComponent in .Net Core MVC?

Github Issue – Feature Request: View Component Slots (很遗憾, 目前没有支持)

类似 Angular 的 ng-content. content 和 template 不同哦, content 是 build 好了 HTML 丢进去. 里面只是显示而已.

template 是丢进去后才 build HTML. 用函数比喻的话, 一个是传 string 进去. 一个是传 () => string 函数进去.

1. 通过 Html.Raw 传入 IHtmlContent

2. 直接渲染对象就可以了

 

不支持泛型 Generic

ViewComponent class 和参数是不可以放泛型的哦. 

参考

Github – Invoke MVC ViewComponent with generic type parameter

stackoverflow – Using a generic model in ASP.NET MVC Razor

Github – Supporting open generic models in Views

 

不支持 @section Inside View Component

需求是这样的, 有一个 View Component 封装了一个 modal 功能. 这个 modal element 需要放到 body 这样 CSS 才容易定位.

如果在 Page, 我们可以通过 @section 把 modal pass to layout 然后放到 body 的最尾部. 但如果不在 Page 而是在 View Component @section 就不可用了.

参考:

Stack Overflow – Where should I include a script for a view component?

Github Issus – Use Section From Layout Inside View Component

目前最好是避开这样的场景. 如果真的避不开可以考虑利用 JS 把 modal element append 到 body.

标签:Core,ASP,name,component,Component,ViewComponent,NET,class,View
From: https://www.cnblogs.com/Alex80/p/17621903.html

相关文章

  • .NET对象的内存布局
    在.NET中,理解对象的内存布局是非常重要的,这将帮助我们更好地理解.NET的运行机制和优化代码,本文将介绍.NET中的对象内存布局。.NET中的数据类型主要分为两类,值类型和引用类型。值类型包括了基本类型(如int、bool、double、char等)、枚举类型(enum)、结构体类型(struct),它们直接存储值。......
  • CAN转PN网关profinet通讯协议与D
    你是否曾经遇到过这样的问题:如何将各种CAN设备连接到PROFINet网络中?捷米JM-PN-CAN通讯网关或许能为你解决这个难题!捷米JM-PN-CAN网关是一款自主研发的通讯网关,具有将从站功能发挥到极致。它能够将各种CAN设备轻松接入到PROfinet网络中,让你的设备实现更加高效、稳定的通信......
  • Asp.Net Core 之 @Html.Action 迁移
    想必只要接触了netcore的小伙伴们已经发现@html.Action()方法官方已经不提供支持了,转而使用 ViewComponents替代了,同时也增加了TagHelper。但是如果想用以前的@Html.Action()方法,我们其实可以自己动手去实现它。下面就开始实现之旅吧!1、创建静态类 HtmlHelperViewExt......
  • .net6 过滤器、管道模型
    管道处理模型1、[中间件](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0)可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。......
  • .NET5从零基础到精通:全面掌握.NET5开发技能【第三章】
    章节第一章:https://www.cnblogs.com/kimiliucn/p/17613434.html第二章:https://www.cnblogs.com/kimiliucn/p/17620153.html第三章:https://www.cnblogs.com/kimiliucn/p/17620159.html十三、权限验证13.1-基于Seesion/Cookies的权限认证为了拦截一些操作:传统的授权方式:S......
  • solr的master-slave和Multiple Cores
    Solrmulticore配置April21st,2011绚丽也尘埃LeaveacommentGotocommentsSolr继续学习中,感觉Solr的multicore主要用途有两个:1、充分利用服务器资源。在一台服务器上部署不用的搜索应用。2、提高一个应用服务能力,在服务器上同时部署同一个应用的多个core,这些core共用一份索......
  • ASP.NET 使用Ajax
    本文将介绍在ASP.NET中如何方便使用Ajax,第一种当然是使用jQuery的ajax,功能强大而且操作简单方便,第二种是使用.NET封装好的ScriptManager。$.ajax向普通页面发送get请求这是最简单的一种方式了,先简单了解jQueryajax的语法,最常用的调用方式是这样:$.ajax({settings});有几个常用......
  • asp.net blazor 新手问题
    刚开始学asp,发布程序其他电脑不能访问:http://localhost:5000和http://127.0.0.1:5000可以访问。但是http://192.168.0.20:5000 不可以。192.168.0.20是笔记本电脑的ip。在笔记本和局域网电脑访问http://192.168.0.20:5000都不可以。 排查思路:网络问题,http网络访问全流程......
  • PROFINET转DeviceNet网关profinet和以太网区别
    捷米JM-DNT-PN这款神器,连接PROFINET和DeviceNet网络,让两边数据轻松传输。这个网关不仅从ETHERNET/IP和DEVICENET一侧读写数据,还可以将缓冲区数据交换,这样就可以在两个网络之间愉快地传递数据了!而且,JM-DNT-PN是自主研发的,本网关连接到PROFINET 总线中做为从站使用,连接到Device......
  • 小版本更新kubernetes
    小版本更新kubernetes背景最近一段时间躺平了没有更新我的博客文档。感谢各位小伙伴一直以来的支持。此脚本基于https://github.com/cby-chen/Kubernetes/仓库内的安装部署文档。此脚本仅会升级k8s相关组件其他组件不进行升级。使用此脚本务必是使用该文档进行部署的集群......