本文内容来自书籍: Marinko Spasojevic - Ultimate ASP.NET Core Web API - From Zero To Six-Figure Backend Developer (2nd edition)
Controllers
只是负责处理requests
,model
,validation
和返回responses
给前端或者HTTP客户端
始终让业务逻辑不参与到Controllers
中
创建presentation Layer
,目的是提供端点给系统,然后consumers
可以和数据交互
我们没有在主项目中创建Controllers
文件夹,然后创建控制器,而是创建Presentation Layer
,这也是一个库,然后这个库引用Service.Contracts
,最后主项目引用Presentation
,这样Presentation
就只是引用了接口,而不知道实现,DI是在主项目中完成
然后在Presentation
中使用Microsoft.AspNetCore.Mvc.Core
包,为了在Presentation
创建控制器
[Route("api/[controller]")] [ApiController] public class CompaniesController : ControllerBase { }
[Route("api/[controller]")]
是属性路由
当HTTP请求进来之后,路由会把请求指向特定的action
,MVC framework
会解析HTTP请求并尝试匹配
框架中,由两种路由的实现
Convention-based routing
name
action
UseRouting
中间件,而在APMapControllers
中配置路由Attribute routing
URI的资源名称应该总是一个名词而不是动词
当两个资源之间有依赖关系的时候,资源的URI应该是
/api/principalResource/{principalId}/dependentResource.
比如/api/companies/{companyId}/employees
因为employees
不能单独存在
在这里,会开始添加一点实际业务逻辑进来
现在要实现的API是获取全部的Companies
ICompanyRepository
添加一个方法public interface ICompanyRepository { IEnumerable<Company> GetAllCompanies(bool trackChanges); }
internal sealed class CompanyRepository : RepositoryBase<Company>, ICompanyRepository { public CompanyRepository(RepositoryContext repositoryContext) :base(repositoryContext) { } public IEnumerable<Company> GetAllCompanies(bool trackChanges) => FindAll(trackChanges) .OrderBy(c => c.Name) .ToList(); }
Service.Contracts
,创建接口public interface ICompanyService { IEnumerable<Company> GetAllCompanies(bool trackChanges); }
这里是直接从数据库层获取了所有的数据,但是这是个不好的做法,现在只是为了完成一个最简单的方法,后期会修改
Service
internal sealed class CompanyService : ICompanyService { private readonly IRepositoryManager _repository; private readonly ILoggerManager _logger; public CompanyService(IRepositoryManager repository, ILoggerManager logger) { _repository = repository; _logger = logger; } public IEnumerable<Company> GetAllCompanies(bool trackChanges) { try { var companies = _repository.Company.GetAllCompanies(trackChanges); return companies; } catch (Exception ex) { _logger.LogError($"Something went wrong in the {nameof(GetAllCompanies)} service method {ex}"); throw; } } }
Presentation
中,使用这个方法返回结果[Route("api/companies")] [ApiController] public class CompaniesController : ControllerBase { private readonly IServiceManager _service; public CompaniesController(IServiceManager service) => _service = service; [HttpGet] public IActionResult GetCompanies() { try { var companies = _service.CompanyService.GetAllCompanies(trackChanges: false); return Ok(companies); } catch { return StatusCode(500, "Internal server error"); } } }
上面说过,经过API请求直接返回数据库的实体类型,不是一个好的处理,需要一个DTO来替代
为什么需要DTO
因为Model主要是用来给EF Core来映射到数据库的,而且,一般Model
是会有导航属性的,但是我们不希望返回给客户端。
还有就是,当我们迁移数据库的时候,实体也被修改了,但是这并不代表我们的API会被修改。一般API制定之后不会经常被修改,它是一种协议
现在,创建一个Shared
库,用作公共资源,创建DataTransferObjects/CompanyDto
来转换Model
public record CompanyDto(Guid Id, string Name, string FullAddress);
然后替换Service.Contracts
中的Entities
引用
使用这个库来自动将两个实体之间映射,去除手动映射
Service
中安装包AutoMapper.Extensions.Microsoft.DependencyInjection
builder.Services.AddAutoMapper(typeof(Program));
profile
文件,用于指定映射的src和despublic class MappingProfile : Profile { public MappingProfile() { CreateMap<Company, CompanyDto>() .ForMember(c => c.FullAddress, opt => opt.MapFrom(x => string.Join(' ', x.Address, x.Country))); } }
ServiceManager
,使用DI将转换服务注入CompanyService
,DI注入转换服务