将 ModelExpression 从 Tag Helper 传递到分部视图
Pass ModelExpression from a Tag Helper to a Partial View
我正在使用示例 found here 来允许从标签助手中呈现局部视图。我在这里要做的是能够像这样定义一个标签助手:
<mydateinput for="@Model.StartDate" />
在标签助手的 c# 代码中,我定义了 "for" 属性,据我所知,这需要定义为 "ModelExpression"。
public class MyDateInputTagHelper : TagHelper
{
public ModelExpression For { get; set; }
...
}
使用前面this article中提到的代码,我正在渲染一个局部视图,并简单地将标签助手的class作为局部视图的模型传递。
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
((IViewContextAware)HtmlHelper).Contextualize(ViewContext);
output.Content.SetHtmlContent(HtmlHelper.Partial("~/Views/Partials/TagHelpers/MyDateInput.cshtml", this));
}
最后,我的局部视图是这样定义的
<input asp-for="For" />
我 运行 遇到的问题是我无法让模型表达式 "For" 正确传递到局部视图中。当我查看 html 源代码时,我只是在输入的 id 和名称属性中看到字面上的名称 "For"。此外,在我的剃刀页面模型中设置的值也没有正确显示。
我想要发生的是 html 将以这样一种方式呈现,即在发布页面时,我的 razor 页面的模型将填充标签助手下选择的值/ 局部视图。它会具体围绕 "StartDate"(在我的示例中),而不是 属性 "For".
有谁知道我做错了什么,以及我可以在此示例中更改哪些内容以正确地将 ModelExpression 传递给局部视图?
无法直接使用 InputTagHelper
执行此操作。有关详细信息,请参阅 Razor/issues #926 and also a similar question on SO。
解决方法
但作为解决方法,您可以使用自定义 HtmlHelper<TModel>
(就像 @Html
)来实现相同的目标。您的局部视图可能如下所示:
@model App.TagHelpers.PartialVM
@{
var PartialHtml = Model.HtmlHelper;
}
@PartialHtml.Label(Model.NewFor.Name,Model.NewFor.Name)
@PartialHtml.TextBox(Model.NewFor.Name, Model.NewModel)
即,使用 @Html.Label()
& @Html.TextBot
而不是 <label>
& <input>
。
这里的 PartialVM
是一个简单的 class,它包含关于模型的元信息:
public class PartialVM
{
public PartialVM(ModelExpression originalFor, IHtmlHelper htmlHelper)
{
var originalExplorer = originalFor.ModelExplorer;
OriginalFor = originalFor;
OriginalExplorer = originalExplorer;
NewModel = originalExplorer.Model;
NewModelExplorer = originalExplorer.GetExplorerForModel(NewModel);
NewFor = new ModelExpression(OriginalFor.Name, NewModelExplorer);
this.HtmlHelper = htmlHelper;
}
public IHtmlHelper HtmlHelper { get; set; }
public ModelExpression OriginalFor { get; set; }
public ModelExplorer OriginalExplorer { get; set; }
public ModelExpression NewFor { get; set; }
public ModelExplorer NewModelExplorer { get; set; }
public Object NewModel { get; set; }
}
请注意 IHtmlHelper
实际上是 IHtmlHelper<TSomeDynamicModel>
而不是普通的 IHtmlHelper
.
最后,将您的 TagHelper
更改如下:
[HtmlTargetElement("my-custom-input")]
public class MyCustomInputTagHelper : TagHelper
{
private readonly IServiceProvider _sp;
[ViewContext]
public ViewContext ViewContext { set; get; }
public ModelExpression For { get; set; }
public MyCustomInputTagHelper(IServiceProvider sp)
{
this._sp = sp;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
var originExplorer = For.ModelExplorer;
var newModel = originExplorer.Model;
var newExplorer = originExplorer.GetExplorerForModel(newModel);
var newFor = new ModelExpression(For.Name, newExplorer);
var ModelType = originExplorer.Container.Model.GetType();
var htmlHelperType = typeof(IHtmlHelper<>).MakeGenericType(ModelType);
var htmlHelper = this._sp.GetService(htmlHelperType) as IHtmlHelper; // get the actual IHtmlHelper<TModel>
(htmlHelper as IViewContextAware).Contextualize(ViewContext);
var vm = new PartialVM(For, htmlHelper);
var writer = new StringWriter();
var content = await htmlHelper.PartialAsync("~/Views/Partials/TagHelpers/MyDateInput.cshtml", vm);
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Content.SetHtmlContent(content);
}
}
现在您可以为 TagHelper
的 For
属性 传递任何 asp-for
表达式字符串,它应该会按预期工作。
测试用例:
假设我们有一个 Dto 模型:
public class XModel {
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string ActiveStatus{ get; set; }
}
您可以通过以下方式呈现它:
/// the action method looks like:
/// var model = new XModel {
/// StartDate = DateTime.Now,
/// EndDate = DateTime.Now.AddYears(1),
/// ActiveStatus = "Active",
/// };
/// return View(model);
@model XModel
<my-custom-input For="StartDate" />
<my-custom-input For="EndDate" />
<my-custom-input For="ActiveStatus" />
这是呈现时的屏幕截图:
或者在您的部分中使用 Html 助手,然后它就会起作用。
// _MovieField.cshtml
@{
string propertyName = (string)ViewData["PropertyName"];
}
<div class="form-group">
@Html.Label(propertyName, null, new{ @class = "control-label" })
@Html.Editor(propertyName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessage(propertyName, null, new{ @class = "text-danger" })
</div>
然后像这样使用它
// EditMovie.cshtml
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Movie.Id" />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Title" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "ReleaseDate" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Genre" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Price" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Rating" }}' />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
我正在使用示例 found here 来允许从标签助手中呈现局部视图。我在这里要做的是能够像这样定义一个标签助手:
<mydateinput for="@Model.StartDate" />
在标签助手的 c# 代码中,我定义了 "for" 属性,据我所知,这需要定义为 "ModelExpression"。
public class MyDateInputTagHelper : TagHelper
{
public ModelExpression For { get; set; }
...
}
使用前面this article中提到的代码,我正在渲染一个局部视图,并简单地将标签助手的class作为局部视图的模型传递。
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
((IViewContextAware)HtmlHelper).Contextualize(ViewContext);
output.Content.SetHtmlContent(HtmlHelper.Partial("~/Views/Partials/TagHelpers/MyDateInput.cshtml", this));
}
最后,我的局部视图是这样定义的
<input asp-for="For" />
我 运行 遇到的问题是我无法让模型表达式 "For" 正确传递到局部视图中。当我查看 html 源代码时,我只是在输入的 id 和名称属性中看到字面上的名称 "For"。此外,在我的剃刀页面模型中设置的值也没有正确显示。
我想要发生的是 html 将以这样一种方式呈现,即在发布页面时,我的 razor 页面的模型将填充标签助手下选择的值/ 局部视图。它会具体围绕 "StartDate"(在我的示例中),而不是 属性 "For".
有谁知道我做错了什么,以及我可以在此示例中更改哪些内容以正确地将 ModelExpression 传递给局部视图?
无法直接使用 InputTagHelper
执行此操作。有关详细信息,请参阅 Razor/issues #926 and also a similar question on SO。
解决方法
但作为解决方法,您可以使用自定义 HtmlHelper<TModel>
(就像 @Html
)来实现相同的目标。您的局部视图可能如下所示:
@model App.TagHelpers.PartialVM
@{
var PartialHtml = Model.HtmlHelper;
}
@PartialHtml.Label(Model.NewFor.Name,Model.NewFor.Name)
@PartialHtml.TextBox(Model.NewFor.Name, Model.NewModel)
即,使用 @Html.Label()
& @Html.TextBot
而不是 <label>
& <input>
。
这里的 PartialVM
是一个简单的 class,它包含关于模型的元信息:
public class PartialVM
{
public PartialVM(ModelExpression originalFor, IHtmlHelper htmlHelper)
{
var originalExplorer = originalFor.ModelExplorer;
OriginalFor = originalFor;
OriginalExplorer = originalExplorer;
NewModel = originalExplorer.Model;
NewModelExplorer = originalExplorer.GetExplorerForModel(NewModel);
NewFor = new ModelExpression(OriginalFor.Name, NewModelExplorer);
this.HtmlHelper = htmlHelper;
}
public IHtmlHelper HtmlHelper { get; set; }
public ModelExpression OriginalFor { get; set; }
public ModelExplorer OriginalExplorer { get; set; }
public ModelExpression NewFor { get; set; }
public ModelExplorer NewModelExplorer { get; set; }
public Object NewModel { get; set; }
}
请注意 IHtmlHelper
实际上是 IHtmlHelper<TSomeDynamicModel>
而不是普通的 IHtmlHelper
.
最后,将您的 TagHelper
更改如下:
[HtmlTargetElement("my-custom-input")]
public class MyCustomInputTagHelper : TagHelper
{
private readonly IServiceProvider _sp;
[ViewContext]
public ViewContext ViewContext { set; get; }
public ModelExpression For { get; set; }
public MyCustomInputTagHelper(IServiceProvider sp)
{
this._sp = sp;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
var originExplorer = For.ModelExplorer;
var newModel = originExplorer.Model;
var newExplorer = originExplorer.GetExplorerForModel(newModel);
var newFor = new ModelExpression(For.Name, newExplorer);
var ModelType = originExplorer.Container.Model.GetType();
var htmlHelperType = typeof(IHtmlHelper<>).MakeGenericType(ModelType);
var htmlHelper = this._sp.GetService(htmlHelperType) as IHtmlHelper; // get the actual IHtmlHelper<TModel>
(htmlHelper as IViewContextAware).Contextualize(ViewContext);
var vm = new PartialVM(For, htmlHelper);
var writer = new StringWriter();
var content = await htmlHelper.PartialAsync("~/Views/Partials/TagHelpers/MyDateInput.cshtml", vm);
output.TagName = "div";
output.TagMode = TagMode.StartTagAndEndTag;
output.Content.SetHtmlContent(content);
}
}
现在您可以为 TagHelper
的 For
属性 传递任何 asp-for
表达式字符串,它应该会按预期工作。
测试用例:
假设我们有一个 Dto 模型:
public class XModel {
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string ActiveStatus{ get; set; }
}
您可以通过以下方式呈现它:
/// the action method looks like:
/// var model = new XModel {
/// StartDate = DateTime.Now,
/// EndDate = DateTime.Now.AddYears(1),
/// ActiveStatus = "Active",
/// };
/// return View(model);
@model XModel
<my-custom-input For="StartDate" />
<my-custom-input For="EndDate" />
<my-custom-input For="ActiveStatus" />
这是呈现时的屏幕截图:
或者在您的部分中使用 Html 助手,然后它就会起作用。
// _MovieField.cshtml
@{
string propertyName = (string)ViewData["PropertyName"];
}
<div class="form-group">
@Html.Label(propertyName, null, new{ @class = "control-label" })
@Html.Editor(propertyName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessage(propertyName, null, new{ @class = "text-danger" })
</div>
然后像这样使用它
// EditMovie.cshtml
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="Movie.Id" />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Title" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "ReleaseDate" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Genre" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Price" }}' />
<partial name="_MovieField" for="Movie"
view-data='new ViewDataDictionary(ViewData){ {"PropertyName", "Rating" }}' />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>