HtmlHelper Generate Validation Markup in Controller - asp.net-mvc

This is somehow related to The Question
I am using jquery unobtrusive validation. I have the following model
public class MyModel
{
[Required]
public int ID { get; set; }
[MinLength(2)]
public string Description { get; set; }
}
I want to generate an html markup with validation(This is what I want to generate my self using code)
<input class="FormElement form-control valid" type="text" data-val="true" data-val-required="The ID field is required." id="ID" name="ID">
<input class="FormElement form-control valid" type="text" data-val="true" data-val-minlength="The field Description must be a string or array type with a minimum length of '2'." data-val-minlength-min="2" id="Description" name="Description" value="description changed 6">
My question is how Can I manually generate the markup from the model attributes inside my controller.
So My code will simply look like this
private readonly IHtmlGenerator htmlGenerator;
private readonly IHtmlHelper helper;
public MyController(IHtmlGenerator htmlGenerator, IHtmlHelper hh)
{
if (htmlGenerator == null)
throw new ArgumentNullException("htmlGenerator");
this.htmlGenerator = htmlGenerator;
this.helper = hh;
}
public IActionResult GenerateHtml()
{
HtmlHelper tempHelper= this.HtmlHelper;
// Generate the markup for ID and Description here from model MyModel(With VALIDATION)
}

Related

MVC Core - strange view rendering issue

I am using MVC to display a simple form in a view:
ViewModel:
public class CreateSaleViewModel
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
public bool ShowInstoreConfirmDetails { get; set; }
}
Controller action:
[HttpGet]
public IActionResult CreateSale()
{
return View(new CreateSaleViewModel());
}
View:
#model CreateSaleViewModel
<form asp-controller="Sales" asp-action="CreateSale" method="post">
<input asp-for="OrderId" />
<input asp-for="TotalAmount" />
<button type="submit" name="CreateSale" id="CreateSale">
button
</button>
</form>
I then post to a new view, where the same details need to be entered. To do this I store the old values in hidden inputs and provide another form to re-enter the details.
ViewModel:
public class ConfirmDetailsViewModel
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
public string ConfirmOrderId { get; set; }
public decimal ConfirmTotalAmount { get; set; }
}
Controller:
[HttpPost("Confirmdetails")]
public IActionResult ConfirmDetails(CreateSaleViewModel model)
{
var viewModel = new ConfirmDetailsViewModel
{
ConfirmOrderId = model.OrderId,
ConfirmTotalAmount = model.TotalAmount,
OrderId = string.Empty,
TotalAmount = 0.0m
};
return View("ConfirmDetails", viewModel);
}
View:
#model ConfirmDetailsViewModel
<form asp-controller="Sales" asp-action="Summary" method="post">
<input type="hidden" value="#Model.ConfirmOrderId" id="OrderIdConfirm" />
<input type="hidden" value="#Model.ConfirmTotalAmount" id="TotalAmountConfirm" />
<input type="hidden" value="#Model.OrderId" id="banana" />
<input asp-for="OrderId" />
<input asp-for="TotalAmount" />
<button type="submit" name="CreateSale" id="CreateSale">
button
</button>
</form>
My problem is on the confirmdetails view orderId and TotalAmount retain the values that were posted from the previous page.
I have debugged the controller and can see the ConfirmOrderId and ConfirmTotalAmount properties have the correct values, and also OrderId and TotalAmount are empty strign and 0 respectively.
Even stranger is that
<input type="hidden" value="#Model.OrderId" id="banana" />
Has the correct value of "".
Does anyone know what is causing this issue?
MVC stores the posted back values in ModelState.
These values are used by default in #Html helpers - as a convenience. This allows the values of hidden form fields to be preserved through postbacks, even if they don't have properties in the view-model.
Unfortunately what is usually a convenience turns into a headache, if you try to modify the model's properties within the action. Helpers take their values from ModelState, ignoring the updated view-model.
To solve this, call ModelState.Clear()
removes all the posted back values from ModelState
the helpers will now use the values from the view-model.
Controller:
[HttpPost]
public IActionResult ConfirmDetails(CreateSaleViewModel model)
{
var viewModel = new ConfirmDetailsViewModel
{
ConfirmOrderId = model.OrderId,
...
};
ModelState.Clear(); // force asp-helpers to use the updated model's values
return View("ConfirmDetails", viewModel);
}

Bind List of objects to a form [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 4 years ago.
I have a class of Signature objects:
public class Signature
{
public int SignatureID { get; set; }
public int FormID { get; set; }
public string Title { get; set; }
public string Email { get; set; }
[Display(Name = "Signed Date:")]
public DateTime? Date { get; set; }
}
I have a Form.cs class that has a virtual list of signatures
public virtual List<Signature> Signatures { get; set; }
In my controller, I populate the list by:
form.Signatures = repository.Signatures.Where(s => s.FormID == form.FormID).ToList();
In my Form View, I display a list of the associated signatures:
#foreach (var signature in Model.Signatures)
{
<div class="text-center">
<label asp-for="#signature.Title"></label>
<input asp-for="#signature.Title" />
<label asp-for="#signature.Email"></label>
<input asp-for="#signature.Email" />
<label asp-for="#signature.Date"></label>
<input disabled asp-for="#signature.Date">
</div>
}
However, I don't know how to update the associated signatures upon my POST method of the form. For example, if I change the Email property of a signature and POST the form, the model does not bind this change into the Form object. In this case, form.Signatures is null.
How can I ensure changes to the <List>Signature items associated with the form are updated on POST?
Use the for loop to generate the elements, as it would add indexing to the property names which is used by model binder to bind to List on the post which does not work with the foreach:
#for (int i=0; i< Model.Signatures.Count; i++)
{
<div class="text-center">
<label asp-for="#Model.Signatures[i].Title"></label>
<input asp-for="#Model.Signatures[i].Title" />
<label asp-for="#Model.Signatures[i].Email"></label>
<input asp-for="#Model.Signatures[i].Email" />
<label asp-for="#Model.Signatures[i].Date"></label>
<input disabled asp-for="#Model.Signatures[i].Date">
</div>
}
Now the elements would be rendered with names like Signatures[0].Title, Signatures[1].Title and the model binder can bind it to the model on post.
Hope it helps.

ASP.NET jQuery unobtrusive model validation injecting data-val-required without required data annotation

Why is my strongly type html having a data-val-required attribute even though I didn't specified in my model property to be required? Here is my model class
public class Document
{
public string Name { get; set; }
public bool IsConfirmed { get; set; }
}
Here is the html being rendered:
<input class="full-width" data-val="true" data-val-required="The IsConfirmed field is required." id="Document_0__IsConfirmed" name="Document.IsConfirmed" type="checkbox" value="true" title="" autocomplete="off" />
<input name="Document.IsConfirmed" type="hidden" value="false" />
Your help will be much appreciated.
Change your model to this
public class Document
{
public string Name { get; set; }
public Nullable<bool> IsConfirmed { get; set; }
}

bool property without Required attribute is required

have a simple ViewModel with three properties like so:
public bool RememberMe { get; set; }
In my view I have a simple #Html.CheckBoxFor(p => p.RememberMe)
I am using Client Side validation enabled using Html.EnableClientValidation();
Why is this being set as a required field?
Try a nullable bool.
public bool? RememberMe { get; set; }
With reference types there are a number of default validation rules applied. If a reference type is not nullable, it becomes required by default. The best illustration of this is if you use a textbox to display some properties (not something you would do in your site, but good for testing purposes):
Model:
public bool? MyBool { get; set; }
public int MyInt { get; set; }
View:
#Html.TextBoxFor(p => p.MyBool)
#Html.TextBoxFor(p => p.MyInt)
You can see from a view source what happens on the page:
<input id="MyNullBool" name="MyNullBool" type="text" value="">
<input data-val="true" data-val-required="The MyBool field is required." id="MyBool" name="MyBool" type="text" value="False">
<input data-val="true" data-val-number="The field MyInt must be a number." data-val-required="The MyInt field is required." id="MyInt" name="MyInt" type="text" value="0">
The nullable bool has no validation attributes, whereas the bool has a data-val-required tag. The int has a data-val-required tag and a data-val-number attribute
Of course, on a checkbox this is all pretty redundant as it can only be checked (true) or not checked (false) so a required tag isn't much use.
#Html.CheckBoxFor(c => c.TermsAndConditions, new { required = "required" })
#Html.ValidationMessageFor(c => c.TermsAndConditions, "you must agree to terms and conditions of Service.)"

Knockout.js bind array of objects to repeated <select>'s

I've got this Knockout.js view model:
{
"LanguageFromTos":
[{
"LanguageFromToId":0,
"LanguageFromId":2,
"LanguageFrom":null,
"AllLanguagesFrom":[
{"Selected":false,"Text":"English","Value":"1"},
{"Selected":false,"Text":"French","Value":"2"},
{"Selected":false,"Text":"Spanish","Value":"3"}
],
"LanguageToId":1,
"LanguageTo":null,
"AllLanguagesTo":[
{"Selected":false,"Text":"English","Value":"1"},
{"Selected":false,"Text":"French","Value":"2"},
{"Selected":false,"Text":"Spanish","Value":"3"}
],
"Users":null
}]
}
And these html <select>s:
<div class="LanguageFromToRow">
<input type="hidden" name="languageFromTos.index" autocomplete="off" value="c50532b0-65d2-4a81-baeb-59b768fd120f" />
<label for="languageFromTos_c50532b0-65d2-4a81-baeb-59b768fd120f__LanguageFromId">From</label>:
<select data-bind="???" data-val="true" data-val-number="The field From must be a number." data-val-required="The From field is required." id="languageFromTos_c50532b0-65d2-4a81-baeb-59b768fd120f__LanguageFromId" name="languageFromTos[c50532b0-65d2-4a81-baeb-59b768fd120f].LanguageFromId">
<option value="1">English</option>
<option selected="selected" value="2">French</option>
<option value="3">Spanish</option>
</select>
<span class="field-validation-valid" data-valmsg-for="languageFromTos[c50532b0-65d2-4a81-baeb-59b768fd120f].LanguageFromId" data-valmsg-replace="true"></span>
<label for="languageFromTos_c50532b0-65d2-4a81-baeb-59b768fd120f__LanguageToId">To</label>:
<select data-bind="???" data-val="true" data-val-number="The field To must be a number." data-val-required="The To field is required." id="languageFromTos_c50532b0-65d2-4a81-baeb-59b768fd120f__LanguageToId" name="languageFromTos[c50532b0-65d2-4a81-baeb-59b768fd120f].LanguageToId">
<option selected="selected" value="1">English</option>
<option value="2">French</option>
<option value="3">Spanish</option>
</select>
<span class="field-validation-valid" data-valmsg-for="languageFromTos[c50532b0-65d2-4a81-baeb-59b768fd120f].LanguageToId" data-valmsg-replace="true"></span>
delete
</div>
Can anyone tell me what I need to add to the data-bind attributes of each select to tie them up to the Knockout.js view model? The value of the first select should be bound to LanguageFromId in the view model and the value of the second select should be bound to LanguageToId in the view model.
LanguageFromTos in the view model is an array, so all of what you see inside may be repeated (2 LanguageFromTos would result in the LanguageFromToRow div being repeated twice, for example). The number of repeats is set both server-side (data posted by the controller may have multiple LanguageFromTos) and client-side (an 'add' button that allows the user to add another div with its contained selects) in different cases, so I presume Knockout's templating is a no-go as MVC needs to loop through and render each row so that it can catch them all on a 'normal' post back.
Any help much appreciated!
Edit
Here's the MVC model for the page:
public class DirectorySearchModel
{
[Display(Name = "User name contains")]
public string UserName { get; set; }
[Display(Name = "First name contains")]
public string FirstName { get; set; }
[Display(Name = "Last name contains")]
public string LastName { get; set; }
[Display(Name = "Languages translated")]
public IEnumerable<LanguageFromTo> LanguageFromTos { get; set; }
}
Here's the LanguageFromTo object:
public class LanguageFromTo
{
[Key]
public virtual int LanguageFromToId { get; set; }
[Display(Name = "From")]
public virtual int LanguageFromId { get; set; }
[ForeignKey("LanguageFromId")]
public virtual Language LanguageFrom { get; set; }
public virtual IEnumerable<SelectListItem> AllLanguagesFrom { get; set; }
[Display(Name = "To")]
public virtual int LanguageToId { get; set; }
[ForeignKey("LanguageToId")]
public virtual Language LanguageTo { get; set; }
public virtual IEnumerable<SelectListItem> AllLanguagesTo { get; set; }
public virtual ICollection<User> Users { get; set; }
}
And here's the cshtml code for the MVC view. This code sits in a partial that's repeated depending on how many LanguageFromTos there are in DirectorySearchModel that's passed to the MVC view:
<div class="LanguageFromToRow">
#using(Html.BeginCollectionItem("languageFromTos")) {
#: #Html.LabelFor(m => m.LanguageFromId): #Html.DropDownListFor(m => m.LanguageFromId, Model.AllLanguagesFrom, new { data_bind = "value: getLanguageFromToById(0).LanguageFromId" }) #Html.ValidationMessageFor(m => m.LanguageFromId)
#: #Html.LabelFor(m => m.LanguageToId): #Html.DropDownListFor(m => m.LanguageToId, Model.AllLanguagesTo, new { data_bind = "value: getLanguageFromToById(0).LanguageToId" }) #Html.ValidationMessageFor(m => m.LanguageToId)
delete
}
</div>
Your design here has some poor choices. Knockout's Templating is the right choice for this, but it is going to require you to start thinking in a different sort of pattern. You do not need to use partials to accomplish the repetition you are after.
Here is a fiddle demonstrating a templating solution. http://jsfiddle.net/tyrsius/XgwLD/3/
Some notes: to populate incomingData, an easy method will be to use #Html.Raw(Json.Encode(Model.DirectorySearchModel ));. This will turn your model into a JSON object, which the viewModel's constructor can easily use.
Now, I didn't use any MVC code in the fiddle because I can't, but you've obviously already found how you can put data-bind in the MVC helpers. This isn't always a bad idea, but for things like selects and DIVs that you want to use as templates, it will probably just makes things harder to read.

Resources