使用Blazor構建CRUD項目

来源:https://www.cnblogs.com/jimokelly/p/18142830
-Advertisement-
Play Games

在小公司中,往往沒有一個前後端分離的大型團隊,去各司其職的負責構建web應用程式。面對比較簡單的需求,可能所謂團隊只有一個人,既要開發前端又要開發後端。 如果能有一項技術,能夠前後端通吃,並且具備非常高的開發效率,那就非常適合小公司的小型項目的小型甚至一人團隊來使用了。 aspdotnet就是這樣高 ...


在小公司中,往往沒有一個前後端分離的大型團隊,去各司其職的負責構建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方法。

本文來自博客園,作者:HalloHerö,轉載請註明原文鏈接:https://www.cnblogs.com/jimokelly/p/18142830


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 亮數據,適合大模型數據準備的可視化高效率數據採集工具。 一、大模型訓練需要數據 大模型數據處理的流程,分為數據採集、數據清洗、數據評估和指令數據標註四個主要階段,而這些階段中最重要的就是數據採集階段,需要海量的數據才能讓大模型涌現智能。 訪問點擊: 亮數據加速數據採集。 數據採集 涉及多種數據源,包 ...
  • 相關參考 https://leejjon.medium.com/how-to-allow-cross-origin-requests-in-a-jax-rs-microservice-d2a6aa2df484 https://stackoverflow.com/questions/28065963/ ...
  • 配置 NGINX 和 NGINX Plus 作為 Web 伺服器 設置虛擬伺服器 在 NGINX Plus 配置文件中,必須包含至少一個 server 指令來定義一個虛擬伺服器。 當 NGINX Plus 處理請求時,首先選擇將服務於該請求的虛擬伺服器。 http { server { # 伺服器配 ...
  • title: Django與前端框架協作開發實戰:高效構建現代Web應用 date: 2024/5/22 20:07:47 updated: 2024/5/22 20:07:47 categories: 後端開發 tags: DjangoREST 前端框架 SSR渲染 SPA路由 SEO優化 組件庫 ...
  • 在使用Wrapper構建條件時,經常因為需要構建的條件過多需要寫半個多小時,還容易粗心寫錯欄位,所以就想搞個可以直接自動構建QueryWrapper的工具類。 ...
  • 今天使用Thinkphp5做非同步任務傳遞where參數時遇到一個問題: 有一段如下代碼: $where['jst.supplier'] = ['exp', Db::raw('>0 or jst.is_supplier=1')]; 在使用swoole做非同步任務時需要把where參數傳遞給非同步任務處理, ...
  • 前言 市面上關於認證授權的框架已經比較豐富了,大都是關於單體應用的認證授權,在分散式架構下,使用比較多的方案是--<應用網關>,網關里集中認證,將認證通過的請求再轉發給代理的服務,這種中心化的方式並不適用於微服務,這裡討論另一種方案--<認證中心>,利用jwt去中心化的特性,減輕認證中心的壓力,有理 ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
一周排行
    -Advertisement-
    Play Games
  • 問題 有很多應用程式在驗證JSON數據的時候用到了JSON Schema。 在微服務架構下,有時候各個微服務由於各種歷史原因,它們所生成的數據對JSON Object屬性名的大小寫規則可能並不統一,它們需要消費的JSON數據的屬性名可能需要大小寫無關。 遺憾的是,目前的JSON Schema沒有這方 ...
  • 首先下載centos07鏡像,建議使用阿裡雲推薦的地址: https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spm=a2c6h.25603864.0.0.59b5f5ad5Nfr0X 其實這裡就已經出現第一個坑了 centos 07 /u ...
  • 相信很多.NETer看了標題,都會忍不住好奇,點進來看看,並且順便準備要噴作者! 這裡,首先要申明一下,作者本人也非常喜歡Linq,也在各個項目中常用Linq。 我愛Linq,Linq優雅萬歲!!!(PS:順便吐槽一下,隔壁Java從8.0版本推出的Streams API,抄了個四不像,一點都不優雅 ...
  • 在人生的重要時刻,我站在了畢業的門檻上,望著前方的道路,心中涌動著對未來的無限憧憬與些許忐忑。面前,兩條道路蜿蜒伸展:一是繼續在職場中尋求穩定,一是勇敢地走出一條屬於自己的創新之路。儘管面臨年齡和現實的挑戰,我仍舊選擇勇往直前,用技術這把鑰匙,開啟新的人生篇章。 迴首過去,我深知時間寶貴,精力有限。 ...
  • 單元測試 前言 時隔多個月,終於抽空學習了點新知識,那麼這次來記錄一下C#怎麼進行單元測試,單元測試是做什麼的。 我相信大部分剛畢業的都很疑惑單元測試是乾什麼的?在小廠實習了6個月後,我發現每天除了寫CRUD就是寫CRUD,幾乎用不到單元測試。寫完一個功能直接上手去測,當然這隻是我個人感受,僅供參考 ...
  • 一:背景 1. 講故事 最近在分析dump時,發現有程式的卡死和WeakReference有關,在以前只知道怎麼用,但不清楚底層邏輯走向是什麼樣的,藉著這個dump的契機來簡單研究下。 二:弱引用的玩法 1. 一些基礎概念 用過WeakReference的朋友都知道這裡面又可以分為弱短和弱長兩個概念 ...
  • 最近想把ET打表工具的報錯提示直接調用win系統彈窗,好讓策劃明顯的知道表格哪裡填錯數據,彈窗需要調用System.Windows.Forms庫。操作如下: 需要在 .csproj 文件中添加: <UseWindowsForms>true</UseWindowsForms> 須將目標平臺設置為 Wi ...
  • 從C#3開始,拓展方法這一特性就得到了廣泛的應用。 此功能允許你能夠使用實例方法的語法調用某個靜態方法,以下是一個獲取/創建文件的靜態方法: public static async Task<StorageFile> GetOrCreateFileAsync(this StorageFolder f ...
  • 在Windows 11下,使用WinUI2.6以上版本的ListView長這樣: 然而到了Win10上,儘管其他控制項的樣式沒有改變,但ListViewItem變成了預設樣式(初代Fluent) 最重大的問題是,Win10上的HorizontalAlignment未被設置成Stretch,可能造成嚴重 ...
  • 前言 周六在公司加班,幹完活後越顯無聊,想著下載RabbiitMQ做個小項目玩玩。然而這一下就下載了2個小時,真讓人頭痛。 簡單的講一下如何安裝吧,網上教程和踩坑文章還是很多的,我講我感覺有用的文章放在本文末尾。 安裝地址 erlang 下載 - Erlang/OTP https://www.erl ...