Emitting an HTML string from anonymous type proper

2019-02-28 04:45发布

I am passing an anonymous type into a dynamic partial view as part of the @model, and one of the properties is a string that contains some HTML. When I use the HtmlHelper methods to render the property, the Razor engine is encoding the string, resulting in literal text on the page - <i>text</i> in this case, instead of the desired text.

Since it is a dynamically typed View, I cannot call the property directly. Specifically, if I try to bind to @Model.MyField, I get a RuntimeBindingException:

'object' does not contain a definition for 'MyField'

Ideally I could create a type (or at least an interface) to specify for the view (which I recommend as the optimal solution), but my scope of work does not permit this. Plus I'm using the Partial View in the first place so that I can recycle the template for different types, which have the same property names but not the same type for those properties (yay legacy code!).

I have looked at several related questions that address similar issues, but the answers do not work for my specific situation (due to needing the anonymous type passed to my @model dynamic view)).

  • Displaying HTML String from Model in MVC Razor View

    • Lists several failed approaches, settles on creating an IHtmlString via @(new HtmlString(stringWithMarkup)) or MvcHtmlString.Create(stringWithMarkup)
    • Both of which require a known type or local variable, and don't work for binding a property of an anonymous object
  • ASP.NET MVC4 - display HTML containing string as raw HTML

    • Accepted answer helps explains what's happening:

      All the output from helpers and other elements in Razor are put through HttpUtility.HtmlEncode, unless they implement IHtmlString.

Attempted solutions

  1. Okay, so I assume I'll just swap my String properties out for one of those IHtmlString properties... nope. Since I have anonymous type, the Razor engine doesn't know that MyField is an IHtmlString, and (I assume) calls .ToString(), which is then encoded as usual.

  2. Alright, maybe @Html.DisplayFor is smarter? Yes, but access is denied:

    'System.Web.Mvc.HtmlHelper' has no applicable method named 'DisplayFor' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

    Oh, right. Dynamically dispatched - I can't call extension methods on anonymous methods, because Razor doesn't know what they are. Because I'm using @model dynamic to explicitly tell it, Jedi-style, "You don't need to see its identification". If I always knew what type it was, then yes, I could cast the object or call the extension method without using the syntax - but again, dynamic and anonymous. Sort of a chicken and egg issue here.

1条回答
混吃等死
2楼-- · 2019-02-28 05:13

I found / compiled two solutions, neither of which am I really happy with. YMMV:

  1. Set ViewBag.MyField in the parent View before rendering each Partial View.

    Okay, I should have figured this one out a lot sooner, but had to get reminded of this possibility here because I rarely use it (preferring strongly-typed views whenever possible). I actually did try this early on, but due to the way I'm rendering the Partial multiple times it didn't seem to be appropriate. I actually still don't like it because in the parent view, I have to keep updating ViewBag.MyField before every call to @Html.Partial (6 times for my use case). This puts C# code and variable reuse way down the page in the middle of my content, where it is easy to miss and hard to maintain.

  2. Use reflection: object myField = ((Type)Model.GetType()).GetProperty("MyField").GetValue(Model);

    This is ultimately what I decided to go with for my use case. Even though reflection wasn't intended for this scenario, even though it requires a little extra error checking. The people who will be maintaining this are more familiar with reflection than .NET MVC, and it consolidates the code into one spot - on the page that it matters to, and at the top with the rest of the "server-side" manipulations. No repeated calls or hunting down references.

    I'm actually not entirely clear on why this works (also works with a dynamic instead of object), but I'm assuming it's something to do with the Razor engine is inspecting the myField object type directly for special rendering of a known type (IHtmlString), rather than seeing an unknown object and needing to access a property that is not known to exist at compile time.

查看更多
登录 后发表回答