I have a problem regarding anonymous objects in C#. The situation is as follows:
I have a C# web app which does NOT use the traditional ASP.NET Razor engine, but instead uses the RazorEngine open source project (https://github.com/Antaris/RazorEngine). I'm not sure if this is relevant, but it could be.
I'm passing a model object to each page I'm displaying. Each model object is different, there are many pages and therefore many different model objects, but I would rather not have to declare separate classes for each model, which is why I've been using anonymous classes:
// In method which displays page A:
var model = new {
Lang = _lang,
PropX = "foo",
PropY = "bar"
};
RazorEngine.Run("templateA", model);
// In a different method which displays page B:
var model = new {
Lang = _lang,
PropZ = "smu"
};
RazorEngine.Run("templateB", model);
You may notice that botn (and in fact, all) those models have a common property (the "Lang" property), a few common properties actually (Lang is the only one displayed in the example above to simplify matters).
My main problem is that I'm trying to ensure that those properties are added to all the models in a way which guarantees that they are included to all pages, and if I later decide to add a new common property, then I can do that in a single place.
One way would of course be to drop the anonymous classes, and use typed classes which all inherit from a single base class, which would declare the common properties. But this would be a lot of boilerplate code, and if there is another solution then I would prefer that.
Another solution would be to either declare the common properties in a sub-property of the model object, or declare the individual page properties in a sub object:
// Either like this:
var model = new {
Common = GetCommonModelProperties(),
PropX = "foo",
PropY = "bar"
};
public object GetCommonModelProperties()
{
return new {
Lang = _lang
};
}
// etc.
// or like this:
var pageModel = new {
PropX = "foo",
PropY = "bar
};
var model = CreateModel(pageModel);
RazorEngine.Run("templateA", model);
// where CreateModel could be implemented like this:
public object CreateModel(object pageModel)
{
return new
{
Lang = _lang,
// etc., whatever common properties there exist
Data = pageModel
};
}
The problem with this approach is that I would have to modify all my templates, either all instances where those pages refer to the common property (I would have to rename all Model.Lang instances to Model.Common.Lang), or to the individual page data (modify Model.AnyProperty to Model.Data.AnyProperty). Of course there is a great risk of errors when such a rewrite takes place.
So: is there a way to create an anonymous object, where a number of its properties are always the same, but the rest can be specified dynamically?
I've tried to create two separate objects, and then combine them into one, using code from this question: Merging anonymous types
var commonModel = new {
Lang = _lang
};
var pageModel = new {
PropX = "foo",
PropY = "bar"
};
var model = Merge(commonModel, pageModel);
and yes, this works. Until I have to use the Lang object, which is of a class type (which I have full control over), and this class overloads operator[]. If I use this workaround, the overload stops working, and I get the error:
Cannot apply indexing with [] to an expression of type 'object'
N.b. the indexing works perfectly fine if I just include the Lang property in a regular anonymous object.
I've also tried to create a separate base class for all the models, declare all the common properties in that class, but also derive it from System.Dynamic.DynamicObject, and override the TryGetMember method which would dynamically look up the page properties from a dictionary (which would work, since those properties are usually simple objects, i.e. they don't override the indexing operator, so I can add those properties dynamically at runtime:
var pageModel = new {
PropX = "foo",
PropY = "bar"
};
var model = CreateMainModel(pageModel);
public object CreateMainModel(object pageModel)
{
var mainModel = new BaseModel()
mainModel.Lang = _lang;
foreach (System.Reflection.PropertyInfo fi in pageModel.GetType().GetProperties())
{
mainModel.PageProperties[fi.Name] = fi.GetValue(pageModel, null);
}
return mainModel;
}
class BaseModel : DynamicObject
{
public LanguageMap Lang { get; set; }
public Dictionary<string, object> PageProperties { get; set; }
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (PageProperties.ContainsKey(binder.Name))
{
result = PageProperties[binder.Name];
return true;
}
return false;
}
}
The problem with this is that properties declared inside a class which derives from DynamicObject will NOT be visible inside the page templates, it seems that only properties returned from TryGetMember are. And if I make the members visible the explicity checking for their names inside TryGetMember, then the indexing stops working just like in the case above.
Now if this was C++, I could create a preprocessor macro:
#define COMMON_MODEL_PROPERTIES \
Lang = _lang
More = _otherProperty
// Where models are declared:
var model = new
{
COMMON_MODEL_PROPERTIES,
PropX = "foo",
PropY = "bar"
}
but this isn't C++... It's C#.
Any ideas?