翼度科技»论坛 编程开发 .net 查看内容

使用Blazor构建CRUD项目

9

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
在小公司中,往往没有一个前后端分离的大型团队,去各司其职的负责构建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文件
  1. using Microsoft.EntityFrameworkCore;
  2. using QuickCRUD.Models;
  3. namespace QuickCRUD;
  4. public class DefaultDbContext : DbContext
  5. {
  6.   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  7.   {
  8.     optionsBuilder.UseSqlite("Data Source=quick_crud.sqlite");
  9.   }
  10.   protected override void OnModelCreating(ModelBuilder modelBuilder)
  11.   {
  12.     base.OnModelCreating(modelBuilder);
  13.     modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
  14.   }
  15.   public DbSet<WeatherForecast> WeatherForecasts { get; set; }
  16. }
复制代码
新建WeatherForecast.cs文件。里面除了模型类,还有一个Configuration类,用来模型与配置数据库中表和表字段的对应关系。
删除自动生成的实例代码里,Pages/Weather.razor中的相关内容。
  1. using Microsoft.EntityFrameworkCore;
  2. using Microsoft.EntityFrameworkCore.Metadata.Builders;
  3. namespace QuickCRUD.Models;
  4. public class WeatherForecast
  5. {
  6.     public int Id { get; set; }
  7.     public DateOnly Date { get; set; }
  8.     public int TemperatureC { get; set; }
  9.     public string? Summary { get; set; }
  10.     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
  11. }
  12. public class WeatherForecastConfig : IEntityTypeConfiguration<WeatherForecast>
  13. {
  14.     public void Configure(EntityTypeBuilder<WeatherForecast> builder)
  15.     {
  16.         builder.ToTable("weather_forcast");
  17.         builder.Property("Id").HasColumnName("obj_id").ValueGeneratedOnAdd();
  18.         builder.Property("Date").HasColumnName("ddate").HasColumnType("Text");
  19.         builder.Property("TemperatureC").HasColumnName("temp_c");
  20.         builder.Property("Summary").HasColumnName("summary");
  21.     }
  22. }
复制代码
Package Manger Console,运行命令
  1. Add-Migration Init
  2. Update-Database
复制代码
此时,将自动生成数据库与表结构

 3. 编写Repo代码

Repo是直接与数据库打交道的代码,提供了基本的对数据库表的CRUD操作
为了操作数据库,注入了DbContext类
新建WeatherForecastRepo.cs文件,里面利用DbContext对象,编写增删改查数据库的基本操作方法:
  1. using Microsoft.EntityFrameworkCore;
  2. using QuickCRUD.Models;
  3. namespace QuickCRUD.Repos;
  4. public class WeatherForecastRepo
  5. {
  6.   private readonly DefaultDbContext _context;
  7.   public WeatherForecastRepo(DefaultDbContext context)
  8.   {
  9.     _context = context;
  10.   }
  11.   public async Task<int> Add(WeatherForecast entity)
  12.   {
  13.     _context.WeatherForecasts.Add(entity);
  14.     return await _context.SaveChangesAsync();
  15.   }
  16.   public async Task<int> DeleteAll()
  17.   {
  18.     _context.WeatherForecasts.RemoveRange(
  19.       _context.WeatherForecasts.Take(_context.WeatherForecasts.Count())
  20.     );
  21.     return await _context.SaveChangesAsync();
  22.   }
  23.   public async Task<int> DeleteById(int id)
  24.   {
  25.     var w = await GetById(id);
  26.     if (w != null)
  27.     {
  28.       _context.WeatherForecasts.Remove(w);
  29.     }
  30.     return await _context.SaveChangesAsync();
  31.   }
  32.   public async Task<List<WeatherForecast>?> GetAll()
  33.   {
  34.     return await _context.WeatherForecasts.ToListAsync();
  35.   }
  36.   public async Task<WeatherForecast?> GetById(int id)
  37.   {
  38.     return await _context.WeatherForecasts.FindAsync(id);
  39.   }
  40.   public async Task<int> Update(WeatherForecast entity)
  41.   {
  42.     var w = await GetById(entity.Id);
  43.     if (w != null)
  44.     {
  45.       _context.WeatherForecasts.Update(entity);
  46.     }
  47.     return await _context.SaveChangesAsync();
  48.   }
  49. }
复制代码
 4. 编写前端list代码


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

 编写展示数据的list页面,在Pages文件夹下建立Weather.razor文件
  1. @page "/weather"
  2. @rendermode InteractiveServer
  3. @inject WeatherForecastService weatherForecastService
  4. @inject NavigationManager nav
  5. <PageTitle>Weather</PageTitle>
  6. <h1>Weather</h1>
  7. <button class="btn btn-sm btn-outline-success" @onclick="BtnNew">New</button>
  8. <button class="btn btn-sm btn-danger" @onclick="BtnDeleteAll">Delete All</button>
  9. <button class="btn btn-sm btn-outline-info" @onclick="BtnGenerateRandomDate">Generate Random Date</button>
  10. @if (forecasts == null)
  11. {
  12.   <p><em>Loading...</em></p>
  13. }
  14. else
  15. {
  16.   <QuickGrid class="table" Items="forecasts.AsQueryable()">
  17.     <PropertyColumn Property="@(f=>f.Id)" />
  18.     <PropertyColumn Property="@(f=>f.Date)" />
  19.     <PropertyColumn Title="Temp.(C)" Property="@(f=>f.TemperatureC)" />
  20.     <PropertyColumn Title="Temp.(F)" Property="@(f=>f.TemperatureF)" />
  21.     <PropertyColumn Property="@(f=>f.Summary)" />
  22.     <TemplateColumn Context="f">
  23.       <button class="btn btn-sm btn-outline-info" @onclick="_=>BtnEdit(f.Id)">Edit</button>
  24.       <button class="btn btn-sm btn-outline-danger" @onclick="_=>BtnDelete(f.Id)">Delete</button>
  25.     </TemplateColumn>
  26.   </QuickGrid>
  27. }
  28. @code {
  29.   private List<WeatherForecast>? forecasts;
  30.   protected override async Task OnInitializedAsync()
  31.   {
  32.     forecasts = await weatherForecastService.AllForecast();
  33.   }
  34.   private void BtnNew()
  35.   {
  36.     nav.NavigateTo("/weather/add", true, true);
  37.   }
  38.   private void BtnEdit(int id)
  39.   {
  40.     nav.NavigateTo($"/weather/edit/{id}", true, true);
  41.   }
  42.   private async Task BtnDelete(int id)
  43.   {
  44.     await weatherForecastService.DeleteForecast(id);
  45.     nav.Refresh(true);
  46.   }
  47.   private async Task BtnDeleteAll()
  48.   {
  49.     await weatherForecastService.DeleteAllForecast();
  50.     nav.Refresh(true);
  51.   }
  52.   private async Task BtnGenerateRandomDate()
  53.   {
  54.     await weatherForecastService.GenerateRandom();
  55.     nav.Refresh(true);
  56.   }
  57. }
复制代码
注入了WeatherForecastService
需要注意页面上方的@rendermode InteractiveServer,这个标注将使得页面在服务端进行渲染,这是必不可少的,因为我们使用的是service,里面注入了repo,而repo中使用的是EF,这就意味着service的代码必须在服务端运行,所以这个页面必须在服务端渲染完毕后,再在前端展示。如果我们的service选择使用HttpClient获取后端api接口数据,则可以使用Wasm模式,就像Count.razor页面。
5. 编写Service

前端页面当需要使用数据时,将注入service,service如果需要向数据库请求数据,则在service中注入repo
编写WeatherForecastService.cs文件
  1. using QuickCRUD.Models;
  2. using QuickCRUD.Repos;
  3. namespace QuickCRUD.Services;
  4. public class WeatherForecastService
  5. {
  6.   private readonly WeatherForecastRepo _repo;
  7.   public WeatherForecastService(WeatherForecastRepo repo)
  8.   {
  9.     _repo = repo;
  10.   }
  11.   public async Task<WeatherForecast> GetById(int id)
  12.   {
  13.     var f = await _repo.GetById(id);
  14.     if (f == null) return new();
  15.     return f;
  16.   }
  17.   public async Task<List<WeatherForecast>> AllForecast()
  18.   {
  19.     var result = await _repo.GetAll();
  20.     if (result == null)
  21.     {
  22.       return [];
  23.     }
  24.     else
  25.     {
  26.       return result;
  27.     }
  28.   }
  29.   public async Task<int> NewForecast(WeatherForecast forecast)
  30.   {
  31.     if (forecast == null) return 0;
  32.     return await _repo.Add(forecast);
  33.   }
  34.   public async Task<int> UpdateForecast(WeatherForecast forecast)
  35.   {
  36.     if (forecast == null) { return 0; }
  37.     return await _repo.Update(forecast);
  38.   }
  39.   public async Task<int> DeleteForecast(int id)
  40.   {
  41.     return await _repo.DeleteById(id);
  42.   }
  43.   public async Task<int> DeleteAllForecast()
  44.   {
  45.     return await _repo.DeleteAll();
  46.   }
  47.   public async Task<int> GenerateRandom()
  48.   {
  49.     var startDate = DateOnly.FromDateTime(DateTime.Now);
  50.     var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
  51.     var forecasts = Enumerable.Range(1, 10).Select(index => new WeatherForecast
  52.     {
  53.       Date = startDate.AddDays(index),
  54.       TemperatureC = Random.Shared.Next(-20, 55),
  55.       Summary = summaries[Random.Shared.Next(summaries.Length)]
  56.     }).ToList();
  57.     foreach (var forecast in forecasts) await _repo.Add(forecast);
  58.     return forecasts.Count;
  59.   }
  60. }
复制代码
6. 编写add和edit子页面

新建WeatherAdd.razor文件
  1. @page "/weather/add"
  2. @rendermode InteractiveServer
  3. @inject WeatherForecastService weatherForecastService
  4. @inject NavigationManager nav
  5. <h1>New Weather Forecast</h1>
  6. <EditForm Model="forecast" OnValidSubmit="SubmitForecast">
  7.   <p>
  8.     <label>
  9.       Date:
  10.       <InputDate @bind-Value="forecast.Date" />
  11.     </label>
  12.   </p>
  13.   <p>
  14.     <label>
  15.       Temperature C:
  16.       <InputNumber @bind-Value="forecast.TemperatureC" />
  17.     </label>
  18.   </p>
  19.   <p>
  20.     <label>
  21.       Summary:
  22.       <InputText @bind-Value="forecast.Summary" />
  23.     </label>
  24.   </p>
  25.   <p>
  26.     <button type="submit" class="btn btn-primary">Submit</button>
  27.   </p>
  28. </EditForm>
  29. <button class="btn btn-outline-primary" @onclick="BtnCancel">Cancel</button>
  30. @code {
  31.   private WeatherForecast forecast { get; set; } = new() { Date = DateOnly.FromDateTime(DateTime.Today) };
  32.   private async Task SubmitForecast()
  33.   {
  34.     await weatherForecastService.NewForecast(forecast);
  35.     nav.NavigateTo("/weather", true, true);
  36.   }
  37.   private void BtnCancel()
  38.   {
  39.     nav.NavigateTo("/weather", true, true);
  40.   }
  41. }
复制代码
新建WeatherEdit.razor文件
  1. @page "/weather/edit/{id:int}"
  2. @rendermode InteractiveServer
  3. @inject WeatherForecastService weatherForecastService
  4. @inject NavigationManager nav
  5. <h1>Edit Weather Forecast</h1>
  6. <h3>Id: @Id</h3>
  7. <EditForm Model="forecast" OnValidSubmit="SubmitForecast">
  8.   <p>
  9.     <label>
  10.       Date:
  11.       <InputDate @bind-Value="forecast.Date" />
  12.     </label>
  13.   </p>
  14.   <p>
  15.     <label>
  16.       Temperature C:
  17.       <InputNumber @bind-Value="forecast.TemperatureC" />
  18.     </label>
  19.   </p>
  20.   <p>
  21.     <label>
  22.       Summary:
  23.       <InputText @bind-Value="forecast.Summary" />
  24.     </label>
  25.   </p>
  26.   <p>
  27.     <button type="submit" class="btn btn-primary">Submit</button>
  28.   </p>
  29. </EditForm>
  30. <button class="btn btn-outline-primary" @onclick="BtnCancel">Cancel</button>
  31. @code {
  32.   [Parameter]
  33.   public int Id { get; set; }
  34.   private WeatherForecast forecast { get; set; } = new();
  35.   protected override async Task OnParametersSetAsync()
  36.   {
  37.     forecast = await weatherForecastService.GetById(Id);
  38.   }
  39.   private void BtnCancel()
  40.   {
  41.     nav.NavigateTo("/weather", true, true);
  42.   }
  43.   private async Task SubmitForecast()
  44.   {
  45.     await weatherForecastService.UpdateForecast(forecast);
  46.     nav.NavigateTo("/weather", true, true);
  47.   }
  48. }
复制代码
edit页面与add页面的不同在于,需要传入id参数
7. 检查依赖注入

检查一下Program.cs文件中,是否将dbcontext,repo和service都配置了依赖注入
  1. builder.Services.AddDbContext<DefaultDbContext>();
  2. builder.Services.AddScoped<WeatherForecastRepo>();
  3. builder.Services.AddScoped<WeatherForecastService>();
复制代码
8. 效果展示


9. 发布


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

 

 最终生成
至此,最简单的CRUD完成了
10. 利用泛型的Repo

目前的Repo需要逐个编写操作数据库的方法,如果新增了一个model,则需要对应添加一个repo类,并再次重新编写所有的CRUD方法。但是因为都是CRUD的标准化方法,可以通过接口和泛型,实现新的model类继承全部CRUD方法。
首先编写一个接口,新建ICRUD.cs
  1. namespace QuickCRUD.Repos;
  2. public interface ICRUD<T, T_ID>
  3. {
  4.   public int GetCount();
  5.   public Task<List<T>?> GetAll();
  6.   public Task<List<T>?> GetLimit(int num);
  7.   public Task<T?> GetById(T_ID id);
  8.   public Task<int> Add(T entity);
  9.   public Task<int> Update(T entity, T_ID id);
  10.   public Task<int> DeleteById(T_ID id);
  11.   public Task<int> DeleteAll();
  12. }
复制代码
然后,编写一个抽象类,AbstractRepo.cs,再抽象类中,同泛型,实现全部接口
  1. using Microsoft.EntityFrameworkCore;
  2. namespace QuickCRUD.Repos;
  3. public abstract class AbstractRepo<T, T_ID>(DefaultDbContext context) : ICRUD<T, T_ID> where T : class
  4. {
  5.   public async Task<int> Add(T entity)
  6.   {
  7.     context.Set<T>().Add(entity);
  8.     return await context.SaveChangesAsync();
  9.   }
  10.   public async Task<int> DeleteAll()
  11.   {
  12.     context.Set<T>().RemoveRange(
  13.       context.Set<T>().Take(context.Set<T>().Count())
  14.     );
  15.     return await context.SaveChangesAsync();
  16.   }
  17.   public async Task<int> DeleteById(T_ID id)
  18.   {
  19.     var w = await GetById(id);
  20.     if (w != null)
  21.     {
  22.       context.Set<T>().Remove(w);
  23.     }
  24.     return await context.SaveChangesAsync();
  25.   }
  26.   public async Task<List<T>?> GetAll()
  27.   {
  28.     return await context.Set<T>().ToListAsync();
  29.   }
  30.   public async Task<T?> GetById(T_ID id)
  31.   {
  32.     return await context.Set<T>().FindAsync(id);
  33.   }
  34.   public int GetCount()
  35.   {
  36.     return context.Set<T>().Count();
  37.   }
  38.   public async Task<List<T>?> GetLimit(int num)
  39.   {
  40.     var result = context.Set<T>().Take(num);
  41.     return await result.ToListAsync();
  42.   }
  43.   public async Task<int> Update(T entity, T_ID id)
  44.   {
  45.     var w = await GetById(id);
  46.     if (w != null)
  47.     {
  48.       context.Set<T>().Update(entity);
  49.     }
  50.     return await context.SaveChangesAsync();
  51.   }
  52. }
复制代码
最后,修改WeatherForcastRepo.cs
  1. using QuickCRUD.Models;
  2. namespace QuickCRUD.Repos;
  3. public class WeatherForecastRepo(DefaultDbContext context) : AbstractRepo<WeatherForecast, int>(context)
  4. {
  5. }
复制代码
WeatherForcastRepo只需要继承抽象类,即可实现全部CRUD接口方法。如果有个性化的数据库操作方法,再在repo中添加方法即可。
如果有新的model,只需要创建一个新的repo,并继承AbstractRepo即可实现全部CRUD方法。

来源:https://www.cnblogs.com/jimokelly/p/18142830
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具