200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]

2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为《ASP.NET Core框架揭秘》的分享。在此次分享中,我按照ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架,并且利用这个 “极简” 的模拟框架阐述了ASP.NET Core框架最核心、最本质的东西。整个框架涉及到的核心代码不会超过200行,涉及到7个核心的对象。由于ASP.NET Core 3.X采用了不同的应用承载方式,所以我们将这个模拟框架升级到3.x版本。[本篇内容节选自即将出版的《ASP.NET Core 3框架解密》,感兴趣的朋友可以通过《“ASP.NET Core 3框架揭秘”读者群,欢迎加入》加入本书读者群,以便及时了解本书的动态。源代码从这里下载。]

目录
一、中间件委托链
     HttpContext
     中间件
     中间件管道的构建
二、服务器
     IServer
     针对服务器的适配
     HttpListenerServer
三、承载服务
     WebHostedService
     WebHostBuilder
     应用构建

一、中间件委托链

通过本篇文章,我将管道最核心的部分提取出来构建一个“迷你版”的ASP.NET Core框架。较之真正的ASP.NET Core框架,虽然重建的模拟框架要简单很多,但是它们采用完全一致的设计。为了能够在真实框架中找到对应物,在定义接口或者类型时会采用真实的名称,但是在API的定义上会做最大限度的简化。

HttpContext

一个HttpContext对象表示针对当前请求的上下文。要理解HttpContext上下文的本质,需要从请求处理管道的层面来讲。对于由一个服务器和多个中间件构成的管道来说,面向传输层的服务器负责请求的监听、接收和最终的响应,当它接收到客户端发送的请求后,需要将请求分发给后续中间件进行处理。对于某个中间件来说,完成自身的请求处理任务之后,在大部分情况下需要将请求分发给后续的中间件。请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。

如下图所示,当服务器接收到请求之后,会创建一个通过HttpContext表示的上下文对象,所有中间件都在这个上下文中完成针对请求的处理工作。那么一个HttpContext对象究竟会携带什么样的上下文信息?一个HTTP事务(Transaction)具有非常清晰的界定,如果从服务器的角度来说就是始于请求的接收,而终于响应的回复,所以请求和响应是两个基本的要素,也是HttpContext承载的最核心的上下文信息。

我们可以将请求和响应理解为一个Web应用的输入与输出,既然HttpContext上下文是针对请求和响应的封装,那么应用程序就可以利用这个上下文对象得到当前请求所有的输入信息,也可以利用它完成我们所需的所有输出工作。所以,我们为ASP.NET Core模拟框架定义了如下这个极简版本的HttpContext类型。

public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
{private readonly HttpListenerContext _context;public HttpListenerFeature(HttpListenerContext context)=> _context = context;Uri IHttpRequestFeature.Url=> _context.Request.Url;NameValueCollection IHttpRequestFeature.Headers=> _context.Request.Headers;NameValueCollection IHttpResponseFeature.Headers=> _context.Response.Headers;Stream IHttpRequestFeature.Body=> _context.Request.InputStream;Stream IHttpResponseFeature.Body=> _context.Response.OutputStream;int IHttpResponseFeature.StatusCode{get => _context.Response.StatusCode;set => _context.Response.StatusCode = value;}
}

如上面的代码片段所示,我们可以利用HttpRequest对象得到当前请求的地址、请求消息的报头集合和主体内容。利用HttpResponse对象,我们不仅可以设置响应的状态码,还可以添加任意的响应报头和写入任意的主体内容。

中间件

HttpContext对象承载了所有与当前请求相关的上下文信息,应用程序针对请求的响应也利用它来完成,所以可以利用一个Action<HttpContext>类型的委托对象来表示针对请求的处理,我们姑且将它称为请求处理器(Handler)。但Action<HttpContext>仅仅是请求处理器针对“同步”编程模式的表现形式,对于面向Task的异步编程模式,这个处理器应该表示成类型为Func<HttpContext,Task>的委托对象。

由于这个表示请求处理器的委托对象具有非常广泛的应用,所以我们为它专门定义了如下这个RequestDelegate委托类型,可以看出它就是对Func<HttpContext,Task>委托的表达。一个RequestDelegate对象表示的是请求处理器,那么中间件在模型中应如何表达?

public delegate Task RequestDelegate(HttpContext context);

作为请求处理管道核心组成部分的中间件可以表示成类型为Func<RequestDelegate, RequestDelegate>的委托对象。换句话说,中间件的输入与输出都是一个RequestDelegate对象。我们可以这样来理解:对于管道中的某个中间件(下图所示的第一个中间件)来说,后续中间件组成的管道体现为一个RequestDelegate对象,由于当前中间件在完成了自身的请求处理任务之后,往往需要将请求分发给后续中间件进行处理,所以它需要将后续中间件构成的RequestDelegate对象作为输入。

当代表当前中间件的委托对象执行之后,如果将它自己“纳入”这个管道,那么代表新管道的RequestDelegate对象就成为该委托对象执行后的输出结果,所以中间件自然就表示成输入和输出类型均为RequestDelegate的Func<RequestDelegate, RequestDelegate>对象。

中间件管道的构建

从事软件行业10多年来,笔者对架构设计越来越具有这样的认识:好的设计一定是“简单”的设计。所以在设计某个开发框架时笔者的目标是再简单点。上面介绍的请求处理管道的设计就具有“简单”的特质:Pipeline = Server + Middlewares。但是“再简单点”其实是可以的,我们可以将多个中间件组成一个单一的请求处理器。请求处理器可以通过RequestDelegate对象来表示,所以整个请求处理管道将具有更加简单的表达:Pipeline = Server + RequestDelegate(见下图12)。

表示中间件的Func<RequestDelegate, RequestDelegate>对象向表示请求处理器的RequestDelegate对象之间的转换是通过IApplicationBuilder对象来完成的。从接口命名可以看出,IApplicationBuilder对象是用来构建“应用程序”(Application)的,实际上,由所有注册中间件构建的RequestDelegate对象就是对应用程序的表达,因为应用程序的意图完全是由注册的中间件达成的。

public interface IApplicationBuilder
{RequestDelegate Build();IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}

如上所示的代码片段是模拟框架对IApplicationBuilder接口的简化定义。它的Use方法用来注册中间件,而Build方法则将所有的中间件按照注册的顺序组装成一个RequestDelegate对象。如下所示的代码片段中ApplicationBuilder类型是对该接口的默认实现。我们给出的代码片段还体现了这样一个细节:当我们将注册的中间件转换成一个表示请求处理器的RequestDelegate对象时,会在管道的尾端添加一个处理器用来响应一个状态码为404的响应。这个细节意味着如果没有注册任何的中间件或者所有注册的中间件都将请求分发给后续管道,那么应用程序会回复一个状态码为404的响应。

public class ApplicationBuilder : IApplicationBuilder
{private readonly IList<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();public RequestDelegate Build(){RequestDelegate next = context =>{context.Response.StatusCode = 404;return Task.CompletedTask;};foreach (var middleware in _middlewares.Reverse()){next = middleware.Invoke(next);}return next;}public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){_middlewares.Add(middleware);return this;}
}

二、服务器

服务器在管道中的职责非常明确:负责HTTP请求的监听、接收和最终的响应。具体来说,启动后的服务器会绑定到指定的端口进行请求监听。一旦有请求抵达,服务器会根据该请求创建代表请求上下文的HttpContext对象,并将该上下文分发给注册的中间件进行处理。当中间件管道完成了针对请求的处理之后,服务器会将最终生成的响应回复给客户端。

IServer

在模拟的ASP.NET Core框架中,我们将服务器定义成一个极度简化的IServer接口。在如下所示的代码片段中,IServer接口具有唯一的StartAsync方法来启动自身代表的服务器。服务器最终需要将接收的请求分发给注册的中间件,而注册的中间件最终会被IApplicationBuilder对象构建成一个代表请求处理器的RequestDelegate对象,StartAsync方法的参数handler代表的就是这样一个对象。

public interface IServer
{Task StartAsync(RequestDelegate handler);
}

针对服务器的适配

面向应用层的HttpContext对象是对请求和响应的抽象与封装,但是请求最初是由面向传输层的服务器接收的,最终的响应也会由服务器回复给客户端。所有ASP.NET Core应用使用的都是同一个HttpContext类型,但是它们可以注册不同类型的服务器,应如何解决两者之间的适配问题?计算机领域有这样一句话:“任何问题都可以通过添加一个抽象层的方式来解决,如果解决不了,那就再加一层。”同一个HttpContext类型与不同服务器类型之间的适配问题自然也可以通过添加一个抽象层来解决。我们将定义在该抽象层的对象称为特性(Feature),特性可以视为对HttpContext某个方面的抽象化描述。

如上图所示,我们可以定义一系列特性接口来为HttpContext提供某个方面的上下文信息,具体的服务器只需要实现这些Feature接口即可。对于所有用来定义特性的接口,最重要的是提供请求信息的IRequestFeature接口和完成响应的IResponseFeature接口。

下面阐述用来适配不同服务器类型的特性在代码层面的定义。如下面的代码片段所示,我们定义了一个IFeatureCollection接口来表示存放特性的集合。可以看出,这是一个以Type和Object作为Key和Value的字典,Key代表注册Feature所采用的类型,而Value代表Feature对象本身,也就是说,我们提供的特性最终是以对应类型(一般为接口类型)进行注册的。为了便于编程,我们定义了Set<T>方法和Get<T>方法来设置与获取特性对象。

public interface IFeatureCollection : IDictionary<Type, object> { }
public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }
public static partial class Extensions
{public static T Get<T>(this IFeatureCollection features)  => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature){features[typeof(T)] = feature;return features;}
}

最核心的两种特性类型就是分别用来表示请求和响应的特性,我们可以采用如下两个接口来表示。可以看出,IHttpRequestFeature接口和IHttpResponseFeature接口具有与抽象类型HttpRequest和HttpResponse完全一致的成员定义。

public interface IHttpRequestFeature
{Uri Url { get; }NameValueCollection Headers { get; }Stream Body { get; }
}
public interface IHttpResponseFeature
{int StatusCode { get; set; }NameValueCollection Headers { get; }Stream Body { get; }
}

我们在前面给出了用于描述请求上下文的HttpContext类型的成员定义,下面介绍其具体实现。如下面的代码片段所示,表示请求和响应的HttpRequest与HttpResponse分别是由对应的特性(IHttpRequestFeature对象和IHttpResponseFeature对象)创建的。HttpContext对象本身则是通过一个表示特性集合的IFeatureCollection 对象来创建的,它会在初始化过程中从这个集合中提取出对应的特性来创建HttpRequest对象和HttpResponse对象。

public class HttpContext
{public HttpRequest Request { get; }public HttpResponse Response { get; }public HttpContext(IFeatureCollection features){Request = new HttpRequest(features);Response = new HttpResponse(features);}
}public class HttpRequest
{private readonly IHttpRequestFeature _feature;public Uri Url=> _feature.Url;public NameValueCollection Headers=> _feature.Headers;public Stream Body=> _feature.Body;public HttpRequest(IFeatureCollection features)=> _feature = features.Get<IHttpRequestFeature>();
}public class HttpResponse
{private readonly IHttpResponseFeature _feature;public NameValueCollection Headers=> _feature.Headers;public Stream Body=> _feature.Body;public int StatusCode{get => _feature.StatusCode;set => _feature.StatusCode = value;}public HttpResponse(IFeatureCollection features)=> _feature = features.Get<IHttpResponseFeature>();
}

换句话说,我们利用HttpContext对象的Request属性提取的请求信息最初来源于IHttpRequestFeature对象,利用它的Response属性针对响应所做的任意操作最终都会作用到IHttpResponseFeature对象上。这两个对象最初是由注册的服务器提供的,这正是同一个ASP.NET Core应用可以自由地选择不同服务器类型的根源所在。

HttpListenerServer

在对服务器的职责和它与HttpContext的适配原理有了清晰的认识之后,我们可以尝试定义一个服务器。我们将接下来定义的服务器类型命名为HttpListenerServer,因为它对请求的监听、接收和响应是由一个HttpListener对象来实现的。由于服务器接收到请求之后需要借助“特性”的适配来构建统一的请求上下文(即HttpContext对象),这也是中间件的执行上下文,所以提供针对性的特性实现是自定义服务类型的关键所在。

对HttpListener有所了解的读者都知道,当它在接收到请求之后同样会创建一个HttpListenerContext对象表示请求上下文。如果使用HttpListener对象作为ASP.NET Core应用的监听器,就意味着不仅所有的请求信息会来源于这个HttpListenerContext对象,我们针对请求的响应最终也需要利用这个上下文对象来完成。HttpListenerServer对应特性所起的作用实际上就是在HttpListenerContext和HttpContext这两种上下文之间搭建起一座如下图所示的桥梁。

上图中用来在HttpListenerContext和HttpContext这两个上下文类型之间完成适配的特性类型被命名为HttpListenerFeature。如下面的代码片段所示,HttpListenerFeature类型同时实现了针对请求和响应的特性接口IHttpRequestFeature与IHttpResponseFeature。

public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
{private readonly HttpListenerContext _context;public HttpListenerFeature(HttpListenerContext context) => _context = context;Uri IHttpRequestFeature.Url => _context.Request.Url;NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;Stream IHttpRequestFeature.Body => _context.Request.InputStream;Stream IHttpResponseFeature.Body => _context.Response.OutputStream;int IHttpResponseFeature.StatusCode{get => _context.Response.StatusCode;set => _context.Response.StatusCode = value;}
}

创建HttpListenerFeature对象时需要提供一个HttpListenerContext对象,IHttpRequestFeature接口的实现成员所提供的请求信息全部来源于这个HttpListenerContext上下文,IHttpResponseFeature接口的实现成员针对响应的操作最终也转移到这个HttpListenerContext上下文上。如下所示的代码片段是针对HttpListener的服务器类型HttpListenerServer的完整定义。我们在创建HttpListenerServer对象的时候可以显式提供一组监听地址,如果没有提供,监听地址会默认设置“localhost:5000”。在实现的StartAsync方法中,我们启动了在构造函数中创建的HttpListenerServer对象,并且在一个无限循环中通过调用其GetContextAsync方法实现了针对请求的监听和接收。

public class HttpListenerServer : IServer
{private readonly HttpListener _httpListener;private readonly string[] _urls;public HttpListenerServer(params string[] urls){_httpListener = new HttpListener();_urls = urls.Any() ? urls : new string[] { "http://localhost:5000/" };}public async Task StartAsync(RequestDelegate handler){Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));_httpListener.Start();while (true){var listenerContext = await _httpListener.GetContextAsync();var feature = new HttpListenerFeature(listenerContext);var features = new FeatureCollection().Set<IHttpRequestFeature>(feature).Set<IHttpResponseFeature>(feature);var httpContext = new HttpContext(features);await handler(httpContext);listenerContext.Response.Close();}}
}

当HttpListener监听到抵达的请求后,我们会得到一个HttpListenerContext对象,此时只需要利用它创建一个HttpListenerFeature对象并且分别以IHttpRequestFeature接口和IHttpResponseFeature接口的形式注册到创建的FeatureCollection集合上。我们最终利用这个FeatureCollection集合创建出代表请求上下文的HttpContext对象,当将它作为参数调用由所有注册中间件共同构建的RequestDelegate对象时,中间件管道将接管并处理该请求。

三、承载服务

到目前为止,我们已经了解构成ASP.NET Core请求处理管道的两个核心要素(服务器和中间件),现在我们的目标是利用.NET Core承载服务系统来承载这一管道。毫无疑问,还需要通过实现IHostedService接口来定义对应的承载服务,为此我们定义了一个名为WebHostedService的承载服务。(关于.NET Core承载服务系统,请参阅我的系列文章《服务承载系统》)

WebHostedService

由于服务器是整个请求处理管道的“龙头”,所以从某种意义上来说,启动一个ASP.NET Core应用就是为启动服务器,所以可以将服务的启动在WebHostedService承载服务中实现。如下面的代码片段所示,创建一个WebHostedService对象时,需要提供服务器对象和由所有注册中间件构建的RequestDelegate对象。在实现的StartAsync方法中,我们只需要调用服务器对象的StartAsync方法启动它即可。

public class WebHostedService : IHostedService
{private readonly IServer _server;private readonly RequestDelegate _handler;public WebHostedService(IServer server, RequestDelegate handler){_server = server;_handler = handler;}public Task StartAsync(CancellationToken cancellationToken) => _server.StartAsync(_handler);public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

到目前为止,我们基本上已经完成了所有核心的工作,如果能够将一个WebHostedService实例注册到.NET Core的承载系统中,它就能够帮助我们启动一个ASP.NET Core应用。为了使这个过程在编程上变得更加便利和“优雅”,我们定义了一个辅助的WebHostBuilder类型。

WebHostBuilder

要创建一个WebHostedService对象,必需显式地提供一个表示服务器的IServer对象,以及由所有注册中间件构建而成的RequestDelegate对象,WebHostBuilder提供了更加便利和“优雅”的服务器与中间件注册方式。如下面的代码片段所示,WebHostBuilder是对额外两个Builder对象的封装:一个是用来构建服务宿主的IHostBuilder对象,另一个是用来注册中间件并最终帮助我们创建RequestDelegate对象的IApplicationBuilder对象。

public class WebHostBuilder
{public IHostBuilder HostBuilder { get; }public IApplicationBuilder ApplicationBuilder { get; }public WebHostBuilder(IHostBuilder hostBuilder, IApplicationBuilder applicationBuilder){HostBuilder = hostBuilder;ApplicationBuilder = applicationBuilder;}
}

我们为WebHostBuilder定义了如下两个扩展方法:UseHttpListenerServer方法完成了针对自定义的服务器类型HttpListenerServer的注册;Configure方法提供了一个Action<IApplication
Builder>类型的参数,利用该参数来注册任意中间件。

public static partial class Extensions
{public static WebHostBuilder UseHttpListenerServer(this WebHostBuilder builder, params string[] urls){builder.HostBuilder.ConfigureServices(svcs => svcs.AddSingleton<IServer>(new HttpListenerServer(urls)));return builder;}public static WebHostBuilder Configure(this WebHostBuilder builder, Action<IApplicationBuilder> configure){configure?.Invoke(builder.ApplicationBuilder);return builder;}
}

代表ASP.NET Core应用的请求处理管道最终是利用承载服务WebHostedService注册到.NET Core的承载系统中的,针对WebHostedService服务的创建和注册体现在为IHostBuilder接口定义的ConfigureWebHost扩展方法上。如下面的代码片段所示,ConfigureWebHost方法定义了一个Action<WebHostBuilder>类型的参数,利用该参数可以注册服务器、中间件及其他相关服务。

public static partial class Extensions
{public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<WebHostBuilder> configure){var webHostBuilder = new WebHostBuilder(builder, new ApplicationBuilder());configure?.Invoke(webHostBuilder);builder.ConfigureServices(svcs => svcs.AddSingleton<IHostedService>(provider => {var server = provider.GetRequiredService<IServer>();var handler = webHostBuilder.ApplicationBuilder.Build();return new WebHostedService(server, handler);}));return builder;}
}

在ConfigureWebHost方法中,我们创建了一个ApplicationBuilder对象,并利用它和当前的IHostBuilder对象创建了一个WebHostBuilder对象,然后将这个WebHostBuilder对象作为参数调用了指定的Action<WebHostBuilder>委托对象。在此之后,我们调用IHostBuilder接口的ConfigureServices方法在依赖注入框架中注册了一个用于创建WebHostedService服务的工厂。对于由该工厂创建的WebHostedService对象来说,服务器来源于注册的服务,而作为请求处理器的RequestDelegate对象则由ApplicationBuilder对象根据注册的中间件构建而成。

应用构建

到目前为止,这个用来模拟ASP.NET Core请求处理管道的“迷你版”框架已经构建完成,下面尝试在它上面开发一个简单的应用。如下面的代码片段所示,我们调用静态类型Host的CreateDefaultBuilder方法创建了一个IHostBuilder对象,然后调用ConfigureWebHost方法并利用提供的Action<WebHostBuilder>对象注册了HttpListenerServer服务器和3个中间件。在调用Build方法构建出作为服务宿主的IHost对象之后,我们调用其Run方法启动所有承载的IHostedSerivce服务。

class Program
{static void Main(){Host.CreateDefaultBuilder().ConfigureWebHost(builder => builder.UseHttpListenerServer().Configure(app => app.Use(FooMiddleware).Use(BarMiddleware).Use(BazMiddleware))).Build().Run();}public static RequestDelegate FooMiddleware(RequestDelegate next)=> async context =>{await context.Response.WriteAsync("Foo=>");await next(context);};public static RequestDelegate BarMiddleware(RequestDelegate next)=> async context =>{await context.Response.WriteAsync("Bar=>");await next(context);};public static RequestDelegate BazMiddleware(RequestDelegate next)=> context => context.Response.WriteAsync("Baz");
}

由于中间件最终体现为一个类型为Func<RequestDelegate, RequestDelegate>的委托对象,所以可以利用与之匹配的方法来定义中间件。演示实例中定义的3个中间件(FooMiddleware、BarMiddleware和BazMiddleware)对应的正是3个静态方法,它们调用WriteAsync扩展方法在响应中写了一段文字。

public static partial class Extensions
{public static Task WriteAsync(this HttpResponse response, string contents){var buffer = Encoding.UTF8.GetBytes(contents);return response.Body.WriteAsync(buffer, 0, buffer.Length);}
}

应用启动之后,如果利用浏览器向应用程序采用的默认监听地址(“http://localhost:5000”)发送一个请求,得到的输出结果如下图所示。浏览器上呈现的文字正是注册的3个中间件写入的。

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

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

相关文章

逻辑回归(二)

逻辑回归 在学习逻辑回归之前我们先回顾一下线性回归。线性回归解决的是回归问题&#xff0c;简单来说就是&#xff0c;我们需要找到一个函数&#xff0c;这个函数需要尽可能的拟合所有训练集的样本点。 逻辑回归解决的是分类问题&#xff0c;它的目标是找到一个函数&#x…

上元节的灯会(灭)-区间dp

题目背景 上元节的庙会上&#xff0c;牛宝靠自己的聪明才智成功破解了花灯阵&#xff0c;点亮了在场所有花灯&#xff0c;但他没料到的是这个游戏包含AB两个项目&#xff0c;A项目就是点亮所有花灯&#xff0c;而B项目则是熄灭所有花灯。不过点亮的是花灯阵&#xff0c;熄灭的…

Asp.Net Core 中IdentityServer4 授权中心之应用实战

一、前言查阅了大多数相关资料&#xff0c;搜索到的IdentityServer4 的应用文章大多是比较简单并且多是翻译官网的文档编写的&#xff0c;我这里在 Asp.Net Core 中IdentityServer4 的应用分析中会以一个电商系统架构升级过程中普遍会遇到的场景进行实战性讲述分析&#xff0c;…

交通标志识别项目教程

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 安装Anaconda 安装Pycharm 安装格式工厂 在上图中这个位置输入cmd回车&#xff0c;即可打开命令终端。用这样的方式打开命令终端…

C# 视频监控系统

去过工厂或者仓库的都知道&#xff0c;在工厂或仓库里面&#xff0c;会有很多不同的流水线&#xff0c;大部分的工厂或仓库&#xff0c;都会在不同流水线的不同工位旁边安装一台电脑&#xff0c;一方面便于工位上的师傅把产品的重要信息录入系统&#xff0c;便于公司系统数据采…

sklearn svm如何选择核函数_机器学习之支持向量机多种核模型对比

机器学习xueyifeiyun1989zx&#xff0c;公众号&#xff1a;围着围巾的小黑机器学习之监督学习实战前文我们提到机器学习中的监督学习&#xff0c;其中有一个模型是我们提到的但是没有训练测试的&#xff0c;叫做支持向量机(简称SVM)。支持向量机也是监督学习里面一个非常容易理…

程序员过关斩将--从每秒6000写请求谈起

点击上方“蓝字”关注我们菜菜哥&#xff0c;紧急求助呀怎么回事&#xff1f;产品经理砍你了&#xff1f;没有&#xff0c;只是写了个新项目&#xff0c;上线就被压垮了什么功能&#xff0c;这么强悍&#xff1f;一个记录用户观看视频进度信息的功能那如果用户基数大&#xff0…

批量将PPM格式图片转化为JPG格式

将PPM格式图片转化为JPG格式 做图像识别的时候数据集常常是ppm格式的&#xff0c;虽然不影响建模训练&#xff0c;但是我们电脑往往不支持ppm格式的图像展示。 比如到做交通标志识别的时候用到的BelgiumTS交通数据集或者德国GTSRB数据集 下载后得到都是ppm格式的图像。 格式转…

Magicodes.IE 2.2里程碑需求和建议征集

简介Magicodes.IE是导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。已加入NCC开源组织。Magicodes.IE 2.0发布Github&#xff1a;https://github.com/dotnetcore/Magicodes.IE码云&#xff08;手动同步&#xff0c;不维护…

交通标志识别教程(二)

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 解压软件包 安装Anaconda 直接下一步&#xff0c;到了这个页面全部勾选&#xff0c;否则不会添加添加环境变量。 安装Pycharm …

深度长文:Power Automation 帮助企业实现数字化转型

01自动化始于您在Ignite 2019上&#xff0c;我们宣布将Flow更改为Power Automate&#xff0c;并在UI Flow连接器的公开预览中引入了机器人流程自动化&#xff08;RPA&#xff09;。我们对几种激动人心的功能感到兴奋&#xff0c;这些功能将在今年全面上市&#xff0c;并想花一点…

什么样的女生适合学计算机?

我需要在这一行中加一些字数&#xff0c;为什么呢&#xff1f;因为我的字数不够300字&#xff0c;无法声明原创&#xff0c;所以我会在这里加一些字数&#xff0c;它们是白色的&#xff0c;你应该看不到&#xff0c;如果你此刻看到了&#xff0c;那你真的太机智了。300字&#…

ora-00923数据类型不一致_小白学 Python(2):基础数据类型(上)

如果我的文章对您有帮助&#xff0c;请关注支持下作者的公众号&#xff1a;极客挖掘机&#xff0c;获取最新干货推送&#xff1a;)人生苦短&#xff0c;我选Python引言前文传送门小白学 Python(1)&#xff1a;开篇接触一门新的语言&#xff0c;肯定要先了解它的基础数据类型。啥…

如何将项目上传到github详细完整版

今天介绍如何利用pycharm创建一个新的项目&#xff0c;然后将项目上传到github&#xff0c;以便日后的学习记录&#xff0c;和版本管理。比如现在我想创建一个项目专门用来学习和研究时间序列算法。 创建虚拟环境 # 创建一个新的虚拟环境 conda create -n TimeSeries python3…

[Abp vNext微服务实践] - 搭建租户管理服务

一、简介ABP模板项目中已经提供了租户登录和管理功能&#xff0c;但是模板项目是单体应用结构&#xff0c;无法单独部署租户服务&#xff0c;所以难以满足微服务的需求。本篇文章将会介绍搭建ABP租户管理服务&#xff0c;并单独部署应用。二、创建工程2.1 创建TenantService.Ho…

编写高性能的C#代码(三)使用SPAN

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。作者介绍&#xff1a;史蒂夫戈登&#xff08;Steve Gordon&#xff09;是Microsoft MVP&#xff0c;Pluralsight的作者&#xff0c;…

pycharm配置git拉取项目代码,并添加版本控制

安装Git 打开网页进入git官网&#xff0c;找到git官网下载地址&#xff0c;下载git工具并且安装。 pycharm配置git 点击File -> Settings -> Version Control -> Git 选择Git安装的路径&#xff0c;点击OK 选择一个项目 进入我们需要拉取的项目&#xff0c;点击…

.NET Core开发实战(第22课:异常处理中间件:区分真异常与逻辑异常)--学习笔记(上)...

22 | 异常处理中间件&#xff1a;区分真异常与逻辑异常这一节我们来讲解一下错误处理的最佳实践系统里面异常处理&#xff0c;ASP.NET Core 提供了四种方式1、异常处理页2、异常处理匿名委托方法3、IExceptionFilter4、ExceptionFilterAttribute源码链接&#xff1a;https://gi…

MYSQL开窗函数详解

基本概念 MYSQL8.0支持窗口函数&#xff08;Window Function&#xff09;&#xff0c;也称分析函数。窗口函数与组分聚合函数类似&#xff0c;但是每一行数据都会生成一个结果。如果我们将mysql与pandas中的DataFrame做类比学习的话他们的对应关系如下&#xff1a; SQL分组聚…

你可能需要了解一下的中台

【中台学习】| 作者 / Edison Zhou这是恰童鞋骚年的第201篇原创文章在数字化转型热潮下&#xff0c;各家企业都想建设中台&#xff0c;那么中台是怎么发展起来的&#xff1f;有哪些类型的中台&#xff1f;中台到底是个啥&#xff1f;本文为你一一解答这些问题。1学习背景与前言…