Learn .NET MAUI Blazor (3), create .NET MAUI Blazor application and use AntDesignBlazor

After a general understanding of Blazor and MAUI, try to create a .NET MAUI Blazor application.
It should be noted that although they are both called MAUI, they are .NET MAUInot .NET MAUI Blazorthe same as .xamlrazor

This series is still MAUI Blazorbased on the idea that to create an MAUI Blazorapplication, it needs to be installed Visual Studio 2022 17.3 或更高版本, and on the installer, check .NET Multi-platform App UI development! It would be best to upgrade to the latest .NET 7.
insert image description here

Create a .NET MAUI Blazor application

Open Visual Studio 2022, select创建新项目

insert image description here
Enter MAUI in the search box, select .NET MAUI Blazor应用, click 下一步!

insert image description here
Give the project a nice name, select the location where the project exists, click 下一步!

insert image description here
Select the target framework, which is selected here .NET 7, click 创建.
insert image description here
Wait for the project to be created and its dependencies restored. The completed directory structure is as follows:
insert image description here

.NET MAUI Blazor requires attention

.NET MAUI Blazor runs on WebView2, WebView2and is a new generation solution for desktop hybrid development launched by Microsoft. It allows native applications (WinForm, WPF, WinUI, Win32), mobile applications (MAUI) to easily embed web technologies. The WebView2 control uses Microsoft Edge as the rendering engine to display web content in client applications and apps. Use WebView2 to embed web code into different parts of client applications and Apps, or build all native applications in a single WebView instance.

Looking at MAUI Blazor this way, .NET MAUI includes the BlazorWebView control, which enables rendering of Razor components into embedded Web Views. By using .NET MAUI with Blazor, you can reuse a set of web UI components across mobile, desktop, and the web.

In other words, it is a Hybrid App (mixed application)!

Debugging .NET MAUI Blazor

To debug MAUI Blazor applications on Windows, Windows 10 1809 and later versions are required, and developer mode must be turned on.
On windows 11, located at 设置-> 隐私和安全性-> 开发者选项-> 开发人员模式
insert image description here
insert image description here
Click Windows Machineto run the program!
insert image description here
If there is no accident, the operation is successful!
insert image description here
At this time, MAUI Blazor uses the bootstrap style and open-iconic icon. can also be seen
inwwwroot/index.html

<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />

There are already many Blazor-based component libraries, so temporarily replace the default bootstrap with a third-party component library, which is used here AntDesignBlazor.

Use AntDesignBlazor component library

Installation dependencies:

PM> NuGet\Install-Package AntDesign.ProLayout -Version 0.13.1

Inject AntDesign

Inject MauiProgram.csthe AntDesign service and set the basic configuration, the complete MauiProgram.cscode

using Microsoft.Extensions.Logging;
using MauiBlazorApp.Data;

namespace MauiBlazorApp;

public static class MauiProgram
{
    
    
	public static MauiApp CreateMauiApp()
	{
    
    
		var builder = MauiApp.CreateBuilder();
		builder
			.UseMauiApp<App>()
			.ConfigureFonts(fonts =>
			{
    
    
				fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
			});

		builder.Services.AddMauiBlazorWebView();

#if DEBUG
		builder.Services.AddBlazorWebViewDeveloperTools();
		builder.Logging.AddDebug();
#endif

		builder.Services.AddSingleton<WeatherForecastService>();
        //注入AntDesign
        builder.Services.AddAntDesign();
		//基本配置
		builder.Services.Configure<ProSettings>(settings =>
		{
    
    
            settings.NavTheme = "light";
            settings.Layout = "side";
            settings.ContentWidth = "Fluid";
			settings.FixedHeader = false;
			settings.FixSiderbar = true;
            settings.Title = "DotNet宝藏库";
			settings.PrimaryColor = "daybreak";
			settings.ColorWeak = false;
			settings.SplitMenus= false;
			settings.HeaderRender= true;
			settings.FooterRender= false;
			settings.MenuRender= true;
			settings.MenuHeaderRender= true;
			settings.HeaderHeight = 48;

		});
		return builder.Build();
	}
}

The configuration items are all written. The meaning of the parameters can be seen from the meaning of the expression, no comments!

import style

open wwwroot/index.html. Since we are using AntDesign, we need to modify it index.html. The modified content is as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
    <title>DotNet宝藏库</title>
    <base href="/" />
    
    <link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" />
    <link rel="stylesheet" href="_content/AntDesign.ProLayout/css/ant-design-pro-layout-blazor.css" />
</head>

<body>

    <div class="status-bar-safe-area"></div>

    <div id="app">
        <style>
            html,
            body,
            #app {
      
      
                height: 100%;
                margin: 0;
                padding: 0;
            }

            #app {
      
      
                background-repeat: no-repeat;
                background-size: 100% auto;
            }

            .page-loading-warp {
      
      
                padding: 98px;
                display: flex;
                justify-content: center;
                align-items: center;
            }

            .ant-spin {
      
      
                -webkit-box-sizing: border-box;
                box-sizing: border-box;
                margin: 0;
                padding: 0;
                color: rgba(0, 0, 0, 0.65);
                font-size: 14px;
                font-variant: tabular-nums;
                line-height: 1.5;
                list-style: none;
                -webkit-font-feature-settings: 'tnum';
                font-feature-settings: 'tnum';
                position: absolute;
                display: none;
                color: #1890ff;
                text-align: center;
                vertical-align: middle;
                opacity: 0;
                -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
                transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
                transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
                transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86), -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
            }

            .ant-spin-spinning {
      
      
                position: static;
                display: inline-block;
                opacity: 1;
            }

            .ant-spin-dot {
      
      
                position: relative;
                display: inline-block;
                font-size: 20px;
                width: 20px;
                height: 20px;
            }

            .ant-spin-dot-item {
      
      
                position: absolute;
                display: block;
                width: 9px;
                height: 9px;
                background-color: #1890ff;
                border-radius: 100%;
                -webkit-transform: scale(0.75);
                -ms-transform: scale(0.75);
                transform: scale(0.75);
                -webkit-transform-origin: 50% 50%;
                -ms-transform-origin: 50% 50%;
                transform-origin: 50% 50%;
                opacity: 0.3;
                -webkit-animation: antSpinMove 1s infinite linear alternate;
                animation: antSpinMove 1s infinite linear alternate;
            }

            .ant-spin-dot-item:nth-child(1) {
      
      
                top: 0;
                left: 0;
            }

            .ant-spin-dot-item:nth-child(2) {
      
      
                top: 0;
                right: 0;
                -webkit-animation-delay: 0.4s;
                animation-delay: 0.4s;
            }

            .ant-spin-dot-item:nth-child(3) {
      
      
                right: 0;
                bottom: 0;
                -webkit-animation-delay: 0.8s;
                animation-delay: 0.8s;
            }

            .ant-spin-dot-item:nth-child(4) {
      
      
                bottom: 0;
                left: 0;
                -webkit-animation-delay: 1.2s;
                animation-delay: 1.2s;
            }

            .ant-spin-dot-spin {
      
      
                -webkit-transform: rotate(45deg);
                -ms-transform: rotate(45deg);
                transform: rotate(45deg);
                -webkit-animation: antRotate 1.2s infinite linear;
                animation: antRotate 1.2s infinite linear;
            }

            .ant-spin-lg .ant-spin-dot {
      
      
                font-size: 32px;
                width: 32px;
                height: 32px;
            }

            .ant-spin-lg .ant-spin-dot i {
      
      
                width: 14px;
                height: 14px;
            }
            .status-bar-safe-area {
      
      
                display: none;
            }

            @supports (-webkit-touch-callout: none) {
      
      
                .status-bar-safe-area {
      
      
                    display: flex;
                    position: sticky;
                    top: 0;
                    height: env(safe-area-inset-top);
                    background-color: #f7f7f7;
                    width: 100%;
                    z-index: 1;
                }

                .flex-column, .navbar-brand {
      
      
                    padding-left: env(safe-area-inset-left);
                }
            }
            @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
      
      
                .ant-spin-blur {
      
      
                    background: #fff;
                    opacity: 0.5;
                }
            }

            @-webkit-keyframes antSpinMove {
      
      
                to {
      
      
                    opacity: 1;
                }
            }

            @keyframes antSpinMove {
      
      
                to {
      
      
                    opacity: 1;
                }
            }

            @-webkit-keyframes antRotate {
      
      
                to {
      
      
                    -webkit-transform: rotate(405deg);
                    transform: rotate(405deg);
                }
            }

            @keyframes antRotate {
      
      
                to {
      
      
                    -webkit-transform: rotate(405deg);
                    transform: rotate(405deg);
                }
            }
        </style>
        <div style="
          display: flex;
          justify-content: center;
          align-items: center;
          flex-direction: column;
          min-height: 420px;
          height: 100%;
        ">
            <div class="page-loading-warp">
                <div class="ant-spin ant-spin-lg ant-spin-spinning">
                    <span class="ant-spin-dot ant-spin-dot-spin">
                        <i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i>
                    </span>
                </div>
            </div>
            <div style="display: flex; justify-content: center; align-items: center;">
                 <div class="loading-progress-text"></div>
            </div>
        </div>
    </div>

   

    <script src="_framework/blazor.webview.js" autostart="false"></script>
    <script src="_content/AntDesign/js/ant-design-blazor.js"></script>
</body>

</html>

join namespace

After _Imports.razoradding AntDesignthe namespace:

@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using MauiBlazorApp
@using MauiBlazorApp.Shared
//引入AntDesign
@using AntDesign

set container

Main.razorjoin in<AntContainer />

<Router AppAssembly="@typeof(Main).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
<!--设置容器-->
<AntContainer />

Modify MainLayout

  • revise MainLayout.razor.
  • Delete MainLayout.razorthe default layout in
  • introduceAntDesign.ProLayout
  • Set the layout toAntDesign.ProLayout
  • Construction menu, links in footer, copyright
  • Create a new folder under the wwwroot directory imagesand put the prepared logo in it

The complete code is as follows:

@using AntDesign.ProLayout
@inherits LayoutComponentBase

<AntDesign.ProLayout.BasicLayout 
    Logo="@("images/logo.png")"
    MenuData="MenuData">
    <ChildContent>
        @Body
    </ChildContent>
    <FooterRender>
        <FooterView Copyright="MauiBlazorApp" Links="Links"></FooterView>
    </FooterRender>
</AntDesign.ProLayout.BasicLayout>
<SettingDrawer />

@code
{
    
    
    private readonly MenuDataItem[] MenuData =
        {
    
    
        new MenuDataItem
        {
    
    
            Path = "/",
            Name = "Home",
            Key = "Home",
            Icon = "home"
        },
        new MenuDataItem
        {
    
    
            Path = "/Counter",
            Name = "Counter",
            Key = "Counter",
            Icon = "plus"
        },
        new MenuDataItem
        {
    
    
            Path = "/FetchData",
            Name = "FetchData",
            Key = "FetchData",
            Icon = "cloud"
        }
    };
    private readonly LinkItem[] Links =
    {
    
    
        new LinkItem
        {
    
    
            Key = "DotNet宝藏库",
            Title = "基于Ant Design Blazor",
            Href = "https://antblazor.com",
            BlankTarget = true
        }
    };
}

At this time, useless content in the project can be deleted, such Shared/NavMenu.razoras wwwroot/css. Since the folder
was deleted , the page elements must have no styles. cssThen simply transform the default pages!

Transform the default page

index.razor

Open Pages/Index.razorand delete the demo component SurveyPrompt . Shared/SurveyPrompt.razorAlso delete it by the way . Replace <h1>Hello, world!</h1>
with Ant Designcomponent.

@page "/"

<Title Level="1">Hello,DotNet宝藏库</Title>

<br />
<Text Type="success">欢迎关注我的公众号!</Text>

Counter.razor

Open it Pages/Counter.razorand change the code to the following:

@page "/counter"

<Title Level="2">HCounter</Title>
<Divider />
<p role="status">Current count: @currentCount</p>

<Button @onclick="IncrementCount" Type="primary">AntDesign 按钮</Button>
@code {
    
    
    private int currentCount = 0;

    private void IncrementCount()
    {
    
    
        currentCount++;
    }
}

FetchData.razor

Open Pages/FetchData.razor, replace the data table with Ant Design, delete all the code on the page, and replace it with the example of Ant Design!

@page "/fetchdata"
@using System.ComponentModel
@using AntDesign.TableModels
@using System.Text.Json

@using MauiBlazorApp.Data
@inject WeatherForecastService ForecastService

<Table @ref="table"
       TItem="WeatherForecast"
       DataSource="@forecasts"
       Total="_total"
       @bind-PageIndex="_pageIndex"
       @bind-PageSize="_pageSize"
       @bind-SelectedRows="selectedRows"
       OnChange="OnChange">
    <Selection Key="@(context.Id.ToString())" />
    <PropertyColumn Property="c=>c.Id" Sortable />
    <PropertyColumn Property="c=>c.Date" Format="yyyy-MM-dd" Sortable />
    <PropertyColumn Property="c=>c.TemperatureC" Sortable />
    <PropertyColumn Title="Temp. (F)" Property="c=>c.TemperatureF" />
    <PropertyColumn Title="Hot" Property="c=>c.Hot">
        <Switch @bind-Value="@context.Hot"></Switch>
    </PropertyColumn>
    <PropertyColumn Property="c=>c.Summary" Sortable />
    <ActionColumn>
        <Space>
            <SpaceItem><Button Danger OnClick="()=>Delete(context.Id)">Delete</Button></SpaceItem>
        </Space>
    </ActionColumn>
</Table>
<br />
<p>PageIndex: @_pageIndex | PageSize: @_pageSize | Total: @_total</p>

<br />
<h5>selections:</h5>
@if (selectedRows != null && selectedRows.Any())
{
    
    
    <Button Danger Size="small" OnClick="@(e => { selectedRows = null; })">Clear</Button>

    @foreach (var selected in selectedRows)
    {
    
    
        <Tag @key="selected.Id" Closable OnClose="e=>RemoveSelection(selected.Id)">@selected.Id - @selected.Summary</Tag>
    }
}

<Button Type="@ButtonType.Primary" OnClick="()=> { _pageIndex--; }">Previous page</Button>
<Button Type="@ButtonType.Primary" OnClick="()=> { _pageIndex++; }">Next Page</Button>

@code {
    
    
    private WeatherForecast[] forecasts;
    IEnumerable<WeatherForecast> selectedRows;
    ITable table;
    int _pageIndex = 1;
    int _pageSize = 10;
    int _total = 0;

    protected override async Task OnInitializedAsync()
    {
    
    
        forecasts = await GetForecastAsync(1, 50);
        _total = 50;
    }
    public class WeatherForecast
    {
    
    
        public int Id {
    
     get; set; }

        [DisplayName("Date")]
        public DateTime? Date {
    
     get; set; }

        [DisplayName("Temp. (C)")]
        public int TemperatureC {
    
     get; set; }

        [DisplayName("Summary")]
        public string Summary {
    
     get; set; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public bool Hot {
    
     get; set; }
    }
    private static readonly string[] Summaries = new[]
    {
    
    
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
    public void OnChange(QueryModel<WeatherForecast> queryModel)
    {
    
    
        Console.WriteLine(JsonSerializer.Serialize(queryModel));
    }
    public Task<WeatherForecast[]> GetForecastAsync(int pageIndex, int pageSize)
    {
    
    
        var rng = new Random();
        return Task.FromResult(Enumerable.Range((pageIndex - 1) * pageSize + 1, pageSize).Select(index =>
        {
    
    
            var temperatureC = rng.Next(-20, 55);
            return new WeatherForecast
                {
    
    
                    Id = index,
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = temperatureC,
                    Summary = Summaries[rng.Next(Summaries.Length)],
                    Hot = temperatureC > 30,
                };
        }).ToArray());
    }
    public void RemoveSelection(int id)
    {
    
    
        var selected = selectedRows.Where(x => x.Id != id);
        selectedRows = selected;
    }

    private void Delete(int id)
    {
    
    
        forecasts = forecasts.Where(x => x.Id != id).ToArray();
        _total = forecasts.Length;
    }
}

running result:

insert image description here
insert image description here
insert image description here

insert image description here

Summarize

No, see you next time

Click on the official account card below to follow me! Learn together and progress together!

Guess you like

Origin blog.csdn.net/sd2208464/article/details/128420305