ASP.NET MVC - Using Model in Custom HTML Helper [duplicate] - asp.net-mvc

This question already has answers here:
Refactor similar CHTML that renders varying properties using EditorFor and LabelFor
(2 answers)
Closed 6 years ago.
I am building an app with ASP.NET MVC and Bootstrap. In my app, I have a view with a Model that looks like this:
public class EntryModel
{
[Required(ErrorMessage="Please enter the name.")]
[Display(Name="Name")]
public string Name { get; set; }
[Required (ErrorMessage="Please enter the description.")]
[Display(Name = "Description")]
public string Description { get; set; }
}
In this app, I've also defined a custom html helper that looks like this:
public static class MyHelpers
{
public static MvcHtmlString MyTextBox(this HtmlHelper helper)
{
var sb = new StringBuilder();
sb.Append("<div class=\"form-group\">");
sb.Append("<label class=\"control-label\" for=\"[controlId]\">[labelValue]</label>");
sb.Append("<input class=\"form-control\" id=\"[controlId]\" name=\"controlId\" type=\"text\" value=\"[propertyValue]\">");
sb.Append("</div>");
return MvcHtmlString.Create(sb.ToString());
}
}
I'm using this helper and model in my Razor view, like this:
#model EntryModel
<h2>Hello</h2>
#using (Html.BeginForm("Add", "Entry", new {}, FormMethod.Post, new { role="form" }))
{
#Html.MyTextBox()
}
I am trying to generate the labelValue, controlId, and propertyValue values in the helper from the properties of the Model. For example, I'd like to use #Html.MyTextBoxFor(m => m.Name) and have the helper generate something like this:
<div class="form-group">
<label class="control-label" for="Name">Name</label>");
<input class="form-control" id="Name" name="Name" type="text" value="Jon">
</div>
Essentially, I'm not sure how to get my model information into my html helper.

Use this example as reference:
public static MvcHtmlString AutoSizedTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
var attributes = new Dictionary<string, Object>();
var memberAccessExpression = (MemberExpression)expression.Body;
var stringLengthAttribs = memberAccessExpression.Member.GetCustomAttributes(
typeof(System.ComponentModel.DataAnnotations.StringLengthAttribute), true);
if (stringLengthAttribs.Length > 0)
{
var length = ((StringLengthAttribute)stringLengthAttribs[0]).MaximumLength;
if (length > 0)
{
attributes.Add("size", length);
attributes.Add("maxlength", length);
}
}
return helper.TextBoxFor(expression, attributes);
}
And you can call it in the view like this:
#Html.AutoSizedTextBoxFor(x => x.Address2)

Related

Create an ASP.NET MVC Html helper similar to DropDownListFor

In certain cases I want to display SelectList object not using DropDownListFor helper. Instead, I want to create a helper that iterates over the SelectListItems, and draw something different.
I have created an EditorTemplate:
#model RadioButtonOptions
<div class=" switch-field noselect" style="padding-left: 0px;">
#foreach (SelectListItem op in Model.Values.Items)
{
var idLabelF = ViewData.TemplateInfo.GetFullHtmlFieldId("") + "_" + op.Value;
var esChecked = "";
if (op.Selected)
{
esChecked = "checked";
}
<input type="radio" id="#idLabelF" name="#(ViewData.TemplateInfo.GetFullHtmlFieldName(""))" value="#op.Value" #esChecked />
<label for="#idLabelF" style="width: 100px;">#op.Text</label>
}
</div>
The RadioButtonOptions class is a ViewModel:
public class RadioButtonOptions
{
public SelectList Values { get; set; }
}
The final resul looks like this:
My ViewModel is like this (simplified):
public class MainVisitVM
{
public MainVisit Visit { get; set; }
public RadioButtonOptions VisitOptions { get; set; }
}
And I use it in Razor View as:
<div class="clearfix">
#Html.LabelFor(x=> x.Visit.Tipo)
<div class="input">
#Html.EditorFor(x=> x.VisitOptions ) //HERE
</div>
</div>
The problem I have is that I want this to work more like the DropDownListFor, so the lamda expresion I pass is the property holding the selected value, and then just pass the SelectList object (or a custom list).
<div class="clearfix">
#Html.LabelFor(x=> x.Visit.Tipo)
<div class="input">
#Html.CustomDropDownListFor(x=> x.Visit.Tipo, Model.VisitOptions ) //This would be ideal!!
</div>
</div>
So, I think doing this using EditorTemplates will not be possible.
Any idea in how to accomplish this?
Thanks to #StephenMuecke suggestion, I ended up with this HtmlHelper extension method:
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
SelectList listOfValues)
{
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
if (listOfValues == null) return MvcHtmlString.Create(string.Empty);
var wrapperDiv = new TagBuilder("div");
wrapperDiv.AddCssClass("switch-field noselect");
wrapperDiv.Attributes.Add("style", "padding-left: 0px;");
var sb = new StringBuilder();
foreach (SelectListItem item in listOfValues)
{
var idLabelF = htmlFieldName.Replace(".","_") + "_" + item.Value;
var label = htmlHelper.Label(idLabelF, item.Text, new { style = "width: 100px;" }).ToHtmlString();
var radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = idLabelF }).ToHtmlString();
sb.AppendFormat("{0}{1}", radio, label);
}
wrapperDiv.InnerHtml = sb.ToString();
return MvcHtmlString.Create(wrapperDiv.ToString());
}
Not particulary proud of my htmlFieldName.Replace(".","_"), but works.

How to add "required" attribute to mvc razor viewmodel text input editor

I have the following MVC 5 Razor HTML helper:
#Html.TextBoxFor(m => m.ShortName,
new { #class = "form-control", #placeholder = "short name"})
I need this field to be required (i.e. have a red outline when user navigates out without putting a value inn). In a WebForms HTML 5 I could just say <input type="text" required /> to have this effect.
What is the proper syntax to accomplish this in a Razor syntax?
You can use the required html attribute if you want:
#Html.TextBoxFor(m => m.ShortName,
new { #class = "form-control", placeholder = "short name", required="required"})
or you can use the RequiredAttribute class in .Net. With jQuery the RequiredAttribute can Validate on the front end and server side. If you want to go the MVC route, I'd suggest reading Data annotations MVC3 Required attribute.
OR
You can get really advanced:
#{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = new Dictionary<string, object>(
Html.GetUnobtrusiveValidationAttributes(ViewData.TemplateInfo.HtmlFieldPrefix));
attributes.Add("class", "form-control");
attributes.Add("placeholder", "short name");
if (ViewData.ModelMetadata.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(RequiredAttribute), true)
.Select(a => a as RequiredAttribute)
.Any(a => a != null))
{
attributes.Add("required", "required");
}
#Html.TextBoxFor(m => m.ShortName, attributes)
}
or if you need it for multiple editor templates:
public static class ViewPageExtensions
{
public static IDictionary<string, object> GetAttributes(this WebViewPage instance)
{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = new Dictionary<string, object>(
instance.Html.GetUnobtrusiveValidationAttributes(
instance.ViewData.TemplateInfo.HtmlFieldPrefix));
if (ViewData.ModelMetadata.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(RequiredAttribute), true)
.Select(a => a as RequiredAttribute)
.Any(a => a != null))
{
attributes.Add("required", "required");
}
}
}
then in your templates:
#{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = this.GetAttributes();
attributes.Add("class", "form-control");
attributes.Add("placeholder", "short name");
#Html.TextBoxFor(m => m.ShortName, attributes)
}
Update 1 (for Tomas who is unfamilar with ViewData).
What's the difference between ViewData and ViewBag?
Excerpt:
So basically it (ViewBag) replaces magic strings:
ViewData["Foo"]
with magic properties:
ViewBag.Foo
On your model class decorate that property with [Required] attribute. I.e.:
[Required]
public string ShortName {get; set;}
A newer way to do this in .NET Core is with TagHelpers.
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro
Building on these examples (MaxLength, Label), you can extend the existing TagHelper to suit your needs.
RequiredTagHelper.cs
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.Linq;
namespace ProjectName.TagHelpers
{
[HtmlTargetElement("input", Attributes = "asp-for")]
public class RequiredTagHelper : TagHelper
{
public override int Order
{
get { return int.MaxValue; }
}
[HtmlAttributeName("asp-for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
if (context.AllAttributes["required"] == null)
{
var isRequired = For.ModelExplorer.Metadata.ValidatorMetadata.Any(a => a is RequiredAttribute);
if (isRequired)
{
var requiredAttribute = new TagHelperAttribute("required");
output.Attributes.Add(requiredAttribute);
}
}
}
}
}
You'll then need to add it to be used in your views:
_ViewImports.cshtml
#using ProjectName
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper "*, ProjectName"
Given the following model:
Foo.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace ProjectName.Models
{
public class Foo
{
public int Id { get; set; }
[Required]
[Display(Name = "Full Name")]
public string Name { get; set; }
}
}
and view (snippet):
New.cshtml
<label asp-for="Name"></label>
<input asp-for="Name"/>
Will result in this HTML:
<label for="Name">Full Name</label>
<input required type="text" data-val="true" data-val-required="The Full Name field is required." id="Name" name="Name" value=""/>
I hope this is helpful to anyone with same question but using .NET Core.
I needed the "required" HTML5 atribute, so I did something like this:
<%: Html.TextBoxFor(model => model.Name, new { #required = true })%>
#Erik's answer didn't fly for me.
Following did:
#Html.TextBoxFor(m => m.ShortName, new { data_val_required = "You need me" })
plus doing this manually under field I had to add error message container
#Html.ValidationMessageFor(m => m.ShortName, null, new { #class = "field-validation-error", data_valmsg_for = "ShortName" })
Hope this saves you some time.

submit complex model with prefix

I'd like to know your opinion about this way used to submit complex models in MVC
For example, if I have a complex model like this:
public class TOIFormDTO
{
public TradeFormDTO Trade { get; set; }
public OrderFormDTO Order { get; set; }
public ItemFormDTO Item { get; set; }
public TOIFormDTO() {
Trade = new TradeFormDTO();
Order = new OrderFormDTO();
Item = new ItemFormDTO();
}
}
And, I bind it to the view in the form:
#using (Html.BeginForm("SaveTOI", "Item", FormMethod.Post, new {id = "FormTOI" }))
{
<div id="tabs" style="width:1100px; height:500px">
<ul>
<li>Trade</li>
<li>Order</li>
<li>Item</li>
</ul>
<fieldset>
<legend></legend>
<div id="tabTrade">
#Html.PartialForCustomPrefix(model => model.Trade, "~/Views/Trade/Trade.cshtml", "Trade")
</div>
<div id="tabOrder" style="display: none">
#Html.PartialForCustomPrefix(model => model.Order, "~/Views/Order/Order.cshtml", "Order")
</div>
<div id="tabItem">
#Html.PartialForCustomPrefix(model => model.Item, "~/Views/Item/Item.cshtml", "Item")
</div>
</fieldset>
</div>
}
By putting a prefix this way:
public static MvcHtmlString PartialForCustomPrefix<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName, string CustomPrefix)
{
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = CustomPrefix
}
};
return helper.Partial(partialViewName, model, viewData);
}
This way, when the form is submitted the model is correctly 'recognized' by the action:
public ActionResult SaveTOI(TOIFormDTO toiToSave)
{
}
Do you think this is a good way to procede, is it solid?
Or do you recognized some potential failure?

MVC Binding Dictionary

I am following this tutorial. Can anyone please explain to me how I can make the textboxes bind back to the Dictionary? Right now all that happens is the data is being displayed in the textboxes - but if I change the textboxes, how do I bind back to the object?. What am I missing? Below is my code:
<input type="text" name="#Model.ExpirimentToRemove[i].Value.Title" value="#Model.ExpirimentToRemove[i].Value.Title"/>
<input type="text" name="#Model.ExpirimentToRemove[i].Value.PreviewDescription" value="#Model.ExpirimentToRemove[i].Value.PreviewDescription"/>
<input type="text" name="#Model.ExpirimentToRemove[i].Value.FullDescription" value="#Model.ExpirimentToRemove[i].Value.FullDescription"/>
The name of your text input field is wrong. You have put the value of the model instead of naming it appropriately as explained in the Hanselman's blog post.
So let's assume that you have some view model:
public class ItemViewModel
{
public string Title { get; set; }
public string PreviewDescription { get; set; }
public string FullDescription { get; set; }
}
and a main view model containing the dictionary:
public class MyViewModel
{
public Dictionary<string, ItemViewModel> ExpirimentToRemove { get; set; }
}
and your POST controller action takes this view model as parameter:
[HttpPost]
public ActionResult Remove(MyViewModel model)
{
...
}
In order to correctly bind to this view model you could have the following:
#model MyViewModel
#using (Html.BeginForm())
{
#for (var i = 0; i < Model.ExpirimentToRemove.Count; i++)
{
<div>
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Key",
Model.ExpirimentToRemove[i].Key
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.Title",
Model.ExpirimentToRemove[i].Value.Title
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.PreviewDescription",
Model.ExpirimentToRemove[i].Value.PreviewDescription
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.FullDescription",
Model.ExpirimentToRemove[i].Value.FullDescription
)
</div>
}
<p><button type="submit">OK</button></p>
}
In this example the key of the dictionary is a simple string value but you could also use a complex type.

MVC, CheckboxList extension html helper..further issues

Good afternoon
I'm hoping this is going to be nice and a simple f**k up by myself but I've gone in deep and currently can't swim.
Ok, previously I asked this question and as such I have implemented the following:
Model
public class CorporateDetails
{
public Guid? Id { get; set; }
[Key]
public int CorporateDetailId { get; set; }
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual EmsType EmsType { get; set; }
}
public class EmsType
{
[Key]
public int? EmsId { get; set; }
public string EmsName { get; set; }
public virtual ICollection<EmsType> EmsTypes { get; set; }
}
Controller
public ActionResult Create()
{
ViewBag.EmsId = new MultiSelectList(db.EmsTypes, "EmsId", "EmsName");
return View();
}
View
<fieldset>
<legend>CorporateDetails</legend>
<div class="editor-label">
#Html.LabelFor(model => model.EmsId, "EmsType")
</div>
<div class="editor-field">
#Html.DropDownList("EmsId", String.Empty)
#Html.ValidationMessageFor(model => model.EmsId)
</div>
<div class="editor-label">
#Html.CheckBoxListFor(model => model.EmsId, (MultiSelectList) ViewBag.EmsId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Where this CheckBoxListFor uses the following extension:
public static class HtmlHelper
{
//Extension
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty[]>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
TProperty[] list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder divTag = new TagBuilder("div");
divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
divTag.InnerHtml += String.Format("<div><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></div>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(divTag.ToString());
}
}
So what's the problem?
Well when I run it I get a nasty error telling me:
"CS0411: The type arguments for method
'Extensions.HtmlHelper.CheckBoxListFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>,
System.Web.Mvc.MultiSelectList, object)' cannot be inferred from the
usage. Try specifying the type arguments explicitly."
Can someone please point out where I have gone wrong as I'm going round in circles and not sure what I have missed.
Any help gratefully appreciated.
Ricardo,
I have shared some of my own MVC helpers on GitHub. Please feel free to browse & use the code as needed. One of the items in the project is a CheckBoxListItemFor helper, it was created specifically to create checkbox lists. Look under InputExtensions.cs
https://github.com/EdCharbeneau/EJC.UIExtensions/tree/master/EJC.UIExtensions/Html
The problem with this here is that my original EmsId is a nullable int and not an int array as required and hence the error.
Please refer to my answer given here for a full explanation of how this can be achieved.

Resources