实现简单的`Blazor`低代码

实现,简单,blazor,代码 · 浏览次数 : 752

小编点评

**生成内容时需要带简单的排版** **例如:** ``` 新建的按钮 多个按钮 MImage Src="https://cdn.masastack.com/stack/images/website/masa-blazor/jack.png" Alt="Jack" /> ``` **其他示例:** * 新增扩展参数输入框 * 多个文本框 * 图片 * 删除 * 标题 *

正文

本篇博客只实现基本的低代码,比如新增组件,动态修改组件参数

创建项目

首先创建一个空的Blazor Server,并且命名LowCode.Web

实现我们还需要引用一个Blazor组件库,由于作者用Masa Blazor比较多所以使用Masa Blazor

安装Masa Blazor

Masa Blazor添加到项目依赖中

<ItemGroup>
	<PackageReference Include="Masa.Blazor" Version="1.0.0-preview.3" />
</ItemGroup>

修改Program.cs文件 增加了builder.Services.AddMasaBlazor();

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddMasaBlazor();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

打开_Imports.razor 添加以下代码

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using LowCode.Web
@using Masa.Blazor
@using BlazorComponent
@using LowCode.Web.Components

修改Pages\_Host.cshtml,添加以下代码

@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace LowCode.Web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <base href="~/"/>
    <link href="css/site.css" rel="stylesheet"/>
    <!-- masa blazor css style -->
    <link href="_content/Masa.Blazor/css/masa-blazor.min.css" rel="stylesheet"/>

    <!--icon file,import need to use-->
    <link href="https://cdn.masastack.com/npm/@("@mdi")/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
</head>
<body>
<component type="typeof(App)" render-mode="ServerPrerendered"/>

<div id="blazor-error-ui">
    <environment include="Staging,Production">
        An error has occurred. This application may no longer respond until reloaded.
    </environment>
    <environment include="Development">
        An unhandled exception has occurred. See browser dev tools for details.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

<script src="_framework/blazor.server.js"></script>
<!--js(should lay the end of file)-->
<script src="_content/BlazorComponent/js/blazor-component.js"></script>
</body>
</html>

修改MainLayout.razor文件

@inherits LayoutComponentBase

<MApp>
    @Body
</MApp>

这样就完成安装了Masa Blazor

然后开始写实现

实现低代码组件设计

创建Components文件夹

创建渲染组件Component\Render.razor,添加以下代码

@using LowCode.Web.Components.Options
@RenderFragment

@code{
    /// <summary>
    /// 渲染组件
    /// </summary>
    [Parameter]
    public RenderFragment RenderFragment { get; set; }

    /// <summary>
    /// 渲染配置
    /// </summary>
    public GenerateMButtonOptions GenerateMButtonOptions { get; set; }

    /// <summary>
    /// 渲染动态代码
    /// </summary>
    public string Code { get; set; }
}

定义组件库模板 DynamicComponentGenerator.razor,由于cs文件不能razor模板,所以创建razor文件,添加以下代码,以下代码我们添加三个组件模板,

@using LowCode.Web.Components.Options

@code {
    public static (RenderFragment, string) GenerateMButton(GenerateMButtonOptions options)
    {
        // 定义模板默认数据
        RenderFragment template = @<MButton Block=options.block
                                            Height=options.height
                                            Width=options.width
                                            Class=@options.Class
                                            Style=@options.Style
                                            Dark=options.dark
                                            Attributes=options.attributes>@options.childContent</MButton>;

        // 模板定义代码 (存在问题)
        var data = $@"<MButton Block={options.block}
                             Height={options.height}
                             Width={options.width}
                             Class={options.Class}
                             Style={options.Style}
                             Dark={options.dark}
        Attributes={options.attributes}>{@options.childContent}</MButton>";

        return (builder => { builder.AddContent(0, template); }, data);
    }


    public static (RenderFragment, string) GenerateMCard(GenerateMButtonOptions options)
    {
        RenderFragment template = @<MCard Height=options.height
                                            Width=options.width
                                            Class=@options.Class
                                            Style=@options.Style
                                            Dark=options.dark
                                            Attributes=options.attributes>@options.childContent</MCard>;

        var data = $@"<MCard Height={options.height}
                             Width={options.width}
                             Class={options.Class}
                             Style={options.Style}
                             Dark={options.dark}
        Attributes={options.attributes}>{@options.childContent}</MCard>";

        return (builder => { builder.AddContent(0, template); }, data);
    }

    public static (RenderFragment, string) GenerateMAvatar(GenerateMButtonOptions options)
    {
        RenderFragment template = @<MAvatar Height=options.height
                                           Width=options.width
                                           Class=@options.Class
                                           Style=@options.Style
                                           Attributes=options.attributes>@options.childContent</MAvatar>;

        var data = $@"<MAvatar Height={options.height}
                             Width={options.width}
                             Class={options.Class}
                             Style={options.Style}
        Attributes={options.attributes}>{@options.childContent}</MAvatar>";

        return (builder => { builder.AddContent(0, template); }, data);
    }
}

添加Component\ComponentType.cs 组件枚举

namespace LowCode.Web.Components;

public enum ComponentType
{
    MButton,

    MCart,

    MAvatar
}

添加Component\ComponentLibrary.razor用于显示支持的组件

<div style="height: 100px">
    <MButton class="button" OnClick="() => OnClick?.Invoke(ComponentType.MButton)">
        <MIcon>
            mdi-card
        </MIcon>
        按钮
    </MButton>
    <MButton class="button" OnClick="() => OnClick?.Invoke(ComponentType.MCart)">
        <MIcon>mdi-id-card</MIcon>
        卡片
    </MButton>
    <MButton class="button" OnClick="() => OnClick?.Invoke(ComponentType.MAvatar)">
        <MIcon>mdi-id-card</MIcon>
        头像
    </MButton>
</div>

@code{

    public delegate void OnClickDelegate(ComponentType type);

    [Parameter]
    public OnClickDelegate? OnClick { get; set; }

}

<style>
    .button {
        margin: 5px;
    }
</style>

新增Component\Options\GenerateMButtonOptions.cs 添加以下代码 ,添加组件时的参数

using BlazorComponent;
using Microsoft.AspNetCore.Components;

namespace LowCode.Web.Components.Options;

public class GenerateMButtonOptions
{
    public string? height { get; set; }

    public string? width { get; set; }

    public bool block { get; set; }

    public bool dark { get; set; }

    public string Style { get; set; } = string.Empty;
    public string Class { get; set; } = string.Empty;

    public Dictionary<string, object>? attributes { get; set; } = new();

    public RenderFragment? childContent { get; set; }
}

然后修改Pages\Index.razor

@page "/"
@using LowCode.Web.Components.Options

<MRow NoGutters>
    <MCol>
        <MCard Class="pa-1"
               Outlined
               Style="height: 100vh"
               tile>

            <ComponentLibrary OnClick="CreateOnClick"></ComponentLibrary>

        </MCard>
        </MCol>
        <MCol Order="2"
          Cols="12"
          Sm="6"
          Md="8">
        <MCard Class="pa-2"
               Outlined
               Style="height: 100vh"
               tile>
            @foreach (var item in Renders)
            {
                <render @onclick="() => Id = item.Key">
                    @item.Value.RenderFragment
                </render>
            }
        </MCard>
    </MCol>
    <MCol Order="3">
        <MCard Class="pa-2"
               Outlined
               Style="height:100vh"
               tile>
            <MCard>
                @*TODO:通过反射实现获取组件参数根据参数类型显示指定组件动态修改参数*@
                @foreach (var item in Renders)
                {
                    var options = item.Value.GenerateMButtonOptions;
                    if (item.Key == Id)
                    {
                        <MTextField @bind-Value="options.width" Label="width"></MTextField>
                        <MTextField @bind-Value="options.height" Label="height"></MTextField>
                        <MTextField @bind-Value="options.Style" Label="Style"></MTextField>
                        <MTextField @bind-Value="options.Class" Label="Class"></MTextField>
                        <MDivider></MDivider>
                        <MButton OnClick="() => AddOptionsAttribute(options.attributes)" Block>新增扩展参数输入框</MButton>
                        @foreach (var e in options.attributes)
                        {
                            <MTextarea NoResize Rows="1" Value="@e.Key" ValueChanged="(v) => { options.attributes.Remove(e.Key);options.attributes.Add(v,e.Value);}"></MTextarea>
                            <MTextarea NoResize Rows="1" Value="@options.attributes[e.Key].ToString()" ValueChanged="(v)=>options.attributes[e.Key]= v"></MTextarea>
                        }
                        <MButton Block OnClick="()=>DeleteComponent(item.Key)">删除</MButton>
                    }
                }
            </MCard>
        </MCard>
    </MCol>
</MRow>

@code {
    public string Id { get; set; }
    private Dictionary<string, Render> Renders = new();

    private RenderFragment RenderFragment { get; set; }

    private void AddOptionsAttribute(Dictionary<string, object> attribute)
    {
        attribute.Add("new","");
    }

    private void DeleteComponent(string key)
    {
        Renders.Remove(key);
    }

    private void CreateOnClick(ComponentType type)
    {
        GenerateMButtonOptions options = null;
        string code;
        switch (type)
        {
            case ComponentType.MButton:
                options = new()
                {
                    childContent = @<span>新建的按钮</span>,
                    attributes = new Dictionary<string, object>(),
                    width = "100px",
                };

                (RenderFragment, code) = DynamicComponentGenerator.GenerateMButton(options);

                Renders.Add(Guid.NewGuid().ToString("N"), new Render() { RenderFragment = RenderFragment, GenerateMButtonOptions = options, Code = code });

                break;
            case ComponentType.MCart:

                options = new()
                {
                    childContent = @<MButton>多个按钮</MButton>,
                    attributes = new Dictionary<string, object>(),
                    width = "100px",
                };

                (RenderFragment, code) = DynamicComponentGenerator.GenerateMCard(options);

                Renders.Add(Guid.NewGuid().ToString("N"), new Render() { RenderFragment = RenderFragment, GenerateMButtonOptions = options, Code = code });
                break;
            case ComponentType.MAvatar:
                options = new()
                {
                    childContent = @<MImage Src="https://cdn.masastack.com/stack/images/website/masa-blazor/jack.png" Alt="Jack"></MImage>,
                    attributes = new Dictionary<string, object>(),
                    width = "100px",
                };

                (RenderFragment, code) = DynamicComponentGenerator.GenerateMAvatar(options);

                Renders.Add(Guid.NewGuid().ToString("N"), new Render() { RenderFragment = RenderFragment, GenerateMButtonOptions = options, Code = code });
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, null);
        }

        StateHasChanged();
    }
}

这样就实现了整个简单的低代码操作,我们可以使用看看效果,简单了解实现原理

我们定义了组件的模板,这个模板是固定的,通过Blazor提供的双向绑定实现动态修改组件参数,这种方式可能不太适合完整的低代码,但是也是不错的思路,

这个项目还处于Demo阶段,不知道是否有大佬一块研究,研究技术极限,Blazor非常好用,推荐

GitHub
项目是MIT开源,希望大佬一块学习,促进Blazor生态

来着Token的分享

与实现简单的`Blazor`低代码相似的内容:

实现简单的`Blazor`低代码

本篇博客只实现基本的低代码,比如新增组件,动态修改组件参数 创建项目 首先创建一个空的Blazor Server,并且命名LowCode.Web 实现我们还需要引用一个Blazor组件库,由于作者用Masa Blazor比较多所以使用Masa Blazor 安装Masa Blazor 将Masa B

1.8 运用C编写ShellCode代码

在笔者前几篇文章中,我们使用汇编语言并通过自定位的方法实现了一个简单的`MessageBox`弹窗功能,但由于汇编语言过于繁琐在编写效率上不仅要考验开发者的底层功底,还需要写出更多的指令集,这对于普通人来说是非常困难的,当然除了通过汇编来实现`ShellCode`的编写以外,使用C同样可以实现编写,在多数情况下读者可以直接使用C开发,只有某些环境下对ShellCode条件有极为苛刻的长度限制时才会

1.8 运用C编写ShellCode代码

在笔者前几篇文章中,我们使用汇编语言并通过自定位的方法实现了一个简单的`MessageBox`弹窗功能,但由于汇编语言过于繁琐在编写效率上不仅要考验开发者的底层功底,还需要写出更多的指令集,这对于普通人来说是非常困难的,当然除了通过汇编来实现`ShellCode`的编写以外,使用C同样可以实现编写,在多数情况下读者可以直接使用C开发,只有某些环境下对ShellCode条件有极为苛刻的长度限制时才会

驱动开发:内核远程堆分配与销毁

在开始学习内核内存读写篇之前,我们先来实现一个简单的内存分配销毁堆的功能,在内核空间内用户依然可以动态的申请与销毁一段可控的堆空间,一般而言内核中提供了`ZwAllocateVirtualMemory`这个函数用于专门分配虚拟空间,而与之相对应的则是`ZwFreeVirtualMemory`此函数则用于销毁堆内存,当我们需要分配内核空间时往往需要切换到对端进程栈上再进行操作,接下来`LyShark

Jmeter学习之五_跟踪被测试服务器的performance

# Jmeter学习之五_跟踪被测试服务器的performance ## 背景 ``` 这几天简单学习了一些基本的测试过程. 可以实现一些简单基本的功能了. 今天晚上继续进行了jmeter的一些学习. 想着可以在测试人大金仓的同时可以查看一下本地的机器性能. ``` ## 用到的工具以及资料 ```

Prometheus+alertmanager实现告警的简单验证

# Prometheus+alertmanager实现告警的简单验证 ## 背景 ``` 学习源自: http://www.mydlq.club/article/126/ 上午没搞定, 中午睡不着,继续学习处理. 发现最恶心的有点事 alertmanager的 --cluster.listen-ad

驱动开发:内核读写内存多级偏移

让我们继续在`《内核读写内存浮点数》`的基础之上做一个简单的延申,如何实现多级偏移读写,其实很简单,读写函数无需改变,只是在读写之前提前做好计算工作,以此来得到一个内存偏移值,并通过调用内存写入原函数实现写出数据的目的。以读取偏移内存为例,如下代码同样来源于本人的`LyMemory`读写驱动项目,其中核心函数为`WIN10_ReadDeviationIntMemory()`该函数的主要作用是通过用

14.4 Socket 双向数据通信

所谓双向数据传输指的是客户端与服务端之间可以无差异的实现数据交互,此类功能实现的核心原理是通过创建`CreateThread()`函数多线程分别接收和发送数据包,这样一旦套接字被建立则两者都可以异步发送消息,本章将实现简单的双向交互功能。首先我们需要封装两个函数,这里`RecvFunction`函数用于接收数据,`SendFunction`函数则用于发送数据,这两段代码在服务端与客户端之间是一致的

一个跨平台的`ChatGPT`悬浮窗工具

# 一个跨平台的`ChatGPT`悬浮窗工具 使用`avalonia`实现的`ChatGPT`的工具,设计成悬浮窗,并且支持插件。 ## 如何实现悬浮窗? 在使用`avalonia`实现悬浮窗也是非常的简单的。 实现我们需要将窗体设置成无边框 在`Window`根节点添加一下属性,想要在Linux下

如何通过gRPC传输文件

在gRPC中,可以通过将文件分割成多个小块,然后使用流式RPC将这些小块发送到服务器来传输文件。以下是一个简单的示例,展示了如何在gRPC中实现文件传输。 首先,我们需要定义一个服务来处理文件传输。在`.proto`文件中,我们可以定义一个`UploadFile`服务,它接收一个流式的`Chunk`