13

I have a razor helper method that needs to take in a Func<> that will return some HTML content to print out. This is what I originally had:

@helper node(string title, Func<HelperResult> descriptions)
{
    ....
    <div>@descriptions()</div>
    ....
}

@node("title", 
              new Func<HelperResult>(() => 
              {
                 return new HelperResult(
                     @<text>
                     <span>"desc1"</span>
                     <span>"desc2"</span>
                     </text>);
              }))

Unfortunately with this my text never gets printed out. No error either.

So I learned about inline helpers, and changed the calling method to this:

@node("title",                     
              @<text>
              <span>"desc1"</span>
              <span>"desc2"</span>
              </text>)

However now I get a compilation error saying

"Delegate 'System.Func' does not take 1 arguments".

But I'm not passing in any arguments.

So if I change it to Func<object,HelperResult> and then call it using @descriptions(null) I get the following error:

"Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type"

I'm sure I have something wrong somewhere, but I'm not sure what it is.

Edit: I think I may have solved that problem but it introduces some other issues.

What I did was to cast the lambda before passing into a dynamic method. I guess that's what the error was trying to say:

@node("title",                     
              ((Func<dynamic, HelperResult>)(@<text>
              <span>"desc1"</span>
              <span>"desc2"</span>
              </text>))

That works and it prints out the span tags correctly. Unfortunately I have to pass in a useless parameter when calling this Func.

Now the issue I have is that my real function does a bit more than just write some spans. It's more like this:

@node("title",                     
              ((Func<dynamic, HelperResult>)(@<text>
              <span>@Helpers.Format(resource.Description,"item")</span>
              </text>))

Where @Helpers.Format is another helper and resource is a (dynamic) variable from the page model.

Of course now the code runs but nothing is printed out (inside the <span> tag). I put a breakpoint inside my Format helper function, and it hits it and all the parameters are correctly set, so I'm not sure why it wouldn't output correctly. Similarly if I just change it to resource.Description then nothing still gets output.

Since it works well outside of this context, I wonder does Razor's inline helpers not capture the outer variables?

encee
  • 4,544
  • 4
  • 33
  • 35

2 Answers2

9

Actually HelperResult is something Microsoft would rather you didn't use, as evidenced by documentation:

public class HelperResult : IHtmlString in namespace System.Web.WebPages

Summary: This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.

A possible solution to your problem might be to wrap your description function in another helper and then pass that helper as a method group to your node helper, like this:

@helper Node(string title, Func<HelperResult> descriptions)
{
    <div>@descriptions()</div>
}

@helper Description() {
    <span>desc1</span>
    <span>desc2</span>
}

@Node("title", Description)

In any case, your first idea shouldn't work because a parameter of type Func is in fact equal to a parameterless function, in which case you need to write the lambda expression like this:

myFunction( () => doSomething)

So your function call would have been:

@node("title", () =>                    
              @<text>
              <span>"desc1"</span>
              <span>"desc2"</span>
              </text>)

Since the future of these helpers is a bit dubious though, I would consider switching to either HtmlHelpers for small snippets of html or Partials for larger chunks.

Community
  • 1
  • 1
Moeri
  • 9,104
  • 5
  • 43
  • 56
  • That better not be the case that HelperResult's future is 'dubious'! We make *extensive* use of HelperResult in our company's fluid bootstrap library (waiting for more perfection till we share it on github). When they said: "and is not intended to be used directly from your code," that does not mean they are pulling support for it, just that it is not going to commonly appear in your code. But many/most MVC toolsets / lower-level libraries depend on such things. "the future of these helpers is a bit dubious" - where did anyone say that in that link? Thankfully, I didn't see it. Cheers. – Nicholas Petersen Jan 14 '14 at 18:59
  • 1
    For instance, this allows us to POWERFULLY and BEAUTIFULLY add Razor code directly to a library: --- public void Add(Func html) { Add(html.ToString()); } --- The user never sees these functions, they just know like magic they can in a chain call somecontrol.Add(@
    howdy
    – Nicholas Petersen Jan 14 '14 at 19:24
  • @NicholasPetersen One major commercial library, Kendo Grid, uses these AFAIK, `.Template(@@item.Blah)`, if that puts you at ease. Do you have any good documentation on these, "MVC inline template" and variations turns up a lot of unrelated articles. They do seem powerful and I'd like to get into using them more myself. – AaronLS Jan 31 '14 at 22:04
  • Thanks for the info Aaron! Yes, it puts me at ease. I don't know if I understand your question on 'mvc inline templates,' are you referring to how Kendo is using these? But not knowing, it would make sense, any given control you could allow the user to customize certain parts of the output with this technique (allowing razor code to be passed into it). – Nicholas Petersen Feb 01 '14 at 23:17
  • I'm a bit late to the party, but I just want to note I was talking about the @helper syntax, not the HelperResult class, although they are probably closely related. I quote from "marcind", who worked on ASP.NET MVC: << [Declarative HTML helpers] are not dead and we are thinking of providing some add-ons (via MvcFutures, or a nuget package) that will address the problems. But at this point their future is "fuzzy" >> Like you said though, I doubt Microsoft would suddenly remove it knowing there are many libraries that use it. – Moeri Feb 10 '14 at 07:23
3
@Test(new Func<object, HelperResult>[]{@<text>hello</text>})

@Test(new Func<object, HelperResult>[]{@<text>hello</text>,@<text>world</text>})


@helper Test(params Func<object, HelperResult>[] results)
{
    foreach (var result in results)   
    {
        @result(null);
    }
}
teamchong
  • 1,326
  • 3
  • 16
  • 13