Reputation: 21
I'm sure this problem has been faced many times before.
I have a lot of CRUD forms, so I have a lot of repeated boiler plate code.
e.g.
<div class="form-group">
<label asp-for="MyModel.Property1" class="control-label"></label>
<input asp-for="MyModel.Property1" class="form-control" />
<span asp-validation-for="MyModel.Property1" class="text-danger"></span>
</div>
The above code being repeated time and time again per property, per form. Always looks the same (except if it's a dropdown for example).
I have managed to get something repeatable running with EditorTemplates, but this feels clunky. I'd need a different template per property type etc. This requires running: @Html.EditorFor(m => m.MyModel.Property1, "_MyString") I feel @Html. is almost discouraged in Razor Pages and the solution should be elsewhere.
I've spent far too long looking into getting a custom TagHelper setup and working, and I don't think it's possible. You can create your own TagHelpers and use TagBuilder, but you then lose all of the logic which is inside the InputTagHelper etc. https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.TagHelpers/src/InputTagHelper.cs
It needs to utilize the Input and Label TagHelpers as there is a lot of logic for example around property type in the InputTagHelper as per the above link. You can't seemingly use these.
I'm looking for a really generic way to cut down on my boiler plate code, and was hoping there might be a magic pill?
Edited to say - you also used to be able to do this in MVC which you can't do via Razor Pages, without creating per model.
public static IHtmlContent MyEditorFor<TModel, TResult>(
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TResult>> expression,
string templateName = "MyDefaultTemplateName")
{
return htmlHelper.EditorFor(expression, templateName);
}
Upvotes: 1
Views: 171
Reputation: 36655
Here is a whole working demo you could follow:
Create the Custom Tag Helper
[HtmlTargetElement("div-group", Attributes = "asp-for")]
public class DivGroupTagHelper : TagHelper
{
private const string ForAttributeName = "asp-for";
private IHtmlGenerator _htmlGenerator;
public DivGroupTagHelper(IHtmlGenerator htmlGenerator)
{
_htmlGenerator = htmlGenerator;
}
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; }
[HtmlAttributeName("class")]
public string CssClass { get; set; }
[HtmlAttributeName("label-class")]
public string LabelCssClass { get; set; }
[HtmlAttributeName("input-class")]
public string InputCssClass { get; set; }
[HtmlAttributeName("validation-class")]
public string ValidationCssClass { get; set; }
[HtmlAttributeNotBound]
[ViewContext]
public ViewContext ViewContext { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.SetAttribute("class", "form-group " + CssClass);
// Create a context and output for the label
var labelContext = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), Guid.NewGuid().ToString("N"));
var labelOutput = new TagHelperOutput("label", new TagHelperAttributeList(), (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
var labelTagHelper = new LabelTagHelper(_htmlGenerator)
{
For = For,
ViewContext = ViewContext
};
await labelTagHelper.ProcessAsync(labelContext, labelOutput);
labelOutput.AddClass("control-label", HtmlEncoder.Default);
if (!string.IsNullOrEmpty(LabelCssClass)) labelOutput.AddClass(LabelCssClass, HtmlEncoder.Default);
//Create a context and output for the input
var inputContext = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), Guid.NewGuid().ToString("N"));
var inputOutput = new TagHelperOutput("input", new TagHelperAttributeList(), (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
var inputTagHelper = new InputTagHelper(_htmlGenerator)
{
For = For,
ViewContext = ViewContext
};
await inputTagHelper.ProcessAsync(inputContext, inputOutput);
inputOutput.AddClass("form-control", HtmlEncoder.Default);
if (!string.IsNullOrEmpty(InputCssClass)) inputOutput.AddClass(InputCssClass, HtmlEncoder.Default);
// Create a context and output for the validation message
var validationContext = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), Guid.NewGuid().ToString("N"));
var validationOutput = new TagHelperOutput("span", new TagHelperAttributeList(), (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
var validationTagHelper = new ValidationMessageTagHelper(_htmlGenerator)
{
For = For,
ViewContext = ViewContext
};
await validationTagHelper.ProcessAsync(validationContext, validationOutput);
validationOutput.AddClass("text-danger", HtmlEncoder.Default);
if (!string.IsNullOrEmpty(ValidationCssClass)) validationOutput.AddClass(ValidationCssClass, HtmlEncoder.Default);
// Append the generated content to the output
output.Content.AppendHtml(labelOutput);
output.Content.AppendHtml(inputOutput);
output.Content.AppendHtml(validationOutput);
}
}
Add the Tag Helper registration in your _ViewImports.cshtml
file
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, YourNamespace
Use the custom Tag Helper in your Razor Pages
@page
@model IndexModel
<form method="post">
<div-group asp-for="Password"></div-group>
<div-group asp-for="Email"></div-group>
<input type="submit" value="Create"/>
</form>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Upvotes: 1