Mvc: Is @Html.Raw(...) working? ... or am I just a nOOb?

Created on 16 Apr 2015  路  25Comments  路  Source: aspnet/Mvc

Trying to use @Html.Raw(@ViewBag.xxx) in a view to inject some raw HTML into the view, but I'm receiving: RuntimeBinderException: 'Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<object>' does not contain a definition for 'Raw'

I'm new to MVC, so I'm not surprised if I'm just doing something wrong.

3 - Done bug

All 25 comments

should work fine. suspect the issue is the second @. try @Html.Raw(ViewBag.xxx) instead.

Thanks. I changed it, but I found out that ...

@Html.Raw("<meta name=\"xx\" content=\"xx\">")

works, but

@Html.Raw(ViewBag.Description)

throws the RuntimeBinderException even with that modification.

The string for that is simply in the top of the rendered body view:

@{
ViewBag.Description = "<meta name=\"description\" content=\"Description here\">";
}

Clearly, I'm learning MVC and not doing this right; however, IHtmlHelper<object> does contain a def for Raw, so getting that exception here seems incorrect regardless of how bad my coding is.

Ok, so I get it now that

<meta name="description" content="@ViewBag.Description">

in the shared view with

@{
ViewBag.Description = "Description here";
}

works. It still seems strange that I can't just inject the entire HTML tag into the view. I'll leave this open while my concern about that RuntimeBinderException being thrown for what I was doing is the correct exception ... and even perhaps to find out why @Html.Raw(ViewBag.Description) fails.

Does casting ViewBag.Description to a string works when passing it to the @Html.Raw() method? Perhaps something goes wrong with the dynamic type in the Raw method, as the RuntimeBinderException suggests.

Something like:

@Html.Raw(ViewBag.Description as string))

This is likely a bug:

@{ 
    ViewBag.Description = "Hey!";
}

<p>@ViewBag.Description</p> // works
<p>Raw: @Html.Raw(ViewBag.Description)</p> //throws modelbinder exception
<p>Raw: @Html.Raw(ViewBag.Description as string)</p> // works

This does work as expected with MVC 5.

interesting. there is absolutely nothing special about the @Html.Raw() methods and the ModelBinderException comes from much lower in the stack. could one of you with a test project please try it out with different DNX variants -- x86 / x64 and CLR / Core CLR?

also, which MVC and DNX versions are you testing?

I was using CoreCLR: dnx-coreclr-x64-1.0.0-beta5-11556 (and MVC 6)

@dougbu I'll be back in the office in three hours (1pm CST). If nobody else checks DNX variants by then, I'll do it and report back.

Html.Raw used to be an instance method but is now an extension method. Extension methods unfortunately cannot be called with dynamic parameters, and everything returned by ViewBag is always dynamic. That's why casting works.

This is _technically_ by design but we could consider a different approach here perhaps, such as getting rid of IHtmlHelper.

@Eilon @Html.Raw() remains an instance method https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs#L524 casting probably works because the Raw(string) method is called instead of Raw(object) though why those are different remains unclear.

The original exception in my app was RuntimeBinderException: 'Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<object>' does not contain a definition for 'Raw', and that app doesn't have a model for the view I was working with.

When I went back and tested in my HelloMVC app, which in my version includes a @model User in the view, it threw: RuntimeBinderException: 'Microsoft.AspNet.Mvc.Rendering.IHtmlHelper<HelloMvc.Models.User>' ...

Sort of made me think that Viewbag (the object) is what @Html.Raw() choked on ... not ViewBag.xxx, which would be a, what?, a dynamic property of the ViewBag object?

... anyway, check this out ... When I try this @Html.Raw(ViewData["xxx"]) ... it works!!

Does this mean that @Html.Raw(Viewbag.xxx) isn't necessarily misinterpreting what it has been given? Is it just not resolving the dynamic object property value of Viewbag.xxx before performing its injection into the markup?

If an object is provided to @Html.Raw(), does it expect the passed object implement its own Raw definition? ... that would explain why it's saying the object doesn't have a Raw definition for ViewBag ... Viewbag the object ... it doesn't and shouldn't.

why those are different remains unclear.

@dougbu maybe this is because most cases you call raw with string and you avoid the null check ? I also don't think it's necessary.

I still don't know why the object one is throwing, this worked with dynamics in mvc5 with no problem.

@dougbu oh yeah weird I guess it's the same as it always was... so not sure what's going on here.

@dougbu @Eilon
Looks like it goes wrong because the Html property of the view uses the generic interface IHtmlHelper<TModel>. This worked in MVC5 because there is no IHtmlHelper interface.


Repro:
An 'html helper' with a Raw method:

public interface IMyHtmlHelper
{
    HtmlString Raw(object x);
}

public interface IMyHtmlHelper<T> : IMyHtmlHelper { }

public class MyHtmlHelper : IMyHtmlHelper
{
    public HtmlString Raw(object value)
    {
        return new HtmlString(value == null ? null : value.ToString());
    }
}

public class MyHtmlHelper<T> : MyHtmlHelper, IMyHtmlHelper<T> { }

With this view:

@{
    IMyHtmlHelper<object> MyHtml = new MyHtmlHelper<object>();
    ViewBag.Test = "<strong>Hello World!</strong>";
}
<p>
    @MyHtml.Raw(ViewBag.Test)
</p>

Causes the same RuntimeBinderException:

at CallSite.Target(Closure , CallSite , IMyHtmlHelper`1 , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at Asp.ASPV__Views_Home_Index_cshtml.<ExecuteAsync>d__13.MoveNext()

However, when I change this line:

IMyHtmlHelper<object> MyHtml = new MyHtmlHelper<object>();

to this:

MyHtmlHelper<object> MyHtml = new MyHtmlHelper<object>();

_(notice that the type of MyHtml is the actual class implementation MyHtmlHelper<object>)_

It _does_ work.

Using the non-generic type works too:

IMyHtmlHelper MyHtml = new MyHtmlHelper();

Should this be a new issue?

@GuardRex the behaviour seen here is as-expected when attempting to pass a dynamic value to an interface. The downside of that interface is a few cases involving dynamic such as the one you hit. (The upside is the ability to replace the implementation completely if you wish.)

You have a number of potential workarounds here, including the cast @henkmollema first mentioned. For example:

<p>Raw title: @Html.Raw((object)ViewBag.Title)</p>
<p>Raw title: @(((HtmlHelper)Html).Raw(ViewBag.Title))</p>
<p>Raw title: @Html.Raw(ViewData["Title"])</p>

Since ViewData (Dictionary) look-ups far out-perform ViewBag (dynamic) invocations, the last is probably the best choice.

BTW @Bartmax the specific casts, null checks, and overloads here are not that important. The relevant bit is just using IHtmlHelper versus calling a non-virtual method on the concrete HtmlHelper class in MVC 5.

@dougbu so there is a way to fix this and keep both advantages. By just changing IHtmlHelper to an abstract base class HtmlHelper with no implementations. You get the best of both worlds

@yishaigalatzer that approach might work in some cases but it isn't possible here. DefaultHtmlHelper<TModel> ends up inheriting from two base classes -- DefaultHtmlHelper and HtmlHelper<TModel>. Delegation to avoid that makes things very messy.

Clearing milestone and assignee in case triage wants to take another look at this.

:+1: just ran into this myself. using @Html.Raw((object)Bundles.vendor.styles) worked but it's not intuitive. Here's my sample project for reference: https://github.com/chmontgomery/ExampleMVC6Application

Possible options include:

  1. Add Encode() and Raw() methods to RazorPage.
  2. Push everything not already part of IHtmlGenerator into that interface, reduce HtmlHelper and HtmlHelper<TModel> to delegations to IHtmlGenerator, and remove IHtmlHelper and IHtmlHelper<TModel>.
  3. ...

A couple of new options in addition to avoiding or removing IHtmlHelper:

  1. Move the Encode() and Raw() methods from IHtmlHelper to IHtmlHelper<TModel>. We never inject properties of the lower-level interface but do use IHtmlHelper.Encode() in a couple of the default display and editor templates. Might need to finesse that slightly...
  2. Add new string Encode(string value); and so on to IHtmlHelper<TModel>. My testing shows this works fine.

tl;dr;
A slightly reduced repro is:

``` c#
class Program
{
public static void Main(string[] args)
{
dynamic joe = "fred";
IMyHtmlHelper2 myHtml = new MyHtmlHelper2();

        Console.WriteLine(myHtml.Raw(joe));
    }

    private interface IMyHtmlHelper
    {
        string Raw(object value);

        string Raw(string value);
    }

    private interface IMyHtmlHelper2 : IMyHtmlHelper
    {
    }

    private class MyHtmlHelper2 : IMyHtmlHelper2
    {
        public string Raw(object value)
        {
            return value == null ? null : value.ToString();
        }

        public string Raw(string value)
        {
            return value;
        }
    }
}

```

Note the problems occur after eliminating generics from the scenario and after removing inheritance from the implementing class. I've also run the above in .NET 4.5 and encountered the identical RuntimeBinderException.

Removed Design label and marked this as a Bug (because it's a regression compared to MVC 5).

8b5931d

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CezaryRynkowski picture CezaryRynkowski  路  4Comments

karthicksundararajan picture karthicksundararajan  路  4Comments

saf-itpro picture saf-itpro  路  3Comments

MariovanZeist picture MariovanZeist  路  4Comments

kspearrin picture kspearrin  路  4Comments