.NET Core 3.0之深入源码理解Host(二) - 艾心❤ - 博客园

mikel阅读(460)

来源: .NET Core 3.0之深入源码理解Host(二) – 艾心❤ – 博客园

写在前面

停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了。本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Program的创建与守护。

关于Host,我们最容易想到的就是程序的启动与停止,而其中隐藏着非常关键的功能,就是Host的初始化,我们所需要的所有资源都必须而且应该在程序启动过程中初始化完成,本文的主要内容并不是Host初始化,前文已经累述。为了更好的守护与管理已经启动的Host,.NET Core 3.0将程序的生命周期事件的订阅开放给开发者,也包括自定义的Host Service对象。

注:本文代码基于.NET Core 3.0 Preview9

host2

.NET Core 3.0中创建Long Run Program

IHost与IHostBuilder

当我们创建Long Run Program时,会首先关注程序的启动与停止,.NET Core 3.0为此提供了一个接口IHost,该接口位于Microsoft.Extensions.Hosting类库中,其源码如下:

   1:  /// <summary>
   2:  /// A program abstraction.
   3:  /// </summary>
   4:  public interface IHost : IDisposable
   5:  {
   6:      /// <summary>
   7:      /// The programs configured services.
   8:      /// </summary>
   9:      IServiceProvider Services { get; }
  10:  
  11:      /// <summary>
  12:      /// Start the program.
  13:      /// </summary>
  14:      /// <param name="cancellationToken">Used to abort program start.</param>
  15:      /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> starts.</returns>
  16:      Task StartAsync(CancellationToken cancellationToken = default);
  17:  
  18:      /// <summary>
  19:      /// Attempts to gracefully stop the program.
  20:      /// </summary>
  21:      /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
  22:      /// <returns>A <see cref="Task"/> that will be completed when the <see cref="IHost"/> stops.</returns>
  23:      Task StopAsync(CancellationToken cancellationToken = default);
  24:  }

该接口含有一个只读属性:IServiceProvider Services { get; },通过该属性,我们可以拿到所有Host初始化时所注入的对象信息。

IHostBuilder接口所承担的核心功能就是程序的初始化,通过:IHost Build()来完成,当然只需要运行一次即可。其初始化内容一般包括以下几个功能:

host3

另外需要说明的是,以上功能的初始化,是通过IHostBuilder提供的接口获取用户输入的信息后,通过调用Build()方法来完成初始化。以下为IHostBuilder的部分源代码:

   1:  /// <summary>
   2:  /// Set up the configuration for the builder itself. This will be used to initialize the <see cref="IHostEnvironment"/>
   3:  /// for use later in the build process. This can be called multiple times and the results will be additive.
   4:  /// </summary>
   5:  /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
   6:  /// to construct the <see cref="IConfiguration"/> for the host.</param>
   7:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
   8:  public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
   9:  {
  10:      _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
  11:      return this;
  12:  }
  13:  
  14:  /// <summary>
  15:  /// Adds services to the container. This can be called multiple times and the results will be additive.
  16:  /// </summary>
  17:  /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
  18:  /// to construct the <see cref="IConfiguration"/> for the host.</param>
  19:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
  20:  public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
  21:  {
  22:      _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
  23:      return this;
  24:  }
  25:  
  26:  /// <summary>
  27:  /// Overrides the factory used to create the service provider.
  28:  /// </summary>
  29:  /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
  30:  /// <param name="factory">A factory used for creating service providers.</param>
  31:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
  32:  public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
  33:  {
  34:      _serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
  35:      return this;
  36:  }
  37:  
  38:  /// <summary>
  39:  /// Enables configuring the instantiated dependency container. This can be called multiple times and
  40:  /// the results will be additive.
  41:  /// </summary>
  42:  /// <typeparam name="TContainerBuilder">The type of the builder to create.</typeparam>
  43:  /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder"/> that will be used
  44:  /// to construct the <see cref="IConfiguration"/> for the host.</param>
  45:  /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
  46:  public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
  47:  {
  48:      _configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
  49:          ?? throw new ArgumentNullException(nameof(configureDelegate))));
  50:      return this;
  51:  }

IHostService

文章开头有说过自定义Host Service对象,那么我们如何自定义呢,其实很简单只需要实现IHostService,并在ConfigureServices中调用services.AddHostedService<MyServiceA>()即可,以下是IHostService的源码:

   1:  /// <summary>
   2:  /// Defines methods for objects that are managed by the host.
   3:  /// </summary>
   4:  public interface IHostedService
   5:  {
   6:      /// <summary>
   7:      /// Triggered when the application host is ready to start the service.
   8:      /// </summary>
   9:      /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
  10:      Task StartAsync(CancellationToken cancellationToken);
  11:  
  12:      /// <summary>
  13:      /// Triggered when the application host is performing a graceful shutdown.
  14:      /// </summary>
  15:      /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
  16:      Task StopAsync(CancellationToken cancellationToken);
  17:  }

根据源码我们可以知道,该接口只有两个方法,即代码程序开始与停止的方法。具体的实现可以参考如下:

   1:  public class MyServiceA : IHostedService, IDisposable
   2:  {
   3:      private bool _stopping;
   4:      private Task _backgroundTask;
   5:  
   6:      public MyServiceA(ILoggerFactory loggerFactory)
   7:      {
   8:          Logger = loggerFactory.CreateLogger<MyServiceB>();
   9:      }
  10:  
  11:      public ILogger Logger { get; }
  12:  
  13:      public Task StartAsync(CancellationToken cancellationToken)
  14:      {
  15:          Logger.LogInformation("MyServiceB is starting.");
  16:          _backgroundTask = BackgroundTask();
  17:          return Task.CompletedTask;
  18:      }
  19:  
  20:      private async Task BackgroundTask()
  21:      {
  22:          while (!_stopping)
  23:          {
  24:              await Task.Delay(TimeSpan.FromSeconds(7));
  25:              Logger.LogInformation("MyServiceB is doing background work.");
  26:          }
  27:  
  28:          Logger.LogInformation("MyServiceB background task is stopping.");
  29:      }
  30:  
  31:      public async Task StopAsync(CancellationToken cancellationToken)
  32:      {
  33:          Logger.LogInformation("MyServiceB is stopping.");
  34:          _stopping = true;
  35:          if (_backgroundTask != null)
  36:          {
  37:              // TODO: cancellation
  38:              await _backgroundTask;
  39:          }
  40:      }
  41:  
  42:      public void Dispose()
  43:      {
  44:          Logger.LogInformation("MyServiceB is disposing.");
  45:      }
  46:  }

IHostService是我们自定义Host管理对象的入口,所有需要压入到Host托管的对象都必须要实现此接口。

Host生命周期的管理

该接口提供了一种我们可以在程序运行期间进行管理的功能,如程序的启动与停止事件的订阅,关于Host生命周期的管理,主要由IHostApplicationLifetime和IHostLifetime这两个接口来完成。

以下是IHostApplicationLifetime的源码​

   1:  public interface IHostApplicationLifetime
   2:  {
   3:      /// <summary>
   4:      /// Triggered when the application host has fully started.
   5:      /// </summary>
   6:      CancellationToken ApplicationStarted { get; }
   7:  
   8:      /// <summary>
   9:      /// Triggered when the application host is performing a graceful shutdown.
  10:      /// Shutdown will block until this event completes.
  11:      /// </summary>
  12:      CancellationToken ApplicationStopping { get; }
  13:  
  14:      /// <summary>
  15:      /// Triggered when the application host is performing a graceful shutdown.
  16:      /// Shutdown will block until this event completes.
  17:      /// </summary>
  18:      CancellationToken ApplicationStopped { get; }
  19:  
  20:      /// <summary>
  21:      /// Requests termination of the current application.
  22:      /// </summary>
  23:      void StopApplication();
  24:  }

IHostLifetime源码如下:

   1:  public interface IHostLifetime
   2:  {
   3:      /// <summary>
   4:      /// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until it's complete before
   5:      /// continuing. This can be used to delay startup until signaled by an external event.
   6:      /// </summary>
   7:      /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
   8:      /// <returns>A <see cref="Task"/>.</returns>
   9:      Task WaitForStartAsync(CancellationToken cancellationToken);
  10:  
  11:      /// <summary>
  12:      /// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host is stopping and it's time to shut down.
  13:      /// </summary>
  14:      /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
  15:      /// <returns>A <see cref="Task"/>.</returns>
  16:      Task StopAsync(CancellationToken cancellationToken);
  17:  }

具体的使用可以参考如下代码:

   1:  public class MyLifetime : IHostLifetime, IDisposable
   2:  {
   3:      .........
   4:  
   5:      private IHostApplicationLifetime ApplicationLifetime { get; }
   6:  
   7:      public ConsoleLifetime(IHostApplicationLifetime applicationLifetime)
   8:      {
   9:          ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
  10:      }
  11:  
  12:      public Task WaitForStartAsync(CancellationToken cancellationToken)
  13:      {
  14:          _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
  15:          {
  16:              ((ConsoleLifetime)state).OnApplicationStarted();
  17:          },
  18:          this);
  19:          _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
  20:          {
  21:              ((ConsoleLifetime)state).OnApplicationStopping();
  22:          },
  23:          this);
  24:  
  25:          .......
  26:  
  27:          return Task.CompletedTask;
  28:      }
  29:  
  30:      private void OnApplicationStarted()
  31:      {
  32:          Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
  33:          Logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
  34:          Logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
  35:      }
  36:  
  37:      private void OnApplicationStopping()
  38:      {
  39:          Logger.LogInformation("Application is shutting down...");
  40:      }
  41:  
  42:      ........
  43:  }

总结

至此,我们知道了创建Long Run Program所需要关注的几个点,分别是继承IHostService、订阅程序的生命周期时间以及Host的初始化过程。相对来说这段内容还是比较简单的,但是开发过程中,依然会遇到很多的问题,比如任务的定时机制、消息的接入、以及程序的性能优化等等,这些都需要我们在实践中进一步总结完善。

.NET Core 3.0之深入源码理解Host(一) - 艾心❤ - 博客园

mikel阅读(414)

来源: .NET Core 3.0之深入源码理解Host(一) – 艾心❤ – 博客园

写在前面

ASP .NET Core中的通用主机构建器是在v2.1中引入的,应用在启动时构建主机,主机作为一个对象用于封装应用资源以及应用程序启动和生存期管理。其主要功能包括配置初始化(包括加载配置以及配置转换为通用的键值对格式),创建托管环境和Host通用上下文、依赖注入等。

在.NET Core 3.0中采用了IHostBuilder用于创建Host,同时也不再建议使用Web主机,而建议使用泛型主机,主要原因是原有的通用主机仅适用于非HTTP负载,为了提供更加广泛的主机方案,需要将HTTP管道与Web主机的接口分离出来。但Web主机仍会向后兼容。

host-update

.NET Core 3.0中创建通用主机

以下代码是V3.0中提供的模板代码,可以看到在创建主机的过程中,已经摒弃了WebHostBuilder的创建方式

   1:  public class Program
   2:  {
   3:      public static void Main(string[] args)
   4:      {
   5:          CreateHostBuilder(args).Build().Run();
   6:      }
   7:  
   8:      public static IHostBuilder CreateHostBuilder(string[] args) =>
   9:          Host.CreateDefaultBuilder(args)
  10:              .ConfigureWebHostDefaults(webBuilder =>
  11:              {
  12:                  webBuilder.UseStartup<Startup>();
  13:              });
  14:  }

而在.NET Core 2.X中

   1:  public class Program
   2:  {
   3:     public static void Main(string[] args)
   4:     {
   5:        CreateWebHostBuilder(args).Build().Run();
   6:     }
   7:  
   8:     public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
   9:        WebHost.CreateDefaultBuilder(args)
  10:           .UseStartup<Startup>();
  11:  }

V3.0模板中提供的CreateHostBuilder()方法看起来非常类似于V2.X中的CreateWebHostBuilder()。

其主要区别在于对WebHost.CreateDefaultBuilder()由Host.CreateDefaultBuilder()替换。使用CreateDefaultBuilder()辅助方法可以非常轻松地从v2.x切换到v3.0。

另一个区别是关于ConfigureWebHostDefaults()的调用。由于新的主机构建器是通用主机构建器,因此我们必须让它知道我们打算为Web主机配置默认设置。这些默认配置我们可以在ConfigureWebHostDefaults()方法中实现

CreateDefaultBuilder

该方法Microsoft.Extensions.Hosting.Host中,它是一个静态类,里面有两个方法,一个有参的CreateDefaultBuilder(string[] args),一个是无参的。

无参方法源码如下,

   1:  public static IHostBuilder CreateDefaultBuilder() =>
   2:              CreateDefaultBuilder(args: null);

可以看到该方法实际上是设置了默认值。

IHostBuilder CreateDefaultBuilder(string[] args)方法主要有以下功能:

创建HostBuilder对象

   1:  var builder = new HostBuilder();

指定Host要使用的内容根目录

   1:  builder.UseContentRoot(Directory.GetCurrentDirectory());

配置初始化(环境变量、appsettings.json、User Secrets)

   1:  builder.ConfigureHostConfiguration(config =>
   2:  {
   3:      config.AddEnvironmentVariables(prefix: "DOTNET_");
   4:      if (args != null)
   5:      {
   6:          config.AddCommandLine(args);
   7:      }
   8:  });
   9:  
  10:  builder.ConfigureAppConfiguration((hostingContext, config) =>
  11:  {
  12:      var env = hostingContext.HostingEnvironment;
  13:  
  14:      config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
  15:            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
  16:  
  17:      if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
  18:      {
  19:          var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
  20:          if (appAssembly != null)
  21:          {
  22:              config.AddUserSecrets(appAssembly, optional: true);
  23:          }
  24:      }
  25:  
  26:      config.AddEnvironmentVariables();
  27:  
  28:      if (args != null)
  29:      {
  30:          config.AddCommandLine(args);
  31:      }
  32:  })

日志

   1:  .ConfigureLogging((hostingContext, logging) =>
   2:  {
   3:      logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
   4:      logging.AddConsole();
   5:      logging.AddDebug();
   6:      logging.AddEventSourceLogger();
   7:  })

在开发环境模式下启用作用域验证

   1:  .UseDefaultServiceProvider((context, options) =>
   2:  {
   3:      var isDevelopment = context.HostingEnvironment.IsDevelopment();
   4:      options.ValidateScopes = isDevelopment;
   5:      options.ValidateOnBuild = isDevelopment;
   6:  });

Build

Build()方法是Microsoft.Extensions.Hosting中,并且该方法只会执行一次,当然这种一次只是在同一个实例里面

   1:  public IHost Build()
   2:  {
   3:      if (_hostBuilt)
   4:      {
   5:          throw new InvalidOperationException("Build can only be called once.");
   6:      }
   7:      _hostBuilt = true;
   8:  
   9:      BuildHostConfiguration();
  10:      CreateHostingEnvironment();
  11:      CreateHostBuilderContext();
  12:      BuildAppConfiguration();
  13:      CreateServiceProvider();
  14:  
  15:      return _appServices.GetRequiredService<IHost>();
  16:  }

该方法主要是包括以下功能:

创建HostingEnvironment

创建HostBuilderContext

配置初始化及格式标准化

DI(创建IHostEnvironment、IHostApplicationLifetime、IHostLifetime、IHost)

Run

Run方法运行应用程序并阻止调用线程,直到主机关闭

   1:  public static void Run(this IHost host)
   2:  {
   3:      host.RunAsync().GetAwaiter().GetResult();
   4:  }

以下是RunAsync的源码,此处可以通过设置CancellationToken的值,使应用程序自动关闭

   1:  public static async Task RunAsync(this IHost host, CancellationToken token = default)
   2:  {
   3:      try
   4:      {
   5:          await host.StartAsync(token);
   6:  
   7:          await host.WaitForShutdownAsync(token);
   8:      }
   9:      finally
  10:      {
  11:  #if DISPOSE_ASYNC
  12:          if (host is IAsyncDisposable asyncDisposable)
  13:          {
  14:              await asyncDisposable.DisposeAsync();
  15:          }
  16:          else
  17:  #endif
  18:          {
  19:              host.Dispose();
  20:          }
  21:  
  22:      }
  23:  }

c# 匿名方法(函数) 匿名委托 内置泛型委托 lamada - 伊一线天 - 博客园

mikel阅读(415)

来源: c# 匿名方法(函数) 匿名委托 内置泛型委托 lamada – 伊一线天 – 博客园

匿名方法:通过匿名委托 、lamada表达式定义的函数具体操作并复制给委托类型;

匿名委托:委托的一种简单化声明方式通过delegate关键字声明;

内置泛型委托:系统已经内置的委托类型主要是不带返回值的Action<T1,,,,Tn>和带返回值的Func<T1,,,Tn,Tresult>

实例代码(运行环境netcoreapp3.1)

复制代码
class demoFunc
        {
            /// <summary>
            /// 定义函数单条语句直接用lamada表达式
            /// </summary>
            /// <param name="x"></param>
           public void funcA( string x)=> Console.WriteLine("this is funcA!{0}",x);
           /// <summary>
           /// 使用内置泛型委托action(返回值为void) 定义委托类型成员变量,并通过那lamada定义匿名函数
           /// </summary>
           public Action<string> funcB= x => Console.WriteLine("this is funcB!{0}", x);
           /// <summary>
           /// 使用内置泛型委托action(返回值为void) 定义委托类型成员变量,
           /// 并通过匿名委托定义匿名函数
           /// </summary>
           public Action<string> FuncB_1= delegate(string s)
           {
               Console.WriteLine("this is funcB_1!{0}", s);
           };
           /// <summary>
           /// 定义委托类型
           /// </summary>
           /// <param name="s"></param>
           public  delegate  void TFuccB_2(string s);
           /// <summary>
           /// 使用匿名函数声明委托
           /// </summary>
           public TFuccB_2 FuncB_2= delegate(string s) {
               Console.WriteLine("this is funcB_2!{0}", s);
           };

           /// <summary>
           /// 使用内置泛型委托func(返回值不可以为void,参数列表中最后一个时返回值),
           /// 定义委托类型成员变量,并通过lamada定义单含带返回值的匿名函数
           /// 单行表达式的返回值就是此匿名函数的返回值
           /// </summary>
           public Func<string, string> funcC=x=>  string.Format("this is funcC!{0}", x);

           /// <summary>
           /// 使用内置泛型委托func(返回值不可以为void),定义委托类型成员变量,
           /// 并通过lamada定义多行代码的匿名函数
           /// </summary>
           public Func<string,string, string> funcD= (x1, x2) =>
           {
               Console.WriteLine("this is funcd!{0}{1}", x1, x2);
               return string.Format("this is funcd!{0}{1}", x1, x2);
           };

        }
复制代码

运行测试代码

复制代码
static void Main(string[] args)
        {
            demoFunc demo=new demoFunc();
            demo.funcA("a");
            demo.funcB("b");
            demo.FuncB_1("b");
            demo.FuncB_2("b");
            Console.WriteLine(demo.funcC("c"));
            Console.WriteLine(demo.funcD("d1","d2"));
            
            Console.WriteLine("Hello World!");
        }
复制代码

运行结果

复制代码
  1 "C:\Program Files\dotnet\dotnet.exe" C:/Users/edzjx/RiderProjects/testDemo/testDemo/bin/Debug/netcoreapp3.1/testDemo.dll
  2 this is funcA!a
  3 this is funcB!b
  4 this is funcB_1!b
  5 this is funcB_2!b
  6 this is funcC!c
  7 this is funcd!d1d2
  8 this is funcd!d1d2
  9 Hello World!
 10 
 11 Process finished with exit code 0.
 12
复制代码

asp.net core 3.1 入口:Program.cs中的Main函数_伊一线天的博客-CSDN博客

mikel阅读(451)

来源: asp.net core 3.1 入口:Program.cs中的Main函数_伊一线天的博客-CSDN博客

本文分析Program.cs 中Main()函数中代码的运行顺序分析ASP.NET core程序的启动,重点不是剖析源码,而是理清程序开始时执行的顺序。到底用了哪些实例,哪些法方。

ASP.NET core 3.1 的程序入口在项目Program.cs文件里,如下。

ususing System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace WebDemo
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
1. Program类
program类是定义在项目根目录Program.cs文件中,所有.net core程序的入口,包括asp.net core 程序。这很有意思,就有点类似控制端程序。相比之前的web项目没有任何程序入口只有web.config、global这类配置文件和全局文件这种没有程序入口web项目,我觉得这类更容易理解asp.net core是怎么工作的。

在asp.net core 3.1中 的Program类里,定义了2个方法:Main() 和CreateHostBuilder()

2. 主程序入口Program.Main()方法
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
Main() 方法是整个项目的入口方法,就如同C系列语言,所有的程序入口都是。这里main()只有一行代码,但是实际上执行了三个函数C:

1 IHostBuilder builder= CreateHostBuilder(args);
2 IHost host=builder.Build();
3 host.Run();
第一行,是定义在Program类的CreateHostBuilder 它主要产生一个IhostBuilder实例builder。

第二行,通过builder.Build()法方产生一个Ihost实例 host。

第三含,通过host.Run()方法,开始运行web项目,这时候就可以响应各种请求了。

而这个过程里最繁琐的就是创建builder,本文重点篇幅就是在这里。

3. 创建并配置主机Builder:Program.CreateHostBuilder(args)法方
分解CreateHostBuilder(args) 的定义
此法方通过lamada表达式定义在Pogram类中。因为它就执行了一句化,所以可以用这种简便的方式定义(关于匿名函数、lamada、内置委托可以参考我之前的文章C# 匿名方法(函数) 匿名委托 内置泛型委托 lamada1)。为了方便阅读按照上面刨析Main()法方,所以它实际的定义如下:

1 public static IHostBuilder CreateHostBuilder(string[] args)
2 {
3 IHostBuilder builder = Host.CreateDefaultBuilder(args);
4 Action < IWebHostBuilder > configAction = delegate(IWebHostBuilder webBuilder)
5 {
6 webBuilder.UseStartup<Startup>();
7 };
8 builder=builder.ConfigureWebHostDefaults(configAction);
9 return builder;
10 }
3.1. builder诞生 :生成IHostBuilder
将从Host.CreateHostBuilder()法方分析入手
CreateHostBuilder()法方的第一行代码是调用Host.CreateDefaultBuilder(args)法方,来创建IBuilder 对象实例。

IHostBuilder builder = Host.CreateDefaultBuilder(args);
IHostBuilder是一个非常重要的实例,有了这个实例才可以继续后续的加载更多的配置操作。通过IHostBuilder.Build()生产IHost实例。最终通过IHost.Run()运行项目。

3.1.1 Host类——用于产生初始的builder静态类

Host类是定义在Microsoft.Extensions.Hosting命名空间(Program.cs中引用了该命名公开)下的静态类.在Program.cs里引入。

声明如下:

1 using Microsoft.Extensions.Hosting;
2
3 namespace Microsoft.Extensions.Hosting
4 {
5 public static class Host
6 {
7 public static IHostBuilder CreateDefaultBuilder();
8 public static IHostBuilder CreateDefaultBuilder(string[] args);
9 }
10 }
Host静态类就一个法方就是CreateDefaultBuilder() 。合计2个重载声明:一个带参数,一个不带参数。参考源码(.NET Core 3.0之深入源码理解Host(一)2)不带参数的重载实际在是将null作为参数调用带参数形式,如下:

1 public static IHostBuilder CreateDefaultBuilder() =>CreateDefaultBuilder(args: null);
3.1.2 Host.CreateDefaultBuilder(args)方法
官方说明是用预先配置的默认值初始化一个Microsoft.Extensions.Hosting.HostBuilder实例(Initializes a new instance of the Microsoft.Extensions.Hosting.HostBuilder class with pre-configured defaults,详细见参考官方文档3)。
相关操作有:设置根目录ContentRootPath ;给Host.IConfiguration 加载:环境变量EnvironmentName,命令行参数args,配置文件IConfiguration.[EnvironmentName] json;还有加载日志模块等等。
详细见参考文档2、3,其中.NET Core 3.0之深入源码理解Host(一),作者从源码角度剖析了此法方,官方文档Host.CreateDefaultBuilder 方法官方说明了法方操作那些内容。

3.2. IHostBuilder转变成IWebHostBuilder
继续对Host.CreateHostBuilder()法方分析,在3.1建立了IHostBuilder实例后,将通过builder.ConfigureWebHostDefaults(Action <IWebHostBuilder >)方法将IHostBuilder实例转变为一个web主机性质的webbuilder(IWebHostBuilder)
IHoustBuilder.ConfigureWebHostDefaults(Action <IWebHostBuilder>)方法定义在Microsoft.Extensions.Hosting.GenericHostBuilderExtensions静态类中(GenericHostBuilderExtensions.cs),此静态类就定义这一个静态方法。

3.2.1. ConfigureWebHostDefaults()参数
此处参数configure是一个内置委托Action <IWebHostBuilder>,这个委托的定义就执行一行代码:
webBuilder.UseStartup<Startup>();

3.2.2. ConfigureWebHostDefaults()方法定义
其实ConfigureWebHostDefaults(Action <IWebHostBuilder >)方法是一个IHostBuilder的扩展方法(所以之前的写法并不准确,缺少了this builder参数,之前省略了)。
为了方便阅读法,用3.中的方式码剖析如下:
1 public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
2 {
3 Action<IWebHostBuilder> webconfigure = delegate(IWebHostBuilder webHostBuilder)
4 {
5 WebHost.ConfigureWebDefaults(webHostBuilder);
6 configure(webHostBuilder);
7 };
8 return builder.ConfigureWebHost(webconfigure);
9 }
其实就调取了IHostBuilder的另一个扩展类ConfigureWebHost(Action<IWebHostBuilder>)(此扩展法方定义在Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions静态类中)。
3.2.2.1 把IHostBuilder转变为IWebHostBuilder: builder.ConfigureWebHost(webconfigure)法方
3.2.2.1.1 Action<IWebHostBuilder> webconfigure参数
内置委托,在这里对委托进行了定义,主要两行代码:
配置IWebHostBuilder的默认web设置。
WebHost.ConfigureWebDefaults(webHostBuilder);
在core2.1中该方法是在CreateHostBuilder()中WebHost.CreateDefaultBuilder(arg)中调用,core3.1,改为Host.ConfigureDefualts(arg)后,通过委托在ConfigureWebHost中调用。因为此方法在委托里调用,在下面章节降到委托执行的时候再详细说明。
调用参数传入的委托
configure(webHostBuilder);
就是3.2.1中的
webBuilder.UseStartup<Startup>();
具体在下面章节讲解委托执行时在详细说明。
3.2.2.1.2 builder.ConfigureWebHost(webconfigure)法方定义
定义在Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions(GenericHostWebHostBuilderExtensions.cs)
1 using System;
2 using Microsoft.AspNetCore.Hosting;
3 using Microsoft.AspNetCore.Hosting.Internal;
4
5 namespace Microsoft.Extensions.Hosting
6 {
7 public static class GenericHostWebHostBuilderExtensions
8 {
9 public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
10 {
11 var webhostBuilder = new GenericWebHostBuilder(builder);
12 configure(webhostBuilder);
13 return builder;
14 }
15 }
16 }

Microsoft.AspNetCore.Hosting.Internal.GenericWebHostBuilder
在asp.net core 2.1 里IWebHostBuilder 在asp.net core 3.1里是怎么把泛型IHostBuilder生产webhost的builder的了,终于扒到它了。
通过Microsoft.AspNetCore.Hosting.Internal.GenericWebHostBuilder类实例。GenericWebHostBuilder是一个内部类。这个类网上资源极少,甚至在官方文档里找不到说明,但可以参考其源码GenericWebHostBuilder.cs源代码。此类声明和其构造函数声明如下:
internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
public GenericWebHostBuilder(IHostBuilder builder);
}
此类非常重要,它是Asp.net core 3.1 项目中IHostBuilder转变成IWebHostBuilder的基石或者源头 一个IHostBuilder实例最早就是通过此类构造函数实例化后变成了2.1里的IWebHostBuilder。实际干的就是注入了一些web有关的服务,详细建议查看源码(参考文档4)
3.2.2.2 给IWebHostBuilder配置Web相关参数委托参数:终于执行委托了
configure(webhostBuilder);
在传入了那么多层委托后,终于我们扒到底了,在实现了IHostBuilder转变为IWebHostBuilder后,所有config有关的委托终于得以执行
1 //在 builder.ConfigureWebHostDefaults()(–>在program.GreateHostBuilder()中)
2 //和下一句代码一同作为委托参数传入builder.ConfigureWebHost()
3 WebHost.ConfigureWebDefaults(webHostBuilder);
4 //Program.CreateHostBuilder()里的作为委托参数传入builder.ConfigureWebHostDefaults()
5 //再经过builder.ConfigureWebHostDefaults()合上一句一同作为委托传入builder.ConfigureWebHost()
6 webBuilder.UseStartup<Startup>();
3.2.2.2.1 WebHost.ConfigureWebDefualts(IWebHostBuilder)方法:
此方法在Asp.net core 2.1中 是WebHost.CreateDefaultBuilder(args) 里最后调用的方法,而在Asp.net core 3.1中,把IWebHostBuilder该为泛型IHostBuilder后,在执行IHostBuilder.ConfigureWebHost()时回调委托执行。此方法作用类似与Host.CreateDefaultBuilder(args),使用预配置配置一些关于Web方式的参数。主要是注入一些web相关的服务,配置主机地址等。此方法没有官方文档说明,建议直接查看源码WebHost.cs源代码(参考文档5)。
3.2.2.2.2 webBuilder.UseStartup<Startup>()法方:
UseStartup()是webbuilder的扩展发放,定义在Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions静态类中(WebHostBuilderExtensions.cs),此方法申明如下
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
从方法名就可以看出是使用Startup类,这个方法主要使用反射技术,反射Startup类,响应startup类中的配置的信息

3.3. 最后梳理一下builder核心的代码
1.IHostBuilder builder = Host.CreateDefaultBuilder(args);
创建一个基础builder(读取app.json)
2.IWebHostBuilder webHostBuilder= new GenericWebHostBuilder(builder);
转换为webbuilder
3.WebHost.ConfigureWebDefaults(webHostBuilder);
给webbuilder使用预先的默认值配置
4.webBuilder.UseStartup<Startup>();
读取startup类配置信息

4.总结
aps.net core 3.1的程序启动代码分析下来。基本3个概念创建builder ,通过builder生成host ,最后使用host执行起来。其中builder及builder配置 ,host主机都是重要的概念。core 3.1 和 core2.1的区别,就是把IWebHostBuilder上再抽象一个IHostBuilder,可以是core 3.1的代码更加的灵活,以后的服务可以不单单是web服务。
另外asp.net core 的builder 配置中可以看到很多非常依赖,控制反转技术也就是注入依赖,好处就是想要什么服务就注册什么服务,更加灵活。
如果想深入学习,建议参考如下文章:

1. .NET Core 3.0之深入源码理解Host(一)
2. .NET Core 3.0之深入源码理解Host(二)
作者的这个2个文章从源码角度简介了Ihostbuilder的相关知识剖析深度更深
3. 官方文档:.NET 通用主机 讲解IHostBuilder相关知识
4. 官方文档:ASP.NET Core 中的应用启动 介绍startup类相关知识

参考文档:
1. C# 匿名方法(函数) 匿名委托 内置泛型委托 lamada 作者:edzjx
2. .NET Core 3.0之深入源码理解Host(一) 作者:艾心
3. 官方文档Host.CreateDefaultBuilder 方法 作者:Micrsoft官方文档
4. GenericWebHostBuilder.cs源代码 作者:Asp.net@github
5. WebHost.cs源代码 作者:Asp.net@github
6. .NET Core 3.0之深入源码理解Host(二) 作者:艾心
7. 官方文档:.NET 通用主机 作者:Micrsoft官方文档
8. 官方文档:ASP.NET Core 中的应用启动 作者:Micrsoft官方文档
————————————————
版权声明:本文为CSDN博主「伊一线天」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/edzjx/article/details/104257596

List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac - ~雨落忧伤~ - 博客园

mikel阅读(475)

来源: List多个字段标识过滤 IIS发布.net core mvc web站点 ASP.NET Core 实战:构建带有版本控制的 API 接口 ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目 Using AutoFac – ~雨落忧伤~ – 博客园

List多个字段标识过滤

 

class Program
{
public static void Main(string[] args)
{

List<T> list = new List<T>();
list.Add(new T() { orderid = 1, houseid = 1 });
list.Add(new T() { orderid = 1, houseid = 1 });
list.Add(new T() { orderid = 1, houseid = 2 });
list.Add(new T() { orderid = 1, houseid =3 });
list.Add(new T() { orderid = 2, houseid = 1 });
list.Add(new T() { orderid = 2, houseid = 2 });
list.Add(new T() { orderid = 2, houseid = 2 });
list.Add(new T() { orderid = 2, houseid = 3 });

var _list= list.Where((x, y) => list.FindIndex(a => a.orderid == x.orderid&&a.houseid==x.houseid) == y);

}
public class T {
public long orderid { get; set; }
public long houseid { get; set; }
}
}

 

_list 结果如下:

 

new T() { orderid = 1, houseid = 1 }
new T() { orderid = 1, houseid = 2 }
new T() { orderid = 1, houseid = 3 }
new T() { orderid = 2, houseid = 1 }
new T() { orderid = 2, houseid = 2 }
new T() { orderid = 2, houseid = 3 }

lista.Where(a => a.name == “…”);
lista.Where((x, y) => lista.FindIndex(a => a.name == x.name && a.age == x.age) == y); // 相当于 where 的第二个重载吧
Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate);
y 相当于 源元素的索引

FindIndex 返回第一个匹配索引

IIS发布.net core mvc web站点

 

 

这里只有操作步骤!

第一、查看IIS是否安装了 AspNetCoreModule,查看路径:IIS-》模块 查看

 

 

 

安装步骤

下载网址:https://www.microsoft.com/net/download/windows

下载完以后点击安装,安装完成以后重启电脑

 

第二、.net Core Mvc 发布站点

右键站点-》发布

 

选择IIS、FTP等=》点击发布=》选择文件系统=》保存路径自行设置(C:\Users\wxd\Desktop\EP.Front.Feeds.H5)=》最后一步:发布(Release/Debugger版本 自行选择)

 

 

 

第三、IIS部署站点

保存路径为第二步发布网站的路径,然后点击确定

 

修改应用程序池,无托管代码

 

 

设置一切OK以后访问网站

 

 

 

 

 

ASP.NET Core 实战:构建带有版本控制的 API 接口

 一、前言

在上一篇的文章中,主要是搭建了我们的开发环境,同时创建了我们的项目模板框架。在整个前后端分离的项目中,后端的 API 接口至关重要,它是前端与后端之间进行沟通的媒介,如何构建一个 “好用” 的 API 接口,是需要我们后端人员好好思考的。
在系统迭代的整个过程中,不可避免的会添加新的资源,或是修改现有的资源,后端接口作为暴露给外界的服务,变动的越小,对服务的使用方造成的印象就越小,因此,如何对我们的 API 接口进行合适的版本控制,我们势必需要首先考虑。

系列目录地址:ASP.NET Core 项目实战
仓储地址:https://github.com/Lanesra712/Grapefruit.VuCore

 二、Step by Step

项目总是在不断迭代的,某些时候,因为业务发展的需要,需要将现有的接口进行升级,而原有的接口却不能立刻停止使用。比如说,你开发了一个接口提供给爱啪啪 1.0 版本使用,后来爱啪啪的版本迭代了,需要接口返回的数据与原先 1.0 版本返回的数据不同了,这时候,接口肯定是需要升级的,可是如果直接升级原有的接口,还在使用 1.0 版本的用户不就 GG 了,因此,如何做到既可以让 1.0 版本的用户使用,也可以让 2.0 版本的用户使用就需要好好考虑了,常见的解决方案,主要有以下几种。

a)使用不同的 API 名称

最简单粗暴,需要变更接口逻辑时就重新起个 API 名称,新的版本调用新的 API 名称,旧的版本调用旧的 API 名称。

https://yuiter.com/api/Secret/Login ##爱啪啪 1.0
https://yuiter.com/api/Secret/NewLogin ##爱啪啪 2.0

b)在 Url 中标明版本号

直接将 API 版本信息添加到请求的 Url 中,调用不同版本的 API ,就在 URL 中直接标明使用的是哪个版本。

https://yuiter.com/api/v1/Secret/Login ##爱啪啪 1.0
https://yuiter.com/api/v2/Secret/Login ##爱啪啪 2.0

c)请求参数中添加版本信息

将 API 的版本信息作为请求的一个参数传递,通过指定参数值来确定请求的 API 版本。

https://yuiter.com/api/Secret/Login?version=1 ##爱啪啪 1.0
https://yuiter.com/api/Secret/Login?version=2 ##爱啪啪 2.0

d)在 header 中标明版本号

前端在请求 API 接口时,在 header 中添加一个参数用来表明请求的版本信息,后端通过前端在 header 中设置的参数来判断,从而执行不同的业务逻辑分支。

复制代码
复制代码
POST https://yuiter.com/api/Secret/Login
Host: yuiter.com  
api-version: v1   ##爱啪啪 1.0

POST https://yuiter.com/api/Secret/Login
Host: yuiter.com  
api-version: v2   ##爱啪啪 2.0
复制代码
复制代码

在 Grapefruit.VuCore 这个项目中,我选择将 API 的版本信息添加到请求的地址中,从而明确的指出当前请求的接口版本信息。

  1、Swagger 集成

  后端完成了接口之后,肯定需要告诉前端,不管是整理成 txt/excel/markdown 文档,亦或是写完一个接口就直接发微信告诉前端,总是要多做一步的事情,而 Swagger 则可以帮我们省去这一步。通过配置之后,Swagger 就可以根据我们的接口自动生成 API 的接口文档,省时,省力。当然,如果前端小姐姐单身可撩,而你碰巧有意的话,另谈。

Swagger 是一个可以将接口文档自动生成,同时可以对接口功能进行测试的开源框架,在 ASP.NET Core 环境下,主流的有 Swashbuckle.AspNetCore 和 NSwag 这两个开源框架帮助我们生成 Swagger documents。这里,我采用的是 Swashbuckle.AspNetCore

在使用 Swashbuckle.AspNetCore 之前,首先我们需要在 API(Grapefruit.WebApi) 项目中添加对于 Swashbuckle.AspNetCore 的引用。你可以直接右键选中 API 项目选择管理 Nuget 程序包进行加载引用,也可以通过程序包管理控制台进行添加引用,这里注意,使用程序包管理控制台时,你需要将默认的项目修改成 API(Grapefruit.WebApi) 项目。当引用添加完成后,我们就可以在项目中配置 Swagger 了。

Install-Package Swashbuckle.AspNetCore

ASP.NET Core 的本质上可以看成是一个控制台程序,在我们创建好的 ASP.NET Core Web API 项目中,存在着两个类文件:Program.cs 以及 Startup.cs。与控制台应用一样,Program 类中的 Main 方法是整个程序的入口,在这个方法中,我们将配置好的 IWebHostBuilder 对象,构建成 IWebHost 对象,并运行该 IWebHost 对象从而达到运行 Web 项目的作用。

在框架生成的 Program 类文件中,在配置 IWebHostBuilder 的过程时,框架默认为我们添加了一些服务,当然,这里你可以注释掉默认的写法,去自己创建一个 WebHostBuilder 对象。同时,对于一个 ASP.NET Core 程序来说,Startup 类是必须的(你可以删除生成的 Startup 类,重新创建一个新的类,但是,这个新创建的类必须包含 Configure 方法,之后只需要在 UseStartup<Startup> 中将该类配置为 Startup 类即可),这里如果不指定 Startup 类会导致启动失败。

在 Startup 类中,存在着 ConfigureServices 和 Configure 这两个方法,在 ConfigureServices 方法中,我们将自定义服务通过依赖注入的方式添加到 IServiceCollection 容器中,而这些容器中的服务,最终都可以在 Configure 方法中进行使用;而 Configure 方法则用于指定 ASP.NET Core 应用程序将如何响应每一个 HTTP 请求,我们可以在这里将我们自己创建的中间件(Middleware)绑定到 IApplicationBuilder 上,从而添加到 HTTP 请求管道中。

这里只是很粗略的说明了 ASP.NET Core 项目的启动过程,想要仔细了解启动过程的推荐园子里的这篇文章 =》ASP.NET Core 2.0 : 七.一张图看透启动背后的秘密,因为 ASP.NET Core 2.1 版本相比于 2.0 版本又有些改变,这里有一些不一样的地方需要你去注意。

当我们简单了解了启动过程后,就可以配置我们的 Swagger 了。Swashbuckle.AspNetCore 帮我们构建好了使用 Swagger 的中间件,我们只需要直接使用即可。

首先我们需要在 ConfigureServices 方法中,将我们的服务添加到 IServiceCollection 容器中,这里,我们需要为生成的 Swagger Document 进行一些配置。

复制代码
复制代码
services.AddSwaggerGen(s =>
{
    s.SwaggerDoc("v1", new Info
    {
        Contact = new Contact
        {
            Name = "Danvic Wang",
            Email = "danvic96@hotmail.com",
            Url = "https://yuiter.com"
        },
        Description = "A front-background project build by ASP.NET Core 2.1 and Vue",
        Title = "Grapefruit.VuCore",
        Version = "v1"
    });
});
复制代码
复制代码

之后,我们就可以在 Configure 方法中启用我们的 Swagger 中间件。

app.UseSwagger();
app.UseSwaggerUI(s =>
{
    s.SwaggerEndpoint("/swagger/v1/swagger.json", "Grapefruit.VuCore API V1.0");
});

此时,当你运行程序,在域名后面输入/swagger 即可访问到我们的 API 文档页面。因为项目启动时默认访问的是我们 api/values 的 GET 请求接口,这里我们可以打开 Properties 下的 launchSetting.json 文件去配置我们的程序默认打开页面。

从上面的图可以看出,不管是使用 IIS 或是程序自托管,我们默认打开的 Url 都是 api/values,这里我们将两种启动方式的 launchUrl 值都修改成 swagger 之后再次运行我们的项目,可以发现,程序默认的打开页面就会变成我们的 API 文档页面。

我们使用 API 文档的目的,就是为了让前端知道请求的方法地址是什么,需要传递什么参数,而现在,并没有办法显示出我们对于参数以及方法的注释,通过查看 Swashbuckle.AspNetCore 的 github 首页可以看到,我们可以通过配置,将生成的 json 文件中包含我们对于 Controller or Action 的 Xml 注释内容,从而达到显示注释信息的功能(最终呈现的 Swagger Doc 是根据之前我们定义的这个 “/swagger/v1/swagger.json” json 文件来生成的)。

右键我们的 API 项目,属性 =》生产,勾选上 XML 文档文件,系统会默认帮我们创建生成 XML 文件的地址,这时候,我们重新生成项目,则会发现,当前项目下会多出这个 XML 文件。在重新生成项目的过程中,你会发现,错误列表会显示很多警告信息,提示我们一些方法没有添加 XML 注释。如果你和我一样强迫症的话,可以把 1591 这个错误添加到上面的禁止显示警告中,这样就可以不再显示这个警告了。

创建好 XML 的注释文件后,我们就可以配置我们的 Swagger 文档,从而达到显示注释的功能。这里,因为我会在 Grapefruit.Application 类库中创建各种的 Dto 对象,而接口中是会调用到这些 Dto 对象的。因此,为了显示这些 Dto 上的注释信息,这里我们也需要生成 Grapefruit.Application 项目的 XML 注释文件。

PS:这里我是将每个项目生成的注释信息 xml 文档地址都放在了程序的基础路径下,如果你将 xml 文档生成在别的位置,这里获取 xml 的方法就需要你进行修改。

复制代码
复制代码
services.AddSwaggerGen(s =>
{
    //...

    //Add comments description
    //
    var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);//get application located directory
    var apiPath = Path.Combine(basePath, "Grapefruit.WebApi.xml");
    var dtoPath = Path.Combine(basePath, "Grapefruit.Application.xml");
    s.IncludeXmlComments(apiPath, true);
    s.IncludeXmlComments(dtoPath, true);
});
复制代码
复制代码

当我们把 Swagger 配置完成之后,我们就可以创建具有版本控制的 API 接口了。

  2、带有版本控制的 API 接口实现

在请求的 API Url 中标明版本号,我不知道你第一时间看到这个实现方式,会想到什么,对于我来说,直接在路由信息中添加版本号不就可以了。。。em,这样过于投机取巧了。。。。

[Route("api/v1/[controller]")]//添加版本信息为v1
[ApiController]
public class ValuesController : ControllerBase
{
}

想了想,在 Url 中添加版本号,这个版本号是不是很像我们在 MVC 中使用的 Area。

Area 是 MVC 中经常使用到的一个功能,我们通常会将某些小的模块拆分成一个个的 Area,而这一个个的小 Area 其实就是这个 MVC 项目中的 MVC。通过为 controller 和 action 添加另一个路由参数 area,从而达到创建具有层次路由的结构。比如,这里,我们可以创建一个 Area 叫 v1,用来存储我们 1.x 版本的 API 接口,之后如果有新的 API 版本,新增一个 Area 即可,是不是很简单,嗯,说干就干。

右击我们的 API 项目,选择添加区域,新增的 Area 名称为 v1。

当 ASP.NET Core 的脚手架程序添加完成 Area 后,则会打开一个文件提示我们需要在 MVC 中间件中创建适用于 Area 的路由定义。

复制代码
复制代码
app.UseMvc(routes =>
{
  routes.MapRoute(
    name : "areas",
    template : "{area:exists}/{controller=Home}/{action=Index}/{id?}"
  );
});
复制代码
复制代码

当我们添加好路由规则定义后,我们在 Area 的 Controllers 文件夹下添加一个 WebAPI Controller。不同于 ASP.NET 中的 Area ,当我们在 ASP.NET Core 创建好一个 Area 之后,脚手架生成的文件中不再有 XXXAreaRegistration(XXX 为 Area 的名称)文件去注册这个 Area,而我们只需要在 Area 中的 Controller 中添加 Area 特性,即可告诉系统框架,这个 Controller 是在当前的 Area 下的。

如果你有自己尝试的话,就会发现,当我们创建好一个 v1 的 Area 后,这个请求的地址并没有按照我们的想法会体现在路由信息中,我们最后还是需要在 Route 中手动指明 API 版本。

复制代码
[Area("v1")]
[Route("api/v1/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}
复制代码

这样的话,和最开始直接在路由信息中写死版本信息其实也就没什么差别了,上网搜了搜,发现巨硬爸爸,也早已为我们准备好了实现版本控制 API 的利器 – Microsoft.AspNetCore.Mvc.Versioning。

和上面使用 Swashbuckle.AspNetCore 的方式相同,在我们使用 Versioning 之前,需要在我们的 API 项目中添加对于该 dll 的引用。这里需要注意下安装的版本问题,因为 Grapefruit.VuCore 这个框架距离现在搭建也有几个月的时间了,在这个月初的时候 .NET Core 2.2 也已经发布了,如果你和我一样还是采用的 .NET Core 2.1 版本的话,这里安装的 Versioning 版本最高只能到 2.3。

Install-Package Microsoft.AspNetCore.Mvc.Versioning

当我们安装完成之后,就可以进行配置了。

复制代码
复制代码
public void ConfigureServices(IServiceCollection services)
{
    services.AddApiVersioning(o =>
    {
        o.ReportApiVersions = true;//return versions in a response header
        o.DefaultApiVersion = new ApiVersion(1, 0);//default version select 
        o.AssumeDefaultVersionWhenUnspecified = true;//if not specifying an api version,show the default version
    });
}
复制代码
复制代码

ReportApiVersions:这个配置是可选的,当我们设置为 true 时,API 会在响应的 header 中返回版本信息。

DefaultApiVersion:指定在请求中未指明版本时要使用的默认 API 版本。这将默认版本为1.0。

AssumeDefaultVersionWhenUnspecified:这个配置项将用于在没有指明 API 版本的情况下提供请求,默认情况下,会请求默认版本的 API,例如,这里就会请求 1.0 版本的 API。

这里,删除我们之前的创建的 Area 和默认的 ValuesController,在 Controllers 文件夹下新增一个 v1 文件夹,将所有 v1 版本的 Controller 都建在这个目录下。新建一个 Controller,添加上 ApiVersion Attribute 指明当前的版本信息。因为我采用的方案是在 Url 中指明 API 版本,所以,我们还需要在 Route 中修改我们的路由属性以对应 API 的版本。这里的 v 只是一个默认的惯例,你也可以不添加。

复制代码
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class VaulesController : ControllerBase
{
}
复制代码

当我们修改好我们的 Controller 之后,运行我们的项目,你会发现,API 文档中显示的请求地址是不对的,难道是我们的配置没起作用吗?通过 Swagger 自带的 API 测试工具测试下我们的接口,原来这里请求的 Url 中已经包含了我们定义的版本信息,当我们指定错误的版本信息时,工具也会告诉我们这个版本的接口不存在。

虽然我们请求的 Url 中已经带上了版本信息,但是 API 文档上显示的请求地址却是不准确的,强迫症,不能忍。这里,需要我们修改生成 Swagger 文档的配置代码,将路由中的版本信息进行替换。重新运行我们的项目,可以发现,文档显示的 Url 地址也已经正确了,自此,我们创建带有版本控制的 API 也就完成了。

复制代码
复制代码
public void ConfigureServices(IServiceCollection services)
{
    services.AddSwaggerGen(s =>
    {
        //...

        //Show the api version in url address
        s.DocInclusionPredicate((version, apiDescription) =>
        {
            var values = apiDescription.RelativePath
                .Split('/')
                .Select(v => v.Replace("v{version}", version));

            apiDescription.RelativePath = string.Join("/", values);

            return true;
        });
    });
}
复制代码
复制代码

 三、总结

本章使用了 Microsoft.AspNetCore.Mvc.Versioning 这一组件来实现我们对于 API 版本控制的功能实现,可能你会有疑问,我们直接在路由中写明版本信息不是更简单吗?在我看来,使用这一组件的目的,在于我们可以以多种的方式实现 API 版本控制的目的,如果哪天你不想在 Url 中指明版本信息后,你可以很快的使用别的形式来完成 API 的版本控制。另外,直接在路由中写上版本信息,是不是会显得我们比较 ‘low’,哈哈哈,开玩笑,最后祝大家圣诞快乐~~~

 

 

 

 

ASP.NET Core 实战:使用 ASP.NET Core Web API 和 Vue.js 搭建前后端分离项目

 一、前言

这几年前端的发展速度就像坐上了火箭,各种的框架一个接一个的出现,需要学习的东西越来越多,分工也越来越细,作为一个 .NET Web 程序猿,多了解了解行业的发展,让自己扩展出新的技能树,对自己的职业发展还是很有帮助的。毕竟,现在都快到9102年了,如果你还是只会 Web Form,或许还是能找到很多的工作机会,可是,这真的不再适应未来的发展了。如果你准备继续在 .NET 平台下进行开发,适时开始拥抱开源,拥抱 ASP.NET Core,即使,现在工作中可能用不到。
雪崩发生时,没有一片雪花是无辜的,你也不会知道那片雪花,会引起最后的雪崩。有些自说自话,见谅。

系列目录地址:ASP.NET Core 项目实战
仓储地址:https://github.com/Lanesra712/Grapefruit.VuCore

 二、Step by Step

在整个的开发过程中,后端应用使用 Visual Studio 2017 进行开发,对于前端项目,则是使用 Visual Studio Code 进行开发,嗯,使用专业的工具做相应的事。对于前端的 Vue 项目,我采用的是 Vue CLI 来进行构建的,当然,巨硬也为我们准备了一套 Vue 的模板,如何使用的方法可以在附录中进行查看。

  1、项目开发环境搭建

1.1、安装 .NET Core

.NET Core 与之前的 .NET Framework 不一样,它不再紧紧的耦合在 Windows 系统上了,因此,我们可以在支持的操作系统上以安装软件的形式安装我们的 .NET Core 开发环境。

打开官网的下载页面(.NET Downloads),找到 .NET Core,这里因为我们需要在当前环境进行开发,所以需要安装 .NET Core SDK,下载完成后,一路 Next 即可。

当我们安装完成后,打开控制台,输入命令,则会显示出我们本机安装的 .NET Core 版本。

dotnet --info ## 或者使用 dotnet --version 查看本机安装的 .NET Core 版本信息

在 .NET Core 中为我们提供了 .NET Core CLI 这一工具使我们使用命令行的方式创建我们的 .NET Core 应用,这里我们还是使用 VS 来创建我们的应用,有兴趣的朋友,可以看看园子里的这篇文章 =》.NET Core dotnet 命令大全

1.2、安装 Node.js & Vue CLI

在整个前后端分离的项目的搭建中,前端的 Vue 项目,是使用 Vue CLI 3 进行搭建的脚手架项目,而 Vue CLI 本质上是一个全局安装的 npm 包,通过安装这一 npm 包可以为我们提供终端里的 vue 命令,因此我们需要使用这一脚手架工具的前提,则是需要我们安装 Node.js 环境。
打开 Node.js 官网(Node.js),选择长期支持版下载,之后一路 Next 下去即可。目前的 Node.js 安装包中已经包含了 npm,因此,我们安装好 Node.js 即可。npm 可以类比于我们 .NET 平台的 Nuget,而默认我们安装的全局组件和缓存默认是在 C:\Users\用户名\AppData\Roaming 下,如果你想修改缓存的地址或者全局安装的包地址则需要自己配置环境,具体可参考 =》Node.js安装及环境配置之Windows篇

PS:Vue CLI 3 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。

当 Node 环境安装好之后,我们就可以安装 Vue CLI 3 脚手架工具了,如果你之前已经全局安装了旧版本的 vue-cli (1.x 或 2.x),则需要先卸载旧版本的 Vue CLI。

npm uninstall vue-cli -g ## 卸载 vue-cli (1.x or 2.x)
npm install -g @vue/cli

安装之后,我们就可以在命令行中使用 vue 命令。

vue ## 查看 vue 相关帮助信息
vue --version ## 查看安装的 vue cli 版本信息

1.3、安装 Git

为代码添加版本控制是必须的,它可以详细的记录你的每一次操作,以及当你的某次作死导致的环境出错时,你可以很快的恢复环境。经常作死的表示,这个巨需要。
Git 作为一个分布式的版本控制系统,与 SVN 这种集中式的版本控制系统不同,我们的本地仓库不仅包含了我们的代码,还包含了每个人对代码的操作历史 log,而 SVN 的历史操作记录只存在于中央仓库中。
这样有什么好处呢?假如,某天中央仓库出错了需要重新创建,因为我们本地的代码不包含操作历史 log,你只能把代码重新放置到中央仓库,而文件的历史版本却丢失了。如果使用 Git 进行版本控制的话,因为我们本地的仓库是一个完整的包含历史操作记录的仓库,我们就可以毫无差别的重新搭建一个中央仓库。
Git 方面的学习教程,可以看看廖雪峰大神写的这一系列的教程 =》Git 教程
打开 Git 官网(Git)下载安装包安装,一路 Next 即可。安装完成后,开始菜单里出现 Git Bash 这个应用,则说明我们的 Git 已经安装成功了。安装 Git 之后,我们需要设置我们的名字以及 Email,从而表明我们的身份,这里使用 Git Bash 设置即可。

git config --global user.name "Your Name" ## 全局设置用户名
git config --global user.email "email@example.com" ## 全局设置邮箱

  2、应用整体框架搭建

  当我们安装好项目开发的环境之后就可以搭建我们的项目框架了,这里我选择将前后端的项目放到同一个 Git 仓储中,你也可以根据你自己的喜好放到多个 Git 仓储中。
新建一个文件夹作为仓储,在创建好的文件夹路径下打开 Git Bash,初始化我们的仓储。如果你勾选了显示隐藏文件夹,则会发现,当我们执行好初始化的命令之后,则会在当前文件夹下创建一个 .git 文件夹。

git init

当然,你也可以使用 VS 进行创建 Git 仓储,使用 VS 创建仓储后会自动帮我们创建 .gitignore 和 .gitattributes 文件,同样的,后续对于该仓储的任何 Git 操作,我们也可以通过 VS 进行。
gitignore 文件表示我们需要忽略的文件或目录,而 gitattribute 则用于设置非文本文件的对比方式,这里我们使用 VS 创建 Git 仓储后生成的 gitignore 文件默认会添加 .NET 项目需要忽略提交的文件和目录。因为,前端的项目我是使用 VS Code 进行开发的,这里,我需要将一些 VS Code 生成的文件也添加到 gitignore 文件中。

.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

创建 ASP.NET Core Web API 的具体过程就不演示了,这里采用的就是基础的多层架构,当我们创建好项目之后,可以看到 VS 右下角铅笔 icon 处会显示我们未做提交的修改。点击 icon ,输入我们的提交信息后,就可以将我们的修改提交到仓储中。

后端的 API 接口应用创建好了,现在我们使用 Vue CLI 来构建我们前端的 Vue 项目。这里,我选择在解决方案的根目录创建我们的前端项目。
在 Vue CLI 3 中,我们不仅可以使用 vue create 命令来创建我们的项目,而且可以使用图形化的页面创建我们的应用。

vue create project-name ## 使用命令行的形式创建
vue ui ## 使用图形化的方式创建

当使用 vue ui 命令后会自动打开创建项目的页面,可以看到,这个路径下,并没有创建好的项目,你可以选择从别的路径下导入,或者是直接创建新的项目。如果你有使用过 Vue CLI 之前的版本,使用大写字母创建项目时是会报错的,但是在 Vue CLI 3 版本中没有出现这种问题。

因为我将前端项目与后端的项目放到同一个仓储中,所以这里就不需要再进行初始化 git 仓库了,对于项目的配置,这里就采用默认的配置。点击创建之后就会自动搭建我们的项目。如何启动这个脚手架项目,可以按照生成的 README 文件中的步骤执行。

到这里,基础的 Vue 脚手架项目就已经搭建完成了,对于添加插件之类的内容,放到我们后面的内容中。另外,虽然我们在创建项目时并没有勾选初始化 Git 仓储,但是 Vue CLI 还是创建了一个 gitignore 文件,如果你和我一样,是将前后端项目放到一个仓储的话,可以把这个文件里的内容复制到项目根目录中的 gitignore 文件中,然后把这个文件删除。

 三、附录

微软官方有提供一套 Vue 的 SPA 应用模板,不过并没有显示在我们使用 VS 创建项目的页面中,而且需要我们添加一个插件之后,使用 .NET Core CLI 的方式创建。因为自己并没有详细了解这块的内容,这里只列出创建的方法,详细的介绍请查看微软的官方文档(Building Single Page Applications on ASP.NET Core with JavaScriptServices )。

## 安装 SPA 模板
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

当你安装好模板之后,可以看到,多了使用 Aurelia、Vue、Knockout 创建 SPA 模板的选项,这时我们就可以使用 dotnet new 命令来创建包含 Vue 的模板应用。模板创建完成后需要安装依赖的包。加载完依赖的包之后,我们就可以通过 VS 或 VS Code 开发调试我们的项目。

dotnet new vue ## 创建 Vue SPA 项目
npm install ## 还原依赖的 npm 包

 四、总结

这一章没有包含很多的内容,主要就是如何搭建我们的 .NET Core 和 Vue 的开发环境,以及创建我们的项目架构,在后面的文章中则会慢慢的阐述整个项目的开发过程,希望可以能对你有一丢丢的帮助。

 

 

Using AutoFac

第一次整理了下关于autofac的一些具体的用法

1. 安装 Autofac: Install-Package Autofac -Version 4.8.1

2. 创建两个类库项目,IService (用于编写接口),ServiceImpl(用于创建实现类)

  • IService 下

public interface IAnimalBark
{
void Bark();
}

public interface IAnimalSleep
{
void Sleep();
}

public interface IUser
{
void AddNew(string name, string pwd);
}

public interface ISchool
{
void AfterSchool();
}

  • ServiceImpl下

public class Dog : IAnimalBark,IAnimalSleep
{
public void Bark()
{
Console.WriteLine(“汪汪汪汪汪”);
}

public void Sleep()
{
Console.WriteLine(“zZ,睡着了”);
}
}

public class Cat : IAnimalBark
{
public void Bark()
{
Console.WriteLine(“喵喵喵”);
}
}

public class User : IUser
{
public void AddNew(string name, string pwd)
{
Console.WriteLine(“添加新的用户:” + name);
}
}

public class School : ISchool
{
public IAnimalBark dog { get; set; }
public void AfterSchool()
{
dog.Bark();
Console.WriteLine(“放学了”);
}
}

3. 原理性使用方法,如果再有一个接口和一个实现类,那就再注册一次

ContainerBuilder builder = new ContainerBuilder();
//注册实现类Dog,当我们 请求IAnimalBark接口 的时候返回的是类Dog的对象,原理性的代码
builder.RegisterType<Dog>().As<IAnimalBark>();
//上面一句也可改成下面一句,这样 请求Dog实现的任何接口 的时候都会返回Dog对象,原理性的代码
//builder.RegisterType<Dog>().AsImplementedInterfaces();
IContainer container = builder.Build();
//请求IAnimalBark接口
IAnimalBark dog = container.Resolve<IAnimalBark>();
dog.Bark();
Console.ReadKey();

4. 如果有很多接口,很多实现类,每次都要注册一次会很麻烦,可以如下进行一次注册

ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load(“Service”);//实现类所在的程序集名称
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();//常用
IContainer container = builder.Build();
IAnimalBark dog = container.Resolve<IAnimalBark>();
IUser user = container.Resolve<IUser>();
dog.Bark();
user.AddNew(“baidu”,”123″);
Console.ReadKey();

5. 如果有多个实现类,container.Resolve<IAnimalBark>();只会返回其中一个对象,如果想返回多个类的对象,应改成container.Resolve<IEnumerable<IAnimalBark>>();

ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load(“Service”);//实现类所在的程序集名称
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces();//常用
IContainer container = builder.Build();
IEnumerable<IAnimalBark> animals = container.Resolve<IEnumerable<IAnimalBark>>();
foreach(var animal in animals)
{
animal.Bark();
}
Console.ReadKey();

6. 如果一个实现类中定义了其他类型的属性(接口类型的属性),在注册时又加上builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired();这样会给属性进行“注入”

ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load(“Service”);//实现类所在的程序集名称
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired();//常用
IContainer container = builder.Build();
ISchool school = container.Resolve<ISchool>();
school.AfterSchool();
Console.ReadKey();

7. 可以通过在builder.RegisterAssemblyTypes(asm)后面以 Instance***()配置来实现Auto对象的生命周期

  •  InstancePerDependency()每次Resolve都返回新的对象

ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load(“Service”);//实现类所在的程序集名称
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired().InstancePerDependency();//常用
IContainer container = builder.Build();
ISchool school = container.Resolve<ISchool>();
ISchool school2 = container.Resolve<ISchool>();
Console.WriteLine(school.Equals(school2));
Console.ReadKey();

  • SingleInstance()每次Resolve都返回同一个对象,推荐

ContainerBuilder builder = new ContainerBuilder();
Assembly asm = Assembly.Load(“Service”);//实现类所在的程序集名称
builder.RegisterAssemblyTypes(asm).AsImplementedInterfaces().PropertiesAutowired().SingleInstance();//常用
IContainer container = builder.Build();
ISchool school = container.Resolve<ISchool>();
ISchool school2 = container.Resolve<ISchool>();
Console.WriteLine(object.ReferenceEquals(school,school2));
Console.ReadKey();

 

【IIS】设置URL重定向 - 狼窝窝 - 博客园

mikel阅读(513)

来源: 【IIS】设置URL重定向 – 狼窝窝 – 博客园

名字解释:

URL转发分为隐含转发和非隐含转发,隐含转发就是指当前域名转发后,仍然显示当前域名,而非隐含转发者指当前域名转发后,显示被转发的地址。

 

前提:

你有一个A网站:地址是:192.168.1.223:8700

B网站:地址是 www.baidu.com

现在要在输入A网站地址后,直接跳转到B网站中【旧版域名转新版域名类似的需求】

 

具体操作:

1、在IIS上安装ARR,下载地址(http://www.iis.net/downloads/microsoft/application-request-routing)

2、完成后打开,Microsoft Web Platform Installer

 

 

3、搜索arr关键字,安装应用程序请求路由

 

4、搜索url关键字,安装url重写工具

 

5、重启服务器,才能在iis目录下看到新安装的ARR

 

6、打开ARR,在右侧功能菜单中点击   Server Proxy Settings

 

7、勾选 Enable proxy,然后点击应用

 

8、打开你要设置URL重写的网站目录,此时就能看到一个URL重写的功能

 

9、双击打开,添加规则

此时可以在当前站点增加Index.html页面,里面说明即将要做的事情。

然后规则名随便起,模式输入:^(./*)  代表匹配当前网站192.168.1.223:8700,跳转规则为输入  192.168.1.223:8700/ss  则默认都跳转至新页面

下面的URL重定向页面直接写你的目标地址即可

 

 

 

PS:如果想在输入 192.168.1.223:8700的地址时就立刻跳转到百度,则需要设置HTTP重定向

 

 

另附:

IIS配置Url重写实现http自动跳转https的重定向方法(100%解决)

 

 

 

 

 

参考:

https://blog.51cto.com/cold2076goddess/1542583

https://blog.csdn.net/kelly921011/article/details/87889062    隐含转发

https://my.oschina.net/tanyixiu/blog/123832      隐含转发

https://blog.csdn.net/kelly921011/article/details/87889062   隐含转发

解决客户端和服务器不支持一般SSL协议版本或加密套件问题_客户端和服务器不支持常用的ssl协议版本或密码套件_Huang-Bo的博客-CSDN博客

mikel阅读(728)

来源: 解决客户端和服务器不支持一般SSL协议版本或加密套件问题_客户端和服务器不支持常用的ssl协议版本或密码套件_Huang-Bo的博客-CSDN博客

错误信息
详细报错信息如下图

错误原因
这种错误通常表示客户端和服务器之间存在协议版本或加密套件不匹配的情况。在SSL(Secure Socket Layer)连接过程中,客户端和服务器需要协商一种相同的加密协议版本和加密套件,以确保数据的安全传输。
具体可能出现的原因包括:
1.协议版本不兼容:客户端和服务器可能支持不同版本的 SSL 协议,导致协议版本不匹配。

2.加密套件不匹配:客户端和服务器可能支持不同的加密套件,导致加密套件不匹配。例如,如果客户端只支持使用 AES 加密算法,而服务器只支持使用 RC4 加密算法,则它们无法协商一种相同的加密套件。

3.协议配置不正确:客户端和服务器的 SSL 协议配置可能不正确,例如使用不安全的协议版本或加密套件,导致连接失败。

解决方法
适用于Windows server 2008 R2 操作系统
1.配置支持高版本TLS协议,去到iis站点所在服务器修改注册表,配置路径如下:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
在该层级下新建两个项,名称分别为TLS1.1和TLS1.2

2.然后在TLS1.1和TLS1.2项中再分别新增两个项Client和Server

3.然后分别再Client和Server项中新增两个(DWORD 32位值) TLS1.1和TLS1.2都要加
key:值
DisabledByDefault:0
Enabled: 1

client项

server项

4.最后重启服务器,再去到浏览器访问看效果

5.检测web站点支持的ssl协议版本号
https://www.ssllabs.com/ssltest/index.html
————————————————
版权声明:本文为CSDN博主「Huang-Bo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Habo_/article/details/129203156

领域驱动模型DDD(一)——服务拆分策略 - 键盘三个键 - 博客园

mikel阅读(501)

来源: 领域驱动模型DDD(一)——服务拆分策略 – 键盘三个键 – 博客园

前言

领域驱动模型设计在业界也喊了几年口号了,但是对于很多“务实”的程序员来说,纸上谈“术”远比敲代码难得太多太多。本人能力有限,在拜读相关作品时既要隐忍书中晦涩难懂的专业名词,又要去迎合西方大牛在撰写的过程中融入的西式故事。我想总会有一部分水平和我类似的码农们,需要一份对系统阐述DDD小白化的文本。因此,本人便自不量力地结合一些简单的项目经验,将领域驱动模型设计思想从理解到落地的实施和总结分享给诸位。当然,如果是某些行业先锋不幸看到本人稚嫩的文字时,就当作是马戏团中的小丑,一笑了之翻页便可。

思维的入门

在学习架构思想的初期,特别时面对架构模型时(例如六边形架构、领域服务拆分),我总会不自然地在对号入座,思考在模型中的这一块放到代码实现上是Controller层还是Dao层,是采用消息中间件还是NoSQL缓存。这种自动联想的“被动技能”在学习微服务设计架构思想的过程中是致命的,过于专注业务的技术实现而脱离架构思想本身的大局观,就容易陷入用“具体”无法概括“抽象”的处境。

对此,让我们先忘了平日在开发中使用的各种被分类到细致入微的技术,带着一种阅读“无用”文学而非可以模仿实操的工具书的心态一起来对领域驱动设计做一个基础性的理解。

1 常用的服务拆分方法

1.1 根据业务能力进行服务拆分

创建微服务架构的策略之一就是采用业务能力进行服务拆分,这也是目前市面上大部分产品设计初期采用的方式。主要原因是这种方法对于架构师来说是比较容易实施的,就好像在做一个分类游戏,关于用户的注册登录以及信息管理可以划分为一个业务,关于文章的发布下架以及内容更迭可以划分为一个业务。

这样的分类手段核心是以业务活动进行划分,也保证了大部分面向对象的程序员在代码实现时可以更快地、更明确地创建实体类,从而开辟出一个个针对不同业务功能的微服务。

倘若我们以程序员盘踞的技术论坛/博客为例,那么它们必须要实现最基础的三大业务:1、用户的注册登录以及用户对个人信息的操作;2、用户可以发布/修改/删除自己的文章;3、网站的后台管理员可以对前面两者进行更高权限的操作。至于更多的用户评论、用户私聊、vip充值等一些功能则是拓展,毕竟没有它们也不会影响到整个网站的正常运作,因此暂不加入讨论。

所以根据上述最基础的三大业务,如果要进行微服务架构设计(当然现实中不会有为了如此简陋的网站采用微服务,不然会被从业的程序员背地挖苦痛骂),我们可以画出相应的映射图【图1-1】:

采用业务能力进行拆分固然方便了架构师和程序员,在应对一般的企业项目时,这种方式是非常稳妥的方案,架构中每个划分出的服务内部后期可以根随功能的需求增加而逐渐迭代(例如,我现在需要文章发布时可以附带图片,那么可以在实现文章业务的服务中添加相关的接口与具体实现代码),但是整体的项目架构是保持不变的。

话虽如此,但读者们要知道上面仅仅是为了实现技术博客网站最基本业务而定义的“初代”架构。一种情况是随着整个技术博客网站的日益庞大,为了迎合用户的需要,我们不得不扩展网站的功能性,例如增添用户对文章点赞/收藏/留言,后台对文章内容是否健康的审核等等,这时候初代被划分好的服务内部就会日益“庞大”,需要我们重新操刀对其进行分解切割。

另外一种情况是由于用户量的提升,致使服务之间的远程调用、进程间的通信次数骤增而导致请求响应效率逐步低下,例如拥有庞大数量的读者用户在访问文章时不仅要通过文章服务获取文章内容,还要通过文章的作者每次调用用户服务来获取作者个人信息简介(在没有缓存机制,每个服务根据业务主体分割明确的假设情况),我们又得考虑把一些服务组合在一起:

例如当我们现在常用的注册/登录手段——利用第三方服务进行短信验证,如果只是为了实现用户注册/登录功能,完全可以并入到用户的服务中【图1-2】:

一旦不单用户登录/注册情况用到短信验证,例如要充值时候也要用到短信验证支付,那么就得把它独立出来【图1-3】:

上面的问题正是以业务进行服务拆分时难以避免的,因为业务必然是不断发展的(参考市面上众多臃肿的软件),被繁杂的业务牵着鼻子走的架构模式必然会陷入到不停拆分或重组的境地,那么服务内部以及服务之间的关系也将逐步模糊紊乱。

1.2 根据子域进行服务拆分

即使这是一段很枯燥的历史,但为了感谢曾在这个领域躬耕的技术先驱们我还是要带上这段文字:Eric Evans在他的经典著作中(Addison-Wesley Professional)提出的领域驱动设计是构建复杂软件的方法论,这些软件通常都是以面向对象和领域模型为核心。

既然是方法论,它提供的是“怎么办”的理论体系。类比蛋炒韭菜,我所能提供的是做这道菜需要什么工具和佐料,放油放蛋放盐放韭菜的先后顺序和比例,至于真正做起来火候的大小,炒菜的姿势,蛋菜的克量等都是视情况而定,并没有固定的要求。这又回到我在“思维入门”中提到的,不要用机械的“具体”去反推“抽象”理论,因为炒菜的姿势最多能影响的是菜的口味,并非是完成这道菜的必要条件。

下面让我们来理解DDD中重要的两个概念:子域和界限上下文。我先以好理解的方式在各位脑海中勾勒出这两者的基本认识,然后各位再去看专业解释会好接受的多。

如果把一个项目业务比作一个国家整体,而子域相当于国家内部的各个省份,这些省份细致地划分了国家每个地域面积大小和居住人群,而所有省份的聚合又构成了整个国家。作为限界上下文,其英文为bounded context,可以直译成“有界的环境”,那么套入前文各位可能举一反三理解成“省界”。但事实并非如此,以“省份”类比子域的话,省界并非是限制人口流动的主要原因,限制人流的本质在于一个省份有一个省份的风土人情和文化语言,例如广东省用粤语交流,福建省用闽南语交流,这些语言在省份内部是大家达成共识都能理解的,但跑到广东说福建话只会让本地人摸不着头脑。

因此,每个省份(子域)之间产生的交流障碍归根于它们各自内部通用语言环境不同(限界上下文)。这个举例或许不符合社会学对于人口流动的分析,但拿来解释子域和限界上下文还是很贴切的。

那么让我们引入教科书对二者的解释来巩固诸位对它们的记忆:子域是领域的一部分,领域是DDD中用来描述应用程序问题域的一个术语。识别子域的方法和识别业务能力一样:分析业务并识别业务的不同专业领域,分析产生的子域定义结果也会和识别业务能力得到的结果非常相近。DDD把领域设计模型的边界成为界限上下文,当使用微服务架构时,每一个界限上下文对应一个或则一组服务,我们可以通过DDD方式定义子域,并把子域对应为每一个服务【图1-4】。

1.3 拆分单体应用的难点

1.网络延时

网络延时是分布式系统中一直存在的问题。不论是以业务进行拆分还是子域进行拆分,对服务不断细化分解会导致各个服务之间的大量往返调用。即使可以通过批量处理API在一次往返中获取多个对象,从而减少延时。但是在其他情况下,解决方案是把多个服务整合到一起,用变成语言中的函数调用替换昂贵的进程通信。

2.同步进程间通信导致可用性降低

举一个最简单的例子,当我们在某东下单一件商品时创建了订单,创建订单的过程中需要获取商品的详细信息和购买者的详细信息,而这其中有一个服务出现了不可用的状态就会导致整个业务创建失败,这种同步进程通信带来的可用性降低让我们不得不折中采用异步消息进行处理。

3.服务之间维持数据的一致性

当我们对服务进行拆分后,服务之间如何保持数据一致性成为重点和难点。还是以某东为例,在活动期间大量用户可能在同一刻参与了秒杀活动,而对于仓存服务与商品服务之间如何保证它们在数量的一致性(比如前台有100个用户下单,那么对于商品来说只需要在原有的数量上减去100然后返回给前端页面作为商品信息的一部分及时展示给用户还有多少存货便可,但是实际上真正需要扣减的应该发生在仓库服务中,因为仓库服务内存储的才是实际库存),怎么让一波狂欢后实际库存与前端保持一致是业务中必须攻克的难题。

4.上帝类阻碍了服务的拆分

分解的一部分障碍就是所谓的上帝类,即全局类或则是“公用”类。上帝类通常为应用程序不同方面实现业务逻辑。

以美团外卖业务举例,一个不经思考设计的订单类中会将以下所有信息属性直接构建成一个类:商家信息属性(商家名称、商家地址等)、用户信息属性(账户、昵称、地址等)、外卖商品属性(外卖商品名称、价格、配料等)、配送方属性(配送员身份、配送员电话号码、配送起始时间、配送截止时间等)。不过这些属性涉及了不同服务中的应用程序,导致了商品系统中必然存在的订单所包含的信息变得特别庞大,并且与其他服务之间因为共同的属性保持着“暧昧”的联系。

一种解决方法是在数据库中创立一个公用的订单数据库,处理订单的所有服务使用此数据库,但这就出现了“紧耦合”的情况。

对此应用DDD将每个服务视为“孤岛”般的子域(尽量不与其他服务发生在属性的上纠葛,形成一座具有特色便于识别的“孤岛”)。所以让我们看看自己手机上美团APP中客户订单,然后再看看拿到外卖时钉在外卖上的商家收到的订单,幸运的话再看看外卖小哥送外卖时接收的订单,它们所包含的信息内容和信息数量绝对是不同。

这代表着在商家子域、用户子域、配送子域中我们根据子域的不同定义了不同侧重点的“订单”,而不是一股脑地都塞进全局订单类里让不同服务进行共享。也正是不同侧重点的“订单”只有在自己子域中才是“有效的”,“能被读懂的”(总不可能让商家去看外卖小哥的订单信息,用户去看商家的订单信息),子域便有了与之对应的界限上下文。

结语

最后我想很多人一直疑惑DDD与微服务到底有什么区别or联系?若用我浅薄的认识来解释那便是:DDD是一个抽象的设计理念,而现今微服务在开发领域的生态技术十分契合地为DDD架构思想的具象化提供了可行性。但诚如各位在文中所见,服务的分割又不仅仅只有DDD这一种方案。DDD给开发者带来的永远不是某种可以更快捷地开发“工具”,甚至夸大一点来说它依赖于技术却超脱技术。或许对于一个狂热的设计者来说,DDD给予他们一个能够决定项目拥有何种生命力与活力的机会,这种犹如“艺术创造”的思维活动便是架构的真正魅力所在。

这一章的基础内容便到此结束,下一章可能会讲一下服务的进程通信或则是Saga管理事务,尽情期待。

本文作者:键盘三个键

本文链接:https://www.cnblogs.com/jianpansangejian/p/16047304.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

删除系统盘大文件“hiberfil.sys”的方法 - 知乎

mikel阅读(1196)

来源: 删除系统盘大文件“hiberfil.sys”的方法 – 知乎

删除系统盘大文件“hiberfil.sys”的方法

最近我的电脑系统C盘空间满了,在清理磁盘空间时在系统盘下有两个超G的隐藏文件,一个是pagefile.sys,2G左右,一个是hiberfil.sys,6G左右,前面一个文件虚拟内存。

而“hiberfil.sys”是系统休眠文件,其大小和物理内存一样大,它可以删除掉,但却不能手动删除掉,只要在“控制面板”中打开“电源选项”,之后在电源管理对话框的“休眠”标签下,去掉“启用休眠”前的勾,重新启动计算机后,休眠文件就会自动删除。

更直接和简便的方法如下:

1. 如果使用了休眠功能,那么打开系统盘就会有一个很大的hiberfil.sys文件,它是将用户正在运行的程序,保存在这里,再启动系统就很快了。如要清理它,可以这样操作:

2. 开始→所有程序→附件→右键命令提示符→以管理员身份运行(否则可能会出错)。

3. 在命令提示符中输入:powercfg -hibernate off 按回车(Enter键)执行命令即可关闭休眠功能。

4. 再去开始菜单的关机选项中查看,休眠菜单已经没有了。

5. 再到系统盘查看,数G大小的hiberfil.sys文件也没有了(如还有可以自行删除)。

6. 如要再次开启休眠功能,可以在命令提示符中输入:powercfg -hibernate on 按回车(Enter键)执行命令即可打开休眠功能(无需重启电脑)。

7. 还可以在控制面板的电源选项中调整启用休眠的等待时间。

进入C:WindowsSystem32 找到cmd.exe 点右键—-以管理员身份运行
在cmd模式下输入命令“powercfg -h off”回车
重新启动计算机,再去看看已经没了!
(不需要关闭账户控制)

“pagefile.sys”是页面交换文件,即虚拟内存文件,这个文件不能删除,不过可以改变其大小和存放位置:文法如下:

1. 右击“我的电脑/属性”,然后在对话框的“高级”标签下单击“性能”下的“设置”按钮,在”性能选项”对话框中切换到“高级”标签下,再单击“虚拟内存”下的“更改”按钮,即可根据需要更改页面文件的大小。

2. 先选中C盘,然后选“无分页文件”,再点“设置”按钮;之后选中要生成该文件的盘符,在下面点选“自定义大小”并输入合适的数值,此数值通常为物理内存的1.5倍,再单击“设置”,最后单击“确定”就可以了。

3. 重新启动电脑,该文件就会存放到其他分区上了。

以下是该pagefile.sys文件的一些简单点的说明.
在一些较新的具有STR功能的主板上安装了xp及更新的操作系统后,在系统区根目录下会有一个 hiberfil.sys 的文件,它的大小和内存容量一样,这是什么文件呢?原来这个就是系统的休眠功能所占用的硬盘空间的文件(Windows 默认休眠功能是处于打开状态的),电脑处于休眠状态时,内存中的所有内容将保存到硬盘,并且电脑会被关闭。电脑在返回打开状态之后,关闭电脑时打开的所有程序和文档将全都还原到桌面。也就是说,当你让电脑进入休眠状态时,Windows 在关闭系统前将所有的内存内容写入hiberfil.sys文件。

而后,当你重新打开电脑,操作系统使用Hiberfil.sys把所有信息放回内存,电脑恢复到关闭前的状态。然而,Windows 在硬盘上留下了hiberfil.sys文件,即使这个文件不再需要了。

Hiberfil.sys文件可能很大,是磁盘碎片整理程序不能整理的特殊系统文件。因此,文件Hiberfil.sys的存在将阻止磁盘碎片整理程序进行彻底的整理操作。

交通部道路运输车辆卫星定位系统部标JTT808、809、796标准大全 - panchanggui - 博客园

mikel阅读(594)

来源: 交通部道路运输车辆卫星定位系统部标JTT808、809、796标准大全 – panchanggui – 博客园

无论是开发GPS设备硬件还是开发应用软件,都要面临一个标准,这个标准就是国家交通部发布的道路运输车辆卫星定位系统部标认证标准,它涵盖了GPS硬件设备参数、功能标准,也包括了设备上传到应用平台的协议标准,同时也包括了平台对平台的互联互传的技术标准。

也就是说凡是根据交通部这个标准开发的应用平台软件,都可以接入不同厂家开发的符合国标的GPS设备发送的上传数据。因为协议是同一的,所以平台也可以将数据转发给各地的省级交通部门的运管中心。

目前国家对需要上路的客车、危险品运输车,简称两客一危,要求必须要安装符合国标的GPS,如果运输承运者没有自己的软件平台,可以使用运管局的客户端软件,并接入到运管中心进行监控,收费对于企业来说也是个负担。现在国家对与GPS终端设备的标准又进行了进一步的扩展,要求必须要加上指纹等身份识别的读卡器,强制性加,费用不低。

平台软件开发商和GPS设备开发商安装标准开发后,需要通过交通部的部标检测和认证,获得部标认证后,这样才可以参加各地的软硬件招标。交通部对平台的功能测试,主要是模拟GPS终端发送标准的协议数据,然后测试是否能正确的接收数据。

GPS应用的开发者必须要了解并掌握这些协议,平台协议主要JT/TB808、809及扩展补充协议,同时协议中对于行车记录仪部分又应用了部颁的GB19056标准,所以这些都要看,JT/T796主要是硬件设备的标准,可以参考。

JTT 808-2011 道路运输车辆卫星定位系统终端通讯协议及数据格式

JTT 796-2011道路运输车辆卫星定位系统平台技术要求

JTT 794-2011北斗完善版-道路运输车辆卫星定位系统北斗兼容车载终端技术规范

JTT 809-2011 道路运输车辆卫星定位系统–平台数据交换

行车记录仪标准GB19056

行车记录仪标准GB19056 硬件参数标准