{
//services.AddMvc(); core 3.0以前是這樣寫(xiě)的,這個(gè)服務(wù)包括了 TageHelper等 WebApi不需要的東西,所有3.0以后可以不這樣寫(xiě)
services.AddControllers();
}

注意配置中間件的區(qū)域管道順序不能隨意改動(dòng)。

管道就是客戶(hù)端通過(guò)一些指令指向服務(wù)器端,在這個(gè)過(guò)程中呢,會(huì)經(jīng)過(guò)一些手動(dòng)配置的中間件,比如說(shuō)路由中間件、靜態(tài)資源中間件等,從客戶(hù)端出發(fā)到服務(wù)器端,將數(shù)據(jù)處理后,再由服務(wù)器端原路返回到客戶(hù)端這樣的一個(gè)過(guò)程。但是在請(qǐng)求的過(guò)程中也不排除中間件出現(xiàn)短路的情況,這樣也就不會(huì)進(jìn)入到第二個(gè)中間件了,而是直接返回到客戶(hù)端。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

API對(duì)外合約:API消費(fèi)者需要使用到三個(gè)概念

API對(duì)外提供統(tǒng)一資源接口,業(yè)界對(duì)RESTful資源命名也有規(guī)則

關(guān)于RESTful API約束

使用名詞而不是動(dòng)詞

要體現(xiàn)資源的結(jié)構(gòu)/關(guān)系

通過(guò)id獲取單個(gè)用戶(hù)應(yīng)該是:api/user/{userId},而不是 api/user/users。這樣寫(xiě)就是讓API具有很好的可讀性和可預(yù)測(cè)性

需求案例1

系統(tǒng)存在兩個(gè)資源:Company(公司)、Employee(員工),現(xiàn)在需要獲取某個(gè)公司下的所有員工

分析:應(yīng)該使用HTTP GET。API在設(shè)計(jì)的時(shí)候需要體現(xiàn)公司與員工的一個(gè)包含關(guān)系

常見(jiàn)錯(cuò)誤做法:api/employees,api/employee/{companyId} 。這兩個(gè)URI都沒(méi)有體現(xiàn)公司和員工的一個(gè)包含關(guān)系

建議做法:api/companies/{companyId}/employees

需求案例2

需要獲取某個(gè)公司下的某個(gè)員工

常見(jiàn)錯(cuò)誤做法:api/employees/{employeeId}

建議做法:api/companies/{companyId}/employees/{employeeId}

自定義查詢(xún)?cè)趺疵?/strong>

需求:獲取所有用戶(hù)信息,并且按年齡從大到小排序

常見(jiàn)錯(cuò)誤做法:api/user/orderby/age

建議做法:api/user?orderby=age (通過(guò)QueryString查詢(xún)字符串,多條件使用 & 符號(hào))

HTTP狀態(tài)碼

請(qǐng)求是否成功?如果請(qǐng)求失敗了,誰(shuí)來(lái)為此負(fù)責(zé)

2xx 開(kāi)頭狀態(tài)碼

3xx 開(kāi)頭狀態(tài)碼

用于跳轉(zhuǎn)。例如告訴瀏覽器搜索引擎,某個(gè)頁(yè)面的網(wǎng)址已經(jīng)永久改變,絕大多數(shù)的WebApi都不需要這類(lèi)的狀態(tài)碼

4xx 開(kāi)頭:客戶(hù)端錯(cuò)誤

5xx 開(kāi)頭狀態(tài)碼

500 – Internal serever error,表示服務(wù)器出現(xiàn)了錯(cuò)誤,客戶(hù)端無(wú)能為力,只能以后再試試

還有就是RESTful API 返回的結(jié)果不一定Json格式的

關(guān)于如何標(biāo)注路由屬性 uri ?

先看控制器代碼:

using Microsoft.AspNetCore.Mvc;
using Routine.Api.Service;
using System;
using System.Threading.Tasks;
namespace Routine.Api.Controllers
{
[ApiController] //好處:ApiController不是強(qiáng)制的
//1.會(huì)啟用使用屬性路由(Attribute Routing)
//2.自動(dòng)HTTP 400響應(yīng)
//3.推斷參數(shù)的綁定源
//4.Multipart/form-data 請(qǐng)求推斷
//5.錯(cuò)誤狀態(tài)代碼的問(wèn)題詳細(xì)信息
[Route("api/companies")] //寫(xiě)法一
//[Route("api/[controller]")] //寫(xiě)法二:意思是相當(dāng)于刨除了Controller后綴,獲取前面的 Companies C可以是小寫(xiě),如果你改名了那么你路由的uri也跟著變了(不建議這樣寫(xiě))
public class CompaniesController:ControllerBase
{
private readonly ICompanyRepository _companyRepository;
public CompaniesController(ICompanyRepository companyRepository)
{
_companyRepository = companyRepository ??
throw new ArgumentNullException(nameof(companyRepository));
}
[HttpGet]
//IActionResult定義了一些合約,它可以代表ActionResult返回的結(jié)果
public async Task<IActionResult> GetCompanies()
{
var companies =await _companyRepository.GetCompaniesAsync();//讀取出來(lái)的是List
return Ok(companies);
}
[HttpGet("{companyId}")] // Controller標(biāo)注了ApiController => uri=> api/companies/{companyId}
public async Task<IActionResult> GetCompany(Guid companyId)
{
//判斷該公司是否存在方法一:這種方法在處理并發(fā)請(qǐng)求時(shí)可能會(huì)出現(xiàn)錯(cuò)誤,原因是查到之后,進(jìn)行刪除,進(jìn)入company后也可能是404找不到了
//var exists =await _companyRepository.CompanyExistsAsync(compamyId);
//if (!exists)
//{
// //不存在應(yīng)該返回404
// return NotFound();
//}
var company = await _companyRepository.GetCompanyAsync(companyId);//讀取出來(lái)的是List
//方法二
if (company==null)
{
return NotFound();
}
return Ok(company);
}
}
}

為了更好的構(gòu)建RESTful API 對(duì)于 uri 的設(shè)計(jì)規(guī)則也有很?chē)?yán)格的要求。

在控制器標(biāo)注 ApiController,它會(huì)自動(dòng)啟用路由屬性

通過(guò) [Route] 設(shè)計(jì)路由規(guī)則

比如:接口一:GetCompanies,請(qǐng)求的方式:GET,通過(guò)Route 去設(shè)置路由規(guī)則 [Router(“api/companies”)],即查詢(xún)所有公司信息

[Router(“api/companies”)] => api/companies

接口二:GetCompany,請(qǐng)求方式:GET,只不過(guò)在 添加了 [HTTPGET(”{companyId}”)] =>api/companies/{companyId},即查詢(xún)某一公司的信息

關(guān)于第二種路由寫(xiě)法請(qǐng)看注釋

通過(guò)Postman工具測(cè)試一下

測(cè)試一:接口一

測(cè)試二:接口二

以上兩個(gè)接口測(cè)試完畢!!!

對(duì)于ASP.NET Core 3.x以前對(duì)于 404 NotFound請(qǐng)求狀態(tài)碼輸出的格式不太友好,而ASP.NET Core 3.x對(duì)于404請(qǐng)求狀態(tài)碼也做了友好的提示。

現(xiàn)在將接口偽造錯(cuò)誤信息,提示 404 如圖:

關(guān)于構(gòu)建 RESTful API 存在的內(nèi)容協(xié)商

所謂內(nèi)容協(xié)商就是這樣一個(gè)過(guò)程,針對(duì)一個(gè)響應(yīng),當(dāng)有多種表述格式可用時(shí),選取最佳的一種表述格式,這些表述可以是XML,JSON,甚至是自定義的格式規(guī)則

Accept Header:負(fù)責(zé)指定輸出類(lèi)型

Media Type(媒體類(lèi)型)

404 Not Acceptable

輸出格式:ASP.NET Core 里面對(duì)應(yīng)的就是 Output Formatters 我們稱(chēng)為:輸出類(lèi)型的格式化器

也就是說(shuō)如果一個(gè)API消費(fèi)者,設(shè)置了Accept Header的媒體類(lèi)型為Json,那么這個(gè)RESTful API也應(yīng)該返回的是JSON,

但是呢如果服務(wù)器只接收XML的格式,這個(gè)時(shí)候請(qǐng)求的媒體類(lèi)型不被服務(wù)器所接受,那么就會(huì)返回 406 這個(gè)狀態(tài)碼

總而言之,盡量避免不寫(xiě)Accept Header,避免客戶(hù)端和服務(wù)器端接收和返回的類(lèi)型不一致導(dǎo)致錯(cuò)誤。

有輸出那么就會(huì)有輸入了!!!

Content-Type-Header:負(fù)責(zé)指定輸入

Media Type(媒體類(lèi)型)

輸出格式:ASP.NET Core里面對(duì)應(yīng)的就是 Input Formatters

比如說(shuō):對(duì)于一個(gè)客戶(hù)端的POST請(qǐng)求,即添加資源信息,那么就需要輸入?yún)?shù),這些參數(shù)可能是放在Body里面,那么在Body里面的這些參數(shù)可能是對(duì)象的那種格式。

那么我們就需要通過(guò) Content-Type-Header來(lái)確定Body里面的參數(shù)是什么樣的類(lèi)型,可能是Json也可能是Xml或者是自定義的格式,指明之后,RESTful API才能更好的對(duì)這些參數(shù)進(jìn)行處理。

看Startup類(lèi)代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Routine.Api.Data;
using Routine.Api.Service;
namespace Routine.Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//services.AddMvc(); core 3.0以前是這樣寫(xiě)的,這個(gè)服務(wù)包括了TageHelper等 WebApi不需要的東西,所有3.0以后可以不這樣寫(xiě)
services.AddControllers(setup =>
{
//setup.ReturnHttpNotAcceptable=false;//如果客戶(hù)端默認(rèn)為xml格式,服務(wù)器端為json,false就不會(huì)返回406
setup.ReturnHttpNotAcceptable = true;//如果請(qǐng)求的類(lèi)型和服務(wù)器請(qǐng)求的類(lèi)型不一致就返回406
//setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
//setup.OutputFormatters.Insert(0, new XmlDataContractSerializerOutputFormatter());
}).AddXmlDataContractSerializerFormatters();
//配置接口服務(wù):涉及到這個(gè)服務(wù)注冊(cè)的生命周期這里采用AddScoped,表示每次的Http請(qǐng)求
services.AddScoped<ICompanyRepository, CompanyRepository>();
//獲取配置文件中的數(shù)據(jù)庫(kù)字符串連接
var sqlConnection = Configuration.GetConnectionString("SqlServerConnection");
//配置上下文類(lèi)DbContext,因?yàn)樗旧硪彩且惶追?wù)
services.AddDbContext<RoutineDbContext>(options =>
{
options.UseSqlServer(sqlConnection);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

setup.ReturnHttpNotAcceptable就是處理是在客戶(hù)端與服務(wù)器端數(shù)據(jù)產(chǎn)生沖突時(shí),是否要即將產(chǎn)生 406 的狀態(tài)碼。

setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter())

分析:實(shí)際上OutputFormatters 是一個(gè)集合 ,通過(guò)Add方法添加服務(wù)器允許接受XML格式的數(shù)據(jù)功能。因?yàn)榧现心J(rèn)只有Json

setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())

分析:實(shí)際上剛剛寫(xiě)的是一種方法。Insert就是指明格式順序,默認(rèn)是JSON,通過(guò)Insert設(shè)置 0 ,就是指明XML為默認(rèn)接受的數(shù)據(jù)格式

實(shí)際上以上兩種寫(xiě)法都是 ASP.NET Core 3.x以前的寫(xiě)法。

ASP.NET Core 3.x的實(shí)際寫(xiě)法:就是在AddControllers后面添加X(jué)mlDataContractSerializerOutputFormatter方法。這樣不管是輸入輸出都已經(jīng)設(shè)置好了XML的格式數(shù)據(jù)

postman接口測(cè)試:取消setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())的注釋

默認(rèn)xml:

最后,關(guān)于構(gòu)建RESTFUL Api的URI規(guī)則及原理? ?

下篇

關(guān)于Entity Model vs 面向外部的Model

Entity Framework Core 使用 Entity Model 用來(lái)表示數(shù)據(jù)庫(kù)里面的記錄。

面向外部的Model 則表示要傳輸?shù)臇|西,有時(shí)候被稱(chēng)為 Dto,有時(shí)候被稱(chēng)為 ViewModel。

關(guān)于Dto,API消費(fèi)者通過(guò)Dto,僅提供給用戶(hù)需要的數(shù)據(jù)起到隔離的作用,防止API消費(fèi)者直接接觸到核心的Entity Model。

可能你會(huì)覺(jué)得有點(diǎn)多余,但是仔細(xì)想想你會(huì)發(fā)現(xiàn),Dto的存在是很有必要的。

Entity Model 與數(shù)據(jù)庫(kù)實(shí)際上應(yīng)該是有種依賴(lài)的關(guān)系,數(shù)據(jù)庫(kù)某一項(xiàng)功能發(fā)生改變,Entity Model也應(yīng)該會(huì)做出相應(yīng)的動(dòng)作,那么這個(gè)時(shí)候 API消費(fèi)者在請(qǐng)求服務(wù)器接口數(shù)據(jù)時(shí),如果直接接觸到了 Entity Model數(shù)據(jù),那么它也就無(wú)法預(yù)測(cè)到底是哪一項(xiàng)功能做出了改變。這個(gè)時(shí)候可能在做 API 請(qǐng)求的時(shí)候發(fā)生不可預(yù)估的錯(cuò)誤。Dto的存在一定程度上解決了這一問(wèn)題。

那么它的作用是?

編寫(xiě)Company的 Dto:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Routine.Api.Models
{
public class CompanyDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}

對(duì)比Company的 Entity Model:

using System;
using System.Collections.Generic;
namespace Routine.Api.Entities
{
/// <summary>
/// 公司
/// </summary>
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Introduction { get; set; }
public ICollection<Employee> Employees { get; set; }
}
}


Id和Name屬性是一致的,對(duì)于 Employees集合 以及 Introduction 字符串為了區(qū)分,這里不提供給 Dto

如何使用?

這里就涉及到了如何從 Entity Model 的數(shù)據(jù)轉(zhuǎn)化到 Dto

分析:我們給API消費(fèi)者提供的數(shù)據(jù)肯定是一個(gè)集合,那么可以先將Company的Dto定義為一個(gè)List集合,再通過(guò)循環(huán) Entity Model 的數(shù)據(jù),將數(shù)據(jù)添加到集合并且賦值給 Dto 對(duì)應(yīng)的屬性。

控制器代碼:

[HttpGet]
//IActionResult定義了一些合約,它可以代表ActionResult返回的結(jié)果
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies()
{
var companies =await _companyRepository.GetCompaniesAsync();//讀取出來(lái)的是List
var companyDtos = new List<CompanyDto>();
foreach (var company in companies)
{
companyDtos.Add(new CompanyDto
{
Id = company.Id,
Name = company.Name
});
};
return Ok(companyDtos);
}
}

這里你可能注意到了 返回的是 ActionResult<T>

關(guān)于 ActionResult<T>,好處就是讓 API 消費(fèi)者意識(shí)到此接口的返回類(lèi)型,就是將接口的返回類(lèi)型進(jìn)一步的明確,可以方便調(diào)用,讓代碼的可讀性也更高。

你可以返回IEnumerable類(lèi)型,也可以直接返回List,當(dāng)然這兩者并沒(méi)有什么區(qū)別,因?yàn)長(zhǎng)ist也實(shí)現(xiàn)了 IEnumerable 這個(gè)接口!

那么這樣做會(huì)面臨又一個(gè)問(wèn)題。如果 Dto 需要的數(shù)據(jù)又20甚至50條往上,那么這樣寫(xiě)會(huì)顯得非常的笨拙而且也很容易出錯(cuò)。

如何處理呢?dotnet生態(tài)給我們提供了一個(gè)很好的對(duì)象屬性映射器 AutoMapper!!!

關(guān)于 AutoMapper,官方解釋?zhuān)夯诩s定的對(duì)象屬性映射器。

它還存在一個(gè)作用,在處理映射關(guān)系時(shí)出現(xiàn)如果出現(xiàn)空引用異常,就是映射的目標(biāo)類(lèi)型出現(xiàn)了與源類(lèi)型不匹配的屬性字段,那么就會(huì)自動(dòng)忽略這一異常。

如何下載?

打開(kāi) nuget 工具包,搜索 AutoMapper ,下載第二個(gè)!!!原因是這個(gè)更好的實(shí)現(xiàn)依賴(lài)注入,可以看到它也依賴(lài)于 AutoMapper,相當(dāng)于把第一個(gè)也一并下載了。

如何使用 AutoMapper?

第一步進(jìn)入 Startup類(lèi) 注冊(cè)AutoMapper服務(wù)!

public void ConfigureServices(IServiceCollection services)
{
//services.AddMvc(); core 3.0以前是這樣寫(xiě)的,這個(gè)服務(wù)包括了TageHelper等 WebApi不需要的東西,所有3.0以后可以不這樣寫(xiě)
services.AddControllers();
//注冊(cè)AutoMapper服務(wù)services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
//配置接口服務(wù):涉及到這個(gè)服務(wù)注冊(cè)的生命周期這里采用AddScoped,表示每次的Http請(qǐng)求
services.AddScoped<ICompanyRepository, CompanyRepository>();
//獲取配置文件中的數(shù)據(jù)庫(kù)字符串連接
var sqlConnection = Configuration.GetConnectionString("SqlServerConnection");
//配置上下文類(lèi)DbContext,因?yàn)樗旧硪彩且惶追?wù)
services.AddDbContext<RoutineDbContext>(options =>
{
options.UseSqlServer(sqlConnection);
});
}

關(guān)于 AddAutoMapper() 方法,實(shí)際上它需要返回一個(gè) 程序集數(shù)組,就是AutoMapper的運(yùn)行配置文件,那么通過(guò) GetAssemblies 去掃描AutoMapper下的所有配置文件即可。

第二步:建立處理 AutoMapper 映射類(lèi)

using AutoMapper;
using Routine.Api.Entities;
using Routine.Api.Models;
namespace Routine.Api.Profiles
{
public class CompanyProfiles:Profile
{
public CompanyProfiles()
{
//添加映射關(guān)系,處理源類(lèi)型與映射目標(biāo)類(lèi)型屬性名稱(chēng)不一致的問(wèn)題
//參數(shù)一:源類(lèi)型,參數(shù)二:目標(biāo)映射類(lèi)型
CreateMap<Company, CompanyDto>()
.ForMember(target=>target.CompanyName,
opt=> opt.MapFrom(src=>src.Name));
}
}
}

分析:通過(guò)CreateMap,對(duì)于參數(shù)一:源類(lèi)型,參數(shù)二:目標(biāo)映射類(lèi)型。

關(guān)于 ForMember方法的作用,有時(shí)候你得考慮一個(gè)情況,前面已經(jīng)說(shuō)過(guò),AutoMapper 是基于約定的對(duì)象到對(duì)象(Object-Object)的屬性映射器,如果所映射的屬性字段不一致一定是無(wú)法映射成功的!

約定即屬性字段與源類(lèi)型屬性名稱(chēng)須一致!!!但是你也可以處理這一情況的發(fā)生,通過(guò)lambda表達(dá)式,將目標(biāo)映射類(lèi)型和源類(lèi)型關(guān)系重映射即可。

第三步:開(kāi)始數(shù)據(jù)映射

先來(lái)看映射前的代碼:通過(guò)集合循環(huán)賦值:

[HttpGet]
//IActionResult定義了一些合約,它可以代表ActionResult返回的結(jié)果
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies()
{
var companies =await _companyRepository.GetCompaniesAsync();//讀取出來(lái)的是List
var companyDtos = new List<CompanyDto>();
foreach (var company in companies)
{
companyDtos.Add(new CompanyDto
{
Id = company.Id,
Name = company.Name
});
}
return Ok(companyDtos);
}

通過(guò) AutoMapper映射:

[HttpGet]
//IActionResult定義了一些合約,它可以代表ActionResult返回的結(jié)果
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies()
{
var companies =await _companyRepository.GetCompaniesAsync();//讀取出來(lái)的是List
var companyDtos = _mapper.Map<IEnumerable<CompanyDto>>(companies);
return Ok(companyDtos);
}

分析:Map()方法處理需要返回的目標(biāo)映射類(lèi)型,然后帶入源類(lèi)型。

關(guān)于獲取父子關(guān)系的資源:

所謂 父:Conmpany(公司)、子:Employees(員工)

可能你注意到了基本上就是主從表的引用關(guān)系

那么我們?cè)谠O(shè)計(jì)AP uri 的時(shí)候也需要考慮到這一點(diǎn)

需求案例 1:查詢(xún)某一公司下的所有員工信息

分析:設(shè)計(jì)到員工信息,也需要需要實(shí)現(xiàn) Entity Model 對(duì) EmployeeDtos 的轉(zhuǎn)換,所以需要建立 EmployeeDto

對(duì)比 Employee 的 Entity Model和EmployeeDto

Entity Model 代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Routine.Api.Entities
{
/// <summary>
/// 員工
/// </summary>
public class Employee
{
public Guid Id { get; set; }
//公司外鍵
public Guid CompanyId { get; set; }
//公司表導(dǎo)航屬性
public Company Company { get; set; }
public string EmployeeNo { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

//性別枚舉
public Gender Gender { get; set; }
public DateTime DateOfBirth { get; set; }
}
}

EmployeeDto 代碼:

分析:對(duì)性別 Gender 枚舉類(lèi)型做了處理,改成了string類(lèi)型,方便調(diào)用。另外對(duì)于姓名 Name 也是將 FirstName 和 LastName合并,年齡 Age 改成了 int類(lèi)型

那么,這些改動(dòng)我們都需要在 EmployeeProfile類(lèi)中在映射時(shí)進(jìn)行標(biāo)注,不然由于對(duì)象屬性映射器的約定,無(wú)法進(jìn)行映射!!!

using System;

namespace Routine.Api.Models
{
public class EmployeeDto
{
public Guid Id { get; set; }
public Guid CompanyId { get; set; }
public string EmployeeNo { get; set; }
public string Name { get; set; }
public string GenderDispaly { get; set; }
public int Age { get; set; }
}
}

EmployeeProfile類(lèi)代碼:

邏輯和 CompanyProfile類(lèi)的映射是一樣的

using AutoMapper;
using Routine.Api.Entities;
using Routine.Api.Models;
using System;

namespace Routine.Api.Profiles
{
public class EmployeeProfile:Profile
{
public EmployeeProfile()
{
CreateMap<Employee, EmployeeDto>()
.ForMember(target => target.Name,
opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
.ForMember(target=>target.GenderDispaly,opt=>opt.MapFrom(src=>src.Gender.ToString()))
.ForMember(target=>target.Age,
opt=>opt.MapFrom(src=>DateTime.Now.Year-src.DateOfBirth.Year));
}
}
}


接下來(lái)開(kāi)始建立 EmployeeController 控制器,來(lái)通過(guò)映射器實(shí)現(xiàn)映射關(guān)系

EmployeeController :

需要注意 uir 的設(shè)計(jì),我們查詢(xún)的是某一個(gè)公司下的所有員工信息,所以也需要是 Entity Model 對(duì) EmployeeDtos的轉(zhuǎn)換,同樣是借助 對(duì)象屬性映射器。

using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Routine.Api.Models;
using Routine.Api.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Routine.Api.Controllers
{
[ApiController]
[Route("api/companies/{companyId}/employees")]
public class EmployeesController:ControllerBase
{
private readonly IMapper _mapper;
private readonly ICompanyRepository _companyRepository;
public EmployeesController(IMapper mapper, ICompanyRepository companyRepository)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_companyRepository = companyRepository ?? throw new ArgumentNullException(nameof(companyRepository));
}
[HttpGet]
public async Task<ActionResult<IEnumerable<EmployeeDto>>> GetEmployeesForCompany(Guid companyId)
{
if (! await _companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
var employees =await _companyRepository.GetEmployeesAsync(companyId);
var employeeDtos = _mapper.Map<IEnumerable<EmployeeDto>>(employees);
return Ok(employeeDtos);
}
}
}

接口測(cè)試(某一公司下的所有員工信息):

需求案例 2:查詢(xún)某一公司下的某一員工信息

來(lái)想想相比需求案例1哪些地方需要進(jìn)行改動(dòng)的?

既然是某一個(gè)員工,說(shuō)明 uir 需要加個(gè)員工的參數(shù) Id進(jìn)去。

還有除了判斷該公司是否存在,還需要判斷該員工是否存在。

另外,既然是某一個(gè)員工,所以返回的應(yīng)該是個(gè)對(duì)象而非IEnumable集合。

代碼:

[HttpGet("{employeeId}")]
public async Task<ActionResult<EmployeeDto>> GetEmployeeForCompany(Guid companyId,Guid employeeId)
{
//判斷公司存不存在
if (!await _companyRepository.CompanyExistsAsync(companyId))
{
return NotFound();
}
//判斷員工存不存在
var employee = await _companyRepository.GetEmployeeAsync(companyId, employeeId);
if (employee==null)
{
return NotFound();
}
//映射到 Dto
var employeeDto = _mapper.Map<EmployeeDto>(employee);
return Ok(employeeDto);
}

接口測(cè)試(某一公司下的某一員工信息):

可以看到測(cè)試成功!

關(guān)于故障處理:

這里的“故障”主要是指服務(wù)器故障或者是拋出異常的故障,ASP.NET Core 對(duì)于 服務(wù)器故障一般會(huì)引發(fā) 500 狀態(tài)碼錯(cuò)誤,對(duì)于這種錯(cuò)誤,會(huì)導(dǎo)致一種后果就是在出現(xiàn)故障后

故障信息會(huì)將程序異常細(xì)節(jié)顯示出來(lái),這就對(duì)API消費(fèi)者不夠友好,而且也造成一定的安全隱患。但此后果是在開(kāi)發(fā)環(huán)境下產(chǎn)生也就是 Development。

當(dāng)然ASP.NET Core開(kāi)發(fā)團(tuán)隊(duì)也意識(shí)到了這種問(wèn)題!

偽造程序異常:

引發(fā)異常后接口測(cè)試:


可以看到此異常已經(jīng)暴露了程序細(xì)節(jié)給 API 消費(fèi)者 ,這種做法欠妥。

怎么辦呢?試試改一下開(kāi)發(fā)的環(huán)境狀態(tài)!

重新測(cè)試接口:

問(wèn)題解決!


但是你可能想根據(jù)這些異常拋出一些自定義的信息給 API 消費(fèi)者 實(shí)際上也可以。

回到 Stratup 類(lèi):添加一個(gè)中間件 app.UseExceptionHandler即可

分析:意思是如果有未處理的異常發(fā)生的時(shí)候就會(huì)走 else 里面的代碼,實(shí)際項(xiàng)目中這一塊需要記錄一下日志

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appBulider =>
{
appBulider.Run(async context =>
{
context.Response.StatusCode = 500
await context.Response.WriteAsync("The program Error!");
});
});
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

再來(lái)測(cè)試一下接口是否成功返回自定義異常信息:

測(cè)試成功!!!

文章轉(zhuǎn)自微信公眾號(hào)@DotNet

#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門(mén)場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)