From afd2c68fbfabdb916417f4cdbcb3073977830c69 Mon Sep 17 00:00:00 2001 From: hello Date: Wed, 3 Apr 2024 16:37:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=83=E9=99=90=E7=B3=BB=E7=BB=9F=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notes/helloshop/model-binding.md | 7 ++ notes/helloshop/model-mapper.md | 66 ++++++++++++++ notes/helloshop/model-validations.md | 54 +++++++++++ .../helloshop/resource-based-authorization.md | 90 +++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 notes/helloshop/model-binding.md create mode 100644 notes/helloshop/model-mapper.md create mode 100644 notes/helloshop/model-validations.md create mode 100644 notes/helloshop/resource-based-authorization.md diff --git a/notes/helloshop/model-binding.md b/notes/helloshop/model-binding.md new file mode 100644 index 0000000..3136659 --- /dev/null +++ b/notes/helloshop/model-binding.md @@ -0,0 +1,7 @@ +## 模型绑定约定 + +实体对象是 EF 中的概念, 每个实体对象对应数据库中的一张表。模型对象是 MVC 中的概念,是 HTTP 请求和响应的数据结构。HTTP 请求中通过 URL 参数、表单、标头、 JSON 数据等方式传递数据,这些数据最终会被绑定为模型对象中,模型经过转换为实体对象后,被持久化到数据库中。 + +针对不同 HTTP 操作,应该使用不同的模型对象,比如创建模型、更新模型、查询模型、删除模型等,这样的好处是可以更好的区分模型对象的职责,比如创建模型只需要包含创建所需的字段,更新模型只需要包含更新所需的字段,查询模型只需要包含查询所需的字段,删除模型只需要包含删除所需的字段,职责单一,维护性好,还可以针对不同的模型提供不同的验证规则。 + +![model-entity-mapper](https://oss.xcode.me/notes/helloshop/model-entity-mapper.svg) diff --git a/notes/helloshop/model-mapper.md b/notes/helloshop/model-mapper.md new file mode 100644 index 0000000..8163643 --- /dev/null +++ b/notes/helloshop/model-mapper.md @@ -0,0 +1,66 @@ +# 自动映射实体和模型 + + +## 使用 AutoMapper 自动映射实体和模型 + +AutoMapper 是一个对象映射工具,可以自动映射实体对象和模型对象,减少手动映射的工作量,提高开发效率,除此之外 Mapster 也是一个高性能的对象映射工具,支持源生成代码,性能更好。AutoMapper 比 Mapster 更加流行,更加成熟,更加稳定,更加易用,更加灵活,更加强大,更加全面,更加受欢迎。AutoMapper 是 .NET Foundation 的一部分,是一个开源项目,是一个非常优秀的对象映射工具。 + +## 安装 NuGet 包 + +```shell +dotnet add package AutoMapper +``` + +## 在依赖注入中注册 AutoMapper + +```csharp +builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly()); +``` + +## 创建映射配置文件 + +```csharp + +public class MappingProfile : Profile +{ + public MappingProfile() + { + CreateMap(); + CreateMap(); + } +} + +``` + +## 使用 AutoMapper 映射实体和模型 + +```csharp + +public class UserService +{ + private readonly IMapper _mapper; + + public UserService(IMapper mapper) + { + _mapper = mapper; + } + + public UserDto GetUser(int id) + { + var user = _dbContext.Users.Find(id); + return _mapper.Map(user); + } + + public void UpdateUser(UserDto userDto) + { + var user = _mapper.Map(userDto); + _dbContext.Users.Update(user); + _dbContext.SaveChanges(); + } +} + +``` + +## 分页模式 + +常见的分页模式有两种,一种是基于页码和页大小的分页模式,一种是基于游标和页大小的分页模式。 \ No newline at end of file diff --git a/notes/helloshop/model-validations.md b/notes/helloshop/model-validations.md new file mode 100644 index 0000000..7b3767f --- /dev/null +++ b/notes/helloshop/model-validations.md @@ -0,0 +1,54 @@ +# 模型自动验证机制 + +## 使用 FluentValidation 开源库 + +FluentValidation 是一个.NET库,用于构建类型安全的验证规则。它的设计目标是提供一个简单、清晰的API,同时还能够支持复杂的验证规则。 + +```shell +dotnet add package FluentValidation.DependencyInjectionExtensions +``` + +## 实现验证器 + +```csharp +public class UserValidator : AbstractValidator +{ + public UserValidator() + { + RuleFor(x => x.Name).NotEmpty().MaximumLength(32); + RuleFor(x => x.Email).NotEmpty().EmailAddress(); + } +} +``` + +## 自动依赖注入 + +```csharp +builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); +``` + +## 自定义验证错误消息 + +```csharp +public class UserValidator : AbstractValidator +{ + public UserValidator() + { + RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("Name is required and must be less than 32 characters."); + RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("Email is required and must be a valid email address."); + } +} +``` + +## 通用错误消息 + +```csharp +public class UserValidator : AbstractValidator +{ + public UserValidator() + { + RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("{PropertyName} is required and must be less than {MaxLength} characters."); + RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("{PropertyName} is required and must be a valid email address."); + } +} +``` \ No newline at end of file diff --git a/notes/helloshop/resource-based-authorization.md b/notes/helloshop/resource-based-authorization.md new file mode 100644 index 0000000..0e47262 --- /dev/null +++ b/notes/helloshop/resource-based-authorization.md @@ -0,0 +1,90 @@ +# 基于资源的授权的最佳实践 + +## 在 Identity Service 中提供权限检查接口 + +```csharp +[HttpHead] +public async Task>> CheckPermission(string permissionName, string? resourceType = null, string? resourceId = null +{ + if (await permissionChecker.IsGrantedAsync(permissionName, resourceType, resourceId)) + { + return Ok(); + } + + return Forbid(); +} +``` + +## 远程权限检查器 + +```csharp +Dictionary parameters = new() +{ + [nameof(name)] = name, + [nameof(resourceType)] = resourceType, + [nameof(resourceId)] = resourceId +}; + +string queryString = QueryHelpers.AddQueryString(string.Empty, parameters); + +HttpRequestMessage request = new(HttpMethod.Head, queryString); + +using HttpResponseMessage response = await httpClient.SendAsync(request); + +return response.IsSuccessStatusCode; +``` + +## 重新实现权限处理程序 + +```csharp +public class PermissionRequirementHandler(IPermissionChecker permissionChecker) : AuthorizationHandler +{ + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement) + { + if (context.Resource is IAuthorizationResource resource) + { + if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name, resource.ResourceType, resource.ResourceId)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return; + } + + if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name)) + { + context.Succeed(requirement); + return; + } + + context.Fail(); + } +} +``` + +## 取消缓存以便测试 + +```csharp +await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions +{ + AbsoluteExpiration = DateTimeOffset.Now +}); +``` + +## 可读性重构 + +重新生成演示数据,并将授权中的 Name 改为 PermissionName,具有更强的可读性。 + +## 资源描述重构 + +```csharp +public record struct ResourceInfo(string ResourceType, string ResourceId) : IAuthorizationResource +{ + public override readonly string ToString() => $"{ResourceType}:{ResourceId}"; +} +``` +