Modify configuration section programmatically in medium trust - asp.net

I have a custom ConfigurationSection in my application:
public class SettingsSection : ConfigurationSection
{
[ConfigurationProperty("Setting")]
public MyElement Setting
{
get
{
return (MyElement)this["Setting"];
}
set { this["Setting"] = value; }
}
}
public class MyElement : ConfigurationElement
{
public override bool IsReadOnly()
{
return false;
}
[ConfigurationProperty("Server")]
public string Server
{
get { return (string)this["Server"]; }
set { this["Server"] = value; }
}
}
In my web.config
<configSections>
<sectionGroup name="mySettingsGroup">
<section name="Setting"
type="MyWebApp.SettingsSection"
requirePermission="false"
restartOnExternalChanges="true"
allowDefinition="Everywhere" />
</sectionGroup>
</configSections>
<mySettingsGroup>
<Setting>
<MyElement Server="serverName" />
</Setting>
</mySettingsGroup>
Reading the section works fine. The issue I'm having is that when I read the section via
var settings = (SettingsSection)WebConfigurationManager.GetSection("mySettingsGroup/Setting");
And then I proceed to modify the Server property:
settings.Server = "something";
This doesn't modify the "Server" property in the web.config file.
Note: This needs to work under medium-trust, so I can't use WebConfigurationManager.OpenWebConfiguration which works fine. Is there an explicit way to tell the ConfigSection to save itself?

Short answer - no. .NET team were (allegedly) meant to fix this in v4, but it didn't happen.
The reason is because using WebConfigurationManager.GetSection returns nested read-only NameValueCollections, which do not persist when you change their values. Using WebConfigurationManager.OpenWebConfiguration, as you've quite rightly ascertained, is the only way to obtain read-write access to the config - but then you'll get a FileIOPermission exception thrown, as OpenWebConfiguration attempts to load all inherited configs down to your web.config - which include the machine-level web.config and machine.config files in C:\WINDOWS\Microsoft.NET\Framework, which are explicitly out-of-scope of Medium Trust.
Long answer - use XDocument / XmlDocument and XPath to get/set config values.

Related

Multi-platform compilation: System.Configuration.ConfigurationManager.GetSection throws error on .NetCore

Background:
We are in the process of migrating .Net application to .Net Core.
As a strategy, we would like to keep the existing functionality intact on Full framework while migrating portion of the application to .Net Core. Full application would support .services over Net remoting and REST API whereas .Net core application will only support REST API services.
We have decided to keep the same code base for entire application and support compilation on multiple platforms (NetcoreApp2.1 and Net472).
There is a single application configuration file. Most of the components are dependent on the information stored in this file. Thus we would like to retain the single configuration file for both platforms.
I used System.Configuration.ConfigurationManager package to access configuration information.
Issue:
ConfigurationManager.GetSection(string) throws exception on .Net core platform whereas it works fine on Net472.
Error Message: Configuration system failed to initialize ---> System.Configuration.ConfigurationErrorsException: Unrecognized configuration section system.runtime.remoting
Work around tried so far:
ConfigurationManager.OpenExeConfiguration(configurationUserLevel).GetSection(string) works perfect on both the platforms for fetching the same section
Sample Code:
static MyConfigurationSection myConfigurationSettings { get; set; }
static void Main(string[] args)
{
LoadSettings();
}
private static void LoadSettings()
{
try
{
//Net472 : Works
//NetCoreApp2.1: Throws exception
myConfigurationSettings = ConfigurationManager.GetSection("myCustomSettings") as MyConfigurationSection;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//Works on both platform
myConfigurationSettings = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).GetSection("myCustomSettings") as MyConfigurationSection;
Console.WriteLine(myConfigurationSettings.Applications.Count);
Console.ReadLine();
}
Here is configuration file
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="myCustomSettings" type="TestConfigManager.MyConfigurationSection, TestConfigManager" />
</configSections>
<myCustomSettings>
<applications/>
</myCustomSettings>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="1111" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
Unfortunately, accessing configuration works slightly differently in the Core Framework (and also .NET 5 and 6). Even with the help of the links below, it took me some time to find it out.
This is how it worked for me:
As preparation, go to NUGET package manager and import
Microsoft.Extensions.Configation,
Microsoft.Extensions.Configation.Json,
Microsoft.Extensions.Configation.Xml
and (optional) Microsoft.Windows.Compatibility
Depending on the type of config file, access it as follows:
App.Config
Example:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="myKey" value="myValue"/>
</appSettings>
</configuration>
Declare
public static AppSettingsSection AppConfig { get; private set; } = null;
Initialize it via
AppConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
.AppSettings;
Read any keys via:
var myValue = AppConfig.Settings["myKey"].Value;
appconfig.json
Example:
{
"AppSettings": {
"myKey": "myValue"
}
}
Declare
public static IConfigurationSection JsonConfig { get; private set; } = null;
Initialize it via
JsonConfig = new ConfigurationBuilder().AddJsonFile("appconfig.json",
optional: true, reloadOnChange: true).Build().GetSection("AppSettings");
Read any keys via:
var myValue = JsonConfig["myKey"];
Helpful links:
cant read app config in c-sharp
how to read appsettings values from json
Comparision between appSettings and ApplicationSettings

Please help! How to specify Dynamic Properties for ASP.NET Profiles

My requirement is to create a framework that can consume standard ASP.NET Profile provider. The application contains two libraries, one for Profile access (saving and retrieving Profile using a ProfileBase class) – A framework class library, and the other one for defining properties for the Profile – Developer client class library which consumes the above framework class library.
The idea here is that the developers don’t need to know about the underlying profile implementation (in the framework class library), and all they have to do is to provide properties in a class so the profile can be set and get as expected.
My implementation is below.
(Please note that I have configured the authentication, connection strings and role providers successfully)
Web.config->
<profile inherits="MyCompany.FrameworkLib.ProfileSettingsService, MyCompany.FrameworkLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=12ac5ebb7ed144" >
<providers>
<clear/>
<add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/" />
</providers>
</profile>
*Our design is not to specify profile properties in the web.config.
Implementation of MyCompany.FrameworkLib.ProfileSettingsService ->
public class ProfileSettingsService : ProfileBase, IProfileSettingsService
{
"**Note that if I un-comment the below code Profile works as expected. I do not want this property to be here, but somehow discover it dynamically based on the values being passed to this class. How can I do this?.**"
//[SettingsAllowAnonymous(false)]
//public string HideTransactionButton
//{
// get { return base["HideTransactionButton"] as string; }
// set { base["HideTransactionButton"] = value; }
//}
#region IProfileSettingsService Members
public T Get<T>(string key, T defaultValue)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key");
}
object profileValue = null;
try
{
profileValue = GetUserProfile().GetPropertyValue(key);
}
catch { }
if (profileValue is T)
{
return (T)profileValue;
}
return defaultValue;
}
public void Set<T>(string key, T value)
{
GetUserProfile().SetPropertyValue(key, value);
GetUserProfile().Save();
}
public ProfileSettingsService GetUserProfile(string username)
{
return Create(username) as ProfileSettingsService;
}
public ProfileSettingsService GetUserProfile()
{
var userName = HttpContext.User.Identity.Name;
if (userName != null)
{
return Create(userName) as ProfileSettingsService;
}
return null;
}
#endregion
}
implementation of MyCompany.ConsumeLib.ProfileContext ->
public class ProfileContext : IProfileContext
{
#region IProfileContext Members
[Dependency] //Note that I use Unity for DI
public IProfileSettingsService ProfileSettingsService { get; set; }
public string HideTransactionButton
{
get { return this.ProfileSettingsService.Get<string>("HideTransactionButton", "false"); }
set { this.ProfileSettingsService.Set("HideTransactionButton", value); }
}
#endregion
}
So the question is how to get the Profile working without having to uncomment
//[SettingsAllowAnonymous(false)]
//public string HideTransactionButton
//{
// get { return base["HideTransactionButton"] as string; }
// set { base["HideTransactionButton"] = value; }
//}
in MyCompany.FrameworkLib.ProfileSettingsService
I need to be able to discover properties dynamically within ProfileSettingsService without having to explicitly specifying properties. In this way, developer doesn’t need to worry about maintaining properties in two libraries - (one in the frameworkLib, and the other one is in the ConsumeLib.)
Any ideas greatly appreciated.
Visit this article, can help you:
http://msdn.microsoft.com/en-us/magazine/cc163724.aspx
also this page in MSDN
Defining ASP.NET Profile Properties
I have decided to take another approach to solver this problem. Unfortunately the way MS has designed the ProfileBase class there is no easy way to do this.
There are 2 possible solutions. I use below 'b'
a. Add profile properties to Web.config and use T4 templates dynamically generate them.
b. Have a look at the http://archive.msdn.microsoft.com/WebProfileBuilder. This will also dynamically generate profile properties at compile time.

Parser Error : Could not load type "x" from appdomain from within virtual directory in IIS7

So my scenarios a little funny but theres a reason for it.
I have a parent web application, called Parent, and a second web application called Child. Child is a virtual directory in IIS7 under Parent which is an application in IIS. Child is not a child directory of parent in the file system, only in IIS as a virtual directory. On application loading (Application_Start in global.asax) in the parent web application i tell it to load the child web dlls from childs bin folder using Assembly.LoadFrom() loading it into the app domain of Parent. Then when i try to visit /Child/Default.aspx I get an error saying:
Parser Error
Parser Error Message: Could not load type 'Child._Default'.
Now the Child.dll (the web dll containing childs code behind etc) is in the app domain of the parent application and i can successfully reflect it and its members from code behind in the Parent page Default.aspx.
Furthermore on the Child/Default.aspx if i change the Inherits="Child._Default" to Inherits="System.Web.UI.Page" and then in <% %> tags on the page enumerate the dlls in the app domain i can see Child.dll and reflect its members and invoke functions.
One thing that works is changing CodeBehind to CodeFile in the page directive. Then the page parses correctly. However this only works when the websites are in uncompiled, non published form.
What's happening is the appdomain isn't looking within it's assembly list when it's trying to resolve the "Child" assembly for the page in the Child project.
What you need to do is use the AssemblyResolve event handler in the AppDomain. You can do so like this:
First we create and AssemblyLoader class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.IO;
namespace Parent
{
internal class AssemblyLoader
{
private static List<AssemblyInformation> virtualDirectoryAssemblies = new List<AssemblyInformation>();
private static readonly string virtualDirectoryBinFolderFormatString = "~/{0}/bin/";
private static readonly string[] pathSplitParams = new string[1] { "\\" };
private static readonly string[] assemblyNameSplitParams = new string[1] { "," };
internal static Assembly AssemblyResolve(object sender, ResolveEventArgs e)
{
var name = e.Name.Split(assemblyNameSplitParams, StringSplitOptions.RemoveEmptyEntries).First();
if (!virtualDirectoryAssemblies.Exists(a => a.Name.Equals(name)))
return null;
return Assembly.LoadFrom(virtualDirectoryAssemblies.Single(a => a.Name.Equals(name)).Path);
}
internal static void LoadVirtualDirectories(List<string> virtualDirectories)
{
foreach (var v in virtualDirectories)
{
var path = HttpContext.Current.Server.MapPath(string.Format(virtualDirectoryBinFolderFormatString, v));
AppDomain.CurrentDomain.AppendPrivatePath(path);
AppDomain.CurrentDomain.SetShadowCopyPath(path);
var assemblies = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).ToList();
foreach (var a in assemblies)
{
var name = a.Split(pathSplitParams, StringSplitOptions.RemoveEmptyEntries).Last().Replace(".dll", string.Empty);
if(!virtualDirectoryAssemblies.Exists(i => i.Name.Equals(name)))
{
virtualDirectoryAssemblies.Add(new AssemblyInformation
{
Name = name,
Path = a
});
}
}
}
}
class AssemblyInformation
{
public string Name { get;set; }
public string Path { get; set; }
}
}
}
In the web.config file for the Parent project I added this (if you have more virtual directories, the idea is to have a comma deliminated list):
<appSettings>
<add key="VirtualDirectories" value="Child"/>
</appSettings>
In the web.config of the child project, you add this reference to the assembly Child assembly:
<system.web>
<compilation>
<assemblies>
<add assembly="Child, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</assemblies>
</compilation>
</system.web>
It can also be like this:
<system.web>
<compilation>
<assemblies>
<add assembly="Child"/>
</assemblies>
</compilation>
</system.web>
Now, last but not least, we put this into the Global.asax:
protected void Application_Start(object sender, EventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += AssemblyLoader.AssemblyResolve;
var virtualDirectories =
ConfigurationManager.AppSettings.Get("VirtualDirectories").Split(new string[1] { "," }, StringSplitOptions.RemoveEmptyEntries).ToList();
AssemblyLoader.LoadVirtualDirectories(virtualDirectories);
}
And we're done... :P

Is there a way to use a Dictionary-like collection as an Application Settings object?

I'd like to store a set of key/value pairs in the application settings of my ASP.NET web app, but I'm not finding a straightforward way to do that. For example, these two questions tell me that StringDictionary etc. won't serialize to XML and suggest that I'll have to roll my own implementation. But it seems like this should be easier to do; after all, web.config is XML and < applicationSettings> is essentially a collection of key/value pairs, so it feels like I'm missing something obvious. Given my specific case below, do I really have to roll my own serialization, or is there an easier workaround?
The web app in question is a basic contact form that sends email to different recipients based on the value of a parameter; e.g. http://www.examplesite.com/Contact.aspx?recipient=support would send email to SupportGroup#exampledomain.com.
The goal is to be able add or remove recipients (or change their addresses) by editing the web.config file so that I don't have to recompile and can easily maintain different configurations in test and production environments. For example:
// I can use something like this for the sender address
SmtpMsg.From = New MailAddress(My.Settings.EmailSender)
// And then just edit this part of web.config to use
// different addresses in different environments.
<setting name="EmailSender" serializeAs="String">
<value>webcontact#exampledomain.com</value>
</setting>
// I want something like this for the recipients
SmtpMsg.To.Add(My.Settings.Recipients("support"))
// and presumably some sort of equivalent xml in web.config
// maybe something like this???
<recipients>
<item name="support" serializeAs="String">
<value>SupportGroup#exampledomain.com</value>
</item>
<!-- add or remove item elements here -->
</recipients>
edit: replaced VB comments w/ C# comments because of the code-coloring
The simplist way would obviously be to just drop them in the app settings, but it wouldn't be very neat:
<appSettings>
<add key="EmailSupport" value="support#somedomain.com" />
<add key="EmailSales" value="sales#somedomain.com" />
</appSettings>
Then in your code you're just doing something like:
if (!string.IsNullOrEmpty(Request["recipient"])) {
string recipientEmail =
WebConfigurationManager.AppSettings["Email" + Request["recipient"]];
// Send your email to recipientEmail
}
If you want to be a bit neater, you can create a custom Configuration Section like this (C# I'm afraid, but the docs have VB as well):
namespace EmailSystem {
public class EmailRecipientsSection : ConfigurationSection {
[ConfigurationProperty("emailSender", IsRequired = true, IsKey = false)]
public string EmailSender {
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("emailRecipients", IsDefaultCollection = true)]
public EmailRecipientCollection EmailRecipients {
get {
var emailRecipientCollection =
(EmailRecipientCollection) base["emailRecipients"];
return emailRecipientCollection;
}
}
}
public class EmailRecipientCollection : ConfigurationElementCollection {
public EmailRecipientElement this[int index] {
get { return (EmailRecipientElement) BaseGet(index); }
set {
if (BaseGet(index) != null) {
BaseRemoveAt(index);
}
BaseAdd(index, value);
}
}
public new EmailRecipientElement this[string name] {
get { return (EmailRecipientElement) BaseGet(name); }
}
protected override ConfigurationElement CreateNewElement() {
return new EmailRecipientElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((EmailRecipientElement) element).Name;
}
}
public class EmailRecipientElement : ConfigurationElement {
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name {
get { return (string) this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("emailAddress", IsRequired = true)]
public string EmailAddress {
get { return (string) this["emailAddress"]; }
set { this["emailAddress"] = value; }
}
}
}
Then in your web.config have something like this:
<configSections>
[...]
<section name="EmailSystem" type="EmailSystem, AssmeblyName" />
</configSections>
<EmailSystem emailSender="fromAddress#somedomain.com">
<emailRecipients>
<clear />
<add name="Support" emailAddress="support#somedomain.com" />
<add name="Sales" emailAddress="sales#somedomain.com" />
</emailRecipients>
</EmailSystem>
Then you can call into this:
emailRecipient = Request["recipient"];
var emailSystem = ConfigurationManager.GetSection("EmailSystem")
as EmailRecipientsSection;
string recipientEmail = emailSystem.EmailRecipients[emailRecipient].emailAddress;
// send email to recipientEmail.
You can do a couple things, though honestly I think this is easiest:
<appSettings>
<add key="testValues" value="someone#abc.com, someoneElse#abc.com, yetAnotherSomeone#abc.com" />
</appSettings>
Then you can get your object via:
String[] temp =
ConfigurationManager.AppSettings.GetValues("testValues").ToString().Split(',');
and then do a simple foreach statement to retrieve. You could even set this as a static object to be cached so it's a quicker retrieve. :)
Hope this helps,
JP
EDIT: An alternative scenario involves:
<appSettings file="test.config">
<!-- other settings to default to if test.config doesn't exist -->
</appSettings>
In this case, if you have a test.config file existing in your test environment, the AppSettings.GetValues() call will be made against that file. If the test.config file does not exist, the ConfigurationManager class will use the values within the appSettings node in your web.config file.

How do I define custom web.config sections with potential child elements and attributes for the properties?

The web applications I develop often require co-dependent configuration settings and there are also settings that have to change as we move between each of our environments.
All our settings are currently simple key-value pairs but it would be useful to create custom config sections so that it is obvious when two values need to change together or when the settings need to change for an environment.
What's the best way to create custom config sections and are there any special considerations to make when retrieving the values?
Using attributes, child config sections and constraints
There is also the possibility to use attributes which automatically takes care of the plumbing, as well as providing the ability to easily add constraints.
I here present an example from code I use myself in one of my sites. With a constraint I dictate the maximum amount of disk space any one user is allowed to use.
MailCenterConfiguration.cs:
namespace Ani {
public sealed class MailCenterConfiguration : ConfigurationSection
{
[ConfigurationProperty("userDiskSpace", IsRequired = true)]
[IntegerValidator(MinValue = 0, MaxValue = 1000000)]
public int UserDiskSpace
{
get { return (int)base["userDiskSpace"]; }
set { base["userDiskSpace"] = value; }
}
}
}
This is set up in web.config like so
<configSections>
<!-- Mailcenter configuration file -->
<section name="mailCenter" type="Ani.MailCenterConfiguration" requirePermission="false"/>
</configSections>
...
<mailCenter userDiskSpace="25000">
<mail
host="my.hostname.com"
port="366" />
</mailCenter>
Child elements
The child xml element mail is created in the same .cs file as the one above. Here I've added constraints on the port. If the port is assigned a value not in this range the runtime will complain when the config is loaded.
MailCenterConfiguration.cs:
public sealed class MailCenterConfiguration : ConfigurationSection
{
[ConfigurationProperty("mail", IsRequired=true)]
public MailElement Mail
{
get { return (MailElement)base["mail"]; }
set { base["mail"] = value; }
}
public class MailElement : ConfigurationElement
{
[ConfigurationProperty("host", IsRequired = true)]
public string Host
{
get { return (string)base["host"]; }
set { base["host"] = value; }
}
[ConfigurationProperty("port", IsRequired = true)]
[IntegerValidator(MinValue = 0, MaxValue = 65535)]
public int Port
{
get { return (int)base["port"]; }
set { base["port"] = value; }
}
Use
To then use it practically in code, all you have to do is instantiate the MailCenterConfigurationObject, this will automatically read the relevant sections from web.config.
MailCenterConfiguration.cs
private static MailCenterConfiguration instance = null;
public static MailCenterConfiguration Instance
{
get
{
if (instance == null)
{
instance = (MailCenterConfiguration)WebConfigurationManager.GetSection("mailCenter");
}
return instance;
}
}
AnotherFile.cs
public void SendMail()
{
MailCenterConfiguration conf = MailCenterConfiguration.Instance;
SmtpClient smtpClient = new SmtpClient(conf.Mail.Host, conf.Mail.Port);
}
Check for validity
I previously mentioned that the runtime will complain when the configuration is loaded and some data does not comply to the rules you have set up (e.g. in MailCenterConfiguration.cs). I tend to want to know these things as soon as possible when my site fires up. One way to solve this is load the configuration in _Global.asax.cx.Application_Start_ , if the configuration is invalid you will be notified of this with the means of an exception. Your site won't start and instead you will be presented detailed exception information in the Yellow screen of death.
Global.asax.cs
protected void Application_ Start(object sender, EventArgs e)
{
MailCenterConfiguration.Instance;
}
Quick'n Dirty:
First create your ConfigurationSection and ConfigurationElement classes:
public class MyStuffSection : ConfigurationSection
{
ConfigurationProperty _MyStuffElement;
public MyStuffSection()
{
_MyStuffElement = new ConfigurationProperty("MyStuff", typeof(MyStuffElement), null);
this.Properties.Add(_MyStuffElement);
}
public MyStuffElement MyStuff
{
get
{
return this[_MyStuffElement] as MyStuffElement;
}
}
}
public class MyStuffElement : ConfigurationElement
{
ConfigurationProperty _SomeStuff;
public MyStuffElement()
{
_SomeStuff = new ConfigurationProperty("SomeStuff", typeof(string), "<UNDEFINED>");
this.Properties.Add(_SomeStuff);
}
public string SomeStuff
{
get
{
return (String)this[_SomeStuff];
}
}
}
Then let the framework know how to handle your configuration classes in web.config:
<configuration>
<configSections>
<section name="MyStuffSection" type="MyWeb.Configuration.MyStuffSection" />
</configSections>
...
And actually add your own section below:
<MyStuffSection>
<MyStuff SomeStuff="Hey There!" />
</MyStuffSection>
Then you can use it in your code thus:
MyWeb.Configuration.MyStuffSection configSection = ConfigurationManager.GetSection("MyStuffSection") as MyWeb.Configuration.MyStuffSection;
if (configSection != null && configSection.MyStuff != null)
{
Response.Write(configSection.MyStuff.SomeStuff);
}
The custom configuration are quite handy thing and often applications end up with a demand for an extendable solution.
For .NET 1.1 please refer the article https://web.archive.org/web/20211027113329/http://aspnet.4guysfromrolla.com/articles/020707-1.aspx
Note: The above solution works for .NET 2.0 as well.
For .NET 2.0 specific solution, please refer the article https://web.archive.org/web/20210802144254/https://aspnet.4guysfromrolla.com/articles/032807-1.aspx
You can accomplish this with Section Handlers. There is a basic overview of how to write one at http://www.codeproject.com/KB/aspnet/ConfigSections.aspx however it refers to app.config which would be pretty much the same as writing one for use in web.config. This will allow you to essentially have your own XML tree in the config file and do some more advanced configuration.
The most simple method, which I found, is using appSettings section.
Add to Web.config the following:
<appSettings>
<add key="MyProp" value="MyVal"/>
</appSettings>
Access from your code
NameValueCollection appSettings = ConfigurationManager.AppSettings;
string myPropVal = appSettings["MyProp"];

Resources