ASP.NET IConfigurationSection is returning null objects for POCO - asp.net

I looked through various solutions posted on StackOverflow -- many were outdated.
The intent is to use IConfigurationSection.Get to return a POCO object from a json section via JsonConfigurationExtensions.
The simplest case:
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{hostEnvironment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
IConfiguration configuration = builder.Build();
return configuration.GetSection("ServerConfiguration").Get<ServerConfiguration>();
And a nested POCO:
public class ServerConfiguration
{
public Authentication Authentication { get; internal set; }
}
public class Authentication
{
public DatabaseConfiguration UserDatabase { get; internal set; }
}
public class DatabaseConfiguration
{
public string ConnectionString { get; internal set; }
public string DatabaseName { get; internal set; }
}
The result is a null object.
In order to "clean up" my code at inception, I actually did not include the set property declarations as it wasn't needed in previous Json to POCO handlers. However, even when declaring these handlers (typically non-public) the ASP.NET implementation for Json file processing was always returning null although retrieving the individual key pairs from the section was successful.

The answer was buried in a response in the ASP.NET forum:
https://github.com/aspnet/Configuration/issues/394#issuecomment-444683884
The resulting change in the code:
1) Make sure there is a declaration of a set handler (internal, protected, private).
2) Specify BindOptions => BindNonPublicProperties.
return configuration.GetSection("ServerConfiguration")
.Get<ServerConfiguration>(c => c.BindNonPublicProperties = true);

Related

How sensitive is the Google OpenID Discovery Document to change?

What I am trying to do
I am trying to implement Google OpenID Connect as a means to login to an ASP.NET Core 3.1 website using Google's instructions:
https://developers.google.com/identity/protocols/oauth2/openid-connect#server-flow
Under step 2 of the server flow (Send an authentication request to Google) they recommend retrieving information from their OpenID Discovery Document:
You should retrieve the base URI from the Discovery document using the authorization_endpoint metadata value.
I am currently trying to dynamically deserialize the JSON to a Dictionary<string, string> by using Newtonsoft.Json. But it is giving me some issues (can't seem to deserialize a JSON string array) and I am considering changing my strategy to creating a model for the Discovery Document and using System.Text.Json to deserialize.
Now my question is
How sensitive is Google's Discovery Document to changes that would lead to me having to update my DiscoveryDocument.cs model?
Dilemma
With the Newtonsoft.Json way everything will still work, even if Google decides to remove a random key.
But using the System.Text.Json is the easy way out for me now and removes a dependency on the Newtonsoft library, though I may run into trouble later if Google's Discovery Document changes.
I think you will have a much easier time to use the Microsoft.IdentityModel.Protocols and
Microsoft.IdentityModel.Protocols.OpenIdConnect NuGet packages and use the included parser to do it all for you. The items in the document is pretty standardized but not every provider provides all the items.
public class OpenIDSettings : IOpenIDSettings
{
public string Issuer { get; }
public string jwks_uri { get; }
public string authorization_endpoint { get; }
public string token_endpoint { get; }
public string userinfo_endpoint { get; }
public string end_session_endpoint { get; }
public string check_session_iframe { get; }
public string revocation_endpoint { get; }
public string introspection_endpoint { get; }
public string device_authorization_endpoint { get; }
public ICollection<string> scopes_supported { get; }
public ICollection<string> claims_supported { get; }
public OpenIDSettings(string endpoint)
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{endpoint}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
//If you get an exception here, then provider is not running or reachable
var document = configurationManager.GetConfigurationAsync().Result;
//Add the necessary code to populate the properties in this class
Issuer = document.Issuer;
jwks_uri = document.JwksUri;
authorization_endpoint = document.AuthorizationEndpoint;
token_endpoint = document.TokenEndpoint;
userinfo_endpoint = document.UserInfoEndpoint;
end_session_endpoint = document.EndSessionEndpoint;
check_session_iframe = document.CheckSessionIframe;
scopes_supported = document.ScopesSupported;
claims_supported = document.ClaimsSupported;
if (document.AdditionalData.ContainsKey("revocation_endpoint"))
revocation_endpoint = (string)(document.AdditionalData["revocation_endpoint"]);
if (document.AdditionalData.ContainsKey("introspection_endpoint"))
introspection_endpoint = (string)(document.AdditionalData["introspection_endpoint"]);
if (document.AdditionalData.ContainsKey("device_authorization_endpoint"))
device_authorization_endpoint = (string)(document.AdditionalData["device_authorization_endpoint"]);
}
}

Swagger different classes in different namespaces with same name don't work

I got (more than) two Api POST endpoints. Each one needs a json as parameter. But when I use the same class name Payload in two endpoint argument classes, Swagger does not work. When I change one of it e.g. from Payload to Payload1 than it works.
Of course I set the right namespaces into the wrapper classes so it finds it Payload. But I would love to use the same name "Payload" each time. How can I use the same class name Payload?
I can keep the json name "Payload" at both cases and just set different names for the property ("Payload1", "Payload2"). It works. But would be nice to have same property names too.,
Endpoint A
[HttpPost()]
public async Task PostPerson([FromBody]JsonWrapperA jsonWrapperA)
namespace myProject.A
{
public class JsonWrapperA
{
[JsonProperty("name", Required = Required.AllowNull)]
public string Name { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("value", Required = Required.AllowNull)]
public double Value { get; set; }
}
}
Endpoint B
[HttpPost()]
public async Task PostCompagn([FromBody]JsonWrapperB jsonWrapperB)
namespace myProject.B
{
public class JsonWrapperB
{
[JsonProperty("compagny", Required = Required.AllowNull)]
public string Compagny { get; set; }
[JsonProperty("payload", Required = Required.AllowNull)]
public Payload Payload { get; set; }
}
public class Payload
{
[JsonProperty("age", Required = Required.AllowNull)]
public double Age{ get; set; }
}
}
By default swagger will attempt to build its Schema Ids for objects that are return types or parameter types for your APIs endpoints, and it will display these objects in the "Models" section of the documentation. It will build these schema Ids based on the class names of the objects.
When you try to have two or more classes named the same, even though they are in different namespaces, then you will get the conflicting schemaIds error:
InvalidOperationException: Conflicting schemaIds: Identical schemaIds detected for types NamespaceOne.MyClass and NamespaceTwo.MyClass. See config settings - "CustomSchemaIds" for a workaround
This means Swagger needs to be configured to change the way it generates its SchemaIds. You can simply tell swagger to use an objects fully qualified name which will include namespaces in the schemaIds. You can do this in your Startup.cs file in the ConfigureServices method like this:
//add using statement for Swagger in Startup.cs
using Swashbuckle.AspNetCore.Swagger;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(config =>
{
//some swagger configuration code.
//use fully qualified object names
config.CustomSchemaIds(x => x.FullName);
}
}
Using Swashbuckle.AspNetCore Version 5.5.1 i've had the same issue so i solved it using JustSomeDude answer, but afterwards all entities were shown with the full name so i needed a way to show only the name. This is what i did:
options.CustomSchemaIds(x => x.FullName); // Enables to support different classes with the same name using the full name with namespace
options.SchemaFilter<NamespaceSchemaFilter>(); // Makes the namespaces hidden for the schemas
Using this filter class:
public class NamespaceSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema is null)
{
throw new System.ArgumentNullException(nameof(schema));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
schema.Title = context.Type.Name; // To replace the full name with namespace with the class name only
}
}

OData paging with expand issue

I'm using OData v5/Web API 2.2 to create an endpoint that will return a list of employees from each company.
My problem occurs when I try to implement server-side paging while also using the OData $expand property. When I try to make a call to
http://localhost:60067/Companies?$expand=Employees
I get an error that says "Could not find a property named 'Employees' on type 'System.Web.OData.Query.Expressions.SelectAllAndExpand_1OfCompanyApiModel'"
However, when I removed the EnableQuery attribute the call to the endpoint or when I didn't expand it works as expected. Does anyone have an idea of what I am doing wrong? I've been googling this for a while but haven't found anything.
Here are some code snippets -
Data Models:
public class CompanyApiModel
{
[Key]
public Guid CompanyGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
public List<EmployeeApiModel> Employees { get; set; }
}
public class EmployeeApiModel
{
[Key]
public Guid EmployeeGuid { get; set; }
[Required]
public string Name { get; set; }
// other properties
}
CompaniesController.cs:
[EnableQuery(PageSize = 10)] // If I comment this out everything works
//[EnableQuery] // This fails as well
public IHttpActionResult Get(ODataQueryOptions<CompanyApiModel> queryOptions)
{
var companies = GetCompanies(queryOptions);
return Ok(companies);
// return Ok(companies.AsQueryable()); // This doesn't work either
}
WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
var routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new OptionsRoutingConvention());
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
// below code allows endpoints to respond with either XML or JSON, depending on accept header preferences sent from client
// (default in absence of accept header is JSON)
var odataFormatters = ODataMediaTypeFormatters.Create();
config.Formatters.InsertRange(0, odataFormatters);
config.EnsureInitialized();
}
public static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "Demos";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<CompanyApiModel>("Companies");
builder.EntitySet<EmployeeApiModel>("Employees");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
Figured out the problem. We were overriding the EnableQuery attribute somewhere in our code and calling it EnableMappedQuery and applying it to the controller. Thus instead of having [EnableQuery(PageSize = 10)] I should have had [EnableMappedQuery(PageSize = 10)].
EnableQuery Attribute do many works,
1. it will validate the queryoption for you.
2. it will apply the queryoption for you.
3. it can add some querysettings like PageSize.
Your scenario not working is because your GetCompanies is already applied the queryoption, so when EnableQuery get the result and apply the queryoption again, it fails, it can't find the expand property, my suggestion is just return original Company and let EnableQuery do the reset of work for you, ODataQueryOption in parameter is also not needed.
If you realy do some custom work in GetCompanies and don't need EnableQuery to apply for you, you can add PageSize in ODataQuerySettings when you call method ODataQueryOptions.ApplyTo(IQueryable, ODataQuerySettings).

ASP.NET 5 DI app setting outside controller

I can DI app setting in the controller like this
private IOptions<AppSettings> appSettings;
public CompanyInfoController(IOptions<AppSettings> appSettings)
{
this.appSettings = appSettings;
}
But how to DI that in my custom class like this
private IOptions<AppSettings> appSettings;
public PermissionFactory(IOptions<AppSettings> appSetting)
{
this.appSettings = appSettings;
}
my register in Startup.cs is
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
The "proper" way
Register your custom class in the DI, the same way you register other dependencies in ConfigureServices method, for example:
services.AddTransient<PermissionFactory>();
(Instead of AddTransient, you can use AddScoped, or any other lifetime that you need)
Then add this dependency to the constructor of your controller:
public CompanyInfoController(IOptions<AppSettings> appSettings, PermissionFactory permFact)
Now, DI knows about PermissionFactory, can instantiate it and will inject it into your controller.
If you want to use PermissionFactory in Configure method, just add it to it's parameter list:
Configure(IApplicationBuilder app, PermissionFactory prov)
Aspnet will do it's magic and inject the class there.
The "nasty" way
If you want to instantiate PermissionFactory somewhere deep in your code, you can also do it in a little nasty way - store reference to IServiceProvider in Startup class:
internal static IServiceProvider ServiceProvider { get;set; }
Configure(IApplicationBuilder app, IServiceProvider prov) {
ServiceProvider = prov;
...
}
Now you can access it like this:
var factory = Startup.ServiceProvider.GetService<PermissionFactory>();
Again, DI will take care of injecting IOptions<AppSettings> into PermissionFactory.
Asp.Net 5 Docs in Dependency Injection
I recommend not passing AppSettings. A class shouldn't depend on something vague - it should depend on exactly what it needs, or close to it. ASP.NET Core makes it easier to move away from the old pattern of depending on AppSettings. If your class depends on AppSettings then you can't really see from the constructor what it depends on. It could depend on any key. If it depends on a more specific interface then its dependency is clearer, more explicit, and you can mock that interface when unit testing.
You can create an interface with the specific settings that your class needs (or something less specific but not too broad) and a class that implements it - for example,
public interface IFooSettings
{
string Name { get; }
IEnumerable Foos { get; }
}
public interface IFoo
{
string Color { get; }
double BarUnits { get; }
}
public class FooSettings : IFooSettings
{
public string Name { get; set; }
public List<Foo> FooList { get; set; }
public IEnumerable Foos
{
get
{
if (FooList == null) FooList = new List<Foo>();
return FooList.Cast<IFoo>();
}
}
}
public class Foo : IFoo
{
public string Color { get; set; }
public double BarUnits { get; set; }
}
Then add a .json file, fooSettings.json:
{
"FooSettings": {
"Name": "MyFooSettings",
"FooList": [
{
"Color": "Red",
"BarUnits": "1.5"
}, {
"Color": "Blue",
"BarUnits": "3.14159'"
}, {
"Color": "Green",
"BarUnits": "-0.99999"
}
]
}
}
Then, in Startup() (in Startup.cs) where we specify what goes into our Configuration, add fooSettings.json:
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true)
.AddJsonFile("fooSettings.json");
Finally, in ConfigureServices() (also in Startup.cs) tell it to load an instance of FooSettings, cast it as IFooSettings (so the properties appear read-only) and supply that single instance for all dependencies on IFooSettings:
var fooSettings = (IFooSettings)ConfigurationBinder.Bind<FooSettings>(
Configuration.GetConfigurationSection("FooSettings"));
services.AddInstance(typeof (IFooSettings), fooSettings);
Now your class - controller, filter, or anything else created by the DI container - can have a dependency on IFooSettings and it will be supplied from the .json file. But you can mock IFooSettings for unit testing.
Original blog post - it's mine so I'm not plagiarizing.
You can do dependency injection in your non-controller classes as well.
In your startup class,
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
// register other dependencies also here
services.AddInstance<IConfiguration>(Configuration);
}
}
Now in your custom class, Have the constructor accept an implementation of IConfiguration
private IConfiguration configuration;
public PermissionFactory(IConfiguration configuration)
{
this.configuration = configuration;
}
public void SomeMethod()
{
var someSection = this.configuration.GetSection("SomeSection");
var someValue= this.configuration.Get<string>("YourItem:SubItem");
}
If you want to DI to action filter reference to Action filters, service filters and type filters in ASP.NET 5 and MVC 6 service filter part.

ASP.NET 5 (i.e. Core 1.0) Immutable Configuration

I can't get ASP.NET 5 (i.e. Core 1.0) to work with immutable configuration classes.
Let me demonstrate first using a mutable class.
config.json
{
"Name": "Lou",
"Age": 30
}
Config.cs (mutable)
public class Config
{
public string Name { get; set; }
public int Age { get; set; }
}
Registration in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var configBuilder = new ConfigurationBuilder()
.AddJsonFile("config.json");
var config = configBuilder.Build();
services.Configure<Config>(config);
services.AddMvc();
}
The code above works well and the values of Config.Name and Config.Age are as expected.
But once I change Config's property setters to private, they are no longer set by the configuration builder:
public class Config
{
public string Name { get; private set; } // == null
public int Age { get; private set; } // == 0
}
I would have expected ASP.NET 5 to use reflection and set the properties like it does with ASP.NET 4, instead of simply ignoring them. Am I missing something?
I created a sample project to demonstrate: https://github.com/johnnyoshika/mvc6-immutable-config
The master branch uses a mutable Config class while the immutable-config branch uses an immutable Config class.
The ConfigurationBinder object doesn't set private properties: https://github.com/aspnet/Configuration/blob/ba1d9b4cbc363db9318f201f08fbfecc72e7922b/src/Microsoft.Extensions.Configuration.Binder/ConfigurationBinder.cs#L64-L82
Here's an issue tracking that: https://github.com/aspnet/Configuration/issues/394
One way to workaround this is to create an interface with getters only that's implemented by your Config code. Then, you can use that in your code.

Resources