10

I am trying to supply some additional configuration data to my asp 5 mvc 6 web app ("rc1-final" libraries) to be deployed on Azure. Part of this additional data consists of an array of data in a json file. I can add this json file to my configuration, and while debugging if I watch the configuration object I can browse to the provider entry and see it does in fact contact the array of data I'm looking for.

However, trying Configuration["extraData"] yields null (as the "value" of that property is null, even though it clearly contains the array of elements.

I have no problem accessing single values that are stored in json config (such as my database connection string, and other values.) It just seems as though json data in array format is not supported?

See the following sample json file (which validates according to jsonlint and seems fine to me)...

//extraData.json
{
  "ExtraData": [
    {
      "Id": 1,
      "Description": "The first identifier"
    },
    {
      "Id": 2,
      "Description": "The second identifier"
    },
    {
      "Id": 3,
      "Description": "The third identifier"
    }
  ]
}

...and the following sample Startup.cs...

//Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Data.Entity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
using System.IO;
using Microsoft.AspNet.Authentication.Cookies;
using System.Net;


namespace MyProj {
    public class Startup {
        public static IConfigurationRoot Configuration;

        public Startup(IHostingEnvironment env) {
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddJsonFile("extraData.json")
                .AddEnvironmentVariables();

            Configuration = builder.Build();
        }

        public void Configure(
            IApplicationBuilder app,
            IHostingEnvironment env,
            MyDbSeed seed,
            ILoggerFactory logging) {

            dynamic xData = JObject.Parse("ExtraData"); //VALUE IS NULL, THROWS EX
            var myList = JsonConvert
                .DeserializeObject<List<MyID>>(
                    xData.ExtraData.ToString()
                );
            //Do something important with this list
        }

        public static void Main(string[] args) => WebApplication.Run<Startup>(args);
    }

    class MyID {
        public int ID { get; set; }
        public string Description { get; set; }
    }
}

What I've been having to do instead (which seems hackish, especially given the handy Configuration system now available)...

dynamic xData = JObject.Parse(
    File.OpenText(
        env.MapPath(
            @"..\extraData.json"
        )
    ).ReadToEnd()
);
var xList = JsonConvert
    .DeserializeObject<List<MyID>>(
        xData.ExtraData.ToString()
    );
//Do something important with the list, eg...
var o = new MyClass(xList);

I'm also uncertain as to the support on azure to do file io like this.

I found this resource which suggests that array/IEnum is supported, but it is just using an in-mem provider...
http://github.com/aspnet/Configuration/blob/dev/test/Microsoft.Extensions.Configuration.Binder.Test/ConfigurationCollectionBindingTests.cs

This resource is the documentation from MS...
http://docs.asp.net/en/latest/fundamentals/configuration.html

Some related questions that have turned up while searching since posted (not 1:1 related to my issue necessarily, but could prove useful for others searching)
How to use ConfigurationBinder in Configure method of startup.cs
Retrieve sections from config.json in ASP.NET 5
ASP.NET 5 (vNext) - Getting a Configuration Setting

Community
  • 1
  • 1
t.j.
  • 1,227
  • 3
  • 16
  • 30
  • Your cases don't match -- `"extraData"` vs `"ExtraData"`. – dbc Dec 04 '15 at 07:26
  • `JObject.Parse("ExtraData");` what do you expect this to do? Use [ConfigurationBinder](https://github.com/aspnet/Configuration/blob/dev/src/Microsoft.Extensions.Configuration.Binder/ConfigurationBinder.cs) instead – Victor Hurdugaci Dec 04 '15 at 07:48
  • `JObject.Parse(string)` parses the argument as json. In your case, it parses "ExtraData" as JSON... – Victor Hurdugaci Dec 04 '15 at 07:49

4 Answers4

29

We can now use GetSection with GetChildren and some LINQ.

// appsettings.json
{
    "SomeArray": [
        "Foo", 
        "Bar",
        "Baz"
    ]
}

// Startup.cs
var someArray = configuration
    .GetSection("SomeArray")
    .GetChildren()
    .Select(x => x.Value)
    .ToArray();

There is also a demo in the Configuration repository's ArrayTest.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
9

try this out:

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.OptionsModel;
using System.Collections.Generic;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            // Set up configuration sources.
            var builder = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .AddJsonFile("extraData.json")
                .AddEnvironmentVariables();

            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; set; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();
            services.Configure<List<ExtraData>>(Configuration.GetSection("ExtraData"));
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            List<ExtraData> result = app.ApplicationServices.GetRequiredService<IOptions<List<ExtraData>>>().Value;

            // you should get what you need. It works in my computer with rc1 update 1
        }

        public static void Main(string[] args) => WebApplication.Run<Startup>(args);
    }

    public class ExtraData
    {
        public int Id { get; set; }
        public string Description { get; set; }
    }
}

EDIT

If compiler complains services.Configure<List<ExtraData>>(Configuration.GetSection("ExtraData")), please add a NuGet reference to Microsoft.Extensions.Options.ConfigurationExtensions

You may also inject it into constructor:

// using Microsoft.Extensions.Options;
public class HomeController : Controller
{
    private List<ExtraData> _extraData;
    public HomeController(IOptions<List<ExtraData>> extraData)
    {
        _extraData = extraData.Value;
    }
}
Ricky
  • 10,044
  • 3
  • 26
  • 31
  • That worked fantastically, thank you! I saw the IOptions bit in the documentation, but it seemed a bit overkill for just parsing the json and grabbing what I needed. In retrospect, using the standard routines is likely a lot less headache in the long run than the hackish method I was using! – t.j. Dec 04 '15 at 19:10
  • Additionally, I injected the IOptions as described in this post... http://stackoverflow.com/a/31914408/3246805 – t.j. Dec 04 '15 at 19:12
  • I don't think this is valid any more. The line `services.Configure>(Configuration.GetSection("ExtraData"));` will error because `Configure` takes a param of type `Action`. Anyone know how to fix that? – Sean Jan 17 '16 at 17:15
  • 1
    @Sean please add a new reference to `Microsoft.Extensions.Options.ConfigurationExtensions` package in project.json. – Ricky Jan 18 '16 at 00:38
  • 1
    Using Options is probably the best practice in most situations, but if you already have the Configuration object and want to read the settings directly all you need is `var result = Configuration.Get("ExtraData");` – PointZeroTwo Mar 30 '16 at 19:52
  • How does this work as a service to be injected into a constructor? – flexxxit Dec 13 '16 at 11:47
  • @flexxxit I have edited my answer to show how to use it. – Ricky Dec 14 '16 at 01:15
6

You can retrieve settings this way:

var extraDataSection = configuration.GetSection("ExtraData");
foreach (IConfigurationSection section in extraDataSection.GetChildren())
{
    var id = section.GetValue<int>("Id");
    var description = section.GetValue<string>("Description");
}
Dmitry Pavlov
  • 30,789
  • 8
  • 97
  • 121
1

With the latest versions I was able to solve this without any additional NuGet packages:

int extraDataCount = 0;
string id = Configuration[GetId(extraDataCount)];
string description = Configuration[GetDescription(extraDataCount)];

var extraDataList = new List<MyID>();
while(id != null && description != null)
{
    extraDataList.Add(new MyID { ID = id, Description = description });
    extraDataCount++;
    id = Configuration[GetId(extraDataCount)];
    description = Configuration[GetDescription(extraDataCount)];
}

private string GetId(int extraDataCount)
{
    return "ExtraData:" + extraDataCount + ":Id";
}

private string GetDescription(int extraDataCount)
{
    return "ExtraData:" + extraDataCount + ":Description";
}