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 🙂
I couldn't found any htmlelemnt could be paresd to formdata when you submit the form
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: