Posting Data in Asp.NET MVC 5 app with JavaScript & ViewModel - javascript

The basic problem is this. I'm using CKEditor for an interface for a blog post of sorts. CKEditor gets the wordcount, but I have to use some client-side JavaScript to clean it up. I want to pass the wordcount into the database so I know how many words each post has.
I have a viewmodel for the post:
public class NewStoryViewModel
{
[Required]
public string Title { get; set; }
[Required]
public string Content { get; set; }
[Required]
public int Genre { get; set; }
public IEnumerable<Genre> Genres { get; set; }
[Required]
public int StoryType { get; set; }
public IEnumerable<StoryType> StoryTypes { get; set; }
public int WordCount { get; set; }
[Required]
public int StoryAgeRange { get; set; }
public IEnumerable<StoryAgeRange> StoryAgeRanges { get; set; }
[Required]
public int Visibility { get; set; }
public IEnumerable<Visibility> Visibilities { get; set; }
}
And the controller for the post:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult New (NewStoryViewModel viewModel)
{
//confirm form data is valid
if (ModelState.IsValid)
{
//create new story object
var newStory = new Story
{
AuthorId = User.Identity.GetUserId(),
Title = viewModel.Title,
Content = viewModel.Content,
GenreId = viewModel.Genre,
StoryTypeId = viewModel.StoryType,
StoryAgeRangeId = viewModel.StoryAgeRange,
VisibilityId = viewModel.Visibility,
CreatedAt = DateTime.Now,
WordCount = viewModel.WordCount
};
//add new story to db
dbContext.Stories.Add(newStory);
//save db
dbContext.SaveChanges();
return RedirectToAction("Index", "Story");
}
else
{
return View(viewModel);
}
}
On the client-side in the razor view, I have this:
$(document).ready(function () {
$('#addStoryBtn').on('click', function () {
//get the content of the div
var wordcount = $('#cke_wordcount_Content').html();
//chop off the words before the number in the string
var slicedWordCount = wordcount.slice(6);
//remove any excess white space
var trimmedWordCount = slicedWordCount.trim();
//capture the index of the slash
var indexOfSlash = trimmedWordCount.indexOf("/");
//split the string at the slash to get the words used out of the total allotted
var finalWordCount = trimmedWordCount.slice(0, indexOfSlash);
//$.ajax({
// url: "/story/new",
// type: 'POST',
// data: {
// WordCount = finalWordCount
// },
// success: function (data) {
// console.log("Success")
// },
// error: function (error) {
// console.log("error is " + error);
// }
//})
});
});
I do this because CKEditor prints the word count out of the maximum like this:
Words: 4/5000
so I use a bit of JS to remove everything I don't need and keep the number before the slash.
But the ajax post didn't work (stepping through the controller, it returns 0).
I thought about using a hiddenfield in the view. Something like:
#Html.Hidden(new { WordCount = finalWordCount })
But the razor view gives me an error that finalWordCount doesn't mean anything in the current context. I surmise it's because finalWordCount is subject to the button click and since the addPost button hasn't been clicked, finalWordCount is undefined.
Any suggestions on how to pass the wordcount to the viewmodel?

You've mentioned in the comments that you're experiencing a 500 internal server error, which I'm guessing is after you've tried Shyju's suggestion to fix the invalid JSON. My guess is you're unable to even debug the controller action right now because it's expecting an anti-forgery token to be passed to it, but you're not sending that in the body of the POST request.
To fix that, try this:
var form = // selector for your form
var token = $('input[name="__RequestVerificationToken"]', form).val();
$.ajax({
url: "/story/new",
type: 'POST',
data: {
__RequestVerificationToken: token,
WordCount: finalWordCount
},
success: function (data) {
console.log("Success")
},
error: function (error) {
console.log("error is " + error);
}
});
That should hopefully fix the validation error, allowing you to at least reach the action.

The MVC application is probably expecting a json format request body, as
that is the default configuration of asp.net MVC.
So before posting the data to the server you need to stringify the model to a proper json.
Try it like this
var data = JSON.stringify({WordCount: finalWordCount});
$.ajax({
url: "/story/new",
type: 'POST',
data: data,
success: function (data) {
console.log("Success")
},
error: function (error) {
console.log("error is " + error);
}
})

Related

Posting JSON object via AJAX to ASP.NET Core Web API

I have tried different methods that have been posted on this site, but nothing seems to work.
I want to create a clothing site (a personal project). The products on the site have their own class, that is built like this:
public class Product
{
public string ProductName { get; set; }
public string ProductPrice { get; set; }
public int Quantity { get; set; }
}
The shopping cart is another class that will contain a list of Product objects and this one is built like this:
public class ShoppingCart
{
[Key]
public int Id { get; set; }
List<Product> ProductList { get; set; }
public string ClientName { get; set; }
public string ClientAddress { get; set; }
public string ClientMail { get; set; }
}
I created an API Controller class and thought that would solve the problem. It looks like this:
[Route("api/Shopping")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
[HttpPost]
public ShoppingCart Save([FromBody] ShoppingCart s)
{
return s;
}
}
In my JavaScript code I create my JSON object and try to post it like this:
var orderB = document.getElementById("orderB");
orderB.addEventListener("click", function () {
var inputName = document.getElementById("inputName").value;
var inputAddress = document.getElementById("inputAddress").value;
var inputMail = document.getElementById("inputMail").value;
var auxArray = [];
for (var i = 0; i < productsAux.length; i++) {
auxArray[i] = { "productName": productsAux[i].titlu, "productPrice": productsAux[i].pret, "quantity": localStorage.getItem(productsAux[i].titlu)};
}
var shoppingCart = {
productList: auxArray,
clientName: inputName,
clientAddress: inputAddress,
clientMail: inputMail
};
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "api/shopping/save",
contentType: "application/json charset=utf-8",
}).done(function (res) {
alert(res);
});
After I push the order button on my page I expect to see the alert pop-up with the callback result which I suppose is the ShoppingCart object that is created using the JSON that I send.
For those coming on later, I would suggest checking that your types are correct on both ends. For instance, if your JS is posting a byte array and C# tries to convert it to an int, the whole object (not just that prop) will be null.
This has caught me many a time.
I opened the Network tab and I got this: I got a 404 (kind of
expected that) , the name of the method 'save' , a type of 'xhr' and a
size of 45B.
The 404 error obviously means the url/routing is wrong. Here to solve it ,you have two ways to achieve.
First way:
You can change url to "api/shopping" in ajax as follow:
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "api/shopping",
contentType: "application/json charset=utf-8",
}).done(function (res) {
alert(res);
})
Second way:
You can change the path name
of Save action by Attribute routing with Http verb attributes as follow:
[Route("api/Shopping")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
[HttpPost("Save")]
public ShoppingCart Save([FromBody] ShoppingCart s)
{
return s;
}
}
Update
According to your comment, in addition to the above updates, you also need to modify the routing settings as follows:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Debug result:

How to pass in nested data from the client to the controller

I have a form that has two sections. 3 input fields and another section with 10 checkboxes.
public class Customerproductdto
{
public string CustomerNumber { get; set; }
public string CustomerName { get; set; }
public string CustomerPhone { get; set; }
List<ProductDetails> GetAllChecked {get;set;}
}
public class ProductDetails
{
public string ProductName{ get; set; }
}
Here is jquery code I am using to get all the values of the checkboxes that were
checked on my form. They are about 10 and users could check everything.
var yourArray[]
$("input:checkbox[name=type]:checked").each(function(){
yourArray.push($(this).val());
});
Here is javascript that I use to collect the data and pass to my controller.
How can I pass in my array here all in one shot?
var objdata =
({
CustomerNumber: txtcustnumber,
CustomerName: txtcustname,
CustomerPhone: txtphone
//How do I pass the yourArray here?
});
var url = "#Url.Action("WriteToDb", "Home")";
var completeData = JSON.stringify({ 'Information': objdata });
$.get(url, { 'objdata': completeData }, function (data) {
$('#mainListContent').html(data);
});
Please note that I will like to deserialize this once I get to the controller.
Here is the method.
public ActionResult WriteToDb(string objdata)
{
Customerproductdto getAllTaskSaved = null;
try
{
var stripOffObjectName = JObject.Parse(objdata)["Information"];
var cleanedData = JsonConvert.DeserializeObject<Customerproductdto>(stripOffObjectName.ToString());
getAllTaskSaved = _dtcDataService.WriteTaskToDb(cleanedData, "Add");
}
catch (Exception ex)
{
logger.Error(ex);
}
return PartialView("_Taskdisplay", getAllTaskSaved);
}

POSTing a complex Dictionary to Controller

I am attempting to post the following type to my controller.
Model
public class EmailNotificationSettings
{
public string EmailHost { get; set; }
public string EmailPassword { get; set; }
public int EmailPort { get; set; }
public string EmailSender { get; set; }
public string EmailUsername { get; set; }
public bool Enabled { get; set; }
public bool EnableUserEmailNotifications { get; set; }
public string RecipientEmail { get; set; }
public Dictionary<NotificationType, NotificationMessageContent> Message { get; set; }
}
NotificationType
public enum NotificationType
{
NewRequest,
Issue,
RequestAvailable,
RequestApproved,
AdminNote,
Test,
}
NotificationMessageContent
public class NotificationMessageContent
{
public string Subject { get; set; }
public string Body { get; set; }
}
Now all of the properties are part of my form except Message.
Here is how I plan to populate Message
View
<label for="newRequestSubject" class="control-label">New Request Subject</label>
<div>
<input type="text" class="form-control form-control-custom " id="newRequestSubject" name="newRequestSubject" value="#Model.Message.FirstOrDefault(x => x.Key == NotificationType.NewRequest).Value.Subject">
</div>
<label for="newRequestBody" class="control-label">New Request Body</label>
<div>
<input type="text" class="form-control form-control-custom " id="newRequestBody" name="newRequestBody" value="#Model.Message.FirstOrDefault(x => x.Key == NotificationType.NewRequest).Value.Body">
</div>
Javascript to Post
$('#save').click(function (e) {
e.preventDefault();
var newRequestObj = {
"Subject": $('#newRequestSubject').val(),
"Body": $('#newRequestBody').val()
};
var message = { 'NewRequest': JSON.stringify(newRequestObj) };
var $form = $("#mainForm");
var data = $form.serialize();
data = data + "&" +JSON.stringify(message);
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
// Do something
}
});
});
Now this is obviously the wrong way to do it as my Message property is not getting populated.
Any idea how I can POST to the controller and it update my model?
Controller if you want to see (Using NancyFX)
private Response SaveEmailNotifications()
{
var settings = this.Bind<EmailNotificationSettings>();
// settings.Message doesn't contain the new values
}
I cannot think of a way to make it work with pure HTML. The problem is you need to submit a control with the name "Message" that contain the data, then MVC mapping engine will map that control to the "Message" property of your model.
The best way I can think of is to use a JavaScript function (bound to the onsubmit event of the form) which collects the info as an array of objects and assign it to a variable called "Message" then serialize it with the rest of the form data and submit the form.

how to assign values to nested object through json object array

This is my model class:
public class SearchForFlight
{
public SearchForFlight()
{
Segments = new otherType();
}
public int AdultCount { get; set; }
public JourneyType JourneyType { get; set; }
public string Sources { get; set; }
public otherType Segments { get; set; }
}
public class otherType
{
public string Origin { get; set; }
public string Destination { get; set; }
public FlightCabinClass FlightCabinClass { get; set;}
public DateTime PreferredDepartureTime { get; set;
public DateTime PreferredArrivalTime { get; set; }
}
Now, My requirement is to post objects along with nested object to an external api.
The required form is something like this:
{
AdultCount: $("#AdultCount").val(),
JourneyType: $("#JourneyType :selected").text(),
PreferredAirlines: null,
Segments: [
{
Origin: $("#Origin").val(),
Destination: $("#Destination").val(),
FlightCabinClass: $("#FlightCabinClass").val(),
PreferredDepartureTime:$("#PreferredDepartureTime").val(),
PreferredArrivalTime: $("#PreferredArrivalTime").val(),
}
]
}
So, i have created another class OtherType and put all those nested objects into it.
I got the idea from this so question
How to send nested json object to mvc controller using ajax
Now, this is my Script tag with all the code inside to post simple objects along with nested objects.But nested objects value comes out to be null.
How should i model this code here.
<script>
$(document).ready(function () {
$("#btnPost").click(function () {
var sof = {
AdultCount: $("#AdultCount").val(),
JourneyType: $("#JourneyType :selected").text(),
PreferredAirlines: null,
Segments: [
{
Origin: $("#Origin").val(),
Destination: $("#Destination").val(),
FlightCabinClass: $("#FlightCabinClass").val(),
PreferredDepartureTime: $("#PreferredDepartureTime").val(),
PreferredArrivalTime: $("#PreferredArrivalTime").val(),
}
],
};
$.ajax(
{
url: "/api/Flight/SearchFlight",
type: "Post",
data: sof,
success: function (data) {
alert(data);
}
});
});
});
</script>
Posted Properties values for Origin, Destination comes out to be null.
The textbox rendered on view page are something like this:
#Html.TextBoxFor(model => model.Segments.Origin)
Any hint please.
Remove the array [] for Segments. Use contentType and stringify in your $.ajax func. Use the generated id for the Origin. It might not be "Origin". So,pls change it accordingly.
<script>
$(document).ready(function () {
$("#btnPost").click(function () {
var sof = {
AdultCount: $("#AdultCount").val(),
JourneyType: $("#JourneyType :selected").text(),
PreferredAirlines: null,
Segments: {
Origin: $("#Origin").val(),
Destination: $("#Destination").val(),
FlightCabinClass: $("#FlightCabinClass").val(),
PreferredDepartureTime: $("#PreferredDepartureTime").val(),
PreferredArrivalTime: $("#PreferredArrivalTime").val(),
},
};
$.ajax(
{
url: "/api/Flight/SearchFlight",
type: "Post",
contentType: 'application/json',
data: JSON.stringify(sof),
success: function (data) {
alert(data);
}
});
});
});
</script>

pass JsonResult object from javascript function in View to Controller

How can I pass JsonResult object from javascript function in View to Controller Action without Ajax call - just javascript - window.location.href = url?
I get JsonResult object from Controller Action to javascript function via Ajax call. Then I want to pass this object back to other Controller Action but I get object with null reference properties.
My javascript function in View:
function order(model) {
$('#details-container').html("<h2>Loading Complete Frame Module. Please wait...</h2>");
$.p({
url: '#Url.Action("CompleteFrameBrandDetails", "PacCompleteFrame")',
data: { item: model },
success: function (xml) {
if (xml.Success) {
$.p({
url: '#Url.Action("GlassCompleteFrame", "PacModule")',
data: JSON.stringify({ b2bXml: xml.Data }),
success: function (model) {
var pacModuleModel = {
Mode: model.Data.Mode,
IframeUrl: model.Data.IframeUrl.toString(),
CustomerNumber: model.Data.CustomerNumber.toString(),
ReadOnly: model.Data.ReadOnly,
GlassXml: model.Data.GlassXml.toString(),
Price: parseFloat(model.Data.Price),
Comission: model.Data.Comission.toString(),
Permissions: null,
Language: model.Data.Language.toString()
};
// here are all values in model.Data correct
// but then I can't figure out how to pass it to Controller Action without Ajax call - just with javascript command
var url = '#Url.Action("GlassCompleteFrameView", "PacModule", "__view__")';
window.location.href = url.replace("__view__", model.Data); //pacModuleModel
}
});
} else {
$.alert({
message: 'error while trying to load xml details'
});
}
}
});
}
My Controller Action:
public ActionResult GlassCompleteFrameView(PacModuleModel model)
{
// here I get object module but
// model.CustomerNumber = null
// model.GlasXml = null
// model.Price = null
// ...
return View("Glass", model);
}
I have also Model like this for automatic Json binding but dont work:
public enum ModuleMode
{
ByProduct,
ByRecipe
}
public partial class PacModuleModel
{
private PacPermissionModel permissionModel;
public ModuleMode Mode { get; set; }
public string IframeUrl { get; set; }
public string CustomerNumber { get; set; }
public bool ReadOnly { get; set; }
public string GlassXml { get; set; }
public double? Price { get; set; }
public string Comission { get; set; }
public PacPermissionModel Permissions
{
get
{
if (permissionModel == null)
{
permissionModel = new PacPermissionModel();
}
return permissionModel;
}
}
public string Language { get; set; }
}
Try this in controller
public JsonResult GlassCompleteFrameView(PacModuleModel model)
{
// here I get object module but
// model.CustomerNumber = null
// model.GlasXml = null
// model.Price = null
// ...
return Json(model, JsonRequestBehavior.AllowGet);
}
The problem was in model. It was more than 45000 char long. Now I use Session variable to get model in GlassCompleteFrameView(PacModuleModel model) and works perfect.
public ActionResult GlassCompleteFrameView(PacModuleModel model)
{
model = Session["xml"] as PacModuleModel;
return View("Glass", model);
}

Categories

Resources