`Dictionary<string, object>` or `dynamic` model is not supported by layout views
See original GitHub issueDescribe the bug
When the context
parameter contains a model of type Dictionary<string, object>
or dynamic
, expressions in a layout view cannot be bound to it.
This seems to be because the way context is passed to layout views is by wrapping the output of the ‘main’ view and the original context into a DynamicViewModel
. Unfortunately, DynamicViewModel
only supports reflection member access.
This conflicts with the guidance in the documentation that, for the best performance, models should be dictionaries.
Expected behavior:
When the value of the context
parameter of a view is a Dictionary<string, object>
or a dynamic
, I expect that expressions in the layout view are able to resolve values as they do in regular views.
In more detail: I expect that DynamicViewModel
is capable of handling regular dictionaries and dynamic values, as well as POCOs.
Test to reproduce
[Fact]
public void CanUseDictionaryModelInLayout()
{
var files = new FakeFileSystem()
{
{ "views\\layout.hbs", "Layout: {{property}}\r\n{{{body}}}" },
{ "views\\someview.hbs", "{{!< layout}}\r\n\r\nBody: {{property}}" },
};
var handlebarsConfiguration = new HandlebarsConfiguration() { FileSystem = files };
var handlebars = Handlebars.Create(handlebarsConfiguration);
var render = handlebars.CompileView("views\\someview.hbs");
var output = render(
new Dictionary<string, object>
{
{ "property", "Foo" },
}
);
Assert.Equal("Layout: Foo\r\n\r\nBody: Foo", output);
// actual value is "Layout: \r\n\r\nBody: Foo"
}
[Fact]
public void CanUseDynamicModelInLayout()
{
var files = new FakeFileSystem()
{
{ "views\\layout.hbs", "Layout: {{property}}\r\n{{{body}}}" },
{ "views\\someview.hbs", "{{!< layout}}\r\n\r\nBody: {{property}}" },
};
dynamic model = new MyDynamicModel();
var handlebarsConfiguration = new HandlebarsConfiguration() { FileSystem = files };
var handlebars = Handlebars.Create(handlebarsConfiguration);
var render = handlebars.CompileView("views\\someview.hbs");
var output = render(
model
);
Assert.Equal("Layout: Foo\r\n\r\nBody: Foo", output);
// actual value is "Layout: \r\n\r\nBody: Foo"
}
private class MyDynamicModel: DynamicObject
{
private readonly Dictionary<string, object> _properties =
new Dictionary<string, object>
{
{ "property", "Foo" },
};
public override IEnumerable<string> GetDynamicMemberNames() => _properties.Keys;
public override bool TryGetMember(GetMemberBinder binder, out object result) =>
_properties.TryGetValue(binder.Name, out result);
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:9 (9 by maintainers)
@zjklee I actually liked the idea of creating a new type, which can be much more focused and less flexible. Like this
In the member accessor we intercept the
body
property, and forward the rest to theObjectDescriptor
for_value
, and we can do the same forGetProperties
(where we amendbody
) and forIterator
which we just forward altogether.Let me know what you think.
@heemskerkerik Yep, I like your suggestion and looking forward to see your PR 😉