在ASP.NET Core中替换@helper

时间:2015-09-01 12:04:03

标签: razor asp.net-core asp.net-core-mvc

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

的东西

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

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

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

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

注意:部分视图似乎没有做到这一点。我认为它只会渲染视图而不是返回视图变量。

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

8 个答案:

答案 0 :(得分:14)

@{
    Func<String, IHtmlContent> foo = @<div>Say @item</div>;
}

答案 1 :(得分:8)

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

@functions {
   private class Dto
   {
      public string Data { get;set; }
   }
}

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

@{
   Func<Dto, IHtmlContent> displayItem = @<span>@item.Data</span>;
}

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

<div>
   @displayItem(new Dto {Data = "testingData1" });
</div>
<div>
   @displayItem(new Dto {Data = "testingData2" });
</div>

答案 2 :(得分:3)

@helper指令was removed因为它不完整且其当前设计不适合新的ASP.NET 5方式&#39;。其中一个原因是在ASP.NET 5 has no concept of special folders时应该在App_Code文件夹中声明帮助程序。因此,团队决定暂时删除该功能。

但是有计划将来把它带回来。请参阅thisthis

答案 3 :(得分:1)

您可以轻松地用ViewComponent(如果需要,可以使用TagHelper)替换该“功能”。 ASP.NET Core对Web设计人员而言更加友好,并且ViewComponents允许您编写HTML而无需任何(对大多数人来说都很奇怪)剃刀代码。

例如:

  1. 创建一个SayComponent : ViewComponent类:

    public class SayComponent : ViewComponent
    {
        public void Render(string message)
        {
            return View(message);
        }
    }
    
  2. 只需{p>在Views/Shared/Say/Default.cshtml下创建一个View文件

    @model string
    
    <div>Message: @Model.</div>
    
  3. 并命名为:

    @await Component.RenderAsync("Say", "some message")
    

为获得更好的体验,请将其添加到您的_ViewImports.cshtml文件中:

@addTagHelper *, YourSolutionName

然后您可以将其用作标签助手:

<vc:say message="some message"></vc:say>

答案 4 :(得分:1)

正如@scott在他的回答中指出的那样,local functions终于从.NET Core 3开始可用。在以前的版本中,人们可以求助于templated Razor delegates

但是,所有答案都没有解决“ App_Code文件夹发生了什么事情”的问题。前面提到的功能是本地解决方案,即,以这些方式定义的辅助功能不能在多个视图之间共享。但是,全局帮助程序功能通常可能比MS提供的开箱即用的视图相关代码重用解决方案更方便。 (标签助手,局部视图,视图组件都有其缺点。)这在thisthis GitHub问题中进行了详细讨论。不幸的是,根据这些论述,MS方面的了解并不多,因此,即使有可能,也不会在不久的将来添加此功能。

但是,我认为,在深入研究框架源代码之后,我可以为该问题提出一个可行的解决方案。

核心思想是我们可以利用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") ? type.Name.Substring(1) : type.Name));

        return (THelpers)CreateRazorPage(helpersViewPath, viewContext);
    }
}

在DI中引入单例IGlobalRazorHelpersFactory服务之后,我们可以将其注入视图中并调用Create方法以获取包含我们的辅助函数的视图实例。通过使用@implements指令,我们甚至可以获得类型安全的访问权限:

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

@functions {
    public void MyAwesomeGlobalFunction(string someParam)
    {
        <div>@someParam</div>
    }
}

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

到目前为止很不错,但是我们可以做得更好!如果我们可以直接在使用者视图中注入帮助程序实例,将会更加方便。我们可以使用IOptions<T> / HtmlLocalizer<T> / ViewLocalizer背后的思想轻松实现这一目标:

public interface IGlobalRazorHelpers<out THelpers> : IViewContextAware
    where THelpers : class
{
    public 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);
}

现在我们必须在Startup.ConfigureServices中注册我们的服务:

services.AddSingleton<IGlobalRazorHelpersFactory, GlobalRazorHelpersFactory>();
services.AddTransient(typeof(IGlobalRazorHelpers<>), typeof(GlobalRazorHelpers<>));

最后,我们准备好使用我们视图中的全局Razor功能:

@inject IGlobalRazorHelpers<IMyGlobalHelpers> MyGlobalHelpers;

@{ MyGlobalHelpers.Instance.MyAwesomeGlobalFunction("Here we go!"); }

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

(在.NET Core 3.1上测试。)

答案 5 :(得分:0)

根据以下Github问题,看来@helper回来了,它将包含在asp .net core 3.0.0预览版4中。

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

答案 6 :(得分:0)

对于.NET Core 3,您可以使用本地功能:

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

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

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

答案 7 :(得分:0)

如何使用局部变量来重新创建可重用标签?

MyProject/Views/Shared/_foo.cshtml

@model string

<div>@Model</div>

MyProject/Views/Courses/Index.cshtml

@{
    Layout = "_Layout";
}

<div>
   <partial name="_foo" model="foo" />
   <partial name="_foo" model="bar" />
   <partial name="_foo" model="baz" />
</div>