Is it possible to initialize a ConfigurationSection from an external file? - c#

I have a custom configuration property in my app. It looks something like this:
public class OverrideConfiguration : ConfigurationSection
{
[ConfigurationProperty(PROP_ADMIN_CONNSTR)]
public StringConfigurationElement AdminConnectionString
{
get { return base[PROP_ADMIN_CONNSTR] as StringConfigurationElement; }
set { base[PROP_ADMIN_CONNSTR] = value; }
}
// .. Various other properties, but you get the idea
}
However, what I'd like is to allow the .config file to be pointed to an external file source. Something like this:
<ServiceOverrides file="Overrides.local.config" />
Now, the built-in configSource attribute is close to what I need, but it has two major issues.
Files must exist. If the file doesn't exist, it errors out.
Files must be in the current directory or in a deeper directory. In other words, I can't point to ..\Overrides.local.config
What I want is pretty much identical to the <appSettings file="..." /> configuration element. However, that attribute seems to be something appSettings implemented, and is not part of the base ConfigurationSection class.
My Question:
Is it possible to override something in ConfigurationSection that will basically read XML data from a different location? I don't want to change any other aspect of my class or do my own XML deserialization or anything. I simply want to check if a file exists, if so, load in the XML contents from that file, otherwise load in the default XML contents.

Ok, I have a working solution. I'm not sure if it's the best approach, but it does appear to work exactly how I want.
private readonly Queue<String> externalSources = new Queue<String>();
protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
{
var externalFile = reader.GetAttribute("File");
if(!String.IsNullOrWhiteSpace(externalFile))
{
externalSources.Enqueue(externalFile);
}
base.DeserializeElement(reader, serializeCollectionKey);
}
protected override void PostDeserialize()
{
base.PostDeserialize();
// Override data with local stuff
if (externalSources.Count == 0) return;
string file = externalSources.Dequeue();
if (System.IO.File.Exists(file))
{
var reader = XmlReader.Create(file);
base.DeserializeSection(reader);
}
}
First, I trap the DeserializeElement event, which happens when we read the <ServiceOverrides> element. We check if it has a File attribute, and if so we add it to a queue of external sources to load.
Next, we trap the PostDeserialize event, which gets called after all the local XML is parsed. If there's an external source in the queue, we dequeue it, check if it actually exists, then create an XmlReader with the contents of that file. Now we can simply call DeserializeSection again and pass in our new reader. The ConfigurationSection class is smart enough to just overwrite or append any new data to the existing configuration. What I get at the end is an aggregation of both configuration files, where the include file wins in the event of a duplicate.
Now, what's this nonsense with the queue? Well, it seems every time you call DeserializeSection, it'll call PostDeserialize again. So, if we simply trapped PostDeserialize, check the File attribute, and call DeserializeSection again, we'd get in an infinite loop. We could just use a flag to remember if we already loaded the external file, but a queue has the added benefit of allowing the include file to load more include files (not that I'd ever want to do that, but you might).
Tips: This will probably work fairly well, and is simple to understand, but if you're using it in production code, there's a few things you could improve on. First, externalSources doesn't really need to be a queue, since these calls aren't actually recursive. You can probably just use a string, and set it to null after you're done processing that file. Second, this could cause an infinite loop in the event of a circular include chain. You could create a List<T> of previously included files, then check if the include already exists in that list before adding it to the queue.
Hope this helps someone!

Related

How to output a file from a roslyn code analyzer?

I'm using the roslyn API to write a DiagnosticAnalyzer and CodeFix.
After I have collected all strings and string-interpolations, I want to write all of them to a file but I am not sure how to do this the best way.
Of course I can always simply do a File.WriteAllText(...) but I'd like to expose more control to the user.
I'm also not sure about how to best trigger the generation of this file, so my questions are:
I do not want to hard-code the filename, what would be the best way to expose this setting to the user of the code-analyzer? A config file? If so, how would I access that? ie: How do I know the directory?
If one string is missing from the file, I'd like to to suggest a code fix like "Project contains changed or new strings, regenerate string file". Is this the best way to do this? Or is it possible to add a button or something to visual studio?
I'm calling the devenv.com executable from the commandline to trigger builds, is there a way to force my code-fix to run either while building, or before/after? Or would I have to "manually" load the solution with roslyn and execute my codefix?
I've just completed a project on this. There are a few things that you will need to do / know.
You will probably need to switch you're portable class library to a class library. otherwise you will have trouble calling the File.WriteAllText()
You can see how to Convert a portable class library to a regular here
This will potentially not appropriately work for when trying to apply all changes to document/project/solution. When Calling from a document/project/solution, the changes are precalcuated and applied in a preview window. If you cancel, an undo action is triggered to undo all changes, if you write to a file during this time, and do not register an undo action you will not undo the changes to the file.
I've opened a bug with roslyn but you can handle instances by override the preview you can see how to do so here
And one more final thing you may need to know is how to access the Solution from the analyzer which, Currently there is a hack I've written to do so here
As Tamas said you can use additional files you can see how to do so here
You can use additional files, but I know on the version I'm using resource files, are not marked as additional files by default they are embeddedResources.
So, for my users to not have to manually mark the resource as additonalFiles I wrote a function to get out the Designer.cs files associated with resource files from the csproj file using xDoc you can use it as an example if you choose to parse the csproj file:
protected List<string> GetEmbeddedResourceResxDocumentPaths(Project project)
{
XDocument xmldoc = XDocument.Load(project.FilePath);
XNamespace msbuild = "http://schemas.microsoft.com/developer/msbuild/2003";
var resxFiles = new List<string>();
foreach (var resource in xmldoc.Descendants(msbuild + "EmbeddedResource"))
{
string includePath = resource.Attribute("Include").Value;
var includeExtension = Path.GetExtension(includePath);
if (0 == string.Compare(includeExtension, RESX_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
{
var outputTag = resource.Elements(msbuild + LAST_GENERATED_TAG).FirstOrDefault();
if (null != outputTag)
{
resxFiles.Add(outputTag.Value);
}
}
}
return resxFiles;
}
For config files you can use the AdditionalFiles msbuild property, which is passed to the analyzers through the context. See here.

Why does any reference to Server.MapPath() kill my HttpContext Request.Params[]?

I am very much a novice in HttpHandlers. This is actually my first attempt at it.
I have this Ajax handler that responds to a jQuery ajax call in my web app front end:
public class ajaxMeetingHandler : IHttpHandler {
public void ProcessRequest(HttpContext context) {
string resultsJSON = "";
string requestType = context.Request.Params["requestType"];
if (!String.IsNullOrEmpty(requestType)) {
switch (requestType) {
case "RecentMXMeetings":
resultsJSON = SerialiseRecentMeetings(context, "Maintenance");
// SerialiseRecentMeetings() is a method in the class
// that works fine and is not included for brevity.
break;
// more cases (not included for brevity)
}
}
public bool IsReusable {
get {
return false;
}
}
}
}
And this works perfectly.
However, if I add either of these two statements anywhere in the code:
var x = context.Server.MapPath("/");
var y = HttpContext.Current.Server.MapPath("/");
...my Context.Request.Params[] collection becomes null, and IsNullOrEmpty(requestType) now sees requestType as null. In fact, ALL the Request.Params[] are null.
If I comment out those statements (and completely rebuild the solution) the thing goes back to working properly.
In the meantime, I am going to move the calls to MapPath() out to a static "RunTimeEnvironment" class so I can get the path I need from there without touching MapPath() from inside this HttpHandler. Is that a viable or recommended solution?
It turns out my problem was not related to the code itself, per se, but how I was running the project.
Visual Studio, when you click on the "Start Debug" button will start the solution at it's root document (ie, index.html). That is UNLESS the current open document is of a runnable type, such as .html. If your current open window is one of your class files (ie, .cs), it will not attempt to run the class file, but will start the debug session at your root document.
However, it WILL attempt to run a Generic Handler (.ashx) all by itself if that is the document you currently have open. And, by doing so, it was not starting at the index.html page which issues my ajax calls and sends parameters to the Handler. So, my Params collection was null because it was literally null. Running the .ashx by itself supplies no parameters.
So, the reason it worked after changing my call type from GET to POST and Back to GET again is because in doing so, I opened the index.html file to make that change, and when I started my debug session again, my current document was the index.html file, not the Generic Handler .ashx file.
I should probably lose a hundred reputations points just for making this dumb of a mistake. But in case it helps others, there it is.

C# check data in json file

I'm writing a plugin for a game server modification.
Basically the plugin revokes the item dropping right from players if they don't have the bypass permission.
This worked but when a player removes an already placed object from the world, the plugin shows the error message and gets in a loop giving the player the same item said player removed. ( I guess it need a break; but I'm not sure)
I tried to extend the plugin by adding excluded items. The plugin would check if the item ID the player tries to drop is listed in a .json config file. If it is, then said player drops the item. If not listed, then the item gets deleted, except if the player has the bypass permission.
The difficulty I'm have is that I don't know how to check the item IDs listed in the .json file.
Another alternative is to leave the .json file and put the allowed item IDs inside the code, but I have no idea how to do this either.
I know this is very basic and easy, but just started with c#. I'm reading one of my teacher's book about basic C#, but I would like to finish this project soon.
http://pastebin.com/RgBmtus9
Thanks in advance guys
The linked code fragment doesn't seem to be complete or to compile standalone. Reading through, your code to serialize and deserialize your Config class looks valid, though I can't actually compile and test your DropBan class.
So, is this what you want?
public class DropBan : TerrariaPlugin
{
private Config config;
bool IsExcluded(int id)
{
if (config == null)
{
ReadConfig();
}
return config != null && config.Exclusions != null && config.Exclusions.Contains(id);
}
public class Config
{
public int[] Exclusions = { 267, 188 };
}
// Rest as as shown at http://pastebin.com/RgBmtus9
}

Alternatives to reading config files from class libraries at design time?

I have a WinForm project that contains several UserControls. This WinForm project has a reference to an assembly (lets call it lib.dll) that is created from another project (Class Library) that exists in a different solution.
Now, several of the UserControls make calls into lib.dll that return values from the app.config file. At runtime lib.dll works fine and returns the necessary data but at design time, I am getting an exception from lib.dll because the app.config sections are NULL (the exceptions are by design).
Now I could go through each control and wrap any code that calls into lib with
if(!DesignMode) { //code }
But that is a lot of controls to go and apply that to. Is there something I can do globally that would be more elegant then testing the DesignMode property?
Edit
In response to the two comments left below: the solutions provided don't appear to work. The assembly that is causing me a problem lives in the same directory as the app.config. The general directory structure looks like this
References Folder
Configurations (Folder)
appsettings.config
app.config
lib.dll
app.config pulls in several other config files (appsettings, cnx strings, etc) which reside in the Configurations directory. In the case of my exception the value I am trying to get resides in one of these ancillary config files that is referenced by app.config.
This is an interesting question. A solution could be to create in lib.dll a static class like this one :
public static class Config
{
private static readonly _param1;
static Config()
{
_param1 = ConfigurationManager.AppSettings["Param1"] ?? "Your default value";
}
public static string Param1
{
get { return _param1; }
}
}
Then, in your code, insted of writing ConfigurationManager.AppSettings["Param1"], you will use Config.Param1. So you won't need to test the property DesignMode.
There are so many ways to do this, IMHO.
One thought that immedidately comes to mind would be to use an inheritance-based approach for the user controls in question? That way, in the base class, you can put that if (DesignMode) check in, and do the correct branching from there.
// if i were to visualizeyour lib.dll data initializer call like this:
class BaseUserControl
{
// i'm guessing that you initialize the data somehow...
void InitializeData()
{
if (!DesignMode)
{
InitializeDataLocal();
}
}
protected virtual InitializeDataLocal()
{
// whatever base behavior you want should go here.
}
}
// in the derived classes, just put the code you currently have for
// fetching the data from lib.dll here...
class UserControl : BaseUserControl
{
protected override InitializeDataLocal()
{
// fetch from lib.dll...
// optionally invoke some base behavior as well,
// if you need to...
base.InitializeDataLocal();
}
}

C#'s application configuration "app.config" file... how to store collections?

I am in the process of writing a service using C# and I need to store a list of strings within the app.config file. I have 161 of these.
How can I store this information in the app.config file? There must be a way, because these are strongly typed values and thus I'm supposed to easily use any valid .NET type in the code to access them!
I want to avoid having one value that uses a comma-separated list for obvious performance issues.
I use Microsoft’s Visual Studio 2010.
In Solution Explorer, expand the Properties node of your project.
In Solution Explorer, double-click the .settings file in which you want to add a new setting. The default name for this file is Settings.settings.
In Settings Designer, set the Name, Type, Scope, and Value for your setting. Each row represents a single setting.
The Type that you need is System.Collections.Specialized.StringCollection. This can be located after clicking Browse at the end of the DropDownList that appears when you click to set the Type.
Click on the button that appears towards the end of the Value TextBox.
Type in your strings, one-by-one, in the dialog that appears.
There is a good article about storing lists (or any custom object) in your app.config files in Best Way Of Saving Lists in App.Config
Essentially, you create an object that represents the data.
public class MyConfig
{
public string[] myList;
public string someOtherValueIfYouWant;
}
And write a config handler for it...
public class ConfigSectionHandler : IConfigurationSectionHandler
{
public const string SECTION_NAME = "MyConfig";
public object Create(object parent, object configContext, XmlNode section)
{
string szConfig = section.SelectSingleNode("//MyConfig").OuterXml;
MyConfig retConf = null;
if (szConfig != string.Empty || szConfig != null)
{
XmlSerializer xsw = new XmlSerializer(typeof(MyConfig));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(szConfig));
ms.Position = 0;
retConf = (MyConfig)xsw.DeSerialize(ms);
}
return retConf;
}
}
And this allows you to put the following XML in your app.config file...
Tell app.config about your cool config section
<configSections>
<section name="MyConfig" type="ConfigSectionHandler,someAssembly" />
</configSection>
And then add your config section...
<MyConfig>
<myList>First one</myList>
<myList>Second one</myList>
<myList>Keep going</myList>
<myList>And so on</myList>
<someOtherValueIfYouWant>some non array config</someOtherValueIfYouWant>
</MyConfig>
https://stackoverflow.com/a/30178144/878539
StringCollection is NOT recommended in VS2015RC for the purpose of annoyance'ness when loading the properties settings dialog window for the project and re-saving the data. If you have a dozen entries for serializeAs="Xml" and everytime you open the properties window it will popup with a dialog for EACH entry telling you to confirm overwrite. Yes/No does absolutely nothing.
The data mismatch for each file will set this off and is a known issue. See my other post related.
Use a workaround such as Custom Config storage or find a way to leave your collections out of the designer or app.config. One or the other.
Viable solution only for those willing to put up with the nag dialog windows or rarely edit the settings property information.

Categories