Custom ViewComponent with asp-for as parameter
Asked Answered
I

2

11

I want wrap this:

<textarea asp-for="@Model.Content" ...>

into reusable ViewComponent, where property will be parameter:

<vc:editor asp-for="@Model.Content" />

I was able to pass asp-for as parameter to the viewcomponent:

public class EditorViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(ModelExpression aspFor = null)
    {
        //when debugging, aspFor has correct value
        return View(aspFor);
    }
}

But I'm not able to evaluate it in component's view. This does not work:

<!-- ViewComponents/Editor/Default.cshtml -->
@model Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression
<textarea asp-for="@Model" />

Any ideas?

Ivy answered 17/3, 2017 at 11:0 Comment(5)
I think you are mixing ViewComponents and TagHelpers. learn.microsoft.com/en-us/aspnet/core/mvc/views/view-componentsLlanes
I just want to wrap my textarea into viewcomponent, but I'm not able to pass the ModelExpression into the textarea. Your answer shows only how to use viewcomponent, but does not help with passing the ModelExpression into the textarea within the viewcomponent.Ivy
Use a different class for your priority. Like modelExpression? Or func<T>Llanes
?? I'm not sure what you mean. What priority? I'm already using ModelExpresionIvy
*property. Ah you probably have to recode this part. Or extend the text area tag helper. You probably want to make a tag helper instead anyways.Llanes
I
2

If you want to pass ModelExpression to underlying ViewComponent and then pass it to TagHelper, you have to do it using @TheModelExpression.Model:

<!-- ViewComponents/Editor/Default.cshtml -->
@model Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression
<textarea asp-for="@Model.Model" />

As @JoelHarkes mentioned, in this particular case, custom taghelper could be more appropriate. Anyway, I still can render PartialView ala Template in the TagHelper:

[HtmlTargetElement("editor", Attributes = "asp-for", TagStructure = TagStructure.WithoutEndTag)]
public class EditorTagHelper : TagHelper
{
    private HtmlHelper _htmlHelper;
    private HtmlEncoder _htmlEncoder;

    public EditorTagHelper(IHtmlHelper htmlHelper, HtmlEncoder htmlEncoder)
    {
        _htmlHelper = htmlHelper as HtmlHelper;
        _htmlEncoder = htmlEncoder;
    }

    [HtmlAttributeName("asp-for")]
    public ModelExpression For { get; set; }

    [ViewContext]
    public ViewContext ViewContext
    {
        set => _htmlHelper.Contextualize(value);
    }


    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = null;

        var partialView = await _htmlHelper.PartialAsync("TagHelpers/Editor", For);

        var writer = new StringWriter();
        partialView.WriteTo(writer, _htmlEncoder);

        output.Content.SetHtmlContent(writer.ToString());
    }
}

the .cshtml template would then look exactly like in viewcomponent.

Ivy answered 30/10, 2017 at 9:10 Comment(2)
If you do this the ModelExpression ends up being the model, instead of the ModelExpression being an expression whose value is a property of the model, which means you're not referencing the actual property which is where any validation attributes would be defined so it's not obvious how this is useful.Ferreira
This was a long time ago, but if I recall correctly, the ModelExpression.Model points to the property, not the original model. So the inner asp-for="@Model.Model" is an expression, whose value is a property, that wraps property of the original model. If will remove this answer, if this is not correct.Ivy
L
2

I think you are mixing ViewComponents and TagHelpers:https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components

View components

View components are invoked in the following matter:

@await Component.InvokeAsync("EditorView", @Model.Property);
// or
<vc:[view-component-name]>

Try the following snippit:

<vc:editor for="@Model.Content" />

Taghelpers

the tag helpers are only invoked like this:

<textarea asp-for="@Model.Content">
Llanes answered 17/3, 2017 at 12:33 Comment(3)
when I rename aspFor parameter to for parameter, I still have the same problem. I cannot pass it to the tag helper inside my ViewComponentIvy
I'm in the same boat. What sort of .NET sorcery and magic goes on behind the scenes for the asp-for to work? None of that is documented (that I can find) and I am wanting to create a library of reusable view components for my project as well, but as far as I can figure out, I have to manually set the property name.Ulrick
@Keith there should be quite some videos explaining how this works in Microsoft's tech demos last year. You best bet is to check taghelper source code on GitHub to help you out.Llanes
I
2

If you want to pass ModelExpression to underlying ViewComponent and then pass it to TagHelper, you have to do it using @TheModelExpression.Model:

<!-- ViewComponents/Editor/Default.cshtml -->
@model Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression
<textarea asp-for="@Model.Model" />

As @JoelHarkes mentioned, in this particular case, custom taghelper could be more appropriate. Anyway, I still can render PartialView ala Template in the TagHelper:

[HtmlTargetElement("editor", Attributes = "asp-for", TagStructure = TagStructure.WithoutEndTag)]
public class EditorTagHelper : TagHelper
{
    private HtmlHelper _htmlHelper;
    private HtmlEncoder _htmlEncoder;

    public EditorTagHelper(IHtmlHelper htmlHelper, HtmlEncoder htmlEncoder)
    {
        _htmlHelper = htmlHelper as HtmlHelper;
        _htmlEncoder = htmlEncoder;
    }

    [HtmlAttributeName("asp-for")]
    public ModelExpression For { get; set; }

    [ViewContext]
    public ViewContext ViewContext
    {
        set => _htmlHelper.Contextualize(value);
    }


    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = null;

        var partialView = await _htmlHelper.PartialAsync("TagHelpers/Editor", For);

        var writer = new StringWriter();
        partialView.WriteTo(writer, _htmlEncoder);

        output.Content.SetHtmlContent(writer.ToString());
    }
}

the .cshtml template would then look exactly like in viewcomponent.

Ivy answered 30/10, 2017 at 9:10 Comment(2)
If you do this the ModelExpression ends up being the model, instead of the ModelExpression being an expression whose value is a property of the model, which means you're not referencing the actual property which is where any validation attributes would be defined so it's not obvious how this is useful.Ferreira
This was a long time ago, but if I recall correctly, the ModelExpression.Model points to the property, not the original model. So the inner asp-for="@Model.Model" is an expression, whose value is a property, that wraps property of the original model. If will remove this answer, if this is not correct.Ivy

© 2022 - 2024 — McMap. All rights reserved.