ASP.NET Core 3.0 : 二十四. 配置的Options模式

2023-05-16

上一章讲到了配置的用法及内部处理机制,对于配置,ASP.NET Core还提供了一种Options模式。(ASP.NET Core 系列目录)

一、Options的使用

上一章有个配置的绑定的例子,可以将配置绑定到一个Theme实例中。也就是在使用对应配置的时候,需要进行一次绑定操作。而Options模式提供了更直接的方式,并且可以通过依赖注入的方式提供配置的读取。下文中称每一条Options配置为Option。

1.简单的不为Option命名的方式

依然采用这个例子,在appsettings.json中存在这样的配置:


{
  "Theme": {
    "Name": "Blue",
    "Color": "#0921DC"
  }
}  

修改一下ValueController,代码如下:


public class ValuesController : Controller
{
    private IOptions<Theme> _options = null;
    public ValuesController(IOptions<Theme> options)
    {
        _options = options;
    }

    public ContentResult GetOptions()
    {
        return new ContentResult() { Content = $"options:{ _options.Value.Name}" };
    }
}  

依然需要在Startup文件中做注册:


    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<Theme>(Configuration.GetSection("Theme"));

        services.AddControllersWithViews();  //3.0中启用的新方法
    }  

 

请求这个Action,获取到的结果为:


options:Blue  

这样就可以在需要使用该配置的时候通过依赖注入的方式使用了。但有个疑问,这里将“Theme”类型绑定了这样的配置,但如果有多个这样的配置呢?就如同下面这样的配置的时候:


  "Themes": [
    {
      "Name": "Blue",
      "Color": "#0921DC"
    },
    {
      "Name": "Red",
      "Color": "#FF4500"
    }
  ]  

在这样的情况下,存在多个Theme的配置,这样对于之前这种依赖注入的方式就不行了。这时系统提供了将注入的Options进行命名的方法。

2.为Option命名的方式

首先需要在Startup文件中注册的时候对其命名,添加如下两条注册代码:


services.Configure<Theme>("ThemeBlue", Configuration.GetSection("Themes:0"));
services.Configure<Theme>("ThemeRed" , Configuration.GetSection("Themes:1"));  

修改ValueController代码,添加IOptionsMonitor<Theme>和IOptionsSnapshot<Theme>两种新的注入方式如下:


        private IOptions<Theme> _options = null;
        private IOptionsMonitor<Theme> _optionsMonitor = null;
        private IOptionsSnapshot<Theme> _optionsSnapshot = null;
        public ValuesController(IOptions<Theme> options, IOptionsMonitor<Theme> optionsMonitor, IOptionsSnapshot<Theme> optionsSnapshot)
        {
            _options = options;
            _optionsMonitor = optionsMonitor;
            _optionsSnapshot = optionsSnapshot;
        }

        public ContentResult GetOptions()
        {
            return new ContentResult() { Content = $"options:{_options.Value.Name}," +
                $"optionsSnapshot:{ _optionsSnapshot.Get("ThemeBlue").Name }," +
                $"optionsMonitor:{_optionsMonitor.Get("ThemeRed").Name}" };
        }  

请求这个Action,获取到的结果为:


options:Blue,optionsSnapshot:Red,optionsMonitor:Gray  

新增的两种注入方式通过Options的名称获取到了对应的Options。为什么是两种呢?它们有什么区别?不知道有没有读者想到上一章配置的重新加载功能。在配置注册的时候,有个reloadOnChange选项,如果它被设置为true的,当对应的数据源发生改变的时候,会进行重新加载。而Options怎么能少了这样的特性呢。

3.Option的自动更新与生命周期

为了验证这三种Options的读取方式的特性,修改Theme类,添加一个Guid字段,并在构造方法中对其赋值,代码如下:


public class Theme
{
    public Theme()
    {
        Guid = Guid.NewGuid();
    }
    public Guid Guid { get; set; }
    public string Name { get; set; }
    public string Color { get; set; }
}  

修改上例中的名为GetOptions的Action的代码如下:


public ContentResult GetOptions()
{
    return new ContentResult()
    {
        Content = $"options:{_options.Value.Name}|{_options.Value.Guid}," +
        $"optionsSnapshot:{ _optionsSnapshot.Get("ThemeBlue").Name }|{_optionsSnapshot.Get("ThemeBlue").Guid}," +
        $"optionsMonitor:{_optionsMonitor.Get("ThemeRed").Name}|{_optionsMonitor.Get("ThemeRed").Guid}"
    };
}  

请求这个Action,返回结果如下:


options:Blue|ad328f15-254f-4505-a79f-4f27db4a393e,optionsSnapshot:Red|dba5f550-29ca-4779-9a02-781dd17f595a,optionsMonitor:Gray|a799fa41-9444-45dd-b51b-fcd15049f98f  

刷新页面,返回结果为:


options:Blue|ad328f15-254f-4505-a79f-4f27db4a393e,optionsSnapshot:Red|a2350cb3-c156-4f71-bb2d-25890fe08bec,optionsMonitor:Gray|a799fa41-9444-45dd-b51b-fcd15049f98f  

可见IOptions和IOptionsMonitor两种方式获取到的Name值和Guid值均未改变,而通过IOptionsSnapshot方式获取到的Name值未改变,但Guid值发生了改变,每次刷新页面均会改变。这类似前面讲依赖注入时做测试的例子,现在猜测Guid未改变的IOptions和IOptionsMonitor两种方式是采用了Singleton模式,而Guid发生改变的IOptionsSnapshot方式是采用了Scoped或Transient模式。如果在这个Action中多次采用IOptionsSnapshot读取_optionsSnapshot.Get("ThemeBlue").Guid的值,会发现同一次请求的值是相同的,不同请求之间的值是不同的,也就是IOptionsSnapshot方式使采用了Scoped模式(此验证示例比较简单,请读者自行修改代码验证)。

在这样的情况下,修改三种获取方式对应的配置项的Name值,例如分别修改为“Blue1”、“Red1”和“Gray1”,再次多次刷新页面查看返回值,会发现如下情况:

IOptions方式:Name和Guid的值始终未变。Name值仍为Blue。

IOptionsSnapshot方式:Name值变为Red1,Guid值单次请求内相同,每次刷新之间不同。

IOptionsMonitor方式:只有修改配置值后第一次刷新的时候将Name值变为了Gray1,Guid未改变。之后多次刷新,这两个值均未做改变。

总结:IOptions和IOptionsMonitor两种方式采用了Singleton模式,但区别在于IOptionsMonitor会监控对应数据源的变化,如果发生了变化则更新实例的配置值,但不会重新提供新的实例。IOptionsSnapshot方式采用了Scoped模式每次请求采用同一个实例,在下一次请求的时候获取到的是一个新的实例,所以如果数据源发生了改变,会读取到新的值。先大概记一下这一的情况,在下文剖析IOptions的内部处理机制的时候就会明白为什么会这样。

4.数据更新提醒

IOptionsMonitor方式还提供了一个OnChange方法,当数据源发生改变的时候会触发它,所以如果想在这时候做点什么,可以利用这个方法实现。示例代码:


_optionsMonitor.OnChange((theme,name)=> { Console.WriteLine(theme.Name +"-"+ name); });  

5.不采用Configuration配置作为数据源的方式

上面的例子都是采用了读取配置的方式,实际上Options模式和上一章的Configuration配置方式使分开的,读取配置只不过是Options模式的一种实现方式,例如可以不使用Configuration中的数据,直接通过如下代码注册:


services.Configure<Theme>("ThemeBlack", theme => {
    theme.Color = "#000000";
    theme.Name = "Black";
});   

6.ConfigureAll方法

系统提供了一个ConfigureAll方法,可以将所有对应的实例统一设置。例如如下代码:


services.ConfigureAll<Theme>(theme => {
     theme.Color = "#000000";
     theme.Name = "Black2";
});  

此时无论通过什么名称去获取Theme的实例,包括不存在对应设置的名称,获取到的值都是本次通过ConfigureAll设置的值。

7.PostConfigure和PostConfigureAll方法

这两个方法和Configure、ConfigureAll方法类似,只是它们会在Configure、ConfigureAll之后执行。

8.多个Configure、ConfigureAll、PostConfigure和PostConfigureAll的执行顺序

可以这样理解,每个Configure都是去修改一个名为其设置的名称的变量,以如下代码为例:


services.Configure<Theme>("ThemeBlack", theme => {
    theme.Color = "#000000";
    theme.Name = "Black";
});   

这条设置就是去修改(注意是修改而不是替换)一个名为"ThemeBlack"的Theme类型的变量,如果该变量不存在,则创建一个Theme实例并赋值。这样就生成了一些变量名为“空字符串、“ThemeBlue”、“ThemeBlack”的变量(只是举例,忽略空字符串作为变量名不合法的顾虑)”。

依次按照代码的顺序执行,这时候如果后面的代码中出现同名的Configure,则修改对应名称的变量的值。如果是ConfigureAll方法,则修改所有类型为Theme的变量的值。

而PostConfigure和PostConfigureAll则在Configure和ConfigureAll之后执行,即使Configure的代码写在了PostConfigure之后也是一样。

至于为什么会是这样的规则,下一节会详细介绍。

二、内部处理机制解析

1. 系统启动阶段,依赖注入

上一节的例子中涉及到了三个接口IOptions、IOptionsSnapshot和IOptionsMonitor,那么就从这三个接口说起。既然Options模式是通过这三个接口的泛型方式注入提供服务的,那么在这之前系统就需要将它们对应的实现注入到依赖注入容器中。这发生在系统启动阶段创建IHost的时候,这时候HostBuilder的Build方法中调用了一个services.AddOptions()方法,这个方法定义在OptionsServiceCollectionExtensions中,代码如下:


public static class OptionsServiceCollectionExtensions
    {
        public static IServiceCollection AddOptions(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
            services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
            services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
            services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
            return services;
        }

        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
            => services.Configure(Options.Options.DefaultName, configureOptions);

        public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions)
            where TOptions : class
        {
            //省略非空验证代码

            services.AddOptions();
            services.AddSingleton<IConfigureOptions<TOptions>>(new ConfigureNamedOptions<TOptions>(name, configureOptions));
            return services;
        }

        public static IServiceCollection ConfigureAll<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
            => services.Configure(name: null, configureOptions: configureOptions);
//省略部分代码
    }  

可见这个AddOptions方法的作用就是进行服务注入,IOptions<>、IOptionsSnapshot<>对应的实现是OptionsManager<>,只是分别采用了Singleton和Scoped两种生命周期模式,IOptionsMonitor<>对应的实现是OptionsMonitor<>,同样为Singleton模式,这也验证了上一节例子中的猜想。除了上面提到的三个接口外,还有IOptionsFactory<>和IOptionsMonitorCache<>两个接口,这也是Options模式中非常重要的两个组成部分,接下来的内容中会用到。

另外的两个Configure方法就是上一节例子中用到的将具体的Theme注册到Options中的方法了。二者的区别就是是否为配置的option命名,而第一个Configure方法就未命名的方法,通过上面的代码可知它实际上是传入了一个默认的Options.Options.DefaultName作为名称,这个默认值是一个空字符串,也就是说,未命名的Option相当于是被命名为空字符串。最终都是按照已命名的方式也就是第二个Configure方法进行处理。还有一个ConfigureAll方法,它是传入了一个null作为Option的名称,也是交由第二个Configure处理。

在第二个Configure方法中仍调用了一次AddOptions方法,然后才是将具体的类型进行注入。AddOptions方法中采用的都是TryAdd方法进行注入,已被注入的不会被再次注入。接下来注册了一个IConfigureOptions<TOptions>接口,对应的实现是ConfigureNamedOptions<TOptions>(name, configureOptions),它的代码如下:


public class ConfigureNamedOptions<TOptions> : IConfigureNamedOptions<TOptions> where TOptions : class
{
    public ConfigureNamedOptions(string name, Action<TOptions> action)
    {
        Name = name;
        Action = action;
}

    public string Name { get; }
    public Action<TOptions> Action { get; }

    public virtual void Configure(string name, TOptions options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        // Null name is used to configure all named options.
        if (Name == null || name == Name)
        {
            Action?.Invoke(options);
        }
    }

    public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}  

它在构造方法中存储了配置的名称(Name)和创建方法(Action),它的两个Configure方法用于在获取Options的值的时候执行对应的Action来创建实例(例如示例中的Theme)。在此时不会被执行。所以在此会出现3中类型的ConfigureNamedOptions,分别是Name值为具体值的、Name值为为空字符串的和Name值为null的。这分别对应了第一节的例子中的为Option命名的Configure方法、不为Option命名的Configure方法、以及ConfigureAll方法。

此处用到的OptionsServiceCollectionExtensions和ConfigureNamedOptions对应的是通过代码直接注册Option的方式,例如第一节例子中的如下方式:


services.Configure<Theme>("ThemeBlack", theme => { new Theme { Color = "#000000", Name = "Black" }; });  

如果是以Configuration作为数据源的方式,例如如下代码


services.Configure<Theme>("ThemeBlue", Configuration.GetSection("Themes:0"));  

用到的是OptionsServiceCollectionExtensions和ConfigureNamedOptions这两个类的子类,分别为OptionsConfigurationServiceCollectionExtensions和NamedConfigureFromConfigurationOptions两个类,通过它们的名字也可以知道是专门用于采用Configuration作为数据源用的,代码类似,只是多了一条关于IOptionsChangeTokenSource的依赖注入,作用是将Configuration的关于数据源变化的监听和Options的关联起来,当数据源发生改变的时候可以及时更新Options中的值,主要的Configure方法代码如下:


public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder)
    where TOptions : class
{
    //省略验证代码

    services.AddOptions();
    services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
    return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}  

同样还有PostConfigure和PostConfigureAll方法,和Configure、ConfigureAll方法类似,只不过注入的类型为IPostConfigureOptions<TOptions>。

2. Options值的获取

Option值的获取也就是从依赖注入容器中获取相应实现的过程。通过依赖注入阶段,已经知道了IOptions<>和IOptionsSnapshot<>对应的实现是OptionsManager<>,就以OptionsManager<>为例看一下依赖注入后的服务提供过程。OptionsManager<>代码如下:


public class OptionsManager<TOptions> : IOptions<TOptions>, IOptionsSnapshot<TOptions> where TOptions : class, new()
{
    private readonly IOptionsFactory<TOptions> _factory;
private readonly OptionsCache<TOptions> _cache = new OptionsCache<TOptions>();

    public OptionsManager(IOptionsFactory<TOptions> factory)
    {
        _factory = factory;
    }

    public TOptions Value
    {
        get
        {
            return Get(Options.DefaultName);
        }
    }

    public virtual TOptions Get(string name)
    {
        name = name ?? Options.DefaultName;
        return _cache.GetOrAdd(name, () => _factory.Create(name));
    }
}  

它有IOptionsFactory<TOptions>和OptionsCache<TOptions>两个重要的成员。如果直接获取Value值,实际上是调用的另一个Get(string name)方法,传入了空字符串作为name值。所以最终值的获取还是在缓存中读取,这里的代码是_cache.GetOrAdd(name, () => _factory.Create(name)),即如果缓存中存在对应的值,则返回,如果不存在,则由_factory去创建。OptionsFactory<TOptions>的代码如下:


public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class, new()
{
    private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
    private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;
    private readonly IEnumerable<IValidateOptions<TOptions>> _validations;

    public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) : this(setups, postConfigures, validations: null)
    { }

    public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
    {
        _setups = setups;
        _postConfigures = postConfigures;
        _validations = validations;
}

    public TOptions Create(string name)
    {
        var options = new TOptions();
        foreach (var setup in _setups)
        {
            if (setup is IConfigureNamedOptions<TOptions> namedSetup)
            {
                namedSetup.Configure(name, options);
            }
            else if (name == Options.DefaultName)
            {
                setup.Configure(options);
            }
        }
        foreach (var post in _postConfigures)
        {
            post.PostConfigure(name, options);
        }

        if (_validations != null)
        {
            var failures = new List<string>();
            foreach (var validate in _validations)
            {
                var result = validate.Validate(name, options);
                if (result.Failed)
                {
                    failures.AddRange(result.Failures);
                }
            }
            if (failures.Count > 0)
            {
                throw new OptionsValidationException(name, typeof(TOptions), failures);
            }
        }

        return options;
    }
}  

主要看它的TOptions Create(string name)方法。这里会遍历它的_setups集合,这个集合类型为IEnumerable<IConfigureOptions<TOptions>>,在讲Options模式的依赖注入的时候已经知道,每一个Configure、ConfigureAll实际上就是向依赖注入容器中注册了一个IConfigureOptions<TOptions>,只是名称可能不同。而PostConfigure和PostConfigureAll方法注册的是IPostConfigureOptions<TOptions>类型,对应的就是_postConfigures集合。

首先会遍历_setups集合,调用IConfigureOptions<TOptions>的Configure方法,这个方法的主要代码就是:


 if (Name == null || name == Name)
 {
      Action?.Invoke(options);
 }  

如果Name值为null,即对应的是ConfigureAll方法,则执行该Action。或者Name值和需要读取的值相同,则执行该Action。

_setups集合遍历之后,同样的机制遍历_postConfigures集合。这就是上一节关于Configure、ConfigureAll、PostConfigure和PostConfigureAll的执行顺序的验证。

最终返回对应的实例并写入缓存。这就是IOptions和IOptionsSnapshot两种模式的处理机制,接下来看一下IOptionsMonitor模式,它对应的实现是OptionsMonitor。代码如下:


public class OptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : class, new()
{
    private readonly IOptionsMonitorCache<TOptions> _cache;
    private readonly IOptionsFactory<TOptions> _factory;
    private readonly IEnumerable<IOptionsChangeTokenSource<TOptions>> _sources;
    internal event Action<TOptions, string> _onChange;

    public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
    {
        _factory = factory;
        _sources = sources;
        _cache = cache;

        foreach (var source in _sources)
        {
                var registration = ChangeToken.OnChange(
                      () => source.GetChangeToken(),
                      (name) => InvokeChanged(name),
                      source.Name);

                _registrations.Add(registration);        
}
    }

    private void InvokeChanged(string name)
    {
        name = name ?? Options.DefaultName;
        _cache.TryRemove(name);
        var options = Get(name);
        if (_onChange != null)
        {
            _onChange.Invoke(options, name);
        }
    }

    public TOptions CurrentValue
    {
        get => Get(Options.DefaultName);
    }

    public virtual TOptions Get(string name)
    {
        name = name ?? Options.DefaultName;
        return _cache.GetOrAdd(name, () => _factory.Create(name));
    }

    public IDisposable OnChange(Action<TOptions, string> listener)
    {
        var disposable = new ChangeTrackerDisposable(this, listener);
        _onChange += disposable.OnChange;
        return disposable;
    }

    internal class ChangeTrackerDisposable : IDisposable
    {
        private readonly Action<TOptions, string> _listener;
        private readonly OptionsMonitor<TOptions> _monitor;

        public ChangeTrackerDisposable(OptionsMonitor<TOptions> monitor, Action<TOptions, string> listener)
        {
            _listener = listener;
            _monitor = monitor;
        }

        public void OnChange(TOptions options, string name) => _listener.Invoke(options, name);

        public void Dispose() => _monitor._onChange -= OnChange;
    }
}  

大部分功能和OptionsManager类似,只是由于它是采用了Singleton模式,所以它是采用监听数据源改变并更新的模式。当通过Configuration作为数据源注册Option的时候,多了一条IOptionsChangeTokenSource的依赖注入。当数据源发生改变的时候更新数据并触发OnChange(Action<TOptions, string> listener),在第一节的数据更新提醒中有相关的例子。

 

转载于:https://www.cnblogs.com/FlyLolo/p/ASPNETCore_24.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ASP.NET Core 3.0 : 二十四. 配置的Options模式 的相关文章

  • 每个会话的 Nhibernate 会话示例

    您好 nhibernate 的一些专业人士能给我每个会话的 Nhibernate 会话实现示例还是 unhaddins 实现吗 或者解释如何做到这一点 此致 恩迪斯 我使用 NHibernate 工作了 4 年 此前我曾与 每次操作打开会话
  • C#:如何在虚拟模式下有效过滤(隐藏)ListView 项目?

    C 如何在虚拟模式下有效过滤 隐藏 ListView 项目 我正在寻找一种在虚拟模式下过滤 隐藏 显示 ListView 中的项目的方法 我将我的项目缓存在列表视图项目数组中 如何有效地使其在按下过滤按钮时仅显示特定的列表视图项目 然后在按
  • PostSharp AssemblyLoadException Autofac

    我正在设置一个新的解决方案 我想在其中使用最新的 Autofac 3 4 和 PostSharp 3 1 42 引用 NuGet 包后 出现以下错误 并且我无法弄清楚发生了什么 我从未选择 Autofac 3 3 0 包 包 config
  • 为什么 CLR 为匿名方法创建新类?

    我在我的项目中也使用了匿名函数 直到知道我在想 C 编译器仅使用用于匿名方法的代码生成一个方法在同一个班 但是 在 IL 中反编译这段代码后 我看到 CLR 创建了一个新类 public class Comparer public dele
  • LINQ to SQL 选择“按多列区分”并返回整个实体

    我正在使用第三方数据库 需要为我正在研究的特定市场选择一组不同的数据 每个市场的数据都是相同的 因此将其全部引入是多余的 并且我不想硬编码围绕它的任何逻辑 因为我们正在与供应商合作解决问题 但我们需要一个修复程序与供应商合作修复以及数据库当
  • 关于 MEF 战略和结构的问题

    我的任务是模块化一个 C 应用程序 该应用程序是一个非常大的 Delphi 应用程序的重写 数据库有 249 个表 业务限制禁止对 NET 进行彻底的重新设计和更好的整体架构 因此我们基本上只是用 C 逐步重写 Delphi 应用程序的模块
  • 如何像用户打字一样将输入发送到控制台?

    这是我的问题 我有一个程序必须在 TTY 中运行 cygwin 提供了这个 TTY 当我重定向 stdIn 时 程序失败 因为它没有 TTY 我无法修改该程序 并且需要某种方法使其自动化 如何抓取 cmd exe 窗口并向其发送数据并使其认
  • 虚拟键盘(类似 Swype 键盘)Windows 窗体应用程序 C#

    我正在尝试使用 c 在 Windows 窗体中创建一个类似 swype 的键盘 我有两个问题 A 我无法重现手指滑动动作 b 我无法识别不同按键下的字母 对于第一个问题 我使用了 Graphics 类和 Pen 类 并像这样使用它 bool
  • C# 有没有办法制作时间范围列表?可配置

    有没有办法列出时间范围 例如 包含以下内容的列表 中午 12 00 至下午 1 00 下午 1 00 至 2 00 ETC 其中划分部分是配置 我认为你必须使用日期时间并将其除以一定的数字 在本例中为一小时 有人可以指出我正确的方向或给我提
  • 在 Windows 窗体应用程序中捕获 MonthCalendar 控件的双击

    如何捕获 System Windows Forms MonthCalendar 控件的双击事件 我尝试过使用 MouseDown 的 MouseEventArgs Clicks 属性 但它始终为 1 即使我双击也是如此 请注意 MonthC
  • 如何获取 WPF 用户控件可见部分的大小?

    我有一个由标签和文本框组成的用户控件 它位于滚动查看器内 我正在其顶部绘制一个装饰器 并且需要将装饰器的大小调整为控件的可见大小 如何获得控件的可见大小 在下图中 绿色矩形是装饰器 正如您所看到的 它被绘制在右侧的滚动条上 是否可以获得渲染
  • Web 客户端异常:底层连接已关闭:无法建立 SSL/TLS 安全通道的信任关系

    我有一个简单的应用程序 它使用 C Web 客户端类来下载网站 HTML 这是我正在使用的代码的精简示例 WebClient wc new WebClient wc Headers Add user agent Mozilla 4 0 co
  • 验证使用 BER/DER 编码的 ASN.1 格式的 C# 中的 DSA 签名

    如何在 C 中验证 DSA 签名 Given 消息文本 签名摘要 通常为 ASN 1 DER 格式 公钥 采用签名的 X 509 证书 PEM 或 DER 格式 我尝试了多种方法 但都没有成功 OpenSSL NET http openss
  • 在主窗体上使用 BeginInvoke 调用的网络任务未执行

    我使用 Visual Studio 2013 构建了一个具有单个表单的 C 应用程序 并且该应用程序有两个更新屏幕的例程 更新屏幕的例程需要在主线程上运行 因此我自己的线程 不与屏幕交互 在需要更新时调用主窗体上的 BeginInvoke
  • 使用 IIS Express 托管网站(临时)

    我有一个网站 MVC3 其开发托管在 IIS Express 中 我遇到了 Cassini Devserver 的错误 不得不升级 现在 我想知道是否可以让本地网络上的其他计算机 在路由器后面 看到托管在我的计算机上的站点 例如 如果我写h
  • 消息在事务处理时未到达 MSMQ

    我在本地计算机中创建了一个私有 MSMQ 我使用以下 C 代码将消息发送到队列 当我将队列更改为事务性队列时 消息未到达 MSMQ 但是 Send 方法中没有抛出异常 我需要做出什么改变才能使其发挥作用 using System using
  • BackgroundWorker 如何决定在哪个线程上运行 RunWorkerCompleted 处理程序?

    我试图弄清楚 BGW 在工作完成后如何决定运行 RunWorkerCompleted 处理程序的线程 我的初始测试使用 WinForm 应用程序 在 UI 线程上 我开始bgw1 RunWorkerAsync 然后我尝试开始bgw2 Run
  • 在库中使用 .Net Standard 1.4 并在应用程序中使用 .Net Framework 4.6.1 时,无法加载文件 System.IO.FileSystem,版本=4.0.1.0

    我有一个包含一个库和两个应用程序的解决方案 这些应用程序代表同一个程序 其中一个针对通过 UAP10 的 Windows App Store 构建 另一个针对使用 Net Framework 4 6 1 的 Microsoft Window
  • docs.microsoft.com 上的 .NET 平台扩展是什么?

    Microsoft Docs 中有一个框架级导航元素 称为 NET 平台扩展 https learn microsoft com en us dotnet api index view dotnet plat ext 2 1 它包含有关最近
  • 通过网络共享的 SQL CE

    我之前见过这个问题 但找不到关于什么是可能 不可能以及什么解决方法可能可用的明确解释 我有一个现有的 C 应用程序 它使用 SQL CE 来存储本地信息 该数据库只能由单个应用程序访问 并存储在用户的 appdata 文件夹中 某些环境将

随机推荐

  • go 只使用函数返回的一个值_详解二:Go 语言机制之逃逸分析

    前序 xff08 Prelude xff09 本系列文章总共四篇 xff0c 主要帮助大家理解 Go 语言中一些语法结构和其背后的设计原则 xff0c 包括指针 栈 堆 逃逸分析和值 指针传递 这是第二篇 xff0c 主要介绍堆和逃逸分析
  • Python+Selenium自动化-清空输入框、输入内容、点击按钮

    Python 43 Selenium自动化 清空输入框 输入内容 点击按钮 1 输入内容 send keys 39 valve 39 xff1a 输入内容valve span class hljs comment 定位输入框 span in
  • requests.get()参数

    查询参数 params 1 参数类型 字典 字典中键值对作为查询参数 2 使用方法 1 res 61 requests get url params 61 params headers 61 headers 2 特点 url为基准的url地
  • 实例方法、类方法和静态方法区别

    class A 实例方法 def f1 self return 1 类方法 64 classmethod def f2 cls return 2 静态方法 64 staticmethod def f3 return 3 a 61 A a f
  • Python私有函数和专有方法

    在任何语言中 xff0c 都会规定某些对象 属性 方法 函数 类等 只能够在某个范围内访问 xff0c 出了这个范围就不能访问了 这是 公 私 之分 此外 xff0c 还会专门为某些特殊的东西指定一些特殊表示 xff0c 比如类的名字就不能
  • 1-mac上安装vscode并配置C++环境

    1软件与扩展 在vscode官网下载安装包 xff0c 并完成安装在vscode软件内 xff0c 下载并安装C C 43 43 扩展 xff0c 如下图所示 2安装编译器 执行下面的代码 xff0c 如果报错 下图第三行 xff0c 说明
  • 深度学习数学基础

    机器学习简介 xff1a 特征向量 目标函数 机器学习分类 xff1a 有监督学习 xff1a 分类问题 xff08 如人脸识别 字符识别 语音识别 xff09 回归问题 无监督学习 xff1a 聚类问题 数据降维 强化学习 xff1a 根
  • zabbix监控Linux服务器丢包率

    http www ttlsa com zabbix zabbix simple checks 这个文章看了 xff0c 还没有实践 1 先创建监控项 xff0c 键值如下 icmppingloss lt 121 131 24 39 gt l
  • 重装@angular/cli reason: write EPROTO 139955972261696:error:1408F10B:SSL routines:ssl3_get_record:wron...

    前几天不小心卸载了 angular 64 cli 然后重装的时候发现 xff0c 一直报错 如下 xff1a 64 ln622653 npm install g 64 angular cli npm ERR code EPROTO npm
  • Outlook2016删不掉主账户的解决方法

    控制面板 gt 账户 gt 邮件把配置文件删了 前两项和Outlook内部打开账户选项一样没用 进第三个 重启Oulook的时候会提示重新建一个配置 就OK了 转载于 https www cnblogs com haimingpro p 6
  • 新版新概念英语1-4册(英音+美音)MP3打包下载

    新版新概念英语第一册MP3 美音 新版新概念英语第一册MP3 英音 新版新概念英语第一册PDF 课文 新版新概念英语第二册MP3 美音 新版新概念英语第二册MP3 英音 新版新概念英语第二册PDF 课文 新版新概念英语第三册MP3 美音 新
  • linux远程windows无法输入,XRDP在Windows下用远程桌面连接,键盘失效有关问题

    XRDP在Windows下用远程桌面连接 xff0c 键盘失效问题 很久没上这个博客了 xff0c 最近在做虚拟化方面的东西 xff0c 有个需求是通过windows远程连接Linux桌面 xff0c 采用的是xrdp 安装和使用xrdp都
  • 3367 【模板】并查集

    题目描述 如题 xff0c 现在有一个并查集 xff0c 你需要完成合并和查询操作 输入输出格式 输入格式 xff1a 第一行包含两个整数N M xff0c 表示共有N个元素和M个操作 接下来M行 xff0c 每行包含三个整数Zi Xi Y
  • MySQL优化之my.conf配置详解

    最近项目不太忙 xff0c 所以有时间静心来研究下mysql的优化 xff0c 对于MySQL的设置是否合理优化 xff0c 直接影响到网站的速度和承载量 xff01 同时 xff0c MySQL也是优化难度最大的一个部分 xff0c 不但
  • NPM全局安装软件包时解决EACCES权限错误

    NPM全局安装软件包时解决EACCES权限错误 Resolving EACCES permissions errors when installing packages globally npm WARN checkPermissions
  • 阿里云学生服务器认证条件详解与选择教程

    简介 xff1a 本文汇总学生购买阿里云服务器以及其它云产品优惠 xff0c 阿里云的云翼计划 xff0c 是阿里云针对在校学生扶持的一项优惠活动 xff0c 只需9元即可购买阿里云服务器 xff0c 而且云服务器配置不低 xff0c 足够
  • deepin系统

    https www uc23 net xinwen 76259 html 据介绍 xff0c 深度操作系统 xff08 deepin xff09 自 2015 年开始 xff0c 就放弃基于 Ubuntu 作为上游 xff0c 选择 Ubu
  • Linux 大文件日志快速定位错误或者异常的位置

    1 得到错误日志或者异常日志的行号 cat n test log grep 34 error 34 cat n test log grep 34 exception 34 2 通过位置往前往后查看日志详细 339563 can not cl
  • 《oracle正则表达式》摘抄+自理

    select from t test regexp A B 1 AAA 2 bbb 3 4 xff01 xff01 xff01 5 吴雁渡 6 12345 7 123AAbb存储 64 xff01 64 445BBC 1 REGEXP LI
  • ASP.NET Core 3.0 : 二十四. 配置的Options模式

    上一章讲到了配置的用法及内部处理机制 xff0c 对于配置 xff0c ASP NET Core还提供了一种Options模式 ASP NET Core 系列目录 一 Options的使用 上一章有个配置的绑定的例子 xff0c 可以将配置