Jonathan Wood
Jonathan Wood

Reputation: 67223

Errors and warnings using @functions block in Razor Pages

Because the @helper directive is no longer supported in ASP.NET Core Razor Pages, I've been using the @functions directive instead.

@functions
{
    void RenderTask(Models.Task task)
    {
        <tr>
            <td class="@Model.CssClass">
                <p class="compact">
                    <span class="font-weight-bold">@task.Title</span>
                    @if (!string.IsNullOrWhiteSpace(task.Description))
                    {
                        <br />@task.Description
                    }
                </p>
            </td>
            <td class="@Model.CssClass">
                <img src="~/images/Edit.png" class="edit-area button-img" data-id="@task.Id" title="Edit" />
                <img src="~/images/Delete.png" class="delete-area button-img" data-id="@task.Id" title="Delete" />
            </td>
        </tr>
    }
}

This seems to work but I get an error:

Error MVC1006: The method contains a TagHelper and therefore must be async and return a Task. For instance, usage of ~/ typically results in a TagHelper and requires an async Task returning parent method.

So I changed this function to be async, and I used the await keyword any place it is called.

@functions
{
    async System.Threading.Tasks.Task RenderTask(Models.Task task)
    {
        <tr>
            <td class="@Model.CssClass">
                <p class="compact">
                    <span class="font-weight-bold">@task.Title</span>
                    @if (!string.IsNullOrWhiteSpace(task.Description))
                    {
                        <br />@task.Description
                    }
                </p>
            </td>
            <td class="@Model.CssClass">
                <img src="~/images/Edit.png" class="edit-area button-img" data-id="@task.Id" title="Edit" />
                <img src="~/images/Delete.png" class="delete-area button-img" data-id="@task.Id" title="Delete" />
            </td>
        </tr>
    }
}

This actually works but I get warnings:

...\Razor\Pages\Tasks\Index.cshtml.g.cs(286,200,286,202): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
...\Razor\Pages\Tasks\Index.cshtml.g.cs(312,200,312,202): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Index.cshtml.g.cs appears to be some sort of intermediary file. But I don't know what the numbers are that follow it, and double clicking these warnings does not take me to the offending line.

At this point, I'm not sure what the problem is. I've Googled extensively but haven't found a good example of this that shows what I'm supposed to be doing. Any suggestions appreciated.

Update:

Here's a section of Index.cshtml.g.cs:

#nullable restore
#line 86 "D:\Users\Jonathan\source\repos\Bamtok\Bamtok\Pages\Tasks\Index.cshtml"
                                                                          Write(task.Id);

#line default
#line hidden
#nullable disable
        __tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
        __tagHelperExecutionContext.AddHtmlAttribute("data-id", Html.Raw(__tagHelperStringValueBuffer), global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
        __tagHelperExecutionContext.AddHtmlAttribute(__tagHelperAttribute_3);
        await __tagHelperRunner.RunAsync(__tagHelperExecutionContext); // *** ERROR HERE! ***
        if (!__tagHelperExecutionContext.Output.IsContentModified)
        {
            await __tagHelperExecutionContext.SetOutputContentAsync();
        }
        Write(__tagHelperExecutionContext.Output);
        __tagHelperExecutionContext = __tagHelperScopeManager.End();
        WriteLiteral("\r\n                ");
        __tagHelperExecutionContext = __tagHelperScopeManager.Begin("img", global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode.SelfClosing, "5fc6845fce9caf31066e5edd3fc6a51f323364e715810", async() => {
        }
        );

Upvotes: 5

Views: 2968

Answers (4)

Dai
Dai

Reputation: 155280

There's at least 4 workarounds:

Solution 1: Return Task but don't be async:

You can safely ignore the compiler saying it must be an async function because all the compiler cares about is that the function returns System.Threading.Tasks.Task. So you can return a System.Threading.Tasks.Task without using await though it means you'll need a superfluous return Task.CompletedTask; in it somewhere:

@functions
{
    System.Threading.Tasks.Task RenderTask( Models.Task task )
    {
        <tr>
            <td class="@Model.CssClass">
                <p class="compact">
                    <span class="font-weight-bold">@task.Title</span>
                    @if (!string.IsNullOrWhiteSpace(task.Description))
                    {
                        <br />@task.Description
                    }
                </p>
            </td>
            <td class="@Model.CssClass">
                <img src="~/images/Edit.png" class="edit-area button-img" data-id="@task.Id" title="Edit" />
                <img src="~/images/Delete.png" class="delete-area button-img" data-id="@task.Id" title="Delete" />
            </td>
        </tr>

        return Task.CompletedTask;
    }
}

Solution 2: Alternatively, use a wrapper:

@functions
{
    System.Threading.Tasks.Task ShutUpRazor<T>( Action<T> renderFunc, T arg )
    {
        renderFunc( arg );
        return Task.CompletedTask;
    }

    void RenderTask( Models.Task task )
    {
        <tr>
            <td class="@Model.CssClass">
                <p class="compact">
                    <span class="font-weight-bold">@task.Title</span>
                    @if (!string.IsNullOrWhiteSpace(task.Description))
                    {
                        <br />@task.Description
                    }
                </p>
            </td>
            <td class="@Model.CssClass">
                <img src="~/images/Edit.png" class="edit-area button-img" data-id="@task.Id" title="Edit" />
                <img src="~/images/Delete.png" class="delete-area button-img" data-id="@task.Id" title="Delete" />
            </td>
        </tr>
    }
}

Usage:

<section>
    @( this.ShutUpRazor( this.RenderTask, this.Model.MyTask ); )
</section>

Solution 3: Suppress the warnings:

@functions
{
    #pragma warning disable 1998
    void RenderTask( etc )
    {
        etc
    }
    #pragma warning restore 1998
}

Solution 4: Use async functions and await only a Yield:

@functions
{
    async System.Threading.Tasks.Task RenderTask( Models.Task task )
    {
        await Task.Yield();

        <tr>
            <td class="@Model.CssClass">
                <p class="compact">
                    <span class="font-weight-bold">@task.Title</span>
                    @if (!string.IsNullOrWhiteSpace(task.Description))
                    {
                        <br />@task.Description
                    }
                </p>
            </td>
            <td class="@Model.CssClass">
                <img src="~/images/Edit.png" class="edit-area button-img" data-id="@task.Id" title="Edit" />
                <img src="~/images/Delete.png" class="delete-area button-img" data-id="@task.Id" title="Delete" />
            </td>
        </tr>
    }
}

Upvotes: 3

bluejay
bluejay

Reputation: 155

@functions 
{
    public async Task RenderItem(string date,string time,string id)
    {
        <td>@date</td>
        <td>@time</td>
        <td>
            <form method="post" class="form-inline" asp-page-handler="view" asp-route-id="@id">
                <button type="submit" class="btn btn-link">@id</button>
            </form>
        </td>
    }
}

Usage

 <tr>@{await RenderItem(@date.ToShortDateString(), time, id);}</tr>

Upvotes: 7

Ondras
Ondras

Reputation: 403

Workaround of the current Razor bug best working for me is to make the function async and call it with await in razor code scope. Example:

async Task RenderName (string name)
{
    <a asp-action="TagHelperForcesAsync">dear @name</a>
}

Used

<p>Hello @{await RenderName("John");}</p>

Upvotes: 1

Jonathan Wood
Jonathan Wood

Reputation: 67223

Microsoft has just confirmed the warnings are a bug.

Upvotes: 6

Related Questions