//Get PropertyDescriptor object for the given property name
var propDesc = TypeDescriptor.GetProperties(typeof(T))[propName];
//Get FillAttributes methodinfo delegate
var methodInfo = propDesc.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic)
.FirstOrDefault(m => m.IsFamily || m.IsPublic && m.Name == "FillAttributes");
//Create Validation attribute
var attribute = new RequiredAttribute();
var attributes= new ValidationAttribute[]{attribute};
//Invoke FillAttribute method
methodInfo.Invoke(propDesc, new object[] { attributes });
Hi I am trying to add Validation attribute at runtime using the above code. However I am getting the below exception:
Collection was of a fixed size
Don't let someone tell you that you can't do it. You can run for president if you want :-)
For your convenience, this is a fully working example
public class SomeAttribute : Attribute
{
public SomeAttribute(string value)
{
this.Value = value;
}
public string Value { get; set; }
}
public class SomeClass
{
public string Value = "Test";
}
[TestMethod]
public void CanAddAttribute()
{
var type = typeof(SomeClass);
var aName = new System.Reflection.AssemblyName("SomeNamespace");
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
var mb = ab.DefineDynamicModule(aName.Name);
var tb = mb.DefineType(type.Name + "Proxy", System.Reflection.TypeAttributes.Public, type);
var attrCtorParams = new Type[] { typeof(string) };
var attrCtorInfo = typeof(SomeAttribute).GetConstructor(attrCtorParams);
var attrBuilder = new CustomAttributeBuilder(attrCtorInfo, new object[] { "Some Value" });
tb.SetCustomAttribute(attrBuilder);
var newType = tb.CreateType();
var instance = (SomeClass)Activator.CreateInstance(newType);
Assert.AreEqual("Test", instance.Value);
var attr = (SomeAttribute)instance.GetType()
.GetCustomAttributes(typeof(SomeAttribute), false)
.SingleOrDefault();
Assert.IsNotNull(attr);
Assert.AreEqual(attr.Value, "Some Value");
}
Sorry, I'm very late to the party. This is for those who might come here later. The top answer is awesome. Recently, a library has been developed, that abstracts away all of that complexity, and gives you something as simple as this:
var attributeType = typeof(CustomAAttribute);
var attributeParams = new object[] { "Jon Snow" };
var typeExtender = new TypeExtender("ClassA");
typeExtender.AddProperty("IsAdded", typeof(bool), attributeType, attributeParams);
To work with. details of how to install and use the library can be found here
Disclaimer: I developed this library and I've been using it for a lot of projects and it works like magic
Use FastDeepCloner which I developed.
public class test{
public string Name{ get; set; }
}
var prop = DeepCloner.GetFastDeepClonerProperties(typeof(test)).First();
prop.Attributes.Add(new JsonIgnoreAttribute());
// now test and se if exist
prop = DeepCloner.GetFastDeepClonerProperties(typeof(test)).First();
bool containAttr = prop.ContainAttribute<JsonIgnoreAttribute>()
// or
JsonIgnoreAttribute myAttr = prop.GetCustomAttribute<JsonIgnoreAttribute>();
It is not working because the FillAttributes method expects a parameter of the IList type and you are passing an array. Below is the implementation of MemberDescriptor.FillAttributes:
protected virtual void FillAttributes(IList attributeList) {
if (originalAttributes != null) {
foreach (Attribute attr in originalAttributes) {
attributeList.Add(attr);
}
}
}
As you can see, FillAttributes just fills the attributeList parameter with all attributes of your property. And to make your code work, change the var attributes= new ValidationAttribute[]{attribute}; line with:
var attributes = new ArrayList { attribute };
This code has nothing with adding attributes to property of type at runtime. This is "adding attribute to the PropertyDescriptor" extracted from type and has no sense unless you are trying to build a type at runtime which is based on an already existing type.
It is not possible to add Attributes in run-time. Attributes are static and cannot be added or removed.
Similar questions:
Can attributes be added dynamically in C#?
Remove C# attribute of a property dynamically
Related
This is a follow up from the question here
Dynamic classes/objects ML.net's PredictionMoadel<TInput, TOutput> Train()
My system cannot use a predefined class at compile time, therefore I tried to feed a dynamic class into ML.NET like below
// field data type
public class Field
{
public string FieldName { get; set; }
public Type FieldType { get; set; }
}
// dynamic class helper
public class DynamicClass : DynamicObject
{
private readonly Dictionary<string, KeyValuePair<Type, object>> _fields;
public DynamicClass(List<Field> fields)
{
_fields = new Dictionary<string, KeyValuePair<Type, object>>();
fields.ForEach(x => _fields.Add(x.FieldName,
new KeyValuePair<Type, object>(x.FieldType, null)));
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (_fields.ContainsKey(binder.Name))
{
var type = _fields[binder.Name].Key;
if (value.GetType() == type)
{
_fields[binder.Name] = new KeyValuePair<Type, object>(type, value);
return true;
}
else throw new Exception("Value " + value + " is not of type " + type.Name);
}
return false;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = _fields[binder.Name].Value;
return true;
}
}
private static void Main(string[] args)
{
var fields = new List<Field>
{
new Field {FieldName = "Name", FieldType = typeof(string)},
new Field {FieldName = "Income", FieldType = typeof(float)}
};
dynamic obj1 = new DynamicClass(fields);
obj1.Name = "John";
obj1.Income = 100f;
dynamic obj2 = new DynamicClass(fields);
obj2.Name = "Alice";
obj2.Income = 200f;
var trainingData = new List<dynamic> {obj1, obj2};
var env = new LocalEnvironment();
var schemaDef = SchemaDefinition.Create(typeof(DynamicClass));
schemaDef.Add(new SchemaDefinition.Column(null, "Name", TextType.Instance));
schemaDef.Add(new SchemaDefinition.Column(null, "Income", NumberType.R4));
var trainDataView = env.CreateStreamingDataView(trainingData, schemaDef);
var pipeline = new CategoricalEstimator(env, "Name")
.Append(new ConcatEstimator(env, "Features", "Name"))
.Append(new FastTreeRegressionTrainer(env, "Income", "Features"));
var model = pipeline.Fit(trainDataView);
}
and got the error: "'No field or property with name 'Name' found in type 'System.Object'". I tried generating the class using Reflection only to run into the same problem.
Is there a workaround? Thanks
For those attempting to do this, I have a working solution that creates the schema and can be used to train data dynamically.
First, grab the code for DynamicTypeProperty and DynamicType from my other answer here.
The following code will create a schema dynamically:
var properties = new List<DynamicTypeProperty>()
{
new DynamicTypeProperty("SepalLength", typeof(float)),
new DynamicTypeProperty("SepalWidth", typeof(float)),
new DynamicTypeProperty("PetalLength", typeof(float)),
new DynamicTypeProperty("PetalWidth", typeof(float)),
};
// create the new type
var dynamicType = DynamicType.CreateDynamicType(properties);
var schema = SchemaDefinition.Create(dynamicType);
You'll then need to create list with the required data. This is done as follows:
var dynamicList = DynamicType.CreateDynamicList(dynamicType);
// get an action that will add to the list
var addAction = DynamicType.GetAddAction(dynamicList);
// call the action, with an object[] containing parameters in exact order added
addAction.Invoke(new object[] {1.1, 2.2, 3.3, 4.4});
// call add action again for each row.
Then you'll need to create an IDataView with the data, this requires using reflection, or the trainers won't infer the correct type.
var mlContext = new MLContext();
var dataType = mlContext.Data.GetType();
var loadMethodGeneric = dataType.GetMethods().First(method => method.Name =="LoadFromEnumerable" && method.IsGenericMethod);
var loadMethod = loadMethodGeneric.MakeGenericMethod(dynamicType);
var trainData = (IDataView) loadMethod.Invoke(mlContext.Data, new[] {dynamicList, schema});
You then, should be able to run the trainData through your pipeline.
Good luck.
Dynamic class doesn't actually create a class definition but it rather provides you with dynamic object.
I looked at the code for SchemaDefinition.Create() it needs an actual class definition to build the schema. So your options are to create and load a class definition dynamically.
You can create your class as string with all dynamic properties and compile it using Microsoft compiler services aka Roslyn. See here. This will generate an assembly (in memory as memory stream or on file system) with your dynamic type.
Now you are only half way there. To get your dynamic type from dynamic assembly you need to load it in the App Domain. See this post.
Once the assembly is loaded you can use 'Activator.CreateInstance()' if it's same domain or if it's your custom domain then you would need yourDomain.CreateInstanceAndUnwrap() to create the object out of dynamically generated Class and to get the type use Assembly.GetType().
Few sample here, A little out of date but will get you on your feet if you are up for this. See CompilerEngine and CompilerService to compile and load the assembly.
Other options: Refelection.Emit() but it requires a great deal of IL level coding. See this post.
Right now I'm using a dummy place holder like this as a workaround
public class TrainingSample
{
public string TextField1;
public string TextField2;
public string TextField3;
public string TextField4;
public string TextField5;
public float FloatField1;
public float FloatField2;
public float FloatField3;
public float FloatField4;
public float FloatField5;
public float FloatField6;
public float FloatField7;
public float FloatField8;
public float FloatField9;
public float FloatField10;
public float FloatField11;
public float FloatField12;
public float FloatField13;
public float FloatField14;
public float FloatField15;
}
I am trying to generate an Excel file using the following code:
public static Stream GenerateFileFromClass<T>(IEnumerable<T> collection, int startrow, int startcolumn, byte[]templateResource)
{
using (Stream template = new MemoryStream(templateResource))//this is an excel file I am using for a base/template
{
using (var tmpl = new ExcelPackage(template))
{
ExcelWorkbook wb = tmpl.Workbook;
if (wb != null)
{
if (wb.Worksheets.Count > 0)
{
ExcelWorksheet ws = wb.Worksheets.First();
ws.Cells[startrow, startcolumn].LoadFromCollection<T>(collection, false);
}
return new MemoryStream(tmpl.GetAsByteArray());
}
else
{
throw new ArgumentException("Unable to load template WorkBook");
}
}
}
}
This works like a treat, however.. I want to ignore a couple of the properties in my class collection, so it matches up with my template. I know that the LoadFromCollection will generate columns in the Excel file based on the public properties of the class, but as I am loading the class using Entity Framework, if I mark the field as private, then EF complains - mostly because one of the fields I don't want to show is the Key.
I have tried to mark the properties I don't want using [XmlIgnore], to no avail. Is there any way to do this, short of loading the whole collection into a dataset or some such and trimming the columns out of that? Or casting to a base class without the properties I don't need?
Yes, EPPlus provides an overload of the .LoadFromCollection<T>() method with a MemberInfo[] parameter for the properties you wish to include.
This gives us all we need to ignore any properties with a certain attribute.
For example, if we want to ignore properties with this custom attribute:
public class EpplusIgnore : Attribute { }
then we can write a little extension method to first find all MemberInfo objects for the properties without the [EpplusIgnore] attribute then to return the result of the correct overload of the .LoadFromCollection method in the EPPlus dll.
Something like this:
public static class Extensions
{
public static ExcelRangeBase LoadFromCollectionFiltered<T>(this ExcelRangeBase #this, IEnumerable<T> collection) where T:class
{
MemberInfo[] membersToInclude = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p=>!Attribute.IsDefined(p,typeof(EpplusIgnore)))
.ToArray();
return #this.LoadFromCollection<T>(collection, false,
OfficeOpenXml.Table.TableStyles.None,
BindingFlags.Instance | BindingFlags.Public,
membersToInclude);
}
}
So, for example, using it like this will ignore the .Key property when exporting a Person collection to excel:
public class Person
{
[EpplusIgnore]
public int Key { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
var demoData = new List<Person> { new Person { Key = 1, Age = 40, Name = "Fred" }, new Person { Key = 2, Name = "Eve", Age = 21 } };
FileInfo fInfo = new FileInfo(#"C:\Temp\Book1.xlsx");
using (var excel = new ExcelPackage())
{
var ws = excel.Workbook.Worksheets.Add("People");
ws.Cells[1, 1].LoadFromCollectionFiltered(demoData);
excel.SaveAs(fInfo);
}
}
}
Giving the output we'd expect:
Thanks Stewart_R, based on your work i made a new one that receives property names:
public static ExcelRangeBase LoadFromCollection<T>(this ExcelRangeBase #this,
IEnumerable<T> collection, string[] propertyNames, bool printHeaders) where T:class
{
MemberInfo[] membersToInclude = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p=>propertyNames.Contains(p.Name))
.ToArray();
return #this.LoadFromCollection<T>(collection, printHeaders,
OfficeOpenXml.Table.TableStyles.None,
BindingFlags.Instance | BindingFlags.Public,
membersToInclude);
}
As of version 5.5 of EPPlus, the EpplusIgnore attribute is baked into the library.
https://github.com/EPPlusSoftware/EPPlus/pull/258
We can use it like below.
using System;
using OfficeOpenXml.Attributes;
namespace ExportToExcel.Services.ExportViewModels
{
[EpplusTable]
public class StudentExportViewModel
{
// [EpplusTableColumn]
[EpplusIgnore]
public string Id { get; set; }
...
}
}
Note that, either the EpplusTable or EpplusTableColumn attribute is required.
Here is a link to the related test cases https://github.com/EPPlusSoftware/EPPlus/blob/develop/src/EPPlusTest/LoadFunctions/LoadFromCollectionAttributesTests.cs
Recently, I had to use EPPlus in a project and I've documented the set-up in this blog post https://www.kajanm.com/blog/exporting-data-to-excel-in-c-sharp/
Below is a short version of Stewart_R's answer. If someone is using a Generic method to create excel then go ahead with the below approach.
public static class EPPlusHelper
{
public static byte[] GetExportToExcelByteArray<T>(IEnumerable<T> data)
{
var memoryStream = new MemoryStream();
ExcelPackage.LicenseContext = LicenseContext.Commercial;
using (var excelPackage = new ExcelPackage(memoryStream))
{
//To get all members without having EpplusIgnore attribute added
MemberInfo[] membersToInclude = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => !Attribute.IsDefined(p, typeof(EpplusIgnore)))
.ToArray();
var worksheet = excelPackage.Workbook.Worksheets.Add("Sheet1");
worksheet.Cells["A1"].LoadFromCollection(data, true, TableStyles.None, BindingFlags.Instance | BindingFlags.Public,
membersToInclude);
worksheet.Cells["A1:AN1"].Style.Font.Bold = true;
worksheet.DefaultColWidth = 20;
return excelPackage.GetAsByteArray();
}
}
}
Then call this method like
public ActionResult ExportToExcel()
{
var result= // Here pass your data (list)
byte[] fileResult = EPPlusHelper.GetExportToExcelByteArray(result);
return File(fileResult, "application/vnd.ms-excel", "FileName.xlsx");
}
Let us say I create a type dynamically using CSharpCodeProvider and choose NOT to persist the results. The assembly that is generated is existing only in memory.
Let us say I Create two types in two different in-memory assemblies:
Assembly1:
public class DynamicTypeA { }
Assembly2:
public class DynamicTypeB
{
public DynamicTypeA MyProperty { get; set; }
}
As you can see the second type has a property of the first type.
Cool. Now I want to explore DynamicTypeB using reflection:
foreach (PropertyInfo pi in typeof(DynamicTypeB).GetProperties())
{
Console.WriteLine(pi.PropertyType.Name);
}
It turns out that PropertyInfo.PropertyType fails when the assembly is not located on disk !!!
This is true for MemberInfo and for all other type investigation constructs.
As we all know lots of .Net APIs is using type investigation on the backend and they would fail when the investigated type happens to live in an in-memory assembly. For Example Expression.Bind takes a MemberInfo as the first parameter and is using it to validate that the type of the expression provided in the second parameter matches the type of the member. When this type happens to be in an in memory assembly Expression.Bind fails.
Can anyone think of a solution?
Creating types dynamically and writing them do disk pollutes the running environment and that is bad, yet without reflection these types are worthless.
Thanks
Manu
It turns out that PropertyInfo.PropertyType fails when the assembly is not located on disk
Are you sure? Take a look:
static void Main( string[] args )
{
string code = #"
namespace foo {
public class DynamicTypeA { }
public class DynamicTypeB {
public DynamicTypeA MyProperty { get; set; }
}
}
";
CSharpCodeProvider csp = new CSharpCodeProvider();
CompilerParameters p = new CompilerParameters();
p.GenerateInMemory = true;
var results = csp.CompileAssemblyFromSource( p, code );
foreach ( Type type in results.CompiledAssembly.GetTypes() )
{
Console.WriteLine( type.Name );
foreach ( PropertyInfo pi in type.GetProperties() )
{
Console.WriteLine( "\t{0}", pi.PropertyType.Name );
}
}
Console.ReadLine();
}
This uses your snippet and works like a charm.
Moving the loop to the inside of the dynamic code doesn't change much, it still works:
string code = #"
using System;
using System.Reflection;
namespace foo {
public class DynamicTypeA { }
public class DynamicTypeB {
public DynamicTypeA MyProperty { get; set; }
}
public class DynamicTypeC {
public void Foo() {
foreach ( PropertyInfo pi in typeof(DynamicTypeB).GetProperties() )
{
Console.WriteLine( pi.PropertyType.Name );
}
}
}
}
";
CSharpCodeProvider csp = new CSharpCodeProvider();
CompilerParameters p = new CompilerParameters();
p.GenerateInMemory = true;
var results = csp.CompileAssemblyFromSource( p, code );
var c = results.CompiledAssembly.CreateInstance( "foo.DynamicTypeC" );
var typeC = c.GetType();
typeC.InvokeMember( "Foo", BindingFlags.InvokeMethod |
BindingFlags.Public | BindingFlags.Instance, null, c, null );
If for some reason you have issues here, you are definitely doing something more complicated.
I found the problem:
I need to load the dynamically compiled assemblies into the current AppDomain, only then I will be able to retrieve any info by reflection.
I would like to thank Sam Alavi for explaining this to me.
Here is the code with the necessary fix:
public class HomeController : Controller
{
public Assembly AssemblyA { get; set; }
public Assembly AssemblyB { get; set; }
public ActionResult Index()
{
var provider = new CSharpCodeProvider();
var parametersA = new CompilerParameters();
parametersA.GenerateInMemory = true;
parametersA.OutputAssembly = "dynamicA.dll";
var code1 = #"namespace DynamicA { public class DynamicClassA { } }";
var result1 = provider.CompileAssemblyFromSource(parametersA, code1);
this.AssemblyA = result1.CompiledAssembly;
var parametersB = new CompilerParameters();
parametersA.GenerateInMemory = true;
parametersB.ReferencedAssemblies.Add("dynamicA.dll");
parametersB.OutputAssembly = "dynamicB.dll";
var code2 = #"using DynamicA; namespace DynamicB { public class DynamicB { public DynamicClassA MyProperty { get; set; } } }";
var results2 = provider.CompileAssemblyFromSource(parametersB, code2);
this.AssemblyB = results2.CompiledAssembly;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
if (e.Name.Contains("dynamicA"))
return this.AssemblyA;
if (e.Name.Contains("dynamicB"))
return this.AssemblyB;
return null;
};
AppDomain.CurrentDomain.Load(this.AssemblyA.FullName);
AppDomain.CurrentDomain.Load(this.AssemblyB.FullName);
var t = results2.CompiledAssembly.DefinedTypes.First();
var pi = t.GetProperty("MyProperty");
var res = pi.PropertyType.Name;
return View(res);
}
}
Suppose I've got a class named Foo.
I cannot change the Foo class but I wan't to extend it with a property named Bar of type string.
Also I've got a lot more classes like Foo so I'm interested in a 'generic' solution.
I'm looking into ExpandoObject, dynamic and it gives me the result I'm asking for but I was wondering it it could be done without using dynamic...
static void Main(string[] args)
{
var foo = new Foo() { Thing = "this" };
var fooplus = Merge(foo, new { Bar = " and that" });
Console.Write(string.Concat(fooplus.Thing, fooplus.Bar));
Console.ReadKey();
}
public class Foo
{
public string Thing { get; set; }
}
public static dynamic Merge(object item1, object item2)
{
if (item1 == null || item2 == null)
return item1 ?? item2 ?? new ExpandoObject();
dynamic expando = new ExpandoObject();
var result = expando as IDictionary<string, object>;
foreach (System.Reflection.PropertyInfo fi in item1.GetType().GetProperties())
{
result[fi.Name] = fi.GetValue(item1, null);
}
foreach (System.Reflection.PropertyInfo fi in item2.GetType().GetProperties())
{
result[fi.Name] = fi.GetValue(item2, null);
}
return result;
}
Your problem can relatively easily be solved by using Reflection.Emit and run-time code generation.
Suppose now you have the following class that you would like to extend.
public class Person
{
public int Age { get; set; }
}
This class represents a person, and contains a property named Age to represent the person's age.
In your case, you would also like to add a Name property of type string to represent the person's name.
The simplest and most streamlined solution would then be to define the following interface.
public interface IPerson
{
string Name { get; set; }
int Age { get; set; }
}
This interface, which will be used to extend your class, should contain all the old properties your current class contains, and the new ones you would like to add. The reason for this will become clear in a moment.
You can now use the following class definition to actually extend your class by creating a new type at runtime which will also make it derive from the above mentioned interface.
class DynamicExtension<T>
{
public K ExtendWith<K>()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Module");
var type = module.DefineType("Class", TypeAttributes.Public, typeof(T));
type.AddInterfaceImplementation(typeof(K));
foreach (var v in typeof(K).GetProperties())
{
var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });
var getGenerator = getter.GetILGenerator();
var setGenerator = setter.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, field);
getGenerator.Emit(OpCodes.Ret);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Stfld, field);
setGenerator.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
property.SetSetMethod(setter);
type.DefineMethodOverride(getter, v.GetGetMethod());
type.DefineMethodOverride(setter, v.GetSetMethod());
}
return (K)Activator.CreateInstance(type.CreateType());
}
}
To actually use this class, simply execute the following lines of code.
class Program
{
static void Main(string[] args)
{
var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();
extended.Age = 25;
extended.Name = "Billy";
Console.WriteLine(extended.Name + " is " + extended.Age);
Console.Read();
}
}
You can now see that the reason we used an interface to extend our newly created class is so that we can have a type-safe way of accessing its properties. If we simply returned an object type, we would be forced to access its properties by Reflection.
EDIT
The following modified version is now able to instantiate complex types located inside the interface, and implement the other simple ones.
The definition of the Person class stays the same, while the IPerson interface now becomes the following.
public interface IPerson
{
string Name { get; set; }
Person Person { get; set; }
}
The DynamicExtension class definition now changes to the following.
class DynamicExtension<T>
{
public T Extend()
{
var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("Module");
var type = module.DefineType("Class", TypeAttributes.Public);
type.AddInterfaceImplementation(typeof(T));
foreach (var v in typeof(T).GetProperties())
{
var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });
var getGenerator = getter.GetILGenerator();
var setGenerator = setter.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, field);
getGenerator.Emit(OpCodes.Ret);
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
setGenerator.Emit(OpCodes.Stfld, field);
setGenerator.Emit(OpCodes.Ret);
property.SetGetMethod(getter);
property.SetSetMethod(setter);
type.DefineMethodOverride(getter, v.GetGetMethod());
type.DefineMethodOverride(setter, v.GetSetMethod());
}
var instance = (T)Activator.CreateInstance(type.CreateType());
foreach (var v in typeof(T).GetProperties().Where(x => x.PropertyType.GetConstructor(new Type[0]) != null))
{
instance.GetType()
.GetProperty(v.Name)
.SetValue(instance, Activator.CreateInstance(v.PropertyType), null);
}
return instance;
}
}
We can now simply execute the following lines of code to get all the appropriate values.
class Program
{
static void Main(string[] args)
{
var extended = new DynamicExtension<IPerson>().Extend();
extended.Person.Age = 25;
extended.Name = "Billy";
Console.WriteLine(extended.Name + " is " + extended.Person.Age);
Console.Read();
}
}
as my comments were getting very verbose, I thought I'd add a new answer. this answer is completely Mario's work and thinking, only has my minor addition to exemplify what I'm trying to put across.
There are a few minor changes to mario's example that would make this work very well, namely, just changing the fact that the existing properties are added as the class object, rather than duplicating the entire class. Anyway, here's how this looks (only amended sections added, all else remains as per mario's answer):
public class Person
{
public int Age { get; set; }
public string FaveQuotation { get; set; }
}
for the IPerson interface, we add the actual Person class, rather than copying the properties:
public interface IPerson
{
// extended property(s)
string Name { get; set; }
// base class to extend - tho we should try to overcome using this
Person Person { get; set; }
}
This translates to an updated usage of:
static void Main(string[] args)
{
var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();
var pocoPerson = new Person
{
Age = 25,
FaveQuotation = "2B or not 2B, that is the pencil"
};
// the end game would be to be able to say:
// extended.Age = 25; extended.FaveQuotation = "etc";
// rather than using the Person object along the lines below
extended.Person = pocoPerson;
extended.Name = "Billy";
Console.WriteLine(extended.Name + " is " + extended.Person.Age
+ " loves to say: '" + extended.Person.FaveQuotation + "'");
Console.ReadKey();
}
Hope this helps the original OP, I know it made me think, tho the jury is still out as to whether the Person class should be flattened to the same level in the method as the new properties!! So in effect, using the line new DynamicExtension<Person>().ExtendWith<IPerson>(); SHOULD return a fully extended new object -intellisence included. Tough call - hmmm...
Without having access to the class definition, the best you could do is create a class which is derived from the target class. Unless the original is Sealed.
I know this is coming late. A nuget package that abstracts all the complexity required to extend a type at runtime has been created. It is as simple as:
var className = "ClassA";
var baseType = typeof(List<string>);
var typeExtender = new TypeExtender(className, baseType);
typeExtender.AddProperty("IsAdded", typeof(bool));
typeExtender.AddProperty<double>("Length");
var returnedClass = typeExtender.FetchType();
var obj = Activator.CreateInstance(returnedClass);
You can find more usage instructions on the repo TypeExtender. Nuget package is at nuget
I have created a custom Property Selector to accept an array in the constructor to say which properties should be included in the search. The approach works well as long as there are no component types, but how do I deal with those? Here is an example:
public class Customer
{
public virtual int Id { get; private set; }
public virtual Name Name { get; set; }
public virtual bool isPreferred { get; set; }
//...etc
}
public class Name
{
public string Title { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Fullname { get; }
}
public class CustomerPropertySelector : Example.IPropertySelector
{
private string[] _propertiesToInclude = { };
public CustomerPropertySelector(string[] propertiesToInclude)
{
this._propertiesToInclude = propertiesToInclude;
}
public bool Include(object propertyValue, String propertyName, NHibernate.Type.IType type)
{
//...Checking for null and zeros etc, excluded for brevity
if (!_propertiesToInclude.Contains(propertyName))
return false;
return true;
}
}
I would like to be able to search by first name, but not necessarily last. The property name is Name however, so both first and last names seem to be part of the same property, and something like Name.Firstname, which would normally work as a criterion, doesn't seem to work here. What would be the best way around that?
EXAMPLE:
Customer exampleCust = new Customer(FirstName: "Owen");
IList<Customer> matchedCustomers = _custRepo.GetByExample(exampleCust, new string[] { "Name.FirstName" });
Given that there are 2 customers in db, only one named "Owen", but both have isPreferred = false, I would like my query to only return the first one. Standard QBE will return both based on isPreferred property.
SOLUTION:
Thank you for the answers, the solution is mostly based on answer by therealmitchconnors, however I couldn't have done it without Mark Perry's answer either.
The trick was to realise that rather than including Name.FirstName property, I actually want to exclude Name.LastName, since QBE only allows us to exclude properties. I used a method adapted from therealmitchconnors's answer to help me determine fully qualified names of properties. Here is the working code:
public IList<T> GetByExample(T exampleInstance, params string[] propertiesToInclude)
{
ICriteria criteria = _session.CreateCriteria(typeof(T));
Example example = Example.Create(exampleInstance);
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var childProperties = GetChildProperties(prop);
foreach (var c in childProperties)
{
if (!propertiesToInclude.Contains(c))
example.ExcludeProperty(c);
}
}
criteria.Add(example);
return criteria.List<T>();
}
private IEnumerable<string> GetChildProperties(System.Reflection.PropertyInfo property)
{
var builtInTypes = new List<Type> { typeof(bool), typeof(byte), typeof(sbyte), typeof(char),
typeof(decimal), typeof(double), typeof(float), typeof(int), typeof(uint), typeof(long),
typeof(ulong), typeof(object), typeof(short), typeof(ushort), typeof(string), typeof(DateTime) };
List<string> propertyNames = new List<string>();
if (!builtInTypes.Contains(property.PropertyType) && !property.PropertyType.IsGenericType)
{
foreach (var subprop in property.PropertyType.GetProperties())
{
var childNames = GetChildProperties(subprop);
propertyNames = propertyNames.Union(childNames.Select(r => property.Name + "." + r)).ToList();
}
}
else
propertyNames.Add(property.Name);
return propertyNames;
}
I wasn't sure of the best way to determine whether a property was a component class or not, any suggestions on how to improve the code are very welcome.
The following code would replace the logic you are using to populate propertiesToInclude. I changed it from an array to a list so I could use the Add method because I am lazy, but I think you get the picture. This does only work for one sub-level of properties. For n levels you would need to recurse.
List<string> _propertiesToInclude = new List<string>();
Type t;
var props = t.GetProperties();
foreach (var prop in props)
{
if (prop.PropertyType.IsClass)
foreach (var subprop in prop.PropertyType.GetProperties())
_propertiesToInclude.Add(string.Format("{0}.{1}", prop.Name, subprop.Name));
else
_propertiesToInclude.Add(prop.Name);
}
I thought I had something but reading your question again you want to know why the QBE NHibernate code doesn't work with component properties.
I think you need to create a sub-criteria query for the Name part.
Perhaps something like this:
public IList<Customer> GetByExample(Customer customer, string[] propertiesToExclude){
Example customerQuery = Example.Create(customer);
Criteria nameCriteria = customerQuery.CreateCriteria<Name>();
nameCriteria.Add(Example.create(customer.Name));
propertiesToExclude.ForEach(x=> customerQuery.ExcludeProperty(x));
propertiesToExclude.ForEach(x=> nameCriteria.ExcludeProperty(x));
return customerQuery.list();
}
This is an example from the NHibernate Test Project, it shows how to exclude Component properties.
[Test]
public void TestExcludingQBE()
{
using (ISession s = OpenSession())
using (ITransaction t = s.BeginTransaction())
{
Componentizable master = GetMaster("hibernate", null, "ope%");
ICriteria crit = s.CreateCriteria(typeof(Componentizable));
Example ex = Example.Create(master).EnableLike()
.ExcludeProperty("Component.SubComponent");
crit.Add(ex);
IList result = crit.List();
Assert.IsNotNull(result);
Assert.AreEqual(3, result.Count);
master = GetMaster("hibernate", "ORM tool", "fake stuff");
crit = s.CreateCriteria(typeof(Componentizable));
ex = Example.Create(master).EnableLike()
.ExcludeProperty("Component.SubComponent.SubName1");
crit.Add(ex);
result = crit.List();
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Count);
t.Commit();
}
}
Source code link