Lexigum
Lexigum

Reputation: 11

Dynamic Razor Components

ok so i am trying to create a some generic components but i am having some issues passing the AspFor value in the .cshtml

i created an input model and the partial

public class InputModel : ComponentModel
{
    public string AspFor {  get; set; }
    public string Value { get; set; } 
    public string? Disabled { get; set; } 
}
public class ComponentModel
{
    public string Id { get; set; }
    public string Label { get; set; }
    public bool IsMandatory { get; set; }
    public ComponentEnum Componente { get; set; }
}
public enum ComponentEnum
{
    Input
}

public static class ComponentEnumExtensions
{
    public static string GetPath(this ComponentEnum component)
    {
        return component switch
        {
            ComponentEnum.Input => "*Path_To_Input.cshtml*"
            _ => string.Empty
        };
    }
}

<div id="@Model.Id-container" class="form-select-container">
    <label class="text-blue800 steps-subtitle">@(Model.Label)</label>
    @if (Model.IsMandatory)
    {
        <span class="text-primary">(Mandatory)</span>
    }
    <label asp-for="@Model.AspFor" class="form-label">@Model.Label</label>
    <input asp-for="@Model.AspFor" class="form-control" value="@Model.Value" />

</div>

i am then creating an instance of the input model and rendering it in the page with this model:

public class RegistoViewModel
{
    public Contactos Contactos { get; set; }
}

public class Contactos
{
    [Required(ErrorMessage = "Email is required")]
    [EmailAddress(ErrorMessage = "Invalid email address")]
    public string Email { get; set; } = string.Empty;
}
@{ 
    var input = new InputModel
    {
                    Id = "inputId",
                    Label = "Email",
                    Componente = ComponentEnum.Input,
                    AspFor = "Contactos.Email",
                    Value = Model.Contactos.Email
    };
}
 
 @await Html.PartialAsync(ComponentEnum.Input.GetPath(), input)

can someone help me understand how can i pass the AspFor dynamicaly and be able to create a generic InputModel

when i create the Div directly in the page it works ok:

<div class="mt-4">
                <div id="@Model.Test.Id-container" class="form-select-container">
                    <label class="text-blue800 steps-subtitle">@(Model.Test.Label)</label>
                    @if (Model.Test.IsMandatory)
                    {
                        <span class="text-primary">(Obrigatório)</span>
                    }
                    <label asp-for="Contactos.Email" class="form-label">Email</label>
                    <input asp-for="Contactos.Email" class="form-control" value="@Model.Test.Value" />

                </div>
</div>
public class RegistoViewModel
{
    public Contactos Contactos { get; set; }
    public InputModel Test{ get; set; }
}

public class Contactos
{
    [Required(ErrorMessage = "Email is required")]
    [EmailAddress(ErrorMessage = "Invalid email address")]
    public string Email { get; set; } = string.Empty;
}

Upvotes: 1

Views: 52

Answers (1)

Qiang Fu
Qiang Fu

Reputation: 9054

You could try create a custom render helper which accept string modelname and propertyname:

    public static class HtmlHelpers
    {
        public static IHtmlContent CustomInputFor(
            this IHtmlHelper htmlHelper,
            string modelTypeName,
            string propertyName,
            object? value = null,
            object? htmlAttributes = null)
        {
            // Get the Type from the model type name
            Type? modelType = Type.GetType(modelTypeName);
            if (modelType == null)
            {
                throw new ArgumentException($"Model type '{modelTypeName}' not found.");
            }

            // If no value is provided, try to create an instance and get the default property value
            if (value == null)
            {
                object? modelInstance = Activator.CreateInstance(modelType);
                if (modelInstance != null)
                {
                    PropertyInfo? property = modelType.GetProperty(propertyName);
                    if (property != null)
                    {
                        value = property.GetValue(modelInstance);
                    }
                }
            }

            // Convert attributes to HTML attributes
            var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);

            // Generate the HTML input
            TagBuilder inputTag = new("input");
            inputTag.MergeAttribute("name", propertyName);
            inputTag.MergeAttribute("value", value?.ToString());
            inputTag.MergeAttributes(attributes, replaceExisting: true);

            // Render the input
            using var writer = new System.IO.StringWriter();
            inputTag.WriteTo(writer, System.Text.Encodings.Web.HtmlEncoder.Default);
            return new HtmlString(writer.ToString());
        }
    }

Then use the helper in parital view like

<div id="@Model.Id-container" class="form-select-container">
    <label class="text-blue800 steps-subtitle">@Model.Label</label>
    @if (Model.IsMandatory)
    {
        <span class="text-primary">(Mandatory)</span>
    }
    @Html.CustomInputFor("ProjectName.Models.Contactos", "Email","[email protected]", new { @class = "form-control" })
</div>

Just note that Type.GetType(modelTypeName); requires format like "namespace.Contactos"

Upvotes: 0

Related Questions