<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: &amp;ladeak</title>
    <description>The latest articles on DEV Community by &amp;ladeak (@ladeak87).</description>
    <link>https://dev.to/ladeak87</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F721936%2Fc0944c15-5932-4fd7-a770-0c780d161d96.jpg</url>
      <title>DEV Community: &amp;ladeak</title>
      <link>https://dev.to/ladeak87</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ladeak87"/>
    <language>en</language>
    <item>
      <title>Using NDepend to improve my Application Code</title>
      <dc:creator>&amp;ladeak</dc:creator>
      <pubDate>Thu, 18 Jan 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/ladeak87/using-ndepend-to-improve-my-application-1412</link>
      <guid>https://dev.to/ladeak87/using-ndepend-to-improve-my-application-1412</guid>
      <description>&lt;h1&gt;
  
  
  Using NDepend to improve my Application
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.ndepend.com/"&gt;NDepend&lt;/a&gt; is a .NET code quality tool that performs static code analysis. It can help to detect code smells and dependency issues. In this post I will take a quick look at using the &lt;a href="https://www.ndepend.com/docs/project-dependency-diagram"&gt;Dependency Graph&lt;/a&gt; feature to clean up one of my open-source &lt;a href="https://github.com/ladeak/Http3Tools"&gt;applications&lt;/a&gt;. NDepend has a desktop application (as well as Visual Studio extension) that can be used by developers in a tight development cycle to address dependency and code quality issues during development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change code&lt;/li&gt;
&lt;li&gt;build application&lt;/li&gt;
&lt;li&gt;run analysis (or configure automatic analysis in NDepend for every build)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool also comes with a handy console application that can be leveraged for enforce quality rules on CI/CD agents. Later in this post I look at generating reports with the &lt;a href="https://www.ndepend.com/docs/ndepend-console"&gt;NDepend Console&lt;/a&gt; on build agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency Graph
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.ndepend.com/docs/project-dependency-diagram"&gt;Dependency Graph&lt;/a&gt; is a tool that can visualize dependencies of application components and identify common issues, such as cycles. I use this tool to re-organize C# classes and to fix namespace declarations in my project. To get started I set up an NDepend project using the desktop application &lt;strong&gt;VisualNDepend.exe&lt;/strong&gt;. In the project properties select the dll-s to be included in the project analysis. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that the NDepend project file can be submitted to source control, so that a common rule set can be shared across the development team.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I leave the rest of the settings as default, and I use the &lt;em&gt;green play button&lt;/em&gt; to &lt;strong&gt;Run Analysis&lt;/strong&gt;. The initial analysis shows a dashboard with issues, quality gate violations and debt. In a tight development cycle a developer can track the trend of metrics or compare them with a known baseline. NDepend stores a single report for each day that is used for visualizing long term trends.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;em&gt;Dependency Graph&lt;/em&gt; tab to visualize the &lt;strong&gt;application map&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kIeyJ0vO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ey92ke56316g6sbmpw5l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kIeyJ0vO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ey92ke56316g6sbmpw5l.png" alt="initial-state" width="800" height="821"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is instantly visible that my dependencies are less than ideal. The red edges with an arrow on both ends indicate cycles in across application components. One of the edges indicate that &lt;em&gt;CHttp.Abstractions&lt;/em&gt; should not reference the &lt;em&gt;CHttp.Statistics&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r1n0baBg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/numy607pfp708xp405ky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r1n0baBg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/numy607pfp708xp405ky.png" alt="select-problem" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point I could search all classes to explore which causes the dependency cycle. However, there is a quicker way with NDepend. Right click on the &lt;em&gt;edge&lt;/em&gt; and select &lt;strong&gt;Open this dependency on Matrix&lt;/strong&gt;. This opens a &lt;a href="https://www.ndepend.com/docs/dependency-structure-matrix-dsm"&gt;Structure Matrix&lt;/a&gt; with the types in the scope of the target namespaces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--epj8nakh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8xz7xe7vnozpprhfb3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--epj8nakh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8xz7xe7vnozpprhfb3q.png" alt="dependency-matrix" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blue and green squares show the dependencies between classes. It is immediately visible that the green squares indicate the undesired dependencies.&lt;/p&gt;

&lt;p&gt;In the next step I refactor the code and repeat the above cycle. After many iterations I resolve all dependency violations resulting a conflict free dependency graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TONa5Mh9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62kkzzsi8g0xemo41u2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TONa5Mh9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62kkzzsi8g0xemo41u2i.png" alt="clean-dependencies" width="322" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During this process I realized a few ideas that helped to become more productive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instead of jumping on the first dependency violation it is better to first outline the desired application structure. Then iterate the analysis, build, refactoring steps until the wanted architecture is achieved.&lt;/li&gt;
&lt;li&gt;The tool performs a static analysis on the build output of the C# compiler. This means that unused &lt;em&gt;using statements&lt;/em&gt; or trimmed code might not be reflected on the diagram. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up Code Coverage analysis
&lt;/h2&gt;

&lt;p&gt;Next step could be setting up code &lt;a href="https://www.ndepend.com/docs/code-coverage"&gt;coverage analysis&lt;/a&gt;. In the desktop NDepend application a developer can add coverage files in &lt;strong&gt;Project Properties&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Analysis&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Code Coverage&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Settings&lt;/strong&gt; window. NDepend handles &lt;em&gt;opencover&lt;/em&gt; coverage format.  &lt;/p&gt;

&lt;p&gt;To setup Open Cover I add the &lt;em&gt;coverage.collector&lt;/em&gt; nuget &lt;a href="https://www.nuget.org/packages/coverlet.collector"&gt;package&lt;/a&gt;. Then create a runsettings file, that specifies the &lt;em&gt;format&lt;/em&gt; to be opencover and defines the excluded files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RunSettings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;DataCollectionRunSettings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DataCollectors&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DataCollector&lt;/span&gt; &lt;span class="na"&gt;friendlyName=&lt;/span&gt;&lt;span class="s"&gt;"XPlat Code Coverage"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Configuration&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;Format&amp;gt;&lt;/span&gt;opencover&lt;span class="nt"&gt;&amp;lt;/Format&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;ExcludeByFile&amp;gt;&lt;/span&gt;**/test/**/*.cs&lt;span class="nt"&gt;&amp;lt;/ExcludeByFile&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Configuration&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/DataCollector&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/DataCollectors&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/DataCollectionRunSettings&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/RunSettings&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test"&gt;dotnet test&lt;/a&gt; command passing the runsettings file as settings to execute the tests and generate the test coverage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet test --settings mysettings.runsettings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command prints the test results and the test coverage file path to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Passed!  - Failed:     0, Passed:    97, Skipped:     0, Total:    97, Duration: 7 s - CHttp.Tests.dll (net8.0)

Attachments:
  D:\repos\Http3Tools\tests\CHttp.Tests\TestResults\9e0687e7-8f5b-4673-a684-0cc969035a29\coverage.opencover.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filename reflects that the generated report is indeed in &lt;em&gt;opencover&lt;/em&gt; format. The containing folder path with file filter should be specified in the NDepend project. Re-running the analysis includes the Coverage report on the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZeiVKh_B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81303iifvg83v6abmfvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZeiVKh_B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/81303iifvg83v6abmfvz.png" alt="coverage-report" width="552" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NDepend Reports
&lt;/h2&gt;

&lt;p&gt;When selecting the &lt;strong&gt;Run Analysis and Build Report&lt;/strong&gt; command in the desktop application, a set of HTML &lt;a href="https://www.ndepend.com/sample-reports/"&gt;report files&lt;/a&gt; are generated. These reports can be viewed in a browser and hosted as static content on server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0pjJaCov--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0wbfql9xu5co2h9nr35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0pjJaCov--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0wbfql9xu5co2h9nr35.png" alt="reports" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A neat feature of these report files is that under issues tab one can navigate to the individual files to explore issues commented on the source code including the code coverage. It does not require a direct link to the source code, as these files are generated during the analysis.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GWlD3Olq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtw1jwngd2uv1nde92lo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GWlD3Olq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtw1jwngd2uv1nde92lo.png" alt="report-file" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NDepend Console
&lt;/h2&gt;

&lt;p&gt;A careful reader might have realized that the HMTL file reports could be generated and shared by a build machine, so that a development team can review and act-up on them for every pull-request. However, the desktop NDepend application is a .NET Framework application that cannot run on a Linux build machine. Fortunately, NDepend comes with a cross-platform &lt;a href="https://www.ndepend.com/docs/ndepend-console"&gt;console&lt;/a&gt; application that can be used for the purpose. After registering the build machine license with the &lt;code&gt;/RegLic&lt;/code&gt; switch a Continuous Integration (CI) job can be extended to invoke the console application to generate the reports for a given NDepend project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet NDepend.Console.MultiOS.dll D:\repos\Http3Tools\ndepend\NDepend.ndproj /ViewReport
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The static report files can be typically served by the build agent as artifacts. There is no need for setting up and maintaining separate code quality services. Even for the long-term trend reports only a shared directory is required, which can be configured with the &lt;code&gt;/TrendStoreDir&lt;/code&gt; switch. The CI pipeline by default enforces the NDepend quality gates, but if the goal is only to generate the reports, the &lt;code&gt;/ForceReturnZeroExitCode&lt;/code&gt; switch can be used to return sucessful exit code from the CLI command.&lt;/p&gt;

&lt;p&gt;The primary purpose of the console application seems to be providing a cross-platform way to generate reports on build machines. However, it could be also integrated with a custom VS Code build task. A developer could directly integrate it with the build process via the &lt;a href="https://code.visualstudio.com/Docs/editor/tasks"&gt;tasks.json&lt;/a&gt; file, setting up a tight development cycle, similar to one provided with the Visual Studio extension.&lt;/p&gt;

&lt;p&gt;While currently the &lt;a href="https://www.ndepend.com/"&gt;NDepend&lt;/a&gt; generates comprehensive reports, a nice addition could be incorporating the &lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview"&gt;Roslyn Analyzer&lt;/a&gt; warnings. Fortunately, this is already in the backlog of the future features for the tool.&lt;/p&gt;

</description>
      <category>ndepend</category>
      <category>staticanalysis</category>
      <category>dependencygraph</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Feature Flags with ConfigCat and Asp.Net Core Options</title>
      <dc:creator>&amp;ladeak</dc:creator>
      <pubDate>Sun, 10 Oct 2021 09:40:25 +0000</pubDate>
      <link>https://dev.to/ladeak87/feature-flags-with-configcat-and-asp-net-core-options-36o7</link>
      <guid>https://dev.to/ladeak87/feature-flags-with-configcat-and-asp-net-core-options-36o7</guid>
      <description>&lt;p&gt;A feature flag or 'feature toggle' is a common technique to enable or disable application features dynamically. For example, feature flags enable product owners to turn on and off features during runtime of the application. Certain features may be turned on/off for given environments, users, or regions only. This way features may be A-B tested, or tested with a given percentage of users, or different countries of the world. It can also provide a solution to meet regional restrictions of different countries.&lt;/p&gt;

&lt;p&gt;In this post I investigate integrating &lt;a href="https://configcat.com/"&gt;ConfigCat's&lt;/a&gt; feature management with an ASP.NET Core 6 web API service and the &lt;code&gt;Options&amp;lt;T&amp;gt;&lt;/code&gt; &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0"&gt;pattern&lt;/a&gt;. I will also focus on using the &lt;em&gt;AutoPolling&lt;/em&gt; mechanism built into the ConfigCat client library, to refresh feature flags' state during application runtime.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Throughout my carrier I have used some sort of a feature flag solution. In some cases, this was a conscious decision built upon a well-designed application architecture, while in other cases it was just an &lt;code&gt;if&lt;/code&gt; statement with a key-value pair in the configuration file. However, I have not yet encountered such a complete service as the one provided by &lt;a href="https://configcat.com/"&gt;ConfigCat&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I will focus on using feature flags solution within web services. Although, I see an even bigger need for such a robust solution in desktop applications. Managing the configuration of a few web service instances is inherently simpler to manage hundreds or thousands of desktop applications, which is common in the enterprise world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Options
&lt;/h2&gt;

&lt;p&gt;Feature flags can be leveraged in applications many ways. In the past, when computer networks were less ubiquitous, feature flags were typically implemented as compiler directives. This way a given code path was compiled into the application or remained as commented out section. The advantage of this solution is less branching and smaller code size. Although to 'toggle' a feature a new compilation of the source code is required, which makes this solution less dynamic. Users would need to uninstall/install or upgrade their application with the new binaries to get a feature toggled.&lt;/p&gt;

&lt;p&gt;Today the most common technique is branching by &lt;code&gt;if&lt;/code&gt; statements. If the features flag is in enabled state a certain code path of the application is executed. For example, when a button is clicked to start an order processing, &lt;em&gt;if&lt;/em&gt; a given feature flag enabled an SMS is also sent to the user. One could express this as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nf"&gt;ProcessOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isSmsFeatureEneabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sendSMS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSmsFeatureEneabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;SendSms&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another approach would be to leverage &lt;a href="https://www.martinfowler.com/bliki/BranchByAbstraction.html"&gt;branching by abstractions&lt;/a&gt;. As this is a larger topic, I am not detailing it within this post, exploring this area may worth its own writing.&lt;/p&gt;

&lt;p&gt;One very recent feature flag I encountered comes from .NET itself: using the &lt;a href="https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/"&gt;HTTP3 preview feature&lt;/a&gt; in &lt;code&gt;HttpClient&lt;/code&gt; requires the developers to proactively enable the feature by setting the &lt;code&gt;&amp;lt;EnablePreviewFeatures&amp;gt;True&amp;lt;/EnablePreviewFeatures&amp;gt;&lt;/code&gt; flag in the csproj file.&lt;/p&gt;

&lt;h2&gt;
  
  
  ConfigCat and Asp.Net Core and .NET 6
&lt;/h2&gt;

&lt;p&gt;Using the ConfigCat's service does not restrict us choosing any of implementation techniques, although one would probably not choose to use compiler directives. In this section I show how one can integrate the ConfigCat's configuration with the &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; pattern of .NET 6. I am using a late preview version of .NET6 at the time of writing this post. DotNet 6 provides a new configuration concept with &lt;code&gt;ConfigurationManager&lt;/code&gt; type, which is not available in the previous versions. &lt;code&gt;ConfigurationManager&lt;/code&gt; allows to initialize configuration sources while using configuration values of previously initialized sources. It achieves this by implementing &lt;code&gt;IConfigurationBuilder, IConfigurationRoot, IConfiguration&lt;/code&gt; interfaces at the same time.&lt;/p&gt;

&lt;p&gt;One consideration to make is that no user specific feature flag will be used, which means that in this implementation flags will not be respected if set for specific users or 'target % users' on the ConfigCat's portal. In all cases the 'To all users' value of the feature flag is used.&lt;/p&gt;

&lt;p&gt;In general, when using &lt;code&gt;Options&amp;lt;T&amp;gt;&lt;/code&gt; pattern, there is no good API to query user specific settings, and in a web application used by multiple users, there is also no effective way to fetch flags for one or a few users during application startup. Thus, all feature flags shall be independent of users when being registered with Options. We can leverage though non-user specific information i.e. semantic version of the application is greater than 1.2.3. I will leave it up for the reader to extend the presented solution with such extensions.&lt;/p&gt;

&lt;p&gt;Let me first preview the whole 'startup' code and the action of the service, then describe the necessary types I created for the solution. Here is &lt;em&gt;Program.cs&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ConfigCat.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConfigCat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FeatureSet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FeatureSet&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseHttpsRedirection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IOptionsSnapshot&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FeatureSet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrandFeature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status200OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status404NotFound&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureSet&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;GrandFeature&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This web API has a single GET endpoint &lt;code&gt;api/feature&lt;/code&gt;. The response depends on a feature flag: &lt;code&gt;GrandFeature&lt;/code&gt;. When the feature is turned on, it returns HTTP 200 OK, with &lt;code&gt;Hello World!&lt;/code&gt; as the content. When the feature is turned off, it returns 404 Not Found. The state of the feature flag is accessed through &lt;code&gt;IOptionsSnapshot&amp;lt;FeatureSet&amp;gt; features&lt;/code&gt;, which I will explain later in this post.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;builder.Configuration.AddConfigCat(false);&lt;/code&gt; uses a custom extension method to add the toggle values of ConfigCat to the Asp.Net Core's configuration.&lt;br&gt;
The second line &lt;code&gt;builder.Services.Configure&amp;lt;FeatureSet&amp;gt;(builder.Configuration.GetSection(nameof(FeatureSet)));&lt;/code&gt; sets up the feature flags with the &lt;code&gt;Options&amp;lt;T&amp;gt;&lt;/code&gt; pattern. This is the standard way to bind a given section of the configuration to a type, while also registering the type with the DI container. Here, I bind the configuration to a type called &lt;code&gt;FeatureSet&lt;/code&gt; which has a single boolean property &lt;em&gt;GrandFeature&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's investigate the custom extension method. The upcoming code focuses on getting the configuration values of ConfigCat into Asp.Net's configuration. Below the extension method uses &lt;code&gt;ConfigurationManager&lt;/code&gt; to read the ConfigCat API key and &lt;em&gt;poll interval&lt;/em&gt; settings from the 'appsettings.json' file. These configuration values are added by Asp.Net web application's file provider during startup. In production, one would prefer to pass the ConfigCat key as a secret or as an environment variable. In either case a configuration source would set the value before &lt;code&gt;AddConfigCat&lt;/code&gt; is invoked. As '0' is an invalid interval for polling, the extension method validates it. The method has an &lt;em&gt;optional&lt;/em&gt; parameter which indicates the desired behavior when ConfigCat cannot fetch the feature flags, while &lt;em&gt;onError&lt;/em&gt; parameter is an action that is invoked in case of an exception. Feature flags are fetched from the service periodically, the &lt;em&gt;onError&lt;/em&gt; parameter provides a way to observe errors during the background polls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Extensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IConfigurationBuilder&lt;/span&gt; &lt;span class="nf"&gt;AddConfigCat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;ConfigurationManager&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;onError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ConfigCat:Key"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ConfigCat:Key"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pollInterval&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"ConfigCat:PollInterval"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pollInterval&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ConfigCat:PollInterval"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pollInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;IConfigurationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatConfigurationSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;RefreshInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsOptional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;OnError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ConfigCatOptions&lt;/code&gt; record type is encapsulating the parameters for &lt;code&gt;ConfigCatConfigurationProvider&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next type is &lt;code&gt;ConfigCatConfigurationSource&lt;/code&gt;. An &lt;code&gt;IConfigurationSource&lt;/code&gt; is required to be implemented as this is the type added to the configuration sources. The responsibility of the type is to create an &lt;code&gt;IConfigurationProvider&lt;/code&gt;. With .NET6 when a configuration provider is removed or modified, all the remaining sources are rebuilt. This implementation returns a lazily instantiated &lt;code&gt;ConfigCatConfigurationProvider&lt;/code&gt; instance. I use the singleton semantics because auto polling built into the &lt;code&gt;ConfigCatConfigurationProvider&lt;/code&gt; refreshes the configuration automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigCatConfigurationSource&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IConfigurationSource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConfigCatOptions&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ConfigCatConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatConfigurationSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConfigCatOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IConfigurationProvider&lt;/span&gt; &lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IConfigurationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_provider&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Another use case could be when the feature flags are read only at application startup. In certain applications this could be a valid scenario. For this, manual polling would be a better choice, and creating a new instance of &lt;code&gt;ConfigCatConfigurationProvider&lt;/code&gt; on every &lt;code&gt;Build()&lt;/code&gt; method invocation would also make sense.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last and most complex class to implement is &lt;code&gt;ConfigCatConfigurationProvider&lt;/code&gt;. This type derives from &lt;code&gt;ConfigurationProvider&lt;/code&gt; which already implements many of the &lt;code&gt;IConfigurationProvider&lt;/code&gt; interface members. Here, I only override the &lt;code&gt;Load()&lt;/code&gt; method, which is invoked by the &lt;em&gt;Host&lt;/em&gt; right after the configuration provider is instantiated. In the first invocation I create a new &lt;code&gt;ConfigCatClient&lt;/code&gt;, and because &lt;code&gt;AutoPollConfiguration&lt;/code&gt; uses a &lt;code&gt;Timer&lt;/code&gt; to load configuration data asynchronously, a task completion source must be waited. Unfortunately, the method signature does not allow to use the &lt;code&gt;await&lt;/code&gt; keyword. Without waiting for the task completion source, further providers would not be able to read the data set by this provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigCatConfigurationProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConfigCatOptions&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AutoPollConfiguration&lt;/span&gt; &lt;span class="n"&gt;_polling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;TaskCompletionSource&lt;/span&gt; &lt;span class="n"&gt;_initialLoad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IConfigCatClient&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;_configCatClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatConfigurationProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ConfigCatOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;_polling&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AutoPollConfiguration&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;SdkKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="n"&gt;PollIntervalSeconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RefreshInterval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;_initialLoad&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TaskCompletionSource&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_polling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnConfigurationChanged&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnConfigurationChanged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnConfigurationChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OnConfigurationChangedEventArgs&lt;/span&gt; &lt;span class="n"&gt;eventArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;LoadData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_initialLoad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrySetResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_configCatClient&lt;/span&gt; &lt;span class="p"&gt;??=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigCatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_polling&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_initialLoad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAwaiter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LoadData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ParseKeys&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsOptional&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;StringComparer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnError&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;OnReload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ParseKeys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_configCatClient&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_configCatClient&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;StringComparer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_configCatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAllKeys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_configCatClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;ConfigCatClient&lt;/code&gt; has loaded the data, the &lt;code&gt;OnConfigurationChanged&lt;/code&gt; event is fired. This is when all key value pairs are loaded. &lt;code&gt;LoadData()&lt;/code&gt; and &lt;code&gt;ParseKeys()&lt;/code&gt; methods read and parse the keys and corresponding values. The result dictionary is set in the &lt;em&gt;Data&lt;/em&gt; property, which is declared by the base class. The only additional logic applied here is to replace the underscore characters with semicolons. This is done, as the ':' character is unsupported in key names, so to deal with the hierarchy of configuration values, another character must be used for the ConfigCat feature names. Using the '_' character resembles a similar behavior to using configuration values with environment variables.&lt;/p&gt;

&lt;p&gt;Note, that &lt;code&gt;LoadData()&lt;/code&gt; method invokes a method from the base type: &lt;code&gt;OnReload();&lt;/code&gt;. This will generate a new change token signaling the configuration provider that the configuration values have changed. The values of options might change due to the built-in auto-polling mechanism, however the &lt;code&gt;OnConfigurationChanged&lt;/code&gt; event is only fired when the values have changed.&lt;/p&gt;

&lt;p&gt;To read the latest values of configuration while serving the HTTP request, an &lt;code&gt;IOptionsSnapshot&amp;lt;FeatureSet&amp;gt;&lt;/code&gt; is passed to the GET request's action handler. This type is useful in scenarios where options should be recomputed on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In one way or another an &lt;em&gt;aging&lt;/em&gt;, but still maintained application requires a solution for feature flags. The more robust this solution is the more choice is given to the development team to isolate certain preview features to certain users. Implementing a custom feature flag solution does not usually provide a competitive advantage, using service built for the purpose makes sense. In this regards, ConfigCat's solution seems a reasonable choice for my next project.&lt;/p&gt;

</description>
      <category>aspnetcore</category>
      <category>optionspattern</category>
      <category>configcat</category>
    </item>
  </channel>
</rss>
