使用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
  • 一個自定義WPF窗體的解決方案,借鑒了呂毅老師的WPF製作高性能的透明背景的異形視窗一文,併在此基礎上增加了滑鼠穿透的功能。可以使得透明窗體的滑鼠事件穿透到下層,在下層窗體中響應。 ...
  • 在C#中使用RabbitMQ做個簡單的發送郵件小項目 前言 好久沒有做項目了,這次做一個發送郵件的小項目。發郵件是一個比較耗時的操作,之前在我的個人博客裡面回覆評論和友鏈申請是會通過發送郵件來通知對方的,不過當時只是簡單的進行了非同步操作。 那麼這次來使用RabbitMQ去統一發送郵件,我的想法是通過 ...
  • 當你使用Edge等瀏覽器或系統軟體播放媒體時,Windows控制中心就會出現相應的媒體信息以及控制播放的功能,如圖。 SMTC (SystemMediaTransportControls) 是一個Windows App SDK (舊為UWP) 中提供的一個API,用於與系統媒體交互。接入SMTC的好 ...
  • 最近在微軟商店,官方上架了新款Win11風格的WPF版UI框架【WPF Gallery Preview 1.0.0.0】,這款應用引入了前沿的Fluent Design UI設計,為用戶帶來全新的視覺體驗。 ...
  • 1.簡單使用實例 1.1 添加log4net.dll的引用。 在NuGet程式包中搜索log4net並添加,此次我所用版本為2.0.17。如下圖: 1.2 添加配置文件 右鍵項目,添加新建項,搜索選擇應用程式配置文件,命名為log4net.config,步驟如下圖: 1.2.1 log4net.co ...
  • 之前也分享過 Swashbuckle.AspNetCore 的使用,不過版本比較老了,本次演示用的示例版本為 .net core 8.0,從安裝使用開始,到根據命名空間分組顯示,十分的有用 ...
  • 在 Visual Studio 中,至少可以創建三種不同類型的類庫: 類庫(.NET Framework) 類庫(.NET 標準) 類庫 (.NET Core) 雖然第一種是我們多年來一直在使用的,但一直感到困惑的一個主要問題是何時使用 .NET Standard 和 .NET Core 類庫類型。 ...
  • WPF的按鈕提供了Template模板,可以通過修改Template模板中的內容對按鈕的樣式進行自定義。結合資源字典,可以將自定義資源在xaml視窗、自定義控制項或者整個App當中調用 ...
  • 實現了一個支持長短按得按鈕組件,單擊可以觸發Click事件,長按可以觸發LongPressed事件,長按鬆開時觸發LongClick事件。還可以和自定義外觀相結合,實現自定義的按鈕外形。 ...
  • 一、WTM是什麼 WalkingTec.Mvvm框架(簡稱WTM)最早開發與2013年,基於Asp.net MVC3 和 最早的Entity Framework, 當初主要是為瞭解決公司內部開發效率低,代碼風格不統一的問題。2017年9月,將代碼移植到了.Net Core上,併進行了深度優化和重構, ...