概念

在ASP.NET Core中我们可以使用一种机制来增强启动时的操作,它就是HostingStartup。如何叫"增强"操作,相信了解过AOP概念的同学应该都非常的熟悉。我们常说AOP使用了关注点分离的方式,增强了对现有逻辑的操作。而我们今天要说的HostingStartup就是为了"增强"启动操作,这种"增强"的操作甚至可以对现有的程序可以做到无改动的操作。例如,外部程序集可通过HostingStartup实现为应用提供配置服务、注册服务或中间件管道操作等。

使用方式

HostingStartup属性表示要在运行时激活的承载启动程序集。大致分为两种情况,一种是自动扫描当前Web程序集中通过HostingStartup指定的类,另一种是手动添加配置hostingstartupassembles指定外部的程序集中通过HostingStartup指定的类。第一种方式相对简单,但是对Web程序本身有入侵,第二种方式稍微复杂一点点,但是可以做到对现有代码无入侵操作,接下来我们分别演示这两种使用方式。

ASP.NET Core中直接定义

首先是在ASP.NET Core程序中直接使用HostingStartup,这种方式比较简单首先在Web程序中随便定义一个类,然后实现IHostingStartup接口,最后别忘了在程序集中添加HostingStartupAttribute指定要启动的类的类型,具体代码如下所示

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
//通过HostingStartup指定要启动的类型
[assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))]
namespace HostStartupWeb
{
public class HostingStartupInWeb : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
//程序启动时打印依据话,代表执行到了这里
Debug.WriteLine("Web程序中HostingStartupInWeb类启动"); //可以添加配置
builder.ConfigureAppConfiguration(config => {
//模拟添加一个一个内存配置
var datas = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("ServiceName", "HostStartupWeb")
};
config.AddInMemoryCollection(datas);
}); //可以添加ConfigureServices
builder.ConfigureServices(services=> {
//模拟注册一个PersonDto
services.AddScoped(provider=>new PersonDto { Id = 1, Name = "yi念之间", Age = 18 });
}); //可以添加Configure
builder.Configure(app => {
//模拟添加一个中间件
app.Use(async (context, next) =>
{
await next();
});
});
}
}
}

仅仅使用上面所示的这些代码,便可在Web程序启动的时候去自动执行HostingStartupInWeb的Configure方法,在这里面我们几乎可以使用所有针对ASP.NET Core程序配置的操作,而且不需要在Web程序中额外添加别的代码就可以自动调用HostingStartupInWeb的Configure方法。

外部程序集引入

我们之前也说过,上面的方式虽然使用起来相对简单一点,仅仅是一点,那就是省去了指定启动程序集的逻辑。但是,上面的方式需要在Web程序中添加,这样的话还是会修改代码。而且,可能更多的时候我们是在外部的程序集中编写HostingStartup逻辑,这时候就需要使用另一种方式在将外部程序集中引入HostingStartup。首先我们要在自定义的程序集中至少引入Microsoft.AspNetCore.Hosting包才能使用HostingStartup

<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />

如果你不需要使用注册中间件的逻辑那么仅仅引入Microsoft.AspNetCore.Hosting.Abstractions即可

<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />

如果需要使用其他功能包,可以自行在定义的程序集中引入。比如我们定义了一个名为HostStartupLib的Standard类库,并创建了名为HostStartupLib的类

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
[assembly: HostingStartup(typeof(HostStartupLib.HostingStartupInLib))]
namespace HostStartupLib
{
public class HostingStartupInLib : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
Debug.WriteLine("Lib程序中HostingStartupInLib类启动"); //添加配置
builder.ConfigureAppConfiguration((context, config) => {
var datas = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("ServiceName", "HostStartupLib")
};
config.AddInMemoryCollection(datas);
}); //添加ConfigureServices
builder.ConfigureServices(services=> {
services.AddScoped(provider=>new PersonDto { Id = 2, Name = "er念之间", Age = 19 });
}); //添加Configure
builder.Configure(app => {
app.Use(async (context, next) =>
{
await next();
});
}); }
}
}

然后我们将自定义的HostStartupLib这个Standard类库引入Web项目中,运行Web程序,发现HostingStartupInLib的Configure方法并不能被调用。其实我们上面说过了,将HostingStartup从外部程序集引入的话需要手动指定启动程序集的名称。指定启动程序集的方式有两种,一种是指定IWebHostBuilder的扩展UseSetting指定

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
//通过UseSetting的方式指定程序集的名称
webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib");
//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2
//webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib;HostStartupLib2");
webBuilder.UseStartup<Startup>();
});

另一种通过添加环境变量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES的方式,可以通过设置launchSettings.json中

"environmentVariables": {
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib"
//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2
//"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib;HostStartupLib2"
}

可以引入多个包含HostingStartup的程序集,在设置WebHostDefaults.HostingStartupAssembliesKey或者ASPNETCORE_HOSTINGSTARTUPASSEMBLIES指定多个程序集名称可以使用英文分号(;)隔开程序集名称。虽然是两种形似指定,但是其实本质是一样的那就是设置配置key为hostingStartupAssemblie配置的值,下面我们会详细讲解。

    通过在程序中设置环境变量的方式等同于Window系统中Set的方式设置环境变量,或Linux系统中export的方式设置环境变量,亦或是直接设置系统环境变量,效果都是一致的。指定完成启动程序集之后,再次运行程序便可以看到HostingStartupInLib的Configure方法被调用到了。在这里我们可以看到如果是使用的环境变量的方式去指定启动程序集的话,对现有代码可以做到完全无入侵。

源码探究

在上面我们简单的介绍了HostingStartup的概念及基本的使用方式,基于这些我们产生了几个疑问

  • 首先是关于HostingStartup的基本工作方式是什么
  • 其次是为什么HostingStartup在Web程序中不需要配置程序集信息就可以被调用到,而通过外部程序集引入HostingStartup需要手动指定程序集
  • 最后是通过外部程序集引入HostingStartup的指定方式为何只能是UseSetting和环境变量的方式

    基于以上几个疑问,我们来探索一下HostingStartup的相关源码,来揭开它的神秘面纱。首先废话不多说直接找到源码位置[点击查看源码]在GenericWebHostBuilder类中的ExecuteHostingStartups方法中,关于GenericWebHostBuilder类我们在上篇文章深入探究ASP.NET Core Startup初始化中主要就是分析这个类,因为这是构建WebHost的默认类,而我们接下来要说的ExecuteHostingStartups方法也是承载在这个类中,直接贴代码如下所示
private void ExecuteHostingStartups()
{
//通过配置_config和当前程序集名称构建WebHostOptions类
var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
//如果PreventHostingStartup属性为true则直接返回
//通过这个可以配置阻止启动逻辑
if (webHostOptions.PreventHostingStartup)
{
return;
} var exceptions = new List<Exception>();
//构建HostingStartupWebHostBuilder
_hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this);
//GetFinalHostingStartupAssemblies获取最终要执行的程序集名称
foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
{
try
{
//通过程序集名称加载程序集信息,因为使用了AssemblyName所以只需要使用程序集名称即可
var assembly = Assembly.Load(new AssemblyName(assemblyName));
//获取包含HostingStartupAttribute的程序集
foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
{
//实例化HostingStartupAttribute的HostingStartupType属性的对象实例
//即我们上面声明的[assembly: HostingStartup(typeof(HostStartupWeb.HostingStartupInWeb))]
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
//调用HostingStartup的Configure方法
hostingStartup.Configure(_hostingStartupWebHostBuilder);
}
}
catch (Exception ex)
{
exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));
}
} if (exceptions.Count > 0)
{
_hostingStartupErrors = new AggregateException(exceptions);
}
}

通过上面的源码我们就可以很清楚的了解到HostingStartup的基本工作方式。获取的程序集中包含的HostingStartupAttribute,通过获取HostingStartupAttribute的HostingStartupType属性得到要执行的IHostingStartup实例,最后执行Configure方法,Configure方法需要传递IWebHostBuilder的实例,而HostingStartupWebHostBuilder正是实现了IWebHostBuilder接口。

    我们了解到了HostStartup的工作方式,接下来我们来探究一下为什么HostingStartup在Web程序中不需要配置程序集信息就可以被调用到,而通过外部程序集引入HostingStartup需要手动指定程序集。通过上面的源码我们可以得到一个信息那就是所有需要启动的程序集信息都是来自WebHostOptions的GetFinalHostingStartupAssemblies方法,接下来我们就来查看一下GetFinalHostingStartupAssemblies方法的实现源码[点击查看源码]

public IEnumerable<string> GetFinalHostingStartupAssemblies()
{
return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase);
}

从这里我们可以看出程序集信息来自于HostingStartupAssemblies属性,而且还要排除掉HostingStartupExcludeAssemblies包含的程序集。我们找到他们初始化的相关逻辑大致如下

//承载启动是需要调用的HostingStartup程序集
public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
//承载启动时排除掉不不要执行的程序集
public IReadOnlyList<string> HostingStartupExcludeAssemblies { get; set; }
//是否阻止HostingStartup启动执行功能,如果设置为false则HostingStartup功能失效
//通过上面的ExecuteHostingStartups方法源码可知
public bool PreventHostingStartup { get; set; }
//应用程序名称
public string ApplicationName { get; set; } public WebHostOptions(IConfiguration configuration, string applicationNameFallback)
{
ApplicationName = configuration[WebHostDefaults.ApplicationKey] ?? applicationNameFallback;
HostingStartupAssemblies = Split($"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}");
HostingStartupExcludeAssemblies = Split(configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]);
PreventHostingStartup = WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey);
} //分隔配置的程序集信息,分隔依据为";"分号,这也是我们上面说过配置多程序集的时候采用分号分隔的原因
private IReadOnlyList<string> Split(string value)
{
return value?.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
?? Array.Empty<string>();
}

首先,通过HostingStartupAssemblies的初始化逻辑我们可以得出,默认会是有两个数据来源,一个是当前的ApplicationName,另一个是通过HostingStartupAssembliesKey配置的程序集信息。这也解答了我们上面说过的为什么HostingStartup在Web程序中不需要配置程序集信息就可以被调用到,而通过外部程序集引入HostingStartup需要手动指定程序集。其次,我们可以了解到通过配置HostingStartupExcludeAssemblies信息排除你不想启动的HostingStartup程序集,而且还可以通过配置PreventHostingStartup值来禁止使用HostingStartup的功能。

通过上面的代码我们还了解到这三个属性的来源的配置名称都是来自WebHostDefaults这个常量类,接下来我们查看一下这三个属性对应的配置名称

public static readonly string HostingStartupAssembliesKey = "hostingStartupAssemblies";
public static readonly string HostingStartupExcludeAssembliesKey = "hostingStartupExcludeAssemblies";
public static readonly string PreventHostingStartupKey = "preventHostingStartup";

也就是说,我们可以可以通过配置这三个名称的配置,来完成HostingStartup相关的功能比如

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
//通过UseSetting的方式指定程序集的名称
webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib");
//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2
//webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "HostStartupLib;HostStartupLib2"); //排除执行HostStartupLib2程序集执行HostingStartup逻辑
webBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, "HostStartupLib2");
//禁用HostingStartup功能
webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true");
webBuilder.UseStartup<Startup>();
});

或通过环境变量的方式去操作

"environmentVariables": {
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib",
//如果HostingStartup存在多个程序集中可以使用;分隔,比如HostStartupLib;HostStartupLib2
//"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "HostStartupLib;HostStartupLib2" //排除执行HostStartupLib2程序集执行HostingStartup逻辑
"ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES":"HostStartupLib2",
//禁用HostingStartup功能
"ASPNETCORE_PREVENTHOSTINGSTARTUP":"true"
}

其实这两种配置方式是完全等价的,为什么这么说呢?首先是在Configuration中获取配置是忽略大小写的,其实是使用ConfigureWebHostDefaults配置WebHost相关信息的时候会添加configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_")逻辑这样的话获取环境变量的时候可以忽略ASPNETCORE_前缀。

那么到目前为止,还有一个疑问尚未解决,那就是为何只能通过UseSetting和环境变量的方式去配置HostingStartup相关配置,解铃还须系铃人,我们在上面的ExecuteHostingStartups方法中看到了这个逻辑

//这里传递了一个_config
var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);

我们可以看到传递了配置Configuration的实例_config,我们到初始化_config地方有如下逻辑

var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
if (!options.SuppressEnvironmentConfiguration)
{
//添加环境变量
configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_");
}
//构建了_config实例
private readonly IConfiguration _config = configBuilder.Build();

也就可以解释为何我们可以通过环境变量去配置HostingStartup,然后我们再来看UseSetting方法的逻辑

public IWebHostBuilder UseSetting(string key, string value)
{
_config[key] = value;
return this;
}

原来UseSetting也是给_config实例设置值,所以无论通过UseSetting或环境环境变量的方式去配置,本质都是在操作_config这个配置实例,到此为止所有谜团均以解开。

在SkyAPM中的使用

我们上面说了HostingStartup可以增强启动时候的操作,可以通过对现有代码无入侵的方式增强程序功能。而SkyAPM-dotnet也正是使用了这个功能,实现了无入侵启动APM监控。我们来回顾一下SkyAPM-dotnet的使用方式

  • 首先是使用Nuget添加SkyAPM.Agent.AspNetCore程序集引用。
  • 其次是在launchSettings.json文件中添加ASPNETCORE_HOSTINGSTARTUPASSEMBLIES:"SkyAPM.Agent.AspNetCore"环境变量配置(等同于set ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=SkyAPM.Agent.AspNetCore或export ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=SkyAPM.Agent.AspNetCore

    的方式,本质都是在配置环境变量)
  • 最后通过SKYWALKING__SERVICENAME设置程序名称

    这里我们通过需要配置ASPNETCORE_HOSTINGSTARTUPASSEMBLIES名称可以看出确实是使用了HostingStartup功能,而通过HostingStartup增强的操作入口肯定就在SkyAPM.Agent.AspNetCore程序集中,我们找到SkyAPM.Agent.AspNetCore程序集的源码[点击查看源码]看到了SkyApmHostingStartup类实现如下
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using SkyApm.Agent.AspNetCore;
using SkyApm.AspNetCore.Diagnostics; [assembly: HostingStartup(typeof(SkyApmHostingStartup))] namespace SkyApm.Agent.AspNetCore
{
internal class SkyApmHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
}
}
}

通过这个我们可以看出确实如此,当然也是等同于我们通过UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SkyApm.Agent.AspNetCore")去配置,我们甚至可使用如下的方式去使用SkyAPM-dotnet

public void ConfigureServices(IServiceCollection services)
{
services.AddSkyAPM(ext => ext.AddAspNetCoreHosting())
}

这些写法其实是完全等价的,但是通过环境变量的方式配置HostingStartup启动程序集的方式无疑是最优雅的。所以我们在日常的学习开发中,最好还是通过这种方式去操作。

改造Zipkin使用

我们在之前的文章ASP.NET Core整合Zipkin链路跟踪中曾演示过基于诊断日志DiagnosticSource改进Zipkin的集成方式,通过本篇文章讲述的HostingStartup我们可以进步一改进Zipkin的集成方式,可以让它使用起来和SkyAPM-dotnet类似的方式,我们基于之前的示例中的ZipkinExtensions程序集中添加一个ZipkinHostingStartup类,用于承载集成Zipkin的操作,代码如下

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection; namespace ZipkinExtensions
{
public class ZipkinHostingStartup: IHostingStartup
{ public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services=> {
services.AddZipkin();
services.AddSingleton<ITraceDiagnosticListener, HttpDiagnosticListener>();
}); builder.Configure(app=> {
IHostApplicationLifetime lifetime = app.ApplicationServices.GetService<IHostApplicationLifetime>();
ILoggerFactory loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
IConfiguration configuration = app.ApplicationServices.GetService<IConfiguration>();
string serivceName = configuration.GetValue<string>("ServiceName");
string zipKinUrl = configuration.GetValue<string>("ASPNETCORE_ZIPKINADDRESS"); app.UseZipkin(lifetime, loggerFactory, serivceName, zipKinUrl);
});
}
}
}

然后在每个项目的launchSettings.json文件中添加如下所示的配置即可,这样的话就可以做到对现有业务代码无任何入侵。

 "environmentVariables": {
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "ZipkinExtensions",
"ASPNETCORE_ZIPKINADDRESS": "http://localhost:9411/"
}

总结

本文介绍了HostingStartup的基本概念,基础使用以及对其源码的分析和在SkyAPM-dotnet中的应用,最后我们改造了Zipkin的集成方式。HostingStartup在一些集成APM或者链路跟踪的类似场景还是非常实用的,或者如果我们有集成一些基础组件或者三方的组件,但是我们的代码中并不需要直接的使用这些组件中的类或者直接的代码关系,均可以使用HostingStartup的方式去集成,为我们实现对现有代码提供无入侵增强提供了强大的支持。关于HostingStartup我也是在看源码中无意发现的,后来发现微软ASP.NET Core官方文档
Use hosting startup assemblies in ASP.NET Core一文中有讲解,然后联想到自己使用过的SkyAPM-dotnet正是使用了HostingStartup+诊断日志DiagnosticSource的方式实现了对代码无入侵的方式进行监控和链路跟踪。于是决定深入研究一下,可谓收获满满,便写下这篇文章希望更多的人能够了解使用这个功能。

欢迎扫码关注我的公众号

ASP.NET Core使用HostingStartup增强启动操作的更多相关文章

  1. ASP.NET Core管道详解[6]: ASP.NET Core应用是如何启动的?[下篇]

    要承载一个ASP.NET Core应用,只需要将GenericWebHostService服务注册到承载系统中即可.但GenericWebHostService服务具有针对其他一系列服务的依赖,所以在 ...

  2. asp.net core mvc剖析:启动流程

    asp.net core mvc是微软开源的跨平台的mvc框架,首先它跟原有的MVC相比,最大的不同就是跨平台,然后又增加了一些非常实用的新功能,比如taghelper,viewcomponent,D ...

  3. 解决ASP.NET Core通过docker-compose up启动应用无法配置https的解决办法

    错误重现一下: 新建了一个ASP.NET Core应用,在VS2017下添加Docker支持,选择Linux环境 然后再给这个web应用再右键添加容器业务流程协调程序支持,然后解决方案就多了一个doc ...

  4. ASP.NET Core 1.0 使用 Dapper 操作 MySql(包含事务)

    操作 MySql 数据库使用MySql.Data程序包(MySql 开发,其他第三方可能会有些问题). project.json 代码: { "version": "1. ...

  5. ASP.NET Core Web API Cassandra CRUD 操作

    在本文中,我们将创建一个简单的 Web API 来实现对一个 “todo” 列表的 CRUD 操作,使用 Apache Cassandra 来存储数据,在这里不会创建 UI ,Web API 的测试将 ...

  6. [译]ASP.NET Core 2.0 本地文件操作

    问题 如何在ASP.NET Core 2.0中受限地访问本地目录和文件信息? 答案 新建一个空项目,修改Startup类,添加访问本地文件所需的服务: public void ConfigureSer ...

  7. [08]ASP.NET Core 中 launchsettings.json 启动配置文件

    ASP.NET Core launchsettings.json 启动配置文件 本文作者:梁桐铭- 微软最有价值专家(Microsoft MVP) 文章会随着版本进行更新,关注我获取最新版本 本文出自 ...

  8. Asp.Net Core 3.1 的启动过程5

    前言 本文主要讲的是Asp.Net Core的启动过程,帮助大家掌握应用程序的关键配置点. 1.创建项目 1.1.用Visual Studio 2019 创建WebApi项目. 这里面可以看到有两个关 ...

  9. asp.net core 之静态文件目录的操作

    文章前言 之前写了一篇关于模拟登录的文章,自我感觉内容不太丰富,今天的这篇文章,希望在内容上能丰富些.本人缺少写文章的经验,技术上也是新手,但我会努力的,希望大家多多支持小弟. asp.net cor ...

随机推荐

  1. js 判断客户端 和 asp.net/C#判断客户端类型

    1.js 判断客户端 <script language="JavaScript"> <!-- onload = function browserRedirect( ...

  2. 微信小程序分类的实现

    微信小程序的分类功能思路 实现思路 1.把屏幕当成一个固定的盒子,然后把盒子分成两边,并让盒子的每一边都能够滚动. 2.通过将左侧边栏元素的id和右边内容的categoryId进行匹配,渲染展示相同i ...

  3. soct的创建方法

    服务器端:ServerSocket提供的实例 ServerSocket server = new ServerSocket(端口号)  客户端:Socket提供的实例 Socket client = ...

  4. CopyTranslator安装与使用

    PDF 格式的文本,本质上是保证了在大部分设备上都能保持清晰完整的排版格式,但不利于进一步使用,但是 PDF 文档文字复制会包括回车键,文字粘粘和翻译都不方便.通常的做法就是,先转换成 Word 格式 ...

  5. CentOS7 安装telnet-0.17-64.el7.x86_64

    1.安装客服端,服务端,xinetd yum -y install telnet telnet-server xinetd 以上要想完成telnet安装,telnet服务端和xinetd必须安装,至于 ...

  6. Android Choreographer 源码分析

    Choreographer 的作用主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整, ...

  7. Java学习的第十七天

    1.静态变量 静态方法 静态代码块 2.今天没问题 3.明天学习abstract和综合实例

  8. Unity报错:xxx AnimationEvent has no function name specified!

    参考:https://blog.csdn.net/register_man/article/details/54172778 在开发时出现了题目中的错误且有动画掉帧的情况,搜索后发现是在动画编辑器中我 ...

  9. MIT 6.S081 Lab5 Copy-On-Write Fork

    前言 最近绝大多数的空闲时间都拿来锤15-445了,很久没动6.S081.前几天回头看了一下一个月前锤完的Lazy Allocation,自己写的代码几乎都不认识了.......看来总结之类的东西最好 ...

  10. php 实现签名验签

    本人php菜鸟,主要使用php实现简单的签名验签功能 以下php代码使用的密钥格式为pem格式,其他证书格式可以使用openssl进行转换(未安装请实现安装): 以下是.p12文件导出pem格式公私钥 ...