I have a config service which wraps the baked-in assembly settings, but I'd also like to override these on the command line.
Currently this code is working fine:
public interface ISettings
{
string Url { get; }
}
public class OperationalSettings : ISettings
{
public string Url { get { return ServiceSettings.Default.Url; } }
}
public class CommandLineModel
{
public string Url;
}
public class CommandLineSettings : ISettings
{
private readonly CommandLineModel _model;
public CommandLineSettings(string serialisedSettings)
{
_model = JsonConvert.DeserializeObject<CommandLineModel>(serialisedSettings);
}
public string Url { get { return _model.Url; } }
}
public class ConfigService
{
private readonly ISettings _settings;
public ConfigService(ISettings settings)
{
_settings = settings;
}
public ISettings settings { get { return _settings; } }
}
Then the test driver code:
class Program
{
static void Main(string[] args)
{
ISettings opSettings = new OperationalSettings();
var commandLineTest = "{Url:'http://overridenurl.com'}";
ISettings commandSettings = new CommandLineSettings(commandLineTest);
var configService = new ConfigService(opSettings);
var configServiceUsingCmdOpts = new ConfigService(commandSettings);
}
}
So with this I can override settings using the command line string. However, what I don't like is that if I have a new settings, I now need to add this in 4 places:
The interface
The concrete implementation of the settings wrapper (OperationalSettings)
The command line model for deserialisation
The command line settings implementation that wraps the deserialised model.
This seems to suffer from scalability once I add more properties. Is there a more efficient way to achieve this without so many code changes?
You might take a look at DynamicObject
public class CommandLineModelDictionary : DynamicObject
{
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
Otherwise If you have a simple scenario in which you need an object that can only add and remove members at run time but that does not need to define specific operations and does not have static members, use the ExpandoObject class
here how to use
class Program
{
public static dynamic Dyn { get; set; }
static void Main(string[] args)
{
dynamic model= new CommandLineModelDictionary();
model.Prop1 = "Foo";
model.Prop2 = "toto";
Console.WriteLine(model.Prop1);
Console.WriteLine(model.Prop2);
//otherwise you can use
dynamic dynModel = new ExpandoObject();
dynModelop1 = "Test1";
dynModel2 = "Test2";
Console.WriteLine(dynModel.Prop1);
Console.WriteLine(dynModel.Prop2);
}
}
Related
I have an immutable DTO that I'd like to fake with Bogus Faker (version 31.0.2) but the property with an overriding rule only returns what it was initialized with by the constructor:
Example DTO (real ones are more complex)
using Xunit;
using Bogus;
namespace SO.Tests
{
class ClassWithInitialization
{
public ClassWithInitialization(string name)
{
this.Name = name
}
public string Name { get; }
}
Example DTO Faker
class FakeClassWithInitialization : Faker<ClassWithInitialization>
{
private FakeClassWithInitialization() { }
public static CreateDefault()
{
return (FakeClassWithInitialization) new FakeClassWithInitialization()
.CustomInstantiator(f => new ClassWithInitialization(null))
.RuleFor(o => o.Name, f => f.Person.FullName);
}
public FakeClassWithInitialization WithName(string name)
{
RuleFor(o => o.Name, f => name);
return this;
}
}
Example Tests
Both of the following tests fail as the Name property remains null as provided in the constructor.
public class Tests
{
[Fact]
public void TestClassWithInitialization()
{
var faker = FakeClassWithInitialization
.CreateDefault();
var testPoco = faker.Generate();
Assert.False(string.IsNullOrEmpty(testPoco.Name)); #fails
}
[Fact]
public void TestClassWithInitialization_with_overriding_rule()
{
var faker = FakeClassWithInitialization
.CreateDefault()
.WithName("John Smith");
var testPoco = faker.Generate();
Assert.AreEqual("John Smith", testPoco.Name); #fails
}
}
}
Although I could use Faker to generate random data for the constructor I would like to be able to use this fake instance to generate alternative versions, for instance, with a fixed Name as exampled by the second test above.
Why is this not working and are there any known workarounds?
Note: this is not the same as the question How can I use Bogus with private setters
It is possible but I'd advise against it because the solution relies on .NET's Reflection.
There's a new Faker<T>(binder:...) binder constructor parameter. The IBinder interface is what Faker<T> uses to reflect over T to discover properties and fields that are settable. Whatever IBinder.GetMembers(Type t) returns is what Faker<> sees in T.
With this information, let's look at how the compiler generates an object with a public parameterized constructor and read-only property:
public class Foo
{
public Foo(string name){
this.Name = name;
}
public string Name { get; }
}
The C# compiler generates:
public class Foo
{
// Fields
[CompilerGenerated, DebuggerBrowsable((DebuggerBrowsableState) DebuggerBrowsableState.Never)]
private readonly string <Name>k__BackingField;
// Methods
public Foo(string name)
{
this.<Name>k__BackingField = name;
}
// Properties
public string Name => this.<Name>k__BackingField;
}
The storage for the Foo.Name property uses a backing field called Foo.<Name>k__BackingField. This backing field is what we need IBinder to hoist into Faker<>. The following BackingFieldBinder : IBinder does this:
public class BackingFieldBinder : IBinder
{
public Dictionary<string, MemberInfo> GetMembers(Type t)
{
var availableFieldsForFakerT = new Dictionary<string, MemberInfo>();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var allMembers = t.GetMembers(bindingFlags);
var allBackingFields = allMembers
.OfType<FieldInfo>()
.Where(fi => fi.IsPrivate && fi.IsInitOnly)
.Where(fi => fi.Name.EndsWith("__BackingField"))
.ToList();
foreach( var backingField in allBackingFields){
var fieldName = backingField.Name.Substring(1).Replace(">k__BackingField","");
availableFieldsForFakerT.Add(fieldName, backingField);
}
return availableFieldsForFakerT;
}
}
Customize the GetMembers() method above to suit your needs. You'll need to change the code if you want to include public fields or properties of T too.
The last problem we have to solve is creating an object without specifying constructor arguments. We can do this by using .GetUninitializedObject() from FormatterServices or RuntimeHelpers. To do this, we'll create an extension method that extends the Faker<T> API as shown below:
public static class MyExtensionsForFakerT
{
public static Faker<T> SkipConstructor<T>(this Faker<T> fakerOfT) where T : class
{
return fakerOfT.CustomInstantiator( _ => FormatterServices.GetUninitializedObject(typeof(T)) as T);
}
}
With these two components in place, we can finally write the following code:
void Main()
{
var backingFieldBinder = new BackingFieldBinder();
var fooFaker = new Faker<Foo>(binder: backingFieldBinder)
.SkipConstructor()
.RuleFor(f => f.Name, f => f.Name.FullName());
var foo = fooFaker.Generate();
foo.Dump();
}
public class Foo
{
public Foo(string name)
{
this.Name = name;
}
public string Name {get;}
}
You can find a full working example here. Additionally, you may find other solutions in Issue 213 helpful.
I just tried this and it seems to work :
class FakeClassWithInitialization : Faker<ClassWithInitialization>
{
private FakeClassWithInitialization() { }
public static FakeClassWithInitialization CreateDefault()
{
return (FakeClassWithInitialization) new FakeClassWithInitialization()
.CustomInstantiator(f => new ClassWithInitialization(f.Person.FullName));
}
}
I used directly the class constructor with the generator instead of using the generator with the property.
I also remove the WithName method that was not used.
Edit : Seems I misunderstood the question.
I don't know much about Bogus. I thought you could use optional parameters in "CreateDefault" method but you told DTO was complex so... There will be too much parameters.
I think you can achieve what you want with the builder pattern :
public class Builder
{
private string _name;
public Builder WithName(string name)
{
_name = name;
return this;
}
public ClassWithInitialization Build()
{
return new Faker<ClassWithInitialization>()
.CustomInstantiator(f =>
new ClassWithInitialization(
_name ?? f.Person.FullName
))
.Generate();
}
}
var faker = new Builder().WithName("Hello").Build();
var faker2 = new Builder().Build();
You can delete the FakeClassWithInitialization and replace it with a classic "Builder".
Setup:
public class Data
{
public int A { get; set; }
public int B { get; set; }
}
public class Runner
{
public static void Run(Data data)
{
data.A = data.B;
data.A = 1;
}
}
class Program
{
static void Main(string[] args)
{
var data = new Data() { A = 1, B = 2 };
Runner.Run(data);
}
}
Problem: I need to implement change tracking here for property names not values. Inside Runner.Run on the first line data.A = data.B I need to record somehow that "A" was set to "B" (literally property names) and then on the next line data.A = 1 I need to record that "A" was set to constant and say forget about it.
Constrains:
When setting one property to another (e.g. A = B) that needs to be recorded
When setting property to anything else (e.g. A = 1 or A = B * 2) this change needs to be forgotten (e.g. remember A only)
Suppose this is the tracker contract being used:
void RecordChange(string setterName, string getterName);
void UnTrackChange(string setterName);
Question:
I would like to somehow proxy the Data class so it still can be used in the interface code (e.g. Runner - is a whole bunch of a business logic code that uses Data) INCLUDING strong-typing and it can track it's changes without modifying the code (e.g. there is lots of places like 'data.A = data.B').
Is there any way to do it without resorting to I guess some magic involving IL generation?
Already investigated/tried:
PostSharp interceptors/Castle.DynamicProxy with interceptors - these alone cannot help. The most I can get out of it is to have a value of data.B inside setter interceptor but not nameof(data.B).
Compiler services - haven't found anything suitable here - getting the name of caller doesn't really help.
Runtine code generation - smth like proxy inherited from DynamicObject or using Relfection.Emit (TypeBuilder probably) - I lose typings.
Current solution:
Use the Tracker implementation of the abovementioned contract and pass it around into every function down the road. Then instead of writing data.A = data.B use method tracker.SetFrom(x => x.A, x => x.B) - tracker holds a Data instance and so this works. BUT in a real codebase it is easy to miss something and it just makes it way less readable.
It is the closest the solution I've come up with. It isn't perfect as I still need to modify all the contracts/methods in the client code to use a new data model but at least all the logic stays the same.
So I'm open for other answers.
Here's the renewed Data model:
public readonly struct NamedProperty<TValue>
{
public NamedProperty(string name, TValue value)
{
Name = name;
Value = value;
}
public string Name { get; }
public TValue Value { get; }
public static implicit operator TValue (NamedProperty<TValue> obj)
=> obj.Value;
public static implicit operator NamedProperty<TValue>(TValue value)
=> new NamedProperty<TValue>(null, value);
}
public interface ISelfTracker<T>
where T : class, ISelfTracker<T>
{
Tracker<T> Tracker { get; set; }
}
public class NamedData : ISelfTracker<NamedData>
{
public virtual NamedProperty<int> A { get; set; }
public virtual NamedProperty<int> B { get; set; }
public Tracker<NamedData> Tracker { get; set; }
}
Basically I've copy-pasted the original Data model but changed all its properties to be aware of their names.
Then the tracker itself:
public class Tracker<T>
where T : class, ISelfTracker<T>
{
public T Instance { get; }
public T Proxy { get; }
public Tracker(T instance)
{
Instance = instance;
Proxy = new ProxyGenerator().CreateClassProxyWithTarget<T>(Instance, new TrackingNamedProxyInterceptor<T>(this));
Proxy.Tracker = this;
}
public void RecordChange(string setterName, string getterName)
{
}
public void UnTrackChange(string setterName)
{
}
}
The interceptor for Castle.DynamicProxy:
public class TrackingNamedProxyInterceptor<T> : IInterceptor
where T : class, ISelfTracker<T>
{
private const string SetterPrefix = "set_";
private const string GetterPrefix = "get_";
private readonly Tracker<T> _tracker;
public TrackingNamedProxyInterceptor(Tracker<T> proxy)
{
_tracker = proxy;
}
public void Intercept(IInvocation invocation)
{
if (IsSetMethod(invocation.Method))
{
string propertyName = GetPropertyName(invocation.Method);
dynamic value = invocation.Arguments[0];
var propertyType = value.GetType();
if (IsOfGenericType(propertyType, typeof(NamedProperty<>)))
{
if (value.Name == null)
{
_tracker.UnTrackChange(propertyName);
}
else
{
_tracker.RecordChange(propertyName, value.Name);
}
var args = new[] { propertyName, value.Value };
invocation.Arguments[0] = Activator.CreateInstance(propertyType, args);
}
}
invocation.Proceed();
}
private string GetPropertyName(MethodInfo method)
=> method.Name.Replace(SetterPrefix, string.Empty).Replace(GetterPrefix, string.Empty);
private bool IsSetMethod(MethodInfo method)
=> method.IsSpecialName && method.Name.StartsWith(SetterPrefix);
private bool IsOfGenericType(Type type, Type openGenericType)
=> type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType;
}
And the modified entry point:
static void Main(string[] args)
{
var data = new Data() { A = 1, B = 2 };
NamedData namedData = Map(data);
var proxy = new Tracker<NamedData>(namedData).Proxy;
Runner.Run(proxy);
Console.ReadLine();
}
The Map() function actually maps Data to NamedData filling in property names.
I have an abstract class for POs.
public abstract class PO
{
public abstract Dictionary<string, object> InvalidFields { get; }
public abstract string PONumber { get; set; }
}
Which is inherited by two different types of PO: CPO and POR.
public class CPO
{
private string poNumber;
public override Dictionary<string, object> InvalidFields => new Dictionary<string, object>();
public override string PONumber
{
get
{
return poNumber;
}
set
{
if (!ValidatePONumber(value)) InvalidFields.Add("CPO Number", value);
poNumber = value;
}
}
}
When ValidatePONumber(value) returns false, it correctly executes InvalidFields.Add() but the dictionary is never actually added to. In the Locals window, a new variable is created named Namespace.PO.InvalidFields.get returned and I can see the new key that is added. However, this.InvalidFields has no values.
So it looks like the dict in the abstract base class PO is being created and added to instead of the derived class CPO.
Thanks to Klaus for pointing out the issue. I resolved by setting up a private invalidFields field and returning that with InvalidFields.get because what I was doing was returning a new Dictionary class with every get.
public class CPO : PO
{
private string poNumber;
private Dictionary<string, object> invalidFields = new Dictionary<string, object>();
public override Dictionary<string, object> InvalidFields { get => invalidFields; }
public override PONumber
{
get
{
return poNumber;
}
set
{
if (!ValidatePONumber(value)) InvalidFields.Add("CPO Number", value);
poNumber = value;
}
}
}
The code as you have given above does not even compile. You derived class is not actually inheriting from the abstract class (I am assuming it is a typo). Even after addition abstract class, the dictionary instantiation throws an error because it is a property definition and missing 'set' definition (InvalidFields is missing 'set' accessor definition.
Also, this sounds to be a duplicate of Overriding fields or properties in subclasses
I have tried this with minor modifications, please see the code below. Like #yransis says in comments, The InvalidFields is being treated as a 'get' property, returning new instance of dictionary every time it is executing the 'Add' step.
class DerivedCpo : AbstractPo
{
private string poNumber;
private Dictionary<string, object> invalidFieldsBackingField;
public override Dictionary<string, object> InvalidFields
{
get
{
return invalidFieldsBackingField;
}
set { this.invalidFieldsBackingField = value; }
}
public DerivedCpo()
{
this.InvalidFields = new Dictionary<string, object>();
}
public override string PoNumber
{
get { return poNumber;}
set
{
if(!ValidatePONumber((value)))
InvalidFields.Add("CPO Number", value);
poNumber = value;
}
}
private bool ValidatePONumber(string value)
{
if (string.IsNullOrEmpty(value))
{
return false;
}
if (value.Length > 5)
return false;
return true;
}
}
Main method calling this class -
static void Main(string[] args)
{
var po = new DerivedCpo();
po.PoNumber = "test1";
po.PoNumber = "thisistheinvalidfieldpo";
if (po.InvalidFields != null && po.InvalidFields.Count > 0)
{
Console.WriteLine(#"There are {0} fields in invalidFields collection", Convert.ToString(po.InvalidFields.Count) );
}
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
In the code above I have added explicit backing field. It is being instantiated only once in the constructor. That way the contents of InvalidFields are persistent. In your code, you are returning a new instance of dictionary, essentially losing the data that was stored between two 'get' calls.
Question
How do I define an incoming Type T constraint that will allow me to call a static method on the class (of type T) to get the intended IndexModel object for passing to Mongo?
Background
I'm currently trying to write a Mongo Provider class that will allow me to ensure my particular database and collection are present before doing any operations with them, since there is a potential that the container or server it resides in could be destroyed and recreated at any time, and I'd prefer to have a safe way in code to ensure that the external dependency is there (instance is beyond my control, so I have to trust that something is there).
One of the things I'm trying to do, since I've managed to do what I stated above for Database and Collection instantiation, is to also generate indexes. My idea was to have a static method on the classes that would return their specific definition of an index model. This way, each class would be responsible for their own Mongo indexes, rather than some convoluted switch-case statement in my Provider based on the incoming type of T.
My first idea was to have an interface that shared this method, but Interfaces don't allow you to declare a static method. Similarly, I tried an Abstract Base-class and found that the static implementation would call the base class that defined the method, rather than any overrides in an inheritor.
Sample Code
public class MyClass
{
public DateTime DateValue { get; set; }
public int GroupId { get; set; }
public string DataType { get; set; }
public static IEnumerable<CreateIndexModel<MyClass>> GetIndexModel(IndexKeysDefinitionBuilder<MyClass> builder)
{
yield return new CreateIndexModel<MyClass>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
}
Edit
I guess I should probably include a shell of my Mongo Provider class. See below:
Edit #2 due to questions about how this hasn't solved my problem, I'm updating the MongoProvider to have the problematic code. Note: Once this method is included, the class will no longer compile, since it isn't possible given what I've done thus far.
public class MongoProvider
{
private readonly IMongoClient _client;
private MongoPrivder(ILookup<string, string> lookup, IMongoClient client)
{
_client = client;
foreach(var database in lookup)
foreach(var collection in database)
Initialize(database.Key, collection);
}
public MongoProvider(IConfiguration config) :this(config.GetMongoObjects(), config.GetMongoClient())
{}
public MongoProvider(IConfiguration config, IMongoClient client) : this(config.GetMongoObjects(), client)
{}
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
// The Problem
private void InitializeIndex<T>(string database, string collection)
{
IEnumerable<CreateIndexModel<T>> models;
switch (T)
{
case MyClass:
model = MyClass.GetIndexModel();
break;
default:
break;
}
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
}
Edit #3
As a stop-gap, I've gone ahead and done something terrible (not sure if it's going to work yet), and I'll supply the example so you can know my best solution thus far.
public static class Extensions
{
#region Object Methods
public static T TryCallMethod<T>(this object obj, string methodName, params object[] args) where T : class
{
var method = obj.GetType().GetMethod(methodName);
if (method != null)
{
return method.Invoke(obj, args) as T;
}
return default;
}
#endregion
}
This allows me to do the following (inside of MongoProvider)
private async void InitializeIndex<T>(string database, string collection) where T : new()
{
var models = new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel");
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
Since it doesn't look like I'm going to get an answer to this, I figured I would provide my solution for future searches of this question. Basically, I added an extension method to the base object class, and used reflection to determine if the method I was looking for was there. From there, I returned a value of true or false, depending on if the method was found, and output the return value to a parameter, in the traditional TryGet pattern.
Note to Future Readers
I do not recommend this approach. This is just how I solved my problem for accessing a method on a type of T. Ideally, an instance method would be implemented, and a signature defined in a common Interface, but that wasn't going to work for my use case.
My Answer
public static class Extensions
{
#region Object Methods
public static bool TryCallMethod<T>(this object obj, string methodName, out T result, params object[] args) where T : class
{
result = null;
var method = obj.GetType().GetMethod(methodName);
if (method == null)
return false;
result = method.Invoke(obj, args) as T;
return true;
}
#endregion
}
My data class looks like this (obfuscated from actual usage)
[BsonDiscriminator("data")]
public class DataClass
{
#region Private Fields
private const string MongoCollectionName = "Data";
#endregion
#region Public Properties
public string CollectionName => MongoCollectionName;
[BsonId]
public ObjectId Id { get; set; }
[BsonElement("date_value")]
public DateTime DateValue { get; set; }
[BsonElement("group_id")]
public int GroupId { get; set; }
[BsonElement("data_type")]
public string DataType { get; set; }
[BsonElement("summary_count")]
public long SummaryCount { get; set; }
[BsonElement("flagged_count")]
public long FlaggedCount { get; set; }
[BsonElement("error_count")]
public long ErrorCount { get; set; }
#endregion
#region Constructor
public DataClass()
{
}
public DataClass(int groupId, string dataType = null, long summaryCount = 0, long flaggedCount = 0, long errorCount = 0)
{
Id = ObjectId.GenerateNewId();
DateValue = DateTime.UtcNow;
GroupId = groupId;
DocCount = summaryCount;
DataType = dataType ?? "default_name";
FlaggedCount = flaggedCount;
ErrorCount = errorCount;
}
#endregion
#region Public Methods
public static IEnumerable<CreateIndexModel<AuditEntry>> GetIndexModel(IndexKeysDefinitionBuilder<AuditEntry> builder)
{
yield return new CreateIndexModel<AuditEntry>(
builder.Combine(
builder.Descending(entry => entry.DateValue),
builder.Ascending(entry => entry.GroupId),
builder.Ascending(entry => entry.DataType)
)
);
}
#endregion
}
I would then call the method in the following fashion, inside my MongoProvider class. The ellipses are present to identify that more code exists within the class.
public class MongoProvider : IMongoProvider
{
#region Private Fields
private readonly IMongoClient _client;
#endregion
#region Constructor
...
#endregion
#region Private Methods
private void Initialize(string database, string collection)
{
var db = _client.GetDatabase(database);
if (!db.ListCollectionNames().ToList().Any(name => name.Equals(collection)))
db.CreateCollection(collection);
}
private async Task InitializeIndex<T>(string database, string collection) where T : new()
{
if(new T().TryCallMethod<IEnumerable<CreateIndexModel<T>>>("GetIndexModel", out var models, new IndexKeysDefinitionBuilder<T>()))
await _client.GetDatabase(database)
.GetCollection<T>(collection)
.Indexes
.CreateManyAsync(models);
}
private static void ValidateOptions<T>(ref FindOptions<T, T> options)
{
if(options != null)
return;
options = new FindOptions<T, T>
{
AllowPartialResults = null,
BatchSize = null,
Collation = null,
Comment = "AspNetWebService",
CursorType = CursorType.NonTailable,
MaxAwaitTime = TimeSpan.FromSeconds(10),
MaxTime = TimeSpan.FromSeconds(10),
Modifiers = null,
NoCursorTimeout = false,
OplogReplay = null
};
}
private static FilterDefinition<T> GetFilterDefinition<T>(Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders)
{
if(builders.Length == 0)
builders = new Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] {b => b.Empty};
return new FilterDefinitionBuilder<T>()
.And(builders
.Select(b => b(new FilterDefinitionBuilder<T>()))
);
}
#endregion
#region Public Methods
public async Task<IReadOnlyCollection<T>> SelectManyAsync<T>(string database, string collection, FindOptions<T, T> options = null, params Func<FilterDefinitionBuilder<T>, FilterDefinition<T>>[] builders) where T : new()
{
ValidateOptions(ref options);
await InitializeIndex<T>(database, collection);
var filter = GetFilterDefinition(builders);
var find = await _client.GetDatabase(database)
.GetCollection<T>(collection)
.FindAsync(filter, options);
return await find.ToListAsync();
}
...
#endregion
}
Ok, this is a tough one.
Introduction: My idea is to attach an instanciated QueryBuilder class which I wrote, to a PropertyGrid. The QueryBuilder class now contains a couple of fields, which are hardcoded like in the example below. Thus allowing a user to specify, which fields should be used in a query in what way (sorted, grouped, and so on). After the user having specified all the settings to these properties (by code or via the PropertyGrid GUI), the QueryBuilder is able to produce a query. Everything is working fine like that. Pseudo code:
class QueryBuilder {
public QBField name {get; set;}
public QBField prename {get; set;}
public QBField zip {get; set;}
// ...
public void QueryBuilder() {
name = new QBField();
prename = new QBField();
// ...
}
public getQuery() {
// logic to build the query
}
}
class QBField {
public bool shown {get; set;}
public bool sortby {get; set;}
public bool groupby {get; set;}
}
Challenge: Now instead of hardcoding each field as public properties in the QueryBuilder class, I was wondering how I could use i.e. a List<string> containing all my fields to "populate" my instanciated QueryBuilder with these properties.
So this leads to three questions:
Could this be accomplished by somehow overriding GetProperties() of the Type of the QueryBuilder class, and if yes, how is it best done?
How can I then iterate through all of these at runtime generated QBField properties and instanciate them? Idea: PropertyDescriptors and Activators?
How can I iterate through all of these properties to read the values of each QBField object? The problem I ran in was, that when reading the Properties of QBField with reflection and trying getValue(obj, null), of course the first parameter needed is an object, which I do not know since I have lots of these QBField objects. Perhaps putting all my QBFields into a List<QBField> and iterating through it? Would that work in this example?
I'm just a bit lost but I feel that I'm very close to the solution. Therefore any help or just pointers in the right direction are most greatly appreciated!
PropertyGrid can be influenced via TypeConverter, ICustomTypeDescriptor and/or TypeDescriptionProvider. Of these, TypeConverter is the simplest, by overriding GetProperties (and mark it as supported).
In any event, you will also need to write a PropertyDescriptor implementation that knows how to take a field and an object, and get/set the value, i.e.
public override void SetValue(object component, object value) {
((YourType)component)[fieldNameSetInConstructor] = value;
}
Here's a basic property bag that exposes everything as string; obviously as you extend this (different property types, change-notification, etc) it gets more complex very quickly. Note also that this TypeConverter approach only works for PropertyGrid; for DataGridView etc you'll need either ICustomTypeDescriptor or TypeDescriptionProvider. For collections you'll need ITypedList. And there are about 20 other interfaces around the edges for specific scenarios. But you get the point ;p The key thing is that our PropertyDescriptor acts as the translation between your actual model (the dictionary in my case), and the model you expose to TypeDescriptor (the fake properties per key).
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var bag = new BasicPropertyBag { Properties = {
new MetaProp("Name", typeof(string)),
new MetaProp("Description", typeof(string)),
new MetaProp("DateOfBirth", typeof(DateTime)
, new CategoryAttribute("Personal"), new DisplayNameAttribute("Date Of Birth"))
} };
bag["Name"] = "foo";
bag["DateOfBirth"] = DateTime.Today;
Application.Run(new Form { Controls = { new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = bag } } });
}
}
public class MetaProp
{
public MetaProp(string name, Type type, params Attribute[] attributes)
{
this.Name = name;
this.Type = type;
if (attributes != null)
{
Attributes = new Attribute[attributes.Length];
attributes.CopyTo(Attributes, 0);
}
}
public string Name { get; private set; }
public Type Type { get; private set; }
public Attribute[] Attributes { get; private set; }
}
[TypeConverter(typeof(BasicPropertyBagConverter))]
class BasicPropertyBag
{
private readonly List<MetaProp> properties = new List<MetaProp>();
public List<MetaProp> Properties { get { return properties; } }
private readonly Dictionary<string, object> values = new Dictionary<string, object>();
public object this[string key]
{
get { object value; return values.TryGetValue(key, out value) ? value : null; }
set { if (value == null) values.Remove(key); else values[key] = value; }
}
class BasicPropertyBagConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptor[] metaProps = (from prop in ((BasicPropertyBag)value).Properties
select new PropertyBagDescriptor(prop.Name, prop.Type, prop.Attributes)).ToArray();
return new PropertyDescriptorCollection(metaProps);
}
}
class PropertyBagDescriptor : PropertyDescriptor
{
private readonly Type type;
public PropertyBagDescriptor(string name, Type type, Attribute[] attributes)
: base(name, attributes) {
this.type = type;
}
public override Type PropertyType { get { return type; } }
public override object GetValue(object component) { return ((BasicPropertyBag)component)[Name]; }
public override void SetValue(object component, object value) { ((BasicPropertyBag)component)[Name] = (string)value; }
public override bool ShouldSerializeValue(object component) { return GetValue(component) != null; }
public override bool CanResetValue(object component) { return true; }
public override void ResetValue(object component) { SetValue(component, null); }
public override bool IsReadOnly { get { return false; } }
public override Type ComponentType { get { return typeof(BasicPropertyBag); } }
}
}