ASP.NET MVC #Html.LabelFor(model => model.Name) Helper - c#

I'm trying to create a helper that will make my form controls in the whole website
Here is what i use till now
#helper editorField(System.Linq.Expressions.Expression<Func<Model, String>> o)
{
<div class="form-group">
#Html.LabelFor(o, new { #class = "col-md-4 control-label" })
#Html.ValidationMessageFor(o, "", new { #class = "col-md-6 col-xs-12 text-errer" })
<div class="col-md-5">
#Html.TextBoxFor(o, new { #class = "form-control" })
</div>
</div>
}
And the way i use it is
#editorField(model => model.Name)
#editorField(model => model.Email)
#editorField(model => model.PhoneNumber)
this make it really easy to change the style and layout of the whole website in 1 place
Now here is the problem
I need to make helper for each Model and data type
Im looking for a way to make it work something like this
#helper editorField(object o)
{
<div class="form-group">
#Html.LabelFor(o, new { #class = "col-md-4 control-label" })
#Html.ValidationMessageFor(o, "", new { #class = "col-md-6 col-xs-12 text-errer" })
<div class="col-md-5">
#Html.TextBoxFor(o, new { #class = "form-control" })
</div>
</div>
}
and it should work for all Models and datatypes

So basically what you are trying to achieve is a generic HTML helper. Unfortunately those are not supported directly in Razor views. But, you can use this workaround:
#functions
{
public HelperResult PasteEditorFor<TModel, TItem>(HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
return editorField(
html.LabelFor(expr, new { #class = "col-md-4 control-label" }),
html.ValidationMessageFor(expr, "", new { #class = "col-md-6 col-xs-12 text-errer" }),
html.TextBoxFor(expr, new { #class = "form-control" }));
}
}
#helper editorField(MvcHtmlString label, MvcHtmlString validationMessage, MvcHtmlString textBox)
{
<div class="form-group">
#label
#validationMessage
<div class="col-md-5">
#textBox
</div>
</div>
}
// usage sample
#PasteEditorFor(Html, m => m.LongDescription)
But as it is still not easy reusable (can't tell at the moment how to make it visible though all views), so I would recommend you to write usual HtmlHelper with usual static class syntax ( look at Alundra the dreamwalker comment) and add it's namespace to list of default one in WebConfig.

Related

How to access item 2 (or 3 or ...x) of an IEnumerable Model by ID

I am looking for a best practice. I have something that works, but I don't know if it's "right."
I am building a page where a customer can enter payment data for three different types of permits. I am sending an IEnumerable of my permit. It contains 3 null permits.
Clarification: An end user can have either 1, 2, or 3 permits, and each permit is of a unique type -- either "fishing," "boating," or "concessions."
I have built the view around this concept:
<div class="form-group row">
<div class="col-md-2" align="right" style="padding-top:5px">
<strong>#Html.LabelFor(model => model.FirstOrDefault().Notes, "Notes:", htmlAttributes: new { #class = "control-label" })</strong>
</div>
<div class="col-md-9">
<div class="row">
<div class="col-md-4" align="left">
#Html.EditorFor(model => model.Take(1).FirstOrDefault().Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Take(1).FirstOrDefault().PermitTypeId, "", new { #class = "text-danger" })
</div>
<div class="col-md-4" align="left">
#Html.EditorFor(model => model.Skip(1).Take(1).FirstOrDefault().Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Skip(1).Take(1).FirstOrDefault().PermitTypeId, "", new { #class = "text-danger" })
</div>
<div class="col-md-4" align="left">
#Html.EditorFor(model => model.Skip(2).Take(1).FirstOrDefault().Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Skip(2).Take(1).FirstOrDefault().PermitTypeId, "", new { #class = "text-danger" })
</div>
</div>
</div>
</div>
The customer enters the data for each appropriate payment type, the Save button sends the data back, and I check to see if each payment type is not null, then process as usual.
Is this the right way to do this? Is there a better way?
Any input would be appreciated.
Regards,
Carthax
You can use a foreach loop in your markup to loop through the enumerable.
<div class="form-group row">
<div class="col-md-2" align="right" style="padding-top:5px">
<strong>#Html.LabelFor(model => model.FirstOrDefault().Notes, "Notes:", htmlAttributes: new { #class = "control-label" })</strong>
</div>
<div class="col-md-9">
<div class="row">
foreach (var item in Model)
{
<div class="col-md-4" align="left">
#Html.EditorFor(modelItem => item.Notes, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.PermitTypeId, "", new { #class = "text-danger" })
</div>
}
</div>
</div>
</div>
IEnumerable cannot be accessed directly by index. The more readable cycle statement that you can use is a foreach, then check for the ID using an if statement.
As the worst complexity of this search is O(n) as it must cycle against every item, the forach solution is ok for me.
I don't know exactly your code base or model, but if you fail validation just if only one of the IDs is null the following code will be my best catch:
private bool ValidateCollection(IEnumerable<Item> itemsCollction)
{
foreach(Item item in itemsCollction)
{
if(item.ID == null)
return false; // At least one of the elements have ID null.
}
return true;
}
Just to say:
LINQ can also be used, but on average cases the execution is far slower than a foreach, because LINQ query creates a lot of overhead.
You can convert the enumerable to an array or list using constructors, but still it must cycle over every item in the collection. Then you can use a for to cycle the array that performs far better than foreach, but this can be useful only if you will reuse the result of the first conversion often. If not, you've wasted time convert IEnumerable to a List or Array.

How do I properly bind the html to the model data

So I am developing my first webpage using ASP.NET MVC and I managed to create a fully working registration page which send the data to the database and stored the user. Simple.
However I didn't really like the look and feel of the element it created for me so I thought I could change it out.
Original code WORKING
<div class="form-group">
#Html.LabelFor(model => model.Firstname, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Firstname, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Firstname, "", new { #class = "text-danger" })
</div>
</div>
My new code NOT WORKING
<div class="row">
<div class="input-group bb-none">
<i class="fas fa-address-card"></i>
<input type="text" placeholder="Firstname">
</div>
</div>
I am 99.9% sure that it's a binding issue. I want the data I put into my new textbox
<input type="text" placeholder="Firstname">
To carry over the data to the model.
What's the part that binds it in the first option?
Tag helpers will resolve down to html and put the property name as both the id and name within the input. The model binder then binds to that.
#Html.TextBoxFor( m => m.Firstname, new { placeholder = "Firstname" })

Adding Html required to TextAreaFor not working c#

I'm trying to add a required to my TextAreaFor, but it won't give the error message when i post it. I'm trying to do it on the followinng line:
#Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { #class = "form-control", required = "" } })
And this is my full code:
#using (Html.BeginForm("_Create", "Comments", FormMethod.Post))
{
#Html.HiddenFor(m => m.ThreadId)
<div class="form-group">
<div class="col-md-10">
#Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { #class = "form-control", required = "" } })
#Html.ValidationMessageFor(model => model.Content, "", new { #class = "text-danger"})
</div>
</div>
<div class="form-group">
<div>
<input type="submit" value="Post" class="btn btn-default" />
</div>
</div>
}
If anyone wanst to do it with html attribute,
#Html.TextAreaFor(model => model.Content, new { required = "required", htmlAttributes = new { #class = "form-control"} })
You don't need required as a html attribute. It should be a data annotation on the model.
[Required]
public string Content { get; set; }
#Html.TextAreaFor(model => model.Content, new { htmlAttributes = new { #class = "form-control", required = "" } })
Should be:
#Html.TextAreaFor(model => model.Content, new { #class = "form-control", required = "required" })
Or if you want to explicitly name the parameter your anonymous object is for:
#Html.TextAreaFor(model => model.Content, htmlAttributes: new { #class = "form-control", required = "" } })
But, if you do not use data-annotation, it could be even easier this way:
<textarea id="Content" name="Content" required class="form-control">#Model.Content</textarea>
(id attribute may be optional, depending on your usages.)
Side note: I tend to minimize uses of html helpers methods. For me, MVC is also about letting you control very precisely the browser client code, which is imo better done by writing it yourself. WebForm is, on this subject, about hiding most of browser client code handling.
Using extensively html helpers, built-in validation logic, and so on, may cause you to lose the precise control of how your page should work.

Refactor similar CHTML that renders varying properties using EditorFor and LabelFor

I am building a project with a lot of common code regarding the razor view.
Example:
<div class="form-group">
#Html.LabelFor(model => model.LayoutFrontAmount, htmlAttributes: new { #class = "control-label col-xs-12 col-sm-4 col-md-3" })
<div class="col-xs-4 col-sm-2 col-md-2 col-lg-1">
#Html.EditorFor(model => model.LayoutFrontAmount, new { htmlAttributes = new { #class = "form-control" } })
</div>
<div class="col-xs-12 col-md-8 col-sm-offset-4 col-md-offset-3">
<span class="help-block">#Html.ValidationMessageFor(model => model.LayoutFrontAmount, "", new { #class = "text-danger" })</span>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LayoutFrontBackAmount, htmlAttributes: new { #class = "control-label col-xs-12 col-sm-4 col-md-3" })
<div class="col-xs-4 col-sm-2 col-md-2 col-lg-1">
#Html.EditorFor(model => model.LayoutFrontBackAmount, new { htmlAttributes = new { #class = "form-control" } })
</div>
<div class="col-xs-12 col-md-8 col-sm-offset-4 col-md-offset-3">
<span class="help-block">#Html.ValidationMessageFor(model => model.LayoutFrontBackAmount, "", new { #class = "text-danger" })</span>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LayoutTRC, htmlAttributes: new { #class = "control-label col-xs-12 col-sm-4 col-md-3" })
<div class="col-xs-4 col-sm-2 col-md-2 col-lg-1">
#Html.EditorFor(model => model.LayoutTRC, new { htmlAttributes = new { #class = "form-control" } })
</div>
<div class="col-xs-12 col-md-8 col-sm-offset-4 col-md-offset-3">
<span class="help-block">#Html.ValidationMessageFor(model => model.LayoutTRC, "", new { #class = "text-danger" })</span>
</div>
</div>
The only thing that changes is the Model's property.
Is there a way to replace all the code with something like:
#TemplateName1(model => model.LayoutFrontAmount)
#TemplateName1(model => model.LayoutFrontBackAmount)
#TemplateName1(model => model.LayoutTRC)
You can create a HtmlHelper extension method that will generate all the html for a property, including the label and input element
using System;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace YourAssembly.Html
{
public static class BootstrapHelper
{
public static MvcHtmlString BootstrapEditorFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
MvcHtmlString label = LabelExtensions.LabelFor(helper, expression, new { #class = "control-label col-xs-12 col-sm-4 col-md-3" });
MvcHtmlString editor = EditorExtensions.EditorFor(helper, expression, new { htmlAttributes = new { #class = "form-control" } });
MvcHtmlString validation = ValidationExtensions.ValidationMessageFor(helper, expression, null, new { #class = "text-danger" });
// Build the input elements
TagBuilder editorDiv = new TagBuilder("div");
editorDiv.AddCssClass("col-xs-4 col-sm-2 col-md-2 col-lg-1");
editorDiv.InnerHtml = editor.ToString();
// Build the validation message elements
TagBuilder validationSpan = new TagBuilder("span");
validationSpan.AddCssClass("help-block");
validationSpan.InnerHtml = validation.ToString();
TagBuilder validationDiv = new TagBuilder("div");
validationDiv.AddCssClass("col-xs-12 col-md-8 col-sm-offset-4 col-md-offset-3");
validationDiv.InnerHtml = validationSpan.ToString();
// Combine all elements
StringBuilder html = new StringBuilder();
html.Append(label.ToString());
html.Append(editorDiv.ToString());
html.Append(validationDiv.ToString());
// Build the outer div
TagBuilder outerDiv = new TagBuilder("div");
outerDiv.AddCssClass("form-group");
outerDiv.InnerHtml = html.ToString();
return MvcHtmlString.Create(outerDiv.ToString());
}
}
}
Then you can register this in your web.config file (means you do not need #using ... in the view
<namespaces>
<add namespace="System.Web.Mvc" />
....
<add namespace="yourAssembly.Html " /> // add this
</namespaces>
Now in your main view your can generate all the html shown in your question with just the following 3 lines of code
#Html.BootstrapEditorFor(m => m.LayoutFrontAmount)
#Html.BootstrapEditorFor(m => m.LayoutFrontBackAmount)
#Html.BootstrapEditorFor(m => m.LayoutTRC)
And if you want this to be reusable across multiple projects, compile it in separate dll and add a reference to it in your projects.
Depends on what the types are for each of LayoutFrontAmount, LayoutFrontBackAmount, and LayoutTRC. Are these all strings? If so, you could have a common view file that stores the template for displaying each, and then display each of them by using #Html.Partial() in your primary view:
MyView.cshtml
#model string
<div class="form-group">
#Html.LabelFor(model => Model, htmlAttributes: new { #class = "control-label col-xs-12 col-sm-4 col-md-3" })
<div class="col-xs-4 col-sm-2 col-md-2 col-lg-1">
#Html.EditorFor(model => Model, new { htmlAttributes = new { #class = "form-control" } })
</div>
<div class="col-xs-12 col-md-8 col-sm-offset-4 col-md-offset-3">
<span class="help-block">#Html.ValidationMessageFor(model => Model, "", new { #class = "text-danger" })</span>
</div>
</div>
And then in your main view file you could render each of LayoutFrontAmount, LayoutFrontBackAmount, and LayoutTRC as follows:
#Html.Partial("MyView", Model.LayoutFrontAmount);
#Html.Partial("MyView", Model.LayoutFrontBackAmount);
#Html.Partial("MyView", Model.LayoutTRC);
If they are different types, that poses a bigger challenge.

Viewmodel values not being returned to controller

I found some similar posts to mine, but I couldn't find an answer that suits my needs for this.
Problem is as follows:
I have a viewmodel like this:
public class PrefViewModel
{
public SelectList countries { get; set; }
public SelectList Provincies { get; set; }
public ApplicationUser user { get; set; }
public Preference MyPref{ get; set; }
public int mycountry { get; set; }
public int myprovince { get; set; }
}
my cshtml looks like this:
#using (Html.BeginForm("Index","Preferences", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.user.UserName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
<span class="textvak">
#Html.TextBoxFor(model => model.user.UserName, new { disabled = "disabled", #readonly = "readonly" })
</span>
#Html.ValidationMessageFor(model => model.user.UserName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Email, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
<span class="textvak">
#Html.TextBoxFor(model => model.user.Email, new { disabled = "disabled", #readonly = "readonly" })
</span>
#Html.ValidationMessageFor(model => model.user.Email, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Unhashed, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.user.Unhashed, new { htmlAttributes = new { #class = "form-control", type = "password" } })
#Html.ValidationMessageFor(model => model.user.Unhashed, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Provincie.Land, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
#Html.DropDownListFor(model => model.mycountry, Model.countries, new { Name = "ddlLand", id = "ddlLanden", #class = "textvak" })
#Html.ValidationMessageFor(model => model.user.Provincie.Land, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.user.Provincie, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="control-label col-md-10">
#Html.DropDownListFor(model => model.myprovince, Model.Provincies, new { #class = "textvak" })
#Html.ValidationMessageFor(model => model.user.Provincie, "", new { #class = "text-danger" })
</div>
</div>
<br />
<table>
<tr>
<td>
<input type="submit" value=#Resources.Wijzig class="btn btn-default" />
</td>
</tr>
</table>
}
and in my controller I try to get the posted PrefViewModel back as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(PrefViewModel TestMymodel)
{
if (ModelState.IsValid)
{
int myCountry = TestMymodel.mycountry;
int myprovince = TestMymodel.myprovince;
}
return View();
}
My problem is that the PrefViewModel TestMymodel never is filled with the values I thought i'm posting back. Even more strange to me is the fact that I do get the Unhashed password back, but all other values are 0 or null.
I can put values inside the PrefViewModel to load the page and that works, but on Posting it's almost entirely empty.
Any ideas?
edit: Would it make any difference that I did change the default model to one that I made up myself? Cause when I Call the Create action for example, I do get the values back in my post (from create offcourse). I'm getting a bit desperate
edit2: this is what was posted:
__RequestVerificationToken:-JYcw0CH2zZ7WrGUiYJM6-R6VxfL41ykTD5EHUjgtyyFcN01AaUU61BYuaRNr4oPdEvDq09aYsOFdb8fObJTXMnTKulADVkGY8CrBG3U71QXw0g7Th86WKl1up4059Zy7mW0SlrWGJpehed586v_5g2
user.Unhashed:Jonas1234-
user.Unhashed:Jonas1234-
ddlLand:1
ddlProvincie:3
(can't add picture with my reputation, so here a link to the full post: http://postimg.org/image/id95wjcxp/ )
Ok, when I change the name of the dropdownlists to the PrefViewModel property name those values get returned correct.
It appears that you have overriden the names of the drop down lists to some values which are different than the property names in your view model. That's why the values are not successfully bound back. Make sure that your input fields respect the same names as the properties on your view model if you want the default model binder to be able to bind them back to the view model.
Also your username textbox has the disabled flag:
#Html.TextBoxFor(model => model.user.UserName, new { disabled = "disabled", #readonly = "readonly" })
so it will not be submitted back to the server. You might need to add an additional hidden field if you want those values to travel back. Or simply use readonly without disabled attribute. Both attributes prevent the user from modifying the value in the corresponding input field but in addition to that the disabled attribute strips it from the POST payload when the form is submitted.
So you may use:
#Html.TextBoxFor(model => model.user.UserName, new { #readonly = "readonly" })

Categories