Integer property without Required property is still required - c#

I'm not applying any [Required] attribute validation on the integer property. But every time I post the form it fired Validation. Initially it was using HTML 5 data attribute on client side. I've set ClientValidationEnabled to false in Web.config. After that it is firing Required attribute validation.
I've created a new project but same situation. Tried to change .Net framework from 4.6 to 4.5 but no success. Also tried VS2015 and VS 2013
Client side razor code
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.LabelFor(model => model.Weight)
#Html.TextBoxFor(model => model.Weight, new { #class = "form-control", #placeholder = "Weight", Value = "" })
#Html.ValidationMessageFor(model => model.Weight, "", new { #class = "text-danger" })
<input type="submit" class="btn btn-default" value="Save" />
}
Model:
public class RegistrationModel
{
public int Weight { get; set; }
}
Action Method
public ActionResult Index()
{
var model = new RegistrationModel();
return View(model);
}
[HttpPost]
public ActionResult Index(RegistrationModel model)
{
if (ModelState.IsValid)
{
}
return View();
}
Thanks

Your model property is typeof int. An int must always have a value (its not nullable) so irrespective of disabling client side validation, as soon as you hit the controller, ModelState will be invalid if you submit a null (empty string) value for Weight.
If you want to allow nulls, then you must make the property nullable
public int? Weight { get; set; }
Side note: You should never attempt to set the value attribute when you are using the html helpers to bind to your model properties.

This is default behavior of integer type property because int has to
have a value even zero.So, you have to make it of type nullable int in the model.

this might help you,
<form method="post" action="/foo" novalidate>...</form>
or
<form>
<input type="email"/> <!-- Will be validated -->
<input type="string" formnovalidate/> <!-- Will not be validated -->
</form>

Related

ASP.NET MVC 5 - Strange behaviour having HTML content in model

I am working on a functionality based on ASP.NET MVC 5 to manage message templates which are rendered as html. Having html-markup in the viewmodel causes some problems.
Those message templates are edited via a WYSIWYG-editor.
Here a basic example of the controller:
public class BlackboardController : Controller
{
public ActionResult Template()
{
return View(new RichTextEditorViewModel()
{
Message = "<h1>I'm a headline</h1><p>I'm a regular text...</p>"
});
}
[HttpPost]
public ActionResult Template(RichTextEditorViewModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Template");
}
}
Basic example of the model:
public class RichTextEditorViewModel
{
[Required]
[Display(Name = "Template name")]
public string TemplateName { get; set; }
[AllowHtml]
[Display(Name = "Message")]
public string Message { get; set; }
}
Part of the view
#using (Html.BeginForm("Template", "Blackboard", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<label class="col-md-4 control-label">Message </label>
<div class="col-md-8">
<div class="input-group">
#Html.TextAreaFor(m => m.Message, new { rows = "20", style = "resize:none;width:400px;", placeholder = Html.DisplayNameFor(m => m.Message), #class = "form-control input-lg textarea-editor" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save template" class="btn btn-default" />
</div>
</div>
}
Everything works fine when I post a html-markup to the controller action. To make that work, I had to decorate the model property containing the markup with the AllowHtml-attribute.
BUT: If the ModelState is not valid, e.g. TemplateName is null, then I still get that HttpRequestValidationException saying:
"A potentially dangerous Request.Form value was detected from the
client"
I couldn't reproduce that behaviour with that basic example, but it happens in my more complex web application. On some sites I found the information, that an exception gets thrown if anything touches a property of the Request-property of the controller or view. Tried to work on that, but it didn't seem to help. Also, I don't know what components are actually accessing the request or containing a reference to that request.
How can it be, that I won't see this exception if the ModelState is valid. And how can it be, that the HttpRequestValidationException gets thrown when the ModelState is invalid.

How can I do client validation to form with ASP.NET MVC 5?

I am new to ASP.NET MVC 5. I like to learn the correct way to build a two way binding between the view and the view model. And take advantage of the client side validation script.
Here is what I have done.
I loading jQuery Library v 1.10.1
I loading Jquery-Validation
I loaded jQuery.Unobtrusive.Validation
I created a ViewModel like so
public class RequestFormViewModel
{
[Required]
[Display(Name = "Day Of")]
public DateTime LocalFrom { get; set; }
[Required]
[Display(Name = "Does not matter since this will be hidden and I use javascript to populate the value here when before the for is submitted")]
public DateTime From { get; set; }
public RequestFormViewModel()
{
}
public RequestFormViewModel(DateTime localFrom, DateTime from)
{
this.LocalFrom = localFrom;
this.From = from;
}
}
And this is how I created my view. Note that I pass a presenter or a business layer (i.e. DefaultViewPresenter) to my view and not the ViewModel. The class DefaultViewPresenter has a property nammed Request. (Below I will show how my presenter looks like)
#model Proj.Presenters.DefaultViewPresenter
#using (Html.BeginForm("Index", "Track", FormMethod.Post, new { #class="form-inline", Id = "TrackActionForm" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Request.LocalFrom, new { Id = "TrackFrom", Name = "From" })
<div class="input-group">
#Html.TextBoxFor(m => m.Request.LocalFrom, new { Value = Model.Request.LocalFrom.ToString("MM/dd/yyyy"), #class = "form-control small", Id = "TrackLocalFrom", Name = "LocalFrom" })
#Html.ValidationMessage("LocalFrom")
<span class="input-group-btn">
<button class="btn btn-info" type="button" id="TrackSubmit">View</button>
</span>
</div>
}
Here is how my presenter looks like
public class DefaultViewPresenter
{
public RequestFormViewModel Request { get; set; }
.... // some other propertied that I need for the view that are not related to my form
....
....
}
Problem
When I submit the form, the form does not get submitted! I don't I get any errors. Its like the submit button has return; function on the click event.
How can I get the script to validate correctly and when the form is valid processed the post request?
What am I missing here? How can I correct this problem?
After running my app this is the HTML markup that is razor is generating
<form novalidate="novalidate" id="TrackActionForm" action="/Track" class="form-inline" method="post">
<input name="__RequestVerificationToken" value="thmJX-Mlj5WjM3e7WMbgtb8KiEf4vuUKGzon4zO18fHDDY3cWpm2M1Lks8HbZDxX2qz7UxpRsoYvz2njNwYS_D8zclTvu9pdsJlSO0ckNLQ1" type="hidden">
<input id="TrackFrom" name="From" data-val="true" data-val-date="The field Day Of must be a date." data-val-required="The Day Of field is required." value="9/14/2016 12:00:00 AM" type="hidden">
<div class="input-group">
<input id="TrackLocalFrom" name="LocalFrom" value="09/14/2016" class="form-control small" type="text">
<span class="field-validation-valid" data-valmsg-for="LocalFrom" data-valmsg-replace="true"></span>
<span class="input-group-btn">
<button class="btn btn-info" type="button" id="TrackSubmit">View</button>
</span>
</div>
</form>
You have a few issues with your implementation.
All the HtmlHelper methods that generate form controls generate the correct name and value attributes necessary for 2 way model binding. You are overriding those values so that the controls now have no relationship to your model.
You also have a hidden input for the Request.LocalFrom property before the textbox, so that when you submit, only the value of the hidden input (the original value of the property) will be bound and the edited value in the textbox will be ignored. In addition, because of that hidden input, the data-val-* attributes generated for client side validation have been applied to the hidden input, not the textbox.
Its not clear why you need 2 view models, and ideally your DefaultViewPresenter view model should contain properties for LocalFrom and From, however with your current models, your view needs to be
#model Proj.Presenters.DefaultViewPresenter
#using (Html.BeginForm("Index", "Track", FormMethod.Post, new { #class="form-inline", Id = "TrackActionForm" }))
{
#Html.AntiForgeryToken()
<div class="input-group">
#Html.TextBoxFor(m => m.Request.LocalFrom, "{0:MM/dd/yyyy}", new { #class = "form-control small" })
#Html.ValidationMessageFor(m => m.Request.LocalFrom)
<span class="input-group-btn">
<button class="btn btn-info" type="submit" id="TrackSubmit">View</button> // change to a submit button
</span>
</div>
}
Note the 2nd parameter in TextBoxFor() is the format string, but that can be omitted if you use the [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] applies to the property. Note also the HtmlHelper methods generate an id attribute based on the property name and there should generally be no reason to overwrite it.
As a side note, it is recommended that in your view models, you make value type properties nullable with the [Required] attribute to protect against under-posting attacks (where a malicious user posts backs and omits a name/value pair for the property, in which case it will be initialized to its default value (DateTime.MinValue).
[Required(ErrorMessage = "Please enter ...")]
[Display(Name = "Day Of")]
public DateTime? LocalFrom { get; set; }
By default JQuery Validation ignores hidden fields for client side validation. Try the following in a script block in your view or _layout.cshtml:
$(document).ready(function(){
$.validator.setDefaults({ ignore: [] });
});
Hope this helps!

ASP.NET MVC one particular view model property becoming null on returning to view

In ASP.NET MVC I have a view that's a form, and I want to be able to save the form and then it returns back to the same page showing the data you entered after saving the data to the database. I'm sure I'm just doing something stupid (this is pretty new to me), but there are some properties that I want to persist and I am setting them on the view model before I return, and I have #Html.HiddenFor inside my view's form. My confusion is that of these items are retained, and some aren't. So I have the following inside my FormController (methods and names have been simplified for brevity):
public ActionResult Index(int? p, int? c)
{
FormViewModel model = new FormViewModel();
model.p = p;
model.c = c;
model.dateStarted = DateTime.Now;
return View(model);
}
[HttpPost]
public ActionResult Index(FormViewModel m)
{
Form form;
bool shouldUpdate = false;
if (m.formID != null) // m.formID is always null, but m.p, c, dateStarted aren't
{
shouldUpdate = true;
form = getFormnWithId((int)m.formID); //gets from database
}
else
{
form = new Form(m);
}
if (shouldUpdate)
{
editForm(form); //edit existing entry
}
else {
addForm(form); //add to database
}
m.formID = form.Id; // formn.Id is valid because the form has been updated with its Id after being added to the database
m.p = form.p;
m.c = form.c;
return View(m);
}
Inside my view (cshtml) file I have #Html.HiddenFor(model=>model.formID) as well as for other properties I want to persist but aren't being set in the form directly.
The formID however, is not persisting, while the other items (represented by c and p and dateStarted) are fine. If I remove the HiddenFor for those other fields, then they don't work. I click save each time, and formID is null in the post, but it's definitely set after the form has been added to the database and the value of the formID is definitely getting sent to the view. I just don't understand why it comes back null but the other properties don't.
Here's what the model looks like:
public class FormViewModel
{
public Nullable<int> formID {get; set;}
public Nullable<int> c { get; set; }
public Nullable<int> p { get; set; }
public System.DateTime dateStarted { get; set; }
//+ other form properties
}
View:
...
<label for="submit-form" class="btn btn-default">Save</label>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal col-md-12">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<!-- various form fields -->
</div>
#Html.HiddenFor(model => model.dateStarted)
#Html.DisplayFor(model => model.dateStarted)<br /> <!-- just to see it while testing-->
#Html.HiddenFor(model => model.c)
#Html.DisplayFor(model => model.c)<br />
#Html.HiddenFor(model => model.p)
#Html.DisplayFor(model => model.p)<br />
#Html.HiddenFor(model => model.formID)
#Html.DisplayFor(model => model.formID)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" name="Command" class="btn btn-default hidden" id="submit-form" />
</div>
</div>
</div>
}
Now that I see you are setting the Form.Id in the POST request, your issue is that you are not following the PRG (Post, Redirect, Get) pattern. You are returning the same view from your POST method without any type of redirect. As a result, the model binder is holding on to the previous value of Form.Id, which was null. The reason that the model binder holds on to previous values is mainly for validation purposes (if the ModelState has an error, you can return the view, the properties remain as the user entered them along with the ModelState errors collection)
To fix this, you either need to redirect to another action or issue ModelState.Clear() in your code before you return the view.
m.formID = form.Id; // form.Id is valid because the form has been
//updated with its Id after being added to the database
m.p = form.p;
m.c = form.c;
ModelState.Clear();
return View(m);

View with multiple models only POST 1 model

This is the model I'm using for a view
public class MainRegisterViewModel
{
public RegisterViewModel RegisterModel { get; set; }
public RegisterFormValuesViewModel RegisterValues { get; set; }
}
RegisterFormValuesViewModel contains all the values for the controls (list of countries, states and stuff like that) and RegisterViewModel contains the information for a user.
Then I load the controls like this.
#model ProjetX.Models.MainRegisterViewModel
#{
IEnumerable<SelectListItem> countries = Model.RegisterValues.Countries.Select(x => new SelectListItem()
{
Text = x.Country,
Value = x.Id.ToString()
});
IEnumerable<SelectListItem> states = Model.RegisterValues.States.Select(x => new SelectListItem()
{
Text = x.State,
Value = x.Id.ToString()
});
}
<div class="container">
<div class="row">
<div class="col-md-12">
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
....
<div class="form-group">
#Html.LabelFor(m => m.RegisterModel.Country, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownListFor(m => m.RegisterModel.Country, countries, new { #class = "form-control" })
</div>
</div>
....
Also the Register function takes a MainRegisterViewModel as parameter.
public async Task<ActionResult> Register(MainRegisterViewModel model)
The problem is when I submit the form, RegisterFormValuesViewModel is NULL.
Any ideas why?
Thank you
Context
I'm doing this because I load the RegisterFormValuesViewModel from an API and I'm trying to call it only once. The problem was when a user POST a form with errors and you return the view back, I had to call the API again to get RegisterFormValuesViewModel.
Before this it was only one model and a viewbag for RegisterFormValuesViewModel but I had to call the API every time the form was loaded because the viewbag wasn't posted. That's why I thought I could use 2 models and POST them both.
If you want the values of RegisterFormValuesViewModel to be posted, they need to included in the form or other location that the ModelBinder looks for values. The (default) model binder will pick up values from the action params, Request.Form, route data, and Request.QueryString (I think Request.Files is included too).
If your RegisterFormValuesViewModel is expensive to create, you can add it's values as hidden fields that are posted with the form or implement a custom ValueProviderFactory that works with application or session state.

Cannot resolve ASP.NET MVC error

I'm a new learner of ASP.NET MVC. This problem should be easy to answer for professions. I'm trying to submit a form (post comment), and getting error.
Here is my controller
[HttpPost]
public ActionResult Index(long id, CommentViewModel comment) {
var service = new UserService();
var articleService = new ArticleService();
comment.UserId = service.GetUserByUsername(User.Identity.Name).UserId;
comment.ArticleId = id;
comment.CommentTime = DateTime.Now;
articleService.AddArticleComment(comment);
return View();
}
here is my ViewModel
public class CommentViewModel : BaseViewModel {
public Int64 CommentId { get; set; }
public Int64 UserId { get; set; }
public Int64 ArticleId { get; set; }
public DateTime CommentTime { get; set; }
public String CommentBody { get; set; }
}
here is my View (HTML)
<form class="form-stacked" id="comment-form" action="#Url.Action("Index", "Article")" method="post" enctype="multipart/form-data">
<label class="control-label" for="commentTextArea">Leave a comment...</label>
<textarea rows="3" id="commentTextArea" name="commentBody" class="span8"></textarea>
<input type="submit" value="Post Comment" />
</form>
this is URL
http://localhost:58856/Article?Id=1
and this is the Error Message
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int64' for method 'System.Web.Mvc.ActionResult Index(Int64, TechCells.Services.ViewModels.CommentViewModel)' in 'TechCells.Web.UI.Controllers.ArticleController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
I guess MVC should automatically detect the id from the URL, and automatically assign ViewModel properties based on names. Right?
What is the reason for this error? How can I fix?
Thanks.
Your form doesn't have an <input name="id" /> (which is necessary per your action signature). Remember that MVC's action arguments are incoming values, so if this is a form submit, all those parameters need to be supplied (though sometimes they can be defaulted in a route definition, but I won't get in to that).
Mis-read and missed the Id coming from a GET parameter. Assuming you follow traditional form handling with a GET & POST action, you're passing the Id to the GET, it renders a view, then on POST the Id's dropped. So, two options:
If you don't need it, remove the long id parameter or make it optional (line id = 0) so that MVC can carry on with a default value.
If you do need it, make sure to pass it off in your Url.Action() as a routeValue so it's received back in.
Also, don't be afraid to use the HTML Helpers, like Html.BeginForm, Html.LabelFor, Html.TextBoxFor, etc.
#using (Html.BeginForm("Index", "Article",
new { id = __ID_FROM_GET_REQUEST_ }, // to hand-off to POST action
FormMethod.Post,
new { enctype = "multipart/form-data" }
))
{
#* I assume these are here to help reference what the comment is regarding *#
#Html.HiddenFor(x => x.UserId)
#Html.HiddenFor(x => x.ArticleId)
#* are these ones auto-generated at creation time? if so, remove these. I
don't know enough about your work-flow, so just going to place them for
now. I also don't know what your validation requirements are. *#
#Html.HiddenFor(x => x.CommentId)
#Html.HiddenFor(x => x.CommentTime)
#* Now we get in to user-interaction *#
#Html.LabelFor(x => x.CommentBody)
#Html.TextAreaFor(x => x.CommentBody, new { #class = "span8", rows = "3" })
<input type="submit" value="Post Comment" />
}
Then for LabelFor to work, make sure you decorate your model:
/* ...snip ... **/
[Display(Name = "Leave a comment...")]
public String CommentBody { get; set; }
/* ...snip ... **/
Changing it to this
public ActionResult Index(long id = 0, CommentViewModel comment)
OR
this
public ActionResult Index(long? id, CommentViewModel comment)
will solve the problem.
The error is occuring because you are not passing any value for id. and id is not nullable.
To solve this, either pass id or change the method signature as above.

Categories