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)
Related
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:
I have a model property who's value can be required or not required on the view based on the value of another property. I have implemented it in the view as follows.
<input #Html.Raw(Model.IsRequired ? "required data-val=true' data-val-required='" + Model.Name + " selection is required.'" : "") asp-for="Name" class="form-control" />
if (Model.IsRequired)
{
<span asp-validation-for="Name" class="text-danger"></span>
}
As indicated based on the Required field value, the validation is applied or not applied.
I also had to add this bit of code.
$("#btnSubmit").on("click", function () {
$("#form").submit();
});
The code works fine in validation of the code, however the message does not get displayed. What am I missing?
I also tried using this answer and changed my validation span to as below, but it did not work either.
<span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span>
If you do not want to add Data Annotations for validation.Here is a simple workaound like below:
1.Model:
public class TestModel
{
public string Name { get; set; }
public bool IsRequired { get; set; }
}
2.View(You need to change your #Html.Raw() and be sure you have added#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}):
#model TestModel
<form id="form">
<input #Html.Raw(Model.IsRequired ? "data-val='true' data-val-required='selection is required.'" : "") id="Name" name="Name" type="text" class="input-validation-error" />
#if (Model.IsRequired)
{
<span asp-validation-for="Name" class="text-danger"></span>
}
<input id="btnSubmit" type="submit" />
</form>
#section Scripts{
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
3.Controller:
public IActionResult Index()
{
var test = new TestModel() { IsRequired = true };
return View(test);
}
4.Result:
At the moment I'm using Angular 2 Beta-9 together with Asp.Net MVC 6.
I'm trying to create a simple contact form as test. The problem is that my form data does not seem to get passed to the server, while everything seems to be going ok on the Angular side.
contact.ts
/// <reference path="../node_modules/angular2/typings/browser.d.ts" />
import {Component, View} from 'angular2/core';
import {NgForm} from 'angular2/common';
import {bootstrap} from 'angular2/platform/browser';
import {Http, Headers, HTTP_PROVIDERS} from 'angular2/http';
#Component({
selector: 'contact',
bindings: [ContactComponent],
viewProviders: [HTTP_PROVIDERS],
templateUrl: '/angular/contact'
})
export class ContactComponent {
http = undefined;
contact = {};
constructor(http: Http) {
this.http = http;
}
onSubmit() {
this.http.post('/contact/send', JSON.stringify(this.contact), new Headers({ 'Content-Type': 'application/json' })).subscribe();
}
}
bootstrap(ContactComponent);
Contact.cshtml
<form #f="ngForm" (ngSubmit)="onSubmit(contact)">
<div>
<div class="form-group">
<label for="name">Name</label>
<input type="text" [(ngModel)]="contact.Name" class="form-control text-input" id="name" placeholder="Name" />
</div>
<div class="form-group">
<label for="email">E-mail</label>
<input type="email" [(ngModel)]="contact.Email" class="form-control text-input" id="email" placeholder="E-mail" />
</div>
</div>
<div>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" [(ngModel)]="contact.Subject" class="form-control text-input" id="subject" placeholder="Subject" />
</div>
</div>
<div>
<div class="form-group">
<label for="message">Bericht</label>
<textarea type="text" [(ngModel)]="contact.Message" class="form-control" id="message" placeholder="Message"></textarea>
</div>
</div>
<div>
<div>
<button type="submit" class="btn btn-success">Send</button>
</div>
</div>
</form>
ContactController.cs
[HttpPost]
public IActionResult SendContact([FromBody]ContactVm contact)
{
//do something
}
ContactVm.cs
public class ContactVm
{
[Required]
[DataType(DataType.Text)]
public string Name { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
[DataType(DataType.Text)]
public string Subject { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Message { get; set; }
}
I can't see or find anything I'm doing wrong. Untill the http.post, this.contact is filled in like it should, but once it reaches the server it's null.
UPDATE
Found that the Request.Form is throwing the following error:
Incorrect Content-Type: text/plain;charset=UTF-8
From what I have found, MVC will "fail silently" if it cannot force your posted value into the parameter.
So, while I am developing I used "object" - which ensures I get ALL posts. And then I fine tune it back to refer to the correct object.
Also, I found that (in Angular 6 which I am using) you definitely don't need to JSON.stringify the object... Look at the Chome Inspect tab, at the POST, an then Preview, and it will give you some detail of the failing...
let headers = new Headers({ 'Content-Type': 'application/json'});
this.http.post('/contact/send',JSON.stringify(this.contact),{ headers: headers })
.subscribe(...);
server side.
[HttpPost]
public IActionResult SendContact(ContactVm contact)
{
//do something
}
If it doesn't work also see below selected answer.
Angular2 Http post request not binding to ASP.NET 5 controller's action\
try this:
getJsonHeader(): Headers {
var headers = new Headers();
headers.append('Content-Type', 'application/json; charset=utf-8');
return headers;
}
onSubmit() {
this.http.post('/contact/send', JSON.stringify(this.contact), { headers: this.getJsonHeader() }).subscribe();
}
I don't think you need JSON.stringify:
this.http.post('/contact/send', this.contact, new Headers({ 'Content-Type': 'application/json' })).subscribe();
This corrected my issue HTH
Usually I do very little work on html side of the application because for the most part I just let that get generated for me.
I am working on an app for a blog with Posts Tags and Comments. What I want to do is when creating a new post, I should be able to add existing tags to the new post. I am trying to use Select2 but I can't figure out how to make the selected values passed to my Create method in the post controller so that they can be stored in the database.
Here is what I am working with:
namespace Blog.Data.Entities
{
public class Post
{
public virtual long PostId { get; set; }
-------------------
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public virtual long TagId { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
}
Post Controller
// POST: /Post/Create
[HttpPost]
public ActionResult Create(PostsCreateViewModel postModel)
{
if (ModelState.IsValid)
{
Post post = new Post
{
Title = postModel.Title,
Body = postModel.Body,
PostDate = _dateTime.UtcNow
};
foreach (var tag in postModel.Tags)
{
post.Tags.Add(_tagRepository.GetTag(tag.TagId));
}
_postRepository.SavePost(post);
return RedirectToAction("Detail");
}
return View(postModel);
}
I am successfully able to load data from remote with: Json code left out
<script type="text/javascript">
$(document).ready(function () {
$("#tags").select2(
{
placeholder: "Select a Tag",
minimumInputLength: 1,
multiple: true,
maximumSelectionSize: 5,
ajax: {
url: '#Url.Action("SearchTag", "Post")',
dataType: 'json',
data: function (term, page) {
return {
searchTerm: term,
page_limit: 10,
page: page,
};
},
results: function (data, page) {
var more = (page * 10) < data.total;
return { results: data, more: more };
}
}
});
});
</script>
View: usually I would have something like
<div class="form-group">
#Html.LabelFor(model => model.Title, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Title)
#Html.ValidationMessageFor(model => model.Title)
</div>
</div>
How can I write similar html for my tag textbox, so that when I click save everything is saved to appropriate tables?
Currently I just have this for select2:
<div class="form-group">
#Html.LabelFor(model => model.Tags, new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input id="tags" style="width: 300px" />
#Html.ValidationMessageFor(model => model.Tags)
</div>
</div>
Which produces;
I'm also using select2 and this works for me:
The View
#Html.ListBoxFor(M => M.TagsId, new MultiSelectList(Model.Tags, "Id", "Description"), new { #class = "form-control select-multiple", multiple = "multiple" })
The ViewModel
public List<Tag> Tags { get; set; } = new List<Tag>();
public int[] TagsId { get; set; }
You can use the Tags collection to fill the options in the multiple select and the TagsId array will be populated with the id of the selected tags. If you're populating the select options via ajax I guess you could ommit the Tags property.
Note I'm using a multiple input select and not a textbox.
Edit 2022 --
Controller:
myViewModel.xSelectedIdsJsonStr = JsonConvert.SerializeObject(xSelectedIdsIntArray);
View
<script>
$(() => {
setTimeout(() => {
let xSelectedIdsIntArray = #Html.Raw(Model.xSelectedIdsJsonStr);
$('[name="xSelectListName"]').val(xSelectedIdsIntArray).trigger('change');
}, 700)
});
</script>
End-----
Old answer -----
If the list and the selected items comes form different sources:
Example:
-Model.CategoryList >> the actual selectList items
-Model.SubCategories (m => m.SubCategories) >> the selected items only (Ids int[])
#Html.DropDownListFor(m => m.SubCategories, Model.CategoryList, Html.DescriptionFor(m => m.SubCategories), new { #class = "form-control m-select2 select2", multiple = "multiple" })
I did it like this with little js code:
#using Newtonsoft.Json;
<script>
//Edit mode
//set "selected" prop for the <select> manaually
$(function () {
var SubCategories_selected = #JsonConvert.SerializeObject(Model.SubCategories);
SubCategories_selected.forEach(function (value, index) {
$('[name="SubCategories"]').find(`[value="${value}"]`).prop("selected", true);
});
//recall the select2 init
$('#SubCategories').select2();
});
</script>
i think it just same as a <select multiple>
for example
<select name="abc" multiple>
<option value=1>a</option>
<option value=2>b</option>
<option value=3>c</option>
<option value=4>d</option>
</select>
you choose option a and option b then submit the form with post method
the post data will be
abc 1
abc 2
and you can get the data in mvc action with a param like IEnumeralbe<int> abc
the action will like
public ActionResult ActionName(IEnumerable<int> abc)
the select2 plugin only change the view,post data also use a http post
What I suggest is to do the following :
first : keep the text box of tags is for displaying only.
second : have hidden inputs that gets in sync with the textbox values by manipulating its value in javascript. each tag in textbox will have hidden input
the hidden inputs will represents the Tags Ids that are selected and showed in the textbox.
the benefit from this way is that the Default model binder will take care of binding the hidden inputs values to your view model property :
[public virtual ICollection Tags { get; set; }]
let tell how the hidden inputs should look like in your html :
<input type="hidden" name="Tags[0].TagId" value="1" />
<input type="hidden" name="Tags[1].TagId" value="2" />
<input type="hidden" name="Tags[2].TagId" value="3" />
for more info about list binding just follow the link :
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
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