我们提供安全,免费的手游软件下载!
本文记录一下 StarBlog 项目的当前状态与接下来 v2 版本的开发规划。
StarBlog 项目从 2022 年开始至今已经 2 年多了,本来早就该给第一期做个小结的,但这种博客类型的项目,一旦稳定能用之后,我就没多大的动力去更新了 ?
博客地址是: blog.deali.cn
另外还有个用 NextJS 重构的 预览版
PS:对了,还有个 StarBlog 的 Vue 前端系列,我之前已经写好了,一直没有发出来,接下来会连载更新,感兴趣的同学可以关注一下。
内容比较简单(当时我是边学 Vue 边做的),不过从这个角度来说也挺适合刚入门的同学阅读,毕竟以初学者的角度写的。
当时开发这个项目的本意是边学边做,作为熟悉 AspNetCore 的练手项目,现在说实话也无法投入很多时间去开发这类博客项目了……毕竟这种类型的项目太基础了。
还记得我年初规划的几个新项目,到现在的进度都还很有限,所以接下来还是得把时间放在这些新项目上。
这里来回顾一下新项目开发计划:
这么看下来也太多了,摊子铺太大了,不好收场啊…
不过精力放在其他项目上也不是意味着不管 StarBlog 项目了,事实上我一直有在做小修小改,这个看 Github commit 就知道了。
从 StarBlog 项目上线至今,我不断学习关于 AspNetCore 的细节知识,相比起刚刚开发这个项目的时候,对框架的熟悉程度提升了一些,自然也发现了之前代码里的局限之处:
[HttpHead]
来实现对 Head 方法的支持
ApiResponse
类型,应该保留框架的
ActionResult
类型,这样功能更多
app.UseExceptionHandler
中间件来实现统一错误处理(也可以使用异常过滤器)
这些问题将是 v2 版本要解决的。
目前规划了一些新的功能和优化,但这肯定不是 v2 版本的全部,各位同学如果有好的建议也可以留言讨论一下~
24 年初我又复习了一些 AspNetCore 框架的功能,比较零散不成体系,与 StarBlog 的开发是息息相关的,所以在本文记录一下吧~
编写过滤器
public class ApiExceptionFilter : IExceptionFilter {
public void OnException(ExceptionContext context) {
var response = new ApiResponse {
StatusCode = 500,
Successful = false,
// 可以根据需求修改此处来更详细地描述错误信息
Message = context.Exception.Message
};
context.Result = new ObjectResult(response) {
StatusCode = 500
};
// 标记异常已处理
context.ExceptionHandled = true;
}
}
注册
builder.Services.AddControllers(options => {
options.Filters.Add();
options.Filters.Add();
});
过滤器可以捕获在 Action 方法或控制器中抛出的异常,并允许开发者对其进行处理,然后返回一个统一的响应格式。
但不是在 Action 方法或控制器中抛出的异常,是捕获不到的,例如加了
[Authorize]
特性的接口,没有提供认证信息的时候访问报 401 错误,这种是捕获不到的。
如果想要在整个应用程序中处理异常,使用中间件可能是更好的选择。中间件可以捕获在请求处理管道中发生的所有类型的异常。
使用
app.UseExceptionHandler
中间件来实现统一错误处理
一个简单的例子,在
Program.cs
里配置内置的
ExceptionHandler
中间件
if (app.Environment.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
else {
app.UseExceptionHandler(applicationBuilder => {
applicationBuilder.Run(async context => {
// 记录日志啥的
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(new { message = "Unexpected error!" });
});
});
}
自己手写中间件
public class ErrorHandlingMiddleware {
private readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next) {
_next = next;
}
public async Task Invoke(HttpContext context) {
try {
await _next(context);
}
catch (Exception ex) {
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception) {
var response = new ApiResponse {
StatusCode = 500,
Successful = false,
Message = exception.Message
};
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
}
使用中间件
app.UseMiddleware();
在 ASP.NET Core 中,当使用
app.UseAuthentication()
和
app.UseAuthorization()
中间件处理认证和授权逻辑时,如果认证或授权失败,这些中间件会直接修改响应,返回 HTTP 状态码如 401(未认证)或 403(未授权)。
这些响应并不是通过异常机制处理的,因此常规的异常处理中间件或
UseExceptionHandler
无法捕获和修改这些特定的错误响应。
要自定义这些错误响应,需要配置认证中间件以使用特定的事件来修改响应。
这通常涉及到在认证方案的配置中添加事件处理逻辑。下面以 JWT 认证为例说明如何自定义 401 和 403 的响应:
在
services.AddAuthentication().AddJwtBearer(options => { ... })
里面添加事件配置
options.Events = new JwtBearerEvents {
OnChallenge = async context => {
if (!context.Response.HasStarted) {
context.HandleResponse(); // 阻止默认的401响应
var response = new ApiResponse {
StatusCode = 401,
Successful = false,
Message = "Authentication failed. You are not authorized."
};
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
},
OnForbidden = async context => {
var response = new ApiResponse {
StatusCode = 403,
Successful = false,
Message = "You do not have permission to access this resource."
};
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
};
在 JWT 认证流程中,
JwtBearerEvents
类提供了多个事件来处理不同的认证相关情景:
OnChallenge
事件是处理返回 401 未认证响应的正确位置。
实现
IModelBinder
接口可以自定义接口的 model bind 行为,这种叫做
Custom Model Binder
,建议放在
Helpers
目录下
例子:输入
guid1,guid2,guid3,guid4
形式的参数,获取公司列表
[HttpGet("{ids}")]
public async Task GetCompanyCollection([FromRoute] IEnumerable ids) {
}
新增
Helpers/ArrayModelBinder.cs
文件
public class ArrayModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (!bindingContext.ModelMetadata.IsEnumerableType) {
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
if (string.IsNullOrWhiteSpace(value)) {
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}
// 使用反射获取参数类型
var elementType = bindingContext.ModelType.GetTypeInfo().GenericTypeParameters[0];
// 根据参数类型,新建一个类型转换器
var converter = TypeDescriptor.GetConverter(elementType);
// 按照 , 分割字符串,
// 指定 `StringSplitOptions.RemoveEmptyEntries` 参数,用以清除分割后的空值,比如 `1,,3,4` -> `["1", "3", "4"]`
var values = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => converter.ConvertFromString(x.Trim()))
.ToArray();
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
return Task.CompletedTask;
}
}
最后在接口签名里指定使用的
ModelBinder
[HttpGet("{ids}")]
public async Task GetCompanyCollection(
[FromRoute]
[ModelBinder(BinderType = typeof(ArrayModelBinder))]
IEnumerable ids) {
}
PS:为了更直观表示出这个 route parameter 是支持传入多个值,可以在参数上加个括号。
[HttpGet("({ids})")]
public async Task GetCompanyCollection(
不知道说啥,现在很少写 C#了,最近.Net9 好像出来了,优化了 GC 算法,等有空来试试~
相关资讯
热门资讯