首页 > 其他分享 >使用Blazor构建CRUD项目

使用Blazor构建CRUD项目

时间:2024-05-23 09:12:31浏览次数:12  
标签:Task return await CRUD 构建 context async Blazor public

在小公司中,往往没有一个前后端分离的大型团队,去各司其职的负责构建web应用程序。面对比较简单的需求,可能所谓团队只有一个人,既要开发前端又要开发后端。

如果能有一项技术,能够前后端通吃,并且具备非常高的开发效率,那就非常适合小公司的小型项目的小型甚至一人团队来使用了。

aspdotnet就是这样高效的后端开发框架,而有了blazor后,C#前端也可以通吃了,真正做到了一套框架,一种语言,前后端通吃。

本文使用aspdotnet + blazor,快速构建了一个CRUD项目。

1. 新建项目

新的Blazor Web App,可以同时使用Blazor Server和Blazor WebAssembly两种渲染模式

勾上sample pages

在生成的解决方案中,有两个项目

后面.Client的,就是WebAssembly的部分,这一部分只需要关注Pages里的页面。当用户访问这个页面时,就是WebAssembly,于是就可以离线操作页面。

如果页面功能不涉及前后台数据交互,则可以使用WebAssembly模式。

例如,问卷调查、考试,从后台获取数据,前提渲染出题目后,就是答题的过程。知道用户提交答案之前,都不需要与后台又交互。这时候整个作答页面可以使用WebAssembly。

2. 添加数据库支持

给项目添加sqlite数据库支持

  • 引入nuget包
  • 编写DbContext类
  • 编写Model类
  • 运行Package Manager Console命令

 新建DefaultDbContext.cs文件

using Microsoft.EntityFrameworkCore;
using QuickCRUD.Models;

namespace QuickCRUD;

public class DefaultDbContext : DbContext
{
  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
    optionsBuilder.UseSqlite("Data Source=quick_crud.sqlite");
  }

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
  }

  public DbSet<WeatherForecast> WeatherForecasts { get; set; }
}

新建WeatherForecast.cs文件。里面除了模型类,还有一个Configuration类,用来模型与配置数据库中表和表字段的对应关系。

删除自动生成的实例代码里,Pages/Weather.razor中的相关内容。

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace QuickCRUD.Models;

public class WeatherForecast
{
    public int Id { get; set; }
    public DateOnly Date { get; set; }
    public int TemperatureC { get; set; }
    public string? Summary { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

public class WeatherForecastConfig : IEntityTypeConfiguration<WeatherForecast>
{
    public void Configure(EntityTypeBuilder<WeatherForecast> builder)
    {
        builder.ToTable("weather_forcast");
        builder.Property("Id").HasColumnName("obj_id").ValueGeneratedOnAdd();
        builder.Property("Date").HasColumnName("ddate").HasColumnType("Text");
        builder.Property("TemperatureC").HasColumnName("temp_c");
        builder.Property("Summary").HasColumnName("summary");
    }
}

Package Manger Console,运行命令

Add-Migration Init
Update-Database

此时,将自动生成数据库与表结构

 3. 编写Repo代码

Repo是直接与数据库打交道的代码,提供了基本的对数据库表的CRUD操作

为了操作数据库,注入了DbContext类

新建WeatherForecastRepo.cs文件,里面利用DbContext对象,编写增删改查数据库的基本操作方法:

using Microsoft.EntityFrameworkCore;
using QuickCRUD.Models;

namespace QuickCRUD.Repos;

public class WeatherForecastRepo
{
  private readonly DefaultDbContext _context;

  public WeatherForecastRepo(DefaultDbContext context)
  {
    _context = context;
  }

  public async Task<int> Add(WeatherForecast entity)
  {
    _context.WeatherForecasts.Add(entity);
    return await _context.SaveChangesAsync();
  }

  public async Task<int> DeleteAll()
  {
    _context.WeatherForecasts.RemoveRange(
      _context.WeatherForecasts.Take(_context.WeatherForecasts.Count())
    );
    return await _context.SaveChangesAsync();
  }

  public async Task<int> DeleteById(int id)
  {
    var w = await GetById(id);
    if (w != null)
    {
      _context.WeatherForecasts.Remove(w);
    }
    return await _context.SaveChangesAsync();
  }

  public async Task<List<WeatherForecast>?> GetAll()
  {
    return await _context.WeatherForecasts.ToListAsync();
  }

  public async Task<WeatherForecast?> GetById(int id)
  {
    return await _context.WeatherForecasts.FindAsync(id);
  }

  public async Task<int> Update(WeatherForecast entity)
  {
    var w = await GetById(entity.Id);
    if (w != null)
    {
      _context.WeatherForecasts.Update(entity);
    }
    return await _context.SaveChangesAsync();
  }
}

 4. 编写前端list代码

  • 引入QuickGrid包
  • 编写前端list展示页面的component
  • 编写add页面component
  • 根据需要编写service文件

 编写展示数据的list页面,在Pages文件夹下建立Weather.razor文件

@page "/weather"
@rendermode InteractiveServer
@inject WeatherForecastService weatherForecastService
@inject NavigationManager nav

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

<button class="btn btn-sm btn-outline-success" @onclick="BtnNew">New</button>
<button class="btn btn-sm btn-danger" @onclick="BtnDeleteAll">Delete All</button>
<button class="btn btn-sm btn-outline-info" @onclick="BtnGenerateRandomDate">Generate Random Date</button>

@if (forecasts == null)
{
  <p><em>Loading...</em></p>
}
else
{
  <QuickGrid class="table" Items="forecasts.AsQueryable()">
    <PropertyColumn Property="@(f=>f.Id)" />
    <PropertyColumn Property="@(f=>f.Date)" />
    <PropertyColumn Title="Temp.(C)" Property="@(f=>f.TemperatureC)" />
    <PropertyColumn Title="Temp.(F)" Property="@(f=>f.TemperatureF)" />
    <PropertyColumn Property="@(f=>f.Summary)" />
    <TemplateColumn Context="f">
      <button class="btn btn-sm btn-outline-info" @onclick="_=>BtnEdit(f.Id)">Edit</button>
      <button class="btn btn-sm btn-outline-danger" @onclick="_=>BtnDelete(f.Id)">Delete</button>
    </TemplateColumn>
  </QuickGrid>
}

@code {
  private List<WeatherForecast>? forecasts;

  protected override async Task OnInitializedAsync()
  {
    forecasts = await weatherForecastService.AllForecast();
  }

  private void BtnNew()
  {
    nav.NavigateTo("/weather/add", true, true);
  }

  private void BtnEdit(int id)
  {
    nav.NavigateTo($"/weather/edit/{id}", true, true);
  }

  private async Task BtnDelete(int id)
  {
    await weatherForecastService.DeleteForecast(id);
    nav.Refresh(true);
  }

  private async Task BtnDeleteAll()
  {
    await weatherForecastService.DeleteAllForecast();
    nav.Refresh(true);
  }

  private async Task BtnGenerateRandomDate()
  {
    await weatherForecastService.GenerateRandom();
    nav.Refresh(true);
  }
}

注入了WeatherForecastService

需要注意页面上方的@rendermode InteractiveServer,这个标注将使得页面在服务端进行渲染,这是必不可少的,因为我们使用的是service,里面注入了repo,而repo中使用的是EF,这就意味着service的代码必须在服务端运行,所以这个页面必须在服务端渲染完毕后,再在前端展示。如果我们的service选择使用HttpClient获取后端api接口数据,则可以使用Wasm模式,就像Count.razor页面。

5. 编写Service

前端页面当需要使用数据时,将注入service,service如果需要向数据库请求数据,则在service中注入repo

编写WeatherForecastService.cs文件

using QuickCRUD.Models;
using QuickCRUD.Repos;

namespace QuickCRUD.Services;

public class WeatherForecastService
{
  private readonly WeatherForecastRepo _repo;

  public WeatherForecastService(WeatherForecastRepo repo)
  {
    _repo = repo;
  }

  public async Task<WeatherForecast> GetById(int id)
  {
    var f = await _repo.GetById(id);
    if (f == null) return new();
    return f;
  }

  public async Task<List<WeatherForecast>> AllForecast()
  {
    var result = await _repo.GetAll();
    if (result == null)
    {
      return [];
    }
    else
    {
      return result;
    }
  }

  public async Task<int> NewForecast(WeatherForecast forecast)
  {
    if (forecast == null) return 0;
    return await _repo.Add(forecast);
  }

  public async Task<int> UpdateForecast(WeatherForecast forecast)
  {
    if (forecast == null) { return 0; }
    return await _repo.Update(forecast);
  }

  public async Task<int> DeleteForecast(int id)
  {
    return await _repo.DeleteById(id);
  }

  public async Task<int> DeleteAllForecast()
  {
    return await _repo.DeleteAll();
  }

  public async Task<int> GenerateRandom()
  {
    var startDate = DateOnly.FromDateTime(DateTime.Now);
    var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
    var forecasts = Enumerable.Range(1, 10).Select(index => new WeatherForecast
    {
      Date = startDate.AddDays(index),
      TemperatureC = Random.Shared.Next(-20, 55),
      Summary = summaries[Random.Shared.Next(summaries.Length)]
    }).ToList();

    foreach (var forecast in forecasts) await _repo.Add(forecast);
    return forecasts.Count;
  }
}

6. 编写add和edit子页面

新建WeatherAdd.razor文件

@page "/weather/add"
@rendermode InteractiveServer
@inject WeatherForecastService weatherForecastService
@inject NavigationManager nav

<h1>New Weather Forecast</h1>

<EditForm Model="forecast" OnValidSubmit="SubmitForecast">
  <p>
    <label>
      Date:
      <InputDate @bind-Value="forecast.Date" />
    </label>
  </p>
  <p>
    <label>
      Temperature C:
      <InputNumber @bind-Value="forecast.TemperatureC" />
    </label>
  </p>
  <p>
    <label>
      Summary:
      <InputText @bind-Value="forecast.Summary" />
    </label>
  </p>
  <p>
    <button type="submit" class="btn btn-primary">Submit</button>
  </p>
</EditForm>
<button class="btn btn-outline-primary" @onclick="BtnCancel">Cancel</button>


@code {
  private WeatherForecast forecast { get; set; } = new() { Date = DateOnly.FromDateTime(DateTime.Today) };

  private async Task SubmitForecast()
  {
    await weatherForecastService.NewForecast(forecast);
    nav.NavigateTo("/weather", true, true);
  }

  private void BtnCancel()
  {
    nav.NavigateTo("/weather", true, true);
  }
}

新建WeatherEdit.razor文件

@page "/weather/edit/{id:int}"
@rendermode InteractiveServer
@inject WeatherForecastService weatherForecastService
@inject NavigationManager nav

<h1>Edit Weather Forecast</h1>
<h3>Id: @Id</h3>

<EditForm Model="forecast" OnValidSubmit="SubmitForecast">
  <p>
    <label>
      Date:
      <InputDate @bind-Value="forecast.Date" />
    </label>
  </p>
  <p>
    <label>
      Temperature C:
      <InputNumber @bind-Value="forecast.TemperatureC" />
    </label>
  </p>
  <p>
    <label>
      Summary:
      <InputText @bind-Value="forecast.Summary" />
    </label>
  </p>
  <p>
    <button type="submit" class="btn btn-primary">Submit</button>
  </p>
</EditForm>
<button class="btn btn-outline-primary" @onclick="BtnCancel">Cancel</button>

@code {
  [Parameter]
  public int Id { get; set; }

  private WeatherForecast forecast { get; set; } = new();

  protected override async Task OnParametersSetAsync()
  {
    forecast = await weatherForecastService.GetById(Id);
  }

  private void BtnCancel()
  {
    nav.NavigateTo("/weather", true, true);
  }

  private async Task SubmitForecast()
  {
    await weatherForecastService.UpdateForecast(forecast);
    nav.NavigateTo("/weather", true, true);
  }
}

edit页面与add页面的不同在于,需要传入id参数

7. 检查依赖注入

检查一下Program.cs文件中,是否将dbcontext,repo和service都配置了依赖注入

builder.Services.AddDbContext<DefaultDbContext>();
builder.Services.AddScoped<WeatherForecastRepo>();
builder.Services.AddScoped<WeatherForecastService>();

8. 效果展示

9. 发布

  • 将sqlite数据库的文件编译属性调整为复制到输出目录
  • publish参数

 

 最终生成

至此,最简单的CRUD完成了

10. 利用泛型的Repo

目前的Repo需要逐个编写操作数据库的方法,如果新增了一个model,则需要对应添加一个repo类,并再次重新编写所有的CRUD方法。但是因为都是CRUD的标准化方法,可以通过接口和泛型,实现新的model类继承全部CRUD方法。

首先编写一个接口,新建ICRUD.cs

namespace QuickCRUD.Repos;

public interface ICRUD<T, T_ID>
{
  public int GetCount();
  public Task<List<T>?> GetAll();
  public Task<List<T>?> GetLimit(int num);
  public Task<T?> GetById(T_ID id);
  public Task<int> Add(T entity);
  public Task<int> Update(T entity, T_ID id);
  public Task<int> DeleteById(T_ID id);
  public Task<int> DeleteAll();
}

然后,编写一个抽象类,AbstractRepo.cs,再抽象类中,同泛型,实现全部接口

using Microsoft.EntityFrameworkCore;

namespace QuickCRUD.Repos;

public abstract class AbstractRepo<T, T_ID>(DefaultDbContext context) : ICRUD<T, T_ID> where T : class
{
  public async Task<int> Add(T entity)
  {
    context.Set<T>().Add(entity);
    return await context.SaveChangesAsync();
  }

  public async Task<int> DeleteAll()
  {
    context.Set<T>().RemoveRange(
      context.Set<T>().Take(context.Set<T>().Count())
    );
    return await context.SaveChangesAsync();
  }

  public async Task<int> DeleteById(T_ID id)
  {
    var w = await GetById(id);
    if (w != null)
    {
      context.Set<T>().Remove(w);
    }
    return await context.SaveChangesAsync();
  }

  public async Task<List<T>?> GetAll()
  {
    return await context.Set<T>().ToListAsync();
  }

  public async Task<T?> GetById(T_ID id)
  {
    return await context.Set<T>().FindAsync(id);
  }

  public int GetCount()
  {
    return context.Set<T>().Count();
  }

  public async Task<List<T>?> GetLimit(int num)
  {
    var result = context.Set<T>().Take(num);
    return await result.ToListAsync();
  }

  public async Task<int> Update(T entity, T_ID id)
  {
    var w = await GetById(id);
    if (w != null)
    {
      context.Set<T>().Update(entity);
    }
    return await context.SaveChangesAsync();
  }
}

最后,修改WeatherForcastRepo.cs

using QuickCRUD.Models;

namespace QuickCRUD.Repos;

public class WeatherForecastRepo(DefaultDbContext context) : AbstractRepo<WeatherForecast, int>(context)
{
}

WeatherForcastRepo只需要继承抽象类,即可实现全部CRUD接口方法。如果有个性化的数据库操作方法,再在repo中添加方法即可。

如果有新的model,只需要创建一个新的repo,并继承AbstractRepo即可实现全部CRUD方法。

标签:Task,return,await,CRUD,构建,context,async,Blazor,public
From: https://www.cnblogs.com/jimokelly/p/18142830

相关文章

  • 开源Blazor UI组件库精选:让你的Blazor项目焕然一新!
    今天给大家推荐一些开源、美观的BlazorUI组件库,这些优秀的开源框架和项目不仅能够帮助开发者们提高开发效率,还能够为他们的项目带来更加丰富的用户体验。注:排名不分先后,都是十分优秀的开源框架和项目​AntDesignBlazorAntDesignBlazor是一个基于Blazor的前端UI组件库,......
  • 构建之法12
    在阅读了《构建之法》的第十二章后,我深感软件开发过程中的项目管理不仅仅是技术层面的挑战,更是一个对团队协作、时间管理和风险控制的综合考验。本章内容为我提供了一个全新的视角,让我对软件开发项目管理的复杂性和重要性有了更深入的理解。本章首先强调了项目管理在软件开发中的......
  • Django与前端框架协作开发实战:高效构建现代Web应用
    title:Django与前端框架协作开发实战:高效构建现代Web应用date:2024/5/2220:07:47updated:2024/5/2220:07:47categories:后端开发tags:DjangoREST前端框架SSR渲染SPA路由SEO优化组件库集成状态管理第1章:简介1.1Django简介Django是一个高级的PythonWeb......
  • 如何全程使用docker部署jeecg平台,无需安装开发环境(主要是如何使用Docker来进行Maven打
    在部署jeecg平台时,文档中即使通过docker部署,也需要安装开发环境编译一部分内容,本文记录使用docker替代安装环境的过程。使用docker的目的是在平台选型的过程中,不希望麻烦的安装环境,同时如果选型不满意,无需卸载环境就能恢复一个干净的系统。部署环境:UbuntuServer20.04docker,......
  • 构建-Cocos2dx-安卓游戏-全-
    构建Cocos2dx安卓游戏(全)原文:zh.annas-archive.org/md5/C5B09CE8256BCC61162F0F46EF01CFDE译者:飞龙协议:CCBY-NC-SA4.0前言Cocos2d-x是最常使用的开源游戏框架。它得到了微软对其移动和桌面平台官方支持,其小巧的核心运行速度比其他框架快,使得它能在低端Android设备上......
  • 通过构建安卓应用学习-Kotlin-全-
    通过构建安卓应用学习Kotlin(全)原文:zh.annas-archive.org/md5/201D65C8BC4C6A97336C0B7173DD6D6D译者:飞龙协议:CCBY-NC-SA4.0前言“教育的目的是培养具有技能和专业知识的优秀人才。真正的教育提升了人的尊严,增加了他或她的自尊。如果每个人都能意识到真正的教育,并在人类......
  • 德邦快递携手火山引擎,构建“数据飞轮”实现精准营销
     在快递行业中,数据的复杂性和多样性一直是企业面临的一大挑战。 在近日的采访中,德邦快递谈到通过引入火山引擎数智平台VeDI旗下系列数据产品,解决了长期困扰其营销活动的数据“黑盒”问题,显著提升了用户识别和营销效率,实现了月活用户和下单用户数的跃升。 德邦快递数字......
  • 使用-Danfo-js-构建数据驱动应用-全-
    使用Danfo.js构建数据驱动应用(全)原文:zh.annas-archive.org/md5/074CFA285BE35C0386726A8DBACE1A4F译者:飞龙协议:CCBY-NC-SA4.0前言大多数数据分析师使用Python和pandas进行数据处理和操作,这得益于这些库提供的便利性和性能。然而,JavaScript开发人员一直希望浏览器......
  • 整合LlamaIndex与LangChain构建高级的查询处理系统
    构建大型语言模型应用程序可能会颇具挑战,尤其是当我们在不同的框架(如Langchain和LlamaIndex)之间进行选择时。LlamaIndex在智能搜索和数据检索方面的性能令人瞩目,而LangChain则作为一个更加通用的应用程序框架,提供了更好的与各种平台的兼容性。本篇文章将介绍如何将LlamaIndex和La......
  • 《构建之法》阅读笔记之二
    第二部分:实践指南主题: 构建的实际应用内容概要:构建过程: 详细介绍了构建过程中的各个阶段,包括需求分析、设计、开发、测试等。对每个阶段的任务和方法进行了具体的描述,例如需求分析阶段可以采用用户故事、用例分析等方法;设计阶段可以采用面向对象设计、设计模式等方法。构......