《ASP.NET Core 3框架揭秘》勘误[逐步完善中…] 第1版第1/2/3/4次印刷(部分错误已经在第3/4次)

[上册]

  • P6 最后1段
    • 原文:KestrelServer是采用libuv创建的跨平台Web服务器。
    • 改为:KestrelServer是一款跨平台Web服务器。
  • P8 第1段
    • 原文:注册的KestrelServer会绑定到“http//localhost:5000”和“https//localhost:5001”这两个地址监听请求
    • 改为:注册的KestrelServer会绑定到“http://localhost:5000”和“https://localhost:5001”这两个地址监听请求
  • P14 第1段
    • 原文:这两个终结点通过预先设置的规则将具有某些特征的请求(如路径、HTTP方法等)映射到对应的终结点
    • 改为:这两个中间件通过预先设置的规则将具有某些特征的请求(如路径、HTTP方法等)映射到对应的终结点
  • P21 第1段
    • 原文:curl.exe or the Invoke-WebRequest
    • 改为: curl或者Invoke-WebRequest
  • P25 第1个代码片段
    • 原文:
    • # 5.2. 设置(运行)工作目录,并将发布文件复制到out子目录下
      WORKDIR /app
      COPY --from= build /app/out .
    • 改为
    • # 5.2. 设置(运行)工作目录,并将发布文件复制到out子目录下
      WORKDIR /app
      COPY --from=build /app/out .
  • P25 第1段
    • 原文:具体来说,这个层将“microsoft/aspnetcore-build:2”作为基础镜像
    • 改为:具体来说,这个层将“mcr.microsoft.com/dotnet/core/sdk:3.0 ”作为基础镜像
  • P30 第4段
    • 原文:除在运行的时候介绍内存占用外
    • 改为:除在运行的时候减少内存占用外
  • P47 第1段
    • 原文:可以发现它具有如下两个程序集的应用
    • 改为:可以发现它具有如下针对两个程序集的引用
  • P51 第3段
    • 原文:之前解决程序集服务的方案就是PCL
    • 改为:之前解决程序集复用的方案就是PCL
  • P51 第3段
    • 原文:对于全新的 .NET 平台来说
    • 改为:对于全新的 .NET Core平台来说
  • P64 第2段
    • 原文:这个方法可以是一个单纯的方法
    • 改为:这个方法可以是一个单纯的抽象方法
  • P67 第2个代码片段
    • 原文:public class FoobarEngineFactory : EngineFactory
    • 改为:public class FoobarEngineFactory : MvcEngineFactory
  • P69 第2个代码片段
    • 原文:.Register<ControllerActivator, SingletonControllerActivator>();
    • 改为:.Register<IControllerActivator, SingletonControllerActivator>();
  • P71 第1个代码片段
    • 原文: public Foo(IBar bar, IBaz):this(bar) =>Baz = baz;
    • 改为:public Foo(IBar bar, IBaz baz):this(bar) =>Baz = baz;
  • P73 第4段
    • 原文:所以采用依赖注入模式的应用可以看作将服务推送到依赖注入容器
    • 改为:所以采用依赖注入模式的应用可以看作将服务推送给被依赖对象
  • P76 第1个代码片段
    • 原文
    • public class Foobar<T1, T2>: IFoobar<T1,T2>
      {
          public IFoo Foo { get; }
          public IBar Bar { get; }
          public Foobar(IFoo foo, IBar bar)
          {
              Foo = foo;
              Bar = bar;
          }
      }
    • 改为
    • public class Foobar<T1, T2>: IFoobar<T1,T2>
      {
          public T1 Foo { get; }
          public T2 Bar { get; }
          public Foobar(T1 foo, T2 bar)
          {
              Foo = foo;
              Bar = bar;
          }
      }
  • P91 第1个代码片段
    • 原文
    • public class Foobar<T1, T2>: IFoobar<T1,T2>
      {
          public IFoo Foo { get; }
          public IBar Bar { get; }
          public Foobar(IFoo foo, IBar bar)
          {
              Foo = foo;
              Bar = bar;
          }
      }
    • 改为
    • public class Foobar<T1, T2>: IFoobar<T1,T2>
      {
          public T1 Foo { get; }
          public T2 Bar { get; }
          public Foobar(T1 foo, T2 bar)
          {
              Foo = foo;
              Bar = bar;
          }
      }
  • P118 第1个代码片段
    • 原文: Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
    • 改为:Debug.Assert(ReferenceEquals(serviceProvider , singletonService.ApplicationServices));
  • P127 第1个代码片段
    • 原文:
    • foreach (var fileInfo in _fileProvider.GetDirectoryContents(subPath))
       {
           render(indent, fileInfo.Name);
           if (fileInfo.IsDirectory)
           {
                  Render($@"{subPath}{fileInfo.Name}".TrimStart('\'));
           }
      }
    • 改为:
    • foreach (var fileInfo in _fileProvider.GetDirectoryContents(subPath))
       {
           render(indent, fileInfo.Name);
           if (fileInfo.IsDirectory)
           {
                  Render($@"{subPath}{fileInfo.Name}".TrimStart('\'));
           }
      }
      indent--;
  • P144 倒数第2段
    • 原文:这两个特殊的FileProvider类型都定义在“Microsoft.Extensions.FileProviders.Abstractions”这个NuGet包中。
    • 改为:这两个特殊的FileProvider类型分别定义在“Microsoft.Extensions.FileProviders.Abstractions”和“Microsoft.Extensions.FileProviders.Composite”NuGet包中。
  • P147 倒数第2段
    • 原文:所以它们分别映射为文件服务器上的目录“c:dir1”和“c:dir1foobar”。
    • 改为:所以它们分别映射为文件服务器上的目录“c: estdir1”和“c: estdir1foobar”。
  • P196 第2段
    • 原文:FileConfigurationSource对象的Optional属性表示当前配置源是否可以默认。如果该属性被设置成False,即使指定的配置文件不存在也不会抛出异常。可默认的配置文件在支持多环境的场景中具有广泛应用。正如前面的演示实例,我们可以按照如下方式加载两个配置文件:基础配置文件appsettings.json一般包含相对全面的配置,针对某个环境的差异化配置则定义在appsettings.{environment}.json文件中。前者是必需的,后者则是可以默认的,这保证了应用程序在缺少基于当前环境的差异化配置文件的情况下依然可以使用定义在基础配置文件中的默认配置。
    • 改为:FileConfigurationSource对象的Optional属性表示当前配置源是否可以缺省。如果该属性被设置成True,即使指定的配置文件不存在也不会抛出异常。可缺省的配置文件在支持多环境的场景中具有广泛应用。正如前面的演示实例,我们可以按照如下方式加载两个配置文件:基础配置文件appsettings.json一般包含相对全面的配置,针对某个环境的差异化配置则定义在appsettings.{environment}.json文件中。前者是必需的,后者则是可以缺省的,这保证了应用程序在缺少基于当前环境的差异化配置文件的情况下依然可以使用定义在基础配置文件中的默认配置。
  • P213 第1段
    • 原文:Lood方法还会利用这个DbContext对象将提供的初始化配置添加到数据库中。
    • 改为:Load方法还会利用这个DbContext对象将提供的初始化配置添加到数据库中。
  • P213 第1个代码片段
    • 原文
    • ...    
      private IDictionary<string, string> Initialize(
              ApplicationSettingsContext dbContext)
          {
              foreach (var item in _initialSettings)
              {
                  dbContext.Settings.Add(new ApplicationSetting(item.Key, item.Value));
              }
              return _initialSettings.ToDictionary(it => it.Key, it => it.Value, 
                  StringComparer.OrdinalIgnoreCase);
          }
      }
    • 改为
    • private IDictionary<string, string> Initialize(
              ApplicationSettingsContext dbContext)
          {
              foreach (var item in _initialSettings)
              {
                  dbContext.Settings.Add(new ApplicationSetting(item.Key, item.Value));
              }
              dbContext.SaveChanges();
              return _initialSettings.ToDictionary(it => it.Key, it => it.Value, 
                  StringComparer.OrdinalIgnoreCase);
          }
      }
  • P226第2段
    • 原文:我们演示的实例已经涉及Options模型的3个重要的接口,它们分别是IOptions<TOptions>和IOptionsSnapshot<TOptions>
    • 改为:我们演示的实例已经涉及Options模型的3个重要的接口,它们分别是IOptions<TOptions>、IOptionsSnapshot<TOptions>和IOptionsMonitor<TOptions>
  • P233 第1段
    • 原文:第二个反省参数代表依赖的服务类型
    • 改为:第二个泛型参数代表依赖的服务类型
  • P279 最后1段
    • 原文:TraceListener具有两个名为TraceData的方法
    • 改为:TraceSource具有两个名为TraceData的方法
  • P299 最后1段
    • 原文:宿主元素为通过Foobar对象转换而成的EventPayload对象
    • 改为:数组元素为通过Foobar对象转换而成的EventPayload对象
  • P309 第2个代码片段
    • 原文:
    • public virtual IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer);
      public virtual IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer, 
      public virtual IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer, 
              Predicate<string> isEnabled);
              Func<string, object, object, bool> isEnabled);
    • 改为
    • public virtual IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer);
      public virtual IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer, Predicate<string> isEnabled);
      public virtual IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer, Func<string, object, object, bool> isEnabled);
  • P311第1段
    • 原文:它采用与演示实例提供的Observer<T>一样的实现方式,即通过指定的委托对象(类型分别为Action<T>和Action<Exception>)实现IObservable<T>接口的3个方法
    • 改为:它采用与演示实例提供的Observer<T>一样的实现方式,即通过指定的委托对象(类型分别为Action<T>和Action<Exception>)实现IObserver<T>接口的3个方法
  • P345 第1个代码片段
    • 原文
      [ProviderAlias("Debug")]
      public class EventLogLoggerProvider : ILoggerProvider {...}
      
      [ProviderAlias("EventLog")]
      public class DebugLoggerProvider : ILoggerProvider {...}
    • 改为
      [ProviderAlias("Debug")]
      public class DebugLoggerProvider : ILoggerProvider {...}
      
      [ProviderAlias("EventLog")]
      public class EventLogLoggerProvider : ILoggerProvider {...}
  • P386 第1个代码片段
    • 原文:"Host": "192.168.0.2" (appsettings.production.json)
    • 改为:"Host": "192.168.0.3"
  • P427 最后一个代码片段
    • 原文
      class Program
      {
          static void Main()
          {            
              Host.CreateDefaultBuilder()
                  .ConfigureServices(svcs => svcs.AddSingleton(
                       new StringContentMiddleware("Hello World!")))
                  .ConfigureWebHost(builder => builder
                  .Configure(app => app.UseMiddleware<StringContentMiddleware>()))
              .Build()
              .Run();
          }
      }
    • 改为
      class Program
      {
          static void Main()
          {            
              Host.CreateDefaultBuilder()
                  .ConfigureServices(svcs => svcs.AddSingleton(
                       new StringContentMiddleware("Hello World!")))
                  .ConfigureWebHostDefaults(builder => builder
                  .Configure(app => app.UseMiddleware<StringContentMiddleware>()))
              .Build()
              .Run();
          }
  • P432第1段
    • 原文:我们从作为参数的ServiceCollection对象中获取当前注册的所有服务
    • 改为:我们从作为参数的IServiceCollection对象中获取当前注册的所有服务
  • 438第1段
    • 原文:对于一个非根容器的IServiceProvider对象来说,其生命周期决定于对应的ServiceScope对象,调用ServiceScope的Dispose方法会导致对封装IServiceProvider对象的回收释放。
    • 改为:对于一个非根容器的IServiceProvider对象来说,其生命周期决定于对应的IServiceScope对象,调用IServiceScope的Dispose方法会导致对封装IServiceProvider对象的回收释放。
  • P451 第1个代码片段
    • 原文:
    • public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action<IApplicationBuilder> configure)
      {
          var applicationName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name;
          ...
      }
    • 改为
    • public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action<IApplicationBuilder> configure)
      {
          var applicationName = configure.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name;
          ...
      }
  • P457 第2段
    • 原文:进而得到承载环境信息的IWebHostEnvironment服务,最终根据提供的环境信息进行有针对性的服务注册
    • 改为:进而得到承载环境信息的IWebHostEnvironment服务,最终根据提供的环境信息进行有针对性的中间件注册
  • P467 第1段
    • 原文:本章将介绍真实的管道,而且会按照类似的设计重建一个Mini版的ASP.NET Core框架。
    • 改为:本章不会介绍真实的管道,而按照类似的设计重建一个Mini版的ASP.NET Core框架。
    • P468 第1个代码片段
    • 原文:
    • public class HttpContext
      {
          public abstract HttpRequest  Request { get; }
          public abstract HttpResponse Response { get; }
      }
      
      public class HttpRequest
      {
          public abstract Uri Url { get;  }
          public abstract NameValueCollection Headers { get; }
          public abstract Stream Body { get;  }
      }
      
      public class HttpResponse
      {
          public abstract int StatusCode { get; set; }
          public abstract NameValueCollection Headers { get; }
      
          public abstract Stream Body { get; }
      }
    • 改为:
    • public class HttpContext
      {
          public HttpRequest Request { get; }
          public HttpResponse Response { get; }
      }
      
      public class HttpRequest
      {
          public Uri Url { get;  }
          public NameValueCollection Headers { get; }
          public Stream Body { get;  }
      }
      
      public class HttpResponse
      {
          public int StatusCode { get; set; }
          public NameValueCollection Headers { get; }
          public Stream  Body { get; }
      }
  • P472 第2段
    • 原文:可以看出,IHttpRequestFeature接口和IHttpResponseFeature接口具有与抽象类型HttpRequest和HttpResponse完全一致的成员定义。
    • 改为:可以看出,IHttpRequestFeature接口和IHttpResponseFeature接口具有与类型HttpRequest和HttpResponse完全一致的成员定义。
  • P481 第2段
    • 原文:也可以获取代表请求的HTTP消息的首部和主题
    • 改为:也可以获取代表请求的HTTP消息的首部和主体
  • P526 第1段
    • 原文:定义在该程序集中的Startup方法会被加载出来
    • 改为:定义在该程序集中的Startup类型会被加载出来

[下册]

  • P563 第2段
    • 原文:第四个类型为IOptions<DirectoryBrowserOptions>的参数用于提供表示配置选项的DirectoryBrowserMiddleware的DirectoryBrowserOptions对象
    • 改为:第四个类型为IOptions<DirectoryBrowserOptions>的参数用于提供表示配置选项的DirectoryBrowserOptions对象
  • P580 最后一段
    • 原文:如果采用字符串“files/{name}.{ext?}”来表示针对某个文件的路由模板,文件名({name})和扩展名(ext?)体现为路由参数,而它们之间的“.”就是RoutePattern的第三种展现形式,被称为分隔符。
    • 改为:如果采用字符串“files/{name}.{ext?}”来表示针对某个文件的路由模板,文件名({name})和扩展名(ext?)体现为路由参数,而它们之间的“.”就是RoutePatternPart的第三种展现形式,被称为分隔符。
  • P583 第3段
    • 原文:在这种状况下就需要利用为注册的路由模式指定不同的匹配的权重或者优先选择一个匹配度最高的路由模式
    • 改为:在这种状况下就需要利用为注册的路由模式指定不同的匹配的权重或者优先选择一个匹配度最高的路由模式
  • P639 第1个代码片段
    • 原文
    • var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat, 
       context.HttpContext.Response.StatusCode));
      var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, 
      context.HttpContext.Response.StatusCode);
                  
      context.HttpContext.Request.Path = newPath;
      context.HttpContext.Request.QueryString = newQueryString;
      await context.Next(context.HttpContext);
    • 改为
    • var newPath = new PathString(string.Format(CultureInfo.InvariantCulture, pathFormat,  context.HttpContext.Response.StatusCode));
      var formatedQueryString = queryFormat == null ? null :string.Format(CultureInfo.InvariantCulture, queryFormat, 
      context.HttpContext.Response.StatusCode);
      var newQueryString = queryFormat == null ? QueryString.Empty : new QueryString(formatedQueryString);
                  
      context.HttpContext.Request.Path = newPath;
      context.HttpContext.Request.QueryString = newQueryString;
      await context.Next(context.HttpContext);
  • P639 第2段
    • 原文:这个特性对应的接口是具有如下定义的IStatusCodeReExecuteFeature,但是该接口仅仅包含两个针对路径的属性,并没有用于携带原始请求上下文的属性,但是默认实现类型StatusCodeReExecuteFeature包含了这个属性。
    • 改为:删除该该内容(3.0中已经修复)
  • P639 第2个代码片段
    • 原文
      public interface IStatusCodeReExecuteFeature
      {
          string OriginalPath { get; set; }
          string OriginalPathBase { get; set; }
      }
    • 改为
      public interface IStatusCodeReExecuteFeature
      {
          string OriginalPathBase { get; set; }
          string OriginalPath { get; set; }
          string OriginalQueryString { get; set; }
      }
  • P648 第2段
    • 原文:这是响应缓存的理论机制和指导思想
    • 改为:这是响应缓存的理论基础和指导思想
  • P666 第2段
    • 原文:一个Tick代表1纳秒,即一千万分之一秒,1 毫秒等于10 000 000纳秒。DateTimeOffset的Ticks数返回距离“0001年1月1日午夜12:00:00”这个基准时间点的纳秒数
    • 改为:一个Tick代表100纳秒,1纳秒一千万分之一秒,1 毫秒等于10 000 000纳秒。DateTimeOffset的Ticks数返回距离“0001年1月1日午夜12:00:00”这个基准时间点的纳秒数
  • P678 第3段
    • 原文:还需要提供一个LoggerFactory对象用来生成记录日志的Logger,以及承载相关配置选项的ResponseCachingOptions对象。
    • 改为:还需要提供一个ILoggerFactory工厂用来生成记录日志的ILogger对象,以及承载相关配置选项的ResponseCachingOptions对象。
  • P710 倒数第1段
    • 原文:一个ClaimsPrincipal对象还有一个必需的AuthenticationScheme属性,该属性表示采用的认证方案名称
    • 改为:一个AuthenticationTicket对象还有一个必需的AuthenticationScheme属性,该属性表示采用的认证方案名称
  • P756 第3段
    • 原文:通过第9章的介绍可知,认证处理器是整个认证系统的核心,授权处理器也是整个授权系统的核心
    • 改为:通过第19章的介绍可知,认证处理器是整个认证系统的核心,授权处理器也是整个授权系统的核心
  • P807
    • 删除_culture字段相关代码,整个DictionaryStringLocalizer 类型定义为

    public class DictionaryStringLocalizer : IStringLocalizer
    {
        private readonly Dictionary<string, LocalizedStringEntry> _entries;
    
        public DictionaryStringLocalizer(Dictionary<string, LocalizedStringEntry> entries)
        => _entries = new Dictionary<string, LocalizedStringEntry>(entries);
    
        public LocalizedString this[string name] => GetString(name, CultureInfo.CurrentUICulture);
    
        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                var raw = this[name];
                return raw.ResourceNotFound ? raw : new LocalizedString(name, string.Format(raw.Value, arguments));
            }
        }
    
        public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
        {
            var culture = CultureInfo.CurrentUICulture;
            foreach (var item in _entries)
            {
                if (includeParentCultures)
                {
                    yield return GetString(item.Key, culture);
                }
                else
                {
                    yield return item.Value.Translations.TryGetValue(culture, out var text)
                        ? new LocalizedString(item.Key, text)
                        : new LocalizedString(item.Key, item.Key, true);
                }
            }
        }
    
        public IStringLocalizer WithCulture(CultureInfo culture) => throw new NotImplementedException();
    
        private LocalizedString GetString(string name, CultureInfo culture)
        {
            if (!_entries.TryGetValue(name, out var entry))
            {
                return new LocalizedString(name, name, true);
            }
    
            if (entry.Translations.TryGetValue(culture, out var message))
            {
                return new LocalizedString(name, message);
            }
    
            if (culture == CultureInfo.InvariantCulture)
            {
                return new LocalizedString(name, entry.Value, true);
            }
    
            return GetString(name, culture.Parent);
        }
    }
  • P824 第1段
    • 原文:状态为Unhealthy的服务被视为不可用(Unavailable),所以响应状态码为“Service Unavailable”
    • 改为:状态为Unhealthy的服务被视为不可用(Unavailable),所以响应状态码为“503 Service Unavailable”
  • P825 第2段
    • 原文:但是不能通过状态码来区分 Healthy 和 Unhealthy 这两种可用状态
    • 改为:但是不能通过状态码来区分 Healthy 和 Degraded 这两种可用状态
  • P836 第1段
    • 原文:AddCheck<T>方法和GetServiceOrCreateInstance<T>方法的差异体现在它们利用依赖注入框架提供对应IHealthCheck对象的方式上。如果直接将IHealthCheck接口的实现类型或者实例注册到依赖注入框架中,AddCheck<T>方法调用的是ActivatorUtilities类型的GetServiceOrCreateInstance<T>方法,意味着它会复用现有的Scoped服务条例或者Singleton服务实例。GetServiceOrCreateInstance<T>方法调用的是ActivatorUtilities类型的CreateInstance<T>方法,意味着它总是创建一个新的IHealthCheck对象。
    • 改为:AddCheck<T>方法和AddTypeActivatedCheck<T>方法的差异体现在它们利用依赖注入框架提供对应IHealthCheck对象的方式上。如果直接将IHealthCheck接口的实现类型或者实例注册到依赖注入框架中,AddCheck<T>方法调用的是ActivatorUtilities类型的GetServiceOrCreateInstance<T>方法,意味着它会复用现有的Scoped服务实例或者Singleton服务实例。AddTypeActivatedCheck<T>方法调用的是ActivatorUtilities类型的CreateInstance<T>方法,意味着它总是创建一个新的IHealthCheck对象。
  • P839 倒数第1段
    • 原文:如下所示的内部DefaultHealthCheckService继承了抽象类HealthCheckService。
    • 改为:如下所示的内部DefaultHealthCheckService类型继承了抽象类HealthCheckService。
  • P857 倒数第1段
    • 原文:我们需要先了解通过HostFilteringOptions类型标识的配置选项。
    • 改为:我们需要先了解通过HostFilteringOptions类型表示的配置选项。
  • P871 倒数第1个代码片段
    • 原文:
      • Remote IP:192.168.0.1
        Host:www.foo.com
        Scheme:http
        X-Original-For:[::1]:54578
        X-Original-Host:localhost:5000
        X-Original-Proto:http
    • 改为
      • Remote IP:192.168.0.254
        Host:www.baz.com
        Scheme:https
        X-Original-For:[::1]:53754
        X-Original-Host:localhost:5000
        X-Original-Proto:http