Using MVC4, how would I process the return value of a JsonResult action in an Ajax form?
All of the examples I was able to locate deal primarily with an html result (ActionResult).
I know this question is poorly framed, missing code and such, but I plan on providing my experience as the answer. Hopefully between this question and the answer there will be some good content.
In an ASP.net controller (C#) you can return a json result as follows (hope you already know it).
[httppost]
public ActionResult MyAction(){
//this is the most amazing content
return Json(new
{
MyResult = "ok",
MyData = "This is some string data!"
});
}
Its not clear for me what you expect from the answer or I am not sure whether this is what you need as an answer, but hope it will be helpful.
If you are using jquery ajax, you can access the controller and get the json results it returns.
$.ajax({
type: "POST",
URL: "/MyController/MyAction",
dataType: "text"
})
.success(function(data){
var dataobj = $.parseJSON(data);
var result = dataobj.MyResult;
var msg = dataobj.MyData;
});
When you are using return new Json(); the server response is of ContentType application/json. But, to use Jquery's parseJSON function correctly, you need to pass the json to the function as a string, otherwise it do not parse it correctly. So, to get the json result as a string or 'text' you need to add dataType: "text" as an option to $.ajax{}. Then the server returns its response as plain text and you can parse json using jquery's parseJSON function. It will return a dynamically created object which includes data returned as json. So you can access those data using the names included in the json string.
Hope this will be helpful somewhat.
So most examples/tutorials on the web will instruct you to return a view via your HttpPost action method. When doing this you would set the 'UpdateTargetId' and InsertionMode properties on the AjaxOptions object.
But if you want to return data and work with it via javascript, the above method falls short.
Rather, you will need to set the OnSuccess method of the AjaxOptions object.
As you can see from the documentation, OnSuccess contains zero helpful information. To use it correctly you need to provide the name of a javascript function available on the current page. This will be used callback-style so make sure you use the appropriate syntax for your use case.
Here's a little demonstration:
Controller methods:
<HttpGet>
Function AjaxIndex() As ActionResult
Dim model As AjaxFormModel = New AjaxFormModel
' AjaxFormModel is a custom class. Architect it as you see fit.
Return View(model)
End Function
<HttpPost>
Function AjaxIndex(ByVal model As AjaxFormModel) As JsonResult
Dim result As AjaxFormResult = Nothing
' AjaxFormResult is a custom class. Fill it in with properties that make sense for you. I personally include .MessageType and .Payload properties.
' TODO: be sure you spin up a new object to pass to the `JsonResult`.
Return New JsonResult With {.Data = result, .JsonRequestBehavior = JsonRequestBehavior.AllowGet}
End Function
Html
Using Ajax.BeginForm("AjaxIndex", "Bootstrap",
routeValues:=Nothing,
ajaxOptions:=New AjaxOptions With {.HttpMethod = "POST",
.OnSuccess = "updateAjaxForm"},
htmlAttributes:=Nothing)
#<text>
<div class="row">
<div class="col-sm-12" id="UnknownError_message">
</div>
</div>
<div class="row">
<div class="col-md-12">
<input type="text" name="Name" />
<div id="Name_message"></div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<input type="submit" class="btn btn-default" value="Save" />
</div>
</div>
</text>
End Using
javascript / jQuery
<script type="text/javascript">
function updateAjaxForm(data) {
//data is a fully reconstituted javascript object at this point. Thank you MVC!
var messageElement = $('#UnknownError_message');
switch (data.MessageType) {
case 0: // Error message
messageElement.html('Error: ' + data.Message);
break;
case 1: // Textual message
messageElement.html('Info: ' + data.Message);
break;
default:
messageElement.html('Unforseen Error.');
}
}
</script>
Related
I'm a novice in MVC, Below is my code
I am unable to read the value of an ID and use that in an decision statement, I am getting "The name "Text" does not exist in current context", I need to work on the if statement based on the value I get from my document.getElementById
#{
var grid = new WebGrid(Model.Abc, canPage: true, canSort: true, rowsPerPage: 50);
}
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
<text>
var obj = document.getElementById("NextAction").value;
</text>
if (#text.obj == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
Try using
document.getElementsByName("NextAction").value;
I have seen in my case that Blazor changes Id to name.
Note: I am using DevexpressBlazor
Did you checked if you are able to see on the html generated that ID?
If yes, Did you have any JS error before?
Looks like the ID not was generated or the place where you are run the getElementById don't have visibility to your specific code.
You are mixing razor syntax and javascript. The line var obj = document.getElementById("NextAction").value; is javascript and should go inside <script> tag. You can't call javascript functions from razor code.
Solution:
Assuming you have a controller named GridController.cs and a view named Grid.cshtml. Inside your controller add a new HttpPost action:
[HttpPost]
public IActionResult NextAction(string nextAction)
{
ViewData["NextAction"] = nextAction;
return View("Grid");
}
Inside the view add a form that posts the nextAction value to the controller:
<form asp-action="NextAction" asp-controller="Grid">
<input type="hidden" value="Start" name="nextAction" />
<button type="submit">Start</button>
</form>
The controller added the NextAction value in the ViewData dictionary so now the view can access it:
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
if (ViewData["NextAction"] == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
You are getting that error because you are using #text.obj. In Razor, once you attached # before any identifier, it considers it a C# or VB variable.
Since we don't have your entire page, you may need to clarify where the source of the NextAction. It will be helpful. See a sample of something similar.
#if(item.Ward == "start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
The item is from the model I am iterating to form the grid.
I try to use DevExtreme component in partial view.
But my partial view page shown when I click on the element.
And in the main page after the click I have error
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'ApplicationEntity' with type 'System.Data.Entity.DynamicProxies.ApplicationEntity_50A6A66F1464C1DE4E8A736E85D88C5AF4F4249EAE26FB21C4F82592E001885D'. Path 'data[0].ApplicationEntity.ApplicationEntityHistories[0]'.
browser console screen
Main page Code:
<div class="row">
<div class="col-md-7">
<button id="btn">CLICK</button
</div>
<div class="col-md-5" id="divPartialViewContainer">
</div>
</div>
<script>
$(document).ready(function () {
$("#btn").on("click", function () {
var text = $(this).text().trim();
if (text.length > 0) {
console.log(text);
$.ajax({
url: '/RiskMap/RiskDetailsPartial/',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ 'param': text }),
success: function (content) {
$('#divPartialViewContainer').html(content);
},
error: function (e)
{
console.log(e);
}
});
}
});
});
</script>
Controller Code
[HttpPost]
public async Task<ActionResult> RiskDetailsPartial(string param)
{
return PartialView("_RiskDetails", new List<Risk>());
}
Partial View code:
#model IEnumerable<Core.Models.Risk>
#using Core.Models
#using Core.ComplexTypes
#{
ViewBag.Title = "Risks";
}
<h2>Risks</h2>
#(Html.DevExtreme().DataGrid<Risk>()
.DataSource(Model)
.Columns(columns =>
{
columns.AddFor(m => m.Id);
columns.AddFor(m => m.Impact);
columns.AddFor(m => m.Probability);
})
)
The message is clear you just need to read it more thoroughly.
Newtonsoft.Json.JsonSerializationException: Self referencing loop
detected for property 'ApplicationEntity' with type
'System.Data.Entity.DynamicProxies.ApplicationEntity_50A6A66F1464C1DE4E8A736E85D88C5AF4F4249EAE26FB21C4F82592E001885D'.
Path 'data[0].ApplicationEntity.ApplicationEntityHistories[0]'.
Json serializer is attempting to serialize some kind of entity(lets call it EntityA) you passed to it, but the problem is that this entity contains another entity(lets call it EntityB) that contains the first entity(EntityA). This is going in circles!
This has also happened to me with my own ORM and I found out the problem is lazy loading. I solved it by adding an interface to each of my entities:
interface IJSonify
{
object Json();
}
Here I simply return an anonymous object. Entity that implements this interface decides how it will represent itself as JSON object.
I had the same problem and I solved it by declaring JsonConvert defaultSettings on webApplication init.
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};
Apparently DevExtreme don't use standard ASP.NET MVC json serializer inside DataSourceLoader.Load(..) method, so if you set ASP.NET MVC json serializer ReferenceLoopHandling setting it is not enough.
Another solution is use [JsonIgnore] dataAnnotation above the property that generate the loop reference
I have a Kendo.MVC project. The view has a model with a field of type List<>. I want to populate the List from a Javascript function. I've tried several ways, but can't get it working. Can someone explain what I'm doing wrong?
So here is my model:
public class Dashboard
{
public List<Note> ListNotes { get; set; }
}
I use the ListNotes on the view like this:
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
}
This works if I populate Model.ListNotes in the controller when the view starts...
public ActionResult DashBoard(string xsr, string vst)
{
var notes = rep.GetNotesByCompanyID(user.ResID, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
Dashboard employee = new Dashboard
{
ResID = intUser,
Type = intType,
FirstName = user.FirstName,
LastName = user.LastName,
ListNotes = listNotes
};
return View(employee);
}
... but I need to populate ListNotes in a Javascript after a user action.
Here is my javascript to make an ajax call to populate ListNotes:
function getReminders(e)
{
var userID = '#ViewBag.CurrUser';
$.ajax({
url: "/api/WoApi/GetReminders/" + userID,
dataType: "json",
type: "GET",
success: function (notes)
{
// Need to assign notes to Model.ListNotes here
}
});
}
Here's the method it calls with the ajax call. I've confirmed ListNotes does have the values I want; it is not empty.
public List<Koorsen.Models.Note> GetReminders(int id)
{
var notes = rep.GetNotesByCompanyID(id, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
foreach (Koorsen.OpenAccess.Note note in notes)
{
Koorsen.Models.Note newNote = new Koorsen.Models.Note()
{
NoteID = note.NoteID,
CompanyID = note.CompanyID,
LocationID = note.LocationID,
NoteText = note.NoteText,
NoteType = note.NoteType,
InternalNote = note.InternalNote,
NoteDate = note.NoteDate,
Active = note.Active,
AddBy = note.AddBy,
AddDate = note.AddDate,
ModBy = note.ModBy,
ModDate = note.ModDate
};
listNotes.Add(newNote);
}
return listNotes;
}
If ListNotes was a string, I would have added a hidden field and populated it in Javascript. But that didn't work for ListNotes. I didn't get an error, but the text on the screen didn't change.
#Html.HiddenFor(x => x.ListNotes)
...
...
$("#ListNotes").val(notes);
I also tried
#Model.ListNotes = notes; // This threw an unterminated template literal error
document.getElementById('ListNotes').value = notes;
I've even tried refreshing the page after assigning the value:
window.location.reload();
and refreshing the panel bar the code is in
var panelBar = $("#IntroPanelBar").data("kendoPanelBar");
panelBar.reload();
Can someone explain how to get this to work?
I don't know if this will cloud the issue, but the reason I need to populate the model in javascript with an ajax call is because Model.ListNotes is being used in a Kendo Panel Bar control and I don't want Model.ListNotes to have a value until the user expands the panel bar.
Here's the code for the panel bar:
#{
#(Html.Kendo().PanelBar().Name("IntroPanelBar")
.Items(items =>
{
items
.Add()
.Text("View Important Notes and Messages")
.Expanded(false)
.Content(
#<text>
#RenderReminders()
</text>
);
}
)
.Events(e => e
.Expand("getReminders")
)
)
}
Here's the helper than renders the contents:
#helper RenderReminders()
{
if (Model.ListNotes.Count <= 0)
{
#Html.Raw("No Current Messages");
}
else
{
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
<br />
}
}
}
The panel bar and the helpers work fine if I populate Model.ListNotes in the controller and pass Model to the view. I just can't get it to populate in the javascript after the user expands the panel bar.
Perhaps this will do it for you. I will provide a small working example I believe you can easily extend to meet your needs. I would recommend writing the html by hand instead of using the helper methods such as #html.raw since #html.raw is just a tool to generate html in the end anyways. You can write html manually accomplish what the helper methods do anyway and I think it will be easier for you in this situation. If you write the html correctly it should bind to the model correctly (which means it won't be empty on your post request model) So if you modify that html using javascript correctly, it will bind to your model correctly as well.
Take a look at some of these examples to get a better idea of what I am talking about:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
So to answer your question...
You could build a hidden container to hold your list values like this (make sure this container is inside the form):
<div id="ListValues" style="display:none">
</div>
Then put the results your ajax post into a javascript variable (not shown).
Then in javascript do something like this:
$('form').off('submit'); //i do this to prevent duplicate bindings depending on how this page may be rendered futuristically as a safety precaution.
$('form').on('submit', function (e) { //on submit, modify the form data to include the information you want inside of your ListNotes
var data = getAjaxResults(); //data represents your ajax results. You can acquire and format that how you'd like I will use the following as an example format for how you could save the results as JSON data: [{NoteID ="1",CompanyID ="2"}]
let listLength = data.length;
for (let i = 0; i < listLength; i++) {
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].NoteID " value="' + data.NoteID +'" />')
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].CompanyID " value="' + data.CompanyID +'" />')
//for your ajax results, do this for each field on the note object
}
})
That should do it! After you submit your form, it should automatically model bind to you ListNotes! You will be able to inpsect this in your debugger on your post controller action.
I have a view with multiple sections. i would like to update sections individually using partial views and ajax.
I have this so far:
Controller:
[HttpPost]
public PartialViewResult userdetailssettings(UserDetails model)
{ .... }
View Html:
<div id="userDetailsPartial">
#Html.Partial("_user_details", Model.userdetails)
</div>
Partial Html:
<div class="form-group">
<div class="col-md-12">
#Html.TextBoxFor(x => x.Forename, new { #class = "form-control", placeholder = "Enter your forename" })
#Html.ValidationMessageFor(x => x.Forename)
</div>
</div>
<div class="form-group">
<div class="col-md-12">
#Html.TextBoxFor(x => x.Surname, new { #class = "form-control", placeholder = "Enter your surname" })
#Html.ValidationMessageFor(x => x.Surname)
</div>
</div>
Javascript on View:
var detailsUrl = "#Url.Action("userdetailssettings", "UserLogin")";
var detailsmodel = JSON.stringify(#Html.Raw(Json.Encode(#Model.userdetails)));
$(document).on('click touchstart', '#saveDetails', function () {
$.ajax({
type: "POST",
dataType: 'json',
data: detailsmodel,
url: detailsUrl,
contentType: "application/json"
}).done(function (res) {
$("#userDetailsPartial").html(res);
addresssearch();
});
});
The model is being passed to the controller by the ajax, however the values are not that of the inputs. They are the original values passed from the controller to open the view.
I have tried wrapping the partial in tags and also tried adding form tags inside the partial.
I have also tried putting this code:
var detailsUrl = "#Url.Action("userdetailssettings", "UserLogin")";
var detailsmodel = JSON.stringify(#Html.Raw(Json.Encode(#Model.userdetails)));
Inside the click function.
Nothing I do passes the updated values from the inputs.
I have thought of creating a new instance of the model from the inputs in the javascript i.e.
var detailsmodel = [ { Forename: $('#Forename').val(), Surname: $('#Surname').val() } ];
But if I am just creating json why can I not just convert the bound model to json.
why can I not just convert the bound model to json
This is because you are using MVC, not MVVM.
The "bound model" is one way from the controller to the view via the model; it's possible you're mixing the term "bound model" with "model" and "binding".
If you POST the form, you'll get the model in the Action (based on parameters of course), but if you pass via ajax, you'll need to get the current values from the form (as in your comment 'creating a new instance of the model from the inputs').
You can generate data to pass via AJAX in various ways, such as:
var data = $("form").serialize();
rather than adding every input manually.
var detailsmodel = JSON.stringify... is set when the view is generated and will not change automatically using MVC.
That's because the data you're passing is statically set at page load, based on #Html.Raw(Json.Encode(#Model.userdetails)).
You would need to use something like $form.serialize(), or otherwise create the post body from the actual fields on the page.
I'm trying to store user input in a javascript array and send it to controller via ajax call. But all I get in controller's parameter is null.
Here's the code:
<table class="table-condensed table-bordered table-striped table-responsive">
#foreach (var project in projects)
{
<tr>
#foreach (var parameter in parameters)
{
<td>
<input type="text" class="form-control remar" id=#i />
</td>
i++;
}
</tr>
}
<tr>
<td colspan=#(parameters.Count() + 1)>
<button class="btn btn-primary pull-right" onclick="insert()">Submit Remarks</button>
</td>
</tr>
</table>
<script>
function insert() {
var remarks = [];
jQuery(".remark").each(function () {
remarks.push(jQuery(this).val());
});
$.ajax({
type: "POST",
url: "#Url.Action("AddRemarksToEvaluationSheet", "Teacher")",
data: JSON.stringify({ function_param: remarks }),
contentType: "application/json; charset=utf-8;"
});
}
</script>
Controller:
public ActionResult AddRemarksToEvaluationSheet(string[] function_param)
{
return View();
}
Any help?
P.S. the above code is edited. It worked!
You've got lots going on here...
First, don't give your input boxes ids of numbers - in this scenario, it doesn't look like you even use the value...But if you need it, put the value into a data element:
<input type="text" class="form-control remark" data-remark-id="#i" />
When retrieving the values, you need to get the value, not the textbox itself:
var remarks = [];
jQuery(".remark").each(function() {
remarks.push(jQuery(this).val());
});
When doing anything weird with parameters, like arrays or complex objects, if you use JSON instead of the default of URL-encoded, it will make things nicer.
You should also avoid absolute paths, and use Url.Action instead, so that it'll work regardless of where your app lives relative to the domain.
$.ajax({
type: "POST",
url: "#Url.Action("AddRemarksToEvaluationSheet", "Teacher")",
data: JSON.stringify({ function_param: remarks }),
contentType: "application/json; charset=utf-8;"
});
And you can accept an array of strings, rather than of objects:
[HttpPost]
public ActionResult AddRemarksToEvaluationSheet(string[] function_param)
{
}
I have a feeling that you aren't getting the remarks in the array in the first place.
If you aren't already, use a browser that allows you to debug the js. If you use Chrome, right-click -> inpsect element (or F12). Go to the 'Sources' tab, go to your js file and put a break point to see what the remarks array looks like.
Regarding the code:
You do not seem to need id attributes on the inputs. Let alone numerical ids.
To populate the remarks array, get all dom elements having the class you placed on all inputs. For each one, push the value in the array.
var remarks = [];
$(".form-control").each(function() {
remarks.push($(this).val());
});
You can add extra code to only add the ones with value.
var remarks = [];
$(".form-control").each(function() {
if($(this).val().length){
remarks.push($(this).val());
}
});
The ajax call:
$.ajax({
type: "POST",
url: addRemarksUrl,
data: JSON.stringify({ function_param: remarks }),
contentType: "application/json; charset=utf-8;"
});
Where addRemarksUrl can be a global variable declared in the html.
There are other ways of getting the url. Have a look here:
How to send razor created Url to js file
This user offers 3 possible solutions:
global js variable
custom "data-" attribute
hidden input