Accessing Xamarin.iOS Settings.Bundle? - c#

I have been trying for quite a while to find a solution to a problem which ought to be pretty straight forward.
What I want is variables that can be edited in the iPhone settings menu while the app is not running. Basically a config file wrapped in the iOS GUI.
This is supposed to be an built-in feature in iOS, and while I can find some methods related to it, I can't find an actual solution.
The closest I've been to getting what I want is where it works like any other variable: Empty on application start, and gets scratched again on application close. And still not visible in the iPhone Settings window.
This is the code I have:
private void LoadSettingsFromIOS()
{
// This is where it works like any other variable. Aka. gets scratched on app closing.
_thisUser.SetValueForKey(new NSString("Blargh"), new NSString("SaveCredentials"));
string stringForKey = _thisUser.StringForKey("SaveCredentials");
// This is where I'm supposed to be able to load the data from settings and set the checkbox's 'On' state to the value. Currently it always returns False.
bool saveCredentials = _thisUser.BoolForKey("SaveCredentials");
chckBoxRememberMe.On = saveCredentials;
}
And my Settings.Bundle Root.pList file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Credentials</string>
<key>Key</key>
<string>SaveCredentials</string>
<key>DefaultValue</key>
<true/>
</dict>
</array>
<key>StringsTable</key>
<string>Root</string>
</dict>
</plist>
Anyone out there who's been messing with Xamarin iOS and knows how this works?

EDIT: Here is a working project in Xamarin: https://github.com/xamarin/monotouch-samples/tree/master/AppPrefs
First, you need to make your view visible in the iPhone Settings window.
To do that, you need to create a folder named "Settings.bundle" in the top-level directory of your app’s bundle. Then, create new file named "Root.plist". The file has to be of the type Property List.
You do that by right-clicking Settings.bundle, then Add -> New File... -> iOS (on the left pane) -> Property List. If you add an empty file and then rename it as .plist, it won't show in iPhone's Settings.
The first element in your Root.plist has to be an Array, and it must contain Dictionaries.
You can find more info here on how to construct your Settings View:
https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/UserDefaults/Preferences/Preferences.html
Pay attention to Figure 4-2 (Strings Filename is not necessary). Also, edit the Root.plist file in the Xamarin editor, it is much easier.
To get values from the newly created Settings, you can use this class (Add as many properties as you want):
public class Settings
{
public static string ApiPath { get; private set; }
const string API_PATH_KEY = "serverAddress"; //this needs to be the Identifier of the field in the Root.plist
public static void SetUpByPreferences()
{
var testVal = NSUserDefaults.StandardUserDefaults.StringForKey(API_PATH_KEY);
if (testVal == null)
LoadDefaultValues();
else
LoadEditedValues();
SavePreferences();
}
static void LoadDefaultValues()
{
var settingsDict = new NSDictionary(NSBundle.MainBundle.PathForResource("Settings.bundle/Root.plist", null));
if (settingsDict != null)
{
var prefSpecifierArray = settingsDict[(NSString)"PreferenceSpecifiers"] as NSArray;
if (prefSpecifierArray != null)
{
foreach (var prefItem in NSArray.FromArray<NSDictionary>(prefSpecifierArray))
{
var key = prefItem[(NSString)"Key"] as NSString;
if (key == null)
continue;
var value = prefItem[(NSString)"DefaultValue"];
if (value == null)
continue;
switch (key.ToString())
{
case API_PATH_KEY:
ApiPath = value.ToString();
break;
default:
break;
}
}
}
}
}
static void LoadEditedValues()
{
ApiPath = NSUserDefaults.StandardUserDefaults.StringForKey(API_PATH_KEY);
}
//Save new preferences to Settings
static void SavePreferences()
{
var appDefaults = NSDictionary.FromObjectsAndKeys(new object[] {
new NSString(ApiPath)
}, new object[] {
API_PATH_KEY
});
NSUserDefaults.StandardUserDefaults.RegisterDefaults(appDefaults);
NSUserDefaults.StandardUserDefaults.Synchronize();
}
}
You just call SetUpByPreferences() (which is the only public method), and then get the values from the properties in the class.

Try this:
NSBundle.MainBundle.PathForResource ("Settings", #"bundle");

Related

Loading Local HTML with WebView Xamarin Forms

I am trying to load a local HTML page in a webview with Xamarin forms.
I am using the basic example in the dev docs although I can get a URL to load I can't get my own HTML pages to load. This only needs to be done through Android so there is no worries about about IOS and Windows.
The Xaml:
<WebView
x:Name="webviewjava"></WebView>
The code behind:
public partial class javscriptExample : ContentPage
{
public interface IBaseUrl { string Get(); }
public javscriptExample()
{
InitializeComponent();
var source = new HtmlWebViewSource();
source.BaseUrl = DependencyService.Get<IBaseUrl>().Get();
webviewjava.Source = source;
}
}
The platform specific file (LocalFile.cs):
Just to note this has been set as an Android asset.
[assembly: Dependency(typeof(LocalFiles))]
namespace maptesting.Droid
{
public class LocalFiles: IBaseUrl
{
public string Get()
{
return "file:///android_asset/";
}
}
}
and under the asset's folder there is a 'TestWebPage.html', also set as an Android asset.
Although I dont know what the problem is I have put it through debug and the base url is coming back blank. Just to be clear im not getting a file not found, the screen is simply blank.
Also, and Im not sure if this makes a difference. There is no syntax highlighting on 'IBaseUrl' in the LocalFiles.cs file. So I'm not sure if it can 'see' it.
Any ideas?
I am also suffering with the same issue,but I resolved in the following way
Use "UrlWebViewSource" instead of "HtmlWebViewSource"
var urlSource = new UrlWebViewSource();
string baseUrl = DependencyService.Get<IWebViewBaseUrl>().GetBaseUrl();
string filePathUrl = Path.Combine(baseUrl, "imprint.html");
urlSource.Url = filePathUrl;
WebBrowser.Source = urlSource;
You must check the file properties for Build Action = BundleResource
Try this code to load local html file
var source = new HtmlWebViewSource();
string url = DependencyService.Get<IBaseUrl>().GetBaseUrl();
string TempUrl = Path.Combine(url, "terms.html");
source.BaseUrl = url;
string html;
try
{
using (var sr = new StreamReader(TempUrl))
{
html = sr.ReadToEnd();
source.Html = html;
}
}
catch(Exception ex){
Console.WriteLine(ex.Message);
}
Implementations of the interface for each platform must then be provided
iOS
[assembly: Dependency(typeof(BaseUrl))]
namespace yournamespace
{
public class BaseUrl: IBaseUrl
{
public string GetBaseUrl()
{
return NSBundle.MainBundle.BundlePath;
}
}
}
Android
[assembly: Dependency (typeof(BaseUrl))]
namespace yournamespace {
public class BaseUrl_Android : IBaseUrl {
public string Get() {
return "file:///android_asset/";
}
}
}
WebView.BaseUrl only tells the WebView where to start looking for files. It's the root folder of the "web site". By default browsers will load the file index.html, so if you rename your file to index.html I believe it should load automatically.
I think this should be possible too:
webviewjava.BaseUrl = DependencyService.Get<IBaseUrl>().Get();
webviewjava.Source = "TestWebPage.html";
Here you're saying "use this location as the default place to look for files" and "look up this file and use it as the source for the HTML".
This is an old post but It may help someone looking to implement with Android, iOS and UWP with just one HTML file. With this approach you only use one HTML file for all platforms.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/files?tabs=vsmac#loading-files-embedded-as-resources
Im not sure if this counts but I found a work around. Instead of taking the above route I simply did this:
webviewjava.Source = "file:///android_asset/TestWebPage.html";
in the code behind, and just left out the IBaseUrl call altogether.
This works as its supposed to.

Settings and IsDirty when modifying the settings through external references

I have a C# application with a settings field that is a collection of custom objects.
When I start the application I create some class instances that take instances from the settings entry collection and keep a reference to them internally and modify them. While debugging I saw that changes done through these external references are not reflected when I callSettings.Default.Save() but changes done by directly accessing the properties likeSettings.Default.<property> work fine.
I looked up the code responsible for Save() and saw that the implementation actually checks a SettingsPropertyValue.IsDirty field to decide whether to or not to serialize it. Of course when I access the external references to the objects in the settings field that value is not set.
Is there any lightweight solution to this?
I don't think I'm the first person to encounter this. One way I can think of is implementing the IsDirty property in the collections I serialize and add INotifyPropertyChanged interface event PropertyChanged for all the contained instances so that the container is being notified of changes and can reflect them to the actual settings property. But that means wrapping each of the settings classes around with this logic. So what I am asking for is if there is a lightweight solution to this found by anyone else who has encountered this issue.
Example
Consider this class:
namespace SettingsTest
{
[DataContract(Name="SettingsObject")]
public class SettingsObject
{
[DataMember]
public string Property { get; set; }
}
}
And the following program:
namespace SettingsTest
{
class Program
{
static void Main(string[] args)
{
var settingsObjects = new List<SettingsObject>();
var settingsObject = new SettingsObject{Property = "foo"};
settingsObjects.Add(settingsObject);
Settings.Default.SettingsObjects = settingsObjects;
Settings.Default.Save();
settingsObject.Property = "bar";
Settings.Default.Save();
}
}
}
After the second Save() call the final output in user.config file is:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<userSettings>
<SettingsTest.Settings>
<setting name="SettingsObjects" serializeAs="Xml">
<value>
<ArrayOfSettingsObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SettingsObject>
<Property>foo</Property>
</SettingsObject>
</ArrayOfSettingsObject>
</value>
</setting>
</SettingsTest.Settings>
</userSettings>
</configuration>
As you can see the modification to the property via the external reference to the SettingsObject instance was not persisted.
You seem to be covering a bunch of issues with this question. As you correctly said "changes done by directly accessing the properties likeSettings.Default. work fine." Modifying in memory references will not cause the settings to be updated automatically as you wanted. INotifyProperty changed is one solution.
If you want a quick and dirty method of serializing these objects into your settings file you can use the following code.
Objects involved:
/// <summary>Generic class to support serializing lists/collections to a settings file.</summary>
[Serializable()]
public class SettingsList<T> : System.Collections.ObjectModel.Collection<T>
{
public string ToBase64()
{
// If you don't want a crash& burn at runtime should probaby add
// this guard clause in: 'if (typeof(T).IsDefined(typeof(SerializableAttribute), false))'
using (var stream = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
byte[] buffer = new byte[(int)stream.Length];
stream.Read(buffer, 0, buffer.Length);
return Convert.ToBase64String(buffer);
}
}
public static SettingsList<T> FromBase64(string settingsList)
{
using (var stream = new MemoryStream(Convert.FromBase64String(settingsList)))
{
var deserialized = new BinaryFormatter().Deserialize(stream);
return (SettingsList<T>)deserialized;
}
}
}
[Serializable()]
public class SettingsObject
{
public string Property { get; set; }
public SettingsObject()
{
}
}
The main method demonstrates the issue you are facing, with a solution.
class Program
{
static void Main(string[] args)
{
// Make sure we don't overwrite the previous run's settings.
if (String.IsNullOrEmpty(Settings.Default.SettingsObjects))
{
// Create the initial settings.
var list = new SettingsList<SettingsObject> {
new SettingsObject { Property = "alpha" },
new SettingsObject { Property = "beta" }
};
Console.WriteLine("settingsObject.Property[0] is {0}", list[0].Property);
//Save initial values to Settings
Settings.Default.SettingsObjects = list.ToBase64();
Settings.Default.Save();
// Change a property
list[0].Property = "theta";
// This is where you went wrong, settings will not be persisted at this point
// because you have only modified the in memory list.
// You need to set the property on settings again to persist the value.
Settings.Default.SettingsObjects = list.ToBase64();
Settings.Default.Save();
}
// pull that property back out & make sure it saved.
var deserialized = SettingsList<SettingsObject>.FromBase64(Settings.Default.SettingsObjects);
Console.WriteLine("settingsObject.Property[0] is {0}", deserialized[0].Property);
Console.WriteLine("Finished! Press any key to continue.");
Console.ReadKey();
}
}
So all I'm doing is storing your whole list of objects as a base64 encoded string. Then we deserialize on the next run.
From what I understand of the question you want to only hold in memory references without hitting the settings object. You could simply run some code at the end of main to persist this list and any other setting you need to. Any changes will still be in memory as long as you hold a reference the object & will remain around until you terminate the application.
If you need the settings to be saved while the application is running just create a Save() method of your own & call it from the end of Main() as well when the user performs an action that requires saving the settings. e.g.
public static void SaveSettings(SettingsList list)
{
Settings.Default.SettingsObjects = list.ToBase64();
Settings.Default.Save();
}
Edit: One caveat as mentioned in the comments below.
From my benchmarks this method is very slow, meaning it's not a good idea to persist large object lists in settings this way. Once you have more than a handful of properties you might want to look at an embedded database like SQLite. A CSV, INI or XML file could also be an option here for large numbers of trivial settings. One benefit of simpler storage formats is easy modification by non-developers eg csv in excel. Of course this may not be what you want ;)

Rendering a view based on XML data

In my ASP MVC Application I am populating the content present in a view based on data from XML files.
What my question is, am I doing this the best way? Surely there has got to be a more efficient and easier way to do what I'm doing here.
Here is an example of the markup in my XML file:
<?xml version="1.0" encoding="utf-8" ?>
<Content>
<!-- P1: Customer details -->
<ContentItem>
<key>Header_CancelImg</key>
<value>~/Content/mainpage/images/close.gif</value>
</ContentItem>
<ContentItem>
<key>Header_CancelText</key>
<value>Cancel this application</value>
</ContentItem>
So, I am deserialzing the contents of this XML file like so:
using (MemoryStream ms = new MemoryStream(System.IO.File.ReadAllBytes(path))) {
XmlSerializer serializer = new XmlSerializer(typeof(Content));
pageContent = serializer.Deserialize(ms) as Content;
}
All good. Now, based on this, what is the best way I can populate my view based on this content? Let me show you what I mean, and how I'm doing it now (very horribly):
#model <project.Models.Content> // This content object contains Content object where the deserializer is present as shown above
#foreach (var contentItem in Model.Item2.contentItemList)
{
#if(contentItem.key == "Header_CancelImg")
{
<img src="#Url.Content(contentItem.value)">
continue;
}
#if(contentItem.key == "Header_CancelText")
{
<p>#contentItem.value</p>
continue;
}
} // and so on
Is there a more easier way I can do this?
Thanks
If you gave your Content class a default property, you could access the data from your model by referencing the key names without having to iterate over all the different possible keys. I have shown a private dictionary property which is lazily initialized on the first hit.
public class Content
{
private Dictionary<string, string> contentItems;
public string this[string key]
{
if (contentItems == null)
{
contentItems = contentItemList.ToDictionary(i => i.Key, i => i.Value);
}
if (contentItems.ContainsKey(key))
{
return contentItems[key];
}
return string.Empty;
}
//other properties
}
Then your razor code could be like this:
#model project.Models.Content
<img src="#Url.Content(Model["Header_CancelImg"])">
<p>#Model["Header_CancelText"]</p>

Loading Properties.Settings from a different file at runtime

Is there any way to load settings from a different file other than the default App.config file at runtime? I'd like to do this after the default config file is loaded.
I use the Settings.Settings GUI in Visual Studio to create my App.config file for me. The config file ends up looking like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="SnipetTester.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<SnipetTester.Properties.Settings>
<setting name="SettingSomething" serializeAs="String">
<value>1234</value>
</setting>
</SnipetTester.Properties.Settings>
</applicationSettings>
</configuration>
In code, I'm able to access the settings like this:
Console.WriteLine("Default setting value: " + Properties.Settings.Default.SettingSomething);
The idea is that when the application is run, I should be able to specify a config file at run time and have the application load the config file into the Properties.Settings.Default object instead of using the default app.config file. The formats of the config files would be the same, but the values of the settings would be different.
I know of a way to do this with the ConfigurationManager.OpenExeConfiguration(configFile);. However, in the tests that I've run, it doesn't update the Properties.Settings.Default object to reflect the new values from the config file.
After thinking about this a bit longer, I've been able to come up with a solution that I like a little better. I'm sure it has some pitfalls, but I think it'll work for what I need it to do.
Essentially, the Properties.Settings class is automatically generated by Visual Studio; it generates the code for the class for you. I was able to find where the code was generated and add a few function calls to load a config file on its own. Here's my addition:
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
//Parses a config file and loads its settings
public void Load(string filename)
{
System.Xml.Linq.XElement xml = null;
try
{
string text = System.IO.File.ReadAllText(filename);
xml = System.Xml.Linq.XElement.Parse(text);
}
catch
{
//Pokemon catch statement (gotta catch 'em all)
//If some exception occurs while loading the file,
//assume either the file was unable to be read or
//the config file is not in the right format.
//The xml variable will be null and none of the
//settings will be loaded.
}
if(xml != null)
{
foreach(System.Xml.Linq.XElement currentElement in xml.Elements())
{
switch (currentElement.Name.LocalName)
{
case "userSettings":
case "applicationSettings":
foreach (System.Xml.Linq.XElement settingNamespace in currentElement.Elements())
{
if (settingNamespace.Name.LocalName == "SnipetTester.Properties.Settings")
{
foreach (System.Xml.Linq.XElement setting in settingNamespace.Elements())
{
LoadSetting(setting);
}
}
}
break;
default:
break;
}
}
}
}
//Loads a setting based on it's xml representation in the config file
private void LoadSetting(System.Xml.Linq.XElement setting)
{
string name = null, type = null, value = null;
if (setting.Name.LocalName == "setting")
{
System.Xml.Linq.XAttribute xName = setting.Attribute("name");
if (xName != null)
{
name = xName.Value;
}
System.Xml.Linq.XAttribute xSerialize = setting.Attribute("serializeAs");
if (xSerialize != null)
{
type = xSerialize.Value;
}
System.Xml.Linq.XElement xValue = setting.Element("value");
if (xValue != null)
{
value = xValue.Value;
}
}
if (string.IsNullOrEmpty(name) == false &&
string.IsNullOrEmpty(type) == false &&
string.IsNullOrEmpty(value) == false)
{
switch (name)
{
//One of the pitfalls is that everytime you add a new
//setting to the config file, you will need to add another
//case to the switch statement.
case "SettingSomething":
this[name] = value;
break;
default:
break;
}
}
}
}
The code I added exposes an Properties.Settings.Load(string filename) function. The function accepts a config filename as a parameter. It will parse the file and load up any settings it encounters in the config file. To revert back to the original configuration, simply call Properties.Settings.Reload().
Hope this might help someone else!
Look at using ExeConfigurationFileMap and ConfigurationManager.OpenMappedExeConfiguration.
See Cracking the Mysteries of .Net 2.0 Configuration
The ExeConfigurationFileMap allows you to specifically configure the
exact pathnames to machine, exe, roaming and local configuration
files, all together, or piecemeal, when calling
OpenMappedExeConfiguration(). You are not required to specify all
files, but all files will be identified and merged when the
Configuration object is created. When using
OpenMappedExeConfiguration, it is important to understand that all
levels of configuration up through the level you request will always
be merged. If you specify a custom exe and local configuration file,
but do not specify a machine and roaming file, the default machine and
roaming files will be found and merged with the specified exe and user
files. This can have unexpected consequences if the specified files
have not been kept properly in sync with default files.
It depends on the type of the application:
Web Application & Windows Application - use the configSource xml attribute if you are willing to store the config files in the same folder (or subfolders) as the application
Create a settings provider and also implement IApplicationSettingsProvider. Samples here and here. You might also need to use the IConfigurationManagerInternal interface to replace the default .NET configuration manager. When implementing the provider don't forget to make a difference between user settings and application settings and the roaming profiles.
If you want to get started quickly just decompile the LocalFileSettingsProvider class (the default settings provider) and change it to your needs (you might find some useles code and might need to replicate all of the classes on which it depends).
Good luck
You can include the types so you don't need to manually update the source every time.
`private void LoadSetting(System.Xml.Linq.XElement setting)
{
string name = null, type = null;
string value = null;
if (setting.Name.LocalName == "setting")
{
System.Xml.Linq.XAttribute xName = setting.Attribute("name");
if (xName != null)
{
name = xName.Value;
}
System.Xml.Linq.XAttribute xSerialize = setting.Attribute("serializeAs");
if (xSerialize != null)
{
type = xSerialize.Value;
}
System.Xml.Linq.XElement xValue = setting.Element("value");
if (xValue != null)
{
if (this[name].GetType() == typeof(System.Collections.Specialized.StringCollection))
{
foreach (string s in xValue.Element("ArrayOfString").Elements())
{
if (!((System.Collections.Specialized.StringCollection)this[name]).Contains(s))
((System.Collections.Specialized.StringCollection)this[name]).Add(s);
}
}
else
{
value = xValue.Value;
}
if (this[name].GetType() == typeof(int))
{
this[name] = int.Parse(value);
}
else if (this[name].GetType() == typeof(bool))
{
this[name] = bool.Parse(value);
}
else
{
this[name] = value;
}
}
}`

Is this an abuse of the Settings Feature?

I've been storing collections of user settings in the Properties.Settings.Default object and using the Visual Studio settings designer (right-click on your project, click on Properties and then click on the Settings tab) to set the thing up. Recently, several users have complained that the data this particular setting tracks is missing, randomly.
To give an idea (not exactly how I do it, but somewhat close), the way it works is I have an object, like this:
class MyObject
{
public static string Property1 { get; set; }
public static string Property2 { get; set; }
public static string Property3 { get; set; }
public static string Property4 { get; set; }
}
Then in code, I might do something like this to save the information:
public void SaveInfo()
{
ArrayList userSetting = new ArrayList();
foreach (Something s in SomeCollectionHere) // For example, a ListView contains the info
{
MyObject o = new MyObject {
Property1 = s.P1;
Property2 = s.P2;
Property3 = s.P3;
Property4 = s.P4;
};
userSetting.Add(o);
}
Properties.Settings.Default.SettingName = userSetting;
}
Now, the code to pull it out is something like this:
public void RestoreInfo()
{
ArrayList setting = Properties.Settings.Default.SettingName;
foreach (object o in setting)
{
MyObject data = (MyObject)o;
// Do something with the data, like load it in a ListView
}
}
I've also made sure to decorate the Settings.Designer.cs file with [global::System.Configuration.SettingsSerializeAs(global::System.Configuration.SettingsSerializeAs.Binary)], like this:
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.SettingsSerializeAs(global::System.Configuration.SettingsSerializeAs.Binary)]
public global::System.Collections.ArrayList SettingName
{
get {
return ((global::System.Collections.ArrayList)(this["SettingName"]));
}
set {
this["SettingName"] = value;
}
}
Now, randomly, the information will disappear. I can debug this and see that Properties.Settings.Default is returning an empty ArrayList for SettingName. I would really rather not use an ArrayList, but I don't see a way to get a generic collection to store in this way.
I'm about to give up and save this information using plain XML on my own. I just wanted to verify that I was indeed pushing this bit of .NET infrastructure too far. Am I right?
I couldn't find an answer as to why these settings were disappearing and since I kept on happening, I ended up storing the complicated sets of settings separately in an XML file, manually serializing and deserializing them myself.
I had a very similar experience when using the SettingsSerializeAs Binary Attribute in the Settings Designer class. It worked in testing, but some time later it failed to restore the property values.
In my case there had been subsequent additions to the Settings made via the designer. Source control history showed that the SettingsSerializeAs Attribute had been removed from Settings.Designer.cs without my knowledge.
I added the following code to check that the attribute hadn't been accidentally lost it the equivalent of the RestoreInfo() method.
#if(DEBUG)
//Verify that the Property has the required attribute for Binary serialization.
System.Reflection.PropertyInfo binarySerializeProperty = Properties.Settings.Default.GetType().GetProperty("SettingName");
object[] customAttributes = binarySerializeProperty.GetCustomAttributes(typeof(System.Configuration.SettingsSerializeAsAttribute), false);
if (customAttributes.Length != 1)
{
throw new ApplicationException("SettingsSerializeAsAttribute required for SettingName property");
}
#endif
Also, only because it is missing from your example, don't forget to call Save. Say after calling SaveInfo().
Properties.Settings.Default.Save();
When using the Settings feature with the User scope, the settings are saved to the currently logged in user's Application Data (AppData in Vista/7) folder. So if UserA logged in, used your application, and then UserB logged in, he wouldn't have UserA's settings loaded, he would have his own.
In what you're trying to accomplish, I would suggest using the XmlSerializer class for serializing an list of objects. The use is pretty simple:
To serialize it:
ArrayList list = new ArrayList();
XmlSerializer s = new XmlSerializer(typeof(ArrayList));
using (FileStream fs = new FileStream(#"C:\path\to\settings.xml", FileMode.OpenOrCreate))
{
s.Serialize(fs, list);
}
To deserialize it:
ArrayList list;
XmlSerializer s = new XmlSerializer(typeof(ArrayList));
using (FileStream fs = new FileStream(#"C:\path\to\settings.xml", FileMode.Open))
{
list = (ArrayList)s.Deserialize(fs);
}
From your example I don't see anything incorrect about what your trying to do. I think the root of the problem your describing might be your assembly version changing? User settings do not auto-magically upgrade themselves (at least I couldn't get them to).
I can appreciate your plight, I went through this a few months ago. I coded up a UserSettings class that provided standard name/value pair collections (KeyValueConfigurationElement) under a named group heading something like the following:
<configSections>
<section name="userSettings" type="CSharpTest.Net.AppConfig.UserSettingsSection, CSharpTest.Net.Library"/>
</configSections>
<userSettings>
<add key="a" value="b"/>
<sections>
<section name="c">
<add key="a" value="y"/>
</section>
</sections>
</userSettings>
Anyway see if this meets your needs or provides some insight into implementing a custom ConfigurationSection to allow what you need.
Oh yea, the code is here:
http://csharptest.net/browse/src/Library/AppConfig

Categories