大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: ASP.NET 修改讲义

说明:在ASP.NET core WebApi的基础上,加一个View,重点在TagHelper。

项目和文件结构 


和WebApi相比,就多一个Views文件夹。

另:Startup.ConfigureServices()中:

services.AddControllersWithViews()
多一个WithViews。



Route

也可以使用特性[Route],但推荐在Startup.Configure()的app.UseEndpoints()中配置:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "category",
        pattern: "/Article/Category{id:int}",
        defaults: new { Controller = "Article", Action = "Category" });

对比Framework,就约束不一样(演示),不再使用:

  1. id = UrlParameter.Optional
  2. constraints参数传正则,
WebApi,直接使用:/?引导的示意符。

路由优先级

当路由规则出现冲突时,以下规则进行:

  1. 首先,使用默认规则(扩展:默认route的RouteOrder更高,因为不建议开发人员自行设置RouteOrder,本文略
  2. 然后,使用用更具体(more specific)的规则。所谓“更具体”,比较难以解释,但和我们的直觉非常类似,比如更多segment(见后文名词解释)的路径就比更少segment的路径具体,segment就比route parameter具体……
    //URL: /Article/Category-New/12
    endpoints.MapControllerRoute(
        name: "category-id",
        pattern: "/Article/Category-{id}",
        defaults: new { Controller = "Article", Action = "Category" });
    endpoints.MapControllerRoute(
        name: "category-action",
        pattern: "/Article/Category-{action}/{id?}",
        defaults: new { Controller = "Article" });

    优先级从高到底:

    /Article/Category/{id}
    /Article/Category
    /Article/Category-{id}     /* complex segement*/
    /Article/{category:int}
    /Article/{category}
    /{article}/{category}
  3. 最后,使用最先找到(写在更上面的)


TagHelper

演示:智能提示

<img asp-append-version="true" src="~/images/banner1.svg" />

生成如下HTML内容:(@想一想@添加的url参数v和它的值,这是干嘛的呢?复习:浏览器缓存

<img src="/images/banner1.svg?v=GaE_EmkeBf-yBbrJ26lpkGd4jkOSh1eVKJaNOw9I4uk" />

这种被高亮加粗显示(演示:智能提示),可以使用 “asp-” 开头属性的标签,会在服务器端“转换”后发送到客户端,被称之为TagHelper —— 类似于Framework中的HtmlHelper(复习:@Html.XXX()),而且在core中HtmlHelper是一样可以使用的!


注意:

  • asp-for的值是大小写敏感的
  • ASP.NET内置的Tag Helper属性都以“asp-”开头。

相较于HtmlHelper,使用Tag Helper更好看(HTML-friendly)一些。

牢记:Tag Helper负责在服务器端生成HTML代码!浏览器不会接收到包含 asp-append-version等属性的HTML内容

PS:超链接(Anchor Tag Helper)价值不大,略。


眼见为实

复习:ASP.NET响应的不是.cshtml文件,而是ASP.NET(根据.cshtml内容)生成对象的方法返回值……

演示:位于obj/<build_configuration>/<target_framework_moniker>/Views文件夹

  • 其中文件结构和Views一一对应
  • 继承自:global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<T>,T对应Model,被声明为属性

为什么@{}和@functions{}有不同的作用域

  • @{}中的内容被放置在ExecuteAsync()方法中
  • @functions{}中的内容被放置在ExecuteAsync()方法外


表单

form和antiforgery

<form asp-action="Index" asp-controller="Register" asp-route-id="12" asp-route-name="fg">
从2.0版本开始,当form元素中:
  • method="post",且
  • action需要由razor生成(为空:没有这个attribute或值为""
razor会自动生成
antiforgery的hide input和cookie。

表单元素

TagHelper可以根据属性类型和DataAnnotations自动推断:

public class UserModel
{
    public string Name { get;set; }
    [DataType(DataType.Password)]
    public string Password { get;set; }
    public bool IsMale { get;set; }

文本类

当属性类型为string类型时,默认生成文本输入框:

<input asp-for="Name" />

还可以生成密码框:

<input asp-for="Password" />

使用textarea生成多行文本输入

<textarea asp-for="SelfDescription"></textarea>
生成的HTML标签上添加id、name和model验证属性……

选择类

  • checkbox:当属性类型为bool值时,默认生成的就是checkbox

  • radio:需要在标签中声明type="radio"

    1. 必须指定一个value值,该value值对应的属性可以是任意类型
    2. 注意:如果类型不匹配,会报异常
    3. 善用可空类型
  • select:下拉列表 asp-items 以前的值类型和@Html.DropDownList一样,……唯一需要注意的是这里要写全带Model.前缀
    <select asp-for="BirthMonth" asp-items="Model.AvailableMonths"></select>
  • CheckboxList:如果想要如CheckboxList一样的绑定,还是要使用for循环,额外添加id的hidden input
    @for (int i = 0; i < Model.Messages.Count; i++)
    {
        <label>@i <input type="checkbox" asp-for="Messages[i].Seleted" /></label>
        <input asp-for="Messages[i].Id" type="hidden" />    
        <br />
    }

验证

不要忘了错误提示:
<span asp-validation-for="PhoneNumber"></span>

其他Form相关

  • label:文本标签
    <label asp-for="SelfDescription"></label>
    生成的HTML标签上会有一个 for="SelfDescription" 的属性。有趣的是,还可以同时生成文本:
    <label for="SelfDescription">自我介绍</label>
    这其实是因为我们在PageModel的SelfDescription属性上声明了特性:(没有声明的话直接使用属性名)
    [Display(Name = "自我介绍")]
  • button:可以指定点击该按钮提交的页面,适用于同一个form里有多个按钮,每个按钮需要不同页面处理的情形:
    <button asp-controller="Log" asp-action="On" type="submit" >提交</button>
    生成:
    <button type="submit" formaction="/Log/On">提交</button>

参考:


PartialView

创建过程,和之前Framework一样……

调用

在父页面使用Partial Tag Helper

<partial name="_Keywords.cshtml"/>

name后面指定viewName即可,一样可以使用“相对(名称)/绝对(路径”两种方式……

注意这里的partial是异步呈现的,core中如果使用@Html.Partial()也应该异步:

@await Html.PartialAsync("_Keyword")

因为core中整个view的呈现都是异步的。抛异常演示:

AspNetCore.Views_Home_Index.ExecuteAsync() in Index.cshtml

for和model

这两种写法是等价的:

<partial name="_Keyword" model="Model.Keywords"/>
<partial name="_Keyword" for="Keywords" />

for和model的作用一样,都是向PartialPage传递一个model数据。


ViewComponent

类似于@Html.Action(),但差别比较大……

最大的好处就是:不受Filter影响,这样ContextPerRequest等技术就便于实施了……

声明

声明一个类,让这个类继承ViewComponent,然后在类中添加一个Invoke()方法

public class LogonStatus : ViewComponent
{
    public IViewComponentResult Invoke(int? id)
    {
        //HttpContext.Session
        //Request.Query
        return  View("_Keyword", "大飞哥"+id);

所以,Invoke()前面要加override之类的,另外注意:

  • 方法只能是public的
  • 返回类型只能是IViewComponentResult
  • 方法名不要写错,^_^
  • 这里的View()和Controller中的View()不一样,直接使用View()而不是PartialView()

Invoke()方法当中,可以使用ViewComponent的大量属性方法,添加各种逻辑等。

但没有Response.XXX,因为ViewComponent能独立的响应HTTP请求(和Framework不一样!)所以Filter对ViewComponent也起作用。

@想一想@:这样好不好?

#体会#:单一职责

除了继承,还有另外两种方式可以定义一个ViewComponent类

  • 附加特性:[ViewComponent]
        [ViewComponent]
        public class _LogOnStatus 
  • 添加后缀:ViewComponent
        public class _LogOnStatusViewComponent

但一般我们并推荐,因为这样不能方便的使用ViewComponent基类的属性(如HttpContext)和方法(如:View())

调用

首先,要在_ViewImports.cshtml中添加一行代码:

@addTagHelper *, Yz17bangMVC

Yz17bangMVC是当前程序集的名称,这行代码的意思是:把Yz17bangMVC中所有TagHelper引入(import)到view。

PS:_ViewImports.cshtml中还可以进行其他设置……

然后就可以在父页面使用vc:调用:

<vc:logon-status id="2"></vc:logon-status>

logon-status是LogonStatus的“异形”,razor会自动在两者之间转换。


Cache

将需要缓存的页面内容直接包裹在cache标签中:(同Framework的[OutputCache]

<cache 
	expires-sliding="TimeSpan.FromSeconds(100)" 
	vary-by-route="id" vary-by-query="name" 
	priority="CacheItemPriority.NeverRemove" 
>
	<h2> @DateTime.Now</h2>
</cache>
常用属性:
  • expires:设置过期时间,可以是绝对时间点(on/after),也可以是滑动过期时间段(sliding
  • vary:设置缓存副本依据,可以是route data,或者url参数(query),以及其他(比如:cookie)
  • priority:优先级

其他参考:Cache Tag Helper


作业

参考MVC Framework完成

学习笔记
源栈学历
大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ASP.NET 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码