使用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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...