在ASP.NET Core中替换@helper

Ife*_*kwu 32 razor asp.net-core-mvc asp.net-core

到目前为止,我认为不ViewComponent解决这两个问题TagHelper.这有什么替代品吗?需要参数并返回的东西HtmlString

我没有看到任何有害的东西:

@helper foo(string something) {
     <div>Say @something</div>
}

var emailbody = classfilenameinAppCodefolder.foo("hello"); //store result in a variable for further processes
Run Code Online (Sandbox Code Playgroud)

现在我相信它在RC之前暂时删除.https://github.com/aspnet/Razor/issues/281https://github.com/aspnet/Mvc/issues/1130嗯!它更好.我希望有人正在努力.没有@helper,建立大型HtmlString或"模板"将是一个严重的痛苦.

注意:部分视图似乎没有成功.我认为它只会渲染视图而不是返回视图变量.

其次,App_Code文件夹发生了什么?

Gar*_*ton 18

根据以下 Github 问题,看起来@helper 回来了,并将包含在 asp .net core 3.0.0 preview 4 中。

https://github.com/aspnet/AspNetCore/issues/5110

更新

从 asp .net core 3 开始,您现在可以在 Razor 代码块中定义本地函数。

@{
    void RenderName(string name)
    {
        <p>Name: <strong>@name</strong></p>
    }

    RenderName("Mahatma Gandhi");
    RenderName("Martin Luther King, Jr.");
}
Run Code Online (Sandbox Code Playgroud)

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#razor-code-blocks

或者,您可以像这样使用@functions 指令:

@{
    RenderName("Mahatma Gandhi");
    RenderName("Martin Luther King, Jr.");
}

@functions {
    private void RenderName(string name)
    {
        <p>Name: <strong>@name</strong></p>
    }
}
Run Code Online (Sandbox Code Playgroud)

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#functions

  • 奇迹并没有发生。仍然无法在 .net core 3 版本中工作 (4认同)
  • 我现在在 asp.net core 3.1 应用程序中使用它,它的工作原理如文档所示。 (3认同)
  • @GaryBrunton 你尝试过你发布的内容吗?这些在 ASP.NET Core 3.1 Razor CSHTML 中不起作用。如果我尝试在本地函数中输入 html,则会收到 html 无法被接受的错误。该函数也存在同样的问题。 (2认同)
  • 我刚刚尝试了本地函数和 @functions 指令,它们都在 3.0 上为我工作。Resharper 显示错误,但代码在运行时有效。我会尝试尽快将其发布到 3.1 并尝试一下。 (2认同)

小智 15

@{
    Func<String, IHtmlContent> foo = @<div>Say @item</div>;
}
Run Code Online (Sandbox Code Playgroud)

  • 我应该把它放在哪里,以便从我的所有视图中都可以看到它? (2认同)

Ada*_*mon 10

正如@scott在他的回答中指出的那样,从 .NET Core 3 开始,本地函数终于可用。在之前的版本中,人们可以诉诸模板化的 Razor 委托

但所有答案都没有解决“App_Code 文件夹发生了什么?”这个问题。上述功能是局部解决方案,即以这些方式定义的辅助函数不能在多个视图之间共享。但全局辅助函数通常比 MS 为视图相关代码重用提供的开箱即用的解决方案更方便。(标签助手、部分视图、视图组件都有其缺点。)这在本期本期GitHub 中进行了深入讨论。不幸的是,根据这些讨论,微软方面并没有太多的了解,因此对于很快添加该功能(如果有的话)的希望不大。

然而,在深入研究框架源代码之后,我认为我可以找到一个可行的解决方案来解决这个问题。

核心思想是我们可以利用 Razor 视图引擎为我们查找任意视图:例如,定义了我们想要全局使用的一些局部函数的部分视图。一旦我们设法获得对该视图的引用,就没有什么可以阻止我们调用它的公共方法。

下面的类GlobalRazorHelpersFactory封装了这个想法:

public interface IGlobalRazorHelpersFactory
{
    dynamic Create(string helpersViewPath, ViewContext viewContext);
    THelpers Create<THelpers>(ViewContext viewContext) where THelpers : class;
}

public class GlobalRazorHelpersOptions
{
    public Dictionary<Type, string> HelpersTypeViewPathMappings { get; } = new Dictionary<Type, string>();
}

public sealed class GlobalRazorHelpersFactory : IGlobalRazorHelpersFactory
{
    private readonly ICompositeViewEngine _viewEngine;
    private readonly IRazorPageActivator _razorPageActivator;

    private readonly ConcurrentDictionary<Type, string> _helpersTypeViewPathMappings;

    public GlobalRazorHelpersFactory(ICompositeViewEngine viewEngine, IRazorPageActivator razorPageActivator, IOptions<GlobalRazorHelpersOptions>? options)
    {
        _viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine));
        _razorPageActivator = razorPageActivator ?? throw new ArgumentNullException(nameof(razorPageActivator));

        var optionsValue = options?.Value;
        _helpersTypeViewPathMappings = new ConcurrentDictionary<Type, string>(optionsValue?.HelpersTypeViewPathMappings ?? Enumerable.Empty<KeyValuePair<Type, string>>());
    }

    public IRazorPage CreateRazorPage(string helpersViewPath, ViewContext viewContext)
    {
        var viewEngineResult = _viewEngine.GetView(viewContext.ExecutingFilePath, helpersViewPath, isMainPage: false);

        var originalLocations = viewEngineResult.SearchedLocations;

        if (!viewEngineResult.Success)
            viewEngineResult = _viewEngine.FindView(viewContext, helpersViewPath, isMainPage: false);

        if (!viewEngineResult.Success)
        {
            var locations = string.Empty;

            if (originalLocations.Any())
                locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations);

            if (viewEngineResult.SearchedLocations.Any())
                locations += Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations);

            throw new InvalidOperationException($"The Razor helpers view '{helpersViewPath}' was not found. The following locations were searched:{locations}");
        }

        var razorPage = ((RazorView)viewEngineResult.View).RazorPage;

        razorPage.ViewContext = viewContext;

        // we need to save and restore the original view data dictionary as it is changed by IRazorPageActivator.Activate
        // https://github.com/dotnet/aspnetcore/blob/v3.1.6/src/Mvc/Mvc.Razor/src/RazorPagePropertyActivator.cs#L59
        var originalViewData = viewContext.ViewData;
        try { _razorPageActivator.Activate(razorPage, viewContext); }
        finally { viewContext.ViewData = originalViewData; }

        return razorPage;
    }

    public dynamic Create(string helpersViewPath, ViewContext viewContext) => CreateRazorPage(helpersViewPath, viewContext);

    public THelpers Create<THelpers>(ViewContext viewContext) where THelpers : class
    {
        var helpersViewPath = _helpersTypeViewPathMappings.GetOrAdd(typeof(THelpers), type => "_" + (type.Name.StartsWith("I", StringComparison.Ordinal) ? type.Name.Substring(1) : type.Name));

        return (THelpers)CreateRazorPage(helpersViewPath, viewContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

将单例IGlobalRazorHelpersFactory服务引入 DI 后,我们可以将其注入视图中并调用该Create方法来获取包含辅助函数的视图实例。

通过在辅助视图中使用@implements指令,我们甚至可以获得类型安全的访问:

@inherits Microsoft.AspNetCore.Mvc.Razor.RazorPage
@implements IMyGlobalHelpers

@functions {
    public void MyAwesomeGlobalFunction(string someParam)
    {
        <div>@someParam</div>
    }
}
Run Code Online (Sandbox Code Playgroud)

GlobalRazorHelpersOptions(可以通过以普通方式配置- by services.Configure<GlobalRazorHelpersOptions>(o => ...)- 来显式定义接口类型来查看路径映射,但通常我们可以简单地依赖实现的命名约定:在接口的情况下IMyGlobalHelpers,它将查找名为的视图_MyGlobalHelpers.cshtml在常规位置。最好将其放入/Views/Shared。)

到目前为止还不错,但我们可以做得更好!如果我们可以直接在消费者视图中注入辅助实例,那就方便多了。IOptions<T>我们可以使用//HtmlLocalizer<T>背后的想法轻松实现这一点ViewLocalizer

public interface IGlobalRazorHelpers<out THelpers> : IViewContextAware
    where THelpers : class
{
    THelpers Instance { get; }
}

public sealed class GlobalRazorHelpers<THelpers> : IGlobalRazorHelpers<THelpers>
    where THelpers : class
{
    private readonly IGlobalRazorHelpersFactory _razorHelpersFactory;

    public GlobalRazorHelpers(IGlobalRazorHelpersFactory razorHelpersFactory)
    {
        _razorHelpersFactory = razorHelpersFactory ?? throw new ArgumentNullException(nameof(razorHelpersFactory));
    }

    private THelpers? _instance;
    public THelpers Instance => _instance ?? throw new InvalidOperationException("The service was not contextualized.");

    public void Contextualize(ViewContext viewContext) => _instance = _razorHelpersFactory.Create<THelpers>(viewContext);
}
Run Code Online (Sandbox Code Playgroud)

现在我们必须在以下位置注册我们的服务Startup.ConfigureServices

services.AddSingleton<IGlobalRazorHelpersFactory, GlobalRazorHelpersFactory>();
services.AddTransient(typeof(IGlobalRazorHelpers<>), typeof(GlobalRazorHelpers<>));
Run Code Online (Sandbox Code Playgroud)

最后,我们准备在视图中使用全局 Razor 函数:

@inject IGlobalRazorHelpers<IMyGlobalHelpers> MyGlobalHelpers;

@{ MyGlobalHelpers.Instance.MyAwesomeGlobalFunction("Here we go!"); }
Run Code Online (Sandbox Code Playgroud)

这比原始的 App_Code + 静态方法功能稍微复杂一些,但我认为这是我们能得到的最接近的。根据我的测试,该解决方案在启用运行时编译的情况下也能很好地工作。到目前为止,我还没有时间进行基准测试,但从理论上讲,它通常应该比使用部分视图更快,因为每个消费者视图仅查找共享视图一次,之后它只是简单的方法调用。但我不确定标签助手。做一些基准测试来比较它们会很有趣。但我把这件事留给了采用者。

(在 .NET Core 3.1 上测试。)

更新

您可以在我的 ASP.NET 样板项目中找到此概念的工作演示:

  • @Desmond 我很高兴你喜欢它!我用完整示例的链接更新了我的答案。我希望这能帮助您更轻松地掌握这个想法。(顺便说一句,理论上你可以在没有接口的情况下通过简单地注入`IGlobalRazorHelpersFactory`并使用返回`dynamic`的`Create`重载来相处。但是,你会失去这种强类型,所以我不推荐在专业领域使用它项目。) (2认同)

Gar*_*ton 8

我想扩展@Alexaku的答案并展示我是如何实现一个类似函数的帮助器.它仅在一个特定页面上有用,但它允许您使用输入参数多次执行一段剃刀代码.语法不是很好,但我发现它在没有razor的@helper函数时非常有用.首先声明某种Dto,它将包含输入参数到函数中.

@functions {
   private class Dto
   {
      public string Data { get;set; }
   }
}
Run Code Online (Sandbox Code Playgroud)

然后声明剃刀功能.请注意,displayItem值可以是多行的,并且还要注意您使用@item访问Dto变量.

@{
   Func<Dto, IHtmlContent> displayItem = @<span>@item.Data</span>;
}
Run Code Online (Sandbox Code Playgroud)

然后,当您想要使用剃刀模板时,您可以从页面中的任何位置调用它,如下所示.

<div>
   @displayItem(new Dto {Data = "testingData1" });
</div>
<div>
   @displayItem(new Dto {Data = "testingData2" });
</div>
Run Code Online (Sandbox Code Playgroud)


小智 8

对于 .NET Core 3,您可以使用本地函数:

@{
    void RenderName(string name)
    {
        <p>Name: <strong>@name</strong></p>
    }

    RenderName("Mahatma Gandhi");
    RenderName("Martin Luther King, Jr.");
}
Run Code Online (Sandbox Code Playgroud)

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#razor-code-blocks


Hen*_*ema 4

@helper指令已被删除,因为它不完整,并且其当前设计不适合新的“ASP.NET 5 方式”。原因之一是助手应该在App_Code文件夹中声明,而 ASP.NET 5没有特殊文件夹的概念。因此团队决定暂时删除该功能。

不过,有计划在未来将其带回来。看看这个这个

  • 嗯......很明显我不会很快与 ASP.NET 5 有任何关系。我宁愿坚持使用 4,或者在必要时迁移到 node.js。到目前为止我还不能保证它的方向。对我来说,这似乎是一个全新的学习曲线。我没有时间,伙计们……这几天要学的东西太多了 (6认同)
  • 截至 2018 年,这仍然是一个问题和功能差距。去搞清楚。 (3认同)