How to add a field in my quartz configuration dynamically? - c#

I have a quartz configuration section in my web.config, and I want to add a key value field to it. (I know I can just go to the web.config and add it manually but that defeats the purpose)
I tried using this way
var config = (NameValueCollection)WebConfigurationManager.GetSection("quartz");
config.Add("quartz.dataSource.quartzDS.connectionString", "data source =..");
but it failed because collection is read only and can't be modified. Any tip to how to do this?
Edit: I ended up copying the config to a nameValueCollection and then copying it to another one (for the readonly properties) add the key values I want and passing it to the function I needed.
var oldConfig = (NameValueCollection)WebConfigurationManager.GetSection("quartz");
var config = Test(oldConfig);
var connectionString = unitOfWork.GetConnectionStringByTenantIdentifier(tenantId);
config.Add("quartz.dataSource.quartzDS.connectionString", connectionString);
await unitOfWork.GetService<SchedulerService>().StartScheduler(config, tenantId);
this way I will have the custom configuration for every tenant the way I want it. Sorry if my question wasn't clear.

You can actually do this in two ways.
One way is to set your dynamic connection string in the standard AppSettings section and then create a new Quartz Scheduler with a new set of XML properties (an example is provided in Quartz.NET distribution, so I will cut this short)
var properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = "XmlConfiguredInstance",
["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz",
... etc.
};
ISchedulerFactory sf = new StdSchedulerFactory(properties);
IScheduler sched = await sf.GetScheduler();
Then you can save your non-const string in the AppSettings and get it form there.
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings.Add("quartz.dataSource.quartzDS.connectionString", connstring);
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
Or you can read your whole settings file as XML, as previously answered, BUT you have to make sure that any edits are done before you initialize the default Quartz Scheduler, as it's properties become read-only, and to change them you will have to create a new ISchedulerFactory anyway, which kinda beats the purpose.
var xmlDoc = new XmlDocument();
xmlDoc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
xmlDoc.SelectSingleNode("//quartz/add[#key='quartz.dataSource.quartzDS.connectionString']").Attributes["value"].Value = "...";
xmlDoc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
ConfigurationManager.RefreshSection("quartz");
But I advise you not to edit your main config file at runtime at all, and instead use an ISchedulerFactory XmlConfiguredInstance while getting and saving the connstring into a UAC-compatible location in any format you like (to prevent Modifying app.config at runtime throws exception from happening)
Still if you want to use the config file you can use this tutorial from Yi Zeng for further reading

You can try using XmlDocument classes to go to a lower level.
Make sure the user of your app has write permissions to the config file
public static void WriteKey(String configFileName, String key, String value)
{
XmlDocument doc = new XmlDocument();
doc.Load(configFileName);
XmlNode node = doc.SelectSingleNode("//quartz");
if (node == null)
{
throw new InvalidOperationException("quartz section not found in config file.");
}
try
{
XmlElement elem = (XmlElement)node.SelectSingleNode(string.Format("//add[#key='{0}']", key));
if ((elem != null))
{
elem.SetAttribute("value", value);
}
else
{
elem = doc.CreateElement("add");
elem.SetAttribute("key", key);
elem.SetAttribute("value", value);
node.AppendChild(elem);
}
doc.Save(configFileName);
}
catch
{
throw new InvalidOperationException("Error writing config file");
}
}

Related

Save data in a Visual Studio WPF aplication without a database

I'm developing a WPF app using C#.
The first thing my app does is trying to connect to the database, so I ask for some data to connect to the database like the name of the server (could be the IP too), the name of the database, the name of MySQL instance user and password, and the port (3306 for default). But I want to save this info in the app because I don't have the database yet to save there.
I want to save this strings in the application without using a database:
Computer Name
Name of the database backup
MySql Instance User
MySql Instance Pass
Port
I don't want to save this data in the database because I need this info for the first use of the application.
With first use I mean before the database backup is even restored to the server from the installer.
You can save file with registry. Try this :
Microsoft.Win32.RegistryKey RegistryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("WPF APPLICATION");
RegistryKey.SetValue(SET THE VALUE);
RegistryKey.Close();
best practice is to store these values in configuration file. like a .ini file or xml file.
if your data is sensitive and you don't wish to see this details directly you can encrypt this data with any convenient encryption method.
so your ini file structure will look like this,
[port]=3306
[ip]=111.222.1.2
hope this will help.
Try using the app.Config.
The main benefit of the app.config is that It's directly attached to your executable. Once you build your solution, the app.config gets copied together with the executable.
From What is App.config in C#.NET? How to use it?:
At its simplest, the app.config is an XML file with many predefined configuration sections available and support for custom configuration sections. A "configuration section" is a snippet of XML with a schema meant to store some type of information.
Settings can be configured using built-in configuration sections such as connectionStrings or appSettings. You can add your own custom configuration sections; this is an advanced topic, but very powerful for building strongly-typed configuration files.
Source for app.config in msdn: How to: Add an Application Configuration File to a C# Project
.NET Applications are compiled with a .config file like "YourApp.exe.config" next to the .exe.
This file should be used for such purposes, and can be accessed in code with the ConfigurationManager.
/You can Save Data in XML file/
//You can Save and load time by this method but it's slow process,
it may crash if data is large and system is slow, it stores data runtime
so takes RAM, its ok to use for few rows without any problem
//use the collection for storing data runtime
List<Person> pers = new List<Person>();
public class Person
{
public string id { get; set; }//1
public string name { get; set; }//2
public string bilno { get; set; }//3
public string mob { get; set; }//4
public DateTime dt { get; set; }//5
}
string path=#"c:\.....";
void save()
{
XmlDocument xdoc = new XmlDocument();
xdoc.Load(path + #"\data.xml");
XmlNode xnode = xdoc.SelectSingleNode("Items");
xnode.RemoveAll();
foreach (Person i in pers)
{
XmlNode xtop = xdoc.CreateElement("Item");
XmlNode x1 = xdoc.CreateElement("a");
XmlNode x2 = xdoc.CreateElement("b");
XmlNode x3 = xdoc.CreateElement("c");
XmlNode x4 = xdoc.CreateElement("d");
XmlNode x5 = xdoc.CreateElement("e");
x1.InnerText = i.id;
x2.InnerText = i.name;
x3.InnerText = i.bilno;
x4.InnerText = i.mob;
x5.InnerText = i.dt.ToFileTime().ToString();
xtop.AppendChild(x1);
xtop.AppendChild(x2);
xtop.AppendChild(x3);
xtop.AppendChild(x4);
xtop.AppendChild(x5);
xdoc.DocumentElement.AppendChild(xtop);
}
xdoc.Save(path + #"\data.xml");
}
void load()
{
XmlDocument xdoc = new XmlDocument();
xdoc.Load(path + #"\data.xml");
foreach (XmlNode xnode in xdoc.SelectNodes("Items/Item"))
{
Person p = new Person();
p.id = xnode.SelectSingleNode("a").InnerText;
p.name = xnode.SelectSingleNode("b").InnerText;
p.bilno = xnode.SelectSingleNode("c").InnerText;
p.mob = xnode.SelectSingleNode("d").InnerText;
p.dt = DateTime.FromFileTime(Convert.ToInt64(xnode.SelectSingleNode("e").InnerText));
}
}

Modify programatically csproj files with Microsoft.Build.Evaluation (instead of Engine)

I would like to read, modify and write back csproj files.
I've found this code, but unfortunately Engine class is depreciated.
Engine engine = new Engine()
Project project = new Project(engine);
project.Load("myproject.csproj");
project.SetProperty("SignAssembly", "true");
project.Save("myproject.csproj");
So I've continued based on the hint I should use Evaluation.ProjectCollection instead of Engine:
var collection = new ProjectCollection();
collection.DefaultToolsVersion = "4.0";
var project = new Project(collection);
// project.Load("myproject.csproj") There is NO Load method :-(
project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// ... modify the project
project.Save(); // Interestingly there is a Save() method
There is no Load method anymore. I've tried to set the property FullPath, but the project still seems empty. Missed I something?
(Please note I do know that the .csproj file is a standard XML file with XSD schema and I know that we could read/write it by using XDocument or XmlDocument. That's a backup plan. Just seeing the .Save() method on the Project class I think I missed something if I can not load an existing .csproj. thx)
I've actually found the answer, hopefully will help others:
Instead of creating a new Project(...) and trying to .Load(...) it, we should use a factory method of the ProjectCollection class.
// Instead of:
// var project = new Project(collection);
// project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// use this:
var project = collection.LoadProject("myproject.csproj")
Since i can't comment:
This won't work in .net core without first setting the MSBuild.exe path variable. The code to do so can be found here
https://blog.rsuter.com/missing-sdk-when-using-the-microsoft-build-package-in-net-core/
and is written here
private static void SetMsBuildExePath()
{
try
{
var startInfo = new ProcessStartInfo("dotnet", "--list-sdks")
{
RedirectStandardOutput = true
};
var process = Process.Start(startInfo);
process.WaitForExit(1000);
var output = process.StandardOutput.ReadToEnd();
var sdkPaths = Regex.Matches(output, "([0-9]+.[0-9]+.[0-9]+) \\[(.*)\\]")
.OfType<Match>()
.Select(m => System.IO.Path.Combine(m.Groups[2].Value, m.Groups[1].Value, "MSBuild.dll"));
var sdkPath = sdkPaths.Last();
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", sdkPath);
}
catch (Exception exception)
{
Console.Write("Could not set MSBUILD_EXE_PATH: " + exception);
}
}

Adding a second SQL data provider to ScrewTurn

I need to add a second sql data provider to our screwturn wiki, i added the connection the web config but can't seem to add it to the startup. I have...
public static string GetSettingsStorageProviderConfiguration2()
{
string config = WebConfigurationManager.AppSettings["SettingsStorageProviderConfig2"];
if (config != null) return config;
else return "";
}
Then in startup I have duplicated the host and settings provider info, this is probably redundant but i'm not sure how to do it otherwise...
Host Host2 = new Host();
ISettingsStorageProviderV30 ssp2 = ProviderLoader.LoadSettingsStorageProvider(WebConfigurationManager.AppSettings["SettingsStorageProvider2"]);
ssp2.Init(Host2, GetSettingsStorageProviderConfiguration2());
Collectors.SettingsProvider2 = ssp2;
I updated TryLogin with
IUsersStorageProviderV30 connection2 = Collectors.UsersProviderCollector.GetProvider(StartupTools.GetSettingsStorageProviderConfiguration()) as IUsersStorageProviderV30;
//IUsersStorageProviderV30[] providers2 =
// Then try all other providers
List<IUsersStorageProviderV30> providers = Collectors.UsersProviderCollector.AllProviders.OfType<IUsersStorageProviderV30>().ToList();
providers.Add(connection2);
But I'm missing something to make connection2 not return null.
Am I way out in left field here? Is there a easier way to go about this?

How to Update (Add/Modify/Delete) keys in AppSettings section of web.config at runtime

I like to Update keys/Values defined in AppSettings section of Web.config at runtime. however I DO NOT want to actually save them to Web.config file.
I have a huge web application that have consists of many modules, DLLs and source code files. A bunch of critical information ranged from database configuration, encryption keys, username and passwords for webservices are saved in AppSettings section of the web.config file. Recent project requirement needs me to move these values out of web.config and keep in a secure storage.
I already secured these values in an external location and I can read them back when application starts.
here is the sample code.
Global.asax
public class Global: System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
Dictionary<string, string> secureConfig = new Dictionary<string,string>{};
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey","VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
ConfigurationManager.AppSettings.Add(item.Key, item.Value);
}
}
}
As you may noticed it is not feasible to change references to AppSettings in a massive code created by multiple programming teams to read their settings from my secureConfig dictionary and on the other hand I should not save these values in web.config file which is available to web administrators and operators, system admins and cloud admins.
To Make programmers life easier, I want to let them add their values to AppSettings section of web.config during development, but they will be removed from there and put to secure storage later during deployment, however these values should be available to program transparently as they are still in AppSettings section.
Question: how can I add values to AppSettings at runtime so program can read them using ConfigurationManager.AppSettings["ACriticalKey"] to get "VeryCriticalValue" without saving them in Web.Config?
Please note: ConfigurationManager.AppSettings.Add(item.Key, item.Value); gives me ConfigurationErrorsException with message The configuration is read only.
Please note: Preferably some settings should be able to stay in AppSettings as before
I know this is an old question, but I ran into the same problem and I found that Set works in the same way as Add, and does not throw an exception, so just replace Add with Set, like so:
ConfigurationManager.AppSettings.Set(item.Key, item.Value);
You need to make use of WebConfigurationManager.OpenWebConfiguration()
Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
config.AppSettings.Settings.Remove("Variable");
config.AppSettings.Settings.Add("Variable", "valyue");
config.Save();
Perhaps this link will help. It references 2.0 but I believe the method is still valid in 4.0.
Also, the SO question on the same/similar topic here may be of interest.
Also, modifying the web.config at runtime should cause an application pool recycle each time. Not trying to tell you how to suck eggs, just thought I'd note it for anyone's prospective interest...Thx.
Thanks to nkvu which directed me to a his first link which in turn sent me to Williarob's post "Override Configuration Manager" I managed to find a solution to my question.
The mentioned blog post covers how to read settings from another XML file and it works with both windowed applications and web applications (with a little modification in config file name and path). Although this blog written on 2010 it is still working fine with .NET4 without problem.
However as I was going to read my configuration from a secure device, I simplified the class and here is how to use the classes provided by Williarob
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Internal;
using System.Linq;
using System.Reflection;
namespace Williablog.Core.Configuration {
public sealed class ConfigSystem: IInternalConfigSystem {
private static IInternalConfigSystem clientConfigSystem;
private object appsettings;
private object connectionStrings;
/// <summary>
/// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config
/// </summary>
public static void Install() {
FieldInfo[] fiStateValues = null;
Type tInitState = typeof(System.Configuration.ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic);
if (null != tInitState) {
fiStateValues = tInitState.GetFields();
}
FieldInfo fiInit = typeof(System.Configuration.ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
FieldInfo fiSystem = typeof(System.Configuration.ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);
if (fiInit != null && fiSystem != null && null != fiStateValues) {
fiInit.SetValue(null, fiStateValues[1].GetValue(null));
fiSystem.SetValue(null, null);
}
ConfigSystem confSys = new ConfigSystem();
Type configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
IInternalConfigSettingsFactory configSettingsFactory = (IInternalConfigSettingsFactory) Activator.CreateInstance(configFactoryType, true);
configSettingsFactory.SetConfigurationSystem(confSys, false);
Type clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
clientConfigSystem = (IInternalConfigSystem) Activator.CreateInstance(clientConfigSystemType, true);
}
#region IInternalConfigSystem Members
public object GetSection(string configKey) {
// get the section from the default location (web.config or app.config)
object section = clientConfigSystem.GetSection(configKey);
switch (configKey) {
case "appSettings":
// Return cached version if exists
if (this.appsettings != null) {
return this.appsettings;
}
// create a new collection because the underlying collection is read-only
var cfg = new NameValueCollection();
// If an AppSettings section exists in Web.config, read and add values from it
if (section is NameValueCollection) {
NameValueCollection localSettings = (NameValueCollection) section;
foreach (string key in localSettings) {
cfg.Add(key, localSettings[key]);
}
}
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey", "VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
if (cfg.AllKeys.Contains(item.Key)) {
cfg[item.Key] = item.Value;
} else {
cfg.Add(item.Key, item.Value);
}
}
// --------------------------------------------------------------------
// Cach the settings for future use
this.appsettings = cfg;
// return the merged version of the items from secure storage and appsettings
section = this.appsettings;
break;
case "connectionStrings":
// Return cached version if exists
if (this.connectionStrings != null) {
return this.connectionStrings;
}
// create a new collection because the underlying collection is read-only
ConnectionStringsSection connectionStringsSection = new ConnectionStringsSection();
// copy the existing connection strings into the new collection
foreach (ConnectionStringSettings connectionStringSetting in ((ConnectionStringsSection) section).ConnectionStrings) {
connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
}
// --------------------------------------------------------------------
// Again Load connection strings from secure storage and merge like below
// connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
// --------------------------------------------------------------------
// Cach the settings for future use
this.connectionStrings = connectionStringsSection;
// return the merged version of the items from secure storage and appsettings
section = this.connectionStrings;
break;
}
return section;
}
public void RefreshConfig(string sectionName) {
if (sectionName == "appSettings") {
this.appsettings = null;
}
if (sectionName == "connectionStrings") {
this.connectionStrings = null;
}
clientConfigSystem.RefreshConfig(sectionName);
}
public bool SupportsUserConfig { get { return clientConfigSystem.SupportsUserConfig; } }
#endregion
}
}
To install this (or original version of configuration override) add following line to
your Global. class (Global.asax.cs) in Application_Start
Williablog.Core.Configuration.ConfigSystem .Install();
like below:
public class Global: System.Web.HttpApplication {
//...
#region protected void Application_Start(...)
protected void Application_Start(object sender, EventArgs e) {
Williablog.Core.Configuration.ConfigSystem .Install();
//...
}
#endregion
//...
}

I can't modify the app.config file

What's wrong with the following code:
private static void UpdateAppSettings(string settingName, string settingValue)
{
if (settingName == null) throw new ArgumentNullException("settingName");
if (settingValue == null) throw new ArgumentNullException("settingValue");
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var appSettings = config.AppSettings;
var setting = appSettings.Settings[settingName];
setting.Value = settingValue;
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("AppSettings");
}
I don't know why it doesn't save the new settings. It first opens the app.config file. After going to appsetting area. It then finds a specific key as settingName and changes the value to settingValue. Afterwards, it saves the file and refreshes it. It almost works; up to config.Save(...). But after that I don't know what happens.
This question seems to be the same as what you are asking.
The answer looks to be this:
config.AppSettings[settingName] = settingValue;
EDIT:
This question has the answer I think
config.AppSettings.Settings[settingName]

Categories

Resources