到目前为止,我并不认为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/281和https://github.com/aspnet/Mvc/issues/1130嗯!它更好。我希望有人正在努力。如果没有@helper
,则构建大型HtmlString
或&#39;模板&#39;会是一个严重的痛苦。
注意:部分视图似乎没有做到这一点。我认为它只会渲染视图而不是返回视图变量。
其次,App_Code文件夹发生了什么?
答案 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
文件夹中声明帮助程序。因此,团队决定暂时删除该功能。
答案 3 :(得分:1)
您可以轻松地用ViewComponent(如果需要,可以使用TagHelper)替换该“功能”。 ASP.NET Core对Web设计人员而言更加友好,并且ViewComponents允许您编写HTML而无需任何(对大多数人来说都很奇怪)剃刀代码。
例如:
创建一个SayComponent : ViewComponent
类:
public class SayComponent : ViewComponent
{
public void Render(string message)
{
return View(message);
}
}
只需{p>在Views/Shared/Say/Default.cshtml
下创建一个View文件
@model string
<div>Message: @Model.</div>
并命名为:
@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提供的开箱即用的视图相关代码重用解决方案更方便。 (标签助手,局部视图,视图组件都有其缺点。)这在this和this 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中。
答案 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>