Is this an abuse of the Settings Feature? - c#

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

Related

How can I share settings between two WPF apps?

I have a pair of apps. One is a game for young kids. The other is a tool for use by the parents. I separate the apps in this way to keep the UI as simple as possible for the kids.
One part of the parent app is to control specific settings of the kids' app. I've just been using Settings.settings for most of my settings up to now, but can't see a simple way for the parent app to access and change the settings in the kids' app (apart from a rather kludgy back-door using XML).
Is there a way, or alternatively, is there another place I should consider keeping my shared settings?
I like the ease of two-way binding for managing settings via a dialog, but could survive without that if necessary.
FWIW: Both apps do use a common DLL where a lot of common code resides. Maybe there's a way of leveraging that?
You can save the settings to a file into a common folder like the below (eg C:\ProgramData\yourfolder)
String DataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\yourfolder";
and access the settings file from both applications. No need of a database and easy of use.
The settings file can be a simple json or whatever you feel comfortable.
In the end, I went with having a class that contains the shared settings. Here's the main part of it:
public class AppConfig
{
public int SaveSize = 1000;
//... other shared settings here
static private bool ready = false;
static private AppConfig instance;
static public AppConfig Instance
{
get
{
if (!ready)
{
ReadFromFile();
}
return instance;
}
}
static private readonly string filename = Common.AppDataFileName("config.xml");
static private void ReadFromFile()
{
ready = true;
if (!File.Exists(filename))
{
instance = new AppConfig();
return;
}
XmlSerializer serializer = new XmlSerializer(typeof(AppConfig));
using (FileStream fs = new FileStream(filename, FileMode.Open))
{
instance = (AppConfig)serializer.Deserialize(fs);
}
}
static public void Save()
{
XmlSerializer serializer = new XmlSerializer(typeof(AppConfig));
using (TextWriter writer = new StreamWriter(filename))
{
serializer.Serialize(writer, Instance);
}
}
}
Because you have to have an instance for serialization, I instantiate that on-demand and then refer to it as needed. E.g.:
int size = AppConfig.Instance.SaveSize;
Don't know if this is the best way, but it works for my needs for now. If it is a bad way to do this, don't be shy to comment below. I'm here to learn!

EF 6 CodeFirst Add-Migration scaffoldes and generates changes that doesn't exist

I have been using EF Migrations for a while in my current project and all was working great, that is until today, the situation is as follows:
I made a small change of adding a string property
I called an API method and got an error that there are changes in the model
I ran the command "Add-Migration MigrationXYZ"
A new migration is created with extra changes that didn't happen
I ran the "Add-Migration MigrationXYZ -Force" to make sure its not a one thing issue, I dropped the DB, restarted VS(2015) but all the same
Another issue is that even if I apply the migration as done by the scaffolder, an error still returns saying "Unable to update database to match the current model because there are pending changes..."
After looking at these changes, they all but one are about having a string property with the [Required] attribute and the scaffolder need to make it nullable, below is a sample.
public partial class MigrationXYZ: DbMigration
{
public override void Up()
{
AddColumn("dbo.Foos", "NewProperty", c => c.String());//<-- Expected Change
AlterColumn("dbo.Bars", "Name", c => c.String());//<-- Unexpected Change
}
public override void Down()
{
AlterColumn("dbo.Bars", "Name", c => c.String(nullable: false));//<-- Unexpected Change
DropColumn("dbo.Foos", "NewProperty");//<-- Expected Change
}
}
public class Bar
{
//This was not touched in ages, some even before adding the first migration
[Required]
public string Name { get; set; }
}
And now I am stuck and don't know how to fix this...Corruption in the Migration state
Edit
I have been trying to debug the Add-Migration command to understand why does EF see the model is different than it really is, but using EF source is not possible when you have dependencies like Identity which needs signed DLLs to work.
However additional research lead me to the answer here which leads to this blog post By #trailmax and the code to decipher the migrations hash, and with a little search in the EF source I made a small app to extract both the current model and the last migration model to compare side to side.
The code to get the current model representation in XML
//Extracted from EF Source Code
public static class DbContextExtensions
{
public static XDocument GetModel(this DbContext context)
{
return GetModel(w => EdmxWriter.WriteEdmx(context, w));
}
public static XDocument GetModel(Action<XmlWriter> writeXml)
{
using (var memoryStream = new MemoryStream())
{
using (var xmlWriter = XmlWriter.Create(
memoryStream, new XmlWriterSettings
{
Indent = true
}))
{
writeXml(xmlWriter);
}
memoryStream.Position = 0;
return XDocument.Load(memoryStream);
}
}
}
//In Program.cs
using (var db = new DbContext())
{
var model = db.GetModel();
using (var streamWriter = new StreamWriter(#"D:\Current.xml"))
{
streamWriter.Write(model);
}
}
The code to extract the model from the migration in XML
//Code from Trailmax Tech Blog
public class MigrationDecompressor
{
public string ConnectionString { get; set; }
public String DecompressMigrationFromSource(IMigrationMetadata migration)
{
var target = migration.Target;
var xmlDoc = Decompress(Convert.FromBase64String(target));
return xmlDoc.ToString();
}
public String DecompressDatabaseMigration(String migrationName)
{
var sqlToExecute = String.Format("select model from __MigrationHistory where migrationId like '%{0}'", migrationName);
using (var connection = new SqlConnection(ConnectionString))
{
connection.Open();
var command = new SqlCommand(sqlToExecute, connection);
var reader = command.ExecuteReader();
if (!reader.HasRows)
{
throw new Exception("Now Rows to display. Probably migration name is incorrect");
}
while (reader.Read())
{
var model = (byte[])reader["model"];
var decompressed = Decompress(model);
return decompressed.ToString();
}
}
throw new Exception("Something went wrong. You should not get here");
}
/// <summary>
/// Stealing decomposer from EF itself:
/// http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Migrations/Edm/ModelCompressor.cs
/// </summary>
private XDocument Decompress(byte[] bytes)
{
using (var memoryStream = new MemoryStream(bytes))
{
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
return XDocument.Load(gzipStream);
}
}
}
}
//Inside Program.cs
var decompresser = new MigrationDecompressor
{
ConnectionString = "<connection string>"
};
var databaseSchemaRecord = decompresser.DecompressDatabaseMigration("<migration name>");
using (var streamWriter = new StreamWriter(#"D:\LastMigration.xml"))
{
streamWriter.Write(databaseSchemaRecord);
}
Unfortunately I still cannot find the issue, the only difference between the model and the one hashed with the last migration is the expected change of the added property, none of the unexpected changes show up, also after running the migration suggested by EF, then comparing the current model with the suggested migration, still the model doesn't match the changes, what should be not null is still not null in the model, while the suggested migration show it as nullable.
The expected changes show up
<Property Name="NewProperty" Type="String" MaxLength="Max" FixedLength="false" Unicode="true" />
.
.
.
<ScalarProperty Name="NewProperty" ColumnName="NewProperty" />
.
.
.
<Property Name="NewProperty" Type="nvarchar(max)" Nullable="true" />
Try rolling back your database to one of your previous ones with
Update-database -targetMigration "nameofpreviousmigration"
(you may need to run update-database before running the above I am not certain)
Then delete your new migration, create a completely new migration and run
update-database.
Hopefully this will fix the problem with it thinking there is an extra migration
Another option but It is probably not the best solution is too manually edit the migration and take out the unexpected part
Well, looking again at #trailmax's answer, I wanted to try something, an info that I didn't include in the question, and was dismissing as the cause since its used in other places, was not changed in this migration, and was dismissed as the cause by #trailmax as well, which is attributes and ExpressiveAnnotations attributes in specific.
My actual Bar class looks like this
public class Bar
{
//This was not touched in ages, some even before adding the first migration
[Required]
[AssertThat(#"<Condition>", ErrorMessage = "Please revise the name")]
public string Name { get; set; }
}
I commented out the AssertThat attribute, and guess what, all the changes that shouldn't exist disappeared.
Please try providing connectionstring and provider explicitly with update-database command. You can find these values in your connectionstring.
Sometimes, we may need to direct the entity framework to connect to right database. One of the cases would be, selecting wrong project as start up project, which will make entity framework assume to connect to default database.
update-database -connectionstring:"" -provider:""

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 ;)

Windows Form Save to XML

I have a form with information in it that the user enters, i want to save this to XML... i'm fairly new to programming but have read XML is the best thing to use. How would i go about it? If it helps im using Sharp Develop as an IDE. Current it has 10 text boxes and 10 datetimepickers.
The easiest thing would be to create a class that stores those 10 values as properties and use xml serialization to convert it to XML, then store it to the file system.
Here's a tutorial: http://www.switchonthecode.com/tutorials/csharp-tutorial-xml-serialization
More Detail:
This is super basic Object Oriented/Windows Forms stuff.
Create a Class that stores each of the values:
public class Values{
public string YourFirstValue { get; set;}
public DateTime YourSecondValue { get; set;}
...
}
and of course you'd want names that map to their actual meanings, but these should suffice for now.
Then, when clicking a button on your form, store the values in that class:
void Button1_OnClick(object sender, EventArgs args){
Values v = new Values();
v.YourFirstValue = this.FirstField.Text;
v.YourSecondValue = this.YourSecondField.Value
...
SaveValues(v);
}
Then implement the SaveValues method to serialize the xml using XmlSerializer for the serialization and StreamWriter to store the result to a file.
public void SaveValues(Values v){
XmlSerializer serializer = new XmlSerializer(typeof(Values));
using(TextWriter textWriter = new StreamWriter(#"C:\TheFileYouWantToStore.xml")){
serializer.Serialize(textWriter, movie);
}
}

How to save state of checkbox, textbox and ... after closing a program in c#

In my C# program that is made with Visual Studio 2010 and uses WinForms, I would like the program to save state of some checkboxes and textboxes so the next time program will be loaded they are checked or unchecked as theire last run's state. Also same with strings inside textboxes and etc...
What will be the proper way to achieve this? Is there a built in stuff in .NET? Any tips and code snippets would be appriciated!
Thanks
You'd probably want to look at reading the relevant values from your UI during the FormClosing event, and then saving them into User Settings.
Have a look at: http://codehill.com/2009/01/saving-user-and-application-settings-in-winforms/
I would bind the value to user settings, and saving the configuration OnClose event.
One way to do this is using an XML configuration file and serializing it:
ConfigManager.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace MyApplication
{ [ Serializable() ]
public class ConfigManager
{
private int removeDays = 7;
public ConfigManager() { }
public int RemoveDays
{
get
{
return removeDays;
}
set
{
removeDays = value;
}
}
}
somewhere in your application
private ConfigManager cm;
private XmlSerializer ser;
...
Then you have to load the configuration:
private void LoadConfig()
{
try
{
cm = new ConfigManager();
ser = new XmlSerializer(typeof(ConfigManager));
filepath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + cm.filepath;
if (File.Exists(filepath))
{
FileStream fs = new FileStream(filepath, FileMode.Open);
cm = (ConfigManager)ser.Deserialize(fs);
// do something
}
} catch (Exception ex) { }
}
To save it:
XmlSerializer ser;
ConfigManager cm;
...
private void saveConfig()
{
try {
cm.RemoveDays = 6;
TextWriter tw = new StreamWriter(filepath, false);
ser.Serialize(tw, cm);
tw.Close();
} catch (Exception ex)
}
You asked very broad question. there are two ways to look at it.
1) If you have a need to persist application level configuration, your best bet is to use Application Settings. One can serialize program settings the user has done using your app, and restore them after the program has restarted. This works with WinForms and WPF:
2) If you need user level persistence, you need user settings.
Also, you can create custom class that implements that stores all of the configuration properties that you need.
Implement ISerializable and mark it [Serializable]. You could just mark it [Serializable], but if you add new properties in the future, you'll run into deserialization problems.
Add a Version property.
Add two static methods: Load and Save. These methods use IsolatedStorage to deserialize/serialize your configuration class to disk. You can use any kind of serialization you want - I use binary. Why not XML? Because binary is faster and users never need to get into these files. I used to do this for .net 2.0.

Categories