asp.net core method to validate the viewmodel - c#

I have a save method which will be called via AJAX post and it passes a object instead of the view model. I parse the object and build the view model. Is there way I can validate that view model object
against the data annotation defined on the view model.
In the below example I tried to check the model state but it always is true and is not validating.
Is there a way I can run validate against ViewModel?
public class InvoiceViewModel
{
public long InvoiceId { get; set;
[Display(Name = "Invoice Number"),Required]
public string InvoiceNumber { get; set;}
[Display(Name = "Type"),Required]
public string InvoiceType { get; set; }
[Display(Name = "Amount"),Required]
public decimal InvoiceAmount { get; set; }
}
// controller method
public JsonResult SaveInvoice([FromBody] object invoice)
{
// parse through the invoice object
var invoiceVM = GetInvoiceViewModel(invoice)
// I tried the ModelState.IsValid - it did nto work
// how can I validate invoiceVM against the data annotation defined
}
public InvoiceViewModel GetInvoiceViewModel(object invoice)
{
}

As Camilo Terevinto said,you need to fix how to pass the model to action by ajax.
Before you did the work,you need to know that if the model data you passed contains wrong format,the backend would recieve null model.For example,InvoiceAmount is a decimal type property,if you do set a string like "aaa" or an empty string " ",it would be null.
And something special about model binding you need to know,the long and decimal type property have default value 0 although you do not pass the property.The Reuiqred attribute does not work for this.
For example,you could see the following gif,I not only do not pass the value but also do not pass the InvoiceAmount property(for how to do not pass the property you could check the following code).But in the backend you could see InvoiceAmount have default value 0:
var data = {
InvoiceId: $("#InvoiceId").val(),
InvoiceNumber: $("#InvoiceNumber").val(),
InvoiceType: $("#InvoiceType").val(),
//InvoiceAmount: $("#InvoiceAmount").val() //remove this...
}
As a conclusion,that is to say,for the long and decimal type property,you could not pass wrong format data to them,but you could send model without using these types' properties.
Here is a whole working demo:
View:
#model InvoiceViewModel
<form>
<div>
<label asp-for="InvoiceId"></label>
<input asp-for="InvoiceId" class="form-control" />
</div>
<div>
<label asp-for="InvoiceNumber"></label>
<input asp-for="InvoiceNumber" class="form-control" />
</div>
<div>
<label asp-for="InvoiceType"></label>
<input asp-for="InvoiceType" class="form-control" />
</div>
<div>
<label asp-for="InvoiceAmount"></label>
<input asp-for="InvoiceAmount" class="form-control" />
</div>
<div>
<input type="button" value="post" onclick="PostData()" />
</div>
</form>
#section Scripts
{
<script>
function PostData() {
var data = {
InvoiceId: $("#InvoiceId").val(),
InvoiceNumber: $("#InvoiceNumber").val(),
InvoiceType: $("#InvoiceType").val(),
InvoiceAmount: $("#InvoiceAmount").val()
}
console.log(data);
console.log(JSON.stringify(data));
$.ajax({
url: "/Home/SaveInvoice",
type: "POST",
contentType: "application/json; charset=utf-8", //be sure add this...
data: JSON.stringify(data),
success: function (res) {
alert(res.message);
}
})
}
</script>
}
Controller:
public JsonResult SaveInvoice([FromBody]InvoiceViewModel invoice)
{
if (ModelState.IsValid)
{
return Json(new { message = "Success" });
}
return Json(new { message = "Error" });
}
Result:

Related

Asp.net core not binding values from Jquery post

First off I am learning Asp.net core. I have a View as follows, when I the blogItemId I retrieve an item and send it as a model to the DetailedView.cshtml as follows:
[HttpGet]
public IActionResult DetailedView(int blogItemId)
{
var item = _mainBlogHelperModel.BlogDataItems.FirstOrDefault(x => x.Id == blogItemId);
if (item == null)
{
return RedirectToAction("MainBlog", new { page = 1, blogCategory = "" });
}
return View(item);
}
Now in the DetailedView.cshtml, I have a form. When I click the "Post Comment" button within the form, I would like to save some model record within the database and update some section with in the view.
Problem number 1: Please see my Model class below, I have a navigation property and foreign key. How do I specify these values within jQuery from my Model.
Problem number 2: I have a breakpoint in the PostComment method, but jQuery is not binding any values. Everything is null. please help.
Here is my DetailedView.cshtml:
#model KGSBlog.Models.BlogData
#SOME CODE HERE #
<form id="formPostComment" method="post" action="BlogData/PostComment">
<div class="row">
<div class="col-md-6">
<input type="text" placeholder="Name *" id="name" name="name">
</div>
<div class="col-md-6">
<input type="email" placeholder="Email *" id="email" name="email">
</div>
</div>
<div class="row">
<div class="col-xs-12">
<input type="text" placeholder="WebSite" name="website">
</div>
</div>
<textarea name="message" placeholder="Message *" id="message" cols="45" rows="10"></textarea>
<button id="btnPostComment" class="btn btn-main m-bottom-40">Post Comment</button>
</form>
#section Scripts {
<!-- jQuery -->
<script src="~/js/jquery.min.js"></script>
<!-- Bootstrap -->
<script src="~/bootstrap/js/bootstrap.min.js"></script>
<!-- Components Plugin -->
<script src="~/js/jquery.easing.1.3.js"></script>
<script type="text/javascript">
$(function () {
$("#btnPostComment").click(function () {
var name = $("#name").val();
var email = $("#email").val();
var message = $("#message").val();
var dataToPost = {};
dataToPost.Name = name;
dataToPost.Email = email;
dataToPost.Comment = message;
//How can I populate the BlogId and Blog properties here?
$.ajax({
url: '#Url.Action("PostComment", "BlogData")',
data: JSON.stringify(dataToPost),
type: "POST",
dataType: 'JSON',
contentType: "application/json",
success: function(data) {
//do stuff with json result
},
error: function(passParams) {
console.log("Error is " + passParams);
}
});
});
});
</script>
}
Here is my BlogDataController PostComment action method where the properties in the blogComment parameter are all null.
[HttpPost]
public async Task<IActionResult> PostComment(BlogComments blogComment)
{
if (blogComment != null)
{
await _context.BlogComments.AddAsync(blogComment);
await _context.SaveChangesAsync();
}
var item = _mainBlogHelperModel.BlogDataItems.FirstOrDefault(x => x.Id == blogComment.Id);
return new JsonResult(new { comment = blogComment.Comment, name = blogComment.Name });
}
The BlogComments model class
public class BlogComments
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Comment { get; set; }
[ForeignKey("BlogID")]
public int BlogId { get; set; }
public BlogData Blog { get; set; }
}
Problem number 1: Ideally, you don't want to pass the Blog property, you just want to pass the BlogId. It's not causing your issue but in my opinion, you shouldn't use the entity as the model, you should have another class that acts as a DTO. Is it good practice to use entity objects as data transfer objects?
You can do what you're looking for with something like this:
dataToPost.BlogId = #Model.Id;
Problem number 2: It might be fixed after you've resolved problem number 1. The problem might be that your action is expecting an int for BlogId but you're currently not passing BlogId.
If that doesn't work, open the network tab in the browser and make sure it's passing the POST body. Then check for ModelState errors in your action to see if any of the properties are failing validation.
Since in your original post you were using contentType: "application/json", you just needed to add a [FromBody] attribute and everything would be working
public async Task<IActionResult> PostComment([FromBody]BlogComments blogComment)
in your second post the code is working because in this case your content type is form-data (by default). In this case you don't neeed [FromBody] attribute
I don't know whats the difference between what I wrote and this, except for some attributes, but i changed the jquery code as follows, after referring to this post and it works. If someone can tell me why my old code was not working and this one is working, that will be great. (May be I shouldn't stringify the object)
First off I made the form like this,
<form id="formPostComment" method="post" enctype="multipart/form-data" asp-action="PostComment" asp-controller="BlogData">
<div class="row">
<div class="col-md-6">
<input type="text" placeholder="Name *" id="name" >
</div>
<div class="col-md-6">
<input type="email" placeholder="Email *" id="email">
</div>
</div>
<div class="row">
<div class="col-xs-12">
<input type="text" placeholder="WebSite" name="website">
</div>
</div>
<textarea placeholder="Message *" id="message" cols="45" rows="10"></textarea>
<submit id="btnPostComment" class="btn btn-main m-bottom-40">Post Comment</button>
</form>
#This is the jquery code.#
#section Scripts{
<!-- jQuery -->
<script src="~/js/jquery.min.js"></script>
<!-- Bootstrap -->
<script src="~/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function () {
$("#btnPostComment").click(function () {
var url = $("#formPostComment").attr("action");
var formData = new FormData();
formData.append("Name", $("#name").val());
formData.append("Email", $("#email").val());
formData.append("Comment", $("#message").val());
formData.append("BlogId",#Model.Id);
$.ajax({
type: 'POST',
url: url,
data: formData,
processData: false,
contentType: false
}).done(function (response) {
if (response.Status === "Success") {
alert("HELLO");
}
});
});
});
</script>
The Controller code, I just changed the parameter like this. Even with the old jquery code, I tried Bind like this and it didn't work.
public async Task<IActionResult> PostComment([Bind("Name,Email,Comment,BlogId")] BlogComments blogComment)

Wrong Custom error message in server side validation for MVC6 TagHelper

In the MVC6 TagHelper i have created two text box and binded model for that with custom error message one textbox of type string and other of type int during the post back the custom error message specified in the data annotation is not rendered properly for type other than string.
My Controller is
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(Sample model)
{
return View(model);
}
}
The Model class is
public class Sample
{
[Required(ErrorMessage = "String value is needed")]
public string StringValue { get; set; }
[Required(ErrorMessage = "Int value is needed")]
public int IntValue { get; set; }
}
And My Html Tag Helper code is
#model ValidationMessage.Models.Sample
<form method="post" action="/Home/Index">
<label>String</label>
<input asp-for="StringValue" type="text" />
<span asp-validation-for="StringValue"></span>
<br />
<label>Int</label>
<input asp-for="IntValue" type="text" />
<span asp-validation-for="IntValue"></span>
<br />
<input type="submit" />
</form>
Here am checking the server side validation alone, If i click the submit button
the first text box span validation shows String value is needed but the second text box span validation shows The value '' is invalid. instead of Int value is needed
Can someone suggest how to resolve this?

Does model maintains its structure when data is received in controller?

I am not sure whether I've framed the question properly above in subject but I will try to explain to my best about the question I have.
I have below ContactUsModel which is a part of HomeViewModel, better say Nested Model Class in a single model
public class ContactUsDataModel
{
public string ContactName { get; set; }
public string ContactEmail { get; set; }
public string ContactMessage { get; set; }
public string ContactPhone { get; set; }
}
and I am getting this Model referred in HomeViewModel as below:
public class HomeViewModel
{
/*My other models goes here*/
public ContactUsDataModel CUDModel { get; set; }
}
Now in Index.cshtml view I strongly create a form view as below:
#model ProjectName.Models.HomeViewModel
<!--I have other views for other models-->
#using (Html.BeginForm("ContactPost", "Home", FormMethod.Post, new { id = "contactform" }))
{
#Html.TextBoxFor(m => m.CUDModel.ContactName, new { #class="contact col-md-6 col-xs-12", placeholder="Your Name *" })
#Html.TextBoxFor(m => m.CUDModel.ContactEmail, new { #class = "contact noMarr col-md-6 col-xs-12", placeholder = "E-mail address *" })
#Html.TextBoxFor(m => m.CUDModel.ContactPhone, new { #class = "contact col-md-12 col-xs-12", placeholder = "Contact Number (optional)" })
#Html.TextAreaFor(m=>m.CUDModel.ContactMessage, new { #class = "contact col-md-12 col-xs-12", placeholder = "Message *" })
<input type="submit" id="submit" class="contact submit" value="Send message">
}
I do ajax Post as below:
$('#contactform').on('submit', function (e) {
e.preventDefault();
var formdata = new FormData($('.contact form').get(0));
$.ajax({
url: $("#contactform").attr('action'),
type: 'POST',
data: formdata,
processData: false,
contentType: false,
//success
success: function (result) {
//Code here
},
error: function (xhr,responseText,status) {
//Code here
}
});
});
and in Controller I tried to receive it as below:
public JsonResult ContactPost(ContactUsDataModel model)
{
var name=model.ContactName; //null
/*Fetch the data and save it and return Json*/
//model is always null
}
For some reason the above model is always null. But this works if I refer the model as HomeViewModel model instead of ContactUsDataModel model in controller parameter like below:
public JsonResult ContactPost(HomeViewModel model)
{
var name=model.CUDModel.ContactName; //gets value
/*Fetch the data and save it and return Json*/
//Model is filled.
}
My question here is even though I fill model of type
ContactUsDataModel in the view I am getting it as null if I refer
directly, but ContactUsModel which is inside HomeViewModel gets
filled. Doesn't type of model matter here. Is the hierarchy its
referred is necessary while fetching in controller?
Well, if your generated <input> name is CUDModel.ContactName instead of simply ContactName, the default Model-Binder wouldn't be able to bind it.
Fortunately, you can use the [Bind] attribute with prefix:
public JsonResult ContactPost([Bind(Prefix="CUDModel")]ContactUsDataModel model)
{
// ...
}
See MSDN
Your view posts the Type you have referenced in the view - #model ProjectName.Models.HomeViewModel - CUDModel is simply a property of HomeViewModel.
Using your web browser, inspect each DOM input element "name" property. MVC automatically maps properties from your inputs to the class using the input's "name" property.
To solve this you can create a custom model binder or create the inputs by hand, specifying the name property in such a way that the automatic model binder can match them to properties of your class.
However, there isn't anything wrong with your controller action taking HomeViewModel as an argument.
More information, found here.

Null model when posting data with Ajax.BeginForm

I've created a partial view that allows a user to enter their email and get added to a subscriber list. Originally I had this working as standard post and everything worked fine.
However now I'm trying to make this an ajax call and for some reason, even though the form is posting to the correct controller, the model is always coming through as null and I can't figure out why.
Here are my views:
Parent view:
#Html.Partial("Shared/Newsletter/NewsletterForm", new NewsletterSubscriber())
Form partial:
#using (Ajax.BeginForm("NewsletterSignUp", "Newsletter", new AjaxOptions { HttpMethod = "POST" }))
{
<input type="text" name="EmailAddress" id="newsletter-email" class="basic-input" placeholder="Email Address">
<button id="submit-newsletter" class="basic-submit">Sign Up</button>
<p id="status"></p>
}
Controller:
[HttpPost]
public ActionResult NewsletterSignUp(NewsletterSubscriber model)
{
if (!ModelState.IsValid)
{
return Content("Please enter a valid e-mail address");
}
// Do logic...
}
The model:
public class NewsletterSubscriber
{
[Required]
[EmailAddress]
[StringLength(300)]
public string EmailAddress { get; set; }
}
Scripts:
<script src="/scripts/jquery.validate.min.js"></script>
<script src="/scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="/scripts/jquery.unobtrusive-ajax.min.js"></script>
<script>
$(function() {
$('#submit-newsletter').on('click', function(e) {
e.preventDefault();
$.post('/umbraco/surface/Newsletter/NewsletterSignUp', function (data) {
console.log(data);
$('#status').show().text(data);
});
});
});
</script>
Like I said, when I step through the code the post is hitting the correct controller, however the model is always coming through as null.
Where am I going wrong?
I just realised I had the ajax form handling the submission for me and I was trying to manually post the form in another script. Once I removed the additional script it worked.
(It's the end of a long day and I missed this obvious mistake!)

MVC 3 dynamically add a viewModel to the View

I have some viewModels similar to these:
public class ResturantViewModel
{
public ResturantViewModel()
{
Clients.Add(new ClientViewModel());
}
public string MyProperty {get;set;}
public IList<ClientViewModel> Clients = new List<ClientViewModel>();
}
public class ClientViewModel
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
In my View I have something like:
#foreach(var client in Model.Clients)
{
<tr>
<td>First Name: #Html.EditorFor(item => client.FirstName)</td>
<td>Last Name: #Html.EditorFor(item => client.LastName)</td>
</tr>
}
I'd like to have a button which can add some new blank ClientViewModels to the ResturantViewModel.Clients list so that it could be rendered in the View and posted to the Resturant/Create action.
Thanks in advance
You may take a look at the following blog post. It uses the WebForms view engine but it could be very easily adapted to Razor.
You need to implement a list of objects inside your view
<input type="text" name='Clients[#index].FirstName' value='#c.FirstName' />
<input type="text" name='Clients[#index].LastName' value='#c.LastName' />
#index++;
after that you have to clone these fields with next index value, so you have to got inputs like these:
<input type="text" name='Clients[0].FirstName' value='#c.FirstName' />
<input type="text" name='Clients[0].LastName' value='#c.LastName' />
<input type="text" name='Clients[1].FirstName' value='#c.FirstName' />
<input type="text" name='Clients[1].LastName' value='#c.LastName' />
<input type="text" name='Clients[2].FirstName' value='#c.FirstName' />
<input type="text" name='Clients[2].LastName' value='#c.LastName' />
In controller you will accept a list of these objects:
List<Client> Clients
Ok thank to you all here is what I've done to resolve.
first I created a new partial View _ClientRow.cshtml:
#using Extensions.CollectionItemsHtmlExtension
#using ClientViewModel
#model ClientViewModel
<div class = "clientEditRow">
#using(Html.BeginCollectionItem("Clients"))
{
#First Name: #Html.TextBoxFor(c=>c.FirstName)
#Last Name: #Html.TextBoxFor(c=>c.LastName)
}
</div>
This partial view renders a new line for a client.
BeginCollectionItem is an Html Extension downloaded following the blog post Darin mentioned.
Then in my view I set:
<legend>Clients</legend>
<fieldset>
<div id="clientEditorRows">
#foreach(var client in Model.Clients)
{
Html.RenderPartial("_ClientRow", client);
}
</div>
#Html.ActionLink("New Client", "NewClientRow", null, new {id = "addItem"})
</fieldset>
...
<script type="text/javascript" scr="../Scripts/listEditor.js"></script>
the foreach loops through all the clients and renders the partial view for every client.
Then in my Controller I wrote this method:
public PartialViewResult NewClientRow()
{
return PartialView("_ClientRow", new ClientViewModel())
}
This method is called by the ActionLink and returns the html for a new line appending it to the previous lines.
Eventually I added this javascript file code from the blog post and modified it to my case:
listEditor.js
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#clientEditorRows").append(html); }
});
return false;
});
This js code appends the html for the new line to the page.
Hope this can help, thank you all again.
alex
P.S.: the HttpPost method receiving the values hasn't been modified and has this signature
[HttpPost]
public ActionResult Create(ResturantViewModel resturantVM)
{
...
}
to note that resturantVM.Clients receives all the values, no need to add a IList Clients parameter

Categories