《Asp.Net Core3 + Vue3入坑教程》 - 3.AutoMapper & Restful API & DI

Iannnnnnnnnnnnn發表於2021-03-01

簡介

《Asp.Net Core3 + Vue3入坑教程》 此教程適合新手入門或者前後端分離嘗試者。可以根據圖文一步一步進操作編碼也可以選擇直接檢視原始碼。每一篇文章都有對應的原始碼

教程後期會將 .Net Core 3升級成 .Net Core 5

目錄

《Asp.Net Core3 + Vue3入坑教程》系列教程目錄

Asp.Net Core後端專案

  1. 後端專案搭建與Swagger配置步驟
  2. 配置CROS策略解決跨域問題
  3. (本文)AutoMapper & Restful API & DI
  4. (暫未發表敬請期待...)EF Core & Postgresql
  5. (暫未發表敬請期待...).Net Core 3升級成 .Net Core 5
  6. (暫未發表敬請期待...)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://github.com/Impartsoft/Simple_Asp.Net_Core/tree/master/Simple_Asp.Net_Core 3.AutoMapper %26 Restful API

參考資料

官網文件-依賴注入生命週期(推薦學習) 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

相關文章