{"slug": "configuring-contextual-options-with-microsoft-extensions-options-contextual", "title": "Configuring contextual options with Microsoft.Extensions.Options.Contextual", "summary": "The article introduces the `Microsoft.Extensions.Options.Contextual` NuGet package, which provides APIs for dynamically configuring options objects based on a provided context (such as user location or country). It explains how to install the package and use the `IContextualOptions<TOptions, TContext>` interface to retrieve configured options at runtime, contrasting this approach with traditional global or named options configuration. The author explores the package's purpose and setup, ultimately leaving it to readers to decide if the library is worth adopting.", "body_md": "In this post I take a brief look at the *Microsoft.Extensions.Options.Contextual* package that I came across the other day. I try to understand the purpose of the package, look at how to install and configure it, and finally consider whether it's something people should consider using themselves.\n\n[What is Microsoft.Extensions.Options.Contextual?](#what-is-microsoft-extensions-options-contextual-)\n\nI was browsing around in [the dotnet repositories](https://github.com/dotnet) for something the other day, when [I spotted this library: Microsoft.Extensions.Options.Contextual](https://github.com/dotnet/extensions/tree/main/src/Libraries/Microsoft.Extensions.Options.Contextual). I was somewhat intrigued considering I hadn't heard this library mentioned before, or had seen it used. What's more, a little probing seemed to suggest it's not used by any *other* first party .NET libraries either. So what is it for, and how does it work?\n\n[According to the library itself](https://github.com/dotnet/extensions/tree/4e8e9a258918dacf17a5dd033f9a01a23fd6692d/src/Libraries/Microsoft.Extensions.Options.Contextual), the library provides:\n\nAPIs for dynamically configuring options based on a given context\n\nThat's pretty vague 😅 What are \"options\" and what is \"context\" here? It's likely easiest to understand with an example, even if it's a simple one. The example below is based on [the documentation in the package](https://github.com/dotnet/extensions/tree/main/src/Libraries/Microsoft.Extensions.Options.Contextual), using the classic \"weather forecast\" scenario.\n\n[Configuring options based on another type](#configuring-options-based-on-another-type)\n\nFirst of all, let's imagine we have some configuration options:\n\n```\ninternal class WeatherForecastOptions\n{\n    public string TemperatureScale { get; set; } = \"Celsius\"; // Celsius or Fahrenheit\n    public int ForecastDays { get; set; }\n}\n```\n\nThis is pretty standard stuff—which unit to use for temperature display, and how many days to include in the forecast.\n\nThe interesting thing is *how* these options are configured. If you're doing global configuration for your app then you might have something like this (using `Configure()`\n\nand/or `AddOptions()`\n\n):\n\n``` js\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services\n    .AddOptions<WeatherForecastOptions>() // Register the options\n    .Configure(x => x.ForecastDays = 7) // Configure in Code\n    .BindConfiguration(builder.Configuration[\"Weather\"]); // Bind to configuration\n```\n\nThat's all well and good, but you're fundamentally \"globally\" configuring the `WeatherForecastOptions`\n\ninstances (whether you're using singleton, transient, or scoped instances).\n\nI'm not going to get into the general\n\n`IOptions<>`\n\nabstraction debate here. Many people rail against it, and I sympathize. That said, if you don't likethoseabstractions, I don't know that you'll like the ones we're introducing shortly 😉.\n\nWhat if, instead, you want to configure an options object arbitrarily based on some context object? For example, imagine you want to change the `TemperatureScale`\n\nbased on the country associated with the current user. This \"context\" might be encapsulated in some `Appcontext`\n\nobject:\n\n```\ninternal class AppContext\n{\n    public Guid UserId { get; set; }\n    public string? Country { get; set; }\n}\n```\n\nNote that this is a different use case to\n\n[where you have a distinct named set of \"global\" options. For]named optionscontextualoptions we explicitly configure the app based on the provided context.\n\nFor example, at the call site, contextual options would look something like this:\n\n```\n// Get an IContextualOptions<TOptions, TContext> from DI\nIContextualOptions<WeatherForecastOptions, AppContext> _contextualOptions;\n\nAppContext context; // Get an instance of the context object from somewhere\n\n// Get an instance of WeatherForecastOptions using the AppContext instance\nWeatherForecastOptions options = await _contextualOptions.GetAsync(context, cancellationToken: default);\n```\n\nThe `IContextualOptions<>`\n\ninstance is available in DI when you set up contextual options, and the `AppContext`\n\nis whatever you want to use to control *how* your options object is created. You then pass one to the other, and voila, you have a configured contextual options object, with properties set based on `AppContext`\n\n.\n\nOf course, there's a lot more to it behind the scenes, so we'll look at how to get started with this in the next section.\n\n[Adding the ](#adding-the-microsoft-extensions-options-contextual-package)*Microsoft.Extensions.Options.Contextual* package\n\n*Microsoft.Extensions.Options.Contextual*package\n\nTo get started with contextual option, you need to add the *Microsoft.Extensions.Options.Contextual* package to your project. Interestingly, it appears that this package has never made it out of preview:\n\nThis means you must use the `--prerelease`\n\nflag when adding the package to your project with the .NET CLI:\n\n```\ndotnet add package Microsoft.Extensions.Options.Contextual --prerelease\n```\n\nThis adds the latest version to your project file:\n\n```\n<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <Nullable>enable</Nullable>\n    <ImplicitUsings>enable</ImplicitUsings>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <!-- Add this 👇 -->\n    <PackageReference Include=\"Microsoft.Extensions.Options.Contextual\" Version=\"10.4.0-preview.1.26160.2\" />\n  </ItemGroup>\n</Project>\n```\n\nThe latest version of the package supports .NET Framework and .NET 8+.\n\nOnce you have the package installed, you can start configuring your options and context.\n\n[Configuring the contextual options](#configuring-the-contextual-options)\n\nOnce we have the package installed, we need to configure all the moving parts:\n\n- Add\n`[OptionsContext]`\n\nto your context object, to drive a source generator - Define an\n`IOptionsContextReceiver`\n\nobject for extracting value from the context - Define how to take the extracted context values and apply them to your options object\n\nIt likely isn't clear why you need all these components at this points, but just go with it for now. It'll become clearer how they all fit together later.\n\n[Adding the ](#adding-the-contextualoptionsgenerator)`ContextualOptionsGenerator`\n\n`ContextualOptionsGenerator`\n\nThe first step is to mark your context object `partial`\n\nand add the `[OptionsContext]`\n\nattribute:\n\n```\n[OptionsContext] // 👈 Add attribute, and make the type partial\ninternal partial class AppContext\n{\n    public Guid UserId { get; set; }\n    public string? Country { get; set; }\n}\n```\n\nI've used a mutable class for\n\n`AppContext`\n\nin the example above, but you could also make it a`struct`\n\n, and (ideally) make it`readonly`\n\ntoo.\n\nThe `[OptionsContext]`\n\nobject drives a source generator, the `ContextualOptionsGenerator`\n\n, which generates a separate partial class, which looks a little like this:\n\n```\n// <auto-generated/>\nusing Microsoft.Extensions.Options.Contextual;\n\npartial class MyAppContext : IOptionsContext\n{\n    void IOptionsContext.PopulateReceiver<T>(T receiver)\n    {\n        receiver.Receive(nameof(Country), Country);\n        receiver.Receive(nameof(UserId), UserId);\n    }\n}\n```\n\nThis makes your context type implement `IOptionsContext`\n\n:\n\n```\npublic interface IOptionsContext\n{\n    void PopulateReceiver<T>(T receiver)\n        where T : IOptionsContextReceiver;\n}\n```\n\nwhich interacts with:\n\n```\npublic interface IOptionsContextReceiver\n{\n    void Receive<T>(string key, T value);\n}\n```\n\nO…K… so what has that got us? 😅 Effectively the source generator provides a way to extract properties from a type by their name, and pass them to a \"receiver\" type. So now we'll implement that receiver.\n\n[Implementing an ](#implementing-an-ioptionscontextreceiver)`IOptionsContextReceiver`\n\n`IOptionsContextReceiver`\n\nImplementing an `IOptionsContextReceiver`\n\nfor your target is pretty simple; just create a type that implements the interface, and which can receive any type of `T`\n\nvalue. as you saw above, this receiver will be invoked with each of the properties of the \"context\" object, so the idea is to simply extract the keys that you care about in your context.\n\nIn the following example, we only care about the `\"Country\"`\n\nkey, so that's the value we extract. I've done a little further manipulation of the value in this example, converting the `\"Country\"`\n\ninto an assumed default temperature unit:\n\n```\n// Implement IOptionsContextReceiver to receive the context values\ninternal class CountryTemperatureReceiver : IOptionsContextReceiver\n{\n    // This property exposes the \"extracted\" values to others\n    public string? DefaultUnit { get; private set; }\n\n    // The receive implementation could receive a T of any type\n    public void Receive<T>(string key, T value)\n    {\n        // When you receive the key you're looking for...\n        if (key == \"Country\")\n        {\n            // ... extract the value and store it\n            DefaultUnit = value is \"USA\" ? \"Fahrenheit\" : \"Celsius\";\n        }\n    }\n}\n```\n\nOk, we're almost there, we have:\n\n- Our \"context\" object\n- A source generated\n`IOptionsContext`\n\nwhich populates a \"receiver\" based on the context object - Our \"receiver\" object\n- Our \"target\" options that we want to configure\n\nNow it's time to put it all together\n\n[Configuring the ](#configuring-the-weatherforecastoptions)`WeatherForecastOptions`\n\n`WeatherForecastOptions`\n\nWe've almost got all the basic pieces, all that remains is to set up the configuration of our options object, `WeatherForecastOptions`\n\n. We can do this partially using \"normal\" options configuration, by providing lambdas or binding to configuration, and partially using our new \"contextual\" configuration options which serves as a contextual \"loader\":\n\n``` js\n// Create a standard .NET app builder (in this case a web app)\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services\n    .Configure<WeatherForecastOptions>(x => x.ForecastDays = 7) // Configure in Code\n    .Configure((IOptionsContext ctx, WeatherForecastOptions opts) => // Configure contextual options\n    {\n        // 1. Create an instance of the receiver \n        var receiver = new CountryTemperatureReceiver();\n\n        // 2. Populate the receiver based on the context\n        ctx.PopulateReceiver(receiver);\n\n        // 3. Update the options based on the receiver's values\n        if (receiver.DefaultUnit is not null)\n        {\n            opts.TemperatureScale = receiver.DefaultUnit;\n        }\n    });\n```\n\nWith the code above we've now configured an `WeatherForecastOptions`\n\nobject, based on some unknown `IOptionsContext`\n\n, via the `CountryTemperatureReceiver`\n\n. This code is invoked as needs be, using the provided `IOptionsContext`\n\ninstance, by the `IContextualOptions`\n\nimplementation.\n\n[Retrieving a contextual ](#retrieving-a-contextual-weatherforecastoptions-object)`WeatherForecastOptions`\n\nobject\n\n`WeatherForecastOptions`\n\nobjectFinally, we have all the pieces in place. All that remains is to use the `IContextualOptions`\n\nobject, as we saw earlier:\n\n```\n// Get an IContextualOptions<TOptions, TContext> from DI\nIContextualOptions<WeatherForecastOptions, AppContext> _contextualOptions;\n\nAppContext context; // Get an instance of the context object from somewhere\n\n// Get an instance of WeatherForecastOptions using the AppContext instance\nWeatherForecastOptions options = await _contextualOptions.GetAsync(context, cancellationToken: default);\n```\n\nWhen invoked, the `IContextualOptions<>`\n\nobject loads a \"globally\" configured `IOptions<WeatherForecastOptions>`\n\ninstance, and then calls our \"loader\" function. This uses the `IOptionsContext`\n\nto pass values to our `IOptionsReceiver`\n\nobject, and then we use *those* values to configure the `WeatherForecastOptions`\n\n. Finally, the configured object is returned, configured both via the standard `IOptions`\n\nsystem and by contextual options.\n\n[So, what's this for again?](#so-what-s-this-for-again-)\n\nSo, if you're anything like me, you might reading this and thinking…Why? Why go to all that length?\n\nOn first blush it looks a *little* bit like \"loose coupling\". *Sure*, you *could* set the default `WeatherForecastOptions`\n\nbased on the properties of the `AppContext`\n\n*directly*, but by using the receiver, you're now \"loosely coupled\".\n\nExcept, you're not, really, are you?\n\nIf you had code like this, then it would be very clear that you're directly coupled:\n\n```\nWeatherForecastOptions opts = new();\nAppContext ctx = new();\n\n// Configure the options directly based on the AppContext\nopts.TemperatureScale = ctx.Country is \"USA\" ? \"Fahrenheit\" : \"Celsius\";\n```\n\nBut to my eyes, introducing the receiver doesn't *really* reduce that coupling. It just means that you're now coupled indirectly via the magic string `\"Country\"`\n\n. If you rename the `Country`\n\nproperty, then suddenly this breaks.\n\nAll of which makes me wonder, why bother? Is all this infrastructure *really* necessary to provide another way to configure an `IOptions`\n\nobject? What was the thinking here?\n\n[Feature flags, theoretically](#feature-flags-theoretically)\n\nLuckily, after playing around for a while, I [found an API proposal for introducing these contextual options](https://github.com/dotnet/extensions/issues/5049). But the funny thing was, the API proposal was closed as \"not planned\" with the API \"needs work\" 😅 So… what happened here?!\n\nThe proposed API and API usage given in the issue are pretty much exactly what is shipped and currently available, and is pretty much the only example around, i.e. the classic `WeatherForecast`\n\nexample.\n\nWhat is revealing is one question raised in the API review meeting:\n\n- How many IOptionsContextReceiver implementations are there in practice?\n\n- There's an internal one for Azure, but we don't know of an open-source version of a > service that provides contextual options.\n- LaunchDarkly is a commercial service that could provide contextual options.\n\nThe fact that they mention LaunchDarkly here is telling. It shows that they were clearly thinking about this as a \"feature flags\" solution.\n\nExcept, there's *already* a feature flags solution from Microsoft, that [I wrote about extensively 7 years ago](/introducing-the-microsoft-featuremanagement-library-adding-feature-flags-to-an-asp-net-core-app-part-1/) 😅 That's *also* based on the .NET configuration system. Now, I haven't really looked at that solution in a *loong* time, but seeing as [Microsoft.FeatureManagement](https://www.nuget.org/packages/Microsoft.FeatureManagement) has about 140 million downloads, I think it's safe to say *some* people think it does the job. So introducing a sideways approach to do something very similar feels a little strange to me.\n\nRight now, I can only see one package that uses the\n\nMicrosoft.Extensions.Options.Contextualand thatisa package for doing feature flagging/experimentation:[https://github.com/excos-platform/config-client]\n\nIn Microsoft's defence, *Microsoft.Extensions.Options.Contextual* is very much marked as experimental, such that you really have to put some effort in if you actually want to use it.\n\n[Definitely experimental](#definitely-experimental)\n\nTo start with, there's no stable version of the *Microsoft.Extensions.Options.Contextual* package. All versions of the package are marked preview, so you'll need to bear that in mind when you install it.\n\nAdditionally, all the APIs are also decorated with the `[Experimental]`\n\nattribute, which generate the `EXTEXP0018`\n\ncompile-time error. This is similar to the `[Obsolete]`\n\nattribute, but it's applied to new APIs that might change, as opposed to old ones that are deprecated. If you want to use any of the APIs you have to explicitly opt in by using `#pragma`\n\nor other methods to disable the warnings:\n\n```\n#pragma warning disable EXTEXP0018\n```\n\nA particularly irritating aspect is the fact that even the source generated code has this error, and seeing as you can't add `#pragma`\n\nto these APIs, you effectively *must* disable the warning globally (which kind of defeats the purpose of the attribute a bit in my opinion). The easiest way to disable it is to add it to the `NoWarn`\n\nvariable in your *.csproj* file:\n\n```\n<PropertyGroup>\n  <!-- Suppresses \"For evaluation purposes only and is subject to change or removal in future updates\" -->\n  <NoWarn>$(NoWarn);EXTEXP0018</NoWarn>\n</PropertyGroup>\n```\n\nSo given all that, is there any value to these APIs, and should you use them?\n\nIn short, I don't think so. If you're completely bought into the `IOptions`\n\nlife, and you *specifically* have a need for something like that, then, maybe, I guess. But with [none of the package versions](https://www.nuget.org/packages/Microsoft.Extensions.Options.Contextual/10.4.0-preview.1.26160.2#versions-body-tab) ever reaching 1000 downloads, it doesn't seem like *many* people consider it worth it.\n\nAnd if what you really want is feature flagging and/or experimentation, then maybe consider taking a look at [OpenFeature](https://openfeature.dev/) instead (something that I might do a post on soon)!\n\n[Summary](#summary)\n\nIn this post I took an introductory look at the *Microsoft.Extensions.Options.Contextual* package. This package builds on the `IOptions`\n\nabstractions common to .NET. I show the basics of how to use the package, focusing on the partial decoupling it provides between a \"context\" object and your options object. Ultimately, I'm not convinced that this \"decoupling\" is worth this extra complexity, so if you have any experience with the package yourself, I'd be interested in your experiences in the comments!", "url": "https://wpnews.pro/news/configuring-contextual-options-with-microsoft-extensions-options-contextual", "canonical_source": "https://andrewlock.net/configuring-contextual-options-with-microsoft-extensions-options-contextual/", "published_at": "2026-04-01 10:00:00+00:00", "updated_at": "2026-05-23 21:37:31.288699+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Microsoft.Extensions.Options.Contextual", "Microsoft"], "alternates": {"html": "https://wpnews.pro/news/configuring-contextual-options-with-microsoft-extensions-options-contextual", "markdown": "https://wpnews.pro/news/configuring-contextual-options-with-microsoft-extensions-options-contextual.md", "text": "https://wpnews.pro/news/configuring-contextual-options-with-microsoft-extensions-options-contextual.txt", "jsonld": "https://wpnews.pro/news/configuring-contextual-options-with-microsoft-extensions-options-contextual.jsonld"}}