I'm trying to make a Config.cs class to use on my project.
Structure is supposed to consist of categories of settings. For example, Config.LogOnDetails should hold the values for MySQL login.
Here is my current structure.
public class Config
{
public string pPath;
public string configPath;
public string configFilePath;
public class LogOnDetails
{
public string MySQLDatabaseName { get; set; }
public string MySQLUser { get; set; }
public string MySQLPassword { get; set; }
public string MySQLAddress { get; set; }
}
public Config()
{
pPath = Directory.GetCurrentDirectory();
configPath = Path.Combine(pPath, #"/config");
configFilePath = Path.Combine(configPath, "/config.json");
//If it doesn't exist, create a directory for the configuration to be stored in
if (!Directory.Exists(configPath))
{
Directory.CreateDirectory("config");
}
if (!File.Exists(configFilePath))
{
string json = JsonConvert.SerializeObject(this);
File.WriteAllText(configFilePath, json);
Console.WriteLine("Created a new blank config file!");
}
}
}
Here is how I'm trying to load the config to the class.
//Initialize configuration
Config.LogOnDetails logOnDetails = new Config.LogOnDetails();
//Load config from json
config.LogOnDetails = JsonConvert.DeserializeObject<Config.LogOnDetails>(config.configFilePath);
But this doesn't seem to work and looks like I don't understand subclasses properly. How can I organize my class so it will work?
json example:
{
"pPath": null,
"configPath": null,
"configFilePath": null,
"MySQLDatabaseName": null,
"MySQLUser": null,
"MySQLPassword": null,
"MySQLAddress": null
}
First off, I'm going to start with a general point. The Config class should know nothing about how it is stored, or where it is stored. That is a completely separate "concern". See Separation of concerns
Start off with the definition of what you want to store. That seems to be your MySql info, and some other info. These should all be individual classes (To be clear, you can nest them, but there is no need to and complicates the answer a little):
public class LogOnDetails
{
public string MySQLDatabaseName { get; set; }
public string MySQLUser { get; set; }
public string MySQLPassword { get; set; }
public string MySQLAddress { get; set; }
}
You can have another one:
public class Settings
{
public string Locale { get; set; }
}
And you can compose these into a master config object
public class Config
{
public string SomeTopLevelProp {get; set; }
public LogOnDetails LogOnDetails { get; set; }
public Settings Settings { get; set; }
}
The way to serialize and deserialize this is fairly straightforward
var config = new Config()
{
SomeTopLevelProp = "ABCDEF",
LogOnDetails = new LogOnDetails()
{
MySqlDatabaseName = "Foo" ,
MySQLUser = "MyUser"
// snip the rest of the props
},
Settings = new Settings
{
Locale = "en-GB"
}
}
var json = JsonConvert.SerializeObject(config );
var mySettingDeserialized = JsonConvert.DeserializeObject<Config>(json);
I have purposely left out the writing to a file part - you seem to know how to do that - but keep it outside of the Config class. For example a separate classes, perhaps just with 2 static methods which knows how/where to store the config
public static class ConfigLoader
{
public static void StoreConfig(Config config, string location) {... }
public static Config LoadConfig(string location) {... }
}
A note on security - storing your database password as plain text in a json config file is generally a bad idea. You might consider encrypting it, and storing that and decrypting it when using the value.
Related
I created an REST api application which has many settings and stored in database. These settings are used during filtering and inserting data to the table.
Because I need to access settings every time I need to insert data. Instead of accessing settings from database, I created a global settings class and I put every settings in that class.
public static class GlobalSettings
{
public static string Setting_1;
public static string Setting_2;
public static string Setting_3;
public static string Setting_4;
public static void Initialize(ISettingsRepo repo)
{
try
{
var settings = new GSettings(repo);
Setting_1 = settings.SetSetting_1();
Setting_2 = settings.SetSetting_2();
Setting_3 = settings.SetSetting_3();
Setting_4 = settings.SetSetting_4();
}
catch (Exception ex)
{
throw new Exception("Error when loading settings.\r\n" + ex.Message);
}
}
}
Here ISettingsRepo is scoped service that will load the settings from database. The functions will initialize the settings to the properties.
Now to initialize GlobalSettings I used configure method in startup class like this.
using (var scope = app.ApplicationServices.CreateScope())
{
Settings.GlobalSettings.Initialize(scope.ServiceProvider
.GetRequiredService<Data_Repo.Settings.ISettingsRepo>());
}
Now I can use this in controller or anywhere in my api and get settings without accessing database. Also I can reload the GlobalSettings any time if settings are updated. But does this method correct way or has memory leak problems?
Is there any better method to do this.?
Example
My appsetting.json have structure like this.
"EmailSettings": {
"MailServer": "",
"MailPort": ,
"Email": "",
"Password": "",
"SenderName": "",
"Sender": "",
"SysAdminEmail": ""
},
I will define my class like this
public class EmailSettings
{
public string MailServer { get; set; }
public int MailPort { get; set; }
public string SenderName { get; set; }
public string Sender { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string SysAdminEmail { get; set; }
}
So we have the the config structure. The last thing we need is register inside Startup.cs
services.Configure<EmailSettings>(configuration.GetSection("EmailSettings"));
To use it inside service class
private readonly IOptions<EmailSettings> _emailSetting;
public EmailSender(IOptions<EmailSettings> emailSetting)
{
_emailSetting = emailSetting;
}
email.From.Add(new MailboxAddress(_emailSetting.Value.SenderName, _emailSetting.Value.Sender));
I want to perform NUnit or MS tests on a class for serialized and deserialized behavior.
I looked at another stackoverflow article here, but I still don't understand how to do this. Please help me to understand how to perform these tests,
Below is my part of code:
namespace PMT.Service.Common.DataContract
{
public partial class MyBankInfo
{
public string MyId { get; set; }
public string MyAccountNumber { get; set; }
public string MyAccountType { get; set; }
public string MyBankName { get; set; }
public string MyBankBranchName { get; set; }
public string MyBankCity { get; set; }
public string MyBankCityPincode { get; set; }
public string MyBankIFSCCode { get; set; }
public void Serialize(BinaryStreamWriter binaryStreamWriter)
{
binaryStreamWriter.Write(MyId);
binaryStreamWriter.Write(MyAccountNumber);
binaryStreamWriter.Write(MyAccountType);
binaryStreamWriter.Write(MyBankName);
binaryStreamWriter.Write(MyBankBranchName);
binaryStreamWriter.Write(MyBankCity);
binaryStreamWriter.Write(MyBankCityPincode);
binaryStreamWriter.Write(MyBankIFSCCode);
}
public bool Deserialize(BinaryStreamReader binaryStreamReader,out string errorString)
{
errorString = string.Empty;
try
{
MyId = binaryStreamReader.ReadString();
MyAccountNumber = binaryStreamReader.ReadString();
MyAccountType = binaryStreamReader.ReadString();
MyBankName = binaryStreamReader.ReadString();
MyBankBranchName = binaryStreamReader.ReadString();
MyBankCity = binaryStreamReader.ReadString();
MyBankCityPincode = binaryStreamReader.ReadString();
MyBankIFSCCode = binaryStreamReader.ReadString();
}
catch (Exception ex)
{
errorString = ex.Message;
}
return string.IsNullOrEmpty(errorString);
}
}
}
There are two ways to test serialization and deserialization: separately or both together.
Separate tests are best if the serialized data is created by or used by some other software that you don't have control over. In that case, exact formats have to be verified. This is also the hard way.
If your data is only serialized and deserialized by your own class, then you can test both at once:
Create a test object
Create a Writer in memory or backed on disk.
Serialize to that writer.
Create a second object and deserialize it from the saved data.
Write a bunch of assertions that compare the original object's properties to the new object's.
In my web application, I read some settings from an external config file during Application_Start, then access them across the application's many methods:
namespace Common
{
public static class CommonConfigSettings
{
public static string DataSource { get; set; }
public static string DatabaseName { get; set; }
public static string DatabaseUserName { get; set; }
public static string DatabasePassword { get; set; }
}
}
During Application_Start these are read from an XML file into the static variable:
DataSource = els.FirstOrDefault(item => item.Attribute("key").Value == "DataSource").Attribute("value").Value;
DatabaseName = els.FirstOrDefault(item => item.Attribute("key").Value == "DatabaseName").Attribute("value").Value;
DatabaseUserName = els.FirstOrDefault(item => item.Attribute("key").Value == "DatabaseUserName").Attribute("value").Value;
DatabasePassword = els.FirstOrDefault(item => item.Attribute("key").Value == "DatabasePassword").Attribute("value").Value;
In the application they are used as follows:
myConn.ConnectionString = string.Format("Persist Security Info=False; User ID={0}; Password={1}; Initial Catalog={2}; Data Source={3}; Connection Timeout=60",
CommonConfigSettings.DatabaseUserName,
CommonConfigSettings.DatabasePassword,
CommonConfigSettings.DatabaseName,
CommonConfigSettings.DataSource);
At no point in time are the static values written to following Application_Start - they are only read back out (although perhaps by 2+ people simultaneously). There are also no static methods, just properties. I read about locking and thread safety here and here, but have only confused myself. Should I implement locking on these values, and if so, at what point please?
If you are absolutely sure that those properties are written just once (and prior to all read operations), there is no need for locking.
EDIT: The question is: Will it always be so? If you would come to the need to replace this database access information at runtime, you would run into the problem of non-atomic operations (e.g. reading a new database username and and old password if the writing thread would be interrupted at the right/"wrong" time). May be it would be good to provide a method to return all needed data in a single struct. This method can be provided with a thread locking mechanism in the future if the need would arise...
public struct DatabaseAccessData
{
public string DataSource { get; set; }
public string DatabaseName { get; set; }
public string DatabaseUserName { get; set; }
public string DatabasePassword { get; set; }
}
public static class CommonConfigSettings
{
private static string DataSource { get; set; }
private static string DatabaseName { get; set; }
private static string DatabaseUserName { get; set; }
private static string DatabasePassword { get; set; }
public static void SetDatabaseAccessData(DatabaseAccessData data)
{
DataSource = data.DataSource;
DatabaseName = data.DatabaseName;
DatabaseUserName = data.DatabaseUserName;
DatabasePassword = data.DatabasePassword;
}
public static DatabaseAccessData GetDatabaseAccessData()
{
return new DatabaseAccessData
{
DataSource = DataSource,
DatabaseName = DatabaseName,
DatabaseUserName = DatabaseUserName,
DatabasePassword = DatabasePassword
};
}
Let me say that i am not a fan of "static" in this case. If some of your classes depend on having the common configuration settings, you should pass an instance of CommonConfigSettings to them via constructor parameter or property (see "Dependency injection"). I prefer the first, as it is more rigid / strict; you cannot forget to pass an important dependency then.
UPDATE 2:
It seems to be a problem with the containing value in Format.
The Debugger shows me "{{MinValue: 6, MaxValue:44}}" for property Format.
When I change this to .Format = new MyFormat(6,44) it works fine.. so maybe its a problem with the web api 2 I'm using ... but anyway it should just push the source-object to the destination-object ;)
UPDATE:
I removed the originial post and added some real code here. the other classes were only dummies to explain my problem.
By the way: The Problem only occurrs when I add the "Format" Property of type object to the class. otherwise it works fine...
I tried to create an simple example to keep things easy ;) but here are parts of my real code
private void InitAutoMapper()
{
if (_mappingInitialized) //static init
return;
//will prevent Mapper to try to map constructor-parameters. With Copy-Constructors otherwise this would cause infiniteloops and stackoverflowexceptions.
Mapper.Configuration.DisableConstructorMapping();
//From Client to Server
...
Mapper.CreateMap<SDK.Model.Form.Field, Field>();
//From Server to Client
...
Mapper.CreateMap<Field, SDK.Model.Form.Field>();
...
//checks if the configuration was correct.
Mapper.AssertConfigurationIsValid();
_mappingInitialized = true;
}
and this is the call
public string CreateEntityField(SDK.Model.Form.Field field)
{
var mappedField = Mapper.Map<Field>(field);
...
}
my Field-class looks like this (source and destination classes look exactly the same. they are just separated in two different namespaces to have the possibility to add different properties in the future.
public class Field : IEntityRelatedEntity, IModificationTrackObject
{
public string Id { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public int Status { get; set; }
public int SubStatus { get; set; }
...many more fields
public FieldType Type { get; set; }
public object Format { get; set; }
public FieldRequirement Required { get; set; }
public Field()
{
Name = null;
Description = string.Empty;
DisplayName = null;
Type = FieldType.Text;
Required = FieldRequirement.Optional;
CreatedOn = new DateTime();
ModifiedOn = new DateTime();
}
public Field(Field copy)
{
Id = copy.Id;
Format = copy.Format;
...
}
}
and this is the exception I get (sorry parts of this exception are in german but it means that the source and destination-Type of Format are not the same)
exception =
{
Mapping types:\r\nJObject -> JObject
Newtonsoft.Json.Linq.JObject -> Newtonsoft.Json.Linq.JObject
Destination path:
Field.Format.Format
Source value:
{
"MinValue": "2",
"MaxValue": "100"
}
}
The Mapper should just copy the source to the destination for property "Format" and shouldn't care about whats in there...
one more thing: when i Ignore the Format-Property everything works fine, too.
Mapper.CreateMap<SDK.Model.Form.Field, Field>().ForMember(m => m.Format, opt => opt.Ignore());
I have a namespace Common.Param in which i created a bunch of simple classes of static strings like this:
public class Url
{
public static string LoginPage;
public static string AdminPage;
public static string ProfilePage;
}
public class LoginDetails
{
public static string Username;
public static string Password;
}
I want to fill those fields with a xml file made like this
<Param>
<Url>
<LoginPage>http://1.2.3.4/Login.aspx</LoginPage>
<AdminPage>http://1.2.3.4/Admin.aspx</AdminPage>
<ProfilePage>http://1.2.3.4/Profile.aspx</ProfilePage>
</Url>
<LoginDetails>
<Username>myUser</Username>
<Password>p4ssw0rd</Password>
</LoginDetails>
...
</Param>
How can i find all classes within my Common.Param namespace and fill their strings programmatically?
Of course you can do it:
var xdoc = XDocument.Load(path_to_xml);
var url = xdoc.Root.Element("Url");
Url.LoginPage = (string)url.Element("LoginPage");
Url.AdminPage = (string)url.Element("AdminPage");
Url.ProfilePage = (string)url.Element("ProfilePage");
var login = xdoc.Root.Element("LoginDetails");
LoginDetails.Username = (string)login.Element("Username");
LoginDetails.Password = (string)login.Element("Password");
But as #Tarion correctly pointed, I would go with non-static data instead. Static data introduce coupling to your system. Also you will not be able to use xml serialization with static data.
Thus your element names in xml match members of your classes, then deserialization will be really simple. Just create Param class which holds both Url and LoginDetails:
public class Param
{
public Url Url { get; set; }
public LoginDetails LoginDetails { get; set; }
}
public class Url
{
public string LoginPage { get; set; }
public string AdminPage { get; set; }
public string ProfilePage { get; set; }
}
public class LoginDetails
{
public string Username { get; set; }
public string Password { get; set; }
}
Deserialization:
XmlSerializer serializer = new XmlSerializer(typeof(Param));
using(StreamReader reader = new StreamReader(path_to_xml))
{
Param param = (Param)serializer.Deserialize(reader);
}
The general thing you are looking for is "Serialization" / "Deserialization".
There are lots of very competent libraries for this. Try looking here
http://www.codeproject.com/Articles/483055/XML-Serialization-and-Deserialization-Part-1
Serialize an object to XML