簡介
《Asp.Net Core3 + Vue3入坑教程》 此教程適合新手入門或者前後端分離嘗試者。可以根據圖文一步一步進操作編碼也可以選擇直接檢視原始碼。每一篇文章都有對應的原始碼
教程後期會將 .Net Core 3升級成 .Net Core 5
目錄
《Asp.Net Core3 + Vue3入坑教程》系列教程目錄
Asp.Net Core後端專案
- 後端專案搭建與Swagger配置步驟
- 配置CROS策略解決跨域問題
- (本文)AutoMapper & Restful API & DI
- (暫未發表敬請期待...)EF Core & Postgresql
- (暫未發表敬請期待...).Net Core 3升級成 .Net Core 5
- (暫未發表敬請期待...)JWT
Vue3 前端專案
暫未發表敬請期待...
本文簡介
本文為《Asp.Net Core3 + Vue3入坑教程》系列教程的後端第三篇 - AutoMapper & Restful API & DI。本文將利用AutoMapper與依賴注入等內容實現一個簡單的Restful API。
實現一個簡單的Restful API
引入NewtonsoftJson3.1.12版本的Nuget包
當前專案使用的SKD是 .net core 3後續將SDK升級之後再升級此Nuget包的版本
配置Startup.cs
程式碼如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;
using Simple_Asp.Net_Core.Data;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
namespace Simple_Asp.Net_Core
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddCORS();
services.AddMvc();
services.AddSwagger();
services.AddControllers().AddNewtonsoftJson(s =>
{
s.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
});
}
app.UseCors("CorsTest");
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}
新建資料夾Models,新建實體Command.cs
程式碼如下:
using System.ComponentModel.DataAnnotations;
namespace Simple_Asp.Net_Core.Models
{
public class Command
{
[Key]
[Required]
public int Id { get; set; }
[Required]
[MaxLength(250)]
public string HowTo { get; set; }
[Required]
public string Line { get; set; }
[Required]
public string Platform { get; set; }
}
}
新建Data資料夾 新建 ICommanderRepo 倉儲層介面,用來定義與資料庫互動的介面
using Simple_Asp.Net_Core.Models;
using System.Collections.Generic;
namespace Simple_Asp.Net_Core.Data
{
public interface ICommanderRepo
{
IEnumerable<Command> GetAllCommands();
}
}
現在我們還沒有資料庫,就先模擬資料返回!新建MockCommanderRepo.cs來實現ICommanderRepo介面
using Simple_Asp.Net_Core.Models;
using System.Collections.Generic;
namespace Simple_Asp.Net_Core.Data
{
public class MockCommanderRepo : ICommanderRepo
{
public IEnumerable<Command> GetAllCommands()
{
var commands = new List<Command>
{
new Command{Id=0, HowTo="Boil an egg", Line="Boil water", Platform="Kettle & Pan"},
new Command{Id=1, HowTo="Cut bread", Line="Get a knife", Platform="knife & chopping board"},
new Command{Id=2, HowTo="Make cup of tea", Line="Place teabag in cup", Platform="Kettle & cup"}
};
return commands;
}
}
}
新建Dtos資料夾,新建類CommandReadDto.cs
上一步模擬實現了從資料庫返回Command實體,但是在返回給前端的時候不能直接返回實體,而是需要轉換成Dto。根據不同的業務場景需要建立不同的Dto
namespace Simple_Asp.Net_Core.Dtos
{
public class CommandReadDto
{
public int Id { get; set; }
public string HowTo { get; set; }
public string Line { get; set; }
}
}
實現ICommanderRepo的依賴注入
生命週期是依賴注入裡非常重要的內容,具體可以參照官方的文件
https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes
這裡使用的註冊方式是AddScoped,讓每一次請求都會建立一個例項
For web applications, a scoped lifetime indicates that services are created once per client request (connection). Register scoped services with AddScoped.
Startup.cs程式碼調整成如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;
using Simple_Asp.Net_Core.Data;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
namespace Simple_Asp.Net_Core
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddCORS();
services.AddMvc();
services.AddSwagger();
services.AddScoped<ICommanderRepo, MockCommanderRepo>();
services.AddControllers().AddNewtonsoftJson(s =>
{
s.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
});
}
app.UseCors("CorsTest");
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}
在編寫Restful API之前還差最後一步,AutoMapper的使用
前面已經建立了Command實體與CommandReadDto Dto,現在我們要讓這Commond實體能夠自動轉換成CommandReadDto Dto
AutoMapper引入Nuget包
再一次配置Startup.cs
程式碼如下:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json.Serialization;
using Simple_Asp.Net_Core.Data;
using Simple_Asp.Net_Core.ServiceProvider;
using System;
namespace Simple_Asp.Net_Core
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddCORS();
services.AddMvc();
services.AddSwagger();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddScoped<ICommanderRepo, MockCommanderRepo>();
services.AddControllers().AddNewtonsoftJson(s =>
{
s.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1");
});
}
app.UseCors("CorsTest");
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
}
新建Profiles資料夾,新建CommandsProfile.cs AutoMpper對映配置類
程式碼如下:
using AutoMapper;
using Simple_Asp.Net_Core.Dtos;
using Simple_Asp.Net_Core.Models;
namespace Simple_Asp.Net_Core.Profiles
{
public class CommandsProfile : Profile
{
public CommandsProfile()
{
//Source -> Target
CreateMap<Command, CommandReadDto>();
}
}
}
在Controllers資料夾下新建控制器CommandsController.cs
將介面注入至 CommandsController 建構函式中
private readonly ICommanderRepo _repository;
private readonly IMapper _mapper;
public CommandsController(ICommanderRepo repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
這時候我們就可以實現 Commands 中 api/commands 請求
//GET api/commands
[HttpGet]
public ActionResult<IEnumerable<CommandReadDto>> GetAllCommmands()
{
var commandItems = _repository.GetAllCommands();
return Ok(_mapper.Map<IEnumerable<CommandReadDto>>(commandItems));
}
除錯專案使用swagger呼叫api/commands介面,後端能夠成功返回資料!
接下來就完成剩下的幾種請求
在Dtos資料夾下新建CommandUpdateDto.cs 與 CommandCreateDto.cs
程式碼如下:
using System.ComponentModel.DataAnnotations;
namespace Simple_Asp.Net_Core.Dtos
{
public class CommandCreateDto
{
[Required]
[MaxLength(250)]
public string HowTo { get; set; }
[Required]
public string Line { get; set; }
[Required]
public string Platform { get; set; }
}
}
using System.ComponentModel.DataAnnotations;
namespace Simple_Asp.Net_Core.Dtos
{
public class CommandUpdateDto
{
[Required]
[MaxLength(250)]
public string HowTo { get; set; }
[Required]
public string Line { get; set; }
[Required]
public string Platform { get; set; }
}
}
修改CommandsProfile.cs
程式碼如下:
using AutoMapper;
using Simple_Asp.Net_Core.Dtos;
using Simple_Asp.Net_Core.Models;
namespace Simple_Asp.Net_Core.Profiles
{
public class CommandsProfile : Profile
{
public CommandsProfile()
{
//Source -> Target
CreateMap<Command, CommandReadDto>();
CreateMap<CommandCreateDto, Command>();
CreateMap<CommandUpdateDto, Command>();
CreateMap<Command, CommandUpdateDto>();
}
}
}
修改ICommanderRepo.cs
程式碼如下:
using Simple_Asp.Net_Core.Models;
using System.Collections.Generic;
namespace Simple_Asp.Net_Core.Data
{
public interface ICommanderRepo
{
bool SaveChanges();
IEnumerable<Command> GetAllCommands();
Command GetCommandById(int id);
void CreateCommand(Command cmd);
void UpdateCommand(Command cmd);
void DeleteCommand(Command cmd);
}
}
修改MockCommanderRepo.cs
模擬倉儲層我們就不做過多的實現,在下一章內容會與資料庫Postgresql進行對接,到時候再實現!
程式碼如下:
using Simple_Asp.Net_Core.Models;
using System.Collections.Generic;
namespace Simple_Asp.Net_Core.Data
{
public class MockCommanderRepo : ICommanderRepo
{
public void CreateCommand(Command cmd)
{
throw new System.NotImplementedException();
}
public void DeleteCommand(Command cmd)
{
throw new System.NotImplementedException();
}
public IEnumerable<Command> GetAllCommands()
{
var commands = new List<Command>
{
new Command{Id=0, HowTo="Boil an egg", Line="Boil water", Platform="Kettle & Pan"},
new Command{Id=1, HowTo="Cut bread", Line="Get a knife", Platform="knife & chopping board"},
new Command{Id=2, HowTo="Make cup of tea", Line="Place teabag in cup", Platform="Kettle & cup"}
};
return commands;
}
public Command GetCommandById(int id)
{
return new Command { Id = 0, HowTo = "Boil an egg", Line = "Boil water", Platform = "Kettle & Pan" };
}
public bool SaveChanges()
{
throw new System.NotImplementedException();
}
public void UpdateCommand(Command cmd)
{
throw new System.NotImplementedException();
}
}
}
修改CommandsController.cs
程式碼如下:
using AutoMapper;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Simple_Asp.Net_Core.Data;
using Simple_Asp.Net_Core.Dtos;
using Simple_Asp.Net_Core.Models;
using System.Collections.Generic;
namespace Simple_Asp.Net_Core.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class CommandsController : ControllerBase
{
private readonly ICommanderRepo _repository;
private readonly IMapper _mapper;
public CommandsController(ICommanderRepo repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
//GET api/commands
[HttpGet]
public ActionResult<IEnumerable<CommandReadDto>> GetAllCommmands()
{
var commandItems = _repository.GetAllCommands();
return Ok(_mapper.Map<IEnumerable<CommandReadDto>>(commandItems));
}
//GET api/commands/{id}
[HttpGet("{id}", Name = "GetCommandById")]
public ActionResult<CommandReadDto> GetCommandById(int id)
{
var commandItem = _repository.GetCommandById(id);
if (commandItem != null)
{
return Ok(_mapper.Map<CommandReadDto>(commandItem));
}
return NotFound();
}
//POST api/commands
[HttpPost]
public ActionResult<CommandReadDto> CreateCommand(CommandCreateDto commandCreateDto)
{
var commandModel = _mapper.Map<Command>(commandCreateDto);
_repository.CreateCommand(commandModel);
_repository.SaveChanges();
var commandReadDto = _mapper.Map<CommandReadDto>(commandModel);
return CreatedAtRoute(nameof(GetCommandById), new { Id = commandReadDto.Id }, commandReadDto);
}
//PUT api/commands/{id}
[HttpPut("{id}")]
public ActionResult UpdateCommand(int id, CommandUpdateDto commandUpdateDto)
{
var commandModelFromRepo = _repository.GetCommandById(id);
if (commandModelFromRepo == null)
{
return NotFound();
}
_mapper.Map(commandUpdateDto, commandModelFromRepo);
_repository.UpdateCommand(commandModelFromRepo);
_repository.SaveChanges();
return NoContent();
}
//PATCH api/commands/{id}
[HttpPatch("{id}")]
public ActionResult PartialCommandUpdate(int id, JsonPatchDocument<CommandUpdateDto> patchDoc)
{
var commandModelFromRepo = _repository.GetCommandById(id);
if (commandModelFromRepo == null)
{
return NotFound();
}
var commandToPatch = _mapper.Map<CommandUpdateDto>(commandModelFromRepo);
patchDoc.ApplyTo(commandToPatch);
if (!TryValidateModel(commandToPatch))
{
return ValidationProblem(ModelState);
}
_mapper.Map(commandToPatch, commandModelFromRepo);
_repository.UpdateCommand(commandModelFromRepo);
_repository.SaveChanges();
return NoContent();
}
//DELETE api/commands/{id}
[HttpDelete("{id}")]
public ActionResult DeleteCommand(int id)
{
var commandModelFromRepo = _repository.GetCommandById(id);
if (commandModelFromRepo == null)
{
return NotFound();
}
_repository.DeleteCommand(commandModelFromRepo);
_repository.SaveChanges();
return NoContent();
}
}
}
實現HttpPatch的時候需要引入JsonPath Nuget包,可以直接如圖所示直接引入,也可以使用Nuget包管理介面進行引入
除錯專案,可以看到Restful API 已開發完成!
重點說明 HttpPatch 請求
PUT 和 PATCH 方法用於更新現有資源。 它們之間的區別是,PUT會替換整個資源,而PATCH 僅指定更改。
接來下我們用swagger來驗證
引數如下:
[
{
"op":"add",
"path":"/Line",
"value":"Barry"
}
]
除錯後端程式碼,可以看到值已經被我們正確修改了 !
更多Patch語法說明可以參考
https://docs.microsoft.com/zh-cn/aspnet/core/web-api/jsonpatch?view=aspnetcore-5.0
總結
本文主要對Simple專案使用了AutoMapper與依賴注入等內容實現了簡單的Restful API開發。在實際開發過程中需要根據不同的業務場景需要建立不同的Dto,不要因為偷懶讓相近的業務功能使用相同的Dto,這樣會讓後續的程式碼維護成本變得更大!
目前針對AutoMpper的使用並不是非常的便捷,後續可以考慮進行提升。依賴注入使用的是自帶的方式實現,後續可以結合第三方元件實現依賴注入
文中提到生命週期是依賴注入裡非常重要的內容,在實際開發過程中要根據具體的業務情況使用正確的生命週期!
GitHub原始碼
注意:原始碼除錯過程中如果出現xml檔案路徑錯誤,需要參照第一章(後端專案搭建與Swagger配置步驟)Swagger配置“配置XML 文件檔案”步驟,取消勾選然後再選中 ,將XML路徑設定成與你的電腦路徑匹配!
參考資料
官網文件-依賴注入生命週期(推薦學習) https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes
Restful API 案例來源 https://www.youtube.com/watch?v=fmvcAzHpsk8
微軟官方文件 https://docs.microsoft.com/zh-cn/aspnet/core/?view=aspnetcore-5.0
DTO理解 https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff649585(v=pandp.10)?redirectedfrom=MSDN
官網文件-Patch請求詳解 https://docs.microsoft.com/zh-cn/aspnet/core/web-api/jsonpatch?view=aspnetcore-5.0