Akka.net asp.net 5 mvc 6 configuration for Hocon - configuration

I am currently trying to use akka.net but the configuration they use HOCON is a different syntax from the json syntax normally used in app.json when configuring
our app.
Does anyone know how to use HOCON with the current app.json configuration?

I use ConfigurationFactory.FromObject and some classes that has the properties that I'm interested in to read the akka-config from appsettings.
var config = ConfigurationFactory.FromObject(new { akka = configuration.GetSection("Akka").Get<AkkaConfig>() });
actorSystem = ActorSystem.Create("Stimpy", config);
Notice that I haven't bothered to figure out how to parse kebab-case properties from appsettings. So I have just renamed the properties excluding the hyphens. Then set the JsonProperty-attribute to the correct name so that FromObject can deserialize it correctly.
public class AkkaConfig
{
[JsonProperty(PropertyName = "log-config-on-start")]
public string logconfigonstart { get; set; }
[JsonProperty(PropertyName = "stdout-loglevel")]
public string stdoutloglevel { get; set; }
public string loglevel { get; set; }
public string[] loggers { get; set; }
public ActorConfig actor { get; set; }
public class ActorConfig
{
public DebugConfig debug { get; set; }
public Dictionary<string, string> serializers { get; set; }
[JsonProperty(PropertyName = "serialization-bindings")]
public Dictionary<string, string> serializationbindings { get; set; }
public class DebugConfig
{
public string receive { get; set; }
public string autoreceive { get; set; }
public string lifecycle { get; set; }
[JsonProperty(PropertyName = "event-stream")]
public string eventstream { get; set; }
public string unhandled { get; set; }
}
}
}
appsettings.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace"
}
},
"Hosting": {
"Url": "http://*:1890"
},
"Akka": {
"logconfigonstart":"on",
"stdoutloglevel":"INFO",
"loglevel": "DEBUG",
"loggers": [ "Akka.Logger.NLog.NLogLogger, Akka.Logger.NLog" ],
"actor": {
"debug": {
"receive": "on",
"autoreceive": "on",
"lifecycle": "on",
"eventstream": "on",
"unhandled": "on"
},
"serializers": {
"hyperion": "Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion"
},
"serializationbindings": {
"System.Object": "hyperion"
}
}
}
}

What you can do is putting the HOCON in its own text file and then executing the following:
/// <summary>
/// Used to load HOCON definitions from a dedicated HOCON file
/// </summary>
public static class HoconLoader
{
/// <summary>
/// Parses a HOCON <see cref="Config" /> object from an underlying file
/// </summary>
/// <param name="path">The path to the HOCON file.</param>
/// <returns>A parsed <see cref="Config" /> object.</returns>
public static Config FromFile(string path)
{
return ConfigurationFactory.ParseString(File.ReadAllText(path));
}
}
Then pass that hocon object into the ActorSystem.Create(string name, Config config)
Dont forget tp make the file "Copy Always" or "Copy If Newer"

Related

Error deserializing json with nested arrays

I'm trying to deserialize a json object that includes nested arrays and I get the following error when I try it in Postman:
{
"errors": {
"accounts": [
"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Orders.Dtos.Accounts' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.\r\nPath 'accounts', line 7, position 17."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-92466567188264cbdc71e6f6d7479688-fe08150cd947ddf1-00"
}
Simplified json to deserialize:
{
"plannedShippingDateAndTime": "2023-02-07T07:06:00 GMT+01:00",
"pickup": {
"isRequested": false
},
"productCode": "N",
"accounts": [
{
"typeCode": "shipper",
"number": "999999999"
}
]
}
Classes:
public class ShipmentData
{
public string PlannedShippingDateAndTime { get; set; }
public Pickup Pickup { get; set; }
public string ProductCode { get; set; } = "N";
public Accounts Accounts { get; set; }
}
public class Pickup
{
public bool isRequested { get; set; }
}
public class Accounts
{
public ArrayAccounts[] ArrayAccounts { get; set; }
}
public class ArrayAccounts
{
public string TypeCode { get; set; }
public string Number { get; set; }
}
Api Controller:
[HttpPost("CreateShipment/{OrderId}")]
public async Task<ActionResult> CreateShipment(string OrderId, ShipMentData shipmentData)
{
...
}
public class ShipmentData
{
public string PlannedShippingDateAndTime { get; set; }
public Pickup Pickup { get; set; }
public string ProductCode { get; set; }
public Account[] Accounts { get; set; }
}
public class Pickup
{
public bool IsRequested { get; set; }
}
public class Account
{
public string TypeCode { get; set; }
public string Number { get; set; }
}
Also Customize JSON binding:
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

How do I add a Claim (or set of claims) to an AppSettings file?

I have an application that is normally connected to an authentication service that provides an identity for the current user. Often, when I'm travelling or commuting, I don't have internet access and need to work in a local mode. In this environment, I want to hard-code a set of claims into the application using the appSettings file in order to give the current user an identity.
I'm having trouble deserializing the System.Security.Claims structure. I'm using the deserialization method found on this post, but the Configuration system isn't recognizing it. Here's the definition of the class I'm trying to read from the configuration:
public class HttpHost
{
public Uri BaseAddress { get; set; }
[JsonConverter(typeof(ClaimConverter))]
public System.Security.Claims Claim { get; set; }
public string Identifier { get; set; }
public TimeSpan Timeout { get; set; }
}
Here's the actual data:
"HttpHosts": [
{
"BaseAddress": "https://localhost/",
"Claim": {
"Issuer": "LOCAL AUTHORITY",
"OriginalIssuer": "LOCAL AUTHORITY",
"Type": "http://my.org/ws/2015/01/identity/claims/mytype",
"Value": "myvalue",
"ValueType": "http://www.w3.org/2001/XMLSchema#string"
},
"Identifier": "ThetaRex.OpenBook.Desktop.Common.OpenBookHost",
"Timeout": "00:01:23"
}],
How do I get the Configuration system to recognize the custom type converter - ClaimConverter - such that it will work when using it with DI:
public HttpHost(IOptions<List<HttpHost>> options)
{
HttpHost httpHost = options.Value.Find(h => h.Identifier == typeof(T).FullName);
...
}
this code was tested in visual studio and working properly
var appSettingsSection = configuration.GetSection("HttpHosts");
HttpHost[] settings = appSettingsSection.Get<HttpHost[]>();
UPDATE
if you need to use a special class Claims you can convert this claim to your class Claims using a mapper or manually ( you can add this code to getter or setter as well)
var claims=settings[0].ClaimDetails;
settings[0].Claims= new System.Security.Claims { Issuer=claims.Issuer, .... }
classes
public class HttpHost
{
public string BaseAddress { get; set; }
[JsonProperty("Claim")]
public ClaimDetails ClaimDetails { private get; set; }
[JsonIgnore]
public System.Security.Claims Claims { get; set; }
public string Identifier { get; set; }
public string Timeout { get; set; }
}
public class ClaimDetails
{
public string Issuer { get; set; }
public string OriginalIssuer { get; set; }
public string Type { get; set; }
public string Value { get; set; }
public string ValueType { get; set; }
}

Deserialize JSON string embedded within JSON directly

I'm using .netcore 3.1 and I'm using System.Text.Json for serialization and deserialization. I didn't know how to phrase my question precisely. I looked around but couldn't find a direct answer for my question.
Apologies if it's a duplicate.
This is a sample JSON response.
{
"properties": {
"subscriptionId": "sub1",
"usageStartTime": "2015-03-03T00:00:00+00:00",
"usageEndTime": "2015-03-04T00:00:00+00:00",
"instanceData": "{\"Microsoft.Resources\":{\"resourceUri\":\"resourceUri1\",\"location\":\"Alaska\",\"tags\":null,\"additionalInfo\":null}}",
"quantity": 2.4000000000,
"meterId": "meterID1"
}
}
I'm interested in directly parsing instanceData.
If you observe closely, instanceData is an embedded JSON string.
{
"Microsoft.Resources": {
"resourceUri": "resourceUri1",
"location": "Alaska",
"tags": null,
"additionalInfo": null
}
}
Question:
Is it possible to parse this instanceData while the whole Json is being parsed? Can we add some Attributes to instanceData field for direct parsing? Right now, I'm accessing the string from the parsed model class and parsing instanceData separately.
This is what I'm doing right now (something like this):
JsonSerializer.Deserialize<MicrosoftResources>(parsedResponse.instanceData).
I have already built model classes for instanceData and other entities. Currently, instanceData is of type string in my root model class.
I'm interested in directly parsing instanceData. If you observe closely, instanceData is an embedded JSON string
Is it possible to parse this instanceData while the whole Json is being parsed?
You can achieve above requirement by creating and using a custom converter, like below.
public class ResConverter : JsonConverter<InstanceData>
{
public override InstanceData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
//you can implement it based on your actual requirement
//...
string jsonData = reader.GetString();
var instanceData = System.Text.Json.JsonSerializer.Deserialize<InstanceData>(jsonData);
return instanceData;
}
Model Classes
public class MyModel
{
public Properties Properties { get; set; }
}
public class Properties
{
public string SubscriptionId { get; set; }
public DateTimeOffset UsageStartTime { get; set; }
public DateTimeOffset UsageEndTime { get; set; }
[JsonConverter(typeof(ResConverter))]
public InstanceData InstanceData { get; set; }
public double Quantity { get; set; }
public string MeterId { get; set; }
}
public class InstanceData
{
[JsonPropertyName("Microsoft.Resources")]
public MicrosoftResources MicrosoftResources { get; set; }
}
public class MicrosoftResources
{
public string ResourceUri { get; set; }
public string Location { get; set; }
public object Tags { get; set; }
public object AdditionalInfo { get; set; }
}
Test code and result
var jsondata = "{\"Properties\":{\"SubscriptionId\":\"sub1\",\"UsageStartTime\":\"2015-03-03T00:00:00+00:00\",\"UsageEndTime\":\"2015-03-04T00:00:00+00:00\",\"InstanceData\":\"{\\u0022Microsoft.Resources\\u0022:{\\u0022ResourceUri\\u0022:\\u0022resourceUri1\\u0022,\\u0022Location\\u0022:\\u0022Alaska\\u0022,\\u0022Tags\\u0022:null,\\u0022AdditionalInfo\\u0022:null}}\",\"Quantity\":2.4,\"MeterId\":\"meterID1\"}}";
MyModel myModel = System.Text.Json.JsonSerializer.Deserialize<MyModel>(jsondata);
If you can change the instanceData to Json instead of string like this.
{
"properties": {
"subscriptionId": "sub1",
"usageStartTime": "2015-03-03T00:00:00+00:00",
"usageEndTime": "2015-03-04T00:00:00+00:00",
"instanceData": {"Microsoft.Resources":{"resourceUri":"resourceUri1","location":"Alaska","tags":null,"additionalInfo":null}},
"quantity": 2.4000000000,
"meterId": "meterID1"
}
}
You can easily deserialize your Json using the example below.
Model Classes:
public partial class Properties
{
[JsonPropertyName("properties")]
public PropertiesClass PropertiesProperties { get; set; }
}
public partial class PropertiesClass
{
[JsonPropertyName("subscriptionId")]
public string SubscriptionId { get; set; }
[JsonPropertyName("usageStartTime")]
public DateTimeOffset UsageStartTime { get; set; }
[JsonPropertyName("usageEndTime")]
public DateTimeOffset UsageEndTime { get; set; }
[JsonPropertyName("instanceData")]
public InstanceData InstanceData { get; set; }
[JsonPropertyName("quantity")]
public double Quantity { get; set; }
[JsonPropertyName("meterId")]
public string MeterId { get; set; }
}
public partial class InstanceData
{
[JsonPropertyName("Microsoft.Resources")]
public MicrosoftResources MicrosoftResources { get; set; }
}
public partial class MicrosoftResources
{
[JsonPropertyName("resourceUri")]
public string ResourceUri { get; set; }
[JsonPropertyName("location")]
public string Location { get; set; }
[JsonPropertyName("tags")]
public object Tags { get; set; }
[JsonPropertyName("additionalInfo")]
public object AdditionalInfo { get; set; }
}
Usage:
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
class Program
{
static void Main(string[] args)
{
// escaped version, just for demo
var json =
"{\r\n \"properties\": {\r\n \"subscriptionId\": \"sub1\",\r\n \"usageStartTime\": \"2015-03-03T00:00:00+00:00\",\r\n \"usageEndTime\": \"2015-03-04T00:00:00+00:00\",\r\n \"instanceData\": {\"Microsoft.Resources\":{\"resourceUri\":\"resourceUri1\",\"location\":\"Alaska\",\"tags\":null,\"additionalInfo\":null}},\r\n \"quantity\": 2.4000000000,\r\n \"meterId\": \"meterID1\"\r\n }\r\n}";
var props = JsonSerializer.Deserialize<Properties>(json);
}
}
Props will have all of the data. I hope this helps.

Reading data from appsettings.json it does not work

I'm trying to read from appsetings.json file some data just like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
//sercies.Configure<Models.AppDate>(Configuration);
services.Configure<Models.AppData>(Configuration.GetSection("AppData"));
//It does not works
string option = Configuration.Get<Models.AppData>().AnotherOption;
//It works
string anotherOption = Configuration["AppData:AnotherOption"];
// Add framework services.
services.AddMvc();
}
With these classes:
public class AppData
{
public Jwt Jwt { get; set; }
public string AnotherOption { get; set; }
}
public class Jwt
{
public string Audience { get; set; }
public string Issuer { get; set; }
}
And in appsettings.json:
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"AppData": {
"Jwt": {
"Audience": "http://localhost:5000",
"Issuer": "http://localhost:5000"
},
"AnotherOption": "Not yet"
}
}
When i debug, option var it's null. ¿How can i implement this?. Ty
I am not really sure why the code above doesn't work.
But I know other ways to do it.
string option = Configuration.GetSection("AppData").Get<Models.AppData>().AnotherOption;
or
string option = ConfigurationBinder.Get<Models.AppData>(Configuration.GetSection("AppData")).AnotherOption;
public IConfiguration Configuration { get; }
Below method can access the data from appsettings.json
public void ConfigureServices(IServiceCollection services)
{
var config = Configuration.GetSection("Application").Get<Application>();
}
//Model class
public class Application
{
public string ServiceUrl { get; set; }
}

How to use a POCO object to access an array of options in the appsettings.json file (ASP.NET 5)

I am using ASP.NET 5 and I want to use POCO classes to access my appsettings.json file. This file looks like this:
{
"Data": {
"ErpSystemConnection": {
"ConnectionString": "[myConnectionString]"
}
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Verbose",
"System": "Information",
"Microsoft": "Information"
}
},
"GoogleAnalytics": {
"Account": [
{
"Name": "AccountName",
"ServiceAccountEmailAddress": "someEmail#someaccount.iam.gserviceaccount.com",
"KeyFileName": "key1.p12",
"Password": "notasecret"
},
{
"Name": "AnotherAccount",
"ServiceAccountEmailAddress": "anotherEmailAccount#someotheraccount.iam.gserviceaccount.com",
"KeyFileName": "key2.p12",
"Password": "notasecret"
}
],
"KeyFilePath": "/googleApis/"
}
}
The 'GoogleAnalytics' key contains an array of accounts that I wish to be able to access in a collection either as a list or an array.
I created a POCO to represent this key that contains a corresponding collection of 'Account' objects:
public class GoogleAnalytics
{
public Account[] Account { get; set; } = new Account[1];
public string KeyFilePath { get; set; }
public GoogleAnalytics()
{
}
}
And the 'Account' object:
public class Account
{
private const string _applicationName = #"Storefront Analytics";
private X509Certificate2 _certificate;
private ServiceAccountCredential _credential;
private AnalyticsService _service;
#region |--Properties--|
public string Name { get; set; }
public string Password { get; set; }
public string ServiceAccountEmailAddress { get; set; }
public string KeyFileName { get; set; }
public string KeyFilePath { get; set; }
public string KeyFileFullPath
{
get
{
return $"{KeyFilePath}{KeyFileName}";
}
}
public X509Certificate2 Certificate
{
get
{
if(_certificate == null)
{
ConfigureInstance();
}
return _certificate;
}
set
{
_certificate = value;
}
}
public ServiceAccountCredential Credential
{
get
{
if (_credential == null)
{
ConfigureInstance();
}
return _credential;
}
set
{
_credential = value;
}
}
public AnalyticsService Service
{
get
{
if (_service == null)
{
ConfigureInstance();
}
return _service;
}
set
{
_service = value;
}
}
#endregion
#region |--Constructors--|
public Account()
{
}
public Account(string password, string keyFileName,
string keyFilePath,
string serviceAccountEmailAddress, string accountName)
{
//TODO: Validate parameters
Password = password;
KeyFileName = keyFileName;
KeyFilePath = keyFilePath;
ServiceAccountEmailAddress = serviceAccountEmailAddress;
Name = accountName;
}
#endregion
private void ConfigureInstance()
{
Certificate = new X509Certificate2(KeyFileFullPath, Password, X509KeyStorageFlags.Exportable);
Credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(ServiceAccountEmailAddress)
{
Scopes = new[] { AnalyticsService.Scope.Analytics }
});
Service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = Credential,
ApplicationName = _applicationName
});
}
}
My Controller:
public class GoogleAnalyticsController : Controller
{
#region |--Properties--|
[FromServices]
private IGoogleAnalyticsRepository _repo { get; set; }
#endregion
public GoogleAnalyticsController(IOptions<GoogleAnalytics> options)
{
var temp = options.Value;
}
}
The 'KeyFilePath' property is properly set in the IOptions instance.
The problem I am having is that the Account array contains null references - none of the accounts are being instantiated. I wondering if I am doing this wrong, or the Options Model doesn't support this type of behavior at this time?
Update in response to Shaun Luttin's answer
I implemented the changes listing in Shaun Luttin's answer. There seems to have been an additional problem. For whatever reason, all of the Account instances' properties were null until I simplified the class as follows:
public class Account
{
public string Name { get; set; }
public string Password { get; set; }
public string ServiceAccountEmailAddress { get; set; }
public string KeyFileName { get; set; }
public string KeyFilePath { get; set; }
}
Short Answer
I wondering if I am doing this wrong, or the Options Model doesn't support this type of behavior at this time?
You are doing one thing wrong. The Options Model does support arrays. You need NOT to initialize your array property with an array of size [1].
public Account[] Account { get; set; } = new Account[1]; // wrong
public Account[] Account { get; set; } // right
Demo
Here is a sample, just for you, that you can find here on GitHub.
MyOptions.cs
namespace OptionsExample
{
public class MyObj
{
public string Name { get; set; }
}
public class MyOptions
{
public string Option1 { get; set; }
public string[] Option2 { get; set; }
public MyObj[] MyObj { get; set; }
}
}
Startup.cs
namespace OptionsExample
{
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.OptionsModel;
using System.Linq;
public class Startup
{
public IConfigurationRoot Config { get; set; }
public Startup(IHostingEnvironment env)
{
Config = new ConfigurationBuilder().AddJsonFile("myoptions.json").Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<MyOptions>(Config);
}
public void Configure(IApplicationBuilder app,
IOptions<MyOptions> opts)
{
app.Run(async (context) =>
{
var message = string.Join(",", opts.Value.MyObj.Select(a => a.Name));
await context.Response.WriteAsync(message);
});
}
}
}
myoptions.json
{
"option1": "option1val",
"option2": [
"option2val1",
"option2val2",
"option2val3"
],
"MyObj": [
{
"Name": "MyObj1"
},
{
"Name": "MyObj2"
}
]
}
project.json dependencies
"dependencies": {
"Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final"
}
Output