Angular 2 post data is null in asp.net MVC 6 - c#

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

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)

How to set a dropdown as a required field in C# Razor if a checkbox is checked

In the ASP.NET Core 3.1 webapp I have a check box, "Food", which is of a boolean type and should be bind to a property HasFood, and I'd like to know how to manage this:
If the check box is checked:
A dropdown list Caterer becomes a required field.
A request cannot be submitted unless the Caterer option is selected from the list.
If the check box is not checked:
The Caterer dropdown list is not a required field.
I am total new to razor, I even dont know how to implement a checkbox. so if you could give me the whole code for this problem, I would be very very thankful
If the check box is checked: A dropdown list Caterer becomes a required field
If the check box is not checked: The Caterer dropdown list is not a required field.
To achieve the requirement, you can try to implement remote validation, like below.
//...
public bool HasFood { get; set; }
[Remote(action: "RequiredOrNot", controller: "Home", AdditionalFields = nameof(HasFood))]
public string SelectedCaterer { get; set; }
//other properties
//...
Action method
[AcceptVerbs("GET", "POST")]
public IActionResult RequiredOrNot(string selectedCaterer, bool hasFood)
{
if (hasFood && selectedCaterer == "0")
{
return Json("The SelectedCaterer field is required.");
}
return Json(true);
}
View page
<form asp-action="CreateCaterer">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="HasFood" /> #Html.DisplayNameFor(model => model.HasFood)
</label>
</div>
<div class="form-group">
<label asp-for="SelectedCaterer" class="control-label"></label>
<select asp-for="SelectedCaterer" class="form-control" asp-items="ViewBag.Caterers">
<option value="0">---Select---</option>
</select>
<span asp-validation-for="SelectedCaterer" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
Test Result
Update:
Above example would works well within ASP.NET Core MVC application. If your project is an ASP.NET Core Razor Pages, you can achieve same remote validation using PageRemote attribute, like below.
public bool HasFood { get; set; }
[PageRemote(ErrorMessage = "The SelectedCaterer field is required.",
AdditionalFields = "HasFood,__RequestVerificationToken",
HttpMethod = "post",
PageHandler = "RequiredOrNot")]
public string SelectedCaterer { get; set; }
The handler method
public JsonResult OnPostRequiredOrNot(string selectedCaterer, bool hasFood)
{
if (hasFood && selectedCaterer == "0")
{
return new JsonResult(false);
}
return new JsonResult(true);
}
You can check the following doc to know more about implementing Remote Validation in Razor Pages:
https://www.learnrazorpages.com/razor-pages/validation/remote-validation

Validating Data Annotation using Jquery, not displaying the error message

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:

.NET Core 2.2. How do I get rid of this HTTP 405 error when trying to delete?

I'm getting a 405 error when trying to do a simple post with a delete. I can read, write, and update just fine, but cannot figure out why Visual Studio 2017 (or, maybe more specifically, IIS Express) won't let me perform a delete.
The internet has a lot of posts regarding web.config, but they're older posts and the more recent one's don't work. In fact, there was no web.config included in my project because Microsoft is moving towards appsettings.json, I believe.
I'm running Visual Studio 2017 v15.9.11 with .NET Core 2.2 in a Docker container. I switched to IIS Express with no luck. I've looked at the applicationhost.config file for IIS Express and WebDav is commented out already. I believe it's happening at the web server level because I'm not getting a hit on the controller when debugging. My best guess is there's a default setting somewhere that's not allowing deletes; I just can't find it.
Model
namespace Efficacy.Models.Entities
{
/* dbo.Contact.Contact */
public class Contact
{
private const string V = "First Name";
public int Id { get; set; }
public ContactType ContactType { get; set; }
public int ContactTypeId { get; set; }
[Required]
//[Display( Description = ("Delete")]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
}
}
DBContext
namespace Efficacy.Data
{
public class EfficacyDBContext : DbContext
{
public virtual DbSet<Contact> Contact { get; protected set; }
public virtual DbSet<ContactType> ContactType { get; protected set; }
public virtual DbSet<HelloNurse> HelloNurse { get; protected set; }
public EfficacyDBContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ContactEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new ContactTypeEntityTypeConfiguration());
}
}
}
Controller
namespace Efficacy.Areas.Ops.Controllers
{
[Area("Ops")]
public class ContactController : Controller
{
private readonly EfficacyDBContext _db;
public ContactController(EfficacyDBContext db)
{
_db = db;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ContactDelete(int id)
{
var contact = await _db.Contact.FindAsync(id);
if (contact == null)
{
return NotFound();
}
_db.Contact.Remove(contact);
await _db.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
…
View
#model Efficacy.Models.Entities.Contact
#{
ViewData["Title"] = "Add Contact";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="container-fluid">
<div class="row">
<div class="col-6">
<h2 class="text-info">Add Contact</h2>
</div>
<div class="col-6 text-right">
&nbsp
</div>
</div>
<div>
<form method="post" asp-area="Ops" asp-controller="Contact"
asp-action="ContactDelete">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<table class="table table-striped border shadow p-3 mb-5 bg-white rounded">
<tr class="table-secondary">
<th><label asp-for="FirstName">First Name</label></th>
<th><label asp-for="MiddleName">Middle</label></th>
<th><label asp-for="LastName">Last Name</label></th>
<th><label asp-for="ContactTypeId">Contact Type</label></th>
<th class="text-right"> </th>
</tr>
<tr>
<td>
<input asp-for="FirstName" class="form-control"
value="#Model.FirstName" />
<span asp-validation-for="FirstName"
class="text-danger"></span>
</td>
<td>
<input asp-for="MiddleName" class="form-control"
value="#Model.MiddleName" />
<span asp-validation-for="FirstName"
class="text-danger"></span>
</td>
<td>
<input asp-for="LastName" class="form-control"
value="#Model.LastName" />
<span asp-validation-for="FirstName"
class="text-danger"></span>
</td>
<td>
#* TODO: Bind *#
<select name="ContactTypeId" class="form-control">
<option value="1">Organization</option>
<option value="2">Lead</option>
<option value="3">Event</option>
<option value="4">Invoice</option>
<option value="5">Contact</option>
</select>
</td>
<td class="text-right">
<input type="submit" class="btn btn-info form-control mb-1"
asp-route-id="#Model.Id" value="Save" />
<a asp-area="ops" asp-controller="Contact"
asp-action="ContactDelete" asp-route-id="#Model.Id"
class="btn btn-danger form-control mb-1">Delete</a>
<a asp-action="Index"
class="btn btn-success form-control">Cancel</a>
</td>
</tr>
</table>
</form>
</div>
</div>
Console Output
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET https://localhost:44382/Ops/Contact/ContactDelete/11
[40m[32minfo[39m[22m[49m: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:44382/Ops/Contact/ContactDelete/11
[40m[32minfo[39m[22m[49m: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint '405 HTTP Method Not Supported'
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint '405 HTTP Method Not Supported'
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint '405 HTTP Method Not Supported'
[40m[32minfo[39m[22m[49m: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '405 HTTP Method Not Supported'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 14.0081ms 405
Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area=Ops}/{controller=Home}/{action=Index}/{id?}");
});
405 HTTP code means Method Not Allowed. Served returned this error because action expects POST request (since it's marked with [HttpPost]) and when user click on anchor tag (<a>) browser sends GET request. In order to send POST you need to use form tag
<form method="post" asp-action="ContactDelete" asp-area="Ops" asp-controller="Contact">
<input name="Id" value="#item.Id" type="hidden" /> <!-- hidden input contains Id value -->
<button type="submit">Delete</button>
</form>
Also you have [ValidateAntiForgeryToken] attribute on the action so it needs anti-forgery token to be sent. FormTagHelper will automatically generate hidden input with this token for you so you don't need to do anything else.
i believe your issue is routing problem
can you try this.
[HttpPost("id")] <----------
[ValidateAntiForgeryToken]
public async Task<IActionResult> ContactDelete([FromRoute(Name = "id" )] int id) <----------
{
var contact = await _db.Contact.FindAsync(id);
if (contact == null)
{
return NotFound();
}
_db.Contact.Remove(contact);
await _db.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
When you use POST method,it's better to include "id" in hidden element in form scope and send it with other values:
<input type="hidden" asp-for="#Model.Id" />
I see you are trying to update and delete the same form.then you should use two Submit buttons with different Actions
<button type="submit" asp-action="Save" asp-controller="Contact" asp-area="Ops" class="btn form-control">
<span>Update</span>
</button>
<button type="submit" asp-action="ContactDelete" asp-controller="Contact" asp-area="Ops" class="btn form-control">
<span>Delete</span>
</button>
one more thing... remove Controller,Action and Area tags in form element and let submit buttons handle that:
<form asp-action="" method="post" enctype="multipart/form-data">

Why do my tag helpers not work in MVC?

I have a form with 3 inputs using tag helpers and a data transfer object with these 3 properties.
When I add the object to my database the values are empty. I set the properties to [Required] and even this doesn't give me an error.
I declared this as model in the form cshtml file:
#model CRM_Collect.Dtos.ClientDto
Form:
<form asp-controller="Client" asp-action="AddClient" method="post">
<div class="form-group">
<label for="companyName">Company</label>
<input asp-for="Company" class="form-control" id="companyName" placeholder="Company name">
</div>
<div class="form-group">
<label for="comment">Comment</label>
<textarea asp-for="Comment" class="form-control" id="comment" rows="3"></textarea>
</div>
<div class="form-group">
<label for="companyWebsite">Website</label>
<input asp-for="Website" class="form-control" id="companyWebsite" placeholder="www.example.com">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
Data transfer class
[Required]
public String Company { get; set; }
[Required]
public String Website { get; set; }
[Required]
public String Comment { get; set; }
Post controller
[HttpPost]
public ActionResult AddClient(ClientDto client)
{
ClientContext clientContext = new ClientContext();
Client clientToAdd = new Client { Comment = client.Comment, Company = client.Company, Website = client.Website };
clientContext.Clients.Add(clientToAdd);
clientContext.SaveChanges();
return View();
}
Explicitly use the FromBody attribute on the action's parameter. Also consider check ing model state as the model is using [Required] validation attribute.
[HttpPost]
public ActionResult AddClient([FromBody]ClientDto client) {
if(ModelState.IsValid) {
using(var clientContext = new ClientContext()) {
var clientToAdd = new Client {
Comment = client.Comment,
Company = client.Company,
Website = client.Website
};
clientContext.Clients.Add(clientToAdd);
clientContext.SaveChanges();
}
}
return View();
}
Turns out I am using a version that doesn't support the helper tags. I am not using ASP.NET Core which is the issue.
This is the most common mistake I have seen when people try to post a form to a controller. You need to have the name attribute set on all your inputs of the form so the controller will process the data inside of the input field and correspond that to the properties of the model serving as the parameter of your action.
Your form should look like this:
<form asp-controller="Client" asp-action="AddClient" method="post">
<div class="form-group">
<label for="companyName">Company</label>
<input asp-for="Company" name="company" class="form-control" id="companyName" placeholder="Company name">
</div>
<div class="form-group">
<label for="comment">Comment</label>
<textarea asp-for="Comment" name="comment" class="form-control" id="comment" rows="3"></textarea>
</div>
<div class="form-group">
<label for="companyWebsite">Website</label>
<input asp-for="Website" name="website" class="form-control" id="companyWebsite" placeholder="www.example.com">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>

Categories