Strongly Typed Settings for Xamarin Android
Recently I've been dabbling a bit in the mobile development space. Ignorantly I tried to add an App.config to my Xamarin Android project, but there was no option available in the Add New Item dialog for it... Hmmm...?
Where on earth do I store configuration settings for your mobile application?
I was a little bit stumped, so I did what any self-respecting developer will do, and started googling. Here is some of the good suggestions that people had:
- Use the #if preprocessor directive to build up your configuration settings.
- Use a library called PCLAppConfig , that allows you to add App.config as an AndroidAsset and it will pretty much work like ConfigurationManager with a few additional setup steps.
- A custom file configuration loader, as explained in this post.
There is a lot of options out there to try, but I wanted to try something else... How else will I make up content for my blog? I wanted to build something that is:
- Build configuration specific,
- Strongly typed,
- Automatically updates on build, and;
- Flexible
1. Build Configuration Specific - SlowCheetah
To make this solution build configuration specific, we are going to make use of transform files. This post explains transform files really well and I've included an extract from the post below.
A transform file is an XML file that specifies how the Web.config file should be changed when it is deployed
...
The purpose of these files is to generate a new Web.config file depending upon the environment. It's extremely useful to store settings in these files...
As you can see, this is very useful indeed. However, they are only mentioning Web.config files, what about other XML files? I've used SlowCheetah in the past to achieve this, and it is a fantastic tool!
It allows you to transform any XML-based file based on your build configuration. E.g. you can create a file called Bob.xml, right-click and choose to add transforms. For this to work from Visual Studio you need to install the extension and whenever you add a transform to an xml file it will add the appropriate build targets to your .csproj file and a NuGet package.
We'll start by adding a text file and renaming it to App.config, and add the following XML with single app setting for the BaseServerUrl:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="BaseServerUrl" value="http://dev.product/api"/>
</appSettings>
</configuration>
We'll then add transforms for our build configurations, by right-clicking on App.config, and selecting Add Transform:
A prompt will popup and you can just click Yes to proceed:
This will add files to our solution for our build configurations, namely App.Debug.config and App.Release.config:
It will also add the SlowCheetah package as a NuGet dependency to your packages.config file:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SlowCheetah" version="2.5.48" targetFramework="portable45-net45+win8+wpa81" />
</packages>
As well as add changes to your project file (.csproj):
Configuration files
<None Include="App.config">
<TransformOnBuild>true</TransformOnBuild>
</None>
<None Include="App.Debug.config">
<DependentUpon>App.config</DependentUpon>
<IsTransformFile>True</IsTransformFile>
</None>
<None Include="App.Release.config">
<DependentUpon>App.config</DependentUpon>
<IsTransformFile>True</IsTransformFile>
</None>
Targets:
<Import Project="..\..\packages\SlowCheetah.2.5.48\build\SlowCheetah.targets" Condition="Exists('..\..\packages\SlowCheetah.2.5.48\build\SlowCheetah.targets')" />
Finally, we'll just add transforms for our BaseServerUrl app setting.
App.Debug.config:
<?xml version="1.0" encoding="utf-8" ?>
<!-- For more information on using transformations
see the web.config examples at http://go.microsoft.com/fwlink/?LinkId=214134. -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="BaseServerUrl" value="http://dev.product/api" xdt:Locator="Match(key)" xdt:Transform="Replace" />
</appSettings>
</configuration>
App.Release.config:
<?xml version="1.0" encoding="utf-8" ?>
<!-- For more information on using transformations
see the web.config examples at http://go.microsoft.com/fwlink/?LinkId=214134. -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="BaseServerUrl" value="http://prod.product/api" xdt:Locator="Match(key)" xdt:Transform="Replace" />
</appSettings>
</configuration>
SlowCheetah will transform the XML file and the resulting file will be copied to project's output directory. SlowCheetah will not update the source file, only the output file. I would like to do that. You can do this by adding a BeforeBuild target to the project file (.csproj):
<Target Name="BeforeBuild">
<TransformXml Condition="Exists('App.$(Configuration).config')" Source="App.config" Transform="App.$(Configuration).config" Destination="App.config" />
</Target>
And build the project to test it out. Go ahead and change the configuration of your project and see how the App.config file changes.
With that, SlowCheetah satisfies our first requirement: Build Configuration Specific.
2. Strongly typed - T4 Templates
I like T4 Templates. Microsoft seems to make good use of it, especially with Entity Framework (*.edmx), and on the Xamarin Android side with the Resources file. The Resources file updates on build, so, I thought I'd try and make it work for our scenario. Let's start by adding a new item to our project and searching for "text template", select the template, and call it CoreSettings.tt:
Add the following code to the file:
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Xml" #>
<#
var className = Path.GetFileName(Host.TemplateFile).Replace(".tt", string.Empty);
#>
/*
Auto-generated code, do not change. If you really need to change something, for example, you need to do this for Web.config, you
can change the template here: '<#= Host.TemplateFile #>'.
*/
namespace <#= GetNamespace() #>
{
public class <#= className #>
{
private static <#= className #> _current;
public static <#= className #> Current => _current ?? (_current = new <#= className #>());
<#
foreach (var item in this.GetAppSettings())
{
#>
public string <#= item.Key #> => "<#= item.Value #>";
<# } #>
}
}
<#+
public Dictionary<string, string> GetAppSettings()
{
var pathToHost = Path.GetDirectoryName(Host.TemplateFile);
Dictionary<string, string> result = new Dictionary<string, string>();
XmlDocument doc = new XmlDocument();
string absolutePath = Path.Combine(pathToHost, "App.config");
doc.Load(absolutePath);
foreach (XmlNode node in doc.SelectNodes("/configuration/appSettings/add"))
{
result.Add(node.Attributes["key"].InnerText, node.Attributes["value"].InnerText);
}
return result;
}
#>
<#+
private string GetNamespace()
{
var pathToHost = Path.GetDirectoryName(Host.TemplateFile);
var namespaceStr = pathToHost.ToString().Substring(pathToHost.LastIndexOf("\\") + 1);
return namespaceStr;
}
#>
The T4 template basically creates a new C# file with the same name and reads the appSettings node from the App.config file. When you save the changes to CoreSettings.tt, CoreSettings.cs will update and now you have a strongly typed settings class:
/*
Auto-generated code, do not change. If you really need to change something, for example, you need to do this for Web.config, you
can change the template here: 'C:\Development\Personal\BarelyCompetent.XamarinSettings\src\BarelyCompetent.XamarinSettings.Core\CoreSettings.tt'.
*/
namespace BarelyCompetent.XamarinSettings.Core
{
public class CoreSettings
{
private static CoreSettings _current;
public static CoreSettings Current => _current ?? (_current = new CoreSettings());
public string BaseServerUrl => "http://dev.product/api";
}
}
I am by no means a T4 expert and the above code is probably not the best way of doing it, but it works for my scenario and you can edit it as you wish.
The T4 Template satisfies our second requirement: Strongly typed, but you must find and save the CoreSettings.tt file every time to get the latest update.
3. Automatically updates on build - Clarius.TransformOnBuild
I recently came across a library called Clarius.TransformOnBuild. The library consists of a MSBuild targets file, that will try to find the Transform.exe (this is what T4 files use in the background) file in the environment that you are building from. If it finds the executable, it will traverse your solution files for items with a Build Action of None that can be transformed.
To get this working you can add the NuGet package to your solution.
Install-Package Clarius.TransformOnBuild
And change the Build Action of CoreSettings.tt to None:
If you build your solution now, you will see that the CoreSettings.cs file will be updated. Clarius.TransformOnBuild satisfies our 3rd requirement: Automatically updates on build.
Finally, we can now use consume our CoreSettings.cs file:
CoreSettings.Current.BaseServerUrl
4. Flexible
In terms of flexibility the solution is simple, and you can extend it easily by changing the T4 Template, or adding additional setting files for different projects. This was written with Xamarin Android in mind, but you can apply the same principles for any .Net project.
Please let me know your thoughts in the comments section below.