Monodroid: Where should I put configuration settings? - configuration

From Miguel de Icaza:
We use a library profile that is better suited for mobile devices, so we removed features that are not necessary (like the entire System.Configuration stack, just like Silverlight does).
After years of .NET development, I'm accustomed to storing configuration settings in web.config and app.config files.
When using Mono for Android, where should I put my configuration settings?
If it matters, I'd like to store different configuration settings for different build configurations as well.

I would probably recommend using shared preferences and compilation symbols to manage different configurations. Below is an example of how you can use a preferences file to add or change keys based on the compilation symbols. Additionally, you could create a separate preferences file that is only available for a particular configuration. Because these keys are not available on all configurations, make sure to always perform checks for them before using.
var prefs = this.GetSharedPreferences("Config File Name", FileCreationMode.Private);
var editor = prefs.Edit();
#if MonoRelease
editor.PutString("MyKey", "My Release Value");
editor.PutString("ReleaseKey", "My Release Value");
#else
editor.PutString("MyKey", "My Debug Value");
editor.PutString("DebugKey", "My Debug Value");
#endif
editor.PutString("CommonKey", "Common Value");
editor.Commit();

We have had exactly the same problem in our current project.
My first impulse was to put the configuration in a sqlite key-value table but then my internal customer reminded me the main reason for a configuration file - it should support simple editing.
So instead we created an XML file and put it there:
string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
And access it using these properties:
public string this[string key]
{
get
{
var document = XDocument.Load(ConfigurationFilePath);
var values = from n in document.Root.Elements()
where n.Name == key
select n.Value;
if(values.Any())
{
return values.First();
}
return null;
}
set
{
var document = XDocument.Load(ConfigurationFilePath);
var values = from n in document.Root.Elements()
where n.Name == key
select n;
if(values.Any())
{
values.First().Value = value;
}
else
{
document.Root.Add(new XElement(key, value));
}
document.Save(ConfigurationFilePath);
}
}
}
via a singleton class we call Configuration so for .NET developers it is very similar to using the app.config files. Might not be the most efficient solution but it gets the job done.

there's a Xamarin centric AppSetting reader: https://www.nuget.org/packages/PCLAppConfig
pretty useful for continuous delivery (so a deployment server such as octopus allows to alter your config file for each environment with values stored on the cd server)
there's a Xamarin centric AppSetting reader available at https://www.nuget.org/packages/PCLAppConfig
it is pretty useful for continuous delivery;
use as per below:
1) Add the nuget package reference to your pcl and platforms projects.
2) Add a app.config file on your PCL project, then as a linked file on all your platform projects. For android, make sure to set the build action to 'AndroidAsset', for UWP set the build action to 'Content'. Add you settings keys/values: <add key="config.text" value="hello from app.settings!" />
3) Initialize the ConfigurationManager.AppSettings on each of your platform project, just after the 'Xamarin.Forms.Forms.Init' statement, that's on AppDelegate in iOS, MainActivity.cs in Android, App in UWP/Windows 8.1/WP 8.1:
ConfigurationManager.Initialise(PCLAppConfig.FileSystemStream.PortableStream.Current);
3) Read your settings : ConfigurationManager.AppSettings["config.text"];

ITNOA
Maybe PCLAppConfig is help you to create and read from app.config in Xamarin.Forms PCL Project or other Xamarin projects.
For different configuration in different build mode such as release and debug you can use Configuration Transform on app.config.

Related

Can you preview ASP.NET Core's appsettings.json environment overrides?

In ASP.NET Core, the JsonConfigurationProvider will load configuration from appsettings.json, and then will read in the environment version, appsettings.{Environment}.json, based on what IHostingEnvironment.EnvironmentName is. The environment version can override the values of the base appsettings.json.
Is there any reasonable way to preview what the resulting overridden configuration looks like?
Obviously, you could write unit tests that explicitly test that elements are overridden to your expectations, but that would be a very laborious workaround with upkeep for every time you change a setting. It's not a good solution if you just wanted to validate that you didn't misplace a bracket or misspell an element name.
Back in ASP.NET's web.config transforms, you could simply right-click on a transform in Visual Studio and choose "Preview Transform". There are also many other ways to preview an XSLT transform outside of Visual Studio. Even for web.config parameterization with Parameters.xml, you could at least execute Web Deploy and review the resulting web.config to make sure it came out right.
There does not seem to be any built-in way to preview appsettings.{Environment}.json's effects on the base file in Visual Studio. I haven't been able to find anything outside of VS to help with this either. JSON overriding doesn't appear to be all that commonplace, even though it is now an integral part of ASP.NET Core.
I've figured out you can achieve a preview with Json.NET's Merge function after loading the appsettings files into JObjects.
Here's a simple console app demonstrating this. Provide it the path to where your appsettings files are and it will emit previews of how they'll look in each environment.
static void Main(string[] args)
{
string targetPath = #"C:\path\to\my\app";
// Parse appsettings.json
var baseConfig = ParseAppSettings($#"{targetPath}\appsettings.json");
// Find all appsettings.{env}.json's
var regex = new Regex(#"appsettings\..+\.json");
var environmentConfigs = Directory.GetFiles(targetPath, "*.json")
.Where(path => regex.IsMatch(path));
foreach (var env in environmentConfigs)
{
// Parse appsettings.{env}.json
var transform = ParseAppSettings(env);
// Clone baseConfig since Merge is a void operation
var result = (JObject)baseConfig.DeepClone();
// Merge the two, making sure to overwrite arrays
result.Merge(transform, new JsonMergeSettings
{
MergeArrayHandling = MergeArrayHandling.Replace
});
// Write the preview to file
string dest = $#"{targetPath}\preview-{Path.GetFileName(env)}";
File.WriteAllText(dest, result.ToString());
}
}
private static JObject ParseAppSettings(string path)
=> JObject.Load(new JsonTextReader(new StreamReader(path)));
While this is no guarantee there won't be some other config source won't override these once deployed, this will at least let you validate that the interactions between these two files will be handled correctly.
There's not really a way to do that, but I think a bit about how this actually works would help you understand why.
With config transforms, there was literal file modification, so it's easy enough to "preview" that, showing the resulting file. The config system in ASP.NET Core is completely different.
It's basically just a dictionary. During startup, each registered configuration provider is run in the order it was registered. The provider reads its configuration source, whether that be a JSON file, system environment variables, command line arguments, etc. and builds key-value pairs, which are then added to the main configuration "dictionary". An "override", such as appsettings.{environment}.json, is really just another JSON provider registered after the appsettings.json provider, which obviously uses a different source (JSON file). Since it's registered after, when an existing key is encountered, its value is overwritten, as is typical for anything being added to a dictionary.
In other words, the "preview" would be completed configuration object (dictionary), which is composed of a number of different sources, not just these JSON files, and things like environment variables or command line arguments will override even the environment-specific JSON (since they're registered after that), so you still wouldn't technically know the the environment-specific JSON applied or not, because the value could be coming from another source that overrode that.
You can use the GetDebugView extension method on the IConfigurationRoot with something like
app.UseEndpoints(endpoints =>
{
if(env.IsDevelopment())
{
endpoints.MapGet("/config", ctx =>
{
var config = (Configuration as IConfigurationRoot).GetDebugView();
return ctx.Response.WriteAsync(config);
});
}
});
However, doing this can impose security risks, as it'll expose all your configuration like connection strings so you should enable this only in development.
You can refer to this article by Andrew Lock to understand how it works: https://andrewlock.net/debugging-configuration-values-in-aspnetcore/

How to use localization in Razor Class Library in Asp.Net Core

I have tried to create the Razor Class Library with Asp.Net Core in following project structure:
I have used in my web application these settings for localization in Startup class:
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(
LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = "Resources"; })
.AddDataAnnotationsLocalization();
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("en")
};
opts.DefaultRequestCulture = new RequestCulture("en");
opts.SupportedCultures = supportedCultures;
opts.SupportedUICultures = supportedCultures;
});
....
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
In Index.cshtml:
#using Microsoft.AspNetCore.Mvc.Localization
#inject IViewLocalizer Localizer
<h1>#Localizer["Title"]</h1>
Unfortunately, the result is only string "Title". I can't load these resx files from Razor Class Library.
How can I use the localization in Razor Class Library like above?
UPDATE: This is very similiar use case - https://github.com/aspnet/Localization/issues/328 - that provides some example.
I haven't tried the accepted answer and based on the comments, it seems the OP didn't get it to work. I implemented a pattern similar to the View/Page locator pattern that MVC/Razor Pages uses namely, that resources can be provided in a RCL or separate assembly and use ViewLocalizer and it'll just find the matching resource string from the highest precedence resource. You can read my implementation and see if it might work for you.
https://terryaney.wordpress.com/2021/01/04/migrating-to-net-core-overridable-localization-in-razor-class-libraries/
You appear to have forgotten to configure localization correctly using AddLocalization
Using details provided from documentation
Reference Globalization and localization in ASP.NET Core
Configure localization
Localization is configured in the ConfigureServices method:
services.AddLocalization(options => options.ResourcesPath = "Resources"); //<<< This is required
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
AddLocalization Adds the localization services to the services container. The code above also sets the resources path to "Resources".
AddViewLocalization Adds support for localized view files.
AddDataAnnotationsLocalization Adds support for localized DataAnnotations validation messages through IStringLocalizer abstractions.
Localization middleware
The current culture on a request is set in the localization Middleware. The localization middleware is enabled in the Configure method. The localization middleware must be configured before any middleware which might check the request culture (for example, app.UseMvcWithDefaultRoute()).
var supportedCultures = new[] {
new CultureInfo("en-US"),
new CultureInfo("en")
};
app.UseRequestLocalization(new RequestLocalizationOptions{
DefaultRequestCulture = new RequestCulture("en"),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures;
});
//...other middleware
app.UseMvcWithDefaultRoute();
The path to the resource file shown in the example image follows the path naming convention given that
you are using the ResourcesPath option which was set to "Resources". This should allow the view to find the resource file in the relative path to the "Resources" folder.
An alternative is to not use the ResourcesPath option, and place the .resx file in the same folder as the view, following the naming convention of course.
Base on additional details provided it was indicated that the UI project would be packaged as a nuget package.
Then have the resources files packaged into the nuget package and have them unpacked to the resources folder of the target project when when installed.
The resources need to be in the site root to be available to the view, so you then need to reference all the files in your .nuspec:
<?xml version="1.0"?>
<package>
<metadata>...
</metadata>
<files>
<!-- Add all resource files -->
<file src="Resources\**\*.resx" target="content\Resources" />
</files>
</package>
Reference Creating NuGet packages

Design time instantiation issues when accessing xml file using XDocument.Load

In my windows store app using the Visual Studio 2012 designer I want to be able to load some model objects for the designer. I've done this plenty of times before where I supply a xaml file using the ms-appx:/// uri without error. However, for this project I need to be able to instantiate a class and have it convert raw xml of a different format into my model objects.
I'm using the following xaml to instantiate my class for the designer:
d:DataContext="{Binding Source={d:DesignInstance Type=model:Walkthroughs, IsDesignTimeCreatable=True}}"
In my Walkthroughs class had code that did this initially:
public Walkthroughs()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
AppDataLoader.LoadWalkthroughs(this, XDocument.Load("ms-appx:///SampleData/walkthroughs.xml"));
}
I first ran into an issue where the XDocument.Load did not understand the ms-appx:/// uri so I modified my code to something very simplistic:
AppDataLoader.LoadWalkthroughs(this, XDocument.Load(#"C:\walkthroughs.xml"));
Now I get access to path '' is denied.
I've tried several directories as well to no avail. I'm even running Visual Studio as an Administrator. If I remove the prefix altogether I get the following error:
Could not find file 'C:\Users\{me}\AppData\Local\Microsoft\VisualStudio\11.0\Designer\ShadowCache\omxyijbu.m4y\yofsmg1x.avh\walkthroughs.xml'.
Has anyone been able to load files from the file system when the designer instantiates objects?
Thanks,
-jeff
XDocument.Load(string uri) seems to have problems with loading Project resources from ms-appx:/
Regarding your second approach: Direct access to "C:" is not permitted. Ther is only a handful of special folders that you can access. Check out my workaround for this (my xml file is within the Assets folder of my project:
var storageFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
storageFolder = await storageFolder.GetFolderAsync("Assets");
var xmlFile = await storageFolder.GetFileAsync("data.xml");
var stream = await xmlFile.OpenReadAsync();
var rdr = new StreamReader(stream.AsStream(), System.Text.Encoding.GetEncoding("ISO-8859-1")); //needed if you have "ä,ß..." in your xml file
var doc = XDocument.Load(rdr);

MEF: "Unable to load one or more of the requested types. Retrieve the LoaderExceptions for more information"

Scenario: I am using Managed Extensibility Framework to load plugins (exports) at runtime based on an interface contract defined in a separate dll. In my Visual Studio solution, I have 3 different projects: The host application, a class library (defining the interface - "IPlugin") and another class library implementing the interface (the export - "MyPlugin.dll").
The host looks for exports in its own root directory, so during testing, I build the whole solution and copy Plugin.dll from the Plugin class library bin/release folder to the host's debug directory so that the host's DirectoryCatalog will find it and be able to add it to the CompositionContainer. Plugin.dll is not automatically copied after each rebuild, so I do that manually each time I've made changes to the contract/implementation.
However, a couple of times I've run the host application without having copied (an updated) Plugin.dll first, and it has thrown an exception during composition:
Unable to load one or more of the requested types. Retrieve the LoaderExceptions for more information
This is of course due to the fact that the Plugin.dll it's trying to import from implements a different version of IPlugin, where the property/method signatures don't match. Although it's easy to avoid this in a controlled and monitored environment, by simply avoiding (duh) obsolete IPlugin implementations in the plugin folder, I cannot rely on such assumptions in the production environment, where legacy plugins could be encountered.
The problem is that this exception effectively botches the whole Compose action and no exports are imported. I would have preferred that the mismatching IPlugin implementations are simply ignored, so that other exports in the catalog(s), implementing the correct version of IPlugin, are still imported.
Is there a way to accomplish this? I'm thinking either of several potential options:
There is a flag to set on the CompositionContainer ("ignore failing imports") prior to or when calling Compose
There is a similar flag to specify on the <ImportMany()> attribute
There is a way to "hook" on to the iteration process underlying Compose(), and be able to deal with each (failed) import individually
Using strong name signing to somehow only look for imports implementing the current version of IPlugin
Ideas?
I have also run into a similar problem.
If you are sure that you want to ignore such "bad" assemblies, then the solution is to call AssemblyCatalog.Parts.ToArray() right after creating each assembly catalog. This will trigger the ReflectionTypeLoadException which you mention. You then have a chance to catch the exception and ignore the bad assembly.
When you have created AssemblyCatalog objects for all the "good" assemblies, you can aggregate them in an AggregateCatalog and pass that to the CompositionContainer constructor.
This issue can be caused by several factors (any exceptions on the loaded assemblies), like the exception says, look at the ExceptionLoader to (hopefully) get some idea
Another problem/solution that I found, is when using DirectoryCatalog, if you don't specify the second parameter "searchPattern", MEF will load ALL the dlls in that folder (including third party), and start looking for export types, that can also cause this issue, a solution is to have a convention name on all the assemblies that export types, and specify that in the DirectoryCatalog constructor, I use *_Plugin.dll, that way MEF will only load assemblies that contain exported types
In my case MEF was loading a NHibernate dll and throwing some assembly version error on the LoaderException (this error can happen with any of the dlls in the directory), this approach solved the problem
Here is an example of above mentioned methods:
var di = new DirectoryInfo(Server.MapPath("../../bin/"));
if (!di.Exists) throw new Exception("Folder not exists: " + di.FullName);
var dlls = di.GetFileSystemInfos("*.dll");
AggregateCatalog agc = new AggregateCatalog();
foreach (var fi in dlls)
{
try
{
var ac = new AssemblyCatalog(Assembly.LoadFile(fi.FullName));
var parts = ac.Parts.ToArray(); // throws ReflectionTypeLoadException
agc.Catalogs.Add(ac);
}
catch (ReflectionTypeLoadException ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
}
CompositionContainer cc = new CompositionContainer(agc);
_providers = cc.GetExports<IDataExchangeProvider>();

Can I have different '.settings' specified for debug and release configurations?

I have a .settings file (containing both User and Application settings) that I would like to contain different values if built in Debug mode. Is there a supported or recommended way to do this?
I use two separate config files for each mode. I copy the files into the BIN folder in the POST-BUILD event.
The Settings.Designer.cs file doesn't contain values, only application setting property declarations. The values are stored separately. Application setting values go into the app.exe.config file, user scoped setting values go in an appdata folder that has a name generated by a hashing algorithm. You'd only have trouble with the latter. Shouldn't matter, the file won't exist when you deploy your Release build to a machine.
In case you mean "can I change the default value" then the answer is: not when you use the settings designer. You'll have to move the setting into a separate class. Make it look similar to this:
using System;
using System.Configuration;
using System.Diagnostics;
namespace ConsoleApplication1.Properties {
internal partial class Settings {
[UserScopedSetting, DebuggerNonUserCode]
#if DEBUG
[DefaultSettingValue("debug value")]
#else
[DefaultSettingValue("release value")]
#endif
public string Setting {
get {
return ((string)(this["Setting"]));
}
set {
this["Setting"] = value;
}
}
}
}
Make sure the namespace name matches the one used in the Settings.Designer.cs file and that you delete the setting from the Settings page.