<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by André Silva on Medium]]></title>
        <description><![CDATA[Stories by André Silva on Medium]]></description>
        <link>https://medium.com/@askpt?source=rss-1744c61bb207------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*xTFqbHzUFXCpFgqtRjyhIw@2x.jpeg</url>
            <title>Stories by André Silva on Medium</title>
            <link>https://medium.com/@askpt?source=rss-1744c61bb207------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 12 Apr 2026 19:53:47 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@askpt/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Multi-Provider Feature Flags in .NET with OpenFeature]]></title>
            <link>https://medium.com/@askpt/multi-provider-feature-flags-in-net-with-openfeature-7808b1f5950e?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/7808b1f5950e</guid>
            <category><![CDATA[openfeature]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Thu, 08 Jan 2026 10:13:39 GMT</pubDate>
            <atom:updated>2026-01-08T17:14:12.377Z</atom:updated>
            <content:encoded><![CDATA[<h3>Multi-Provider Feature Flags in .NET with OpenFeature</h3><h4>Feature flags rarely live in just one place. You may have a vendor service, some environment-based overrides, and a couple of in-memory fallbacks hidden within your app. OpenFeature&#39;s MultiProvider lets you compose all of these behind a single, clean client in your .NET applications.​</h4><figure><img alt="OpenFeature Logo" src="https://cdn-images-1.medium.com/max/940/1*aVFrq7-0q7ng2_u2MNYHEg.png" /><figcaption>OpenFeature Logo</figcaption></figure><p>If you’re interested in standardising and streamlining feature flag management across different tools and platforms, consider exploring the OpenFeature project. OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging. It allows you to integrate with your favourite feature flag management tool or in-house solutions without vendor lock-in. It supports many programming languages and makes switching providers or consolidating multiple solutions behind a single interface easy. To learn more about how OpenFeature can enhance your development workflow, visit the <a href="https://openfeature.dev">OpenFeature website</a>.</p><p>In this post, you&#39;ll see how to wire up <a href="https://www.nuget.org/packages/OpenFeature.Providers.MultiProvider">OpenFeature.Providers.MultiProvider</a> with dependency injection, how strategies decide which provider &quot;wins,&quot; and a few practical patterns like primary/fallback, migrations, and tenant-specific routing.​</p><h3>TL;DR</h3><ul><li>OpenFeature.Providers.MultiProvider is a composite FeatureProvider for the OpenFeature .NET SDK.​</li><li>It allows you to register multiple underlying providers and select a strategy for routing and combining evaluations.​</li><li>You configure it with:<br>-AddMultiProvider(...) when using dependency injection, or <br>-new MultiProvider(IEnumerable&lt;ProviderEntry&gt;, IStrategy) when bootstrapping manually.​</li><li>Your application code continues to use Api.Instance.GetClient(...).GetXxxValueAsync(...) the MultiProvider hides all routing logic.​</li></ul><h3>What the MultiProvider Actually Is</h3><p>In OpenFeature, a FeatureProvider abstracts &quot;where flags come from&quot; for a client. A typical provider wraps a single backend, such as a remote flag service, a configuration file, or an in-memory map.​</p><p>OpenFeature.Providers.MultiProvider changes that model slightly.​</p><ul><li>It is itself a FeatureProvider.</li><li>Internally, it holds a collection of ProviderEntry instances, each wrapping a concrete FeatureProvider plus an optional name.​</li><li>A strategy (for example, FirstMatchStrategy) decides:<br>- Which underlying provider(s) to call for a given flag key.<br>- How to combine or short-circuit their results.</li></ul><p>Because MultiProvider is still just a FeatureProvider, you can set it as either the default provider or a named &quot;domain&quot; provider in your DI configuration.​</p><h3>Wiring MultiProvider with Dependency Injection</h3><p>The recommended way to use MultiProvider in .NET is through the OpenFeature hosting/DI integration. You typically start with something like this:​</p><pre>using Microsoft.Extensions.DependencyInjection;<br>using OpenFeature.Hosting;<br>using OpenFeature.Providers.MultiProvider.DependencyInjection;<br><br>// inside Program.cs or equivalent bootstrapping:<br>var services = new ServiceCollection();<br>services.AddOpenFeature(featureBuilder =&gt;<br>{<br>    featureBuilder<br>        .AddMultiProvider(&quot;multi-provider&quot;, multiProviderBuilder =&gt;<br>        {<br>            // configure providers + strategy here<br>        });<br>});</pre><p>A few key points about what happens here:​</p><ul><li>AddOpenFeature(...) gives you an OpenFeatureBuilder.</li><li>AddMultiProvider(...) (extension method from FeatureBuilderExtensions):<br>- Creates a MultiProviderBuilder.<br>- Let&#39;s you register multiple inner providers.<br>- Registers the constructed MultiProvider as the provider for the given domain (for example, &quot;multi-provider&quot;).​</li></ul><p>You later consume it like any other OpenFeature client:</p><pre>// After building the ServiceProvider and starting the host:<br>var client = sp.GetService&lt;IFeatureClient&gt;();<br>var value = await client.GetBooleanValueAsync(&quot;my-flag&quot;, defaultValue: false);</pre><p>The client is unaware that a MultiProvider backs it; it just sees a standard OpenFeature provider.​</p><h3>Adding Providers: Factories, Instances, and Types</h3><p>MultiProviderBuilder gives you several ways to add underlying providers so you can match whatever style your composition root uses.​</p><h3>Factory-based (DI-friendly) registration</h3><p>In DI-centric apps (ASP.NET Core, generic host, etc.), a factory-based approach is often the most flexible:</p><pre>using OpenFeature.Providers.MultiProvider.DependencyInjection;<br><br>// Inside AddOpenFeature(...)<br>featureBuilder.AddMultiProvider(&quot;multi-provider&quot;, multiProviderBuilder =&gt;<br>{<br>    multiProviderBuilder<br>        // Provider registered with a simple factory<br>        .AddProvider(&quot;primary&quot;, sp =&gt;<br>        {<br>            // sp is the IServiceProvider<br>            var flags = new Dictionary&lt;string, bool&gt;<br>            {<br>                { &quot;my-flag&quot;, true }<br>            };<br>            return new InMemoryProvider(flags);<br>        })<br>        .AddProvider(&quot;fallback&quot;, sp =&gt;<br>        {<br>            // Example of a fictional provider with DI dependencies<br>            var httpClient = sp.GetRequiredService&lt;HttpClient&gt;();<br>            return new MyCompanyFeatureProvider(httpClient);<br>        });<br>});</pre><p>Notes:​</p><ul><li>Each call to .AddProvider(...) registers one underlying FeatureProvider.</li><li>The name (&quot;primary&quot;, &quot;fallback&quot;) is mainly for strategies and logging; your application code rarely references it directly.​</li><li>The factory receives the IServiceProvider, so that you can resolve any dependencies from the container.​</li></ul><h3>Registering existing instances</h3><p>If you already have provider instances created in your composition root, you can add them directly:</p><pre>var primaryProvider = new InMemoryProvider(primaryFlags);<br>var fallbackProvider = new InMemoryProvider(fallbackFlags);<br>featureBuilder.AddMultiProvider(&quot;multi-provider&quot;, multiProviderBuilder =&gt;<br>{<br>    multiProviderBuilder<br>        .AddProvider(&quot;primary&quot;, primaryProvider)<br>        .AddProvider(&quot;fallback&quot;, fallbackProvider);<br>});</pre><p>This behaviour is handy when you want explicit construction logic or to reuse a provider across multiple domains.​</p><h3>Type-based registration</h3><p>You can also let DI construct the providers, optionally customising them via a factory:</p><pre>featureBuilder.AddMultiProvider(&quot;multi-provider&quot;, multiProviderBuilder =&gt;<br>{<br>    multiProviderBuilder<br>        // Provider resolved from DI<br>        .AddProvider&lt;MyCompanyFeatureProvider&gt;(&quot;primary&quot;)<br>        // Provider resolved from DI but customized by a factory<br>        .AddProvider&lt;MyCompanyFeatureProvider&gt;(&quot;secondary&quot;, sp =&gt;<br>        {<br>            var config = sp.GetRequiredService&lt;MyProviderConfig&gt;();<br>            return new MyCompanyFeatureProvider(config);<br>        });<br>});</pre><p>In this case, MyCompanyFeatureProvider must itself be registered with the container, for example, with services.AddSingleton&lt;MyCompanyFeatureProvider&gt;();.​</p><h3>Strategies: How Evaluations Are Combined</h3><p>Multiple providers alone are not enough; the MultiProvider needs a strategy to decide how to evaluate them and when to stop. Strategies live under OpenFeature.Providers.MultiProvider.Strategies and implement the logic for:​</p><ul><li>Which provider(s) to try for a given flag key?</li><li>Whether to short-circuit or continue &quot;flag not found&quot;?</li><li>How to interpret errors vs. &quot;not found&quot;?​</li></ul><h3>Configuring a strategy via DI</h3><p>With DI, you typically chain .UseStrategy&lt;TStrategy&gt;() on the builder:</p><pre>using OpenFeature.Providers.MultiProvider.Strategies;<br><br>featureBuilder.AddMultiProvider(&quot;multi-provider&quot;, multiProviderBuilder =&gt;<br>{<br>    multiProviderBuilder<br>        .AddProvider(&quot;primary&quot;, sp =&gt; new InMemoryProvider(primaryFlags))<br>        .AddProvider(&quot;secondary&quot;, sp =&gt; new InMemoryProvider(secondaryFlags))<br>        .UseStrategy&lt;FirstMatchStrategy&gt;(); // default-style strategy<br>});</pre><p>FirstMatchStrategy behaves conceptually like this:​</p><ul><li>Evaluate providers in the registered order.</li><li>Return the first successful result for the requested flag key.</li><li>If a provider reports FLAG_NOT_FOUND, move on to the next.</li><li>If all providers fail or do not have the flag, surface the appropriate error or default.​</li></ul><p>This strategy is ideal when you want a &quot;primary provider plus fallback&quot; pattern, where one provider is considered the source of truth, and the others only step in if needed.​</p><h3>Strategy-driven routing patterns</h3><p>By swapping or customising strategies, you can implement a few common patterns without changing call sites:​</p><h4>Override provider</h4><ul><li>One provider holds overrides for specific keys or tenants (for example, environment variables or admin overrides).</li><li>If it does not handle a flag, the strategy falls back to a base provider.</li></ul><h4>Migration provider</h4><ul><li>One provider represents the &quot;new&quot; system and another the legacy system.</li><li>The strategy prefers the new provider but falls back when a flag is missing, so you can move flags gradually.​</li></ul><h4>Key or tenant-based routing</h4><ul><li>A custom strategy can be routed based on flag key patterns or evaluation context (for example, tenant ID in the context).</li><li>The logic lives in the Strategies namespace instead of being scattered through business logic.​</li></ul><p>The key takeaway is that routing remains within the MultiProvider + strategy, not within your controllers, services, or repositories.​</p><h3>Manual Setup Without Dependency Injection</h3><p>If you are not using the hosting/DI integration, you can construct the MultiProvider directly and set it on the Api. The pattern is straightforward:​</p><pre>using OpenFeature;<br>using OpenFeature.Providers.InMemory;<br>using OpenFeature.Providers.MultiProvider;<br>using OpenFeature.Providers.MultiProvider.Models;<br>using OpenFeature.Providers.MultiProvider.Strategies;<br><br>// Create inner providers<br>var provider1 = new InMemoryProvider(provider1Flags);<br>var provider2 = new InMemoryProvider(provider2Flags);<br><br>// Wrap them into ProviderEntry instances<br>var entries = new List&lt;ProviderEntry&gt;<br>{<br>    new(provider1, &quot;Provider1&quot;),<br>    new(provider2, &quot;Provider2&quot;)<br>};<br><br>// Build the MultiProvider<br>var strategy = new FirstMatchStrategy();<br>var multiProvider = new MultiProvider(entries, strategy);<br><br>// Register it as the default provider<br>await Api.Instance.SetProviderAsync(multiProvider);<br><br>// Use normally<br>var client = Api.Instance.GetClient();<br>var value = await client.GetBooleanValueAsync(&quot;my-flag&quot;, defaultValue: false);</pre><p>Your client code does not change if you later swap InMemoryProvider for MyCompanyFeatureProvider or update the provider list and strategy.​</p><h3>Concrete Use Cases</h3><p>Here are a few scenarios where MultiProvider shines in .NET applications:​</p><h4>Primary + fallback provider</h4><ul><li>Primary: a remote provider (for example, your company&#39;s feature flag service).</li><li>Fallback: an in-memory provider with safe defaults or local overrides.</li><li>Strategy: FirstMatchStrategy, so you hit the primary first and only fall back when needed.​</li></ul><p>This strategy enables you to maintain a single OpenFeature client while still achieving layered resilience and overrides.</p><h4>Gradual migration between systems</h4><ul><li>Providers: LegacyFeatureProvider and NewFeatureProvider.</li><li>Strategy: try the new provider first; if a flag is missing, fall back to the legacy provider.​</li></ul><p>You can migrate flags to the new system incrementally, without changing call sites or deploying invasive refactorings.​</p><h4>Tenant-specific providers with a custom strategy</h4><ul><li>Each tenant may get its own provider instance or configuration.</li><li>A custom strategy inspects the evaluation context (for example, tenantId) and routes to the right provider entry.​</li></ul><p>This behaviour keeps tenant routing logic out of your business code and inside a testable, composable strategy.</p><h3>Error Handling, Thread Safety, and Lifecycle</h3><p>MultiProvider is designed to behave like any other provider from the client&#39;s perspective, including error handling and lifecycle.​</p><h4>Error handling</h4><ul><li>MultiProvider aggregates errors from underlying providers according to the chosen strategy.</li><li>The OpenFeature SDK still returns the usual resolution result; the strategy decides how to interpret partial failures or multiple issues.​</li></ul><h4>Thread safety</h4><ul><li>MultiProvider is designed for concurrent use in typical .NET scenarios (for example, ASP.NET Core, background services).</li></ul><h4>Lifecycle</h4><ul><li>As a FeatureProvider, MultiProvider participates in initialisation and shutdown.</li><li>When you shut down the host or dispose of the provider, underlying providers are disposed of as needed.​</li></ul><p>These details mostly &quot;just work,&quot; which means you can focus on provider composition and strategy choice rather than on mechanics.</p><h3>One Client, Many Providers</h3><p>Once MultiProvider is wired in, your application code keeps calling Api.Instance.GetClient(...).GetXxxValueAsync(...) as usual. Behind that single client, you can combine vendors, environment overrides, in-memory defaults, and tenant-specific logic with a pluggable strategy.​</p><p>If you are standardising feature flags across multiple .NET services, MultiProvider is a pragmatic way to grow from &quot;one provider per app&quot; to a more flexible, layered model without rewriting call sites.​</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7808b1f5950e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dev Containers: Removing Friction to Accelerate Contributor Onboarding]]></title>
            <link>https://medium.com/@askpt/dev-containers-removing-friction-to-accelerate-contributor-onboarding-391c09ccfbb2?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/391c09ccfbb2</guid>
            <category><![CDATA[devcontainer]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[github-codespaces]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[onboarding]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Tue, 02 Dec 2025 12:54:30 GMT</pubDate>
            <atom:updated>2025-12-02T12:54:30.022Z</atom:updated>
            <content:encoded><![CDATA[<p><em>What if every potential open source contributor could go from “git clone” to their first real code change in under five minutes?</em></p><p>For many projects, reality looks different: contributors waste hours wrestling with tricky setups, dependency hell, or platform quirks. Some give up before their first PR. As maintainers of the <a href="https://github.com/open-feature/dotnet-sdk">OpenFeature .NET SDK</a>, this problem hit home for us. But with Dev Containers and GitHub Codespaces, we flipped the script.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5FyQeKQuWTIB7HTW" /><figcaption>Photo by <a href="https://unsplash.com/@growtika?utm_source=medium&amp;utm_medium=referral">Growtika</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>The Friction Crisis: Why Setup Kills Contributions</h3><p>Ask any open source maintainer about the most significant hurdle facing new contributors, and they’ll point to complex setup instructions and inconsistent environments. Developer onboarding friction is a well-known barrier to success. For example:</p><ul><li>Platform mismatches (M1/M2 Macs, Windows WSL, Linux variants)</li><li>The infamous “works on my machine” syndrome</li><li>Hours lost before even running dotnet build</li></ul><p>While surveys indicate that motivation to contribute is high, hidden technical barriers often hinder progress. What does this cost maintainers? Fewer contributors, less innovation, and more “support” time untangling setup issues instead of reviewing code.</p><h3>Dev Containers: The Five-Minute Onboarding Dream</h3><p>Dev Containers enable us to define everything about a project’s development environment, which means that OS, dependencies, tools and even extensions are specified in code. Pair that with GitHub Codespaces and suddenly, onboarding is instant and reproducible. GitHub Codespaces is a browser-based, one-click workspace that runs these containers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*5vAhdDzh3EywW9bz.png" /><figcaption>dev containers structure</figcaption></figure><h3>How We Did It: Applying Dev Containers to OpenFeature .NET SDK</h3><p>When we introduced devcontainers to our <a href="https://github.com/open-feature/dotnet-sdk">OpenFeature .NET SDK repo</a>:</p><ol><li>Standardised Setup: .devcontainer/devcontainer.json codified our build tools, base images, and extensions. Every Codespace runs identically.</li><li>Cloud-Native Ready: We added patterns for orchestrating the E2E tests, making experimentation nearly effortless.</li><li>Samples project: We have a sample project where new contributors can easily play and test the changes.</li></ol><h3>Practical Tips for Maintainers</h3><p>Want to replicate this success? Here’s what worked for us:</p><ul><li>Start simple: Add a basic .devcontainer/devcontainer.json setup with your essential tools.</li><li>Show, don’t tell: Badge your repository and update the documentation to highlight “one-click” setups.</li><li>Iterate: Gather contributor feedback and keep evolving your environment. Dev Containers are flexible and easy to update.</li><li>Measure: Track key metrics such as time-to-first-PR and issue closure rates.</li><li>Leverage Templates: Use (or create) Dev Container templates tailored for your specific tech stack, cloud-native or multi-language projects.</li></ul><h3>Codespaces: Making Dev Containers Accessible for All</h3><p>With GitHub Codespaces, the barriers continue to fall and anyone can contribute.</p><ul><li>Educational sandboxes: Want to run docs or tests only? Pre-configure lightweight containers for these tasks.</li><li>Onboard maintainers: Codify advanced setups like CI, local clusters and secrets, so your core team’s environment is as solid as your newcomers’.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JCQojPjx5y0aao1t" /><figcaption>Photo by <a href="https://unsplash.com/@rubaitulazad?utm_source=medium&amp;utm_medium=referral">Rubaitul Azad</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>The Broader Impact</h3><p>Lowering the bar for entry created a <em>welcoming</em>, low-friction experience that keeps people coming back and raises the overall quality of our project.</p><h3>Key Resources</h3><ul><li><a href="https://github.com/open-feature/dotnet-sdk">OpenFeature .NET SDK with Dev Containers</a></li><li><a href="https://containers.dev/">Dev Containers Specification</a></li><li><a href="https://github.com/features/codespaces">GitHub Codespaces Documentation</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=391c09ccfbb2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What’s New in C# 14: Transforming .NET Coding]]></title>
            <link>https://medium.com/@askpt/whats-new-in-c-14-transforming-net-coding-8f5e6c0f9b6a?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f5e6c0f9b6a</guid>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[c-sharp-programming]]></category>
            <category><![CDATA[dotnet-core]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Wed, 12 Nov 2025 09:30:17 GMT</pubDate>
            <atom:updated>2025-11-12T09:52:14.723Z</atom:updated>
            <content:encoded><![CDATA[<h3>What’s New in C# 14: Transforming .NET Coding</h3><h4>C# 14 is here, and it’s packed with updates designed to make your code cleaner, faster, and more enjoyable to write. Whether you’re maintaining legacy systems or building new apps on .NET 10, these new features will modernise your workflows and unlock new power.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*09phOoZe9kFX7eYH" /><figcaption>Photo by <a href="https://unsplash.com/@ikukevk?utm_source=medium&amp;utm_medium=referral">Kevin Ku</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>Extension Members: Levelling Up Extensions</h3><p>If you like extension methods, you’re going to <em>love</em> the new extension blocks. In C# 14, you can declare extension methods, properties, and even indexers more naturally.</p><p>Old extension method:</p><pre>public static class IntegerExtensions<br>{<br>    public static bool IsDefault(this int value) =&gt; value == 0;<br>}</pre><p>New extension block:</p><pre>public static class IntegerExtensions<br>{<br>    extension(int value)<br>    {<br>        public bool IsDefault() =&gt; value == 0;<br>    }<br>}</pre><p>Now, everything you extend gets grouped, with methods, properties, and even private fields for caching.</p><h3>Extension Properties &amp; Indexers: Cleaner API for Collections</h3><p>Stop writing helper methods everywhere. Add properties and indexers so your code reads naturally:</p><pre>public static class CollectionExtensions<br>{<br>    extension&lt;T&gt;(IEnumerable&lt;T&gt; source)<br>    {<br>        public bool IsEmpty =&gt; !source.Any();<br>        public int Count =&gt; source.Count();<br>    }<br>}</pre><p>Use IsEmpty in your collection and your intent shine through. There is no need for more confusing method calls.</p><h3>Private Fields in Extensions: Cache Like a Pro</h3><p>Extension blocks give you private fields for expensive computations:</p><pre>public static class CollectionExtensions<br>{<br>    extension&lt;T&gt;(IEnumerable&lt;T&gt; source)<br>    {<br>        private int? _count;<br><br>        public bool IsEmpty =&gt; !source.Any();<br>        public int Count =&gt; _count ??= source.Count();<br>    }<br>}</pre><p>This feature maintains performance while keeping your API clean.</p><h3>Static Extension Members: Utility Methods for Types</h3><p>You can add static helpers and factory methods.</p><pre>public static class UserExtensions<br>{<br>    extension(User)<br>    {<br>        public static User CreateDefault() =&gt; new User {...};<br>    }<br>}<br>var user = User.CreateDefault();</pre><h3>Null-Conditional Assignment: Safer, Shorter Code</h3><p>Skip manual null checks:</p><pre>var user = GetUser();<br>user?.Profile = LoadProfile();</pre><p>This is cleaner and consistent, making your code safer and easier to read.</p><h3>The field Keyword: No More Backing Fields</h3><p>Use the new field keyword to reference auto-generated property backing fields, simplifying lazy initialisation:</p><pre>public class User<br>{<br>    public string FirstName<br>    {<br>        get =&gt; field ??= &quot;John&quot;;<br>        set =&gt; field = value;<br>    }<br>}</pre><p>Less boilerplate, more clarity. If you already have a property called field, you can use @field for example:</p><pre>public class User<br>{<br>    private string field;<br><br>    public string FirstName<br>    {<br>        get =&gt; @field ??= &quot;John&quot;;<br>        set =&gt; @field = value;<br>    }<br>}</pre><h3>Lambda Parameters With Modifiers: Shorter, Smarter Functions</h3><p>Modifiers like ref, out, and in work in lambda expressions. No full type needed:</p><pre>TryParse&lt;int&gt; parse = (text, out result) =&gt; int.TryParse(text, out result);</pre><h3>Partial Constructors &amp; Events: More Modular Code</h3><p>Split the constructor/event logic across files for better organisation and source generation:</p><pre>public partial class User<br>{<br>    public partial User(string name);<br>}<br>// ... implementation elsewhere</pre><h3>More Highlights</h3><ul><li>Implicit conversions between Span&lt;T&gt;, ReadOnlySpan&lt;T&gt;, and arrays for easier, safer memory handling.</li><li>nameof operator upgrades: supports generic types for better diagnostics and metaprogramming.</li><li>Custom compound assignments: define your own logic for operators like +=.</li><li>Full support in .NET 10 and modern toolchains.</li></ul><h3>Final Thoughts</h3><p>C# 14 makes extension members, null handling, and API design more elegant. With features that reduce boilerplate and add expressive power, the language is more intuitive than ever. Start exploring these updates and see all the details <a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f5e6c0f9b6a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building Secure Package Pipelines]]></title>
            <link>https://medium.com/@askpt/building-secure-package-pipelines-1b1f4bc2049f?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b1f4bc2049f</guid>
            <category><![CDATA[secrets]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Wed, 29 Oct 2025 13:42:35 GMT</pubDate>
            <atom:updated>2025-12-16T14:22:13.783Z</atom:updated>
            <content:encoded><![CDATA[<h4>Modern software supply chain security is now achievable. This post highlights OIDC authentication, SBOM generation, cryptographic attestations, multi-platform CI and more.</h4><figure><img alt="Stock image with a laptop and code" src="https://cdn-images-1.medium.com/max/1024/1*HP5zLOG27MjF9YuQVfDBkQ@2x.jpeg" /><figcaption>Photo by <a href="https://arnoldfrancisca.com">Arnold Francisca</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>The Legacy Approach: Secrets Everywhere</h3><p>Traditionally, publishing packages meant generating API tokens and storing them in GitHub as repository secrets. This approach comes with apparent drawbacks:</p><ul><li>Security risks: If a token leaks, attackers gain unfettered publish access.</li><li>Rotation headaches: Expiring tokens require manual maintenance.</li><li>Auditing gaps: It&#39;s hard to know which token was used and when.</li><li>Blast radius: One compromised token threatens all packages.</li></ul><h3>OIDC Authentication: No More Secrets</h3><p>We moved to OpenID Connect (OIDC) for credential-free authentication. Instead of storing secrets, GitHub Actions now generate a short-lived identity token at the time of publication. Our workflow grants only the permissions needed, with id-token: write for OIDC.</p><h4>Implementation Steps</h4><ul><li>Enable trusted publishing in your package registry (such as NuGet in our case).</li><li>Configure GitHub Actions to only run on merges to main.</li><li>Utilise environmental protection to require manual approvals for production.</li></ul><figure><img alt="Diagram on how OIDC works with GitHub actions" src="https://cdn-images-1.medium.com/max/1024/0*_55XX5VMNwNKk5qE" /><figcaption>Diagram explaining OIDC for GitHub actions</figcaption></figure><h4>Key Benefits</h4><ul><li>Tokens are meticulously designed for single-use applications, ensuring a clear and transparent association with each publication, which is linked directly to an identifiable workflow run.</li><li>This strategy significantly reduces the potential for security breaches, as each token is restricted to a single workflow. By implementing this method, we not only enhance the integrity of our processes but also strengthen our overarching security strategy, ensuring that every transaction and data flow remains protected and accountable. This approach creates a structured environment where risks are minimised, promoting trust and reliability in our operations.</li></ul><pre>name: Release<br>on:<br>  push:<br>    branches:<br>      - main<br>permissions:<br>  id-token: write      # For OIDC authentication<br>jobs:<br>  release:<br>    runs-on: ubuntu-latest<br>    environment:<br>      name: nuget-production<br>    steps:<br>        # Get a short-lived NuGet API key<br>      - name: NuGet login (OIDC → temp API key)<br>        uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544 # v1<br>        id: login<br>        with:<br>          user: ${{secrets.NUGET_USER}}<br><br>      - name: Publish to Nuget<br>        run: dotnet nuget push &quot;src/**/*.nupkg&quot; --api-key &quot;${{ steps.login.outputs.NUGET_API_KEY }}&quot; --source https://api.nuget.org/v3/index.json</pre><h3>SBOMs: Transparency by Default</h3><p>A Software Bill of Materials (SBOM) is like a nutrition label for your package: every dependency and version listed for all to see. It helps with CVE management, regulatory compliance, and trust.</p><p>We created a composite GitHub Action using <a href="https://cyclonedx.org/">CycloneDX</a>:</p><ul><li>Generates SBOMs for every package before distribution.</li><li>Attests authenticity using GitHub&#39;s action for keyless signing.</li><li>Uploads SBOMs as downloadable GitHub artefacts.</li></ul><p>Every published package gets:</p><ul><li>A machine-readable SBOM</li><li>A cryptographic attestation</li><li>Provenance consumers can audit (when they choose to)</li></ul><pre>name: &quot;Generate and Attest SBOM&quot;<br>description: &quot;Generate SBOM for a .NET project, upload it to a release, and create an attestation&quot;<br><br>inputs:<br>  github-token:<br>    description: &quot;GitHub token for uploading the SBOM to the release&quot;<br>    required: true<br>  project-name:<br>    description: &quot;Name of the project for SBOM generation&quot;<br>    required: true<br>  release-tag:<br>    description: &quot;Tag name for the release&quot;<br>    required: true<br><br>runs:<br>  using: &quot;composite&quot;<br>  steps:<br>    - name: Install CycloneDX.NET<br>      shell: bash<br>      run: dotnet tool install --global CycloneDX --version 5.2.0<br><br>    - name: Generate SBOM<br>      shell: bash<br>      run: |<br>        # Create artifacts/sboms directory if it doesn&#39;t exist<br>        mkdir -p ./artifacts/sboms/<br>        # Generate SBOM using CycloneDX<br>        dotnet CycloneDX --json --exclude-dev -sv &quot;${{ inputs.release-tag }}&quot; ./src/${{ inputs.project-name }}/${{ inputs.project-name }}.csproj --output ./artifacts/sboms/ -fn ${{ inputs.project-name }}.bom.json<br><br>    - name: Upload SBOM to release<br>      shell: bash<br>      env:<br>        GITHUB_TOKEN: ${{ inputs.github-token }}<br>      run: |<br>        gh release upload ${{ inputs.release-tag }} ./artifacts/sboms/${{ inputs.project-name }}.bom.json<br><br>    - name: Attest package<br>      uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3.0.0<br>      with:<br>        subject-path: src/**/${{ inputs.project-name }}.*.nupkg<br>        sbom-path: ./artifacts/sboms/${{ inputs.project-name }}.bom.json</pre><h3>Renovate: Keeping Dependencies Up To Date</h3><p>Outdated dependencies are a classic source of vulnerabilities. We use <a href="https://www.mend.io/renovate/">Renovate</a> to keep our packages, build tools, and GitHub Actions up to date automatically.</p><p>Renovate creates pull requests whenever:</p><ul><li>A new patch or minor version is released</li><li>Security advisories affect our dependencies</li><li>Key GitHub Actions have upstream updates</li></ul><p>We have scheduled upgrades for our production dependencies and enabled auto-merging for development tools. This approach keeps our SBOM up to date, reduces manual effort, and ensures that we can quickly address issues with upstream fixes. Reviewing Renovate pull requests (PRs) has become a regular part of our maintenance process, significantly enhancing our security.</p><blockquote>Note: <a href="https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide">GitHub’s Dependabot</a> can perform similar updates and is also a great option for automating dependency management. We chose Renovate for its advanced customization, broader ecosystem support, and detailed PR metadata.</blockquote><h3>CodeQL: Automating Static Analysis for Vulnerabilities</h3><p>Ensuring supply chain security involves identifying vulnerabilities before code is shipped. We utilise <a href="https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql">CodeQL</a>, GitHub&#39;s static analysis tool, to automatically scan all code changes for known patterns of insecure code, injection flaws, and risks from dependencies.</p><p>How it works:</p><ul><li>CodeQL runs on every PR, push to main and in a defined schedule</li><li>Scans .NET, YAML, and JavaScript code (where applicable)</li><li>Flags suspect code for review alongside test results</li></ul><p>CodeQL has identified risky constructs, code smells, and insecure usages that would be difficult to spot in a review alone. If a vulnerability is found, the CI fails, and we fix it before merging. This process builds confidence in every release.</p><h3>GitHub Actions Security: SHAs and Permission Scoping</h3><p>To use GitHub Actions safely, treat them like any other dependency by pinning them to commit SHA (short for &quot;hash&quot;) instead of tags.</p><p>Tags can be moved or overwritten. A compromised action repository could point v1 to malicious code tomorrow. By pinning to the full SHA, you ensure the exact code version you&#39;ve reviewed runs every time:</p><pre>- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332  # v4.1.7</pre><p>Renovate or Dependabot keeps these SHAs updated, and we can review changes in PRs before merging.</p><p>Permission scoping is just as critical. We explicitly declare minimal permissions for GITHUB_TOKEN at the workflow level:</p><pre>permissions:<br>  contents: read<br>  id-token: write<br>  actions: read</pre><p>Never grant blanket write-all permissions. Each workflow gets only what it needs. This limits the damage if a workflow or third-party action is compromised.</p><p>Why this matters:</p><ul><li>Prevents tag confusion attacks</li><li>Reduces the blast radius of compromised actions</li><li>Creates an audit trail for every action version change</li><li>Forces intentional permission grants</li></ul><p>These are small changes that dramatically reduce supply chain risk.</p><h3>Multi-Platform Testing: Matrix Builds</h3><p>Our SDK targets Linux, Windows, macOS, x64, ARM64, and multiple .NET versions. Complete coverage isn&#39;t practical (macOS and Windows runners are expensive), so we use matrix builds for the most critical combinations. This job catches:</p><ul><li>NativeAOT issues on ARM64</li><li>Platform-specific bugs and API behaviours</li><li>Version/dependency conflicts</li></ul><p>Artefacts and test results are kept separate for each configuration and uploaded to Codecov for coverage tracking.</p><h3>Build Attestations: Trust, Not Just Locks</h3><p>The <a href="https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations">build attestations</a> create a record which is cryptographically stored:</p><ul><li>Where artefacts were built</li><li>When (timestamp)</li><li>How (build commands, dependencies)</li><li>Who authorised them</li></ul><p>We use GitHub&#39;s own action in our workflows to generate these signatures. Anyone can verify using the GitHub CLI tool.</p><p>Honest Truth: Very few consumers verify build attestations today. We&#39;re laying the groundwork:</p><ul><li>It&#39;s easy (about 30 seconds extra per build)</li><li>Prepares us for future compliance</li><li>It&#39;s simply the right thing to do</li></ul><pre>- name: Generate artifact attestation<br>  uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0<br>  with:<br>    subject-path: &quot;src/**/*.nupkg&quot;</pre><h3>Automation: The Maintainer Experience</h3><p><a href="https://github.com/googleapis/release-please-action">Release Please</a> automates nearly everything:</p><ul><li>Analyses commits for semantic versioning</li><li>Generates release notes and change logs</li><li>Opens and merges release PRs</li><li>Publish packages when ready</li></ul><p>We enforce quality gates on every PR (commit formatting, DCO checks, scope validation), and use caching to reduce CI times.</p><h3>Conclusion</h3><p>As an open-source project, we faced a choice: implement supply chain security. The results:</p><ul><li>No secrets kept or rotated</li><li>Comprehensive CI/CD in GitHub&#39;s free tier</li><li>Audit trails and provenance established</li><li>Most work automated</li></ul><p>Security doesn&#39;t have to be complicated. With today&#39;s open source tools, it&#39;s mostly about persistence and learning.</p><p>You can find these techniques in the following repository: <a href="https://github.com/open-feature/dotnet-sdk">https://github.com/open-feature/dotnet-sdk</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b1f4bc2049f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[OpenFeature speaks F#]]></title>
            <link>https://medium.com/@askpt/openfeature-speaks-f-0052fffed92a?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/0052fffed92a</guid>
            <category><![CDATA[fsharp]]></category>
            <category><![CDATA[feature-flags]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[openfeature]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Tue, 22 Apr 2025 10:09:32 GMT</pubDate>
            <atom:updated>2025-12-16T14:20:30.680Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="OpenFeature Logo" src="https://cdn-images-1.medium.com/max/940/1*aVFrq7-0q7ng2_u2MNYHEg.png" /><figcaption>OpenFeature Logo</figcaption></figure><p>If you’re interested in standardizing and streamlining feature flag management across different tools and platforms, consider exploring the OpenFeature project. OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging. It allows you to integrate with your favourite feature flag management tool or in-house solutions without vendor lock-in. It supports many programming languages and makes switching providers or consolidating multiple solutions behind a single interface easy. To learn more about how OpenFeature can enhance your development workflow, visit the <a href="https://openfeature.dev">OpenFeature website</a>.</p><h3>Introduction​</h3><p>This walk-through teaches you the basics of using OpenFeature in .net within an ASP.NET Core Web application written in F#.</p><p>You’ll learn how to:</p><ul><li>Integrate the OpenFeature .net SDK</li><li>Install and configure the OpenFeature provider</li><li>Perform basic feature flagging</li></ul><figure><img alt="F# Example Code" src="https://cdn-images-1.medium.com/max/1024/0*LOC4nrbFbr65W8MT" /><figcaption>F# Example Code</figcaption></figure><h3>Requirements<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#requirements">​</a></h3><p>This walk-through assumes that:</p><ul><li>You have a basic knowledge of F# and .net</li><li>You have installed the <a href="https://dotnet.microsoft.com/en-us/">.net 8</a> or later SDK</li><li>You have Docker installed and running on the host system</li></ul><h3>Walk-through<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#walk-through">​</a></h3><h3>Step 1: Create a .net 8 Web application<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#step-1-create-a-net-8-web-application">​</a></h3><p>To get started, you can use the .net SDK to initialise a web application. Open a terminal (<strong>shell</strong>, <strong>Command Prompt</strong>, or <strong>bash</strong>) and paste the following commands:</p><pre>dotnet new webapi -o openfeature-fsharp-sample -lang F#<br>cd openfeature-fsharp-sample<br>dotnet run</pre><h3>Step 2: Add dependencies<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#step-2-add-dependencies">​</a></h3><p>You can install the latest OpenFeature and OpenFeature.Contrib.Providers.Flagd packages into your .net web application with NuGet.</p><pre>dotnet add package OpenFeature<br>dotnet add package OpenFeature.Contrib.Providers.Flagd</pre><h3>Step 3: Add code<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#step-3-add-code">​</a></h3><p>The following will initialise an FlagD provider for use within the web application. Open a code editor and add the F# code below to the Program.fs.</p><pre>namespace openfeature_fsharp_sample<br><br>#nowarn &quot;20&quot;<br><br>open System<br>open Microsoft.AspNetCore.Builder<br>open Microsoft.Extensions.DependencyInjection<br>open Microsoft.Extensions.Hosting<br>// Adding the namespaces here<br>open OpenFeature<br>open OpenFeature.Contrib.Providers.Flagd<br><br>module Program =<br>    let exitCode = 0<br><br>    [&lt;EntryPoint&gt;]<br>    let main args =<br><br>        let builder = WebApplication.CreateBuilder(args)<br><br>        builder.Services.AddControllers()<br><br>        let app = builder.Build()<br>        <br>        // Register your feature flag provider<br>        Api.Instance.SetProviderAsync(new FlagdProvider(new Uri(&quot;http://localhost:8013&quot;))).Wait()<br><br>        app.UseHttpsRedirection()<br><br>        app.UseAuthorization()<br>        app.MapControllers()<br><br>        app.Run()<br><br>        exitCode</pre><p>Now let&#39;s edit WeatherForecastController.fs to include the feature flag client call.</p><pre>namespace openfeature_fsharp_sample.Controllers<br><br>open System<br>open Microsoft.AspNetCore.Mvc<br>open Microsoft.Extensions.Logging<br>open openfeature_fsharp_sample<br>// Adding the namespaces here<br>open OpenFeature<br><br>[&lt;ApiController&gt;]<br>[&lt;Route(&quot;[controller]&quot;)&gt;]<br>type WeatherForecastController(logger: ILogger&lt;WeatherForecastController&gt;) =<br>    inherit ControllerBase()<br><br>    let summaries =<br>        [| &quot;Freezing&quot;<br>           &quot;Bracing&quot;<br>           &quot;Chilly&quot;<br>           &quot;Cool&quot;<br>           &quot;Mild&quot;<br>           &quot;Warm&quot;<br>           &quot;Balmy&quot;<br>           &quot;Hot&quot;<br>           &quot;Sweltering&quot;<br>           &quot;Scorching&quot; |]<br><br>    // Changing the get response<br>    [&lt;HttpGet&gt;]<br>    member _.Get() =<br>        async {<br>            // Geting the current instance of the client<br>            let client = Api.Instance.GetClient()<br>            <br>            // The flag is evaluated here<br>            let! flag =<br>                client.GetBooleanValueAsync(&quot;weather-forecast-return-5&quot;, false)<br>                |&gt; Async.AwaitTask<br><br>            let rng = Random()<br>            // if flag is true, we should return 5, otherwise 3<br>            let count = if flag then 5 else 3<br><br>            return<br>                [| for index in 0 .. (count - 1) -&gt;<br>                       { Date = DateTime.Now.AddDays(float index)<br>                         TemperatureC = rng.Next(-20, 55)<br>                         Summary = summaries.[rng.Next(summaries.Length)] } |]<br>        }</pre><h3>Step 4: Run the initial application<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#step-4-run-the-initial-application">​</a></h3><p>Let’s compile and run the application.</p><pre>dotnet build<br>dotnet run</pre><p>You should see a line in the logs with the following: Now listening on: http://localhost:5251, although the port number may differ. You can visit the following URL in your browser: http://localhost:5251/weatherforecast (adjust port number as necessary). You should see a list with 3 weather forecasts.</p><p>“Why am I seeing that value?”, you may ask. Well, it’s because a provider hasn’t been configured yet. Without a provider to actually evaluate flags, OpenFeature will return the default value. In the next step, you’ll learn how to add a provider.</p><h3>Step 5: Configure a provider (flagd)<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#step-5-configure-a-provider-flagd">​</a></h3><p>Providers are an important concept in OpenFeature because they are responsible for the flag evaluation. As we saw in the previous step, OpenFeature without a provider always returns the default value. If we want to actually perform feature flagging, we’ll need to register a provider.</p><p>Create a new file named flags.flagd.json and add the following JSON. Notice that there&#39;s a flag called weather-forecast-return-5 which matches the flag key referenced earlier. The weather-forecast-return-5 flag has on and off variants that return true and false respectively. The state property controls whether the feature flag is active or not. Finally, the defaultVariant property controls the variant that should be returned. In this case, the defaultVariant is off, the value false would be returned.</p><pre>{<br>  &quot;flags&quot;: {<br>    &quot;weather-forecast-return-5&quot;: {<br>      &quot;variants&quot;: {<br>        &quot;on&quot;: true,<br>        &quot;off&quot;: false<br>      },<br>      &quot;state&quot;: &quot;ENABLED&quot;,<br>      &quot;defaultVariant&quot;: &quot;off&quot;<br>    }<br>  }<br>}</pre><blockquote><em>NOTE: This configuration is specific for flagd and varies across providers.</em></blockquote><p>With the flagd configuration in place, start the flagd service with the following Docker command.</p><blockquote><em>NOTE: On Windows WSL is required both for running docker and to store the file. This is a limitation of Docker (</em><a href="https://github.com/docker/for-win/issues/8479"><em>https://github.com/docker/for-win/issues/8479</em></a><em>)</em></blockquote><pre>docker run -p 8013:8013 -v $(pwd)/:/etc/flagd/ -it ghcr.io/open-feature/flagd:latest start --uri file:/etc/flagd/flags.flagd.json</pre><h3>Step 6: Change the flags (flagd)<a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet#step-6-rerun-the-application">​</a></h3><p>Let’s change the feature flag in our flags.flagd.json, making defaultVariant to on</p><pre>{<br>  &quot;flags&quot;: {<br>    &quot;welcome-message&quot;: {<br>      &quot;variants&quot;: {<br>        &quot;on&quot;: true,<br>        &quot;off&quot;: false<br>      },<br>      &quot;state&quot;: &quot;ENABLED&quot;,<br>      &quot;defaultVariant&quot;: &quot;on&quot;<br>    }<br>  }<br>}</pre><p>Revisit the endpoint <a href="http://localhost:5251/weatherforecast">http://localhost:5251/weatherforecast</a>. You will see a list with 5 weather forecasts.</p><h3>References</h3><ul><li><a href="https://openfeature.dev/docs/tutorials/getting-started/dotnet">https://openfeature.dev/docs/tutorials/getting-started/dotnet</a></li><li><a href="https://openfeature.dev/">https://openfeature.dev/</a></li><li><a href="https://flagd.dev/">https://flagd.dev/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0052fffed92a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why OpenFeature chose environments to store publishing secrets]]></title>
            <link>https://medium.com/@askpt/why-openfeature-chose-environments-to-store-publishing-secrets-80eb6b3586b3?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/80eb6b3586b3</guid>
            <category><![CDATA[openfeature]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[ci-cd-pipeline]]></category>
            <category><![CDATA[publishing]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Thu, 17 Apr 2025 14:39:51 GMT</pubDate>
            <atom:updated>2025-04-17T14:39:51.177Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*aVFrq7-0q7ng2_u2MNYHEg.png" /><figcaption>OpenFeature Logo</figcaption></figure><p>If you’re interested in standardizing and streamlining feature flag management across different tools and platforms, consider exploring the OpenFeature project. OpenFeature is an open specification that provides a vendor-agnostic, community-driven API for feature flagging. It allows you to integrate with your favourite feature flag management tool or in-house solutions without vendor lock-in. It supports many programming languages and makes switching providers or consolidating multiple solutions behind a single interface easy. To learn more about how OpenFeature can enhance your development workflow, visit the <a href="https://openfeature.dev">OpenFeature website</a>.</p><p>Managing secrets securely in CI/CD pipelines is a critical challenge, especially for organizations with multiple repositories, complex permission structures, and a need to balance developer productivity with robust security. Here&#39;s why we decided to use environments to store our secrets:</p><h4>Scale: Many Repositories Publishing Libraries</h4><p>With many repositories, each potentially publishing library, the risk of secret sprawl and accidental exposure increases. Centralizing secrets in environments allows us to:</p><ul><li>Avoid duplicating secrets across repositories, reducing the attack surface and simplifying management</li><li>Enforce consistent security policies and access controls across all projects</li></ul><h4>Permission Model: Approvers vs. Maintainers</h4><p>Our workflow distinguishes between <em>approvers</em> and <em>maintainers</em>:</p><ul><li>Both roles have write permissions, but maintainers have higher trust.</li><li>Only maintainers can merge main due to CODEOWNERS and branch protection rules, giving them control over what gets published.</li></ul><p>However, approvers can create branches and propose changes, including workflow modifications. This behaviour creates a risk: if secrets are accessible in all branches, an approver could (intentionally or accidentally) exfiltrate secrets or trigger unauthorized publishing by modifying workflows.</p><h4>Limiting Secret Exposure: Main Branch-Only Access</h4><p>By configuring secrets to be accessible only in workflows running on the main branch (using environment protection rules), we achieve several security benefits:</p><ul><li>Prevents Exfiltration: Secrets are unavailable to workflows running on a feature or non-main branches. So, even if an approver creates a branch with a malicious workflow, it cannot access the secrets.</li><li>Stops Unauthorized Publishing: Only code that has passed through the required review and merge process (i.e., merged to main by a maintainer) can access the secrets needed for publishing and closing a significant attack vector.</li><li>Implements Least Privilege: Secrets are only available where necessary, following the principle of least privilege and reducing the risk of accidental or malicious misuse.</li></ul><h4>Real-World Precedents and Risks</h4><p>Several high-profile breaches have demonstrated the dangers of insufficient secret management in CI/CD:</p><ul><li>The Codecov breach allowed attackers to exfiltrate secrets from environment variables in thousands of build pipelines.</li><li>Overly permissive access controls have led to attackers publishing malicious code or stealing credentials from CI/CD systems.</li></ul><p>We directly address these risks by restricting secret access to protected environments and mainline workflows.</p><h3>Our Approach</h3><p>We decided to leverage the GitHub environments feature to store our publishing secrets. This way, only the pipeline that was triggered in main is used.</p><h4>Creating a new environment</h4><p>To create a new environment on GitHub, navigate to your repository&#39;s Settings and select Environments from the sidebar. Click New Environment, provide a name (we used &quot;publish&quot;), and proceed to configure it. Once the environment is created, you can add secrets by selecting it, clicking Add secret, entering a name and value for the secret (for example, NUGET_TOKEN), and saving it. These secrets will only be accessible to workflows that reference this environment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/821/1*Dcc0dBXIRIQt2d6QsAGhkw.png" /><figcaption>Configuration screen for Environments in GitHub</figcaption></figure><p>To restrict deployments to a specific branch, such as main, edit the environment&#39;s protection rules. Under the environment settings, find the Deployment branches section and choose Selected branches. Add main (or your desired branch) to ensure only workflows triggered from this branch can access the environment and its secrets.</p><h4>Configure the GitHub Action</h4><p>In your GitHub Actions workflow file, you must specify the environment for the relevant job by adding the environment key. For example:</p><pre>jobs:<br> deploy:<br> runs-on: ubuntu-latest<br> environment: publish<br> steps:<br> # your deployment steps here<br></pre><p>This change ensures that the job only runs with access to <br>the environment’s secrets if triggered from the allowed branch. <br>As shown above, you must update your workflow YAML to include the environment field for the deployment job.</p><h4>References</h4><ul><li><a href="https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment">https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment</a></li><li><a href="https://github.com/open-feature/dotnet-sdk/pull/431">https://github.com/open-feature/dotnet-sdk/pull/431</a></li><li><a href="https://openfeature.dev/">https://openfeature.dev/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=80eb6b3586b3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Props file and dependabot behaviour]]></title>
            <link>https://medium.com/@askpt/props-file-and-dependabot-behaviour-430dcecd5304?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/430dcecd5304</guid>
            <category><![CDATA[productivity]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[dependabot]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Wed, 16 Apr 2025 14:25:37 GMT</pubDate>
            <atom:updated>2025-12-16T14:23:37.874Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RfANTCEOuOxeJaN3" /><figcaption>Dependabot icon</figcaption></figure><p>The Directory.Build.props and Directory.Packages.props are files allowing the developer to share configurations and variables between different dotnet projects in the same repository. This makes enforcing the projects following the same rules, such as the Language version or even the dotnet target framework, easier. Please note that the file Directory.Build.props must be in the repository’s root folder or the solution to all children’s projects that need to be affected. <br>Let’s see an example of this Directory.Build.props file.</p><h4><em>Directory.Build.props</em></h4><pre>&lt;Project&gt;<br>    &lt;!-- Shared Settings --&gt;<br>    &lt;PropertyGroup&gt;<br>        &lt;TreatWarningsAsErrors&gt;true&lt;/TreatWarningsAsErrors&gt;<br>        &lt;LangVersion&gt;latest&lt;/LangVersion&gt;<br>        &lt;Nullable&gt;enable&lt;/Nullable&gt;<br>        &lt;ImplicitUsings&gt;enable&lt;/ImplicitUsings&gt;<br>    &lt;/PropertyGroup&gt;<br>    &lt;!-- Versions --&gt;<br>    &lt;PropertyGroup&gt;<br>        &lt;AspNetCoreVersion&gt;5.0.0&lt;/AspNetCoreVersion&gt;<br>        &lt;EntityFrameworkVersion&gt;5.0.2&lt;/EntityFrameworkVersion&gt;<br>    &lt;/PropertyGroup&gt;<br>&lt;/Project&gt;</pre><h4>Project.csproj</h4><pre>&lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&gt;<br><br>    &lt;!-- Specific project properties that override the properties present on Directory.Build.props --&gt;<br>    &lt;PropertyGroup&gt;<br>        &lt;TargetFramework&gt;net5.0&lt;/TargetFramework&gt;<br>        &lt;LangVersion&gt;latest&lt;/LangVersion&gt;<br>    &lt;/PropertyGroup&gt;<br><br>    &lt;!-- Nuget packages using the version from Directory.Build.props --&gt;<br>    &lt;ItemGroup&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Components.WebAssembly.Server&quot; Version=&quot;$(AspNetCoreVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Mvc.NewtonsoftJson&quot; Version=&quot;$(AspNetCoreVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore&quot; Version=&quot;$(AspNetCoreVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Identity.EntityFrameworkCore&quot; Version=&quot;$(AspNetCoreVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Identity.UI&quot; Version=&quot;$(AspNetCoreVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.AspNetCore.ApiAuthorization.IdentityServer&quot; Version=&quot;$(AspNetCoreVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.EntityFrameworkCore.Sqlite&quot; Version=&quot;$(EntityFrameworkVersion)&quot; /&gt;<br>        &lt;PackageReference Include=&quot;Microsoft.EntityFrameworkCore.Tools&quot; Version=&quot;$(EntityFrameworkVersion)&quot; /&gt;<br>    &lt;/ItemGroup&gt;<br><br>    &lt;!-- Other properties here --&gt;<br><br>&lt;/Project&gt;</pre><h4>Using it as a versioning system for NuGet dependencies</h4><p>As shown in the top two files, we are creating a variable named AspNetCoreVersion in the Directory.Build.props. Using that, we can ensure that all the packages that use that variable as a version number will use the same version. We can prevent multiple updates and mismatch versions by only controlling one variable.</p><h4>Integration with dependabot</h4><p>Integrating this way of versioning the dependencies works well with dependabot. The bot will give us a list of affected packages that will affect the change of the version. For example, in the example above, the bot would create a new pull request for EntityFrameworkVersion stating that it is going to upgrade from 1.0.0 to 1.0.1, affecting the packages Microsoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore.Tools. <br>View the <a href="https://github.com/askpt/blazzing-pizza-workshop/pull/28">Pull Request</a> for more information and the screenshot below for details:</p><figure><img alt="Pull request with dependabot details" src="https://cdn-images-1.medium.com/max/922/1*L6Ud3adAvKP_lVyxd07ENQ.png" /></figure><h4>References</h4><ul><li><a href="https://docs.github.com/en/code-security/dependabot">https://docs.github.com/en/code-security/dependabot</a></li><li><a href="https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022">https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022</a></li><li><a href="https://github.com/askpt/blazzing-pizza-workshop">https://github.com/askpt/blazzing-pizza-workshop</a></li></ul><h4><strong>Samples</strong></h4><p><a href="https://github.com/askpt/blazzing-pizza-workshop">GitHub - askpt/blazzing-pizza-workshop: Blazor WebAssembly Workshop</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=430dcecd5304" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cancellation Tokens in F#]]></title>
            <link>https://medium.com/@askpt/cancellation-tokens-in-f-4f05673897af?source=rss-1744c61bb207------2</link>
            <guid isPermaLink="false">https://medium.com/p/4f05673897af</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[fsharp]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[asynchronous]]></category>
            <dc:creator><![CDATA[André Silva]]></dc:creator>
            <pubDate>Sun, 13 Apr 2025 17:41:48 GMT</pubDate>
            <atom:updated>2025-12-16T14:22:53.544Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/288/0*jr0FhY8nKZdyUj9v" /><figcaption>fsharp logo</figcaption></figure><p>This post targets the Async library. The Task library was introduced in the newer versions of F# (dotnet 6.0+). In this article, I’m going to focus only on the Asynclibrary.</p><h3>Basic usage of Cancellation Tokens in F#</h3><p>To illustrate how cancellation tokens are used, we can isolate two scenarios: asynchronous functions chained together where cancellation tokens are propagated through the execution chain and asynchronous functions executed independently, without further chained asynchronous calls. Let us focus on the second scenario first. <br>By specifying the timeout in the constructor of CancellationTokenSource, we request that the task be cancelled after 200 milliseconds. In this example, we make a simple console print to show that the task has been cancelled. <br>Please note that in this example, the cancellation token is not explicitly passed to the Async.Sleep function, so we don’t need to check if the token has been cancelled in the following examples.</p><pre>open System.Threading<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        while (true) do<br>            printfn &quot;Waiting&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 59231349<br>Starting<br>Cancellation token: 59231349<br>Waiting<br>Waiting<br>Cancelled</pre><h3>Complex usage of Cancellation Tokens in F#</h3><p>For this scenario, we want to show how a cancellation token is shared from the parent task to the child tasks using the Async module functions to start new tasks. <br>This section will describe and explain cancellation tokens in functions such as Async.StartChild or Async.Start.</p><h4>Code Examples</h4><p>This section will include a few code examples with selected Async functions that help us execute/start asynchronous expressions. <br>We are using the Async.OnCancel function to show the user that the task was properly cancelled. As stated in the previous section, we are not checking if we should cancel the tasks since it’s done by the function Async.Sleep. This happens simply because the F# runtime does that job for us and makes all the cancellations it needs, except in cases where we share the cancellation tokens with the child tasks.</p><h4>do! / let! / return! / use!</h4><p>This is the easiest way to start a new task in F#. In this example, the do! makes sure the Cancellation Token is shared with all child tasks. This means that when the token is cancelled, the tasks should be cancelled, as proved by the hash code printed in the output.</p><pre>open System.Threading<br><br>let taskChild() : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting Child&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token child: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled Task Child&quot;)<br><br>        while (true) do<br>            printfn &quot;Waiting Child&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        do! taskChild()<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 3139257<br>Starting<br>Cancellation token: 3139257<br>Starting Child<br>Cancellation token child: 3139257<br>Waiting Child<br>Waiting Child<br>Cancelled Task Child<br>Cancelled</pre><h4>Async.StartChild</h4><p>This starts a child computation within an asynchronous workflow. This allows multiple asynchronous computations to be executed simultaneously. <br>In this example, the cancellation token is not shared, but internally, when a task is started as a child, the child cancellation token is linked to the parent cancellation token. That’s why we have different hash codes for the parent and child tasks, but both get cancelled.</p><pre>open System.Threading<br><br>let taskChild() : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting Child&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token child: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled Task Child&quot;)<br><br>        while (true) do<br>            printfn &quot;Waiting Child&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        let! completor = taskChild () |&gt; Async.StartChild<br>        let! result = completor<br><br>        return result<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 52169655<br>Starting<br>Cancellation token: 52169655<br>Starting Child<br>Cancellation token child: 6634750<br>Waiting Child<br>Waiting Child<br>Cancelled Task Child<br>Cancelled</pre><h4>Async.Start</h4><p>Starts the asynchronous computation in the thread pool. In this example, the cancellation token is not propagated to the child task since we are using the Start function to trigger the task initialization. This way of kicking off a task might be useful for work where we want a fire-and-forget behaviour since the child’s task is never cancelled. <br>This function also accepts a cancellation token as an argument. When a token is shared, the child task is cancelled.</p><pre>open System.Threading<br><br>let taskChild() : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting Child&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token child: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled Task Child&quot;)<br><br>        while (true) do<br>            printfn &quot;Waiting Child&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        Async.Start(taskChild ())<br><br>        do! Async.Sleep(1000)<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 24709875<br>Starting<br>Cancellation token: 24709875<br>Starting Child<br>Cancellation token child: 40392858<br>Waiting Child<br>Waiting Child<br>Cancelled<br>Waiting Child<br>Waiting Child<br>Waiting Child</pre><h4>Async.StartImmediate</h4><p>Runs an asynchronous computation, starting immediately on the current operating system thread. This is similar to the Async.Startfunction we saw earlier. The same rules regarding cancellation tokens apply to this function as well.</p><pre>open System.Threading<br><br>let taskChild() : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting Child&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token child: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled Task Child&quot;)<br><br>        while (true) do<br>            printfn &quot;Waiting Child&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        Async.StartImmediate(taskChild ())<br><br>        do! Async.Sleep(1000)<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 59545942<br>Starting<br>Cancellation token: 59545942<br>Starting Child<br>Cancellation token child: 40392858<br>Waiting Child<br>Waiting Child<br>Cancelled<br>Waiting Child<br>Waiting Child<br>Waiting Child</pre><h4>Async.RunSynchronously</h4><p>This function gets a task by argument and runs it synchronously. It also exposes a cancellation token passed as an argument, which will be used as the cancellation token for the child tasks.</p><pre>open System.Threading<br><br>let taskChild() : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting Child&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token child: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled Task Child&quot;)<br><br>        while (true) do<br>            printfn &quot;Waiting Child&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        taskChild () |&gt; Async.RunSynchronously<br><br>        printfn &quot;Line after RunSynchronously&quot;<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 12025920<br>Starting<br>Cancellation token: 12025920<br>Starting Child<br>Cancellation token child: 40392858<br>Waiting Child<br>Waiting Child<br>Cancelled<br>Waiting Child<br>Waiting Child<br>Waiting Child</pre><h4>Working with multiple sub-tasks</h4><p>Interestingly, the tasks respect the order in which they have been called to be cancelled. In this example, the Grand Child task is the first to be cancelled, whereas the first one called was the last one to be cancelled. As expected, the Grand Child 2 task is never called. <br>Also, please note that task 2 is never called since task 1 never finishes the execution.</p><pre>open System.Threading<br><br>let taskGrandchild(order: int) : Async&lt;unit&gt; =<br>    async {<br>        printfn $&quot;Starting Grandchild({order})&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token GrandChild({order}): {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn $&quot;Cancelled Task Grandchild: {order}&quot;)<br><br>        while (true) do<br>            printfn $&quot;Waiting Grandchild({order})&quot;<br><br>            do! Async.Sleep(100)<br>    }<br><br>let taskChild() : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting Child&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token child: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled Task Child&quot;)<br><br>        while (true) do<br>            do! taskGrandchild(1)<br>            do! taskGrandchild(2)<br>    }<br><br>let task () : Async&lt;unit&gt; =<br>    async {<br>        printfn &quot;Starting&quot;<br><br>        let! ct = Async.CancellationToken<br>        printfn $&quot;Cancellation token: {ct.GetHashCode()}&quot;<br><br>        use! c = Async.OnCancel(fun () -&gt; printfn &quot;Cancelled&quot;)<br><br>        do! taskChild()<br>    }<br><br>let cts = new CancellationTokenSource(200)<br>let ct = cts.Token<br>printfn $&quot;Cancellation token main: {ct.GetHashCode()}&quot;<br>Async.Start(task (), cts.Token)<br>Async.Sleep 500 |&gt; Async.RunSynchronously</pre><pre>Cancellation token main: 26465690<br>Starting<br>Cancellation token: 26465690<br>Starting Child<br>Cancellation token child: 26465690<br>Starting Grandchild(1)<br>Cancellation token GrandChild(1): 26465690<br>Waiting Grandchild(1)<br>Waiting Grandchild(1)<br>Cancelled Task Grandchild: 1<br>Cancelled Task Child<br>Cancelled</pre><h4>Comparison Table from the Code Examples</h4><p>In the table below, we compare the different calls and behaviours.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fdatawrapper.dwcdn.net%2FkwerF%2F1%2F&amp;display_name=Datawrapper&amp;url=https%3A%2F%2Fdatawrapper.dwcdn.net%2FkwerF%2F1%2F&amp;image=https%3A%2F%2Fdatawrapper.dwcdn.net%2FkwerF%2Fplain-s.png%3Fv%3D1&amp;type=text%2Fhtml&amp;schema=dwcdn" width="600" height="334" frameborder="0" scrolling="no"><a href="https://medium.com/media/1e8e954043d4ebd2db3bf28949110d67/href">https://medium.com/media/1e8e954043d4ebd2db3bf28949110d67/href</a></iframe><p>Examples: <br>a) Compute a value asynchronously <br>b) Save transactions in the database <br>c) Send an event in an Event-Driven system <br>d) Create a log in the logging system <br>e) Retrieve or post information into an API that results from what we need later</p><h3>References</h3><ul><li><a href="https://fsharpforfunandprofit.com/posts/concurrency-async-and-parallel/#cancelling-workflows">Asynchronous programming | F# for Fun and Profit</a></li><li><a href="https://tysonwilliams.coding.blog/2020-12-01_csharp_task_to_fsharp_async">Converting asynchronous cancellation from C# to F# | Tyson Williams</a></li><li><a href="https://hub.packtpub.com/asynchronous-programming-f/">Asynchronous Programming in F# | Packt</a></li><li><a href="https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/async">Async programming in F# | Microsoft Docs</a></li><li><a href="https://github.com/fsharp/fsharp/blob/577d06b9ec7192a6adafefd09ade0ed10b13897d/src/fsharp/FSharp.Core/control.fs">control.fs | GitHub</a></li><li><a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/async-padl-revised-v2.pdf">The F# Asynchronous Programming Model</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4f05673897af" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>