跳到主要内容
C# WebAPI 创建与核心功能实战 | 极客日志
C#
C# WebAPI 创建与核心功能实战 综述由AI生成 基于 C# ASP.NET Core 创建 WebAPI 的完整流程。涵盖项目初始化、Minimal APIs 与控制器两种实现方式、中间件管道配置、模型绑定(路由、查询、头部、体)、数据注解与自定义验证、过滤器处理模型验证与异常、EF Core 数据库操作及迁移,以及基于 JWT 的身份认证与安全机制。通过衬衫管理案例演示了 CRUD 操作及安全控制。
DotNetGuy 发布于 2026/4/6 更新于 2026/5/23 26 浏览1、创建 WebApi 项目
Program.cs 代码保留如下:
var builder = WebApplication.CreateBuilder(args );
var app = builder.Build();
app.UseHttpsRedirection();
app.Run();
2、Minimal APIs 最小 API 使用
在 Program.cs 中进行最小 API 使用:
var builder = WebApplication.CreateBuilder(args );
var app = builder.Build();
app.UseHttpsRedirection();
app.MapGet("/shirts" , () => { return "获取所有衬衫数据列表" ; });
app.MapGet("/shirts/{id}" , (int id) => { return $"获取 ID 为 {id} 的衬衫数据" ; });
app.MapPost("/shirts" , () => { return "创建一件新的衬衫" ; });
app.MapPut("/shirts/{id}" , (int id) => { ; });
app.MapDelete( , ( id) => { ; });
app.Run();
return
$"更新 ID 为 {id} 的衬衫数据"
"/shirts/{id}"
int
return
$"删除 ID 为 {id} 的衬衫数据"
3、ASP.NET Core 中间件管道 app 中的 Use 开头的方法都是中间件组件使用方法。
4、Web API 控制器实现 Web API using Microsoft.AspNetCore.Mvc;
namespace WebApiTest.Controllers
{
[ApiController ]
public class ShirtsController : ControllerBase
{
[HttpGet ]
[Route("api/shirts" ) ]
public string GetShirts () { return "获取所有衬衫数据列表" ; }
[HttpGet ]
[Route("api/shirts/{id}" ) ]
public string GetShirtById (int id ) { return $"获取 ID 为 {id} 的衬衫数据" ; }
[HttpPost ]
[Route("api/shirts" ) ]
public string CreateShirt () { return "创建一件新的衬衫" ; }
[HttpPut ]
[Route("api/shirts/{id}" ) ]
public string UpdateShirt (int id ) { return $"更新 ID 为 {id} 的衬衫数据" ; }
[HttpDelete ]
[Route("api/shirts/{id}" ) ]
public string DeleteShirt (int id ) { return $"删除 ID 为 {id} 的衬衫数据" ; }
}
}
方法 2:将路由写在类前面,再在方法前面指定操作动词
using Microsoft.AspNetCore.Mvc;
namespace WebApiTest.Controllers
{
[ApiController ]
[Route("/api/[controller]" ) ]
public class ShirtsController : ControllerBase
{
[HttpGet ]
public string GetShirts () { return "获取所有衬衫数据列表" ; }
[HttpGet("{id}" ) ]
public string GetShirtById (int id ) { return $"获取 ID 为 {id} 的衬衫数据" ; }
[HttpPost ]
public string CreateShirt () { return "创建一件新的衬衫" ; }
[HttpPut("{id}" ) ]
public string UpdateShirt (int id ) { return $"更新 ID 为 {id} 的衬衫数据" ; }
[HttpDelete("{id}" ) ]
public string DeleteShirt (int id ) { return $"删除 ID 为 {id} 的衬衫数据" ; }
}
}
5、基于控制器的 Web API 的路由 使用注解 [Route("/shirts")] 设置 Web API 的 URL,可以在方法前使用,也可以在类前使用。
6、模型绑定,将 Http 请求中的数据映射到操作方法的参数 6.1 从路由绑定,在路由 Route("/shirts/{id}") 或在操作动词 HttpGet("{id}") 设置,在方法参数前设置 [FromRoute] 也可以省略
[HttpGet("{id}" ) ]
public string GetShirtById ([FromRoute]int id ) { return $"获取 ID 为 {id} 的衬衫数据" ; }
6.2 从查询字符串中绑定,方法参数前使用注解 [FromQuery]
[HttpGet("{id}" ) ]
public string GetShirtById ([FromRoute]int id, [FromQuery]string color ) { return $"获取 ID 为 {id} 的衬衫数据" ; }
6.3 从请求头 Header 中绑定,方法参数前使用注解 [FromHeader]
[HttpGet("{id}" ) ]
public string GetShirtById ([FromRoute]int id, [FromQuery]string color, [FromHeader]int size ) { return $"获取 ID 为 {id} 的衬衫数据,衬衫颜色为{color} ,大小为{size} " ; }
6.4 从请求体 Body JSON 格式中绑定,方法参数前使用注解 [FromBody]
[HttpPost ]
public string CreateShirt ([FromBody]Shirt shirt ) { return $"创建一件新的衬衫,{shirt.Id} , {shirt.Name} , {shirt.Color} , {shirt.Gender} , {shirt.Price} " ; }
6.5 从请求体 Body 表单中绑定,方法参数前使用注解 [FromForm]
[HttpPost ]
public string CreateShirt ([FromForm]Shirt shirt ) { return $"创建一件新的衬衫,{shirt.Id} , {shirt.Name} , {shirt.Color} , {shirt.Gender} , {shirt.Price} " ; }
7、数据注解模型验证
8、ValidationAttribute 模型验证,继承 ValidationAttribute 创建脚本 Shirt_EnsureCorrectSizingAttribute.cs
using System.ComponentModel.DataAnnotations;
using WebApiDemo.Models;
namespace WebApiTest.Models.Validations
{
public class Shirt_EnsureCorrectSizingAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object ? value , ValidationContext validationContext)
{
var shirt = validationContext.ObjectInstance as Shirt;
if (shirt != null && !string .IsNullOrEmpty(shirt.Gender))
{
if (shirt.Gender.Equals("men" , StringComparison.OrdinalIgnoreCase) && shirt.Size < 8 )
{
return new ValidationResult("男性衬衫的尺码必须大于或等于 8。" );
}
else if (shirt.Gender.Equals("women" , StringComparison.OrdinalIgnoreCase) && shirt.Size < 6 )
{
return new ValidationResult("女性衬衫的尺码必须大于或等于 6。" );
}
}
return ValidationResult.Success;
}
}
}
9、Web API 返回类型 正确返回使用 Ok(返回数据)
未找到使用 NotFound()
错误响应使用 BadRequest()
[HttpGet("{id}" ) ]
public IActionResult GetShirtById ([FromRoute]int id, [FromQuery]string color, [FromHeader]int size )
{
if (id <= 0 )
{
return BadRequest();
}
else if (id > 10 )
{
return NotFound();
}
return Ok($"获取 ID 为 {id} 的衬衫数据,衬衫颜色为{color} ,大小为{size} " );
}
10、操作过滤器进行模型验证,继承 ActionFilterAttribute 用户输入传递的 id 可能不符合规范,可以通过操作过滤器进行模型验证对 id 进行验证
创建脚本 Shirt_ValidateShirtIdFilterAttribute
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace WebApiTest.Filters.ActionFilters
{
public class Shirt_ValidateShirtIdFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting (ActionExecutingContext context )
{
base .OnActionExecuting(context);
var id = context.ActionArguments["id" ] as int ?;
if (id.HasValue)
{
if (id.Value <= 0 )
{
context.ModelState.AddModelError("Id" , "衬衫 ID 必须大于 0。" );
var problemDetails = new Microsoft.AspNetCore.Mvc.ValidationProblemDetails(context.ModelState)
{
Status = 400 ,
Title = "请求参数错误" ,
};
context.Result = new BadRequestObjectResult(problemDetails);
}
}
}
}
}
11、异常过滤器实现异常处理 更新衬衫之前可能其他请求把该衬衫已经删掉,更新时可能会报错,模拟该情景
创建脚本 Shirt_HandleUpdateExceptionFilterAttribute
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace WebApiTest.Filters.ExceptionFilters
{
public class Shirt_HandleUpdateExceptionFilterAttribute : ExceptionFilterAttribute
{
public int ?[] shirtsId = new int ?[] { 1 , 2 , 3 , 4 , 5 };
public override void OnException (ExceptionContext context )
{
base .OnException(context);
var strShirtId = context.RouteData.Values["id" ] as string ;
if (int .TryParse(strShirtId, out int shirtId))
{
if (shirtsId.FirstOrDefault(x => x == shirtId) == null )
{
context.ModelState.AddModelError("Id" , $"衬衫已经不存在" );
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Status = StatusCodes.Status404NotFound,
};
context.Result = new NotFoundObjectResult(problemDetails);
}
}
}
}
}
12、Web API 操作数据库
12.1 下载安装 Sql Server Developer Edition 和 SQL Server Management Studio 打开 SQL Server Management Studio 连接本地数据库
12.2 安装需要使用的包
EntityFrameworkCore
EntityFrameworkCore.Design
EntityFrameworkCore.Tools
EntityFrameworkCore.SqlServer
12.3 创建数据库上下文 1)创建脚本 ApplicationDbContext
using Microsoft.EntityFrameworkCore;
using WebApiDemo.Models;
namespace WebApiTest.Data
{
public class ApplicationDbContext : DbContext
{
public DbSet <Shirt > Shirts { get ; set ; }
protected override void OnModelCreating (ModelBuilder modelBuilder )
{
base .OnModelCreating(modelBuilder);
modelBuilder.Entity<Shirt>().HasData(
new Shirt { Id = 1 , Name = "衬衫 1" , Color = "red" , Gender = "men" , Size = 10 , Price = 100 },
new Shirt { Id = 2 , Name = "衬衫 2" , Color = "blue" , Gender = "men" , Size = 12 , Price = 140 },
new Shirt { Id = 3 , Name = "衬衫 3" , Color = "黑色" , Gender = "women" , Size = 11 , Price = 132 },
new Shirt { Id = 4 , Name = "衬衫 4" , Color = "白色" , Gender = "women" , Size = 7 , Price = 151 }
);
}
}
}
12.4 执行数据库迁移 2)获取连接字符串,视图->服务器资源管理器->数据连接
Data Source=(local);Initial Catalog=master;Integrated Security=True;Trust Server Certificate=True
using Microsoft.EntityFrameworkCore;
using WebApiDemo.Models;
namespace WebApiTest.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext (DbContextOptions options ) : base (options ) { }
public DbSet <Shirt > Shirts { get ; set ; }
protected override void OnModelCreating (ModelBuilder modelBuilder )
{
base .OnModelCreating(modelBuilder);
modelBuilder.Entity<Shirt>().HasData(
new Shirt { Id = 1 , Name = "衬衫 1" , Color = "red" , Gender = "men" , Size = 10 , Price = 100 },
new Shirt { Id = 2 , Name = "衬衫 2" , Color = "blue" , Gender = "men" , Size = 12 , Price = 140 },
new Shirt { Id = 3 , Name = "衬衫 3" , Color = "黑色" , Gender = "women" , Size = 11 , Price = 132 },
new Shirt { Id = 4 , Name = "衬衫 4" , Color = "白色" , Gender = "women" , Size = 7 , Price = 151 }
);
}
}
}
4)在 Program.cs 添加运行 EntityFrameworkCore 所需的服务
builder.Services.AddDbContext<ApplicationDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("ShirtStoreManagement" )); });
控制台运行命令 Add-Migration Init(Init 可以替换为该次进行操作说明)
在控制台运行迁移代码,Update-Database Init(Init 为迁移代码名称,不指定会执行最新的代码)
12.5 使用 EF Core 实现 Get 端点 在 ShirtsController 构造函数中依赖注入 Db 上下文
[HttpGet ]
public IActionResult GetShirts ()
{
return Ok(db.Shirts.ToList());
}
12.6 使用 EF Core 实现 Get by Id 端点 修改 Shirt_ValidateShirtIdFilterAttribute,实现验证 ID 在数据库是否存在
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApiTest.Data;
namespace WebApiTest.Filters.ActionFilters
{
public class Shirt_ValidateShirtIdFilterAttribute : ActionFilterAttribute
{
private readonly ApplicationDbContext db;
public Shirt_ValidateShirtIdFilterAttribute (ApplicationDbContext db )
{
this .db = db;
}
public override void OnActionExecuting (ActionExecutingContext context )
{
base .OnActionExecuting(context);
var id = context.ActionArguments["id" ] as int ?;
if (id.HasValue)
{
if (id.Value <= 0 )
{
context.ModelState.AddModelError("Id" , "衬衫 ID 必须大于 0。" );
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Status = 400 ,
Title = "请求参数错误" ,
};
context.Result = new BadRequestObjectResult(problemDetails);
}
else
{
var shirt = db.Shirts.Find(id.Value);
if (shirt == null )
{
context.ModelState.AddModelError("Id" , $"ID 为{id.Value} 的衬衫在数据库不存在" );
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Status = 400 ,
Title = "衬衫 ID 不存在" ,
};
context.Result = new NotFoundObjectResult(problemDetails);
}
else
{
context.HttpContext.Items["shirt" ] = shirt;
}
}
}
}
}
}
在操作过滤器中查询数据库存在 ID 衬衫在 context.HttpContext.Items 中进行存在,然后在控制器中获取,避免重复进行数据库查询减少性能开销
[HttpGet("{id}" ) ]
[TypeFilter(typeof(Shirt_ValidateShirtIdFilterAttribute)) ]
public IActionResult GetShirtById ([FromRoute]int id )
{
return Ok(HttpContext.Items["shirt" ]);
}
12.7 使用 EF Core 实现 Post 端点
[HttpPost ]
public IActionResult CreateShirt ([FromBody]Shirt shirt )
{
this .db.Shirts.Add(shirt);
this .db.SaveChanges();
return CreatedAtAction(nameof (GetShirtById), new { id = shirt.Id}, shirt);
}
12.8 使用 EF Core 实现 Put 端点 修改代码 Shirt_HandleUpdateExceptionFilterAttribute,实现验证当前 ID 数据是否还存在,可能存在当前操作之前数据被删除的可能
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApiTest.Data;
namespace WebApiTest.Filters.ExceptionFilters
{
public class Shirt_HandleUpdateExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly ApplicationDbContext db;
public Shirt_HandleUpdateExceptionFilterAttribute (ApplicationDbContext db )
{
this .db = db;
}
public override void OnException (ExceptionContext context )
{
base .OnException(context);
var strShirtId = context.RouteData.Values["id" ] as string ;
if (int .TryParse(strShirtId, out int shirtId))
{
if (this .db.Shirts.FirstOrDefault(x => x.Id == shirtId) == null )
{
context.ModelState.AddModelError("Id" , $"衬衫已经在数据库不存在,可能在此之前被删除了" );
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Status = StatusCodes.Status404NotFound,
};
context.Result = new NotFoundObjectResult(problemDetails);
}
}
}
}
}
[HttpPut("{id}" ) ]
[TypeFilter(typeof(Shirt_ValidateShirtIdFilterAttribute)) ]
[TypeFilter(typeof(Shirt_HandleUpdateExceptionFilterAttribute)) ]
public IActionResult UpdateShirt (int id, [FromBody]Shirt shirt )
{
var shirtToUpdate = HttpContext.Items["shirt" ] as Shirt;
shirtToUpdate.Name = shirt.Name;
shirtToUpdate.Size = shirt.Size;
shirtToUpdate.Color = shirt.Color;
shirtToUpdate.Gender = shirt.Gender;
shirtToUpdate.Price = shirt.Price;
this .db.SaveChanges();
return Ok($"ID 为{id} 衬衫更新成功" );
}
12.9 使用 EF Core 实现 Delete 端点
[HttpDelete("{id}" ) ]
[TypeFilter(typeof(Shirt_ValidateShirtIdFilterAttribute)) ]
public IActionResult DeleteShirt (int id )
{
var shirtToDelete = HttpContext.Items["shirt" ] as Shirt;
this .db.Shirts.Remove(shirtToDelete);
this .db.SaveChanges();
return Ok(shirtToDelete);
}
13、Web Api 安全机制,使用 JWT 生成令牌和验证令牌 1)创建应用程序信息类,Application.cs
namespace WebApiDemo.Authority
{
public class Application
{
public int ApplicationId { get ; set ; }
public string ? ApplicationName { get ; set ; }
public string ? ClientId { get ; set ; }
public string ? Secret { get ; set ; }
public string ? Scopes { get ; set ; }
}
}
2)创建应用内容存储,AppRepository.cs,替代已存储数据库的应用程序注册信息
namespace WebApiDemo.Authority
{
public static class AppRepository
{
private static List<Application> _applications = new List<Application>
{
new Application { ApplicationId = 1 , ApplicationName = "MVCWebApp" , ClientId = "53D3C1E6-4587-4AD5-8C6E-A8E4BD59940E" , Secret = "0673FC70-0514-4011-B4A3-DF9BC03201BC" , Scopes = "read,write,delete" }
};
public static Application? GetApplicationByClientId(string clientId)
{
return _applications.FirstOrDefault(app => app.ClientId == clientId);
}
}
}
3)创建应用凭证类,AppCredential.cs,用于接受认证请求参数
namespace WebApiDemo.Authority
{
public class AppCredential
{
public string ? ClientId { get ; set ; } = string .Empty;
public string ? Secret { get ; set ; } = string .Empty;
}
}
4)创建应用认证控制器接口,AuthorityController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using WebApiDemo.Authority;
namespace WebApiDemo.Controllers
{
[ApiController ]
public class AuthorityController : ControllerBase
{
private readonly IConfiguration configuration;
public AuthorityController (IConfiguration configuration )
{
this .configuration = configuration;
}
[HttpPost("auth" ) ]
public IActionResult Authenticate ([FromBody] AppCredential credential )
{
if (Authenticator.Authenticate(credential.ClientId, credential.Secret))
{
var expiresAt = DateTime.UtcNow.AddMinutes(10 );
return Ok(new { access_token = Authenticator.CreateToken(credential.ClientId, expiresAt, configuration["SecurityKey" ] ?? string .Empty), expires_at = expiresAt });
}
else
{
ModelState.AddModelError("Unauthorized" , "未被授权" );
var problemDetails = new ValidationProblemDetails(ModelState)
{
Status = StatusCodes.Status401Unauthorized,
};
return new UnauthorizedObjectResult(problemDetails);
}
}
}
}
6)创建应用认证身份验证逻辑类,Authenticator.cs
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
namespace WebApiDemo.Authority
{
public static class Authenticator
{
public static bool Authenticate (string clientId, string secret )
{
var app = AppRepository.GetApplicationByClientId(clientId);
if (app == null ) return false ;
return (app.ClientId == clientId && app.Secret == secret);
}
public static string CreateToken (string clientId, DateTime expiresAt, string strSecretKey )
{
var signingCredentials = new SigningCredentials(
new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(strSecretKey)),
SecurityAlgorithms.HmacSha256Signature);
var app = AppRepository.GetApplicationByClientId(clientId);
var claimsDictionary = new Dictionary<string , object >
{
{ "AppName" , app?.ApplicationName??string .Empty },
};
var scopes = app?.Scopes?.Split(',' ) ?? Array.Empty<string >();
if (scopes.Length > 0 )
{
foreach (var scope in scopes)
{
claimsDictionary.Add(scope.Trim().ToLower(), "true" );
}
}
var tokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = signingCredentials,
Claims = claimsDictionary,
Expires = expiresAt,
NotBefore = DateTime.UtcNow,
};
var tokenHandler = new JsonWebTokenHandler();
return tokenHandler.CreateToken(tokenDescriptor);
}
public static async Task<IEnumerable<Claim>?> VerifyTokenAsync(string tokenString, string securityKey)
{
if (string .IsNullOrWhiteSpace(tokenString) || string .IsNullOrWhiteSpace(securityKey))
{
return null ;
}
var keyBytes = System.Text.Encoding.UTF8.GetBytes(securityKey);
var tokenHandle = new JsonWebTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = false ,
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
ValidateIssuerSigningKey = false ,
ValidateAudience = false ,
ValidateLifetime = true ,
ClockSkew = TimeSpan.Zero,
};
try
{
var result = await tokenHandle.ValidateTokenAsync(tokenString, validationParameters);
if (result.SecurityToken != null )
{
var tokenObject = tokenHandle.ReadJsonWebToken(tokenString);
return tokenObject.Claims ?? Enumerable.Empty<Claim>();
}
else
{
return null ;
}
}
catch (SecurityTokenMalformedException)
{
return null ;
}
catch (SecurityTokenExpiredException)
{
return null ;
}
catch (SecurityTokenInvalidSignatureException)
{
return null ;
}
catch (Exception)
{
throw ;
}
}
}
}
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApiDemo.Attributes;
using WebApiDemo.Authority;
namespace WebApiDemo.Filters.AuthFilters
{
public class JwtTokenAuthFilterAttribute : Attribute , IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync (AuthorizationFilterContext context )
{
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization" , out var authHeader))
{
context.Result = new UnauthorizedResult();
return ;
}
string tokenString = authHeader.ToString();
if (tokenString.StartsWith("Bearer " , StringComparison.OrdinalIgnoreCase))
{
tokenString = tokenString.Substring("Bearer " .Length).Trim();
}
else
{
context.Result = new UnauthorizedResult();
return ;
}
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
var securityKey = configuration?["SecurityKey" ] ?? string .Empty;
var claims = await Authenticator.VerifyTokenAsync(tokenString, securityKey);
if (claims == null )
{
context.Result = new UnauthorizedResult();
}
else
{
var requiredClaims = context.ActionDescriptor.EndpointMetadata
.OfType<RequiredClaimAttribute>()
.ToList();
if (requiredClaims != null && !requiredClaims.All(rc => claims.Any(c => c.Type.Equals(rc.ClaimType, StringComparison.OrdinalIgnoreCase) && c.Value.Equals(rc.ClaimValue, StringComparison.OrdinalIgnoreCase))))
{
context.Result = new StatusCodeResult(403 );
}
}
}
}
}
8)授权、权限作用域校验类,RequiredClaimAttribute.cs
namespace WebApiDemo.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,AllowMultiple = true) ]
public class RequiredClaimAttribute : Attribute
{
public string ClaimType { get ; }
public string ClaimValue { get ; }
public RequiredClaimAttribute (string claimType, string claimValue )
{
this .ClaimType = claimType;
this .ClaimValue = claimValue;
}
}
}
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online