ASP.NET Core 2.2 : 二十三. 深入聊一聊配置的內部處理機制

来源:https://www.cnblogs.com/FlyLolo/archive/2019/09/23/ASPNETCore_23.html
-Advertisement-
Play Games

上一章介紹了配置的多種數據源被註冊、載入和獲取的過程,本節看一下這個過程系統是如何實現的。(ASP.NET Core 系列目錄) 一、數據源的註冊 在上一節介紹的數據源設置中,appsettings.json、命令行、環境變數三種方式是被系統自動載入的,這是因為系統在webHost.CreateDe ...


上一章介紹了配置的多種數據源被註冊、載入和獲取的過程,本節看一下這個過程系統是如何實現的。(ASP.NET Core 系列目錄)

一、數據源的註冊

在上一節介紹的數據源設置中,appsettings.json、命令行、環境變數三種方式是被系統自動載入的,這是因為系統在webHost.CreateDefaultBuilder(args)中已經為這三種數據源進了註冊,那麼就從這個方法說起。這個方法中同樣調用了ConfigureAppConfiguration方法,代碼如下:

public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
    var builder = newWebHostBuilder();
    //省略部分代碼
    builder.UseKestrel((builderContext, options) =>
        {
            options.Configure(builderContext.Configuration.GetSection("Kestrel"));
        })
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            var env = hostingContext.HostingEnvironment;
            config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional:true, reloadOnChange: true);
            if(env.IsDevelopment())
            {
                var appAssembly = Assembly.Load(newAssemblyName(env.ApplicationName));
                if(appAssembly != null)
                {
                    config.AddUserSecrets(appAssembly, optional: true);
                }
            }

            config.AddEnvironmentVariables();
            if(args != null)
            {
                config.AddCommandLine(args);
            }
       })

       //省略部分代碼

    return builder;
}

 

看一下其中的ConfigureAppConfiguration方法,載入的內容主要有四種,首先載入的是appsettings.json和appsettings.{env.EnvironmentName}.json兩個JSON文件,關於env.EnvironmentName在前面的章節已經說過,常見的有Development、Staging 和 Production三種值,在我們開發調試時一般是Development,也就是會載入appsettings.json和appsettings. Development.json兩個JSON文件。第二種載入的是用戶機密文件,這僅限於Development狀態下,會通過config.AddUserSecrets方法載入。第三種是通過config.AddEnvironmentVariables方法載入的環境變數,第四種是通過config.AddCommandLine方法載入的命令行參數。

註意:這裡的ConfigureAppConfiguration方法這時候是不會被執行的,只是將這個方法作為一個Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate添加到了WebHostBuilder的_configureServicesDelegates屬性中。configureServicesDelegates是一個List<Action<WebHostBuilderContext, IConfigurationBuilder>>類型的集合。對應代碼如下:

public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
    if(configureDelegate == null)
    {
        throw new ArgumentNullException(nameof(configureDelegate));
    }

    _configureAppConfigurationBuilderDelegates.Add(configureDelegate);
    returnthis;
}

 

上一節的例子中,我們在webHost.CreateDefaultBuilder(args)方法之後再次調用ConfigureAppConfiguration方法添加了一些自定義的數據源,這個方法也是沒有執行,同樣被添加到了這個集合中。直到WebHostBuilder通過它的Build()方法創建WebHost的時候,才會遍歷這個集合逐一執行。這段代碼寫在被Build()方法調用的BuildCommonServices()中:

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    //省略部分代碼
    var builder = new ConfigurationBuilder()
        .SetBasePath(_hostingEnvironment.ContentRootPath)
        .AddConfiguration(_config);

    foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
    {
        configureAppConfiguration(_context, builder);
    }

    var configuration = builder.Build();
    services.AddSingleton<IConfiguration>(configuration);
    _context.Configuration = configuration;
//省略部分代碼
    return services;
}

 

首先創建了一個ConfigurationBuilder對象,然後通過foreach迴圈逐一執行被添加到集合_configureAppConfigurationBuilderDelegates中的configureAppConfiguration方法,那麼在執行的時候,這些不同的數據源是如何被載入的呢?這部分功能在namespace Microsoft.Extensions.Configuration命名空間中。

以appsettings.json對應的config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)方法為例,進一步看一下它的實現方式。首先介紹的是IConfigurationBuilder介面,對應的實現類是ConfigurationBuilder,代碼如下:

public class ConfigurationBuilder : IConfigurationBuilder
    {
        public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

        public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();

        public IConfigurationBuilder Add(IConfigurationSource source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            Sources.Add(source);
            return this;
        }
        //省略了IConfigurationRoot Build()方法,下文介紹
    }

 

ConfigureAppConfiguration方法中調用的AddJsonFile方法來自JsonConfigurationExtensions類,代碼如下:

public static class JsonConfigurationExtensions
{
//省略部分代碼

    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
        if (string.IsNullOrEmpty(path))
        {
            throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path));
        }

        return builder.AddJsonFile(s =>
        {
            s.FileProvider = provider;
            s.Path = path;
            s.Optional = optional;
            s.ReloadOnChange = reloadOnChange;
            s.ResolveFileProvider();
        });
    }
    public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource)
        => builder.Add(configureSource);
}

 

AddJsonFile方法會創建一個JsonConfigurationSource並通過ConfigurationBuilder的Add(IConfigurationSource source)方法將這個JsonConfigurationSource添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中去。

同理,針對環境變數,存在對應的EnvironmentVariablesExtensions,會創建一個對應的EnvironmentVariablesConfigurationSource添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中去。這樣的還有CommandLineConfigurationExtensions和CommandLineConfigurationSource等,最終結果就是會根據數據源的載入順序,生成多個XXXConfigurationSource對象(它們都直接或間接實現了IConfigurationSource介面)添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中。

在Program文件的WebHost.CreateDefaultBuilder(args)方法中的ConfigureAppConfiguration方法被調用後,如果在CreateDefaultBuilder方法之後再次調用了ConfigureAppConfiguration方法並添加了數據源(如同上一節的例子),同樣會生成相應的XXXConfigurationSource對象添加到ConfigurationBuilder的IList<IConfigurationSource> Sources集和中。

註意:這裡不是每一種數據源生成一個XXXConfigurationSource,而是按照每次添加生成一個XXXConfigurationSource,並且遵循添加的先後順序。例如添加多個JSON文件,會生成多個JsonConfigurationSource。

這些ConfigurationSource之間的關係如下圖1:

 

圖1

到這裡各種數據源的收集工作完成,都添加到了ConfigurationBuilder的IList<IConfigurationSource> Sources屬性中。

回到BuildCommonServices方法中,通過foreach迴圈逐一執行了configureAppConfiguration方法獲取到IList<IConfigurationSource>之後,下一句是varconfiguration = builder.Build(),這是調用ConfigurationBuilder的Build()方法創建了一個IConfigurationRoot對象。對應代碼如下:

public class ConfigurationBuilder : IConfigurationBuilder
    {
        public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

        //省略部分代碼

        public IConfigurationRoot Build()
        {
            var providers = new List<IConfigurationProvider>();
            foreach (var source in Sources)
            {
                var provider = source.Build(this);
                providers.Add(provider);
            }
            return new ConfigurationRoot(providers);
        }

    }

 

這個方法主要體現了兩個過程:首先,遍歷IList<IConfigurationSource> Sources集合,主要調用其中的各個IConfigurationSource的Build方法創建對應的IConfigurationProvider,最終生成一個List<IConfigurationProvider>;第二,通過集合List<IConfigurationProvider>創建了ConfigurationRoot。ConfigurationRoot實現了IConfigurationRoot介面。

先看第一個過程,依然以JsonConfigurationSource為例,代碼如下:

    public class JsonConfigurationSource : FileConfigurationSource
    {
        public override IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            EnsureDefaults(builder);
            return new JsonConfigurationProvider(this);
        }
    }

 

JsonConfigurationSource會通過Build方法創建一個名為JsonConfigurationProvider的對象。通過JsonConfigurationProvider的名字可知,它是針對JSON類型的,也就是意味著不同類型的IConfigurationSource創建的IConfigurationProvider類型也是不一樣的,對應圖18‑4中的IConfigurationSource,生成的IConfigurationProvider關係如下圖2。

 

圖2

系統中添加的多個數據源被轉換成了一個個對應的ConfigurationProvider,這些ConfigurationProvider組成了一個ConfigurationProvider的集合。

再看一下第二個過程,ConfigurationBuilder的Build方法的最後一句是return new ConfigurationRoot(providers),就是通過第一個過程創建的ConfigurationProvider的集合創建ConfigurationRoot。ConfigurationRoot代碼如下:

public class ConfigurationRoot : IConfigurationRoot
    {
        private IList<IConfigurationProvider> _providers;
        private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();

        public ConfigurationRoot(IList<IConfigurationProvider> providers)
        {
            if (providers == null)
            {
                throw new ArgumentNullException(nameof(providers));
            }

            _providers = providers;
            foreach (var p in providers)
            {
                p.Load();
                ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged());
            }
        }
//省略部分代碼
}

 

可以看出,ConfigurationRoot的構造方法主要的作用就是將ConfigurationProvider的集合作為自己的一個屬性的值,並遍歷這個集合,逐一調用這些ConfigurationProvider的Load方法,併為ChangeToken的OnChange方法綁定數據源的改變通知和處理方法。

二、數據源的載入

從圖18‑5可知,所有類型數據源最終創建的XXXConfigurationProvider都繼承自ConfigurationProvider,所以它們都有一個Load方法和一個IDictionary<string, string> 類型的Data 屬性,它們是整個配置系統的重要核心。Load方法用於數據源的數據的讀取與處理,而Data用於保存最終結果。通過逐一調用Provider的Load方法完成了整個配置系統的數據載入。

以JsonConfigurationProvider為例,它繼承自FileConfigurationProvider,所以先看一下FileConfigurationProvider的代碼:

public abstract class FileConfigurationProvider : ConfigurationProvider
{
//省略部分代碼
    private void Load(bool reload)
    {
        var file = Source.FileProvider?.GetFileInfo(Source.Path);
        if (file == null || !file.Exists)
        {
        //省略部分代碼
        }
        else
        {
            if (reload)
            {
                Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            }
            using (var stream = file.CreateReadStream())
            {
                try
                {
                    Load(stream);
                }
                catch (Exception e)
                {
//省略部分代碼
                }
            }
        }
        OnReload();
    }
    public override void Load()
    {
        Load(reload: false);
}
    public abstract void Load(Stream stream);
} 

本段代碼的主要功能就是讀取文件生成stream,然後調用Load(stream)方法解析文件內容。從圖18‑5可知,JsonConfigurationProvider、IniConfigurationProvider、XmlConfigurationProvider都是繼承自FileConfigurationProvider,而對應JSON、INI、XML三種數據源來說,只是文件內容的格式不同,所以將通用的讀取文件內容的功能交給了FileConfigurationProvider來完成,而這三個子類的ConfigurationProvider只需要將FileConfigurationProvider讀取到的文件內容的解析即可。所以這個參數為stream 的Load方法寫在JsonConfigurationProvider、IniConfigurationProvider、XmlConfigurationProvider這樣的子類中,用於專門處理自身對應的格式的文件。

JsonConfigurationProvider代碼如下:

public class JsonConfigurationProvider : FileConfigurationProvider
{
    public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }

    public override void Load(Stream stream)
    {
        try
        {
            Data = JsonConfigurationFileParser.Parse(stream);
        }
        catch (JsonReaderException e)
        {
            string errorLine = string.Empty;
            if (stream.CanSeek)
            {
                stream.Seek(0, SeekOrigin.Begin);

                IEnumerable<string> fileContent;
                using (var streamReader = new StreamReader(stream))
                {
                    fileContent = ReadLines(streamReader);
                    errorLine = RetrieveErrorContext(e, fileContent);
                }
            }

            throw new FormatException(Resources.FormatError_JSONParseError(e.LineNumber, errorLine), e);
        }
    }
   //省略部分代碼
}

 

JsonConfigurationProvider中關於JSON文件的解析由JsonConfigurationFileParser.Parse(stream)完成的。最終的解析結果被賦值給了父類ConfigurationProvider的名為Data的屬性中。

所以最終每個數據源的內容都分別被解析成了IDictionary<string, string>集合,這個集合作為對應的ConfigurationProvider的一個屬性。而眾多ConfigurationProvider組成的集合又作為ConfigurationRoot的屬性。最終它們的關係圖如下圖3:

 

圖3

到此,配置的載入與數據的轉換工作完成。下圖4展示了這個過程。

 

 

圖4

 

三、配置的讀取

第一節的例子中,通過_configuration["Theme:Color"]的方式獲取到了對應的配置值,這是如何實現的呢?現在我們已經瞭解了數據源的載入過程,而這個_configuration就是數據源被載入後的最終產出物,即ConfigurationRoot,見圖18‑7。它的代碼如下:

public class ConfigurationRoot : IConfigurationRoot
{
    private IList<IConfigurationProvider> _providers;
    private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();

    //省略了上文已講過的構造方法

    public IEnumerable<IConfigurationProvider> Providers => _providers;
    public string this[string key]
    {
        get
        {
            foreach (var provider in _providers.Reverse())
            {
                string value;

                if (provider.TryGet(key, out value))
                {
                    return value;
                }
            }

            return null;
        }

        set
        {
            if (!_providers.Any())
            {
                throw new InvalidOperationException(Resources.Error_NoSources);
            }

            foreach (var provider in _providers)
            {
                provider.Set(key, value);
            }
        }
    }

    public IEnumerable<IConfigurationSection> GetChildren() => GetChildrenImplementation(null);

    internal IEnumerable<IConfigurationSection> GetChildrenImplementation(string path)
    {
        return _providers
            .Aggregate(Enumerable.Empty<string>(),
                (seed, source) => source.GetChildKeys(seed, path))
            .Distinct()
            .Select(key => GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
    }

    public IChangeToken GetReloadToken() => _changeToken;

    public IConfigurationSection GetSection(string key) 
        => new ConfigurationSection(this, key);

    public void Reload()
    {
        foreach (var provider in _providers)
        {
            provider.Load();
        }
        RaiseChanged();
    }

    private void RaiseChanged()
    {
        var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
        previousToken.OnReload();
    }
}

 

對應_configuration["Theme:Color"]的讀取方式的是索引器“string this[string key]”,通過查看其get方法可知,它是通過倒序遍歷所有ConfigurationProvider,在ConfigurationProvider的Data中嘗試查找是否存在Key為"Theme:Color"的值。這也說明瞭第一節的例子中,在Theme.json中設置了Theme對象的值後,原本在appsettings.json設置的Theme的值被覆蓋的原因。從圖18‑6中可以看到,該值其實也是被讀取並載入的,只是由於ConfigurationRoot的“倒序”遍歷ConfigurationProvider的方式導致後註冊的Theme.json中的Theme值先被查找到了。同時驗證了所有配置值均認為是string類型的約定。

ConfigurationRoot還有一個GetSection方法,會返回一個IConfigurationSection對象,對應的是ConfigurationSection類。它的代碼如下:

public class ConfigurationSection : IConfigurationSection
    {
        private readonly ConfigurationRoot _root;
        private readonly string _path;
        private string _key;

        public ConfigurationSection(ConfigurationRoot root, string path)
        {
            if (root == null)
            {
                throw new ArgumentNullException(nameof(root));
            }

            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            _root = root;
            _path = path;
        }

        public string Path => _path;
        public string Key
        {
            get
            {
                if (_key == null)
                {
                    // Key is calculated lazily as last portion of Path
                    _key = ConfigurationPath.GetSectionKey(_path);
                }
                return _key;
            }
        }
        public string Value
        {
            get
            {
                return _root[Path];
            }
            set
            {
                _root[Path] = value;
            }
        }
        public string this[string key]
        {
            get
            {
                return _root[ConfigurationPath.Combine(Path, key)];
            }

            set
            {
                _root[ConfigurationPath.Combine(Path, key)] = value;
            }
        }

        public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));

        public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);

        public IChangeToken GetReloadToken() => _root.GetReloadToken();
}

 

它的代碼很簡單,可以說沒有什麼實質的代碼,它只是保存了當前路徑和對ConfigurationRoot的引用。它的方法大多是通過調用ConfigurationRoot的對應方法完成的,通過它自身的路徑計算在ConfigurationRoot中對應的Key,從而獲取對應的值。而ConfigurationRoot對配置值的讀取功能以及數據源的重新載入功能(Reload方法)也是通過ConfigurationProvider實現的,實際數據也是保存在ConfigurationProvider的Data值中。所以ConfigurationRoot和ConfigurationSection就像一個外殼,自身並不負責數據源的載入(或重載)與存儲,只負責構建了一個配置值的讀取功能。

而由於配置值的讀取是按照數據源載入順序的倒序進行的,所以對於Key值相同的多個配置,只會讀取後載入的數據源中的配置,那麼ConfigurationRoot和ConfigurationSection就模擬出了一個樹狀結構,如下圖5:

 

圖5

本圖是以如下配置為例:

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

 

ConfigurationRoot利用它制定的讀取規則,將這樣的配置模擬成瞭如圖18‑8這樣的樹,它有這樣的特性:

A.所有節點都認為是一個ConfigurationSection,不同的是對於“Theme”這樣的節點的值為空(圖中用空心橢圓表示),而“Name”和“Color”這樣的節點有對應的值(圖中用實心橢圓表示)。

B.由於對Key值相同的多個配置只會讀取後載入的數據源中的配置,所以不會出現相同路徑的同名節點。例如第一節例子中多種數據源配置了“Theme”值,在這裡只會體現最後載入的配置項。

四、配置的更新

由於ConfigurationRoot未實際保存數據源中載入的配置值,所以配置的更新實際還是由對應的ConfigurationProvider來完成。以JsonConfigurationProvider、IniConfigurationProvider、XmlConfigurationProvider為例,它們的數據源都是具體文件,所以對文件內容的改變的監控也是放在FileConfigurationProvider中。FileConfigurationProvider的構造方法中添加了對設置了對應文件的監控,當然這裡會首先判斷數據源的ReloadOnChange選項是否被設置為True了。

    public abstract class FileConfigurationProvider : ConfigurationProvider
    {
        public FileConfigurationProvider(FileConfigurationSource source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            Source = source;

            if (Source.ReloadOnChange && Source.FileProvider != null)
            {
                changeToken.OnChange(
                    () => Source.FileProvider.Watch(Source.Path),
                    () => {
                        Thread.Sleep(Source.ReloadDelay);
                        Load(reload: true);
                    });
            }
        }
       //省略其他代碼
}

 

所以當數據源發生改變並且ReloadOnChange被設置為True的時候,對應的ConfigurationProvider就會重新載入數據。但ConfigurationProvider更新數據源也不會改變它在ConfigurationRoot的IEnumerable<IConfigurationProvider>列表中的順序。如果在列表中存在A和B兩個ConfigurationProvider並且含有相同的配置項,B排在A後面,那麼對於這些相同的配置項來說,A中的是被B中的“覆蓋”的。即使A的數據更新了,它依然處於“被覆蓋”的位置,應用中讀取相應配置項的依然是讀取B中的配置項。

五、配置的綁定

在第一節的例子中講過了兩種獲取配置值的方式,類似這樣_configuration["Theme:Name"]和_configuration.GetValue<string>("Theme:Color","#000000")可以獲取到Theme的Name和Color的值,那麼就會有下麵這樣的疑問:

appsettings.json中存在如下這樣的配置

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

 

新建一個Theme類如下:

    public class Theme
    {
        public string Name { get; set; }
        public string Color { get; set; }
    }

 

是否可以將配置值獲取並賦值到這樣的一個Theme的實例中呢?

當然可以,系統提供了這樣的功能,可以採用如下代碼實現:

     Theme theme = new Theme();
     _configuration.GetSection("Theme").Bind(theme);

 

綁定功能由ConfigurationBinder實現,邏輯不複雜,讀者如果感興趣的可自行查看其代碼。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 場景 Winform中實現讀取xml配置文件並動態配置ZedGraph的RadioGroup的選項: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100540708 在上面實現了將RadioGroup的選項根據配置文件動態配置後 ...
  • 期待已久的.NET Core 3.0即將發佈! .NET Core 3.0在.NET Conf上發佈。大約還有9個多小時後,.NET Conf開始啟動。 為期3天的大概日程安排如下: 第1天-9月23日 9:00-10:00 Microsoft Studios播出的主題演講10:00-17:00從M ...
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • 單例類public class SnappingClass : ISnappingEnvironment, IExtension { // private static readonly SnappingClass instance = null; static SnappingClass() { ... ...
  • 原文地址:https://blog.csdn.net/FL1623863129/article/details/89013137 VS2019於昨日正式發佈,博主立馬下載一個專業版嘗嘗鮮,但是發現項目打開都沒反應,而且VS2019都死在進程,怎麼也打不開,這不是安裝包而是沒有以管理員運行,只要在屬性 ...
  • 對try catch finally的理解1.finally 總是會運行的,即使在catch中thorw拋出異常了。2.finally 在 return後沒有結束,而是繼續運行finally2.順序的話就是對try-》finally 或者 try-》catch-》finally不管怎樣finally ...
  • @[toc] 前言 中秋過完不知不覺都已經快兩周沒動這個工程了,最近業務需要總算開始搞後臺雲服務了,果斷直接net core搞起,在做的中間遇到了不少問題,這個後續會一點點列出來包括解決方法,今天就先把之前挖的坑填一個。 Redis 之前在緩存那篇提到過, Cookie , Session , Ca ...
  • Controller繼承ControllrBase,ControllerBase繼承IController,而IController里只有一個Execute方法 1、ControllrBase里的Execute(),裡面調用了ExecuteCore(),而ExecuteCore()是個抽象方法,抽象 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...