I have a class LanguagePopupMessage which is used all over the application (and external libraries). If this class is constructed it fetches the namespace where it's created and adds a suffix to be unique.
The Question is: How can get all LanguagePopupMessage definitions including the fieldname parameter?
Im using structuremap in my application. It's also scanning all libraries at startup, so maybe there is a possiblity how to automaticate it. 👀
using System;
using System.Diagnostics;
namespace ConsoleApp1
{
/// <summary>
/// Creates the namespace for a popup window and has an additional flag for the caption
/// </summary>
public class LanguagePopupMessage
{
public string Namespace { get; }
public string Caption => $"{Namespace}Caption";
public LanguagePopupMessage(string fieldName)
{
if(string.IsNullOrEmpty(fieldName))
throw new ArgumentNullException(nameof(fieldName));
if (_GetNamespace() is Type type)
{
Namespace = $"{type}.{fieldName}";
}
else
{
throw new InvalidOperationException("could not fetch the namespace");
}
}
private Type _GetNamespace()
{
StackTrace st = new StackTrace();
foreach (var sf in st.GetFrames())
{
var type = sf.GetMethod().DeclaringType;
if (type != GetType())
{
return type;
}
}
return null;
}
public override string ToString()
{
return $"Namespace '{Namespace}' Caption '{Caption}'";
}
}
class Program
{
//ConsoleApp1.Program.PopupMessage.ConfigNotLoaded
//ConsoleApp1.Program.PopupMessage.ConfigNotLoadedCaption
private static readonly LanguagePopupMessage _CONFIG_NOT_LOADED_POPUP_MESSAGE = new LanguagePopupMessage("ConfigNotLoaded");
static void Main(string[] args)
{
Console.ReadKey();
}
}
}
namespace ConsoleApp1.Subfolder
{
public class SubfolderClass
{
/// <summary>
/// ConsoleApp1.Subfolder.SubfolderClass.FooMessage
/// ConsoleApp1.Subfolder.SubfolderClass.FooMessageCaption
/// </summary>
public static readonly LanguagePopupMessage Message = new LanguagePopupMessage("FooMessage");
}
}
I made a custom IRegistrationConvention - FindAllLanguagePopupMessages for structuremap. During runtime a new container scans all libraries -> all Types if there are any FieldInfo of type LanguagePopupMessage and adding it into a collection.
To get better performance, I made an Attribute - ContainsTranslationDefinition to filter the classes.
Sourcecode
public class ContainsTranslationDefinition : Attribute
{ }
/// <summary>
/// Creates the namespace for a popup window and has an additional flag for the caption
/// </summary>
public class LanguagePopupMessage
{
public string Namespace { get; }
public string Caption => $"{Namespace}Caption";
public LanguagePopupMessage(string fieldName)
{
if(string.IsNullOrEmpty(fieldName))
throw new ArgumentNullException(nameof(fieldName));
if (_GetNamespace() is Type type)
{
Namespace = $"{type}.{fieldName}";
}
else
{
throw new InvalidOperationException("could not fetch the namespace");
}
}
private Type _GetNamespace()
{
StackTrace st = new StackTrace();
foreach (var sf in st.GetFrames())
{
var type = sf.GetMethod().DeclaringType;
if (type != GetType())
{
return type;
}
}
return null;
}
public override string ToString()
{
return $"Namespace '{Namespace}' Caption '{Caption}'";
}
}
/// <summary>
/// Add <see cref="LanguagePopupMessage"/> into the <see cref="Container.Model"/> type lifecycle
/// </summary>
public class FindAllLanguagePopupMessages : IRegistrationConvention
{
private readonly ILifecycle _Lifecycle = new UniquePerRequestLifecycle();
public void ScanTypes(TypeSet types, Registry registry)
{
List<LanguagePopupMessage> dec = new List<LanguagePopupMessage>();
foreach (Type type in types.AllTypes())
{
if (!Attribute.IsDefined(type, typeof(ContainsTranslationDefinition)))
{
continue;
}
_FindConfigDeclarations(type, dec);
}
foreach (LanguagePopupMessage languagePopupMessage in dec)
{
Console.WriteLine($"{languagePopupMessage}");
}
}
private static void _FindConfigDeclarations(Type type, List<LanguagePopupMessage> declarations)
{
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
declarations.AddRange(fields
.Where(info => info.IsInitOnly && typeof(LanguagePopupMessage).IsAssignableFrom(info.FieldType))
.Select(info => (LanguagePopupMessage)info.GetValue(null)));
// find all nested class types and run method recursively
foreach (var nestedType in type.GetNestedTypes(BindingFlags.Public))
{
_FindConfigDeclarations(nestedType, declarations);
}
}
}
[ContainsTranslationDefinition]
public class TestClass
{
private static readonly LanguagePopupMessage _CONFIG_1 = new LanguagePopupMessage("ConfigNotLoaded1");
private static readonly LanguagePopupMessage _CONFIG_2 = new LanguagePopupMessage("ConfigNotLoaded2");
}
[ContainsTranslationDefinition]
public class Program
{
//ConsoleApp1.Program.PopupMessage.ConfigNotLoaded
//ConsoleApp1.Program.PopupMessage.ConfigNotLoadedCaption
private static readonly LanguagePopupMessage _CONFIG_NOT_LOADED_POPUP_MESSAGE = new LanguagePopupMessage("ConfigNotLoaded3");
static void Main(string[] args)
{
// Create container and tell where to look for depencies
IContainer container = new Container(c => c.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
scanner.AssembliesFromApplicationBaseDirectory();
scanner.With(new FindAllLanguagePopupMessages());
}));
Console.ReadKey();
}
}
Preview
Related
How do I separate my code into their own classes and still have it function the same? This is currently what my code looks like.
using System;
using System.Collections.Generic;
using System.Xml;
using XCENT.JobServer.JobPlugIn;
using System.IO;
using HPD.API.Utility.DataAccess;
namespace DataPurge
{
public class Purge : IJob, IJobControl {
public IJobControl JobControl { get { return ( this ); } }
public int MaxInstanceCount { get { return 1; } }
public string Name { get { return "DataPurge"; } }
public Purge() { }
public void Run( string XmlFragment ) {
XmlNode xmlNode = null;
try
{
xmlNode = Common.ConstructXmlNodeFromString(XmlFragment, "Params");
var list = DataList();
foreach (var item in list)
{
var factory = new PurgerFactory(item);
IPurger purge = factory.Purger;
purge.Purge();
purge = null;
factory = null;
}
}
catch (Exception ex)
{
throw;
}
}
public interface IPurger
{
void Purge();
}
public enum PurgeType
{
File,
Database,
}
public class FilePurger : IPurger
{
private Parameters parameter;
public FilePurger(Parameters parameter)
{
this.parameter = parameter;
}
public void Purge()
{
var files = new DirectoryInfo(parameter.FilePath).GetFiles();
foreach (var file in files)
{
if (DateTime.Now - file.CreationTime > TimeSpan.FromDays(7))
{
File.Delete(file.FullName);
}
}
}
}
public class DbPurger : IPurger
{
private Parameters parameter;
public DbPurger(Parameters parameter)
{
this.parameter = parameter;
}
public void Purge()
{
var access = new SqlDataAccess();
var sqlParams = new Dictionary<string, object>();
sqlParams.Add("#OlderThanDays", parameter.OlderThanDays);
access.ExecuteNonQuery(parameter.CString, parameter.SPName, sqlParams, 30, false);
}
}
private List<Parameters> DataList()
{
var sqlParams = new SqlDataAccess();
var list = sqlParams.GetDataTableAsList<Parameters>("Data Source = MYSERVER; Initial Catalog = MYDATABASE; User ID = UID; Password = PASSWORD;", "purge.spoDataTable", null);
return list;
}
public class PurgerFactory
{
public IPurger Purger { get; set; }
public PurgerFactory(Parameters parameter)
{
PurgeType type = (PurgeType)Enum.Parse(typeof(PurgeType), parameter.PurgeType);
switch (type)
{
case PurgeType.File:
Purger = new FilePurger(parameter);
break;
case PurgeType.Database:
Purger = new DbPurger(parameter);
break;
default:
throw new NotImplementedException();
}
}
}
/// <summary>
/// Used to submit a job via the job monitor
/// </summary>
public XmlNode JobXMLNode => Common.ConstructXmlNodeFromString("" +
"<JobParams>" +
" <Param Name=\"InfrastructureAPI\" DataType=\"String\">" +
" <Description>Infrastructure API URL.</Description>" +
" </Param>" +
" <Param Name=\"EnvironmentName\" DataType=\"String\">" +
" <Description>The current environment.</Description>" +
" </Param>" +
"</JobParams>",
"JobParams");
}
}
Currently all parts of the program are stuffed into this one single class. I want to separate them out into their own separate classes to make the code much cleaner but still have it function the same. I'm still a beginner coder and don't know the first place to start. Any help would be much appreciated!
You should create a file IPurger.cs for the interface IPurger, then a file FilePurger.cs for the class FilePurger, the file DbPurger.cs for the class DbPurger and lastly PurgerFactory.cs for the class PurgerFactory.
That should clean up your code quite well.
If that enum is used from multiple places, you may want to place it in its own class too, perhaps a generic Enums.cs.
I have an application splittet into two projects: a web application and a class library. The Startup is only in the web application:
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
I wanna have my appsettings.json in that class library and being loaded from there. Is that possible? How can I do that?
The best solution I have found requires creating a new IFileProvider and IFileInfo, and then embedding the JSON settings files in your assembly.
The solution reuses the existing AddJsonFile logic. This way you only need to tell the configuration system how and where to locate the JSON file, not how to parse it.
The solution is compatible with .NET Core 1.0.
Usage:
public class Startup
{
private readonly AppSettings _appSettings;
public Startup(IHostingEnvironment env)
{
Assembly assembly = GetType().GetTypeInfo().Assembly;
new ConfigurationBuilder()
.AddEmbeddedJsonFile(assembly, "appsettings.json")
.AddEmbeddedJsonFile(assembly, $"appsettings.{env.EnvironmentName.ToLower()}.json")
.Build();
}
...
}
Embed the files by updating the project.json for the class library:
...
"buildOptions": {
"embed": [
"appsettings.json",
"appsettings.development.json",
"appsettings.production.json"
]
}
...
IEmbeddedFileInfo implementation:
public class EmbeddedFileInfo : IFileInfo
{
private readonly Stream _fileStream;
public EmbeddedFileInfo(string name, Stream fileStream)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (fileStream == null) throw new ArgumentNullException(nameof(fileStream));
_fileStream = fileStream;
Exists = true;
IsDirectory = false;
Length = fileStream.Length;
Name = name;
PhysicalPath = name;
LastModified = DateTimeOffset.Now;
}
public Stream CreateReadStream()
{
return _fileStream;
}
public bool Exists { get; }
public bool IsDirectory { get; }
public long Length { get; }
public string Name { get; }
public string PhysicalPath { get; }
public DateTimeOffset LastModified { get; }
}
IFileInfo implementation:
public class EmbeddedFileProvider : IFileProvider
{
private readonly Assembly _assembly;
public EmbeddedFileProvider(Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
_assembly = assembly;
}
public IFileInfo GetFileInfo(string subpath)
{
string fullFileName = $"{_assembly.GetName().Name}.{subpath}";
bool isFileEmbedded = _assembly.GetManifestResourceNames().Contains(fullFileName);
return isFileEmbedded
? new EmbeddedFileInfo(subpath, _assembly.GetManifestResourceStream(fullFileName))
: (IFileInfo) new NotFoundFileInfo(subpath);
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
}
public IChangeToken Watch(string filter)
{
throw new NotImplementedException();
}
}
Create the easy to use AddEmbeddedJsonFile extension method.
public static class ConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddEmbeddedJsonFile(this IConfigurationBuilder cb,
Assembly assembly, string name, bool optional = false)
{
// reload on change is not supported, always pass in false
return cb.AddJsonFile(new EmbeddedFileProvider(assembly), name, optional, false);
}
}
Yes you could implement IConfigurationProvider
There is a base class ConfigurationProvider that you can inherit from then override all the virtual methods.
You can also see how the JsonConfigurationProvider is implemented.
So I guess your implementation could use the Json provider code internally against embedded json files.
Then you would also want to implement ConfigurationBuilder extension to register your provider similar as the code for using json config.
Someone else can correct me, but I don't think what you are looking for exists.
App Configs and AppSettings files are read at runtime by the application that is running.
The Class Library cannot see any AppSettings specific to itself, because when it runs at run time, it is in the folder of the running application.
The only potential way I can see for you to get your class library contain the json file, is to have the json file as an embedded resource.
Eg: In the solution, select the json file, and set it to Embedded Resource instead of 'content'.
The problem becomes getting the embedded config file out of your assembly, and then loaded.
AddJsonFile accepts a path to the json file.
You could however extract the Json file to a temp directory, then load from there.
static byte[] StreamToBytes(Stream input)
{
int capacity = input.CanSeek ? (int)input.Length : 0;
using (MemoryStream output = new MemoryStream(capacity))
{
int readLength;
byte[] buffer = new byte[capacity/*4096*/]; //An array of bytes
do
{
readLength = input.Read(buffer, 0, buffer.Length); //Read the memory data, into the buffer
output.Write(buffer, 0, readLength);
}
while (readLength != 0); //Do all this while the readLength is not 0
return output.ToArray(); //When finished, return the finished MemoryStream object as an array.
}
}
Assembly yourAssembly = Assembly.GetAssembly(typeof(MyTypeWithinAssembly));
using (Stream input = yourAssembly.GetManifestResourceStream("NameSpace.Resources.Config.json")) // Acquire the dll from local memory/resources.
{
byte[] byteData = StreamToBytes(input);
System.IO.File.WriteAllBytes(Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)+"//Config.json",new byte[]{});
}
var builder = new ConfigurationBuilder()
.AddJsonFile(Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)+"//Config.json");
You should in theory be able to specify a type from the class library, in order to help the c# code target that class library specifically. Then you just need to provide the namespace and path to the embedded json file.
Here is my solution, thanks Baaleos and Joe for your advices.
project.json
"resource": [
"appsettings.json"
]
startup.cs
var builder = new ConfigurationBuilder()
.Add(new SettingsConfigurationProvider("appsettings.json"))
.AddEnvironmentVariables();
this.Configuration = builder.Build();
namespace ClassLibrary
{
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
/// <summary>
/// A JSON file based <see cref="ConfigurationProvider"/> for embedded resources.
/// </summary>
public class SettingsConfigurationProvider : ConfigurationProvider
{
/// <summary>
/// Initializes a new instance of <see cref="SettingsConfigurationProvider"/>.
/// </summary>
/// <param name="name">Name of the JSON configuration file.</param>
/// <param name="optional">Determines if the configuration is optional.</param>
public SettingsConfigurationProvider(string name)
: this(name, false)
{
}
/// <summary>
/// Initializes a new instance of <see cref="SettingsConfigurationProvider"/>.
/// </summary>
/// <param name="name">Name of the JSON configuration file.</param>
/// <param name="optional">Determines if the configuration is optional.</param>
public SettingsConfigurationProvider(string name, bool optional)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name must be a non-empty string.", nameof(name));
}
this.Optional = optional;
this.Name = name;
}
/// <summary>
/// Gets a value that determines if this instance of <see cref="SettingsConfigurationProvider"/> is optional.
/// </summary>
public bool Optional { get; }
/// <summary>
/// The name of the file backing this instance of <see cref="SettingsConfigurationProvider"/>.
/// </summary>
public string Name { get; }
/// <summary>
/// Loads the contents of the embedded resource with name <see cref="Path"/>.
/// </summary>
/// <exception cref="FileNotFoundException">If <see cref="Optional"/> is <c>false</c> and a
/// resource does not exist with name <see cref="Path"/>.</exception>
public override void Load()
{
Assembly assembly = Assembly.GetAssembly(typeof(SettingsConfigurationProvider));
var resourceName = $"{assembly.GetName().Name}.{this.Name}";
var resources = assembly.GetManifestResourceNames();
if (!resources.Contains(resourceName))
{
if (Optional)
{
Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
else
{
throw new FileNotFoundException($"The configuration file with name '{this.Name}' was not found and is not optional.");
}
}
else
{
using (Stream settingsStream = assembly.GetManifestResourceStream(resourceName))
{
Load(settingsStream);
}
}
}
internal void Load(Stream stream)
{
JsonConfigurationFileParser parser = new JsonConfigurationFileParser();
try
{
Data = parser.Parse(stream);
}
catch (JsonReaderException e)
{
string errorLine = string.Empty;
if (stream.CanSeek)
{
stream.Seek(0, SeekOrigin.Begin);
IEnumerable<string> fileContent;
using (var streamReader = new StreamReader(stream))
{
fileContent = ReadLines(streamReader);
errorLine = RetrieveErrorContext(e, fileContent);
}
}
throw new FormatException($"Could not parse the JSON file. Error on line number '{e.LineNumber}': '{e}'.");
}
}
private static string RetrieveErrorContext(JsonReaderException e, IEnumerable<string> fileContent)
{
string errorLine;
if (e.LineNumber >= 2)
{
var errorContext = fileContent.Skip(e.LineNumber - 2).Take(2).ToList();
errorLine = errorContext[0].Trim() + Environment.NewLine + errorContext[1].Trim();
}
else
{
var possibleLineContent = fileContent.Skip(e.LineNumber - 1).FirstOrDefault();
errorLine = possibleLineContent ?? string.Empty;
}
return errorLine;
}
private static IEnumerable<string> ReadLines(StreamReader streamReader)
{
string line;
do
{
line = streamReader.ReadLine();
yield return line;
} while (line != null);
}
}
}
You need also the JsonConfigurationFileParser:
namespace ClassLibrary
{
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
internal class JsonConfigurationFileParser
{
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Stack<string> _context = new Stack<string>();
private string _currentPath;
private JsonTextReader _reader;
public IDictionary<string, string> Parse(Stream input)
{
_data.Clear();
_reader = new JsonTextReader(new StreamReader(input));
_reader.DateParseHandling = DateParseHandling.None;
var jsonConfig = JObject.Load(_reader);
VisitJObject(jsonConfig);
return _data;
}
private void VisitJObject(JObject jObject)
{
foreach (var property in jObject.Properties())
{
EnterContext(property.Name);
VisitProperty(property);
ExitContext();
}
}
private void VisitProperty(JProperty property)
{
VisitToken(property.Value);
}
private void VisitToken(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
VisitJObject(token.Value<JObject>());
break;
case JTokenType.Array:
VisitArray(token.Value<JArray>());
break;
case JTokenType.Integer:
case JTokenType.Float:
case JTokenType.String:
case JTokenType.Boolean:
case JTokenType.Bytes:
case JTokenType.Raw:
case JTokenType.Null:
VisitPrimitive(token);
break;
default:
throw new FormatException($#"
Unsupported JSON token '{_reader.TokenType}' was found.
Path '{_reader.Path}',
line {_reader.LineNumber}
position {_reader.LinePosition}.");
}
}
private void VisitArray(JArray array)
{
for (int index = 0; index < array.Count; index++)
{
EnterContext(index.ToString());
VisitToken(array[index]);
ExitContext();
}
}
private void VisitPrimitive(JToken data)
{
var key = _currentPath;
if (_data.ContainsKey(key))
{
throw new FormatException($"A duplicate key '{key}' was found.");
}
_data[key] = data.ToString();
}
private void EnterContext(string context)
{
_context.Push(context);
_currentPath = string.Join(Constants.KeyDelimiter, _context.Reverse());
}
private void ExitContext()
{
_context.Pop();
_currentPath = string.Join(Constants.KeyDelimiter, _context.Reverse());
}
}
}
Please refer to the following code. Using MEF I have created a manager object and a plug-in, which imports the IQueryEngine as it should.
However, when I call the method _queryEngine.Get() in the plugin, I get a MissingMethodException, like the method isn't implemented in the concrete implementation of QueryEngine, however it is implemented. I believe that this has to do with the ManagedElementDTO class, but I am not sure what to do here.
Can anyone shed some light on what is going on?
I have an interface:
public interface IQueryEngine
{
ManagedElementDTO Get();
}
And the implementation:
public class QueryEngine : IQueryEngine
{
public ManagedElementDTO Get()
{
return new ManagedElementDTO();
}
}
This engine is declared in a manager:
[Export]
public class ProviderSupervisor : ISupervisor
{
[Export("QueryEngine")]
private IQueryEngine _queryEngine;
[ImportMany(typeof(IProviderModule))]
private IEnumerable<IProviderModule> _providers;
public ProviderSupervisor(IStore store)
{
_queryEngine = new QueryEngine();
CreateContainer();
}
/// <summary>
/// Creates the composition container and composes the parts.
/// </summary>
/// <returns>The container.</returns>
private CompositionContainer CreateContainer()
{
try
{
var catalog = new AggregateCatalog();
var directory = GetProviderDirectory();
if (directory.Exists)
{
catalog.Catalogs.Add(new DirectoryCatalog(directory.FullName));
}
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
return container;
}
catch (Exception)
{
// TODO: Log Error
throw;
}
}
private static DirectoryInfo GetProviderDirectory()
{
var location = ConfigurationSupervisor.GetInstance().ProviderLocation;
if (!string.IsNullOrWhiteSpace(location))
{
var providerLocation = new DirectoryInfo(Environment.ExpandEnvironmentVariables(location));
if (providerLocation.Exists)
{
return providerLocation;
}
}
// Use the current assembly location as the default.
var exeLocation = new FileInfo(Assembly.GetExecutingAssembly().Location);
return exeLocation.Directory;
}
}
and the plug-in:
[Export(typeof(IProviderModule))]
public class Provider : IProviderModule, IDisposable
{
private IQueryEngine _queryEngine;
#region Properties
/// <summary>
/// Gets or sets the query engine.
/// </summary>
[Import("QueryEngine", typeof(IQueryEngine), RequiredCreationPolicy = CreationPolicy.Shared)]
public IQueryEngine QueryEngine
{
get { return _queryEngine; }
set
{
_queryEngine = value;
}
}
public void InvokeQueryEngine()
{
var item = _queryEngine.Get(); // Exception!
}
}
In order to export your QueryEngine type you should be doing the following
[Export(typeof(IQueryEngine))]
public class QueryEngine : QueryEngine
Now in the plugin class importing it should be as simple as
[Import(typeof(IQueryEngine), RequiredCreationPolicy = CreationPolicy.Shared)]
public IQueryEngine QueryEngine
{
get { return _queryEngine; }
set
{
_queryEngine = value;
}
}
I'm not quite sure what you are attempting to do with the [Export] on the IQueryEngine field. You should be able to delete that entirely
I've noticed that Microsoft has implemented a CssTextWriter which is internal
internal sealed class CssTextWriter : TextWriter
{
....
}
Is there a Css writer for .net that?
For example I would like to write code such as:
CssTextWriter writer = new CssTextWriter(textWriter);
writer.WriteBeginCssRule("p");
writer.WriteAttribute("font-family", "Arial,Liberation Sans,DejaVu Sans,sans-serif");
writer.WriteEndCssRule();
The above code would output to the stream as follows:
p { font-family: Arial,Liberation Sans,DejaVu Sans,sans-serif; }
dotLess
http://www.dotlesscss.org/ looks like it would do the job, but was a bit much I just needed a single clss
I wrapped calls to the internal Microsoft class (yes naughty and it might go away in future releases of .net etc....)
public class CssTextWriter
{
public CssTextWriter(TextWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
this.Writer = writer;
this.Initialize();
}
/// <summary>
/// Gets the writer.
/// </summary>
/// <value>
/// The writer.
/// </value>
public TextWriter Writer { get; private set; }
/// <summary>
/// Gets or sets the internal CSS text writer.
/// </summary>
/// <value>
/// The internal CSS text writer.
/// </value>
private object InternalCssTextWriter
{
get;
set;
}
public void WriteBeginCssRule(string selector)
{
this.InternalCssTextWriter.InvokeMethod("WriteBeginCssRule", new[] { selector });
}
public void WriteEndCssRule()
{
this.InternalCssTextWriter.InvokeMethod("WriteEndCssRule");
}
public void WriteAttribute(string name, string value)
{
this.InternalCssTextWriter.InvokeMethod("WriteAttribute", new[] { name, value }, new Type[] { typeof(string), typeof(string) });
}
public void Write(string value)
{
this.InternalCssTextWriter.InvokeMethod("Write", new[] { value }, new Type[] { typeof(string) });
}
public void WriteAttribute(HtmlTextWriterStyle key, string value)
{
this.InternalCssTextWriter.InvokeMethod("WriteAttribute", new object[] { key, value }, new Type[] { typeof(HtmlTextWriterStyle), typeof(string) });
}
private void Initialize()
{
Type internalType = typeof(System.Web.UI.HtmlTextWriter).Assembly.GetType("System.Web.UI.CssTextWriter");
ConstructorInfo ctor = internalType.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0];
this.InternalCssTextWriter = ctor.Invoke(new[] { this.Writer });
}
}
there have been quite some posts about this, all trying to serialize a Func delegate.
But could someone think of an alternative, when the use of the delegate is always clear?
We have a generic create command, which takes a delegate as paramater in the constructor. This delegate will create the Item for the create command:
public class CreateCommand<T> : Command
{
public T Item;
protected Func<T> Constructor;
public ClientCreateCommand(Func<T> constructor)
{
Constructor = constructor;
}
public override void Execute()
{
Item = Constructor();
}
}
The command is used like this:
var c = new CreateCommand<MyType>( () => Factory.CreateMyType(param1, param2, ...) );
History.Insert(c);
Then the History serializes the command and sends it to the server. ofc the delegate can't be serialized as is and we get an exception.
Now could someone think of a very simple Constructor class that can be serialized and does the same job than the lambda expresseion? Means it takes a list of paramters and returns an instance of type T, that we then can write somethink like this:
var constructor = new Constructor<MyType>(param1, param2, ...);
var c = new CreateCommand<MyType>(constructor);
History.Insert(c);
How would the Constructor class look like? Thanks for any ideas!
EDIT(2): I've provided a couple of complete example implementations. They are categorized below as "Implementation 1" and "Implementation 2".
Your delegate is essentially a factory. You could define a factory interface and create a class that implements that interface for your Item class. Below is an example:
public interface IFactory<T>
{
T Create();
}
[Serializable]
public class ExampleItemFactory : IFactory<T>
{
public int Param1 { get; set; }
public string Param2 { get; set; }
#region IFactory<T> Members
public Item Create()
{
return new Item(this.Param1, this.Param2);
}
#endregion
}
public class CreateCommand<T> : Command
{
public T Item;
protected IFactory<T> _ItemFactory;
public CreateCommand(IFactory<T> factory)
{
_ItemFactory = factory;
}
public override void Execute()
{
Item = _ItemFactory.Create();
}
}
You would utilize this code in the following manner:
IFactory<Item> itemFactory = new ExampleItemFactory { Param1 = 5, Param2 = "Example!" };
CreateCommand<Item> command = new CreateCommand<Item>(itemFactory);
command.Execute();
EDIT(1): The specific implementations of IFactory<T> that your application needs will be up to you. You could create specific factory classes for each class that you need, or you could create some kind of factory that dynamically creates an instance using, for example, the Activator.CreateInstance function or perhaps using some kind of Inversion of Control framework such as Spring or StructureMap.
Below is a complete example implementation that uses two factory implementations. One implementation can create any type given an array of arguments using that type's constructor with matching parameters. Another implementation creates any type that has been registered with my "Factory" class.
The Debug.Assert statements ensure that everything is behaving as intended. I ran this application without error.
Implementation 1
[Serializable]
public abstract class Command
{
public abstract void Execute();
}
public class Factory
{
static Dictionary<Type, Func<object[], object>> _DelegateCache = new Dictionary<Type, Func<object[], object>>();
public static void Register<T>(Func<object[], object> #delegate)
{
_DelegateCache[typeof(T)] = #delegate;
}
public static T CreateMyType<T>(params object[] args)
{
return (T)_DelegateCache[typeof(T)](args);
}
}
public interface IFactory<T>
{
T Create();
}
[Serializable]
public class CreateCommand<T> : Command
{
public T Item { get; protected set; }
protected IFactory<T> _ItemFactory;
public CreateCommand(IFactory<T> itemFactory)
{
this._ItemFactory = itemFactory;
}
public override void Execute()
{
this.Item = this._ItemFactory.Create();
}
}
// This class is a base class that represents a factory capable of creating an instance using a dynamic set of arguments.
[Serializable]
public abstract class DynamicFactory<T> : IFactory<T>
{
public object[] Args { get; protected set; }
public DynamicFactory(params object[] args)
{
this.Args = args;
}
public DynamicFactory(int numberOfArgs)
{
if (numberOfArgs < 0)
throw new ArgumentOutOfRangeException("numberOfArgs", "The numberOfArgs parameter must be greater than or equal to zero.");
this.Args = new object[numberOfArgs];
}
#region IFactory<T> Members
public abstract T Create();
#endregion
}
// This implementation uses the Activator.CreateInstance function to create an instance
[Serializable]
public class DynamicConstructorFactory<T> : DynamicFactory<T>
{
public DynamicConstructorFactory(params object[] args) : base(args) { }
public DynamicConstructorFactory(int numberOfArgs) : base(numberOfArgs) { }
public override T Create()
{
return (T)Activator.CreateInstance(typeof(T), this.Args);
}
}
// This implementation uses the Factory.CreateMyType function to create an instance
[Serializable]
public class MyTypeFactory<T> : DynamicFactory<T>
{
public MyTypeFactory(params object[] args) : base(args) { }
public MyTypeFactory(int numberOfArgs) : base(numberOfArgs) { }
public override T Create()
{
return Factory.CreateMyType<T>(this.Args);
}
}
[Serializable]
class DefaultConstructorExample
{
public DefaultConstructorExample()
{
}
}
[Serializable]
class NoDefaultConstructorExample
{
public NoDefaultConstructorExample(int a, string b, float c)
{
}
}
[Serializable]
class PrivateConstructorExample
{
private int _A;
private string _B;
private float _C;
private PrivateConstructorExample()
{
}
public static void Register()
{
// register a delegate with the Factory class that will construct an instance of this class using an array of arguments
Factory.Register<PrivateConstructorExample>((args) =>
{
if (args == null || args.Length != 3)
throw new ArgumentException("Expected 3 arguments.", "args");
if (!(args[0] is int))
throw new ArgumentException("First argument must be of type System.Int32.", "args[0]");
if (!(args[1] is string))
throw new ArgumentException("Second argument must be of type System.String.", "args[1]");
if (!(args[2] is float))
throw new ArgumentException("Third argument must be of type System.Single.", "args[2]");
var instance = new PrivateConstructorExample();
instance._A = (int)args[0];
instance._B = (string)args[1];
instance._C = (float)args[2];
return instance;
});
}
}
class Program
{
static void Main(string[] args)
{
var factory1 = new DynamicConstructorFactory<DefaultConstructorExample>(null);
var command1 = new CreateCommand<DefaultConstructorExample>(factory1);
var factory2 = new DynamicConstructorFactory<NoDefaultConstructorExample>(3);
factory2.Args[0] = 5;
factory2.Args[1] = "ABC";
factory2.Args[2] = 7.1f;
var command2 = new CreateCommand<NoDefaultConstructorExample>(factory2);
PrivateConstructorExample.Register(); // register this class so that it can be created by the Factory.CreateMyType function
var factory3 = new MyTypeFactory<PrivateConstructorExample>(3);
factory3.Args[0] = 5;
factory3.Args[1] = "ABC";
factory3.Args[2] = 7.1f;
var command3 = new CreateCommand<PrivateConstructorExample>(factory3);
VerifySerializability<DefaultConstructorExample>(command1);
VerifySerializability<NoDefaultConstructorExample>(command2);
VerifySerializability<PrivateConstructorExample>(command3);
}
static void VerifySerializability<T>(CreateCommand<T> originalCommand)
{
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (var stream = new System.IO.MemoryStream())
{
System.Diagnostics.Debug.Assert(originalCommand.Item == null); // assert that originalCommand does not yet have a value for Item
serializer.Serialize(stream, originalCommand); // serialize the originalCommand object
stream.Seek(0, System.IO.SeekOrigin.Begin); // reset the stream position to the beginning for deserialization
// deserialize
var deserializedCommand = serializer.Deserialize(stream) as CreateCommand<T>;
System.Diagnostics.Debug.Assert(deserializedCommand.Item == null); // assert that deserializedCommand still does not have a value for Item
deserializedCommand.Execute();
System.Diagnostics.Debug.Assert(deserializedCommand.Item != null); // assert that deserializedCommand now has a value for Item
}
}
}
EDIT(2): After re-reading the question, I think I got a better idea of what the asker was really trying to get at. Essentially, we still want to take advantage of the flexibility offered by lambda expressions / anonymous delegates, but avoid the serialization issues.
Below is another example implementation that utilizes a Factory<T> class to store delegates used to return instances of type T.
Implementation 2
[Serializable]
public abstract class Command
{
public abstract void Execute();
}
[Serializable]
public abstract class CreateCommand<T> : Command
{
public T Item { get; protected set; }
}
public class Factory<T>
{
private static readonly object _SyncLock = new object();
private static Func<T> _CreateFunc;
private static Dictionary<string, Func<T>> _CreateFuncDictionary;
/// <summary>
/// Registers a default Create Func delegate for type <typeparamref name="T"/>.
/// </summary>
public static void Register(Func<T> createFunc)
{
lock (_SyncLock)
{
_CreateFunc = createFunc;
}
}
public static T Create()
{
lock (_SyncLock)
{
if(_CreateFunc == null)
throw new InvalidOperationException(string.Format("A [{0}] delegate must be registered as the default delegate for type [{1}]..", typeof(Func<T>).FullName, typeof(T).FullName));
return _CreateFunc();
}
}
/// <summary>
/// Registers a Create Func delegate for type <typeparamref name="T"/> using the given key.
/// </summary>
/// <param name="key"></param>
/// <param name="createFunc"></param>
public static void Register(string key, Func<T> createFunc)
{
lock (_SyncLock)
{
if (_CreateFuncDictionary == null)
_CreateFuncDictionary = new Dictionary<string, Func<T>>();
_CreateFuncDictionary[key] = createFunc;
}
}
public static T Create(string key)
{
lock (_SyncLock)
{
Func<T> createFunc;
if (_CreateFuncDictionary != null && _CreateFuncDictionary.TryGetValue(key, out createFunc))
return createFunc();
else
throw new InvalidOperationException(string.Format("A [{0}] delegate must be registered with the given key \"{1}\".", typeof(Func<T>).FullName, key));
}
}
}
[Serializable]
public class CreateCommandWithDefaultDelegate<T> : CreateCommand<T>
{
public override void Execute()
{
this.Item = Factory<T>.Create();
}
}
[Serializable]
public class CreateCommandWithKeyedDelegate<T> : CreateCommand<T>
{
public string CreateKey { get; set; }
public CreateCommandWithKeyedDelegate(string createKey)
{
this.CreateKey = createKey;
}
public override void Execute()
{
this.Item = Factory<T>.Create(this.CreateKey);
}
}
[Serializable]
class DefaultConstructorExample
{
public DefaultConstructorExample()
{
}
}
[Serializable]
class NoDefaultConstructorExample
{
public NoDefaultConstructorExample(int a, string b, float c)
{
}
}
[Serializable]
class PublicPropertiesExample
{
public int A { get; set; }
public string B { get; set; }
public float C { get; set; }
}
class Program
{
static void Main(string[] args)
{
// register delegates for each type
Factory<DefaultConstructorExample>.Register(() => new DefaultConstructorExample());
Factory<NoDefaultConstructorExample>.Register(() => new NoDefaultConstructorExample(5, "ABC", 7.1f));
Factory<PublicPropertiesExample>.Register(() => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });
// create commands
var command1 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
var command2 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
var command3 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
// verify that each command can be serialized/deserialized and that the creation logic works
VerifySerializability<DefaultConstructorExample>(command1);
VerifySerializability<DefaultConstructorExample>(command2);
VerifySerializability<DefaultConstructorExample>(command3);
// register additional delegates for each type, distinguished by key
Factory<DefaultConstructorExample>.Register("CreateCommand", () => new DefaultConstructorExample());
Factory<NoDefaultConstructorExample>.Register("CreateCommand", () => new NoDefaultConstructorExample(5, "ABC", 7.1f));
Factory<PublicPropertiesExample>.Register("CreateCommand", () => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });
// create commands, passing in the create key to the constructor
var command4 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
var command5 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
var command6 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
// verify that each command can be serialized/deserialized and that the creation logic works
VerifySerializability<DefaultConstructorExample>(command4);
VerifySerializability<DefaultConstructorExample>(command5);
VerifySerializability<DefaultConstructorExample>(command6);
}
static void VerifySerializability<T>(CreateCommand<T> originalCommand)
{
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using (var stream = new System.IO.MemoryStream())
{
System.Diagnostics.Debug.Assert(originalCommand.Item == null); // assert that originalCommand does not yet have a value for Item
serializer.Serialize(stream, originalCommand); // serialize the originalCommand object
stream.Seek(0, System.IO.SeekOrigin.Begin); // reset the stream position to the beginning for deserialization
// deserialize
var deserializedCommand = serializer.Deserialize(stream) as CreateCommand<T>;
System.Diagnostics.Debug.Assert(deserializedCommand.Item == null); // assert that deserializedCommand still does not have a value for Item
deserializedCommand.Execute();
System.Diagnostics.Debug.Assert(deserializedCommand.Item != null); // assert that deserializedCommand now has a value for Item
}
}
}