I need to improve the reporting of errors in app.config files.
I have a tiny test app that includes a setting of type "int". If I change its value in app.config to something that isn't a valid integer, I'd expect an exception to get raised that I can catch and report. Unfortunately, something is eating the exception. Is there a straightforward way to prevent that behaviour and let the exception propagate out?
My Settings.Designer.cs file (generated by Visual Studio):
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(
"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default { get { return defaultInstance; } }
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("17")]
public int SecondSetting
{
get { return ((int)(this["SecondSetting"])); }
set { this["SecondSetting"] = value; }
}
}
My C# test app:
static void Main (string[] args)
{
try
{
Console.WriteLine(AppConfigTests.Properties.Settings.Default.SecondSetting);
}
catch (Exception x)
{
Console.WriteLine(x.ToString());
}
}
The relevant portion of my App.config file (note that the value isn't a valid integer):
<userSettings>
<AppConfigTests.Properties.Settings>
<setting name="SecondSetting" serializeAs="String">
<value>1foo7</value>
</setting>
</AppConfigTests.Properties.Settings>
</userSettings>
A System.Format exception is getting thrown by System.Number.StringToNumber, but something is catching it and throwing it away, and my catch block never gets entered. In the Visual Studio debugger output, I found "A first chance exception of type 'System.FormatException' occurred in mscorlib.dll", but that doesn't help me except to further confirm that an exception was thrown.
I tried adding an IntegerValidatorAttribute to my setting property in Settings.Designer.cs, but that didn't help (and I made sure the .cs didn't get regenerated).
I tried adding the following code at the top of my main() method, but that didn't help either:
foreach (SettingsProperty sp in AppConfigTests.Properties.Settings.Default.Properties)
sp.ThrowOnErrorDeserializing = true;
I've thought of implementing my own ConfigurationSection, but I'm hoping there's an easier solution.
Just to reiterate: I need a way to report errors in settings values in app.config files.
(If I break the XML syntax in App.config (by removing an angle bracket, for example), my catch block gets a nice exception with all the detail I could ask for.)
You can do it like this (assuming you have all the setting values overriden in app.config):
foreach (SettingsPropertyValue propertyValue in AppConfigTests.Properties.Settings.Default.PropertyValues) {
if (propertyValue.UsingDefaultValue) {
throw new Exception(propertyValue.Name + " is not deserialized properly.");
}
}
If you're writing web application, it is firing this event when deserialization fails:
try {
if (this.IsHostedInAspnet()) {
object[] args = new object[] { this.Property, this, ex };
Type type = Type.GetType("System.Web.Management.WebBaseEvent, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
type.InvokeMember("RaisePropertyDeserializationWebErrorEvent", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, null, args, CultureInfo.InvariantCulture);
}
}
catch {
}
Otherwise, it just get back to default value, so only thing you can to is iterate trough all values and check if they're not defaulted.
Related
I have a Framework 4.8 C# app that uses ClearScript to allow JavaScript to be used as an extension language. I am able to write plugins as DLLs and attach them at runtime, viz
JSE.Script.attach = (Func<string, bool>)Attach;
...
private static bool Attach(string dllPath, string name = "")
{
var status = false;
var htc = new HostTypeCollection();
try
{
var assem = Assembly.Load(AssemblyName.GetAssemblyName(dllPath));
htc.AddAssembly(assem);
if (name.Length == 0)
{
name = assem.FullName.Split(',')[0];
}
JSE.AddHostObject(name, htc); //FIXME checkout the hosttypes
Console.Error.WriteLine($"Attached {dllPath} as {name}");
status = true;
}
catch (ReflectionTypeLoadException rtle)
{
foreach (var item in rtle.LoaderExceptions)
{
Console.Error.WriteLine(item.Message);
T.Fail(item.Message);
}
}
catch (FileNotFoundException fnfe)
{
Console.Error.WriteLine(fnfe.Message);
T.Fail(fnfe.Message);
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
T.Fail(e.Message);
}
return status;
}
This permits my scripts to have lines like
attach(".\\Plugin_GoogleAds_Metrics.dll");
H = Plugin_GoogleAds_Metrics.GoogleAds_Metrics.Historical;
H.EnableTrace("GAM");
...
I've made a public repo of the plugin for those interested.
What's not working in this situation is that when I try to execute the plugin's GetAccountInformation method, and execution reaches the GoogleAdsServiceClient googleAdsService = client.GetService(Services.V11.GoogleAdsService); line, an error is thrown complaining about Google.Protobuf, viz
Exception has been thrown by the target of an invocation.
at JScript global code (Script [23] [temp]:5:0) -> acc = H.GetAccountInformation(auths.Item1, 7273576109, true)
at Microsoft.ClearScript.ScriptEngine.ThrowScriptError(IScriptEngineException scriptError)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.ThrowScriptError(Exception exception)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.<>c__DisplayClass57_0`1.<ScriptInvoke>b__0()
at Microsoft.ClearScript.ScriptEngine.ScriptInvokeInternal[T](Func`1 func)
at Microsoft.ClearScript.ScriptEngine.ScriptInvoke[T](Func`1 func)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.ScriptInvoke[T](Func`1 func)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.Execute(UniqueDocumentInfo documentInfo, String code, Boolean evaluate)
at Microsoft.ClearScript.Windows.JScriptEngine.Execute(UniqueDocumentInfo documentInfo, String code, Boolean evaluate)
at Microsoft.ClearScript.ScriptEngine.Evaluate(UniqueDocumentInfo documentInfo, String code, Boolean marshalResult)
at Microsoft.ClearScript.ScriptEngine.Evaluate(DocumentInfo documentInfo, String code)
at Microsoft.ClearScript.ScriptEngine.Evaluate(String documentName, Boolean discard, String code)
at Microsoft.ClearScript.ScriptEngine.Evaluate(String documentName, String code)
at Microsoft.ClearScript.ScriptEngine.Evaluate(String code)
at RulesetRunner.Program.Run(JScriptEngine& jSE, String scriptText, Config cfg, Dictionary`2 settings) in C:\Users\bugma\Source\Repos\Present\BORR\RulesetRunner\RunManagementPartials.cs:line 72
Exception has been thrown by the target of an invocation.
Exception has been thrown by the target of an invocation.
Could not load file or assembly 'Google.Protobuf, Version=3.15.8.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604' or one of its dependencies. The system cannot find the file specified.
So
I am using the latest Google.Ads.GoogleAds library
AutoGenerateBindingRedirects has been set to true in the csproj file
Add-BindingRedirect has been executed in the context of the Plugin's project
The Plugin_GoogleAds_Metrics.dll is in the same folder as the Google.Protobuf.dll
Where to from here?
What fixed this was including Google.Ads.GoogleAds in the calling app. I didn't have to explicitly mention the symbols in the main binary, just have the library in the build. What I expect this did was to include all the relevant DLLs next to the main EXE.
This is definitely not what I wanted. I wanted to be able to hive off all the DLLs into a separate plugin folder and only have them connected when I attached the plugin. Sadly, this does not seem to be achievable at this point. And now I'm wondering about the other plugins I've written that use Google technologies.
Attempts to use Configuration.Save() fail when the WPF program is installed under "Program Files". If installed elsewhere, the Save() works as expected. Isn't the program itself authorized to edit its own .config file?
I get two exceptions:
System.Configuration: An error occurred loading a configuration file: Access to the path 'C:\Program Files\Advanced Applications\ConfigurationTest\ejoqasrr.tmp' is denied. (C:\Program Files\Advanced Applications\ConfigurationTest\WPF.exe.Config)
mscorlib: Access to the path 'C:\Program Files\Advanced Applications\ConfigurationTest\ejoqasrr.tmp' is denied.
I tried opening with ConfigurationUserLevel.PerUserRoaming) instead of ConfigurationUserLevel.None but it still failed, reporting different exceptions:
System.Configuration: An error occurred executing the configuration section handler for appSettings.
System.Configuration: ConfigurationSection properties cannot be edited when locked.
[NOTE: Searching found an article that appears related but didn't provide a resolution for this issue:
Access to path denied for App.Config on Windows 8. How can I update App.Config?
The first two methods are called from MainVM and invoke the underlying base methods which expect to find a correlated property on the passed in object and then synchronize the appropriate values in AppSettings.
private void SettingsLoad()
{
SettingsLoad(this);
}
private void SettingsSave()
{
try
{
SettingsSave(this);
}
catch (Exception ex)
{
currentMessageAction = MessageAction.Acknowledge;
window.MessagePanel.Show("EXCEPTION!", Utility.ParseException(ex));
}
}
....
public static void SettingsLoad(object main)
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
foreach (KeyValueConfigurationElement setting in config.AppSettings.Settings)
{
// Attempt to get a reference to the property and, if found, set its value
var prop = main.GetType().GetProperty(setting.Key);
if (prop != null)
{
prop.SetValue(main, setting.Value);
}
}
}
public static void SettingsSave(object main)
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
foreach (KeyValueConfigurationElement setting in config.AppSettings.Settings)
{
// Attempt to get a reference to the property and, if found, update its value
var prop = main.GetType().GetProperty(setting.Key);
if (prop != null)
{
setting.Value = prop.GetValue(main).ToString();
}
}
// This line produces an exception
config.Save(ConfigurationSaveMode.Modified);
}
Isn't the program itself authorized to edit its own .config file?
No. The user account under which the application is running must have permissons to write to the folder/file.
Apparently it doesn't have the appropriate permissions to write to the "Program Files" folder in your case.
How to Set File and Folder Permissions in Windows
I created a simple API via ASP.NET MVC 4:
public class ActionController : ApiController
{
[WebMethod]
public string getCommunities()
{
try
{
MethodClass method = new MethodClass();
return method.getCommunities();
}
catch(Exception ex)
{
return ex.Message.ToString();
}
}
}
which is trying to call this method in the Method class:
public string getCommunities()
{
return "bbb";
}
but for whatever reason, I get this error:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">cannot parse xml! Check file path</string>
I tried Googling for the error, but came up with nothing, has anyone seen this error before? and how do I fix it?
As already pointed in comments, you are looking for your bug in the wrong place. method.getCommunities() is throwing an error with message "cannot parse xml! Check file path".
Googling your error it seems to me that you are throwing a custom exception: searching for that string in your code may point you to the right place.
As a quick proof of concept I changed the standard API generated by Visual Studio Web API template.
public string Get(int id)
{
try
{
var t = 0;
var i = 1 / t;
return "bbb";
}
catch { return "ABBA"; }
}
which exactly returns the custom error message as xml string
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">ABBA</string>
I attempted to replicate the case you mention by creating simple ActionController.cs in ASP.Net MVC 4 template as follow:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Services;
namespace MvcApiApplicationTrial1.Controllers
{
public class ActionController : ApiController
{
[WebMethod]
public string getCommunities() {
try {
MethodClass method = new MethodClass();
return method.getCommunities();
} catch (Exception ex) {
return ex.Message.ToString();
}
}
}
public class MethodClass
{
public string getCommunities() {
return "bbb";
}
}
}
And call it in the web browser (Chrome) with the following url:
http://localhost:56491/api/Action/getCommunities
And get the following correct result:
If you declare, define, and call things right, your code should have no problem at all.
So, I suggest you to re-check your declaration, definition, as well as your calling to the related Controller/Method again. Your problem may lay somewhere else.
And since the error seems to be a custom error, judging from the code posted alone, likely that the problem lays somewhere in your getCommunities method. Check the method, try to find the "cannot parse xml!" text there. Alternatively, but less likely, the error is in the MethodClass constructor. Same thing, check your MethodClass, try to find the "cannot parse xml!" text.
As for the given case as what you have posted in your question, I found no issue at all.
But anything else in between try and "bbb" can also potentially be the source of the created error. Checking the error text would be my first step if there are more things in the try block and I am unsure where the error may actually be generated.
in Global.asax.cs should put code bellow:
GlobalConfiguration.Configuration.Formatters.Clear();
GlobalConfiguration.Configuration.Formatters.Add(new System.Net.Http.Formatting.XmlMediaTypeFormatter());
and in WebApiConfig code bellow:
config.Formatters.XmlFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
I have a C# application which uses a C# script interface. That means that my application will compile C# code and run it.
I am using the System.CodeDom.Compiler class to do it with.
The problem is that if I run the code below it throws an InvalidCastException because it is trying to cast a string to an int in my dynamic code.
If I catch the exception I have no indication where in the 'dynamic code' that error occured. For instance 'InvalidCastException on line 8'.
I get a stack trace, but no line numbers.
Any ideas? I want to present to our users enough information to know where their error is.
public class NotDynamicClass
{
public object GetValue()
{
return "value";
}
}
class Program
{
public static void Main()
{
var provider = CSharpCodeProvider.CreateProvider("c#");
var options = new CompilerParameters();
options.ReferencedAssemblies.Add("DynamicCodingTest.exe");
var results = provider.CompileAssemblyFromSource(options, new[]
{
#"
using DynamicCodingTest;
public class DynamicClass
{
public static void Main()
{
NotDynamicClass #class = new NotDynamicClass();
int value = (int)#class.GetValue();
}
}"
});
var t = results.CompiledAssembly.GetType("DynamicClass");
t.GetMethod("Main").Invoke(null, null);
}
}
You need to set IncludDebugInformation to true on your CompilerParameters.
Update: At the bottom of the MSDN documentation there is a community remark:
For C#, if you set this property to true you need to also set GenerateInMemory to false and set the value of OutputAssembly to a valid file name. This will generate an assembly and a .pdb file on disk and give you file and line number information in any stacktraces thrown from your compiled code.
The project I'm currently working on uses Enterprise Libraries V3.1 framework for logging.
I need to take the log file that's generated and archive it off at specific points. The built in Trace Listeners seem to keep the file open in-between logging events. I've set up a custom Trace Listener which will append to a file and close it, so that the file is always shiftable.
It looks like this (minus error handling for clarity):
[ConfigurationElementType(typeof(CustomTraceListenerData))]
public class AlwaysClosedTextFileTraceListener : CustomTraceListener
{
private string logFilePath;
public AlwaysClosedTextFileTraceListener ()
{
logFilePath = #"hardcodedpath\log.txt";
}
public override void Write(string message)
{
using (StreamWriter logFile = File.AppendText(logFilePath))
{
logFile.Write(message);
logFile.Flush();
logFile.Close();
}
}
public override void WriteLine(string message)
{
using (StreamWriter logFile = File.AppendText(logFilePath))
{
logFile.WriteLine(message);
logFile.Flush();
}
}
public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
{
if (data is LogEntry && this.Formatter != null)
{
WriteLine(this.Formatter.Format(data as LogEntry));
}
else
{
WriteLine(data.ToString());
}
}
}
This works fine, but I'd much rather be passing in the path as a parameter somehow, rather than hardcoding it.
For fun, I tried adding it to the constructor, to see what happens:
public LogFolderTraceListener(string logFilePath)
{
this.logFilePath = logFilePath;
}
When I do this, I get returned an error message hinting towards what I'm doing wrong:
System.InvalidOperationException : The type 'AlwaysClosedTextFileTraceListener' specified for custom trace listener named 'MyLogFile' does not a default constructor, which is required when no InitData is specified in the configuration.
From here on in, my investigations have very much come to, the opposite of dead ends, infinite probability problems.
I have found this thumbing through the source code for the inbuilt RollingTraceListener
There is a class RollingFlatFileTraceListenerData : TraceListenerData which seems to contain all the settings passed into the constructor
Camped out at the bottom of the file for RollingFlatFileTraceListenerData is the class RollingTraceListenerAssembler : TraceListenerAsssembler which seems to be a factory
There is another class SystemDiagnosticsTraceListenerNode : TraceListenerNode which seems to make the Data class presentable to the configuration application
My question is this: how do I create a CustomTraceListener with a configurable parameter of path?
The CustomTraceListener derives from TraceListener, this has a StringDictionary called Attributes.
This will contain all the attributes in the configuration line for your TraceListener and can be gotten out by name, eg.
string logFileName= Attributes["fileName"]
I suspect that perhaps the Enterprise Application Blocks although (probably) wonderful, seem unnecessarily complicated and ultimately more trouble than their worth for this kind of customisation.
the problem is typical microsoft .. (add your own adjectives here) ..
1) when you add a custom trace listener, the 'raw' app.config statement added is:
name="Custom Trace Listener" initializeData="" formatter="Text Formatter" />
2) notice the 'initializeData' - this is what the cryptic error message is calling'InitData'.
3) So what its all saying is that you need to have a constructor that accepts initialization data - in vb parlance:
sub new (byval initstuff as string)
4) OR remove the 'initializeData=""' and have a default constructor:
sub new()
I suspect the P&P folks live in a bubble.
riix.
For what it is worth this is how I implemented it. In my this.buildCurrPath() I can read from a config file or in this case I just get the "launch pad" for the web app. But it works fine for me. I have not put it into any production code yet, but it should go out soon.
[ConfigurationElementType(typeof(CustomTraceListenerData))]
public class CustomListener: CustomTraceListener
{
#region Fields (3)
private int logSize;
StreamWriter sw;
#endregion Fields
#region Constructors (1)
public CustomListener ():base()
{
string startPath = this.buildCurrPath();
sw = new StreamWriter(startPath + "\\Logs\\test.log");
sw.AutoFlush = true;
}
I have just had the same issue (except with Enterprise Library v4.1).
The solution I've found is to remove the default constructor and the only have a constructor with a string parameter for the filename i.e.
public AlwaysClosedTextFileTraceListener (string pathParameter)
{
logFilePath = pathParameter;
}
Then in the app.config put your path in the initializeData parameter
<add ... initializeData="C:\Logs\myLog.log" />
Whilst this isn't recognised by the Entriprise Library configuration editor and isn't as neat as it could be, it works as long as there is only one parameter.
If someone works out how to do it properly, please post and let us know - it's not supposed to be this difficult, surely.