最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 《进击吧!Blazor!》第一章 4.数据交互

    正文概述 掘金(MicrosoftReactor)   2021-02-06   461

    上一次课程我们完成了ToDo应用的界面制作,这次我们要将客户端的数据写入数据库,并从数据库中读物我们需要的数据。

    数据交互过程

    我们先看一下从客户端到数据库的流程

    《进击吧!Blazor!》第一章 4.数据交互

    Blazor

    Blazor客户端就是我们上节课做的ToDo程序。

    HttpClient

    HttpClient就是我们完成网络通讯用的组件,对于这类组件我们希望在一个应用中只构造一次,这样避免重复分配资源,因此我们在Program.cs中进行注册。

    public class Program
    {
        public static async Task Main(string[] args)
        {
            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
        }
    }
    

    BaseAddress为基地址,这样我们使用时,Url只需要传入相对地址即可,此处默认为当前主机的地址。 DefaultRequestHeaders 默认HTTP请求头参数 Timeout 连接超时参数

    • 依赖关系注入

    上面通过服务注入的方式实现了HttpClient全局共享(单例),那么如何使用服务?这里我们就需要引入一下“依赖关系注入 (DI)”的概念。

    DI是一种技术,基本原理是把有依赖关系的类放到容器中,解析出这些类的实例,就是依赖注入。应用可通过将内置服务注入组件来使用这些服务。 应用还可定义和注册自定义服务,并通过 DI 使其在整个应用中可用。

    该技术在 Blazor 应用中常用于以下两个方面:

    《进击吧!Blazor!》第一章 4.数据交互

    服务生存期决定了服务何时创建,何时销毁,有三种模式:

    ScopedBlazor WebAssembly 应用当前没有 DI 范围的概念。 已注册 Scoped 的服务的行为与 Singleton 服务类似。 但是,Blazor Server 托管模型支持 Scoped 生存期。 在 Blazor Server 应用中,Scoped服务注册的范围为“连接”。 因此,即使当前意图是在浏览器中运行客户端,对于范围应限定为当前用户的服务来说,首选使用 Scoped 服务。

    Singleton:DI 创建服务的单个实例。 需要 Singleton 服务的所有组件都会接收同一服务的实例。

    Transient:每当组件从服务容器获取 Transient 服务的实例时,它都会接收该服务的新实例。

    这里的 HttpClient 使用了 AddScoped 方法,那么就是当前范围内使用同一个实例,因为项目是Blazor WebAssembly模式,所以相当于单例服务。

    ASP.Net Core

    我用ASP.Net Core项目给Blazor应用提供WebAPI接口

    项目结构如下

    《进击吧!Blazor!》第一章 4.数据交互

    • launchSettings.json

    这里配置了我们调试的方式,端口等,相对于普通的Web项目多了inspectUri属性,具有以下作用:

    1. 使 IDE 能够检测到该应用为 Blazor WebAssembly 应用。
    2. 指示脚本调试基础结构通过 Blazor 的调试代理连接到浏览器。
    3. 已启动的浏览器 (browserInspectUri) 上 WebSocket 协议 (wsProtocol)、主机 (url.hostname)、端口 (url.port) 和检查器 URI 的占位符值由框架提供。
    {
    //省略其他配置
        "profiles": {
          "IIS Express": {
            "commandName": "IISExpress",
            "launchBrowser": true,
            "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
            "environmentVariables": {
              "ASPNETCORE_ENVIRONMENT": "Development"
            }
          },
    //省略其他配置
    }
    
    • Controllers

    控制器(Controller)放在这里,站点的路由表是通过遍历项目中带有ApiControllerAttribute(基类ControllerAttribute)的类,然后寻找里面的方法实现,他和Blazor的路由表创建方法上有点相似。

        [ApiController]
        [Route("api/[controller]/[action]")]
        public class TaskController : ControllerBase
    

    Route定义了路由格式,上例中[controller]/[action]意为使用Controlleraction的名称作为路由地址,这样写可以省去每个action上标记路由名字的麻烦。

    • Pages

    存放页面文件的位置,因为我们的项目页面全部使用Blazor构建,所以用不到此文件夹,因此这里就不做介绍了。

    • appsettings.json

    站点的配置文件,我们的项目就用到了数据库链接字符串配置

    • Program.cs

    应用的Main函数在这里,这里完成了Host的创建与启动

    • Startup.cs

    启动类,项目启动时的服务注册,配置等工作都在此处完成 ConfigureServices使用此方法将服务添加到容器。 Configure使用此方法来配置HTTP请求管道。

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    //省略其他代码
        app.UseBlazorFrameworkFiles();
        app.UseStaticFiles();
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
            endpoints.MapControllers();
            endpoints.MapFallbackToFile("index.html");
        });
    }
    

    app.UseBlazorFrameworkFiles();配置应用程序提供Blazor WebAssembly框架文件,默认根路径为/,也可以自定义路径前缀 endpoints.MapControllers();添加控制器(Controller)的路由。 endpoints.MapFallbackToFile("index.html");添加默认路由地址是index.html

    EF Code

    所有的数据我们需要保存入数据库,这里我选择使用EF Core作为我们的数据访问技术

    EF Core部分特点

    • Entity Framework (EF) Core 是轻量化、可扩展、开源和跨平台版的数据访问技术。
    • EF Core可用作对象关系映射程序 (O/RM),能让我们用对象来处理数据库,使用Linq进行查询,这样我们就可以不用编写大量SQL代码了。
    • EF Core 支持多个数据库引擎,比如MySQL、SQLite等。

    《进击吧!Blazor!》第一章 4.数据交互

    他支持采用Code Firs或者Database First两种模式

    • Code Firs用代码编写对象关系,然后通过它创建数据库。
    • Database First可以提供现有数据库,反向生成对象映射 。

    《进击吧!Blazor!》第一章 4.数据交互

    Database

    数据库我选择SQL Server,使用全套微软技术栈工具链使用体验比较好,当然我们也可以选择其他数据库。

    SQL Server产品家族中有一个SQL Server LocalDB的东西,它是SQL Server的一个超级精简版本,安装包只有几十MB(安装好后200+MB),它包含了数据库的基础功能,但是不支持联网,只能本机连接,对于个人开发资源占用少,强烈推荐,VS安装Web开发组件会默认安装此数据库。

    连接时服务器名称默认是(localdb)\MSSQLLocalDB,也可以使用C:\Program Files\Microsoft SQL Server\130\Tools\Binn\SqlLocalDB.exe进行配置数据库实例

    《进击吧!Blazor!》第一章 4.数据交互

    我们可以使用VS的SQL Server对象资源管理器来查看我们的数据库,不过我这里强烈推荐使用SQL Server Management Studio (SSMS) 的“数据库关系图”功能来维护数据库,可视化编辑表,主外键关系等,保存即更新数据库,这对于数据库优先的模式下开发非常友好,效率极高。

    下图是我们ToDo应用使用的表结构。

    《进击吧!Blazor!》第一章 4.数据交互

    代码实战

    上面介绍了数据交互的流程概念,接下来我们改造上回制作的ToDo项目。

    引入和配置EF Code

    我们先创建一个ToDo.Entity项目用于存储ORM映射以及EF的Context。

    使用EF Core Power Tools工具创建代码

    因为我们上面已经把数据库设计完成了,所以我们采用Database First模式创建EF相关的代码。

    此处推荐一个从数据库到EF实体的代码生成扩展EF Core Power Tools

    《进击吧!Blazor!》第一章 4.数据交互

    选择要连接的数据库。

    《进击吧!Blazor!》第一章 4.数据交互

    选择要添加的数据库对象。

    《进击吧!Blazor!》第一章 4.数据交互

    配置Context的名称和命名空间等,下图是我常用配置。

    《进击吧!Blazor!》第一章 4.数据交互

    EF Core Power Tools生成的代码文件如下

    《进击吧!Blazor!》第一章 4.数据交互

    appsettings.json中添加链接字符串

    打开ToDo.Server\appsettings.json添加数据库连接字符串

      "ConnectionStrings": {
        "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=ToDo;Integrated Security=True"
      },
    

    ConfigureServices中添加服务注册

    打开ToDo.Server\Startup.cs,把TodoContext注册到DbContext中为,并设置连接字符串

    services.AddDbContext<TodoContext>(options =>
    {
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
    });
    

    有时候我们需要在输出EF执行的SQL语句,这便于我们调试以及优化数据库,下面的配置就把EF日志输出到控制台

    /// <summary>
    /// 输出日志
    /// </summary>
    public static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });
    
    public void ConfigureServices(IServiceCollection services)
    {
    //省略其他代码
        services.AddDbContext<TodoContext>(options =>
        {
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")).UseLoggerFactory(loggerFactory);
        });
    }
    

    功能实现

    首先创建ToDo.Server\Controllers\TaskController.cs文件用于编写WebAPI接口,代码如下:

    namespace ToDo.Server.Controllers
    {
        [ApiController]
        [Route("api/[controller]/[action]")]
        public class TaskController : ControllerBase
        {
            TodoContext Context;
    
            public TaskController(TodoContext context)
            {
                Context = context;
            }
        }
    }
    

    通过依赖注入将TodoContext注入到当前类中。

    1. 列出当天的所有代办工作

    ToDo.Server

    TaskController.cs中添加GetToDayTask方法用于返回当前待办数据。

    [HttpGet]
    public List<TaskDto> GetToDayTask()
    {
        var result = Context.Task.Where(x => x.PlanTime == DateTime.Now.Date);
        return QueryToDto(result).ToList();
    }
    
    [NonAction]
    private IQueryable<TaskDto> QueryToDto(IQueryable<Entity.Task> query)
    {
        return query.Select(x => new TaskDto()
        {
            TaskId = x.TaskId,
            Title = x.Title,
            Description = x.Description,
            PlanTime = x.PlanTime,
            Deadline = x.Deadline,
            IsImportant = x.IsImportant,
            IsFinish = x.IsFinish,
        });
    }
    

    ToDo.Client

    添加Pages\ToDay.razor.cs类文件,VS会自动将ToDay.razorToDay.razor.cs折叠到一起。

    《进击吧!Blazor!》第一章 4.数据交互

    在类定义中增加partial关键字,声明类为局部类,你可以理解成ToDay.razorToDay.razor.cs中的代码都属于同一个类,只是放在不同文件中,编译器编译时会将他们合并到一起后进行编译。

        public partial class ToDay
    

    接着做一下代码迁移

    1. Pages\ToDay.razor文件@code{}中的代码剪切到ToDay.razor.cs
    2. Pages\ToDay.razor文件@inject代码采用[Inject] public MessageService MsgSrv { get; set; }这样的格式等价的迁移到ToDay.razor.cs

    这样做我们可以实现界面代码与业务代码分开在不同的文件中,方便整理代码,提高代码可读性。

    ToDay.razor.cs中添加HttpClient的依赖注入,用于向服务端发起Http请求

    [Inject] public HttpClient Http { get; set; }
    

    修改OnInitializedAsync方法的代码

    private List<TaskDto> taskDtos = new List<TaskDto>();
    bool isLoading = true;
    protected async override Task OnInitializedAsync()
    {
        isLoading = true;
        taskDtos = await Http.GetFromJsonAsync<List<TaskDto>>("api/Task/GetToDayTask");
        isLoading = false;
        await base.OnInitializedAsync();
    }
    

    Http.GetFromJsonAsync<List<TaskDto>>使用HttpGet模式请求数据,这里使用await进行异步等待,充分利用await可以极大的简化代码量。 isLoading是载入状态,网络通讯必定有延迟,避免白屏,我们在载入前后分别改变载入状态,同时修改ToDay.razor代码添加Spin组件用于显示载入效果。

    <PageHeader Title="@("我的一天")" Subyyyy年MM月dd日")"></PageHeader>
    <Spin Spinning="isLoading"><!--插入代码-->
        @foreach (var item in taskDtos)
    <!--省略其他代码-->
            <Input @bind-Value="@newTask.Title" OnkeyUp="OnInsert" />
        </div>
    </Spin><!--插入代码-->
    

    2. 添加代办

    ToDo.Server TaskController.cs中添加SaveTask方法用于保存新的待办内容

    [HttpPost]
    public Guid SaveTask(TaskDto dto)
    {
        Entity.Task entity;
        if (dto.TaskId == Guid.Empty)
        {
            entity = new Entity.Task();
            entity.TaskId = Guid.NewGuid();
            Context.Add(entity);
        }
        else
        {
            entity = Context.Task.FirstOrDefault(x => x.TaskId == dto.TaskId);
        }
        entity.Title = dto.Title;
        entity.Description = dto.Description;
        entity.PlanTime = dto.PlanTime;
        entity.Deadline = dto.Deadline;
        entity.IsImportant = dto.IsImportant;
        entity.IsFinish = dto.IsFinish;
        Context.SaveChanges();
        return entity.TaskId;
    }
    

    我通过判断dto.TaskId的值,直接将新增与更新写在一个接口中,这样可以复用代码。

    ToDo.Client ToDay.razor.cs文件修改OnInsert方法相关的代码

    TaskDto newTask = new TaskDto() { PlanTime = DateTime.Now.Date };
    [Inject] public MessageService MsgSrv { get; set; }
    bool isNewLoading = false;
    async void OnInsert(KeyboardEventArgs e)
    {
        if (e.Code == "Enter")
        {
            if (string.IsNullOrWhiteSpace(newTask.Title))
            {
                MsgSrv.Error($"标题必须填写");
                return;
            }
            isNewLoading = true;
            var result = await Http.PostAsJsonAsync<TaskDto>($"api/Task/SaveTask", newTask);
            if (result.IsSuccessStatusCode)
            {
                newTask.TaskId = await result.Content.ReadFromJsonAsync<Guid>();
                taskDtos.Add(newTask);
                newTask = new TaskDto() { PlanTime = DateTime.Now.Date };
            }
            else
            {
                MsgSrv.Error($"请求发生错误 {result.StatusCode}");
            }
            isNewLoading = false;
            StateHasChanged();
        }
    }
    

    ToDay.razor文件增加保存时等待组件

        <Spin Spinning="isNewLoading"><!--插入代码-->
            <div class="task-input">
                <DatePicker Picker="@DatePickerType.Date" @bind-Value="@newTask.PlanTime" />
                <Input @bind-Value="@newTask.Title" OnkeyUp="OnInsert" />
            </div>
        </Spin><!--插入代码-->
    

    通过Http.PostAsJsonAsync调用api/Task/SaveTasknewTask内容提交到后端并保存,返回的HttpResponseMessage包含了状态编码等,如果成功就在界面上显示新的待办,失败就提示错误

    MessageService全局展示操作反馈信息。

    3. 编辑待办

    ToDo.Server TaskController.cs中添加GetTaskDto方法用于获取待办信息

    public TaskDto GetTaskDto(Guid taskId)
    {
        var result = Context.Task.Where(x => x.TaskId == taskId);
        return QueryToDto(result).FirstOrDefault();
    }
    

    ToDo.Client TaskInfo.razor文件中增加Spin@if代码

    <Spin Spinning="isLoading">
        @if (taskDto != null)<!--页面打开时taskDto并没有值,所以直接绑定到Form会发生异常,所以这里需要做一个不为空判断-->
        {
            <Form OnFinish="OnSave" Model="taskDto" LabelColSpan="8"><!--当用户点击submit按钮时会触发OnFinish事件,所以通常会在这里进行保存操作-->
    <!--省略其他代码-->
                <div>
                    <Button HtmlType="submit">保存</Button>
                    <Button OnClick="OnCancel">取消</Button>
                </div>
            </Form>
        }
    </Spin>
    

    TaskInfo.razor.cs添加下面代码

    public partial class TaskInfo : DrawerTemplate<TaskDto, TaskDto>
    {
        [Inject]
        public HttpClient Http { get; set; }
    
        [Inject]
        public MessageService MsgSvr { get; set; }
    
        TaskDto taskDto;
    
        bool isLoading = false;
    
        protected override async Task OnInitializedAsync()
        {
            //通过api/Task/GetTaskDto接口获得待办内容
            taskDto = await Http.GetFromJsonAsync<TaskDto>($"api/Task/GetTaskDto?taskId={base.Options.TaskId}");
            await base.OnInitializedAsync();
        }
    
        async void OnSave()
        {
            var result = await Http.PostAsJsonAsync<TaskDto>($"api/Task/SaveTask", taskDto);
            if (result.StatusCode == System.Net.HttpStatusCode.OK)
            {
                await base.CloseAsync(taskDto);//关闭抽屉,并返回当前待办数据
            }
            else
            {
                MsgSvr.Error($"请求发生错误 {result.StatusCode}");
            }
        }
    
        async void OnCancel()
        {
            await base.CloseAsync(null);//如果点击了取消,那么将null返回出去
        }
    }
    

    ToDay.razor.cs中的OnCardClick方法更新

    [Inject] public DrawerService DrawerSrv { get; set; }
    async void OnCardClick(TaskDto task)
    {
        var result = await DrawerSrv.CreateDialogAsync<TaskInfo, TaskDto, TaskDto>(task, title: task.Title, width: 450);
        if (result == null) return;
        var index = taskDtos.FindIndex(x => x.TaskId == result.TaskId);
        taskDtos[index] = result;
        await InvokeAsync(StateHasChanged);
    }
    

    DrawerSrv.CreateDialogAsync相对于DrawerSrv.CreateAsync简化了调用方法,默认将抽屉的CloseAsync参数返回,这就简化了每次使用抽屉时需要注册CloseAsync事件的麻烦,也让代码更加清晰。 title: task.Title, width: 450使用可选参数简化对抽屉的参数配置。

    4. 修改重要程度

    ToDo.Server TaskController.cs中添加SetImportant方法用于修改IsImportant字段的值

    [HttpPost]
    public void SetImportant(SetImportantReq req)
    {
        var entity = Context.Task.FirstOrDefault(x => x.TaskId == req.TaskId);
        entity.IsImportant = req.IsImportant;
        Context.SaveChanges();
    }
    

    ToDo.Shared 添加SetImportantReq类用于SetImportant接口请求参数

    public class SetImportantReq
    {
        public Guid TaskId { get; set; }
        public bool IsImportant { get; set; }
    }
    

    ToDo.Client ToDay.razor.cs中的OnStar方法更新

    private async void OnStar(TaskDto task)
    {
        var req = new SetImportantReq()//ToDo.Shared项目中的类可以前后端公用,这就是Blazor优势之一。
        {
            TaskId = task.TaskId,
            IsImportant = !task.IsImportant,
        };
    
        var result = await Http.PostAsJsonAsync<SetImportantReq>("api/Task/SetImportant", req);
        if (result.IsSuccessStatusCode)
        {
            task.IsImportant = req.IsImportant;//请求成功后需要修改本地重要状态
            StateHasChanged();//状态改变,刷新页面的显示
        }
    }
    

    5. 修改完成状态

    ToDo.Server TaskController.cs中添加SetFinish方法用于修改IsFinish字段的值

    [HttpPost]
    public void SetFinish(SetFinishReq req)
    {
        var entity = Context.Task.FirstOrDefault(x => x.TaskId == req.TaskId);
        entity.IsFinish = req.IsFinish;
        Context.SaveChanges();
    }
    

    ToDo.Shared 添加SetFinishReq类用于SetFinish接口请求参数

    public class SetFinishReq
    {
        public Guid TaskId { get; set; }
    
        public bool IsFinish { get; set; }
    }
    

    ToDo.Client ToDay.razor.cs中的OnFinish方法更新

    private async void OnFinish(TaskDto task)
    {
        var req = new SetFinishReq()
        {
            TaskId = task.TaskId,
            IsFinish = !task.IsFinish,
        };
    
        var result = await Http.PostAsJsonAsync<SetFinishReq>("api/Task/SetFinish", req);
        if (result.IsSuccessStatusCode)
        {
            task.IsFinish = req.IsFinish;
            StateHasChanged();
        }
    }
    

    6. 删除代办

    ToDo.Server TaskController.cs中添加DelTask方法用于删除待办。

    [HttpDelete]
    public void DelTask(Guid taskId)
    {
        Context.Task.Remove(Context.Task.Find(taskId));
        Context.SaveChanges();
    }
    

    ToDo.Client ToDay.razor.cs中的OnFinish方法更新

    [Inject] public ConfirmService ConfirmSrv { get; set; }
    public async Task OnDel(TaskDto task)
    {
        if (await ConfirmSrv.Show($"是否删除任务 {task.Title}", "删除", ConfirmButtons.YesNo, ConfirmIcon.Info) == ConfirmResult.Yes)
        {
            taskDtos.Remove(task);
        }
    }
    

    ConfirmService可以快捷地弹出一个内置的确认框,类似于 Windows MessageBox。

    7. 查询代办

    ToDo.Server TaskController.cs中添加GetSearch方法用于修改SetFinish字段的值

    [HttpPost]
    public GetSearchRsp GetSearch(GetSearchReq req)
    {
        if (req.PageIndex == 0) req.PageIndex = 1;
        var query = Context.Task.Where(x => x.Title.Contains(req.QueryTitle ?? ""));
    
        foreach (var sort in req.Sorts)
        {
            if (sort.SortOrder == "descend")
                query = query.OrderBy(sort.SortField + " DESC");
            else
                query = query.OrderBy(sort.SortField);
        }
    
        var result = new GetSearchRsp()
        {
            Data = QueryToDto(query.Skip(--req.PageIndex * req.PageSize).Take(req.PageSize)).ToList(),
            Total = query.Count(),
        };
    
        return result;
    }
    

    if (req.PageIndex == 0) req.PageIndex = 1 ?吐槽开始:几乎所有的UI框架页码都是从0开始,但是AntDesign规范的页码是从1开始的,然而没有载入数据时又是返回0?,所以要特别注意。

    OrderBy使用了System.Linq.Dynamic.Core 扩展包,它提供了一些动态的Linq支持,比如此处排序我传入的参数不是一个表达式,而是一个字符串,这样可以让代码灵活性大增。

    ToDo.Shared

    public class GetSearchReq
    {
        public string QueryTitle { get; set; }
        public int PageIndex { get; set; }
        public int PageSize { get; set; }
        public List<SortFieldName> Sorts { get; set; }
    }
    
    public class SortFieldName
    {
        /// <summary>
        /// 排序字段
        /// </summary>
        public string SortField { get; set; }
        /// <summary>
        /// 排序方向
        /// </summary>
        public string SortOrder { get; set; }
    }
        
    public class GetSearchRsp
    {
        public List<TaskDto> Data { get; set; }
        public int Total { get; set; }
    }
    

    ToDo.Client TaskSearch.razor 文件的代码

    @page "/search"
    <PageHeader Title="@("全部待办事项")" Sub数量:{total}")"></PageHeader>
    <Search @bind-Value="queryTitle" OnSearch="OnSearch"></Search>
    <Table Loading="@isLoading" DataSource="@datas" PageSize="10" Total="@total" OnChange="OnChange" TItem="TaskDto">
        <AntDesign.Column @bind-Field="@context.Title" Sortable>
            @context.Title
            @if (context.IsImportant)
            {
                <Tag Color="orange">重要</Tag>
            }
        </AntDesign.Column>
        <AntDesign.Column @bind-Field="@context.Description" />
        <AntDesign.Column @bind-Field="@context.PlanTime" Sortable />
        <AntDesign.Column @bind-Field="@context.Deadline" />
        <AntDesign.Column @bind-Field="@context.IsFinish">
            @if (context.IsFinish)
            {
                <Icon Type="check" Theme="outline" />
            }
        </AntDesign.Column>
    </Table>
    

    TaskSearch.razor.cs 文件的代码

    [Inject] public HttpClient Http { get; set; }
    private bool isLoading = false;
    List<TaskDto> datas = new List<TaskDto>();
    private string queryTitle;
    private int total = 0;
    //点击查询按钮时检索数据
    private async Task OnSearch()
    {
        await OnQuery(1, 10, new List<SortFieldName>());
    }
    //当前页码,排序发生改变时调用查询方法检索数据
    private async Task OnChange(AntDesign.TableModels.QueryModel<TaskDto> queryModel)
    {
        await OnQuery(
            queryModel.PageIndex,
            queryModel.PageSize,
            queryModel.SortModel.Where(x => string.IsNullOrEmpty(x.SortType.Name) == false).OrderBy(x => x.Priority)
            .Select(x => new SortFieldName() { SortField = x.FieldName, SortOrder = x.SortType.Name }).ToList()
            );
    }
    //检索数据
    private async Task OnQuery(int pageIndex, int pageSize, List<SortFieldName> sort)
    {
        isLoading = true;
        var req = new GetSearchReq()
        {
            QueryTitle = queryTitle,
            PageIndex = pageIndex,
            PageSize = pageSize,
            Sorts = sort,
        };
        var httpRsp = await Http.PostAsJsonAsync<GetSearchReq>($"api/Task/GetSearch", req);
        var result = await httpRsp.Content.ReadFromJsonAsync<GetSearchRsp>();
        datas = result.Data;
        total = result.Total;
        isLoading = false;
    }
    

    Search带有查询按钮的文本框框

    查看详细服务

    我的一天全部页面上均存在打开待办详情的功能需求,这时我们就可以自己做一个服务将两边的功能合并到一起。 添加TaskDetailServices.cs文件,加入以下代码

    namespace ToDo.Client
    {
        public class TaskDetailServices
        {
            public DrawerService DrawerSvr { get; set; }
    
            public TaskDetailServices(DrawerService drawerSvr)
            {
                DrawerSvr = drawerSvr;
            }
    
            public async Task EditTask(TaskDto taskDto, List<TaskDto> datas)
            {
                var taskItem = await DrawerSvr.CreateDialogAsync<TaskInfo, TaskDto, TaskDto>(taskDto, title: taskDto.Title, width: 450);
                if (taskItem == null) return;
                var index = datas.FindIndex(x => x.TaskId == taskItem.TaskId);
                datas[index] = taskItem;
            }
        }
    }
    

    TaskDetailServices(DrawerService drawerSvr) 只有razor文件可以使用[Inject]标记属性进行注入服务,普通得类需要在构造函数中定义才能注入服务。

    Program.cs文件中注册TaskDetailServices

    builder.Services.AddScoped<TaskDetailServices>();
    

    TaskSearch.razor文件中添加详情按钮

        <AntDesign.Column TData="object">
            <Button OnClick="x=>OnDetail(context)">详情</Button>
        </AntDesign.Column>
    

    TaskSearch.razor.cs 插入以下代码,我们注入自定义的服务,使用服务中的方法打开编辑界面。

    [Inject] public TaskDetailServices TaskSrv { get; set; }
    
    private async Task OnDetail(TaskDto taskDto)
    {
        await TaskSrv.EditTask(taskDto, datas);
    }
    

    次回预告

    下一次我们要介绍Blazor的精髓,也是我个人认为Blazor框架体系中最优秀的特性——组件。我们通过几个小实例展示Blazor的组件开发方法,敬请期待

    更多学习资料:

    aka.ms/LearnBlazor


    起源地下载网 » 《进击吧!Blazor!》第一章 4.数据交互

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元