How to pass in a lambda to a Razor helper method?

2019-04-19 06:26发布

问题:

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?

回答1:

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.



回答2:

@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);
    }
}