Coming from an ASP.NET Core world, I am used to working with appsettings.json files to store any configuration values. Multiple environments are easily supported by creating additional, nested files: appsettings.dev.json, appsettings.staging.json, …
Unfortunately, Xamarin doesn’t support such files out of the box. Instead of resorting to hard-coding settings in static files, we can leverage MSBuild and Json.NET to store our settings in json files in a cross-platform manner.
The idea is simple: we create folders per Build Configuration that each contain a config.json file. This json file contains the settings for the given Build Configuration (the environment, if you will).
After each successful build, the config.json file under the corresponding folder is copied to another folder using an MSBuild Copy Task. During construction of our IoC container (when the app starts up), the json file in that folder is deserialized to an object and registered as a singleton in our container.
For example: the config.json file under Debug contains an ApiUrl that connects to a staging environment, whereas the settings under Release point to the production environment.
When building with the Debug Build Configuration, the config file in the Debug folder is copied and eventually gets deserialized and registered.
When making requests to the Api in our app, said requests will always be routed to the staging Api.
When using the Release configuration, on the other hand, the config.json file containing the production url is used. Therefore, all subsequent requests from our app are routed to our production environment.
Need more? Supporting additional environments is as easy as creating additional Build Configurations and matching folders.
This also works well on CI build servers since they always have to pass a Build Configuration to MSBuild to build your project. You could test your app locally with the Debug Configuration and let your CI server output artifacts for e.g. a staging app and a store-ready app with corresponding, custom Build Configurations.
Consider the following solution:
I want to keep the configuration files in the shared netstandard project Foo, since they apply for both Android and iOS.
First, we add 2 folders to Foo, namely Configuration-Source and Configuration . The Configuration-Source folder contains 2 subfolders: Debug and Release. Create a config.json file in both of these folders.
The content of these files, in this example, is:
This is what our project looks like, with the Configuration folders:
To copy the correct config.json file to our Configuration folder, we will leverage MSBuild’s Copy Task. This task is simple enough: it takes in an array of SourceFiles and copies them to the given DestinationFolder.
Let’s update the csproj file of Foo so the Copy Task runs after each build. Add the following snippet right below the
Before we continue, let’s take a look at what’s going on here.
The AfterTargets="Build" attribute indicates that this target runs right after the Build target completes. If the build should fail, the files aren’t copied.
$(MsBuildProjectDirectory) and $(Configuration) are MSBuild Project Properties that will output, respectively, the directory of the current Project and the Build Configuration that was passed to MSBuild. This is how our Copy Task knows that it should copy the config.json file from our Debug folder when using the Debug Build Configuration (or the Release folder when using the Release configuration).
The DestinationFolder property receives the path to our Configuration folder, instructing the Copy Task to paste the files in that folder.
Before we dive into deserializing our config into objects, we have to locate the physical file on disc first. Since we’re building a cross-platform app, I’m reluctant to use e.g. Directory.GetCurrentDirectory() or Environment.CurrentDirectory and work my way up to the Configuration folder — we simply can’t make any assumptions about the underlying file system at runtime. It could be Android, iOS or UWP.
Instead, I prefer to embed our config.json file into the Foo Assembly as an Embedded Resource. We can then use the GetManifestResourceStream method to read the Resource as a stream. No pathfinding needed.
By default, the config.json file that is copied in the Configuration folder will have None as its Build Action. To mark it as an Embedded Resource, add the following node to the csproj of Foo:
Great, we now have a json file that contains our environment-specific settings as an Embedded Resource in our netstandard library.
As I mentioned before, we can leverage GetManifestResourceStream to read the file as a stream. The result of that read operation, a string containing our json contents, can be deserialized into an object.
That object, in this example, looks simple enough:
Notice how I decorated the class Configuration with an interface IConfiguration. This isn’t necessary, but since this configuration is going to be injected, it makes sense to inject the interface and provide Configuration as the implementation through our IoC container. Among others, this makes for less coupling and easier unit testing.
The following example shows how to deserialize our file into such a Configuration object:
Let’s break this down step by step.
First, I’m using Autofac as an Inversion of Control container. Autofac enables splitting registrations into separate files using Modules. Anything related to configuration gets registered in the container in this ConfigurationModule.
Using Assembly.GetAssembly(typeof(IConfiguration)) , I get the assembly that contains the IConfiguration class. This assumes that your config.json files are in the same assembly as IConfiguration. In our example, I could also have used Assembly.GetExecutingAssembly() — however, looking specifically for the assembly of IConfigration is a lot safer and less prone to break.
As soon as I have our Assembly, I invoke our aforementioned friend GetManifestResourceStream. Note that it takes in a string parameter: this is the name of the Embedded Resource. By default, the name of an Embedded Resource is namespace.filename, in our case: Foo.Configuration.config.json. You can change this name using the Logical Name property in the csproj file; see this blogpost for a detailed explanation.
The stream of that Embedded Resource is read into a string, jsonString, which is then deserialized into a Configuration object using Json.NET.
That object is then registered into the container as IConfiguration and scoped as SingleInstance so every request to the container gets the same instance.
And that’s it! Now we can inject this IConfiguration into whoever needs it. Our code just uses the interface and is completely decoupled from any environment-specific logic.
👏 Thanks for reading! 👏
Fill in the form below and we’ll get back to you as soon as possible.
Thanks for getting in touch!Oops. You seem to have written your full name in invisible ink. Please enter it so we can read it. Oops. You seem to have written your company in invisible ink. Please enter it so we can read it. It seems your e-mail doesn’t exist. Please enter a real one so we can contact you. Oops. You seem to have written your telephone in invisible ink. Please enter it so we can read it. Sorry, we could not send the enquiry.