I'm using log4net with C# to logging my app.
I know I can do it like this:
GlobalContext.Properties["PropertyName"] = "NewValue";
XmlConfigurator.Configure();
And it works.
But it's not thaaaat dynamic, as I have to call Configure again to set a new value.
Is there a way to set a property value before call ILog.Info?
Something like that:
//here I set a new value for %property{PropertyName}
log.Info("Value to log");
//here I set a another one for %property{PropertyName}
log.Info("Value to log 2");
You can use %property{PropertyName} in the conversionPattern of your PatternLayout, and you will get a new value logged each time you change the property value.
If you use a property for appender configuration properties, such as a filename or directory for a FileAppender, you will of course need to reconfigure after changing the property value.
Log4Net supports various contexts. GlobalContext, as you've found, is one of them. ThreadContext is another, and I think is more appropriate in your scenario:
log4net.ThreadContext["PropertyName"] = "NewValue";
There's no need to call Configure. Properties set in the ThreadContext are available for any calls to the logger from the current thread. Reference the property within the appender config in the normal way:
%property{PropertyName}
Based on #joe comment I've wrote my own appender as follow:
public class MyCustomAppender : RollingFileAppender
{
bool firstRun = true;
string fileNamePattern = null;
protected override void Append(LoggingEvent loggingEvent)
{
CloseFile();
File = fileNamePattern.Replace("__filename__", ThreadContext.Properties["PropertyName"].ToString());
LockingModel.OpenFile(File, true, Encoding.UTF8);
LockingModel.AcquireLock();
OpenFile(File, true);
base.Append(loggingEvent);
DoAppend(loggingEvent);
}
public override string File
{
get
{
if (firstRun)
{
firstRun = false;
fileNamePattern = base.File;
}
return base.File;
}
set
{
base.File = value;
}
}
}
And it works, which doesn't mean is correct. I don't know if it's a good thing inside the overrode Append method close, acquire lock and open file everytime I log something.
Any thoughts?
Related
I have properly overwrite commit in InstallerSetup.cs I do not wish to write the user entered value to app.config but rather I want to pass the string Context.Parameters["TESTPARAMETER"]; to another class in form1.cs on load function. I tried string test = InstallerSetup.Context.Parameters["TESTPARAMETER"];
but getting InstallerSetup.Context is null. Please Help.
InstallerSetup.cs
public static string SQLSERVERNAME = "";
public static string HMSTENANTDB;
public static string SQLLOGIN;
public static string SQLPASSWORD;
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
try
{
SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
HMSTENANTDB = Context.Parameters["HMSTENANTDB"];
SQLLOGIN = Context.Parameters["SQLLOGIN"];
SQLPASSWORD = Context.Parameters["SQLPASSWORD"];
}
catch (Exception e)
{
MessageBox.Show("Failed to update the application configuration file : " + e.Message);
base.Rollback(savedState);
}
}
from1.cs
InstallerSetup InsSetup = new InstallerSetup();
string Vsqlserver = InsSetup.Installers.Count.ToString();
string Vtenant = "";
if (InsSetup.Context != null)
{
Vtenant = InsSetup.Context.Parameters["HMSTENANTDB"];
}
else
{
Vtenant = "context is null";
}
As far as I can tell, the issue is that the property values are not being passed into the custom action. That would be the most obvious explanation. A commentfrom the poster says:
"passed those parameters to the custom action...................................... SQLSERVERNAME = Context.Parameters["SQLSERVERNAME"];
etc...
//................there is only these 4 lines in my custom actions"
which is essentially repeating the code that was previously posted.
This is NOT passing the values into the custom action. This is retrieving values which must already have been passed into the custom action.
Assuming that the custom action has been correctly added to (typically) the install nod of the custom action, and also assuming that the property names are in a TextBoxes dialog in the install, the values must be passed in to the custom action via the CustomActionData settings. To use one example, the CustomActionData setting must be something like:
/SQLSERVERNAME=[SQLSERVERNAME]
or /SQLSERVERNAME=[EDITA1] if EDIOTA1 is being used because that's the default property name.
However there is no reference to the TextBoxes (or any other) install dialog in the original question, so it's not really clear where the value of (say) SQLSERVERNAME is supposed to come from. It may be passed in on the msiexec command line, for example.
Basically the problem is that each time the assembly version changes (i.e. the user installs a new version of the application) all their settings are reset the the defaults (or more accurately a new user.config file is created in a folder with a different version number as the name)
How can I keep the same settings when upgrading versions, since using ini files or the registry seem to be discouraged?
When we used Clickonce it seemed to be able to handle this, so it seems like it should be able to be done, but I'm not sure how.
ApplicationSettingsBase has a method called Upgrade which migrates all settings from the previous version.
In order to run the merge whenever you publish a new version of your application you can define a boolean flag in your settings file that defaults to true. Name it UpgradeRequired or something similar.
Then, at application start you check to see if the flag is set and if it is, call the Upgrade method, set the flag to false and save your configuration.
if (Settings.Default.UpgradeRequired)
{
Settings.Default.Upgrade();
Settings.Default.UpgradeRequired = false;
Settings.Default.Save();
}
Read more about the Upgrade method at MSDN. The GetPreviousVersion might also be worth a look if you need to do some custom merging.
The next short solution works for me when we need to upgrade only once per version. It does not required additional settings like UpgradeRequired:
if (!ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).HasFile)
Settings.Default.Upgrade();
I know it's been awhile...
In a winforms app, just call My.Settings.Upgrade() before you load them. This will get the latest settings, whether the current version or a previous version.
Here's my research in case anyone else is having a hard time with migrating settings that have been changed/removed. Basic problem is that GetPreviousVersion() does not work if you have renamed or removed the setting in the new version of your application. So you need to keep the setting in your Settings class, but add a few attributes/artifacts to it so that you don't inadvertently use it in the code elsewhere, making it obsolete. A sample obsolete setting would look like this in VB.NET (can easily be translated to C#):
<UserScopedSetting(),
DebuggerNonUserCode(),
DefaultSettingValue(""),
Obsolete("Do not use this property for any purpose. Use YOUR_NEW_SETTING_NAME instead."),
NoSettingsVersionUpgrade()>
Public Property OldSettingName() As String
Get
Throw New NotSupportedException("This property is obsolete")
End Get
Set
Throw New NotSupportedException("This property is obsolete")
End Set
End Property
Make sure you add this property to the same namespace/class that has your application settings. In VB.NET, this class is named MySettings and is available in My namespace. You can use partial class functionality to prevent your obsolete settings from mixing up with your current settings.
Full credit to jsharrison for posting an excellent article about this issue. You can read more details about it there.
Here's a variation on the solutions presented here that encapsulates the upgrade logic into an abstract class that settings classes can derive from.
Some proposed solutions use a DefaultSettingsValue attribute to specify a value that indicates when previous settings were not loaded. My preference is to simply use a type whose default value indicates this. As a bonus, a DateTime? is helpful debugging information.
public abstract class UserSettingsBase : ApplicationSettingsBase
{
public UserSettingsBase() : base()
{
// Accessing a property attempts to load the settings for this assembly version
// If LastSaved has no value (default) an upgrade might be needed
if (LastSaved == null)
{
Upgrade();
}
}
[UserScopedSetting]
public DateTime? LastSaved
{
get { return (DateTime?)this[nameof(LastSaved)]; }
private set { this[nameof(LastSaved)] = value; }
}
public override void Save()
{
LastSaved = DateTime.Now;
base.Save();
}
}
Derive from UserSettingsBase:
public class MySettings : UserSettingsBase
{
[UserScopedSetting]
public string SomeSetting
{
get { return (string)this[nameof(SomeSetting)]; }
set { this[nameof(SomeSetting)] = value; }
}
public MySettings() : base() { }
}
And use it:
// Existing settings are loaded and upgraded if needed
MySettings settings = new MySettings();
...
settings.SomeSetting = "SomeValue";
...
settings.Save();
If your changes to user.settings are done programmatically, how about maintaining a copy of (just) the modifications to user.settings in a separate file, e.g. user.customized.settings?
You probably still want to maintain and load the modified settings in user.settings as well. But this way when you install a newer version of your application with its newer version of user.settings you can ask the user if they want to continue to use their modified settings by copying them back into the new user.settings. You could import them wholesale, or get fancier and ask the user to confirm which settings they want to continue to use.
EDIT: I read too quickly over the "more accurately" part about assembly versions causing a new user.settings to be installed into a new version-specific directory. Thus, the idea above probably doesn't help you, but may provide some food for thought.
This is how I handled it:
public virtual void LoadSettings(ServiceFileFormBaseSettings settings = null, bool resetSettingsToDefaults = false)
{
if (settings == null)
return;
if (resetSettingsToDefaults)
settings.Reset();
else
{
settings.Reload();
if (settings.IsDefault)
settings.Upgrade();
}
this.Size = settings.FormSize;
}
and in the settings class, I defined the IsDefault property:
// SaveSettings always sets this to be FALSE.
// This will have the default value TRUE when first deployed, or immediately after an upgrade.
// When the settings exist, this is false.
//
[UserScopedSettingAttribute()]
[DefaultSettingValueAttribute("true")]
public virtual bool IsDefault
{
get { return (bool)this["IsDefault"]; }
set { this["IsDefault"] = value; }
}
In the SaveSettings, I set IsDefault to false:
public virtual void SaveSettings(ServiceFileFormBaseSettings settings = null)
{
if (settings == null) // ignore calls from this base form, if any
return;
settings.IsDefault = false;
settings.FormSize = this.Size;
settings.Save();
}
I am working on writing a unit test for the following function..
public virtual FacadeClass InitNewAMSObject()
{
FacadeClass output = null;
if (ClassMapInfo != null)
{
// Override the metadata caching option
// sets property in external dll
Avectra.netForum.Common.Config.CacheMetaData = false;
if (!String.IsNullOrEmpty(ClassMapInfo.AMSClassName))
{
output = DataUtils.InstantiateFacadeObject(ClassMapInfo.AMSClassName);
}
}
else
{
throw new System.ApplicationException("Need to add the attribute");
}
return output;
}
I cannot get past the line with the comment "sets property in external dll". My shim never seems to 'make it over' to the function being tested. It always throws an error, and its actually trying to use the dll instead of my shim. It may look like i'm setting a property to false only, but the dll uses the property like a method in it's Setter. The .Config is always in an errored state because its constructors (in the dll) are attempting to set up database connections. I expect it to be in at least a null state, and also have the .Fakes. in the class name. I just want to skip over it because I do not write this dll, its 3rd party.
I've researched for a day now and cannot find an example anywhere of how to shim a property that is set on a referenced dll.
Here is my current test for what its worth
[TestMethod]
public void InitNewAMSObjectTEST()
{
using (ShimsContext.Create())
{
Address amsc = new Address();
bool test = true;
ShimConfig.CacheMetaDataGet = () => test;
ShimConfig.CacheMetaDataSetBoolean = value => test = value;
amsc.InitNewAMSObject();
}
}
I am writing some unit test cases using fakes framework. I am using an object ShimFileCreationInformation from Microsoft.SharePoint.Client.Fakes namespace. Now, I pass this object to a function. Inside the function, I am trying to assign a value to the Url property.
fileCreationInformation.Url = value;
But even though the value is present, nothing gets assigned to Url properly and it remains null. Is there any workaround for this problem? To make things worse, there is not documentation available on ShimFileCreationInformation object.
Code sample:
ShimFileCreationInformation fileCreationInformation = new ShimFileCreationInformation();
SomeFunction(fileCreationInformation);
SomeFunction :
public void SomeFunction(FileCreationInformation fileCreationInformation)
{
fileCreationInformation.Url = value; // This statement had so effect on fileCreationInformation.Url
}
fileCreationInformation.Url = value;
Setting the value directly as above will not work since you are setting the value of the Shim and not the actual object. You need to use ShimFileCreationInformation.AllInstances.UrlGet so thay whenever the Url Get is called it will return the value you specify.
Your code should look something like below:
[TestMethod]
public void derived_test()
{
using (ShimsContext.Create())
{
ShimFileCreationInformation fileCreationInformation = new ShimFileCreationInformation();
ShimFileCreationInformation.AllInstances.UrlGet = (instance) => value;
SomeFunction(fileCreationInformation);
}
}
public void SomeFunction(FileCreationInformation fileCreationInformation)
{
var url = fileCreationInformation.Url;
// Check url variable above. It should be set to value
fileCreationInformation.Url = value; // This statement will not work since you are trying to set the value of the Shim and you need to use `ShimFileCreationInformation.AllInstances.UrlGet` to set property value for Shims
}
I'm creating a custom workflow activity in VS2010 targeting .NET 3.5. The DLL is actually being used in a Microsoft System Center Service Manager custom workflow, but I don't think that is my issue.
I have a public string property, that the user types in the string of what the activity should use. However, when the WF runs, it errors out 'value cannot be null'. I want to target if it is my code or something else.
When we drag my custom activity onto the designer, I'm able to type in the text of the string on the designer for that property.
public static DependencyProperty ChangeRequestStageProperty = DependencyProperty.Register("ChangeRequestStage", typeof(String), typeof(UpdateChangeRequestStage));
[DescriptionAttribute("The value to set the ChangeRequestStage Property in the ChangeRequest Extension class.")]
[CategoryAttribute("Change Request Extension")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public String Stage
{
get { return ((String)(base.GetValue(UpdateChangeRequestStage.ChangeRequestStageProperty))); }
set { base.SetValue(UpdateChangeRequestStage.ChangeRequestStageProperty, value); }
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
EnterpriseManagementGroup emg = CreateEMG();
//System.WorkItem.ChangeRequest Extension - ClassExtension_928bec0a_cac4_4a0a_bd89_7146c9052fbe
ManagementPackClass mpcChangeRequest = emg.EntityTypes.GetClass(new Guid("8c6c6057-56ad-3862-47ec-dc0dde80a071"));
//System.WorkItemContainsActivity Relationship Class
ManagementPackRelationship workItemContainsActivityRelationship = emg.EntityTypes.GetRelationshipClass(new Guid("2DA498BE-0485-B2B2-D520-6EBD1698E61B"));
EnterpriseManagementObject changeRequest = null;
//Loop thru each emo (Change Request in this case), and assign it. There will never be more than 1 emo returned
foreach (EnterpriseManagementObject obj in emg.EntityObjects.GetRelatedObjects<EnterpriseManagementObject>(executionContext.ContextGuid, workItemContainsActivityRelationship, TraversalDepth.OneLevel, ObjectQueryOptions.Default))
{ changeRequest = obj; }
EnterpriseManagementObjectProjection emop = new EnterpriseManagementObjectProjection(changeRequest);
if (emop != null)
{ emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage; }
emop.Commit();
return base.Execute(executionContext);
}
Since it is getting a 'value cannot be null' error, I'm guessing it's on this line:
emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage;
I'm going to test and see if hardcoding a value works or not. Any ideas?
enter code here
try this
if (emop != null && emop.Object[mpcChangeRequest, "ChangeRequestStage"] != null)
emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage
I didn't want to leave this question wide open, so I'm updating it as to how I resolved this (a long time ago).
Rather than working with an EnterpriseManagementObjectProjection (emop), I worked with a standard EnterpriseManagementObject (emo). From there, I was able to follow a similar format from above:
ManagementPackClass mpcChangeRequest = emg.EntityTypes.GetClass(new Guid("8c246fc5-4e5e-0605-dc23-91f7a362615b"));
changeRequest[mpcChangeRequest, "ChangeRequestStage"].Value = this.Stage;
changeRequest.Commit();