深入探究ASP.NET Core异常处理中间件

前言

    全局异常处理是我们编程过程中不可或缺的重要环节。有了全局异常处理机制给我们带来了很多便捷,首先我们不用满屏幕处理程序可能出现的异常,其次我们可以对异常进行统一的处理,比如收集异常信息或者返回统一的格式等等。ASP.NET Core为我们提供了两种机制去处理全局异常,一是基于中间件的方式,二是基于Filter过滤器的方式。Filter过滤器的方式相对来说比较简单,就是捕获Action执行过程中出现的异常,然后调用注册的Filter去执行处理异常信息,在这里就不过多介绍这种方式了,接下来我们主要介绍中间件的方式。

异常处理中间件

    ASP.NET Core为我们提供了几种不同处理异常方式的中间件分别是UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePages、UseStatusCodePagesWithRedirects、UseStatusCodePagesWithReExecute。这几种方式处理的思路是一致的都是通过捕获该管道后续的管道执行过程中出现的异常,只是处理的方式不一样。一般推荐全局异常处理相关中间件写到所有管道的最开始,这样可以捕获到整个执行管道过程中的异常信息。接下来我们介绍一下最常用的三个异常处理中间件UseDeveloperExceptionPage、UseExceptionHandler、UseStatusCodePage。

UseDeveloperExceptionPage

UseDeveloperExceptionPage的使用场景大部分是开发阶段,通过名称我们就可以看出,通过它捕获的异常会返回一个异常界面,它的使用方式很简单

//这个判断不是必须的,但是在正式环境中给用户展示代码错误信息,终究不是合理的
if (env.IsDevelopment())
{app.UseDeveloperExceptionPage();
}

如果程序出现异常,出现的效果是这个样子的
这里包含了非常详细的异常堆栈信息、请求参数、Cookie信息、Header信息、和路由终结点相关的信息。接下来我们找到UseDeveloperExceptionPage所在的扩展类

public static class DeveloperExceptionPageExtensions
{public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app){return app.UseMiddleware<DeveloperExceptionPageMiddleware>();}public static IApplicationBuilder UseDeveloperExceptionPage(this IApplicationBuilder app,DeveloperExceptionPageOptions options){return app.UseMiddleware<DeveloperExceptionPageMiddleware>(Options.Create(options));}
}

我们看到有两个扩展方法一个是无参的,另一个是可以传递DeveloperExceptionPageOptions的扩展方法,因为平时使用无参的方法所以我们看下DeveloperExceptionPageOptions包含了哪些信息,找到DeveloperExceptionPageOptions源码

public class DeveloperExceptionPageOptions
{public DeveloperExceptionPageOptions(){SourceCodeLineCount = 6;}/// <summary>/// 展示出现异常代码的地方上下展示多少行的代码信息,默认是6行/// </summary>public int SourceCodeLineCount { get; set; }/// <summary>/// 通过这个文件提供程序我们可以猜测到,我们可以自定义异常错误界面/// </summary>public IFileProvider FileProvider { get; set; }
}

接下来我们就看核心的DeveloperExceptionPageMiddleware中间件大致是如何工作的

public class DeveloperExceptionPageMiddleware
{private readonly RequestDelegate _next;private readonly DeveloperExceptionPageOptions _options;private readonly ILogger _logger;private readonly IFileProvider _fileProvider;private readonly DiagnosticSource _diagnosticSource;private readonly ExceptionDetailsProvider _exceptionDetailsProvider;private readonly Func<ErrorContext, Task> _exceptionHandler;private static readonly MediaTypeHeaderValue _textHtmlMediaType = new MediaTypeHeaderValue("text/html");public DeveloperExceptionPageMiddleware(RequestDelegate next,IOptions<DeveloperExceptionPageOptions> options,ILoggerFactory loggerFactory,IWebHostEnvironment hostingEnvironment,DiagnosticSource diagnosticSource,IEnumerable<IDeveloperPageExceptionFilter> filters){_next = next;_options = options.Value;_logger = loggerFactory.CreateLogger<DeveloperExceptionPageMiddleware>();//默认使用ContentRootFileProvider_fileProvider = _options.FileProvider ?? hostingEnvironment.ContentRootFileProvider;//可以发送诊断日志_diagnosticSource = diagnosticSource;_exceptionDetailsProvider = new ExceptionDetailsProvider(_fileProvider, _options.SourceCodeLineCount);_exceptionHandler = DisplayException;//构建IDeveloperPageExceptionFilter执行管道,说明我们同时还可以通过程序的方式捕获异常信息foreach (var filter in filters.Reverse()){var nextFilter = _exceptionHandler;_exceptionHandler = errorContext => filter.HandleExceptionAsync(errorContext, nextFilter);}}public async Task Invoke(HttpContext context){try{await _next(context);}catch (Exception ex){_logger.UnhandledException(ex);if (context.Response.HasStarted){_logger.ResponseStartedErrorPageMiddleware();throw;}try{//清除输出相关信息,将状态码设为500context.Response.Clear();context.Response.StatusCode = 500;//核心处理await _exceptionHandler(new ErrorContext(context, ex));//发送名称为Microsoft.AspNetCore.Diagnostics.UnhandledException诊断日志,我们可以自定义订阅者处理异常if (_diagnosticSource.IsEnabled("Microsoft.AspNetCore.Diagnostics.UnhandledException")){_diagnosticSource.Write("Microsoft.AspNetCore.Diagnostics.UnhandledException", new { httpContext = context, exception = ex });}return;}catch (Exception ex2){_logger.DisplayErrorPageException(ex2);}throw;}}
}

通过上面代码我们可以了解到我们可以通过自定义IDeveloperPageExceptionFilter的方式拦截到异常信息做处理

public class MyDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{private readonly ILogger<MyDeveloperPageExceptionFilter> _logger;public MyDeveloperPageExceptionFilter(ILogger<MyDeveloperPageExceptionFilter> logger){_logger = logger;}public async Task HandleExceptionAsync(ErrorContext errorContext, Func<ErrorContext, Task> next){_logger.LogInformation($"状态码:{errorContext.HttpContext.Response.StatusCode},异常信息:{errorContext.Exception.Message}");await next(errorContext);}
}

自定义的MyDeveloperPageExceptionFilter是需要注入的

services.AddSingleton<IDeveloperPageExceptionFilter,MyDeveloperPageExceptionFilter>();

同时还可以通过诊断日志的方式处理异常信息,我使用了Microsoft.Extensions.DiagnosticAdapter扩展包,所以可以定义强类型类

public class DiagnosticCollector
{private readonly ILogger<DiagnosticCollector> _logger;public DiagnosticCollector(ILogger<DiagnosticCollector> logger){_logger = logger;}[DiagnosticName("Microsoft.AspNetCore.Diagnostics.UnhandledException")]public void OnRequestStart(HttpContext httpContext, Exception exception){_logger.LogInformation($"诊断日志收集到异常,状态码:{httpContext.Response.StatusCode},异常信息:{exception.Message}");}
}

通过这里可以看出,异常处理扩展性还是非常强的,这仅仅是.Net Core设计方式的冰山一角。刚才我们提到_exceptionHandler才是处理的核心,通过构造函数可知这个委托是通过DisplayException方法初始化的,接下来我们看这里的相关实现

private Task DisplayException(ErrorContext errorContext)
{var httpContext = errorContext.HttpContext;var headers = httpContext.Request.GetTypedHeaders();var acceptHeader = headers.Accept;//如果acceptHeader不存在或者类型不是text/plain,将以字符串的形式输出异常,比如通过代码或者Postman的方式调用并未设置头信息if (acceptHeader == null || !acceptHeader.Any(h => h.IsSubsetOf(_textHtmlMediaType))){httpContext.Response.ContentType = "text/plain";var sb = new StringBuilder();sb.AppendLine(errorContext.Exception.ToString());sb.AppendLine();sb.AppendLine("HEADERS");sb.AppendLine("=======");foreach (var pair in httpContext.Request.Headers){sb.AppendLine($"{pair.Key}: {pair.Value}");}return httpContext.Response.WriteAsync(sb.ToString());}//判断是否为编译时异常,比如视图文件可以动态编译if (errorContext.Exception is ICompilationException compilationException){return DisplayCompilationException(httpContext, compilationException);}//处理运行时异常return DisplayRuntimeException(httpContext, errorContext.Exception);
}

关于DisplayCompilationException我们这里就不做过多解释了,在Asp.Net Core中cshtml文件可以动态编译,有兴趣的同学可以自行了解。我们重点看下DisplayRuntimeException处理

private Task DisplayRuntimeException(HttpContext context, Exception ex)
{//获取终结点信息var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;EndpointModel endpointModel = null;if (endpoint != null){endpointModel = new EndpointModel();endpointModel.DisplayName = endpoint.DisplayName;if (endpoint is RouteEndpoint routeEndpoint){endpointModel.RoutePattern = routeEndpoint.RoutePattern.RawText;endpointModel.Order = routeEndpoint.Order;var httpMethods = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods;if (httpMethods != null){endpointModel.HttpMethods = string.Join(", ", httpMethods);}}}var request = context.Request;//往视图还是个输出的模型,对于我们上面截图展示的信息对应的数据var model = new ErrorPageModel{Options = _options,//异常详情ErrorDetails = _exceptionDetailsProvider.GetDetails(ex),//查询参数相关Query = request.Query,//Cookie信息Cookies = request.Cookies,//头信息Headers = request.Headers,//路由信息RouteValues = request.RouteValues,//终结点信息Endpoint = endpointModel};var errorPage = new ErrorPage(model);//执行输出视图页面,也就是我们看到的开发者页面return errorPage.ExecuteAsync(context);
}

其整体实现思路就是捕获后续执行过程中出现的异常,如果出现异常则包装异常信息以及Http上下文和路由相关信息到ErrorPageModel模型中,然后这个模型作为异常展示界面的数据模型进行展示。

UseExceptionHandler

UseExceptionHandler可能是我们在实际开发过程中使用最多的方式。UseDeveloperExceptionPage固然强大,但是返回的终究还是供开发者使用的界面,通过UseExceptionHandler我们可以通过自己的方式处理异常信息,这里就需要我自己编码

app.UseExceptionHandler(configure =>
{configure.Run(async context =>{var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();var ex = exceptionHandlerPathFeature?.Error;if (ex != null){context.Response.ContentType = "text/plain;charset=utf-8";await context.Response.WriteAsync(ex.ToString());}});
});
//或
app.UseExceptionHandler(new ExceptionHandlerOptions
{ExceptionHandler = async context =>{var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();var ex = exceptionHandlerPathFeature?.Error;if (ex != null){context.Response.ContentType = "text/plain;charset=utf-8";await context.Response.WriteAsync(ex.ToString());}}
});
//或
app.UseExceptionHandler(new ExceptionHandlerOptions
{ExceptionHandlingPath = new PathString("/exception")
});

通过上面的实现方式我们大概可以猜出扩展方法的几种类型找到源码位置ExceptionHandlerExtensions扩展类

public static class ExceptionHandlerExtensions
{public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app){return app.UseMiddleware<ExceptionHandlerMiddleware>();}public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, string errorHandlingPath){return app.UseExceptionHandler(new ExceptionHandlerOptions{ExceptionHandlingPath = new PathString(errorHandlingPath)});}public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, Action<IApplicationBuilder> configure){//创建新的执行管道var subAppBuilder = app.New();configure(subAppBuilder);var exceptionHandlerPipeline = subAppBuilder.Build();return app.UseExceptionHandler(new ExceptionHandlerOptions{ExceptionHandler = exceptionHandlerPipeline});}public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, ExceptionHandlerOptions options){return app.UseMiddleware<ExceptionHandlerMiddleware>(Options.Create(options));}
}

通过UseExceptionHandler扩展方法我们可以知道这么多扩展方法其实本质都是在构建ExceptionHandlerOptions我们来看一下大致实现

public class ExceptionHandlerOptions
{/// <summary>/// 指定处理异常的终结点路径/// </summary>public PathString ExceptionHandlingPath { get; set; }/// <summary>/// 指定处理异常的终结点委托/// </summary>public RequestDelegate ExceptionHandler { get; set; }
}

这个类非常简单,要么指定处理异常信息的具体终结点路径,要么自定义终结点委托处理异常信息。通过上面的使用示例可以很清楚的看到这一点,接下来我们查看一下ExceptionHandlerMiddleware中间件的大致实现

public class ExceptionHandlerMiddleware
{private readonly RequestDelegate _next;private readonly ExceptionHandlerOptions _options;private readonly ILogger _logger;private readonly Func<object, Task> _clearCacheHeadersDelegate;private readonly DiagnosticListener _diagnosticListener;public ExceptionHandlerMiddleware(RequestDelegate next,ILoggerFactory loggerFactory,IOptions<ExceptionHandlerOptions> options,DiagnosticListener diagnosticListener){_next = next;_options = options.Value;_logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();_clearCacheHeadersDelegate = ClearCacheHeaders;_diagnosticListener = diagnosticListener;//ExceptionHandler和ExceptionHandlingPath不同同时不存在if (_options.ExceptionHandler == null){if (_options.ExceptionHandlingPath == null){throw new InvalidOperationException(Resources.ExceptionHandlerOptions_NotConfiguredCorrectly);}else{_options.ExceptionHandler = _next;}}}public Task Invoke(HttpContext context){ExceptionDispatchInfo edi;try{var task = _next(context);//task未完成情况if (!task.IsCompletedSuccessfully){return Awaited(this, context, task);}return Task.CompletedTask;}catch (Exception exception){edi = ExceptionDispatchInfo.Capture(exception);}return HandleException(context, edi);//处理未完成taskstatic async Task Awaited(ExceptionHandlerMiddleware middleware, HttpContext context, Task task){ExceptionDispatchInfo edi = null;try{//等待完成await task;}catch (Exception exception){//收集异常信息edi = ExceptionDispatchInfo.Capture(exception);}if (edi != null){await middleware.HandleException(context, edi);}}}
}

通过这段处理我们可以看出所有的异常处理都指向当前类的HandleException方法

private async Task HandleException(HttpContext context, ExceptionDispatchInfo edi)
{_logger.UnhandledException(edi.SourceException);// 如果输出已经开始执行了,后续的代码就没必要执行了,直接重新抛出if (context.Response.HasStarted){_logger.ResponseStartedErrorHandler();edi.Throw();}PathString originalPath = context.Request.Path;//如果指定处理异常的终结点,将异常处理交给指定的终结点去处理if (_options.ExceptionHandlingPath.HasValue){//将处理路径指向,异常处理终结点路径context.Request.Path = _options.ExceptionHandlingPath;}try{//清除原有HTTP上下文信息,为了明确指定程序出现异常,防止异常未被处理而后续当做正常操作执行ClearHttpContext(context);//将异常信息包装成ExceptionHandlerFeature,后续处理程序获取异常信息都是通过ExceptionHandlerFeaturevar exceptionHandlerFeature = new ExceptionHandlerFeature(){//异常信息Error = edi.SourceException,//原始路径Path = originalPath.Value,};//将包装的ExceptionHandlerFeature放入到上下文中,后续处理程序可通过HttpContext获取异常信息context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);//设置状态码context.Response.StatusCode = 500;context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);//调用给定的异常处理终结点处理异常信息await _options.ExceptionHandler(context);//同样也可以发送诊断日志,可以利用处理程序返回输出,诊断日志记录异常将职责分离if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled("Microsoft.AspNetCore.Diagnostics.HandledException")){_diagnosticListener.Write("Microsoft.AspNetCore.Diagnostics.HandledException", new { httpContext = context, exception = edi.SourceException });}return;}catch (Exception ex2){_logger.ErrorHandlerException(ex2);}finally{//异常处理结束后,恢复原始的请求路径,供后续执行程序能拿到原始的请求信息context.Request.Path = originalPath;}//如果异常没被处理则重新抛出edi.Throw();
}

最后还有一段清除上下文和清除输出缓存的方法,因为程序一旦发生了异常,可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算。而且异常信息保留输出缓存是没有意义的。

private static void ClearHttpContext(HttpContext context)
{context.Response.Clear();//因为可能创建了新的终结点,所以执行管道会有所调整,所以需要重新计算context.SetEndpoint(endpoint: null);var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();routeValuesFeature?.RouteValues?.Clear();
}private static Task ClearCacheHeaders(object state)
{//清除输出缓存相关var headers = ((HttpResponse)state).Headers;headers[HeaderNames.CacheControl] = "no-cache";headers[HeaderNames.Pragma] = "no-cache";headers[HeaderNames.Expires] = "-1";headers.Remove(HeaderNames.ETag);return Task.CompletedTask;
}

从上面的代码我们可以看出UseExceptionHandler要比UseDeveloperExceptionPage实现方式简单很多。其大致思路就是捕获后续管道执行异常,如果存在异常则将异常包装成ExceptionHandlerFeature,放入到Http上下文中。之所以相对简单主要原因还是UseExceptionHandler最终处理异常由我们自定义的终结点去处理,所以它只是负责包装异常相关信息,并将它交于我们定义的异常处理终结点。

UseStatusCodePages

无论是UseDeveloperExceptionPage还是UseExceptionHandler都是通过捕获异常的方式去处理异常信息,UseStatusCodePages则是通过Http状态码去判断是否为成功的返回并进行处理,使用方式如下

app.UseStatusCodePages();
//或
app.UseStatusCodePages("text/plain;charset=utf-8", "状态码:{0}");
//或
app.UseStatusCodePages(async context =>
{context.HttpContext.Response.ContentType = "text/plain;charset=utf-8";await context.HttpContext.Response.WriteAsync($"状态码:{context.HttpContext.Response.StatusCode}");
});
//或
app.UseStatusCodePages(new StatusCodePagesOptions { HandleAsync = async context=> {context.HttpContext.Response.ContentType = "text/plain;charset=utf-8";await context.HttpContext.Response.WriteAsync($"状态码:{context.HttpContext.Response.StatusCode}");
}});
//或
app.UseStatusCodePages(configure =>
{configure.Run(async context =>{await context.Response.WriteAsync($"状态码:{context.Response.StatusCode}");});
});

接下来我们查看一下UseStatusCodePages扩展方法相关实现

public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options)
{return app.UseMiddleware<StatusCodePagesMiddleware>(Options.Create(options));
}public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app)
{return app.UseMiddleware<StatusCodePagesMiddleware>();
}public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Func<StatusCodeContext, Task> handler)
{return app.UseStatusCodePages(new StatusCodePagesOptions{HandleAsync = handler});
}public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, string contentType, string bodyFormat)
{return app.UseStatusCodePages(context =>{var body = string.Format(CultureInfo.InvariantCulture, bodyFormat, context.HttpContext.Response.StatusCode);context.HttpContext.Response.ContentType = contentType;return context.HttpContext.Response.WriteAsync(body);});
}

虽然扩展方法比较多,但是本质都是组装StatusCodePagesOptions,所以我们直接查看源码

public class StatusCodePagesOptions
{public StatusCodePagesOptions(){//初始化HandleAsync = context =>{var statusCode = context.HttpContext.Response.StatusCode;var body = BuildResponseBody(statusCode);context.HttpContext.Response.ContentType = "text/plain";return context.HttpContext.Response.WriteAsync(body);};}private string BuildResponseBody(int httpStatusCode){//组装默认消息模板var internetExplorerWorkaround = new string(' ', 500);var reasonPhrase = ReasonPhrases.GetReasonPhrase(httpStatusCode);return string.Format(CultureInfo.InvariantCulture, "Status Code: {0}{1}{2}{3}",httpStatusCode,string.IsNullOrWhiteSpace(reasonPhrase) ? "" : "; ",reasonPhrase,internetExplorerWorkaround);}public Func<StatusCodeContext, Task> HandleAsync { get; set; }
}

看着代码不少,其实都是吓唬人的,就是给HandleAsync一个默认值,这个默认值里有默认的输出模板。接下来我们查看一下StatusCodePagesMiddleware中间件源码

public class StatusCodePagesMiddleware
{private readonly RequestDelegate _next;private readonly StatusCodePagesOptions _options;public StatusCodePagesMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options){_next = next;_options = options.Value;}public async Task Invoke(HttpContext context){//初始化StatusCodePagesFeaturevar statusCodeFeature = new StatusCodePagesFeature();context.Features.Set<IStatusCodePagesFeature>(statusCodeFeature);await _next(context);if (!statusCodeFeature.Enabled){return;}//这个范围外的Http状态码直接忽略,不受程序处理只处理值为400-600之间的状态码if (context.Response.HasStarted|| context.Response.StatusCode < 400|| context.Response.StatusCode >= 600|| context.Response.ContentLength.HasValue|| !string.IsNullOrEmpty(context.Response.ContentType)){return;}//将状态信息包装到StatusCodeContext,传递给自定义处理终结点var statusCodeContext = new StatusCodeContext(context, _options, _next);await _options.HandleAsync(statusCodeContext);}
}

这个中间件的实现思路更为简单,主要就是拦截请求判断Http状态码,判断是否是400-600,也就是4xx 5xx相关的状态码,如果符合则包装成StatusCodeContext,交由自定义的终结点去处理。

总结

关于常用异常处理中间件我们介绍到这里就差不多了,接下来我们总结对比一下三种中间件的异同和大致实现的方式

  • UseDeveloperExceptionPage中间件主要工作方式就是捕获后续中间件执行异常,如果存在异常则将异常信息包装成ErrorPageModel视图模型,然后通过这个模型去渲染开发者异常界面。

  • UseExceptionHandler中间件核心思路和UseDeveloperExceptionPage类似都是通过捕获后续中间件执行异常,不同之处在于UseExceptionHandler将捕获的异常信息包装到ExceptionHandlerFeature然后将其放入Http上下文中,后续的异常处理终结点通过Http上下文获取到异常信息进行处理。

  • UseStatusCodePages中间件相对于前两种中间件最为简单,其核心思路就是获取执行完成后的Http状态码判断是否是4xx 5xx相关,如果是则执行自定义的状态码拦截终结点。这个中间件核心是围绕StatusCode其实并不包含处理异常相关的逻辑,所以整体实现相对简单。

最后我们再来总结下使用中间件的方式和使用IExceptionFilter的方式的区别

  • 中间件的方式是对整个请求执行管道进行异常捕获,主要是负责整个请求过程中的异常捕获,其生命周期更靠前,捕获异常的范围更广泛。毕竟MVC只是Asp.Net Core终结点的一种实现方式,目前Asp.Net Core还可以处理GRPC Signalr等其它类型的终结点信息。

  • IExceptionFilter主要是针对Action执行过程中的异常,毕竟终结点只是中间件的一种形式,所以可处理范围比较有限,主要适用于MVC程序。对于其它终结点类型有点无能为力。

以上就是文章的全部内容,由于能力有限,如果存在理解不周之处请多多谅解。我觉得学习一个东西,如果你能了解到它的工作方式或者实现原理,肯定会对你的编程思路有所提升,看过的代码用过的东西可能会忘,但是思路一旦形成,将会改变你以后的思维方式。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309054.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

.NET Core加解密实战系列之——消息摘要与数字签名算法

简介加解密现状&#xff0c;编写此系列文章的背景&#xff1a;需要考虑系统环境兼容性问题&#xff08;Linux、Windows&#xff09;语言互通问题&#xff08;如C#、Java等&#xff09;&#xff08;加解密本质上没有语言之分&#xff0c;所以原则上不存在互通性问题&#xff09;…

造轮子-AgileConfig一个基于.NetCore开发的轻量级配置中心

微服务确实是行业的一个趋势&#xff0c;我自己也在把一些项目往微服务架构迁移。玩微服务架构配置中心是一个绕不过去的东西&#xff0c;有很多大牌的组件可以选&#xff0c;比如spring-cloud-config&#xff0c;apoll&#xff0c;disconf等等。而我为什么还要造一个轮子呢&am…

SQL Server 分页+json分享

1。SQL Server 版本2012 新增SQL分页的写法最近封装一个轻量级的ORM用到了分页&#xff0c;以前只知道使用Row_Number函数&#xff0c;现在发现sqlserver 新增的 {orderBy} offset {start} rows fetch next {pageSize} rows only 也挺好用的。简单回顾下 sqlserver 各个版本支持…

用十行代码快速创建权限管理系统

&#xff08;坚持做自己&#xff09;为了防止说是标题党&#xff0c;我先展示下真是就需要十行代码&#xff1a;当然还有appsettings.json配置文件&#xff0c;和种子数据文件&#xff0c;这个不算代码之内。1、项目背景介绍Blog.Core项目开源也两年了&#xff0c;经过了很多许…

ERP的配置管理实践

源宝导读&#xff1a;随着ERP系统的日益复杂&#xff0c;应用部署的方式越来越复杂&#xff0c;应用的配置也变得越来越庞杂&#xff0c;难以维护和管理。本文将介绍配置中心服务通过集中化、可离线的架构设计&#xff0c;解决ERP配置问题的实践经验。一、背景随着ERP业务的日益…

《LIO-SAM阅读笔记》1.IMU预积分模块

前言&#xff1a; LIO-SAM是一个多传感器融合的紧耦合SLAM框架&#xff0c;融合的传感器类型有雷达、IMU和GPS&#xff0c;其中雷达和IMU在LIO-SAM框架中必须使用的。LIO-SAM的优化策略采用了GTSAM库&#xff0c;GTSAM库采用了因子图的优化方法&#xff0c;其提供了一些列C的外…

EntityFramework Core 迁移忽略主外键关系

【导读】本文来源于一位公众号童鞋私信我的问题&#xff0c;在我稍加思索后给出了如下一种方案&#xff0c;在此之前我也思考过这个问题&#xff0c;借此机会我稍微看了下&#xff0c;目前能够想到的也只是本文所述方案。为何要忽略主外键关系我们不仅疑惑为何要忽略主外键关系…

你很可能需要知道这个调试小技巧

缘起 最近在调试的时候&#xff0c;需要观察第三方容器中每一个元素的值。默认情况下&#xff0c;vs 并不知道如何显示第三方容器的内容&#xff0c;只能手动观察容器中的每一个值&#xff0c;超级不方便。我找到一个非常给力的好办法&#xff0c;你还知道其它好办法吗&#xf…

全宇宙首本 VS Code 中文书,来了!

大家好&#xff01;我是韩骏&#xff0c;VS Code 中文社区创始人&#xff0c;VS Code 的代码贡献者。2013 年&#xff0c;毕业于上海交通大学软件学院&#xff0c;现在是微软开发平台事业部的软件工程师。写过 20 多款 VS Code 插件&#xff0c;其中最热门的 Code Runner 插件有…

C# 从1到Core--委托与事件

委托与事件在C#1.0的时候就有了&#xff0c;随着C#版本的不断更新&#xff0c;有些写法和功能也在不断改变。本文温故一下这些改变&#xff0c;以及在NET Core中关于事件的一点改变。一、C#1.0 从委托开始1. 基本方式什么是委托&#xff0c;就不说概念了&#xff0c;用例子说话…

开源导入导出库Magicodes.IE 多sheet导入教程

多Sheet导入教程说明本教程主要说明如何使用Magicodes.IE.Excel完成多个Sheet数据的Excel导入。要点多个相同格式的Sheet数据导入多个不同格式的Sheet数据导入主要步骤1. 多个相同格式的Sheet数据导入1.1 创建导入Sheet的Dto主要代码如下所示&#xff1a;学生数据Dto/// <su…

解决 Azure AD 在 Azure Front Door 下登录失败的问题

点击上方关注“汪宇杰博客” ^_^导语最近我给博客系统加上了 Azure Front Door&#xff0c;集齐了12项 Azure 服务打算召唤神龙。没想到刚上线&#xff0c;Azure AD 的单点登录就爆了。OIDC 跳转错误当我尝试登录博客后台的时候&#xff0c;OIDC的跳转URL突然变成了 https://ed…

《Unit Testing》2.1 经典学派如何做测试隔离

经典学派如何解决隔离问题首先&#xff0c;再回顾一下单元测试的三个重要特性&#xff1a;验证一小段代码&#xff08;或者叫一个单元&#xff09;执行速度快使用隔离的方式进行针对第一个特性就会引出一个问题&#xff1a;多小的一段代码才足够小&#xff1f;如果你采用针对每…

Hacker News热文:请停止学习框架,学习领域驱动设计(DDD)(获500个点赞)

在 Hacker News 上获得接近 500 个点赞的一篇名为《停止学习框架》的文章称&#xff1a;我们是程序员&#xff0c;每天都在了解最新的技术&#xff0c;每天都在学习编程语言、框架和库&#xff0c;因为我们知道的现代编程工具越多越好&#xff0c;对吧&#xff1f;不停地追随 A…

C#跨平台开源项目实战(WPF/Android/IOS/Blazor)

个人介绍由于本人从业WPF开发, 考虑到国内的WPF开发环境并不是很好, 资源少、项目案例少, 所以导致很多初学者就已经断了念头。所以我作为WPF的从业者, 就在2019年,开始了发布自己的WPF相关的免费教学视频。发布开源的项目实践, WPF的基础视频、项目实践视频, 包括WPF UI设计视…

[开源] .Net 使用 ORM 访问 神舟通用数据库(神通)

前言天津神舟通用数据技术有限公司&#xff08;简称“神舟通用公司”&#xff09;&#xff0c;隶属于中国航天科技集团&#xff08;CASC&#xff09;。是国内从事数据库、大数据解决方案和数据挖掘分析产品研发的专业公司。公司获得了国家核高基科技重大专项重点支持&#xff0…

在 Xunit 中使用依赖注入

在 Xunit 中使用依赖注入Intro之前写过一篇 xunit 的依赖注入相关的文章&#xff0c;但是实际使用起来不是那么方便今天介绍一个基于xunit和微软依赖注入框架的“真正”的依赖注入使用方式 ——— Xunit.DependencyInjection, 来自大师的作品&#xff0c;让你在测试代码里使用依…

C#由转换二进制所引起的思考,了解下?

【导读】最近遇到很有意思转换二进制的问题&#xff0c;有部分童鞋俨然已了解&#xff0c;可能也有一部分童鞋没碰到过也就不知情&#xff0c;这里我们来深入学习下转换二进制所带来的问题。在写此篇文章时&#xff0c;非常开心&#xff0c;收到再一次连任MVP的邮件&#xff0c…

.Net Core In Docker 在容器内编译并发布

Docker可以说是现在微服务&#xff0c;DevOps的基础&#xff0c;咱们.Net Core自然也得上Docker。.Net Core发布到Docker容器的教程网上也有不少&#xff0c;但是今天还是想来写一写。你搜.Net core程序发布到Docker网上一般常见的有两种方案&#xff1a;1、在本地编译成Dll文件…

带你深入探究云原生时代的分布式操作系统 Kubernetes

过去几年&#xff0c;以 docker、kubernetes 为代表的容器技术已发展为一项通用技术&#xff0c;BAT、滴滴、京东、头条等大厂&#xff0c;都争相把容器和 k8s 项目作为技术重心&#xff0c;试图“放长线钓大鱼”。就说腾讯吧&#xff0c;目前基本所有业务都跑在云上&#xff0…