Configuration Files In Xamarin Forms

The Xamarin Forms framework does not include a way to read configuration files out of the box, much like ASP.NET. Because of this, many developers hard code static values. In these configuration files developers will normally put API uri’s, local DB connection strings etc. However this makes it hard to manage, especially when going into production or using Continuous Integration and only exacerbated by platform specific values. Plus you don’t want production configuration settings in your source control.

I have created my own configuration file loader, portable library compatible, that gets loaded into a configuration object. This approach involves creating config.json and platform.json and loading them into an object that implements IConfiguration. This solution does require Newtonsoft.Json.

Note: These configuration files are for mobile app configuration, not user configurable settings.

files_tablet

JSON Configuration File

In your portable library create config.json, Build Action as Content and Copy Always. Below is an example.

{
    "BusinessRules": {
        "UsernameMinLength": 4
    },
    "Infrastructure": {
        "SQLiteDatabase": "db.db3",
        "AzureApi": "https://xxxxxx.azurewebsites.net"
    }
}

For UWP this is enough, however Android and iOS can’t read files copied as content from a portable library. The easiest way to do this is with linked files.

iOS

  1. In the project root, right click and Add > Existing Item
  2. Go to your PCL folder and find the JSON file.
  3. Though instead of pressing add, press the drop down, next to the Add button, and Add as Link.

Or, if you want to use the Pre-Build event to copy the file you can do this.

xcopy "$(ProjectDir)..\MobilePortableProject\config.json" "$(ProjectDir)" /Y /R

Android

  1. In the Assets folder, right click and Add > Existing Item
  2. Go to your PCL folder and find the JSON file.
  3. Though instead of pressing add, press the drop down, next to the Add button, and Add as Link.

Or, if you want to use the Pre-Build event to copy the file you can do this.

xcopy "$(ProjectDir)..\MobilePortableProject\config.json" "$(ProjectDir)\Assets\" /Y

Platform JSON File

Now lets create a separate platform.json file for platform specific configuration values.

{
    "HockeyAppId": "6xx6xxxxxxxxxxxxxx77xx1"
}

In UWP and iOS create the file in the project root and set Build Action to Content. For Android create in the Assets Folder and set Build Action to AndroidAsset.

Configuration Interface

public interface IConfiguration
{
    IInfrastructure Infrastructure { get; }

    IBusinessRules BusinessRules { get; }

    IPlatform Platform { get; }
}

public interface IBusinessRules
{
    int UsernameMinLength { get; }
}

public interface IInfrastructure
{
    string AzureApi { get; }
    string SQLiteDatabase { get; }
}

public interface IPlatform
{
    string HockeyAppId { get; }
}

When you create your class implementing this interface, don’t forget to also provide the set method, so they can be loaded. Only the interface’s have no set method to ensure developers don’t get any idea that they can be changed.

Reading Files

To read the files we need to create an IFileStorage object to read the files from each platform. You can use your own file retrieval code, however I am detailing the reading of the files here so you can see exactly how it is reading the file.

iOS

public class FileStorage : IFileStorage
{
    public Task<byte[]> ReadAsBytes(string filename)
    {
        var data = File.ReadAllBytes(filename);

        if (data != null)
            data = data.CleanByteOrderMark();

        return Task.FromResult(data);
    }

    public async Task<string> ReadAsString(string filename)
    {
        var data = await ReadAsBytes(filename);

        if (data == null)
             return string.Empty;

        return System.Text.Encoding.UTF8.GetString(data);
     }
}

Android

public class FileStorage : IFileStorage
{ 
    private Context _context = Application.Context;
    public async Task<string> ReadAsString(string filename)
    {
        using (var asset = _context.Assets.Open(filename))
        using (var streamReader = new StreamReader(asset))
        {
            return await streamReader.ReadToEndAsync();
        }
    }
}

UWP

public class FileStorage : IFileStorage
{
    public async Task<string> ReadAsString(string filename)
    {
        var bytes = await ReadAsBytes(filename);
        return Encoding.UTF8.GetString(bytes.CleanByteOrderMark());
    }
 
    public async Task<byte[]> ReadAsBytes(string filename)
    {
        var folderStructure = "MobileProjectName/";
        var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri($"ms-appx:///{folderStructure}{filename}"));

        var buffer = await FileIO.ReadBufferAsync(file);
        using (var dataReader = Windows.Storage.Streams.DataReader.FromBuffer(buffer))
        {
            byte[] bytes = new byte[buffer.Length];
            dataReader.ReadBytes(bytes);
            return bytes;
        }
     }
}

Clean ByteOrderMark (BOM)

You will find on some platforms, a BOM is placed at the start of each json file. While it looks like the string is coming back perfectly fine, it has a small BOM hidden at the beginning, which will cause issues when deserializing.

public static byte[] CleanByteOrderMark(this byte[] bytes)
{ 
    var bom = new byte[] { 0xEF, 0xBB, 0xBF };
    var empty = Enumerable.Empty<byte>();
    if (bytes.Take(3).SequenceEqual(bom))
        return bytes.Skip(3).ToArray();

    return bytes; 
}

Loading Configuration Files

To load the configuration I have a function that brings back the IConfiguration object. We pass in the IFileStorage from above.

public static async Task<IConfiguration> Build(IFileStorage fileStorage)
{ 
    var platformFile = await fileStorage.ReadAsString("platform.json");

    var platform = JsonConvert.DeserializeObject<Platform>(platformFile);
 
    var configurationFile = await fileStorage.ReadAsString("config.json");

    var configuration = JsonConvert.DeserializeObject<Definition.Configuration>(configurationFile);

    configuration.Platform = platform;

    return configuration;
}

Now you can inject IConfiguration with your Dependency Injection framework and use it throughout your mobile app.

Benefits

It seems overly complicated to perform just a simple reading of values, however the benefits come into play in the following ways.

  1. Don’t have production secrets or keys in your source controlled config files.
  2. Easily replace config files during your CI deployment.
  3. Easily separate platform specific configuration values.
  4. Easier unit testing, just inject an object that implements IConfiguration.
Microsoft MVP | Xamarin MVP | Xamarin Certified Developer |
Exrin MVVM Framework | Xamarin Forms Developer | Melbourne, Australia

Related Posts

12 Comments

  1. Rob

    I don’t understand why you say you can’t read json files stored in PCL, it’s pretty simple.

    var assembly = typeof(ApiService).GetTypeInfo().Assembly;
    using (Stream stream = assembly.GetManifestResourceStream($”LINK.Mobile.Resources.{filename}”))
    {
    using (var reader = new System.IO.StreamReader(stream))
    {
    string json = reader.ReadToEnd();

    }
    }

    Maybe I am missing something though

    1. Adam Pedley

      The only reason I steered clear of that is because the Resource is embedded, whereas the JSON file is a separate file that can be modified post build.

      However it could be added as an embedded resource, as your CI process could switch the file pre-build.

      To answer your question directly, specifically a file marked as Content in a PCL, can’t be accessed was what I was referring to.

  2. Rob

    Here’s a more clean version:

    var assembly = typeof(App).GetTypeInfo().Assembly;
    using (Stream stream = assembly.GetManifestResourceStream(filename))
    {
    using (var reader = new StreamReader(stream))
    {
    string json = reader.ReadToEnd();
    }
    }

  3. Sebastián Cabrera

    Very good and useful post! Before I used the compilation variables but I was not completely satisfied.

    Only some notes:

    In json config there is a curly brace missing at the end.

    Under Configuration Interface – IPlatform under the class, but the interface is called IPlatformConfiguration

    Under Reading Files (Android) I had to use “Application.Context.Assets.Open(filename)”

Leave A Comment?