I have an application that uses unity FW to resolve objects throughout.
I have done some change to the framework and the classes which can be seen in the code comment as "NEW CHANGE"
The wrapper class looks like
public static class ContractResolver
{
public static T Resolve<T>() //This is been used in many places in application
{
IUnityContainer container = new UnityContainer();
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
return container.Resolve<T>();
}
//NEW CHANGE: This is the new function that suppose to return the instance of parameterised constructor
public static T Resolve<T>(ParameterOverride[] parameterOverrides)
{
IUnityContainer container = new UnityContainer();
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
return container.Resolve<T>(parameterOverrides);
}
}
The configuration looks like
<unity>
<containers>
<container>
<types>
<type type ="UnityTest.IImageRepositoryService, UnityTest" mapTo="UnityTest.ImageRepositoryService, UnityTest"/>
</types>
</container>
</containers>
</unity>
The classes and interface looks like
public interface IImageRepositoryService
{
bool Exists(string imageName);
}
public class ImageRepositoryService : IImageRepositoryService
{
private readonly string mFilterName = "StandardImageFilter";
//[InjectionConstructor]
public ImageRepositoryService()
{
DatabaseQueryProvider.Query("Image", mFilterName);
}
//NEW CHANGE. A CONSTRUCTOR THAT ACCEPTS A PARAMETER
//[InjectionConstructor]
public ImageRepositoryService(string filterName)
{
mFilterName = filterName;
DatabaseQueryProvider.Query("Image", filterName);
}
public bool Exists(string imageName)
{
Console.WriteLine("The image " + imageName + " found in filter " + mFilterName);
return true;
}
}
The usage looks like
var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
serviceDefault.Exists("myimage.bmp");
The new changes breaks the old usage. i.e.
var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
Throws exception
Resolution of the dependency failed, type = "UnityTest.IImageRepositoryService", name = "(none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The type String cannot be constructed. You must configure the container to supply this value.
I would like to have the new functionality at the same time do not want to break the old functionality.
var serviceDefault = ContractResolver.Resolve<IImageRepositoryService>();
serviceDefault.Exists("myimage.bmp");
Should display the message in the console "The image myimage.bmp found in filter StandardImageFilter"
var parameterOverride1 = new ParameterOverride("filterName", "filter1");
var servicefilter1 = ContractResolver.Resolve<IImageRepositoryService>(new[] { parameterOverride1 });
servicefilter1.Exists("myimage.bmp");
Should display the message in the console "The image myimage.bmp found in filter filter1"
var parameterOverride2 = new ParameterOverride("filterName", "filter2");
var servicefilter2 = ContractResolver.Resolve<IImageRepositoryService>(new[] { parameterOverride2 });
servicefilter2.Exists("myimage.bmp");
Should display the message in the console "The image myimage.bmp found in filter filter2"
How solve this problem?
If you want to resolve the same type (in this case IImageRepositoryService) but have different calls to Resolve invoke different constructors then you will need to use named registrations.
In your case you can do this in the XML configuration:
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type ="UnityTest.IImageRepositoryService, UnityTest" mapTo="UnityTest.ImageRepositoryService, UnityTest">
<constructor />
</register>
<register name="ParameterizedRepository"
type="UnityTest.IImageRepositoryService, UnityTest"
mapTo="UnityTest.ImageRepositoryService, UnityTest">
<constructor>
<param name="filterName" value="dummyValue" />
</constructor>
</register>
</container>
</unity>
Note, that I've used the Unity 2 (and 3) configuration style.
So this tells Unity that when resolving using the name "ParameterizedRepository" to invoke the constructor with the parameter named "filterName". I've used a dummy value here because we are going to override the value at runtime anyway:
var imageRepositoryService = container.Resolve<IImageRepositoryService>(
"ParameterizedRepository",
new ParameterOverride("filterName", "filter2"));
So that's how to get what you want using Unity so in terms of your wrapper class you should add a name parameter:
public static class ContractResolver
{
//NEW CHANGE: This is the new function that suppose to return the instance of parameterised constructor
public static T Resolve<T>(string name, params ParameterOverride[] parameterOverrides)
{
IUnityContainer container = new UnityContainer();
var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
return container.Resolve<T>(name, parameterOverrides);
}
}
A few unsolicited comments (in the spirit of trying to be helpful):
It looks like you are using Unity version 1. If so, you might want to consider upgrading (version 3 was released recently) and if you are not using Unity version 1, then you might want to consider changing the XML configuration syntax to use the newer approach as well as using the LoadConfiguration() extension method.
I'm not sure why every call to ContractResolver.Resolve() creates a new Unity container and then loads the configuration. This could be a bit of a performance hit. Usually, you would create a container and load the configuration once and use that instance for the lifetime of the application.
I can understand how you would be tempted to hide the container implementation behind the ContractResolver but with the addition of ParameterOverride (which are Unity specific) the abstraction is becoming a bit leaky.
Related
Demorepo:
https://github.com/gabbersepp/csharp-dynamic-replace-class
How to use:
Checkout
Compile
Delete TestLib.dll & TestLib.pdb from console/bin/Debug
Execute console.exe through cmd
Read first:
I have something in mind I want to achieve at work and I think that this would be the best solution. So please do not discuss if I can solve this with another way. If I would like to discuss about this, I will create a new SO post.
Given:
A class in a lib:
namespace Test.TestLib
{
public class Class1
{
}
}
And a second class which creates an instance of it:
namespace console
{
public class AnotherClass
{
public void Create()
{
new Class1();
}
}
}
And a console app that calls create:
static void Main(string[] args)
{
//...
new AnotherClass().Create();
}
Please keep in mind that only Class1 is in an extra lib. The other two classes are in the same.
What I want to do:
Replace the Type Class1 at runtime:
AssemblyName dynamicAssemblyName = new AssemblyName("TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
dynamicAssembly =
AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
var dynamicModule = dynamicAssembly.DefineDynamicModule("TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
var modelType = dynamicModule.DefineType("Test.TestLib.Class1", TypeAttributes.Class).CreateType();
This works. If i am using the Activator: Activator.CreateInstance(modelType); I get a new instance of this type.
But:
When the line with new Class1 is reached, an exception is thrown:
Unbehandelte Ausnahme: System.MethodAccessException: Fehler beim
Versuch der Methode "console.AnotherClass.Create()", auf Methode
"Test.TestLib.Class1..ctor()" zuzugreifen. bei
console.AnotherClass.Create()
Something like:
Unhandled exception: System.MethodAccessException: An error occurred
while trying to access the method "console.AnotherClass.Create ()"
method "Test.TestLib.Class1..ctor ()".
at console.AnotherClass.Create ()
Question:
Is this possible?
Sidenote:
The deletion of the TestLib files from the /debug folder is required because otherwise the AsemblyResolve Event (please see the repo for a full example) is not raised
The constructor of your newly created Type is not public, hence the MethodAccessAcception.
You can create a public default constructor using the following call on the TypeBuilder instance:
var modelTypeBuilder = dynamicModule.DefineType("Test.TestLib.Class1", TypeAttributes.Class);
modelTypeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
var modelType = modelTypeBuilder.CreateType();
Otherwise you could use modelTypeBuilder.DefineConstructor() to set more possible options.
add TypeAttributes.Public to DefineType, it will execute successfully
Check this: https://github.com/aIjundi/csharp-dynamic-replace-class
I would like to create a dictionary and populate it in the configuration file for Unity like shown here. However, that example seems to be from an older version of Unity IoC but I would like to know how to do this with Unity 4.
<type ... to="MyTypeServer">
<typeConfig>
<property Name="Converters" KeyType"string" ValueType="IConverter">
<entry>
<key>csv</key>
<value><dependency name="csvConverter"/></value>
</entry>
<entry>
<key>xml</key>
<value><dependency name="xmlConverter"/></value>
</entry>
</property>
</typeConfig>
</type>
<type name="csvConverter" from="IConverter" to="MyCsvConverter">
</type>
<type name="xmlConverter" from="IConverter" to="MyXmlConverter">
</type>
And here is the class:
public class MyTypeServer
{
public IDictionary<string, IConverter> Converters
{
set;
private get;
}
public void DoConversion(string fileName)
{
string fileType = Path.GetFileExtension(fileName);
IConverter converter = Converters[fileType];
if (converter != null)
converter.DoConversion(fileName);
..
...
}
}
I have been trying for hours and researching but no luck.
From the codeplex link you posted:
This is a quick pseudo code of the type of stuff we can have with dictionary.
To me, this reads "we could do something like this if we implemented the feature". Aligns with my experience with unity, I've never come across something like this.
What you can do however: register all the converters, have them all injected as array and then build the dictionary yourself.
// register in code or in xml...
container.RegisterType<IConverter, XmlConverter>( "xml-Converter" );
container.RegisterType<IConverter, JsonConverter>( "json-Converter" );
internal class ConverterConsumer
{
public ConverterConsumer( IConverter[] converters )
{
_converters = converters.ToDictionary( x => x.FileType, x => x );
}
#region private
private Dictionary<string, IConverter> _converters;
#endregion
}
public interface IConverter
{
string FileType { get; }
void DoConversion( string fileName );
}
Posting as an answer because I do not have sufficient points to comment. This is what I did to solve my problem. Not exactly a Dictionary approach but it might help.
My requirements - Store app settings in the Unity XML file as opposed to app.config
I had various objects which were registered in the Unity XML and they had properties like connection strings, Azure queue names , Azure blob container names, etc. I found myself duplicating these values in the XML very often. I could make my objects read values from *appSettings** element of app.config or some other configuration section. However, I chose not to use the app.config for the sake of keeping my objects more testable.
My solution - Use the <instance> element to register reusable string values
I registered all reusable connection strings in a single location as shown below and without any duplications:
<instance name="cnstring1" value="blah connection string 1"></instance>
<instance name="cnstring2" value="blah connection string 2"></instance>
<instance name="azurequeue1" value="name of receiver queue "></instance>
<instance name="azurequeue2" value="name of sender queue "></instance>
<instance name="azurestoragecnstring" value="your azure storage account connection string 0001"></instance>
Reference the name-value pairs using the dependency element whereever required.
Example XML:
<register name="i2" mapTo="someimplementation" type="someinterface">
<property name="Database">
<dependency name="cnstring1" />
</property>
</register>
Example C# code snippet:
public string Database { get; set; }
At run time the property Database of the object with the registration i2 will be set to the value of blah connection string 1
Caveats
Storing connection strings in plain text XML might not be safe especially if the connection string has username and password values.
I am currently injection the concrete ConfigFileSettings of the IConfigFileSettings into classes that require a name for the connection string. The simplified code looks as follows:
public interface IConfigFileSettings
{
string GetConnectionString(string name);
}
public class ConfigFileSettings : IConfigFileSettings
{
public string GetConnectionString(string name)
{
return ConfigurationManager.ConnectionStrings[name].Name;
}
}
This works fine for webapi's hosted in iis, windows services and console application. I guess:
ConfigurationManager.ConnectionStrings[name].Name
won't work in worker roles. Can I adapt the GetConnectionString method to make it work in all environments transparently? Also even if I get the connection sting name (e.g. from a .cscfg file) my code will look for:
<connectionStrings> ... </connectionStrings>
I guess I cannot just add an entry into the .cscfg file?
You can put them in the .cscfg file like:
<?xml version="1.0"?>
<ServiceConfiguration serviceName="Web.Azure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="3" osVersion="*" schemaVersion="2013-03.2.0">
<Role name="Worker">
<Instances count="2" />
<ConfigurationSettings>
<Setting name="connectionstringname" value="connectionstringvalue" />
</ConfigurationSettings>
</Role>
</ServiceConfiguration>
Then you can read them by:
var connectionstring = RoleEnvironment.GetConfigurationSettingValue("connectionstringname");
This is a small utility for strong typed config in azure implemented with Castle Windsor DictionaryAdapter. I use it in my projects. Usage :
Explore the code a bit to get the idea
Define your strongly typed config interfaces - Look at Configuration.Interfaces assembly
Define your factories / use azure config provider in composition root and use dictionary adapter to fill the config dictionaries -
Inject your config like in MyWorkerService.cs
using Configuration.Interfaces;
using Persistence.Interfaces;
using Worker.Services.Interfaces;
namespace Worker.Services
{
public class MyWorkerService : IMyWorkerService
{
private readonly IConnectionStrings _connectionStrings;
private readonly IAzureServiceConfiguration _azureServiceConfiguration;
private readonly IMicrosoftStorageConfig _microsoftStorageConfig;
private readonly IPersitenceServiceConfigDependent _persitenceServiceConfigDependent;
private readonly IAppConfigSettings _appConfigSettings;
public MyWorkerService(
IPersitenceServiceConfigDependent persitenceServiceConfigDependent,
IConnectionStrings connectionStrings,
IAzureServiceConfiguration azureServiceConfiguration,
IMicrosoftStorageConfig microsoftStorageConfig,
IAppConfigSettings appConfigSettings)
{
_connectionStrings = connectionStrings;
_azureServiceConfiguration = azureServiceConfiguration;
_microsoftStorageConfig = microsoftStorageConfig;
_persitenceServiceConfigDependent = persitenceServiceConfigDependent;
_appConfigSettings = appConfigSettings;
}
public string DoWork()
{
_persitenceServiceConfigDependent.ConfigDependentAction("blah");
var configSetting = _microsoftStorageConfig.StorageConnectionString;
return $"Job done :" +
$" <br> msConfig : {configSetting}, " +
$" <br> azureConfig.ServiceBusConnectionString:{_azureServiceConfiguration.ServiceBusConnectionString} " +
$" <br> webConfig.SubscriptionId:{_appConfigSettings.SubscriptionId} " +
$" <br> connectionStrings.DefaultConnection :{_connectionStrings.DefaultConnection}";
}
}
}
I'm building a library to interface with a third party. Communication is through XML and HTTP Posts. That's working.
But, whatever code uses the library does not need to be aware of the internal classes. My internal objects are serialized to XML using this method:
internal static string SerializeXML(Object obj)
{
XmlSerializer serializer = new XmlSerializer(obj.GetType(), "some.domain");
//settings
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
using (StringWriter stream = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(stream, settings))
{
serializer.Serialize(writer, obj);
}
return stream.ToString();
}
}
However, when I change my classes' access modifier to internal, I get an exception at runtime:
[System.InvalidOperationException] = {"MyNamespace.MyClass is inaccessible due to its protection level. Only public types can be processed."}
That exception happens in the first line of the code above.
I would like my library's classes not to be public because I do not want to expose them. Can I do that? How can I make internal types serializable, using my generic serializer? What am I doing wrong?
From Sowmy Srinivasan's Blog - Serializing internal types using XmlSerializer:
Being able to serialize internal types is one of the common requests
seen by the XmlSerializer team. It is a reasonable request from people
shipping libraries. They do not want to make the XmlSerializer types
public just for the sake of the serializer. I recently moved from the
team that wrote the XmlSerializer to a team that consumes
XmlSerializer. When I came across a similar request I said, "No way.
Use DataContractSerializer".
The reason is simple. XmlSerializer works by generating code. The
generated code lives in a dynamically generated assembly and needs to
access the types being serialized. Since XmlSerializer was developed
in a time before the advent of lightweight code generation, the
generated code cannot access anything other than public types in
another assembly. Hence the types being serialized has to be public.
I hear astute readers whisper "It does not have to be public if
'InternalsVisibleTo' attribute is used".
I say, "Right, but the name of the generated assembly is not known
upfront. To which assembly do you make the internals visible to?"
Astute readers : "the assembly name is known if one uses 'sgen.exe'"
Me: "For sgen to generate serializer for your types, they have to be
public"
Astute readers : "We could do a two pass compilation. One pass for
sgen with types as public and another pass for shipping with types as
internals."
They may be right! If I ask the astute readers to write me a sample
they would probably write something like this. (Disclaimer: This is
not the official solution. YMMV)
using System;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;
using System.Reflection;
[assembly: InternalsVisibleTo("Program.XmlSerializers")]
namespace InternalTypesInXmlSerializer
{
class Program
{
static void Main(string[] args)
{
Address address = new Address();
address.Street = "One Microsoft Way";
address.City = "Redmond";
address.Zip = 98053;
Order order = new Order();
order.BillTo = address;
order.ShipTo = address;
XmlSerializer xmlSerializer = GetSerializer(typeof(Order));
xmlSerializer.Serialize(Console.Out, order);
}
static XmlSerializer GetSerializer(Type type)
{
#if Pass1
return new XmlSerializer(type);
#else
Assembly serializersDll = Assembly.Load("Program.XmlSerializers");
Type xmlSerializerFactoryType = serializersDll.GetType("Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializerContract");
MethodInfo getSerializerMethod = xmlSerializerFactoryType.GetMethod("GetSerializer", BindingFlags.Public | BindingFlags.Instance);
return (XmlSerializer)getSerializerMethod.Invoke(Activator.CreateInstance(xmlSerializerFactoryType), new object[] { type });
#endif
}
}
#if Pass1
public class Address
#else
internal class Address
#endif
{
public string Street;
public string City;
public int Zip;
}
#if Pass1
public class Order
#else
internal class Order
#endif
{
public Address ShipTo;
public Address BillTo;
}
}
Some astute 'hacking' readers may go as far as giving me the build.cmd
to compile it.
csc /d:Pass1 program.cs
sgen program.exe
csc program.cs
As an alternative you can use dynamically created public classes (which won't be exposed to the 3rd party):
static void Main()
{
var emailType = CreateEmailType();
dynamic email = Activator.CreateInstance(emailType);
email.From = "x#xpto.com";
email.To = "y#acme.com";
email.Subject = "Dynamic Type";
email.Boby = "XmlSerializer can use this!";
}
static Type CreateEmailType()
{
var assemblyName = new AssemblyName("DynamicAssembly");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
var typeBuilder = moduleBuilder.DefineType(
"Email",
(
TypeAttributes.Public |
TypeAttributes.Sealed |
TypeAttributes.SequentialLayout |
TypeAttributes.Serializable
),
typeof(ValueType)
);
typeBuilder.DefineField("From", typeof(string), FieldAttributes.Public);
typeBuilder.DefineField("To", typeof(string), FieldAttributes.Public);
typeBuilder.DefineField("Subject", typeof(string), FieldAttributes.Public);
typeBuilder.DefineField("Body", typeof(string), FieldAttributes.Public);
return typeBuilder.CreateType();
}
The no headache solution is to use the NetBike.Xml NuGet package (.NET Standard 2.0) which is compatible with System.Xml.XmlSerializer XML attributes and support internal types out of the box.
Usage is straightforward, here's an excerpt from the README:
var serializer = new XmlSerializer();
var xml = "<Foo><Id>1</Id><Name>test</Name></Foo>";
var foo = serializer.Deserialize<Foo>(new StringReader(xml));
This may help you: MRB_ObjectSaver
This project helps you save/load/clone any object in c# to/from a file/string. In compare to "c# serialization" this method keeps reference to objects and link between objects will not break. (see: SerializeObjectTest.cs for an example) Furthermore, the type have not be marked as [Serializable]
You can also use something called xgenplus (http://xgenplus.codeplex.com/) this generates the code that would normally get executed at runtime. Then you add that to your solution, and compile it as part of your solution. At that point, it doesn't matter if your object is internal - you can add the pre-generated code in the same namespace. The performance for this is blazing fast, as its all pre-generated.
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.