Search code examples
jquery.net-corerazorasp.net-core-mvc

Model is not bounded in the custom HTML in Razor


Good day fellow developers, I am just learning ASP.NET Core MVC and now I am facing a problem where the model is not bound with the input upon submitting the form. I created the input, specifically a dropdown button through a custom HTML Helper.

I have already tried different solutions, but still the issue persist.

Here is my code:

BootstrapHtml.cs

public static IHtmlContent BootstrapDropdownFor<TModel, TValue>(
this IHtmlHelper<TModel> htmlHelper, 
Expression<Func<TModel, TValue>> expression, 
IEnumerable<SelectListItem> list)
{
    var expressionHelper = htmlHelper.ViewContext.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider)) as ModelExpressionProvider;
    var fieldName = expressionHelper.GetExpressionText(expression);
    var fullBindingName = htmlHelper.ViewContext.HttpContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
    var fieldId = TagBuilder.CreateSanitizedId(fullBindingName, "a");
    var metaData = expressionHelper.CreateModelExpression(htmlHelper.ViewData, expression);
    var value = metaData.Model;

    var button = new TagBuilder("input")
                     {  
                         Attributes = 
                             {
                                 { "id", fieldId },
                                 { "name", fullBindingName },
                                 { "type", "button" },
                                 { "data-bs-toggle", dropdown },
                                 { "value", value == null > string.empty : value.ToString() },
                             }
                     }

      var wrapper = new TagBuilder("div");
      wrapper.AddCssClass("dropdown");
   
      wrapper.InnerHtml.AppendHtml(button);
      button.InnerHtml.AppendHtml(BuildDropdownList(id, list));

      using (var writer = new StringWriter())
      {
          wrapper.WriteTo(writer, HtmlEncoder.Default);
          string asd = writer.ToString();
          return new HtmlString(asd);
      }
}

private static TagBuilder BuildDropdownList(string id, IEnumerable<SelectListItem> selectListItems) 
{
    var ul = new TagBuilder("ul")
                 { 
                     Attributes = 
                         {
                             { "class", "dropdown-menu" },
                             { "aria-labelledby", id }
                         }
                 };
    var listItem = new TagBuilder("li");
    listItem.Attributes.Add("class", "dropdown-item");
 
    foreach(var item in selectListItems)
    {
        var value = string.IsNullOrEmpty(item.value) ? string.Empty : item.Value;
        var disabledClass = item.Disabled ? "disabled" : string.Empty;
        ul.InnerHtml.AppendHtml($"<li value=\"{value}\" class=\"{disabledClass} dropdown-item\">" + $"<span>{item.Text}</span>" + "</li>");
    }
  
    return ul;
}

This is how I use the above element in Razor page.

Test.cshtml

@model SomeModel

<form data-ajax="true" data-ajax-url="AreaName/ControllerName/ActionName" data-ajax-method="POST" data-ajax-success="OnSuccess(data)">

   @Html.BootstrapDropdownFor(a => a.DummyProperty, (IEnumerable<SelectListItem>)ViewData["ViewDataWithList"])

<button type="Submit">Create Record</button>
</form>

Upon clicking the "Create Record" Button I did not see the value selected from the custom HTML. Verified under Developer tool of the browser > Network > FormData (from Payload of the API). I am assuming the custom HTML was not bounded in the property "DummyProperty" under "SomeModel" model.

Any help/tips/feedback is greatly appreciated. Thank you in advance 🙂


Solution

  • I couldn't found any htmlelemnt could be paresd to formdata when you submit the form

    enter image description here

    If you want to keep your design partern without <select>,you would need a hidden input and some js/jquery codes to hold your selected value

    For Example,I tried as below:

    public static class MyExtension
        {
            public static IHtmlContent BootstrapDropdownFor<TModel, TValue>(
    this IHtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TValue>> expression,
    IEnumerable<SelectListItem> list)
            {
    
                var expressionHelper = htmlHelper.ViewContext.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider)) as ModelExpressionProvider;
                var fieldName = expressionHelper.GetExpressionText(expression);
                var fullBindingName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
                var fieldId = TagBuilder.CreateSanitizedId(fullBindingName, "a");
                var metaData = expressionHelper.CreateModelExpression(htmlHelper.ViewData, expression);
                var value = metaData.Model;
    
                var button = new TagBuilder("input")
                {
                    Attributes =
                    {
                      {"id", fieldId},
                      {"name", fullBindingName},
                      { "data-bs-toggle", "dropdown" },
                      {"type", "button"},
                      {"value", value == null ? "Select" : value.ToString()},
                      {"class", "myclass"}
                     }
                };
                var hiddeninput = new TagBuilder("input")
                {
                    Attributes =
                    {
                      {"id", fieldId+"hd"},
                      {"name", fullBindingName},
                      {"type", "text"},
                      {"hidden" ,"true"},
                      {"value","Value" },
                      {"class", "myclasshd"}
                    }
                };
    
                var wrapper = new TagBuilder("div");
                wrapper.AddCssClass("dropdown");
                wrapper.InnerHtml.AppendHtml(button);
                wrapper.InnerHtml.AppendHtml(hiddeninput);
                button.InnerHtml.AppendHtml(BuildDropdownList(fieldId, list));
    
                using var writer = new StringWriter();
    
                wrapper.WriteTo(writer, HtmlEncoder.Default);
                string asd = writer.ToString();
                return new HtmlString(asd);
    
            }
    
            private static TagBuilder BuildDropdownList(string id, IEnumerable<SelectListItem> selectListItems)
            {
    
                var ul = new TagBuilder("ul")
                {
                    Attributes =
                    {
                     {"class", "dropdown-menu"},
                     {"aria-labelledby", id}
                     }
                };
                var listItem = new TagBuilder("li");
                listItem.Attributes.Add("class", "dropdown-item");
    
                foreach (var item in selectListItems)
                {
                    var value = string.IsNullOrEmpty(item.Value) ? string.Empty : item.Value;
                    var disabledClass = item.Disabled ? "disabled" : string.Empty;
                    ul.InnerHtml.AppendHtml($"<li value=\"{value}\" class=\"{disabledClass} dropdown-item\">" + $"<span>{item.Text}</span>" + "</li>");
                }
    
                return ul;
            }
        }
    

    Model:

        public class SomeClass
        {
            public string? SomeProp { get; set; }
           
        }
    

    View:

    @model SomeClass
    <form method="POST">
    
        @Html.BootstrapDropdownFor(a => a.SomeProp, new List<SelectListItem>(){
        new SelectListItem(){Text="Text1",Value="Text1"},
        new SelectListItem(){Text="Text2",Value="Text2"},
        new SelectListItem(){Text="Text3",Value="Text3"}
        })
    
        <button type="Submit">Create Record</button>
    </form>
    
    
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script>
        $("li.dropdown-item").click(function () {
            var val = $(this).children('span').html()
            console.log(val);
            $(".myclass").val(val)
            $(".myclasshd").val(val)
        })
    </script>
    

    Result:

    enter image description here