Asp.NetCoreWebApi - RESTful Api
目录
- [参考文章](#参考文章) - [REST](#rest) - [常用http动词](#常用http动词) - [WebApi 在 Asp.NetCore 中的实现](#webapi-在-aspnetcore-中的实现) - [创建WebApi项目.](#创建webapi项目) - [集成Entity Framework Core操作Mysql](#集成entity-framework-core操作mysql) - [安装相关的包(为Xxxx.Infrastructure项目安装)](#安装相关的包为xxxxinfrastructure项目安装) - [建立Entity和Context](#建立entity和context) - [ConfigureService中注入EF服务](#configureservice中注入ef服务) - [迁移数据库](#迁移数据库) - [迁移数据库失败, 提示 `Unable to create an object of type '参考文章
REST
REST : 具象状态传输(Representational State Transfer,简称REST),是Roy Thomas Fielding博士于2000年在他的博士论文 "Architectural Styles and the Design of Network-based Software Architectures" 中提出来的一种万维网软件架构风格。
目前在三种主流的Web服务实现方案中,因为REST模式与复杂的SOAP和XML-RPC相比更加简洁,越来越多的web服务开始采用REST风格设计和实现。例如,Amazon.com提供接近REST风格的Web服务执行图书查询;
符合REST设计风格的Web API称为RESTful API。它从以下三个方面资源进行定义:
- 直观简短的资源地址:URI,比如:http://example.com/resources/ .
- 传输的资源:Web服务接受与返回的互联网媒体类型,比如:JSON,XML,YAML等...
- 对资源的操作:Web服务在该资源上所支持的一系列请求方法(比如:POST,GET,PUT或DELETE).
PUT和DELETE方法是幂等方法.GET方法是安全方法(不会对服务器端有修改,因此当然也是幂等的).
ps 关于幂等方法 :
看这篇 理解HTTP幂等性.
简单说,客户端多次请求服务端返回的结果都相同,那么就说这个操作是幂等的.(个人理解,详细的看上面给的文章)
不像基于SOAP的Web服务,RESTful Web服务并没有“正式”的标准。这是因为REST是一种架构,而SOAP只是一个协议。虽然REST不是一个标准,但大部分RESTful Web服务实现会使用HTTP、URI、JSON和XML等各种标准。
常用http动词
括号中是相应的SQL命令.
- GET(SELECT) : 从服务器取出资源(一项或多项).
- POST(CREATE) : 在服务器新建一个资源.
- PUT(UPDATE) : 在服务器更新资源(客户端提供改变后的完整资源).
- PATCH(UPDATE) : 在服务器更新资源(客户端提供改变的属性).
- DELETE(DELETE) : 在服务器删除资源.
WebApi 在 Asp.NetCore 中的实现
这里以用户增删改查为例.
创建WebApi项目.
参考ASP.NET Core WebAPI 开发-新建WebAPI项目.
注意,本文建立的Asp.NetCore WebApi项目选择.net core版本是2.2,不建议使用其他版本,2.1版本下会遇到依赖文件冲突问题!所以一定要选择2.2版本的.net core.
集成Entity Framework Core操作Mysql
安装相关的包(为Xxxx.Infrastructure项目安装)
- Microsoft.EntityFrameworkCore.Design
- Pomelo.EntityFrameworkCore.MySql
这里注意一下,Mysql官方的包是 MySql.Data.EntityFrameworkCore
,但是这个包有bug,我在github上看到有人说有替代方案 - Pomelo.EntityFrameworkCore.MySql
,经过尝试,后者比前者好用.所有这里就选择后者了.使用前者的话可能会导致数据库迁移失败(Update的时候).
PS: Mysql文档原文:
Install the MySql.Data.EntityFrameworkCore NuGet package.
For EF Core 1.1 only: If you plan to scaffold a database, install the MySql.Data.EntityFrameworkCore.Design NuGet package as well.EFCore - MySql文档
Mysql版本要求:
Mysql版本要高于5.7
使用最新版本的Mysql Connector(2019 6/27 目前是8.x).为Xxxx.Infrastructure项目安装EFCore相关的包:
为Xxxx.Api项目安装 Pomelo.EntityFrameworkCore.MySql
建立Entity和Context
ApiUser
```CSharp namespace ApiStudy.Core.Entities { using System; public class ApiUser { public Guid Guid { get; set; } public string Name { get; set; } public string Passwd { get; set; } public DateTime RegistrationDate { get; set; } public DateTime Birth { get; set; } public string ProfilePhotoUrl { get; set; } public string PhoneNumber { get; set; } public string Email { get; set; } } } ```UserContext
```CSharp namespace ApiStudy.Infrastructure.Database { using ApiStudy.Core.Entities; using Microsoft.EntityFrameworkCore; public class UserContext:DbContext { public UserContext(DbContextOptionsConfigureService中注入EF服务
services.AddDbContext<UserContext>(options =>
{
string connString = "Server=Xxx:xxx:xxx:xxx;Database=Xxxx;Uid=root;Pwd=Xxxxx; ";
options.UseMySQL(connString);
});
迁移数据库
- 在Tools > NuGet Package Manager > Package Manager Console输入命令.
- Add-Migration Xxx 添加迁移.
PS : 如果迁移不想要,使用 Remove-Migration 命令删除迁移. - Update-Database 更新到数据库.
迁移数据库失败, 提示 Unable to create an object of type '<Xxxx>Context'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
原因应该是EfCore迁移工具不知道如何创建 DbContext
导致的.
解决方案
在DbContext
所在的项目下新建一个类:
/// <summary>
/// 设计时DbContext的创建, 告诉EF Core迁移工具如何创建DbContext
/// </summary>
public class <Xxxx>ContextFactory : IDesignTimeDbContextFactory<<Xxxx>Context>
{
public <Xxxx>Context CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<<Xxxx>Context>();
optionsBuilder.UseMySql(
@"Server=[服务器ip];Database=[数据库]];Uid=[用户名];Pwd=[密码];");
return new <Xxxx>Context(optionsBuilder.Options);
}
}
数据库迁移结果
为数据库创建种子数据
写一个创建种子数据的类
UserContextSeed
namespace ApiStudy.Infrastructure.Database { using ApiStudy.Core.Entities; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Threading.Tasks; public class UserContextSeed { public static async Task SeedAsync(UserContext context,ILoggerFactory loggerFactory) { try { if (!context.ApiUsers.Any()) { context.ApiUsers.AddRange( new ApiUser { Guid = Guid.NewGuid(), Name = "la", Birth = new DateTime(1998, 11, 29), RegistrationDate = new DateTime(2019, 6, 28), Passwd = "123587", ProfilePhotoUrl = "https://www.laggage.top/", PhoneNumber = "10086", Email = "yu@outlook.com" }, new ApiUser { Guid = Guid.NewGuid(), Name = "David", Birth = new DateTime(1995, 8, 29), RegistrationDate = new DateTime(2019, 3, 28), Passwd = "awt87495987", ProfilePhotoUrl = "https://www.laggage.top/", PhoneNumber = "1008611", Email = "David@outlook.com" }, new ApiUser { Guid = Guid.NewGuid(), Name = "David", Birth = new DateTime(2001, 8, 19), RegistrationDate = new DateTime(2019, 4, 25), Passwd = "awt87495987", ProfilePhotoUrl = "https://www.laggage.top/", PhoneNumber = "1008611", Email = "David@outlook.com" }, new ApiUser { Guid = Guid.NewGuid(), Name = "Linus", Birth = new DateTime(1999, 10, 26), RegistrationDate = new DateTime(2018, 2, 8), Passwd = "awt87495987", ProfilePhotoUrl = "https://www.laggage.top/", PhoneNumber = "17084759987", Email = "Linus@outlook.com" }, new ApiUser { Guid = Guid.NewGuid(), Name = "YouYou", Birth = new DateTime(1992, 1, 26), RegistrationDate = new DateTime(2015, 7, 8), Passwd = "grwe874864987", ProfilePhotoUrl = "https://www.laggage.top/", PhoneNumber = "17084759987", Email = "YouYou@outlook.com" }, new ApiUser { Guid = Guid.NewGuid(), Name = "小白", Birth = new DateTime(1997, 9, 30), RegistrationDate = new DateTime(2018, 11, 28), Passwd = "gewa749864", ProfilePhotoUrl = "https://www.laggage.top/", PhoneNumber = "17084759987", Email = "BaiBai@outlook.com" }); await context.SaveChangesAsync(); } } catch(Exception ex) { ILogger logger = loggerFactory.CreateLogger<UserContextSeed>(); logger.LogError(ex, "Error occurred while seeding database"); } } } }
修改Program.Main方法
Program.Main
IWebHost host = CreateWebHostBuilder(args).Build(); using (IServiceScope scope = host.Services.CreateScope()) { IServiceProvider provider = scope.ServiceProvider; UserContext userContext = provider.GetService<UserContext>(); ILoggerFactory loggerFactory = provider.GetService<ILoggerFactory>(); UserContextSeed.SeedAsync(userContext, loggerFactory).Wait(); } host.Run();
这个时候运行程序会出现异常,打断点看一下异常信息:Data too long for column 'Guid' at row 1
可以猜到,Mysql的varbinary(16)放不下C# Guid.NewGuid()方法生成的Guid,所以配置一下数据库Guid字段类型为varchar(256)可以解决问题.
解决方案:
修改 UserContext.OnModelCreating 方法
配置一下 ApiUser.Guid 属性到Mysql数据库的映射:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApiUser>().Property(p => p.Guid)
.HasColumnType("nvarchar(256)");
modelBuilder.Entity<ApiUser>().HasKey(u => u.Guid);
base.OnModelCreating(modelBuilder);
}
支持https
将所有http请求全部映射到https
Startup中:
ConfigureServices方法注册,并配置端口和状态码等:
services.AddHttpsRedirection(…)
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = 5001;
});
Configure方法使用该中间件:
app.UseHttpsRedirection()
支持HSTS
ConfigureServices方法注册
看官方文档
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
options.ExcludedHosts.Add("example.com");
options.ExcludedHosts.Add("www.example.com");
});
Configure方法配置中间件管道
app.UseHsts();
注意 app.UseHsts() 方法最好放在 app.UseHttps() 方法之后.
使用SerilLog
有关日志的微软官方文档
SerilLog github仓库
该github仓库上有详细的使用说明.
使用方法:
安装nuget包
- Serilog.AspNetCore
- Serilog.Sinks.Console
添加代码
Program.Main方法中:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
修改Program.CreateWebHostBuilder(...)
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog(); // <-- Add this line;
}
自行测试
Asp.NetCore配置文件
默认配置文件
默认 appsettings.json
ConfigurationBuilder().AddJsonFile("appsettings.json").Build()-->IConfigurationRoot(IConfiguration)
获得配置
IConfiguration[“Key:ChildKey”]
针对”ConnectionStrings:xxx”,可以使用IConfiguration.GetConnectionString(“xxx”)
private static IConfiguration Configuration { get; set; }
public StartupDevelopment(IConfiguration config)
{
Configuration = config;
}
...
Configuration[“Key:ChildKey”]
自定义一个异常处理,ExceptionHandler
弄一个类,写一个扩展方法处理异常
namespace ApiStudy.Api.Extensions
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
public static class ExceptionHandlingExtensions
{
public static void UseCustomExceptionHandler(this IApplicationBuilder app,ILoggerFactory loggerFactory)
{
app.UseExceptionHandler(
builder => builder.Run(async context =>
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
Exception ex = context.Features.Get<Exception>();
if (!(ex is null))
{
ILogger logger = loggerFactory.CreateLogger("ApiStudy.Api.Extensions.ExceptionHandlingExtensions");
logger.LogError(ex, "Error occurred.");
}
await context.Response.WriteAsync(ex?.Message ?? "Error occurred, but cannot get exception message.For more detail, go to see the log.");
}));
}
}
}
在Configuration中使用扩展方法
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseCustomExceptionHandler(loggerFactory); //modified code
//app.UseDeveloperExceptionPage();
app.UseHsts();
app.UseHttpsRedirection();
app.UseMvc(); //使用默认路由
}
实现数据接口类(Resource),使用AutoMapper在Resource和Entity中映射
为Entity类创建对应的Resource类
ApiUserResource
```CSharp namespace ApiStudy.Infrastructure.Resources { using System; public class ApiUserResource { public Guid Guid { get; set; } public string Name { get; set; } //public string Passwd { get; set; } public DateTime RegistrationDate { get; set; } public DateTime Birth { get; set; } public string ProfilePhotoUrl { get; set; } public string PhoneNumber { get; set; } public string Email { get; set; } } } ```使用 AutoMapper
- 添加nuget包
AutoMapper
AutoMapper.Extensions.Microsoft.DependencyInjection
配置映射
可以创建Profile
CreateMap<TSource,TDestination>()
MappingProfile
namespace ApiStudy.Api.Extensions { using ApiStudy.Core.Entities; using ApiStudy.Infrastructure.Resources; using AutoMapper; using System; using System.Text; public class MappingProfile : Profile { public MappingProfile() { CreateMap<ApiUser, ApiUserResource>() .ForMember( d => d.Passwd, opt => opt.AddTransform(s => Convert.ToBase64String(Encoding.Default.GetBytes(s)))); CreateMap<ApiUserResource, ApiUser>() .ForMember( d => d.Passwd, opt => opt.AddTransform(s => Encoding.Default.GetString(Convert.FromBase64String(s)))); } } }
注入服务 ->
services.AddAutoMapper()
使用FluentValidation
安装Nuget包
- FluentValidation
- FluentValidation.AspNetCore
为每一个Resource配置验证器
继承于AbstractValidator
ApiUserResourceValidator
namespace ApiStudy.Infrastructure.Resources { using FluentValidation; public class ApiUserResourceValidator : AbstractValidator<ApiUserResource> { public ApiUserResourceValidator() { RuleFor(s => s.Name) .MaximumLength(80) .WithName("用户名") .WithMessage("{PropertyName}的最大长度为80") .NotEmpty() .WithMessage("{PropertyName}不能为空!"); } } }
注册到容器:services.AddTransient<>()
services.AddTransient<IValidator<ApiUserResource>, ApiUserResourceValidator>();
实现Http Get(翻页,过滤,排序)
基本的Get实现
```CSharp [HttpGet] public async Task资源命名
资源应该使用名词,例
- api/getusers就是不正确的.
- GET api/users就是正确的
资源命名层次结构
- 例如
api/department/{departmentId}/emoloyees
, 这就表示了department
(部门)和员工
(employee)之前是主从关系. - 而
api/department/{departmentId}/emoloyees/{employeeId}
,就表示了该部门下的某个员
工.
内容协商
ASP.NET Core支持输出和输入两种格式化器.
- 用于输出的media type放在Accept Header里,表示客户端接受这种格式的输出.
- 用于输入的media type放Content-Type Header里,表示客户端传进来的数据是这种格式.
- ReturnHttpNotAcceptable设为true,如果客户端请求不支持的数据格式,就会返回406.
services.AddMvc(options => { options.ReturnHttpNotAcceptable = true; });
- 支持输出XML格式:
options.OutputFormatters.Add(newXmlDataContractSerializerOutputFormatter());
翻页
构造翻页请求参数类
QueryParameters
```CSharp namespace ApiStudy.Core.Entities { using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; public abstract class QueryParameters : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private const int DefaultPageSize = 10; private const int DefaultMaxPageSize = 100; private int _pageIndex = 1; public virtual int PageIndex { get => _pageIndex; set => SetField(ref _pageIndex, value); } private int _pageSize = DefaultPageSize; public virtual int PageSize { get => _pageSize; set => SetField(ref _pageSize, value); } private int _maxPageSize = DefaultMaxPageSize; public virtual int MaxPageSize { get => _maxPageSize; set => SetField(ref _maxPageSize, value); } public string OrderBy { get; set; } public string Fields { get; set; } protected void SetFieldApiUserParameters
```CSharp namespace ApiStudy.Core.Entities { public class ApiUserParameters:QueryParameters { public string UserName { get; set; } } } ```Repository实现支持翻页请求参数的方法
Repository相关代码
```CSharp /*----- ApiUserRepository -----*/ public PaginatedListUserController部分代码
```CSharp ... [HttpGet(Name = "GetAllApiUsers")] public async TaskPS注意,为HttpGet方法添加参数的话,在.net core2.2版本下,去掉那个ApiUserController上的 [ApiController());] 特性,否则参数传不进来..net core3.0中据说已经修复这个问题.
搜索(过滤)
修改Repository代码:
public PaginatedList<ApiUser> GetAllApiUsers(ApiUserParameters parameters)
{
IQueryable<ApiUser> query = _context.ApiUsers.AsQueryable();
query = query.Skip(parameters.PageIndex * parameters.PageSize)
.Take(parameters.PageSize);
if (!string.IsNullOrEmpty(parameters.UserName))
query = _context.ApiUsers.Where(
x => StringComparer.OrdinalIgnoreCase.Compare(x.Name, parameters.UserName) == 0);
return new PaginatedList<ApiUser>(
parameters.PageIndex,
parameters.PageSize,
query.Count(),
query);
}
排序
>排序思路
- 需要安装System.Linq.Dynamic.Core
思路:
- PropertyMappingContainer
- PropertyMapping(ApiUserPropertyMapping)
- MappedProperty
- PropertyMapping(ApiUserPropertyMapping)
MappedProperty
```CSharp namespace ApiStudy.Infrastructure.Services { public struct MappedProperty { public MappedProperty(string name, bool revert = false) { Name = name; Revert = revert; } public string Name { get; set; } public bool Revert { get; set; } } } ```IPropertyMapping
```CSharp namespace ApiStudy.Infrastructure.Services { using System.Collections.Generic; public interface IPropertyMapping { DictionaryPropertyMapping
```CSharp namespace ApiStudy.Infrastructure.Services { using System.Collections.Generic; public abstract class PropertyMappingIPropertyMappingContainer
```CSharp namespace ApiStudy.Infrastructure.Services { public interface IPropertyMappingContainer { void RegisterPropertyMappingContainer
```CSharp namespace ApiStudy.Infrastructure.Services { using System; using System.Linq; using System.Collections.Generic; public class PropertyMappingContainer : IPropertyMappingContainer { protected internal readonly IListQueryExtensions
```CSharp namespace ApiStudy.Infrastructure.Extensions { using ApiStudy.Infrastructure.Services; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; public static class QueryExtensions { public static IQueryableUserController 部分代码
```CSharp [HttpGet(Name = "GetAllApiUsers")] public async Task资源塑形(Resource shaping)
返回 资源的指定字段
ApiStudy.Infrastructure.Extensions.TypeExtensions
```CSharp namespace ApiStudy.Infrastructure.Extensions { using System; using System.Collections.Generic; using System.Reflection; public static class TypeExtensions { public static IEnumerableApiStudy.Infrastructure.Extensions.ObjectExtensions
```CSharp namespace ApiStudy.Infrastructure.Extensions { using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Reflection; public static class ObjectExtensions { public static ExpandoObject ToDynamicObject(this object source, in string fields = null) { ListApiStudy.Infrastructure.Extensions.IEnumerableExtensions
```CSharp namespace ApiStudy.Infrastructure.Extensions { using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Reflection; public static class IEnumerableExtensions { public static IEnumerableApiStudy.Infrastructure.Services.TypeHelperServices
```CSharp namespace ApiStudy.Infrastructure.Services { using System.Reflection; public class TypeHelperServices : ITypeHelperServices { public bool HasPropertiesUserContext.GetAllApiUsers(), UserContext.Get()
```CSharp [HttpGet(Name = "GetAllApiUsers")] public async Task配置返回的json名称风格为CamelCase
StartupDevelopment.ConfigureServices
services.AddMvc(options =>
{
options.ReturnHttpNotAcceptable = true;
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
})
.AddJsonOptions(options =>
{
//added code
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
HATEOAS
REST里最复杂的约束,构建成熟RESTAPI的核心
- 可进化性,自我描述
- 超媒体(Hypermedia,例如超链接)驱动如何消
费和使用API
UserContext
```CSharp private IEnumerable创建供应商特定媒体类型
- application/vnd.mycompany.hateoas+json
- vnd是vendor的缩写,这一条是mime type的原则,表示这个媒体类型是供应商特定的
- 自定义的标识,也可能还包括额外的值,这里我是用的是公司名,随后是hateoas表示返回的响应里面要
包含链接 - “+json”
- 在Startup里注册.
判断Media Type类型
- [FromHeader(Name = "Accept")] stringmediaType
//Startup.ConfigureServices 中注册媒体类型
services.AddMvc(options =>
{
options.ReturnHttpNotAcceptable = true;
//options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
JsonOutputFormatter formatter = options.OutputFormatters.OfType<JsonOutputFormatter>().FirstOrDefault();
formatter.SupportedMediaTypes.Add("application/vnd.laggage.hateoas+json");
})
// get方法中判断媒体类型
if (mediaType == "application/json")
return Ok(shapedApiUserResources);
else if (mediaType == "application/vnd.laggage.hateoas+json")
{
...
return;
}
注意,要是的 Action 认识 application/vnd.laggage.hateoss+json ,需要在Startup.ConfigureServices中注册这个媒体类型,上面的代码给出了具体操作.
UserContext
```CSharp [HttpGet(Name = "GetAllApiUsers")] public async Task- 自定义Action约束.
RequestHeaderMatchingMediaTypeAttribute
```CSharp [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)] public class RequestHeaderMatchingMediaTypeAttribute : Attribute, IActionConstraint { private readonly string _requestHeaderToMatch; private readonly string[] _mediaTypes; public RequestHeaderMatchingMediaTypeAttribute(string requestHeaderToMatch, string[] mediaTypes) { _requestHeaderToMatch = requestHeaderToMatch; _mediaTypes = mediaTypes; } public bool Accept(ActionConstraintContext context) { var requestHeaders = context.RouteContext.HttpContext.Request.Headers; if (!requestHeaders.ContainsKey(_requestHeaderToMatch)) { return false; } foreach (var mediaType in _mediaTypes) { var mediaTypeMatches = string.Equals(requestHeaders[_requestHeaderToMatch].ToString(), mediaType, StringComparison.OrdinalIgnoreCase); if (mediaTypeMatches) { return true; } } return false; } public int Order { get; } = 0; } ```UserContext
```CSharp [HttpGet(Name = "GetAllApiUsers")] [RequestHeaderMatchingMediaType("Accept",new string[] { "application/vnd.laggage.hateoas+json" })] public async TaskPost添加资源
Post - 不安全,非幂等
要返回添加好的资源,并且返回头中有获得新创建资源的连接.
安全性和幂等性
- 安全性是指方法执行后并不会改变资源的表述
- 幂等性是指方法无论执行多少次都会得到同样
的结果
代码实现
StartUp中注册Fluent,用于验证
services.AddMvc(...)
.AddFluentValidation();
services.AddTransient<IValidator<ApiUserAddResource>, ApiUserAddResourceValidator>();
ApiStudy.Infrastructure.Resources.ApiUserAddResourceValidator
```CSharp namespace ApiStudy.Infrastructure.Resources { using FluentValidation; public class ApiUserAddResourceValidator : AbstractValidatorUserContext.AddApiUser()
```CSharp [HttpPost(Name = "CreateApiUser")] [RequestHeaderMatchingMediaType("Content-Type",new string[] { "application/vnd.laggage.create.apiuser+json" })] [RequestHeaderMatchingMediaType("Accept",new string[] { "application/vnd.laggage.hateoas+json" })] public async TaskDelete
- 参数 : ID
- 幂等的
- 多次请求的副作用和单次请求的副作用是一样的.每次发送了DELETE请求之后,服务器的状态都是一样的.
- 不安全
ApiStudy.Api.Controllers.UserController
```CSharp [HttpDelete("{guid}",Name = "DeleteApiUser")] public async TaskPUT & PATCH
相关类:
ApiStudy.Infrastructure.Resources.ApiUserAddOrUpdateResource
namespace ApiStudy.Infrastructure.Resources
{
using System;
public abstract class ApiUserAddOrUpdateResource
{
public string Name { get; set; }
public string Passwd { get; set; }
public DateTime Birth { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
}
}
ApiStudy.Infrastructure.Resources.ApiUserAddResource
```CSharp namespace ApiStudy.Infrastructure.Resources { public class ApiUserAddResource:ApiUserAddOrUpdateResource { } } ```ApiStudy.Infrastructure.Resources.ApiUserUpdateResource
```CSharp namespace ApiStudy.Infrastructure.Resources { public class ApiUserUpdateResource : ApiUserAddOrUpdateResource { } } ```ApiStudy.Infrastructure.Resources.ApiUserAddOrUpdateResourceValidator
```CSharp namespace ApiStudy.Infrastructure.Resources { using FluentValidation; public class ApiUserAddOrUpdateResourceValidatorPUT 整体更新
- 返回204
- 参数
- ID,
- [FromBody]XxxxUpdateResource
ApiStudy.Api.Controllers.UpdateApiUser
```CSharp [HttpPut("{guid}",Name = "PutApiUser")] public async TaskPATCH
- Content-Type
- application/json-patch+json
- 返回204
- 参数
- ID
- [FromBody] JsonPatchDocument
- op操作
- 添加:{“op”: "add", "path": "/xxx", "value": "xxx"},如果该属性不存,那么就添加该属性,如
果属性存在,就改变属性的值。这个对静态类型不适用。 - 删除:{“op”: "remove", "path": "/xxx"},删除某个属性,或把它设为默认值(例如空值)。
- 替换:{“op”: "replace", "path": "/xxx", "value": "xxx"},改变属性的值,也可以理解为先执行
了删除,然后进行添加。 - 复制:{“op”: "copy", "from": "/xxx", "path": "/yyy"},把某个属性的值赋给目标属性。
- 移动:{“op”: "move", "from": "/xxx", "path": "/yyy"},把源属性的值赋值给目标属性,并把源
属性删除或设成默认值。 - 测试:{“op”: "test", "path": "/xxx", "value": "xxx"},测试目标属性的值和指定的值是一样的。
- 添加:{“op”: "add", "path": "/xxx", "value": "xxx"},如果该属性不存,那么就添加该属性,如
- path,资源的属性名
- 可以有层级结构
- value 更新的值
[
{
"op":"replace",
"path":"/name",
"value":"阿黄"
},
{
"op":"remove",
"path":"/email"
}
]
ApiStudy.Api.Controllers.UserContext.UpdateApiUser
```CSharp [HttpPatch("{guid}",Name = "PatchApiUser")] [RequestHeaderMatchingMediaType("Content-Type",new string[] { "application/vnd.laggage.patch.apiuser+json" })] public async TaskHttp常用方法总结
- GET(获取资源):
- GET api/countries,返回200,集合数据;找不到数据返回404。
- GET api/countries/{id},返回200,单个数据;找不到返回404.
- DELETE(删除资源)
- DELETE api/countries/{id},成功204;没找到资源404。
- DELETE api/countries,很少用,也是204或者404.
- POST (创建资源):
- POST api/countries,成功返回201和单个数据;如果资源没有创建则返回404
- POST api/countries/{id},肯定不会成功,返回404或409.
- POST api/countrycollections,成功返回201和集合;没创建资源则返回404
- PUT (整体更新):
- PUT api/countries/{id},成功可以返回200,204;没找到资源则返回404
- PUT api/countries,集合操作很少见,返回200,204或404
- PATCH(局部更新):
- PATCHapi/countries/{id},200单个数据,204或者404
- PATCHapi/countries,集合操作很少见,返回200集合,204或404.
原文地址:https://www.cnblogs.com/Laggage/p/11117768.html
- 利用d3.js对QQ群资料进行大数据可视化分析
- 海量数据迁移之分区并行切分(r2笔记60天)
- 数据结构和算法——kd树
- shell脚本心得(r2笔记58天)
- C/C++——柔性数组
- 用shell脚本巧妙统计文件(r2笔记57天)
- MATLAB技巧——imshow多张图片
- MATLAB技巧——sort和sortrows函数
- Python对商品属性进行二次分类并输出多层嵌套字典
- 通过shell得到数据库中权限的脚本(r2笔记77天)
- 用Python实现PCA和MDA降维和聚类
- 通过shell解析dump生成parfile(r2笔记76天)
- Web Spider实战1——简单的爬虫实战(爬取"豆瓣读书评分9分以上榜单")
- 如何用R语言从网上读取多样格式数据
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android studio so库找不到问题解决办法
- Android使用ViewPager实现屏幕滑动效果
- Android 自定义view实现进度条加载效果实例代码
- Android电池电量监听的示例代码
- Android 解决ScrollView嵌套CridView显示问题
- Android利用zxing快速集成二维码扫描的实例教程
- Android中使用SharedPreferences完成记住账号密码的功能
- Android Intent封装的实例详解
- Android自定义Drawable实现圆角效果
- Android ApplicationInfo 应用程序信息的详解
- Android UI控件Switch的使用方法
- Android如何读写CSV文件方法示例
- Android 静默安装和卸载的方法
- Android自定义单例AlertDialog详解
- Android Build类的详解及简单实例