Think I'm having a brain melting moment but is it possible to load configuration from both appsettings and environment variables? I have some config that looks like
"DataFactorySettings": {
"ClientId": "foobar",
"FactoryName": "factory",
"PipelineName": "pipeline",
"PipelineParameters": "params",
"ResourceGroup": "rg",
"Subscription": "sub",
"TenantId": "tenant"
}
I have an environment variable entitled DATAFACTORYSETTINGS_CLIENTSECRET which contains the value secret.
My configuration class that binds to this looks like:
public class DataFactorySettings
{
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string FactoryName { get; set; }
public IDictionary<string, string> ParsedPipelineParameters { get; }
public string PipelineName { get; set; }
public string PipelineParameters { get; set; }
public string ResourceGroup { get; set; }
public string Subscription { get; set; }
public string TenantId { get; set; }
}
Got a basic console app that has the following setup
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", optional:false)
.Build();
For me to bind all these values together I do the following:
var a = new DataFactorySettings();
config.Bind("DataFactorySettings", a); // binds only appsettings.json
a.ClientSecret = config["DATAFACTORYSETTINGS_CLIENTSECRET"]; // grab env variable
I have two questions:
Is there a way to do this out the box so it will automatically bind?
If I had all the configuration as environment variables can I bind this to a concrete type in one go like I can do with appsettings?
Not 100% sure on this but I think a single underscore doesn't indicate a nested property. Either of the following may work depending on your OS:
DATAFACTORYSETTINGS__CLIENTSECRET (note the double underscore)
DATAFACTORYSETTINGS:CLIENTSECRET
The Bind should then work as you expect.
Related
I am new to dotnet, trying out dotnet 6 minimal API. I have two models:
namespace Linker.Models
{
class Link : BaseEntity
{
[MaxLength(2048)]
public string Url { get; set;} = default!;
[MaxLength(65536)]
public string? Description { get; set; }
[Required]
public User Owner { get; set; } = default!;
[Required]
public Space Space { get; set; } = default!;
}
}
And:
namespace Linker.Models
{
class Space : BaseEntity
{
public string Name { get; set; } = default!;
public string Code { get; set; } = default!;
public User Owner { get; set; } = default!;
public List<Link> Links { get; set; } = new List<Link>();
}
}
Now when I try to serialize Space model I get error System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. (make sense because Path: $.Links.Space.Links.Space.Links.Space.Links.Space.Links.Space.Links...). Is it posible to prevent dotnet from serializing object this deep? I don't need for dotnet to even try to serialize such a deep relations
You can set ReferenceHandler.Preserve in the JsonSerializerOptions. These docs
How to preserve references and handle or ignore circular references in System.Text.Json discuss further.
For manual serialization/deserialization pass the options to the JsonSerializer:
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.Preserve
};
string serialized = JsonSerializer.Serialize(model, options);
Or to configure globally in minimal API:
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
// Set the JSON serializer options
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
});
You could instead ignore the circular references rather than handling them by using ReferenceHandler.IgnoreCycles. The serializer will set the circular references to null, so there's potential for data loss using this method.
The reason the global configuration is getting ignored is because the wrong JsonOptions is being used. The following should work:
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
... rest of code
My default for JsonOptions is Microsoft.AspNetCore.Mvc.JsonOptions, which was not the correct JsonOptions object to change and so globally did not to work.
Try adding [JsonIgnore] before the Space declaration as below:
namespace Linker.Models
{
class Link : BaseEntity
{
[MaxLength(2048)]
public string Url { get; set;} = default!;
[MaxLength(65536)]
public string? Description { get; set; }
[Required]
public User Owner { get; set; } = default!;
[JsonIgnore]
[Required]
public Space Space { get; set; } = default!;
}
}
I am building a C# .NET 5.0 application to talk to an external API for which we have multiple accounts that we can post data to. I am trying to store these details in appsettings.json file and use the options pattern to retrieve the settings.
I have the following structure in my app settings.json
"SomeApi": {
"Url": "https://api.someapi.net/v3/",
"Secret": "mysecretvlaue",
"ClientId": "myclientid",
"Accounts": [
{
"Name": "Bob",
"Username": "HisUsername",
"Password": "HisPassword",
"PostingLocation": "HisLocation"
},
{
"Name": "Fred",
"Username": "HisUsername",
"Password": "HisPassword",
"PostingLocation": "HisLocation"
}
]
},
I have a class set up like this:
public class SomeApiOptions
{
public const string SomeApi = "SomeApi";
public string Url { get; set; }
public string Secret { get; set; }
public string ClientId { get; set; }
public List<Account> Accounts { get; set; }
}
public class Account
{
string Name { get; set; }
string Username { get; set; }
string Password { get; set; }
string PostingLocation { get; set; }
}
My startup.cs has this:
services.Configure<SomeApiOptions>(Configuration.GetSection(SomeApiOptions.SomeApi));
Finally, I inject IOptions to the class where I need to use the settings like so:
Public MyClass (IOptionsSnapshot<SomeApiOptions> options)
The issue I am having is the Accounts never get populated with their respective values.
Url, Secret etc. are populated and the number of elements items in the Accounts list is correct, but all of the properties have null values.
I have looked at similar questions and tried restructuring my JSON a few ways but still end up with null values.
Any help greatly appreciated.
I see a couple of problems here.
First, none of the properties in the Account class are public so you need to change that:
Second, the Location property doesn't match the JSON which is PostingLocation. Your class should be:
public class Account
{
public string Name { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string PostingLocation { get; set; }
}
I'm trying to bind my appsettings.json file to my AppSettings class. It works for all properties except for "Microsoft.Hosting.Lifetime" simply because I don't know how to create a property in a C# class with dots in it.
Here is my appsettings.json file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
...
}
Here is AppSettings.cs:
using System;
using Microsoft.Extensions.Configuration;
namespace AMI.TBoard.LCTransfer
{
public class AppSettings
{
public Logging Logging { get; set; }
public ConnectionStrings ConnectionStrings { get; set; }
public FtpConnectionStrings FtpConnectionStrings { get; set; }
public void BindConfig(IConfiguration config)
{
config.Bind(this);
}
}
public class Logging
{
public LogLevel LogLevel { get; set; }
}
public class LogLevel
{
public string Default { get; set; }
public string Microsoft { get; set; }
}
public class ConnectionStrings
{
public string appDbConnection { get; set; }
}
public class FtpConnectionStrings
{
public string url { get; set; }
public string username { get; set; }
public string password { get; set; }
public string exportFolder { get; set; }
public string importFolder { get; set; }
}
}
Note that all properties in appsettings.json map onto properties by the same name in AppSettings.cs. Nested properties bind to nested classes (I'm not showing the connection string properties in appsettings.json for obvious reasons, but they're there).
Note also that there is no binding to "Microsoft.Hosting.Lifetime". What would I have to add to AppSettings.cs to bind it to "Microsoft.Hosting.Lifetime"?
EDIT: Here is how I do the binding:
In program.cs:
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var appSettings = new AppSettings();
appSettings.BindConfig(config);
In AppSettings, you can see where I bind the configuration above.
private string _microsoftHostingLifetime;
public string MicrosoftHostingLifetime {
get
{
if (string.IsNullOrWhiteSpace(_microsoftHostingLifetime))
{
_microsoftHostingLifetime = Configuration["Logging:LogLevel:Microsoft.Hosting.Lifetime"];
}
return _microsoftHostingLifetime;
}
set
{
_microsoftHostingLifetime = value;
}
}
I deal by doing like this
I used to get values of my configuration like this:
public Startup(IConfiguration Configuration)
{
GraphDBL.Config.Uri = Configuration.GetSection("neo4j:host").Value;
GraphDBL.Config.UserName = Configuration.GetSection("neo4j:user_name").Value;
GraphDBL.Config.Password = Configuration.GetSection("neo4j:password").Value;
}
my Config class `
public class Config
{
public static string Uri { get; set; }
public static string UserName { get; set; }
public static string Password { get; set; }
}
How can i get the result of this in one process? not in turn?
If you examine IConfigurationSection, you will see it has a Value property that you can use to get the value.
You have to specify the full path as a paramter to GetSection, using a colon like this:
GetSection("neo4j:host").Value
You need to add Microsoft.Extensions.Configuration.Binder package to be able to use GetValue extension method.
To map section to a class you need to have properties with same names as configuration
public class Conf
{
public string Host { get; set; }
public string User_Name { get; set; }
public string Password { get; set; }
}
And get configuration like this
GraphDBL.Config = Configuration.GetSection("neo4j").Get<Config>();
Or if you don't want change properties names you can use this trick
public class Config
{
private string host { get; set; }
private string user_name { get; set; }
public string Uri
{
get
{
return host;
}
set
{
host = value;
}
}
public string UserName
{
get { return user_name; }
set
{
user_name = value;
}
}
public string Password { get; set; }
}
And get config
GraphDBL.Config = Configuration.GetSection("neo4j").Get<Config>(options => options.BindNonPublicProperties = true);
I am getting tdata from a certain endpoint and the problem id on serialization to my classes. I want to cast the bellow data to my class but cant get how the class should be structured. Check out the data .....
{
"-LYG_AI_oGYjNBrzMlKF": {
"chatDispayText": "",
"chatId": "-LYG_AI_oGYjNBrzMlKF",
"chatName": "",
"chattype": "single",
"imageUrl": "https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Amazing-celebrities-hd-wallpaper-PIC-WPD004734.jpg",
"lastMessageSent": "aiye",
"lastMessageSentTime": 1549704416263,
"synched": false,
"users": {
"-LYG_AIZ5MvTbjR7DACe": "Uicpm3L15TX0c15pKCI6KUEARyB3",
"-LYG_AI_oGYjNBrzMlKE": "Xsr0z9lsqNOEytX61lJvaGz1A8F2"
}
}
}
If the data you get out the endpoint has a dynamic structure, you can make use of a key-vale pair collection or a dictionary. For instance:
JObject jObject = JObject.Parse(Data); // This would already give you a key-value pair collection
Dictionary<String,Object> collection = new Dictionary<String, Object>();
foreach(var obj in jObject){
collection.Add(obj.Key, obj.Value);
}
However, this isn't a strongly typed approach which means that it is not effective in the majority of scenarios. A better solution when dealing with endpoints would be to define a class with fixed schema, actually something you need in your code, and then map the class to the object yielded by the endpoint using a metadata struct. For example:
public class ChatInfoModel
{
[JsonProperty(Metadata.ChatId)]
public long ChatId { get; set; }
[JsonProperty(Metadata.ChatId, Required = Required.AllowNull)]
public String Message { get; set; }
}
public struct Metadata
{
public const String ChatId = "userChatId";
public const String Message = "messageTxt";
}
And then
var deserializedObject = JsonConvert.DeserializeObject<ChatInfoModel>(data);
However, if your class has the exact same naming convention (but should not necessarily follow the camelCase naming convention) for its properties as in the serialized data, the JsonProperty attribute would not be needed.
You can also deserialize the object without using JsonProperty attribute manually using the first approach, and it is actually advantageous in certain scenarios where your schema comes from a configuration file rather than a struct.
Take inspiration from the Structure below:
public class Rootobject
{
public LYG_AI_Ogyjnbrzmlkf LYG_AI_oGYjNBrzMlKF { get; set; }
}
public class LYG_AI_Ogyjnbrzmlkf
{
public string chatDispayText { get; set; }
public string chatId { get; set; }
public string chatName { get; set; }
public string chattype { get; set; }
public string imageUrl { get; set; }
public string lastMessageSent { get; set; }
public long lastMessageSentTime { get; set; }
public bool synched { get; set; }
public Users users { get; set; }
}
public class Users
{
public string LYG_AIZ5MvTbjR7DACe { get; set; }
public string LYG_AI_oGYjNBrzMlKE { get; set; }
}