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