zoukankan      html  css  js  c++  java
  • Blazor入门:ASP.NET Core Razor 组件

    官方文档原文位置:
    https://docs.microsoft.com/zh-cn/aspnet/core/blazor/components?view=aspnetcore-3.1

    本文并不是独立教程的文章,而是属于对微软文档的讲解和说明。

    组件:项目 Blazor 中,使用 .razor 结尾的文件,称为组件;而 Blazor 中的组件,正式名称是 razor 组件

    Blazor 组件是 razor 过渡而来的,使用 razor 的基本语法特性,但是 Balzor 不支持 razor 中的标记帮助程序。

    关于组件

    .razor 文件分为页面(带@page)和组件(不带@page,或者说页面组件和非页面组件。两者区别在于页面有路由,可以直接通过 URI 访问,一般放在 Page 文件夹中;而组件,作为一个部件,必须嵌入其它组件中,在页面中显示,一般放到 Shared 文件夹中,供多个页面共享、复用。
    本文接下来所指的组件都是非页面组件。
    .razor 文件中,开头有 @page 标记的,就是页面组件,没有的就是非页面组件。
    当然两者并没有严格的区分。
    组件命名时,应该带上 Component 后缀。

    组件类

    每个 .razor 文件,在编译后会生成一个类,称为组件类。 生成的类的名称与文件名匹配。
    因此,每个 .razor 文件,必须以大写字母开头,按照类名命名规范定义文件名称。


    `.razor` ,以 `@code{}` 包含 C# 代码,这部分代码除了组件间可以使用,程序中也可以正常使用,因为属于类的一部分。

    创建 Test.razor 文件,文件内容如下:

    @code{
        public string Name { get; set; }
    }
    

    Pargrom 中:

                Pages.Test test = new Pages.Test();
                test.Name = "Blazor";
    

    简单来说,就是可以作为一个类来使用。@code{} 中定义的成员,就是类的成员。
    成员正常使用 public 、private 等访问修饰符修饰。

    静态资产

    默认静态资源文件位置在项目的 wwwroot 目录,前端(.razor、.cshtml)等,默认寻址时,使用绝对路径 / 即可访问资源。
    例如:

    <img alt="Company logo" src="/images/logo.png" />
    

    这个路径是要放到前端才能,由前端访问时 ASP.NET Core 框架自动处理,相当于前端访问 / ,后端访问 D:/test/Blazor/wwwroot

    路由与路由参数

    页面组件使用 @page 设置此页面的访问地址,这里没有 Controller 和 Action 的分层和路由导航(相对地址),直接是一个绝对的访问地址,并且全局唯一。

    Index.razor 中,路由:

    @page "/"
    

    Blazor 不支持像 Controller 和 Action 那样设置灵活的 URL 可选参数(URL Query),例如:

            [HttpGet("Test/{Id}")]
            public string Test([FromQuery]int Id)
            {
                return "123";
            }
    

    Blazor 如果想通过 URL Query 传递参数,可以使用 {Name}

    @page "/test"
    @page "/test/{Id}"
    
    <h2>@Id</h2>
    
    @code{
        [Parameter]
        public string Id { get; set; } = "123";
    }
    

    因为 Blazor 不支持可选参数,因此,如果只设置 @page "/test/{Id}",那么每次访问都必须带有这个参数值。
    需要使用 [Parameter] 来修饰成员,才能捕获 @page "/test/{Id}"


    另外,理由参数是 string 类型,不能自动转为数值类型。不如会报错:

    InvalidOperationException: Unable to set property 'Id' on object of type 'BlazorApp1.Pages.Test'. The error was: Unable to cast object of type 'System.String' to type 'System.Int32'.
    

    你可以接收后,显式转为数值类型。

    组件参数

    @code 代码块中,使用 [Parameter] 修饰的公共属性,那么这个属性就会标识为组件指定参数。
    注意官网文档中,这个小节的代码示例,实际是不允许这样写得的。
    目前,有两个地方需要使用 [Parameter] 特性,一个是前一小节的路由参数绑定,另一个是嵌入组件时使用。
    示例:
    Test.razor 文件内容:

    <h2>@Title</h2>
    
    @code{
        [Parameter]
        public string Title { get; set; } = "test";
    }
    

    别的组件嵌入 Test.razor 这个组件时,就可以使用 Title 传递参数进去:

    <Test Title="111" />
    
    

    请勿创建会写入其自己的组参数属性的组件

    前面我们说到, [Parameter] 特性的使用,这个特性时作为参数传递而使用的。
    对于路由参数,其修饰的属性应该是 privite,对于其它组件传递参数,属性应该设置为 public
    如果一个组件的 @code{} 成员不需要被外界作为参数使用,就应该设置为 private
    因为 .razor 一般不会作为类来使用。、;而且不设置 [Parameter] 的属性,别的组件也使用不了这个属性。

    那么,文档说 “请勿创建会写入其自己的组参数属性的组件”,指定是 [Parmeter] 休息的属性,是作为参数传递使用的,不要在组件中修改这个属性的值。

    如果实在要操作的话,可以先拷贝这个值,使用别的变量操作,示例:

    <h2>@Title</h2>
    
    @code{
        [Parameter]
        public string Title { get; set; } = "test";
    
        private string _Title;
        protected override void OnInitialized()
        {
            _Title = Title;
        }
    }
    
    

    这样,组件要操作的话,可以使用 _Title ,保留 Title
    OnInitalized() 是一个组件初始化的方法,也可以理解成构造函数,可以参考 https://docs.microsoft.com/zh-cn/aspnet/core/blazor/lifecycle?view=aspnetcore-3.1#component-initialization-methods

    子内容

    因为组件是可以嵌套的,可以要求另一个组件显示要求的内容。

    • 被多个组件使用,不同组件要呈现不一样的内容;
    • 要根据父组件的配置,显示子组件;
    • 组件 A 要求使用到的组件 B,显示其传递的内容;

    简单来说,就是将页面内容作为复杂类型传递给另一个组件,要求这个组件显示出来。
    那么,子内容指的是一个组件可以接收另一个组件的内容,使用 RenderFragment 来接收内容。
    示例如下:
    Test.razor 中,内容:

    <div>@Children</div>
    
    @code{
        [Parameter]
        public RenderFragment Children { get; set; }
    }
    
    

    另一个组件:

    @page "/"
    
    <Test Children=r />
    @code{
        private RenderFragment r =@<h1>测试子内容</h1>;
    }
    
    

    RenderFragment 的使用,请自行查阅资料。

    属性展开

    属性展开是使用字典类型表示一个 Html 标签的多个属性。

    <input id="1"
           maxlength="@Maxlength"
           placeholder="@Placeholder"
           required="@Required"
           size="@Size" />
    
    <input id="2"
           @attributes="InputAttributes" />
    
    @code {
        #region
        private string Maxlength { get; set; } = "10";
        private string Placeholder { get; set; } = "Input placeholder text";
        private string Required { get; set; } = "required";
        private string Size { get; set; } = "50";
        #endregion
    
        // 使用字典键值对表示
        public Dictionary<string, object> InputAttributes { get; set; } = new Dictionary<string, object>()
        {
                { "maxlength", "10" },
                { "placeholder", "Input placeholder text" },
                { "required", "required" },
                { "size", "50" }
         };
    }
    
    

    任意参数

    [Paramter] 特性,只有一个属性,其定义如下:

            public bool CaptureUnmatchedValues { get; set; }
    
    

    文档说明:[Parameter] 上的 CaptureUnmatchedValues 属性允许参数匹配所有不匹配任何其他参数的特性。
    其作用是通过字典接收在父组件中出现但是未在 @code{} 中定义的参数属性。
    例如:
    Test.razor

    @code{
        // 这个属性没有用,随便起个名字测试
        [Parameter]
        public string A { get; set; }
    
        [Parameter(CaptureUnmatchedValues = true)]
        public IDictionary<string, object> AdditionalAttributes { get; set; }
    }
    
    

    父组件中使用:

    <Test A="A"
          B="B"
          C="C" />
    
    

    B、C 都是 Test.razor 中没有出现过的,那么这些参数和参数值都会自动转为键值对存储到 AdditionalAttributes 中。

    测试示例:
    Test.razor 中的内容

    <ul>
        @foreach (var item in AdditionalAttributes)
        {
            <li>@item.Key - @item.Value</li>
        }
    </ul>
    
    @code{
        // 这个属性没有用,随便起个名字测试
        [Parameter]
        public string TTT { get; set; }
    
        [Parameter(CaptureUnmatchedValues = true)]
        public IDictionary<string, object> AdditionalAttributes { get; set; }
    }
    
    

    其它组件使用:

    @page "/"
    
    <Test TTT="ces"
          id="useIndividualParams"
          maxlength="10"
          placeholder="Input placeholder text"
          required="required"
          size="50" />
    
    

    捕获对组件的引用

    组件引用提供了一种引用组件实例的方法,使用 @ref 可以实现引用对参数的引用。
    创建一个 Test.razor 文件,内容不限。
    在一个组件中,引用该组件实例

    @page "/"
    <Test @ref="_test" />
    @code{
        private Test _test;
    }
    
    

    在使用 Test.razor 组件的同时,保留了引用,以便在 @code{} 中使用其成员。

    在外部调用组件方法以更新状态

    组件继承了 ComponentBase 类型,有个 InvokeAsync 方法可用于外界更新此 UI 的状态。

    示例如下:
    创建 MyUIServer 类型,

        // 能够向所有正在打开的 Index.razor 页面发送通知
        public static class MyUIServer
        {
            // 向所有人发送通知
            public static async Task ToMessage(string message)
            {
                if (events != null)
                {
                    await events.Invoke(message);
                }
            }
            public static void AddEvent(Func<string, Task> func)
            {
                events += func;
            }
            public static void RemoveEvent(Func<string, Task> func)
            {
                events -= func;
            }
            private static event Func<string, Task> events;
        }
    
    

    Index.razor

    @page "/"
    @using BlazorApp1.Data
    @implements IDisposable
    
    <input @bind="_message" />
    <button @onclick="Btn">发送消息</button>
    <ul>
        @foreach (var item in messageList)
        {
            <li>@item</li>
        }
    </ul>
    
    @code {
        private string _message;
        private List<string> messageList = new List<string>();
        // 进入页面时
        protected override void OnInitialized()
        {
            MyUIServer.AddEvent(UIEvent);
        }
    
        // 退出当前页面UI后移除该事件
        public void Dispose()
        {
            MyUIServer.RemoveEvent(UIEvent);
        }
    
        protected async Task UIEvent(string message)
        {
            // 组件自带的方法,用于外部调用更新状态
            await InvokeAsync(() =>
            {
                messageList.Add(message);
                StateHasChanged();
            });
        }
    
        // 向所有正在访问 Index.razor 页面发送消息
        private async Task Btn()
        {
            await MyUIServer.ToMessage(_message);
        }
    
    }
    
    

    打开多个窗口,访问页面 https://localhost:5001/,在其中一个窗口输入内容并且点击按钮,即可将消息内容推送到其它窗口。

    下面是一个修改官网示例的示例:
    创建一个类型 NotifierService

        public class NotifierService
        {
            public async Task Update(string key, int value)
            {
                if (Notify != null)
                {
                    await Notify.Invoke(key, value);
                }
            }
    
            public event Func<string, int, Task> Notify;
        }
    
    

    该类型的 Notify 可以绑定多个事件;通过调用 Update() 方法,可以触发各个事件。
    在 Startup 中注入服务 services.AddSingleton<NotifierService>();
    Index.razor 中,内容为:

    @page "/"
    @using BlazorApp1.Data
    @inject NotifierService Notifier
    @implements IDisposable
    
    <p>Last update: @_lastNotification.key = @_lastNotification.value</p>
    
    @code {
        private (string key, int value) _lastNotification;
    
        protected override void OnInitialized()
        {
            Notifier.Notify += OnNotify;
        }
    
        public async Task OnNotify(string key, int value)
        {
            // 组件自带的方法,用于外部调用更新状态
            await InvokeAsync(() =>
            {
                _lastNotification = (key, value);
                StateHasChanged();
            });
        }
    
        // 退出当前页面UI后移除该事件
        public void Dispose()
        {
            Notifier.Notify -= OnNotify;
        }
    }
    
    

    Test.razor 文件中:

    @page "/test"
    @using BlazorApp1.Data
    @inject NotifierService Notifier
    Key:
    <input @bind="Key" />
    Value:
    <input @bind="Value" />
    <button @onclick="Update">更新</button>
    
    @code{
        private string Key { get; set; }
        private int? Value { get; set; }
        private async Task Update()
        {
            await Notifier.Update(Key, Value.Value);
            Key = string.Empty;
            Value = null;
        }
    }
    
    

    然后启动项目,一个页面打开 https://localhost:5001/ ,另一个页面打开 https://localhost:5001/test
    test 页面输入 Key 和 Value,点击按钮,即可通知到所有正在打开 Index.razor 的页面。

    使用 @ 键控制是否保留元素和组件

    在使用表格或了表等元素时,如果出现插入或删除、更新等情况,整个表格或列表,就会被重新渲染。这样会带来比较大的性能消耗。
    一般使用绑定的元素,其更新是自动的,不需要人为控制。
    在能保证每一项的某个元素列,都是唯一的时候,我们可以使用 @key 关键字来优化组件。
    示例:

    @page "/"
    @using BlazorApp1.Data
    
    Key:
    <input @bind="_key" />
    Value:
    <input @bind="_value" />
    <button @onclick="Add">添加</button>
    <button @onclick="Remove">移除</button>
    <ul>
        @foreach (var item in dic)
        {
            <li @key="item.Key">@item.Key - @item.Value</li>
        }
    </ul>
    
    @code {
        private int? _key;
        private int _value;
        private List<MyData> dic { get; set; } = new List<MyData>();
        private void Add()
        {
            if (_key == null)
                return;
            dic.Add(new MyData
            {
                Key = _key.Value,
                Value = _value
            });
            _key = null;
        }
        private void Remove()
        {
            if (_key == null)
                return;
            dic.Remove(dic.First(x => x.Key == _key.Value));
            _key = null;
        }
    }
    
    

    指定基类

    @inherits 指令可用于指定组件的基类。 组件都默认继承了 ComponentBase 。
    示例:
    创建文件 TestBase.razor ,内容如下

    @code{
        protected int Id { get; set; }
    }
    
    

    创建 Test.razor ,文件内容如下

    @inherits TestBase
    @code{ 
        public int Get()
        {
            return Id;
        }
    }
    
    

    指定属性

    可以通过 @attribute 指令在 Razor 组件中指定组件的特性(属性)。 例如页面需要登录才能访问,则添加 [Authorize]

    @page "/"
    @attribute [Authorize]
    
    

    导入组件

    当要使用的组件与当前组件在同一个命名空间时,不需要“导入”,如果两者不在同一个命名空间,则可以使用 @using 导入此组件。

    原始 HTML

    使用 MarkupString 类型可以将字符串转为 HTML 元素对象。

    @html
    
    @code{ 
        public MarkupString html = (MarkupString)"<h1> Test </h1>";
    }
    
    
  • 相关阅读:
    杂谈
    P1441 砝码称重
    P3159 [CQOI2012]交换棋子
    P5200 [USACO19JAN]Sleepy Cow Sorting
    P5201 [USACO19JAN]Shortcut
    P5196 [USACO19JAN]Cow Poetry
    20190922UVA测试
    P4014 分配问题
    P4012 深海机器人问题
    P2050 [NOI2012]美食节
  • 原文地址:https://www.cnblogs.com/whuanle/p/12862384.html
Copyright © 2011-2022 走看看