Asp.net Core: middleware to controller conversion issue? - c#

I have a Swagger project where I'm doing OAuth (token provider + verification). Everything is working fine, but the token provider was implemented as middleware based on a sample I found online. I want to convert the token provider middleware to a controller so it shows up in Swagger and users quit bugging me on how to get a token :).
In the startup.cs, I created a TokenProviderOptions object and populated it with values that live in the startup.cs (since they also get passed to the oauth verification part). I was then doing:
app.UseMiddleware<TokenProviderMiddleware>(Options.Create(tokenProviderOptions));
and the middleware was getting the options.
Now that I'm getting rid of the middleware, how can I pass in the tokenProvider options to the controller? Seems kind of weird to put it in DI as a singleton.

You can resolve options from the dependency injection container in controllers and other services using the IOptions<T> interface. For example:
public class TokenProviderController
{
private readonly IOptions<TokenProviderOptions> _options;
public TokenProviderController(IOptions<TokenProviderOptions> options)
{
_options = options;
}
}
You can then access the options values using _options.Value.
The options can be configured in the startup class. Typically you populate them from configuration:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
private IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TokenProviderOptions>(Configuration);
}
// ...
}
If your options consist of hard-coded values, you can use a delegate to configure the binding:
services.Configure<TokenProviderOptions>(o =>
{
o.Foo = "Bar";
});
For more info check out the documentation on the options pattern.

Related

Dependency Injection how to re-add all services to builder.Services after config change

This question is specific to NET Maui in my case.
My Settings file:
{
"MyConfig": {
"FactoryConfig1": {
"SomeType1": "SomeConfig1"
},
"FactoryConfig2": {
"SomeType2": "SomeConfig2"
}
}
}
Let's say I'm creating my services like this:
var builder = MauiApp.CreateBuilder();
builder.Services.AddSingleton<IService1, Service1>();
builder.Services.AddSingleton<IService2, Service2>();
builder.Services.AddTransient<Service3>();
// and config file-- how to modify this to use IOptions to use with IOptions Monitor?
var a = Assembly.GetExecutingAssembly();
using var stream = a.GetManifestResourceStream("MauiBlazorTestApp.appsettings.json");
var config = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
builder.Configuration.AddConfiguration(config);
return builder.Build();
then in another class I get the config:
public class Service1 : IService1
{
IConfiguration _configuration;
public Service1(IConfiguration configuration)
{
_configuration = configuration;
}
public void SomeMethod()
{
var myConfig = _configuration.GetRequiredSection("MyConfig").Get<MyConfig>();
}
}
In each of the services, I'm injecting an IConfiguration with the configuration parameters used in each service from an appsettings.json file.
The code above works the way it is but what if sometime after starting the app, and after the services have been already created with the existing settings, the user changes some settings in appsettings.json from a UI dashboard page, how can I reload all those services with the new config settings for each...
I have to clear the services, and re-add them, is this possible, in a class outside program.cs?
It would work if I restart the application, but can it be done without, just a refresh of the DI?
As #Nkosi mentioned in the comments you should always use option pattern and not directly access the IConfiguration object.
In your case just use a class to store the desired values from configuration:
public class MyConfig
{
// The class to bind IConfiguration to
public string YourSetting { get; set; }
}
add options to the service collection and configure them. Also note the reloadOnChange parameter used during setup which indicates whether the configuration is reloaded if the file changes.
//...
builder.Configuration
.AddJsonFile("MauiBlazorTestApp.appsettings.json", optional: false, reloadOnChange: true);
// Add and configure your options
builder.Services.AddOptions();
builder.Services.Configure<MyConfig>(builder.Configuration.GetRequiredSection("MyConfig"));
//...
and finally inject the options into the target services and access the settings as needed.
public class Service1 : IService1
{
private readonly IOptionsMonitor<MyConfig> _options;
public Service1(IOptionsMonitor<MyConfig> options)
{
_options = options;
}
public void SomeMethod()
{
// Read the current value provided via IConfiguration
MyConfig appSettings = _options.CurrentValue;
var value = appSettings.YourSetting;
//...use setting as needed
}
}
When ever the current setting is requested, it will pull the latest version from settings, even if the service is a singeton.
For more information you can find this pattern very well documented here and especially for the IOptionsMonitor here

Use Multiple Services for Singleton in ASP.NET Core 3.1

I am using the repository method for a data access class. So the constructor looks something like this:
public class MongoDbUnitOfWork : IMongoDbUnitOfWork
{
private readonly ILogger _logger;
private readonly IConfiguration _config;
public MongoDbUnitOfWork(ILogger logger, IConfiguration config)
{
_logger = logger;
_config = config
//do other stuff here, create database connection, etc.
}
}
public interface IMongoDbUnitOfWork
{
// various methods go in here
}
The key thing is that the constructor relies on the fact that 2 services are parsed to it.
Then in startup.cs I tried to do the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMongoDbUnitOfWork>(sp =>
{
var logger = sp.GetRequiredService<ILogger>();
var config = sp.GetRequiredService<IConfiguration>();
return new MongoDbUnitOfWork(logger, config);
});
//add other services
}
This compiled but did not work when I tried to run an API route through a controller. I got an error stating:
System.InvalidOperationException: Unable to resolve service for type 'NamespaceDetailsHere.IMongoDbUnitOfWork' while attempting to activate 'NamespaceDetailsHere.Controllersv1.TestController'.
Then I ran a little Debug.WriteLine() script in startup.cs to see whether the ILogger and IConfiguration services existed. They did. I'm not sure what I'm doing wrong here.
The ASP.NET Core service container will automatically resolve the services dependencies that are injected through the constructor, so you dont need the action configuration at all. When the service is constructed, any dependencies in the constructor are automatically required (as you're able to see with the exception).
Simply register your services with
services.AddSingleton<IMongoDbUnitOfWork, MongoDbUnitOfWork>();

ocelot change configuration without restarting app

I'm trying to change ocelot configuration without restarting app and I don't want use Consul.
Trying to search simple solution.
What I'm trying.
Registered configuration file as reloadOnChange:true
public Startup(IHostingEnvironment env)
{
var builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder();
builder.SetBasePath(env.ContentRootPath)
.AddJsonFile("configuration.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
And added Configuration as Singleton
services.AddSingleton<IConfiguration>(Configuration);
Also writed service which gives me ability change this singleton configuration using service.
[Route("api/[controller]")]
public class XBConfigurationController : Controller
{
public XBConfigurationController(IConfiguration configuration)
{
_configuration = configuration as IConfigurationRoot;
}
private readonly IConfigurationRoot _configuration;
// GET: /<controller>/
[HttpGet]
public IActionResult Get()
{
_configuration.Reload();
return Ok();
}
}
What's wrong? Why ocelot not refreshing it's internal configuration?
I solved my problem using administration api.
services.AddOcelot(Configuration).AddPolly().AddAdministration("/administration", "secret");
And added base url to ocelot configuration file.
"GlobalConfiguration": {
"RequestIdKey": "OcRequestId",
"AdministrationPath": "/administration",
"BaseUrl": "http://localhost:54864"
}
After changing code followed by this steps
Generate token using this service http://localhost:54864/administration/connect/token
Get current configuration using this api http://localhost:54864/administration/configuration with Get method and Bearer Token
Set modified configuration as json input to this api http://localhost:54864/administration/configuration
Additional info you can get
ocelot.postman_collection.json
Ocelot Administration Api Documentation

ASP.NET Core 2.0 inject Controller with Autofac

I'm trying to inject my controller with Autofac. Unfortunately I am unable to configure Autofac in away so that the 'DefaultControllerActivator` wont construct my controllers?
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterModule<ServiceModule>();
containerBuilder.Populate(services);
containerBuilder.RegisterType<LoginController>().PropertiesAutowired();
ApplicationContainer = containerBuilder.Build();
return new AutofacServiceProvider(this.ApplicationContainer);
}
public class ServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterModule(new DataProviderModule());
builder.RegisterType(typeof(LoginService)).As(typeof(ILoginService)).InstancePerRequest();
}
}
[Route("api/[controller]")]
public class LoginController : Controller
{
private readonly ILoginService _loginService;
public LoginController(ILoginService loginService)
{
_loginService = loginService;
}
}
I followed the documentation of Autofac as shown above. Unfortunately the LoginController will not be constructed because it requires an injection.
edit: If there is a way of using "Modules" without Autofac, I'd be very interesting for any suggestions :)
Thanks you in advance!
By default, ASP.NET Core will resolve the controller parameters from the container but doesn’t actually resolve the controller from the container. This usually isn’t an issue but it does mean:
The lifecycle of the controller is handled by the framework, not the request lifetime.
The lifecycle of controller constructor parameters is handled by the request lifetime.
Special wiring that you may have done during registration of the controller (like setting up property injection) won’t work.
You can change this by specifying AddControllersAsServices() when you register MVC with the service collection. Doing that will automatically register controller types into the IServiceCollection when you call builder.Populate(services).
public class Startup
{
public IContainer ApplicationContainer {get; private set;}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add controllers as services so they'll be resolved.
services.AddMvc().AddControllersAsServices();
var builder = new ContainerBuilder();
// When you do service population, it will include your controller
// types automatically.
builder.Populate(services);
// If you want to set up a controller for, say, property injection
// you can override the controller registration after populating services.
builder.RegisterType<MyController>().PropertiesAutowired();
this.ApplicationContainer = builder.Build();
return new AutofacServiceProvider(this.ApplicationContainer);
}
}
Use InstancePerLifetimeScope in ASP.NET Core. The differences between ASP.NET and ASP.NET Core like this are documented.

Getting a Configuration Value in ASP.NET 5 (vNext)

I am struggling with some concepts in ASP.NET 5 (vNext).
One of those is the Dependency Injection approach used for configuration. It seems like I have to pass a parameter all the way through the stack. I'm probably misunderstanding something or doing it wrong.
Imagine I have a config property named "contactEmailAddress". I'll use that config property to send an email when a new order is placed. With that scenario in mind, my ASP.NET 5 stack will look like this:
Startup.cs
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
var configuration = new Configuration().AddJsonFile("config.json");
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseErrorPage();
app.UseMvc(routes =>
{
routes.MapRoute("default",
"{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index" });
}
);
app.UseWelcomePage();
}
AppSettings.cs
public class AppSettings
{
public string ContactEmailAddress { get; set; }
}
config.json
{
"AppSettings": {
"ContactEmailAddress":"support#mycompany.com"
}
}
OrderController.cs
[Route("orders")]
public class OrdersController : Controller
{
private IOptions<AppSettings> AppSettings { get; set; }
public OrdersController(IOptions<AppSettings> appSettings)
{
AppSettings = appSettings;
}
[HttpGet("new-order")]
public IActionResult OrderCreate()
{
var viewModel = new OrderViewModel();
return View(viewModel);
}
[HttpPost("new-order")]
public IActionResult OrderCreate(OrderViewModel viewModel)
{
return new HttpStatusCodeResult(200);
}
}
Order.cs
public class Order()
{
public void Save(IOptions<AppSettings> appSettings)
{
// Send email to address in appSettings
}
public static List<Order> FindAll(IOptions<AppSettings> appSettings)
{
// Send report email to address in appSettings
return new List<Order>();
}
}
As the example above shows, I'm passing AppSettings through the entire stack. This does not feel correct. To further my worries, this approach will not work if I'm attempt to use a third-party library that needs to access configuration settings. How can a third-party library access configuration settings? Am I misunderstanding something? Is there a better way to do this?
You are entangling 2 different run time resource provider, AppSettings and Dependency Injection.
AppSettings, provides run-time access to Application specific values like UICulture strings, Contact Email, etc.
DI Containers are factories that Manage access to Services and their lifetime scopes. For example, If a MVC Controller needed access to your EmailService, you would configure
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add EmailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, EmailService>();
}
Then, if our Home Controller needs access to your EmailService, we add a dependency on it's Interface by adding it as a parameter to the Controller constructor
public class HomeController : Controller
{
private readonly IEmailService _emailService;
private readonly string _emailContact;
/// The framework will inject an instance of an IEmailService implementation.
public HomeController(IEmailService emailService)
{
_emailService = emailService;
_emailContact = System.Configuration.ConfigurationManager.
AppSettings.Get("ContactEmail");
}
[HttpPost]
public void EmailSupport([FromBody] string message)
{
if (!ModelState.IsValid)
{
Context.Response.StatusCode = 400;
}
else
{
_emailService.Send(_emailContact, message);
The purpose of Dependancy Injection is to manage access and lifetimes of services.
In the previous example, in our Application Startup, we configured the DI Factory to associate application requests for IEmailService with EmailService. So when our Controllers are instantiate by the MVC Framework, the framework notices that our Home Controller expects IEmailService, the framework checks our Application Services Collection. It finds mapping instructions and Inject a Singleton EmailService (a descendant of the occupying Interface) into our Home Controller.
Super Polymorphic Factorific - alodocious!
Why is this important?
If your contact email changes, you change the AppSetting value and are done. All requests for "ContactEmail" from ConfigurationManager are Globally changed. Strings are easy. No need for Injection when we can just hash.
If your Repository, Email Service, Logging Service, etc changes, you want a Global way to change all references to this service. Service reference aren't as easily transferred as immutable string literals. Service instantiation should be handled by a factory to configure the Service's settings and dependencies.
So, in a year you develop a RobustMailService:
Class RobustMailService : IEmailService
{
....
}
As long as your new RobustMailService inherits and implements the IEmailService Interface, you can substitute all references to your mail service Globally by changing :
public void ConfigureServices(IServiceCollection services)
{
// Add all dependencies needed by Mvc.
services.AddMvc();
// Add RobustMailService to the collection. When an instance is needed,
// the framework injects this instance to the objects that needs it
services.AddSingleton<IEmailService, RobustMailService>();
}
This can be achieved using IOptions assessor service as it seems you were trying.
We can begin by creating a class with all of the variables that your controller needs from configuration.
public class VariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
public class OtherVariablesNeeded
{
public string Foo1{ get; set; }
public int Foo2{ get; set; }
}
We now need to tell the middleware that the controller needs this class in the constructor of the controller using dependency injection, we do this using IOptions accessor service.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
public class MyController: Controller{
private readonly VariablesNeeded _variablesNeeded;
public MyController(IOptions<VariablesNeeded> variablesNeeded) {
_variablesNeeded= variablesNeeded.Value;
}
public ActionResult TestVariables() {
return Content(_variablesNeeded.Foo1 + _variablesNeeded.Foo2);
}
}
To get the variables from your configuration files, we create a constructor for the startup class, and a configuration property.
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
/* This is the fairly standard procedure now for configuration builders which will pull from appsettings (potentially with an environmental suffix), and environment variables. */
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Now we need to make sure the pipeline actually supplies the controller with this service.
In your ConfigureServices method in your Startup class, you want to use the Options middleware, and inject an object of type VariablesNeeded in to the pipeline.
public void ConfigureServices(IServiceCollection services)
{
// Tells the pipeline we want to use IOption Assessor Services
services.AddOptions();
// Injects the object VariablesNeeded in to the pipeline with our desired variables
services.Configure<VariablesNeeded>(x =>
{
x.Foo1 = Configuration["KeyInAppSettings"]
x.Foo2 = Convert.ToInt32(Configuration["KeyParentName:KeyInAppSettings"])
});
//You may want another set of options for another controller, or perhaps to pass both to our "MyController" if so, you just add it to the pipeline
services.Configure<OtherVariablesNeeded>(x =>
{
x.Foo1 = "Other Test String",
x.Foo2 = 2
});
//The rest of your configure services...
}
For more information see the chapter on Using Options and configuration objects in the ASPCore Docs

Categories

Resources