You can use IConfigureOptions or IPostConfigureOptions if you need to access services while configuring options, see https://andrewlock.net/simplifying-dependency-injection-for-iconfigureoptions-with-the-configureoptions-helper/
Alright, the answer here was to use a delegate. Previously, I was adding the code like so:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(BuildAzureB2CSection(signInPolicy, clientId, redirectURI))
Where I was trying to get the IContentLoader directly in the Startup.ConfigureServices() method. Whereas the AddMicrosoftIdentityWebApp() method also has an overload where you can use a delegate to supply the app with the config that gets executed after the IContenLoader is registered:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
var cl = services.BuildServiceProvider().GetRequiredService<IContentLoader>();
//configure options here
});
Building the container is not just about performance, you might also get unwanted side effects. Not sure I can explain it better than the link I posted. But you something like this:
// In your startup class
services.ConfigureOptions<YourConfigurer>();
public class YourConfigurer : IConfigureOptions<TheOptionsClass>
{
private readonly IContentLoader _contentLoader;
public YourConfigurer(IContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public void Configure(TheOptionsClass options)
{
// Use _contentLoader here to fetch content and set the options below...
options.TheSetting = xxx;
}
}
There won't be any race conditions here. This configuration is called when needed and after the container has been built. Using the service locator in your startup class won't work, since it needs the container to be built. The service locator is also considered an anti-pattern.
This link might be helpful https://learn.microsoft.com/en-us/dotnet/core/extensions/options
As for the options pattern and the blog post you sent, I understand how to implement the interface, I just don't understand how to use it because my authentication methods need to also be called in my configure services method.
Maybe the following example will help convey my confusion. Say I have the above implementation that you just sent and I register it in my DI container, my startup class would look as follows:
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IConfigureOptions<MyOpenIdConnectOptions>, ConfigureMyOpenIdConnectOptions>();
var myOpenIdConnectOptions = services.BuildServiceProvider().GetRequiredService<IConfigureOptions<MyOpenIdConnectOptions>>();
var authBuilder = services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme);
foreach (var clientId in myOpenIdConnectOptions.ClientIds)
{
authBuilder.AddMicrosoftIdentityWebApp(clientId);
}
}
}
This may give a different error, but I would have the same problem as before, right?
Okay, I'm understanding the IConfigureOptions class a little more now. My confusion above was that I didn't understand I was working with my settings directly. This approach will not work for me as my requirements are too dynamic. When you use IConfigureOptions you get the benefit of using DI to compute sections of settings. However, in my scenario, I would need to know the amount of settings that exist prior to initialization, this is not feasible for me as the schema is not a collection. I need to be able to iterate over all of my sites and add authentication for each one. I cannot call my initialization method a single time and get multiple different results in this fashion. At least, I do not understand it enough to do that yet.
The idea with IConfigureOptions, was that you should configure the options class 'MicrosoftIdentityOptions' with it. Not the way you did it with your own class 'MyOpenIdConnectOptions'. But there's more to it than just configuring that options class to get this working, I realized when looking through the source code of the Microsoft.Identity.Web library.
You will have a real hard time adding these schemes dynamically. I would recommend configuring OpenID Connect completely manually https://docs.developers.optimizely.com/content-management-system/docs/integrate-azure-ad-using-openid-connect or do it statically. How often does these settings change? Do you really need to do this dynamically?
Hello,
I am upgrading a cms and commerce solution from CMS 11 to 12. This site uses Azure AD B2C as one of their authentication schemes. Azure AD B2C has this concept of a ClientID, among other configuration values. Because this is a multi-site setup, the ClientId is stored on the Start Page. In their CMS 11 Startup class, they loaded all of their Start Pages and looped through them, initializing Open Id Connect for each of their Start Pages with a different Client Id.
In CMS 12, I have been unable to access the IContentLoader in any way, during Startup in the place that I need it (the ConfigureServices method). I have tried injecting it into the Startup class, used the ServiceBuilder within the ConfigureServices method, and even tried a Configurable Module that was depending on CMS Initialization, Commerce Initialization, and Service Container Initialization. The initilization moldule approach essentially has the same issue in a different flavor. I need the IContentLoader in the ConfigureContainer method, but can only get it in the Initialize method.
Each time, the site will not start up due to an exception from retrieving the IContentLoader before it is available. I'll paste the CMS 11 code below, if anyone knows how to translate this to CMS 12 or how to inject authentication config values after site startup, let me know. Any help is appreciated!