In an effort to make a progress reporting process a little more reliable and decouple it from the request/response, I am performing the processing in a Windows Service and persisting the intended response to a file. When the client starts polling for updates, the intention is that the controller returns the contents of the file, whatever they are, as a JSON string.
The contents of the file are pre-serialized to JSON. This is to ensure that there is nothing standing in the way of the response. No processing needs to happen (short of reading the file contents into a string and returning it) to get the response.
I initially though this would be fairly simple, but it is not turning out to be the case.
Currently my controller method looks thusly:
Controller
Updated
[HttpPost]
public JsonResult UpdateBatchSearchMembers()
{
string path = Properties.Settings.Default.ResponsePath;
string returntext;
if (!System.IO.File.Exists(path))
returntext = Properties.Settings.Default.EmptyBatchSearchUpdate;
else
returntext = System.IO.File.ReadAllText(path);
return this.Json(returntext);
}
And Fiddler is returning this as the raw response
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Mon, 19 Mar 2012 20:30:05 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 3.0
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 81
Connection: Close
"{\"StopPolling\":false,\"BatchSearchProgressReports\":[],\"MemberStatuses\":[]}"
AJAX
Updated
The following will likely be changed later, but for now this was working when I was generating the response class and returning it as JSON like a normal person.
this.CheckForUpdate = function () {
var parent = this;
if (this.BatchSearchId != null && WorkflowState.SelectedSearchList != "") {
showAjaxLoader = false;
if (progressPending != true) {
progressPending = true;
$.ajax({
url: WorkflowState.UpdateBatchLink + "?SearchListID=" + WorkflowState.SelectedSearchList,
type: 'POST',
contentType: 'application/json; charset=utf-8',
cache: false,
success: function (data) {
for (var i = 0; i < data.MemberStatuses.length; i++) {
var response = data.MemberStatuses[i];
parent.UpdateCellStatus(response);
}
if (data.StopPolling = true) {
parent.StopPullingForUpdates();
}
showAjaxLoader = true;
}
});
progressPending = false;
}
}
The issue, I believe, is that the Json action result is intended to take an object (your model) and create an HTTP response with content as the JSON-formatted data from your model object.
What you are passing to the controller's Json method, though, is a JSON-formatted string object, so it is "serializing" the string object to JSON, which is why the content of the HTTP response is surrounded by double-quotes (I'm assuming that is the problem).
I think you can look into using the Content action result as an alternative to the Json action result, since you essentially already have the raw content for the HTTP response available.
return this.Content(returntext, "application/json");
// not sure off-hand if you should also specify "charset=utf-8" here,
// or if that is done automatically
Another alternative would be to deserialize the JSON result from the service into an object and then pass that object to the controller's Json method, but the disadvantage there is that you would be de-serializing and then re-serializing the data, which may be unnecessary for your purposes.
You just need to return standard ContentResult and set ContentType to "application/json".
You can create custom ActionResult for it:
public class JsonStringResult : ContentResult
{
public JsonStringResult(string json)
{
Content = json;
ContentType = "application/json";
}
}
And then return it's instance:
[HttpPost]
public ActionResult UpdateBatchSearchMembers()
{
string returntext;
if (!System.IO.File.Exists(path))
returntext = Properties.Settings.Default.EmptyBatchSearchUpdate;
else
returntext = Properties.Settings.Default.ResponsePath;
return new JsonStringResult(returntext);
}
Yeah that's it without no further issues, to avoid raw string json this is it.
public ActionResult GetJson()
{
var json = System.IO.File.ReadAllText(
Server.MapPath(#"~/App_Data/content.json"));
return new ContentResult
{
Content = json,
ContentType = "application/json",
ContentEncoding = Encoding.UTF8
};
}
NOTE: please note that method return type of JsonResult is not working for me, since JsonResult and ContentResult both inherit ActionResult but there is no relationship between them.
Use the following code in your controller:
return Json(new { success = string }, JsonRequestBehavior.AllowGet);
and in JavaScript:
success: function (data) {
var response = data.success;
....
}
All answers here provide good and working code. But someone would be dissatisfied that they all use ContentType as return type and not JsonResult.
Unfortunately JsonResult is using JavaScriptSerializer without option to disable it. The best way to get around this is to inherit JsonResult.
I copied most of the code from original JsonResult and created JsonStringResult class that returns passed string as application/json. Code for this class is below
public class JsonStringResult : JsonResult
{
public JsonStringResult(string data)
{
JsonRequestBehavior = JsonRequestBehavior.DenyGet;
Data = data;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("Get request is not allowed!");
}
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
response.Write(Data);
}
}
}
Example usage:
var json = JsonConvert.SerializeObject(data);
return new JsonStringResult(json);
Related
I posted an XmlRequest with native JavaScript to my controller, but it doesn't accept the request body as a parameter. I.e. it reads it as a null even if the parameter is meant to be a string.
The request:
request.open("POST", "https://localhost:44328/CommodityTypes/PostData");
//request.setRequestHeader('Content-type', 'text'); // maybe that makes the problem?
request.send("Message");
The controller:
[HttpPost]
public string PostData(string Text)
{
return JsonSerializer.Serialize(Text);
}
Will be happy to get any advice on it.
After getting deeper into the business I found, that one can specify 'object' as
a parameter to parse for ('[FromBody]' attribute stays), getting a curious object, that gives the JSON message invoking ToString() method. If you have a variable or uneven structure of incoming JSON you may use this aproach.
Though there must be some other, meant way of handling the issue.
You can create object data = { "text": "YourText" } and send JSON.stringify(data)
And need set header xmlhttp.setRequestHeader('Content-Type', 'application/json');
var uri = '/CommodityTypes/PostData';
var xmlhttp;
xmlhttp = new XMLHttpRequest();
var data = { "text": "YourText" };
var sendstr = JSON.stringify(data);
xmlhttp.open("POST", uri, true);
xmlhttp.setRequestHeader('Content-Type', 'application/json');
xmlhttp.send(sendstr);
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
alert(xmlhttp.responseText);
}
in POST you have to send the Text parameter as FormData so it can be recognized and mapped by the method
var data = new FormData();
data.append("Text", "test");
var xhr = new XMLHttpRequest();
xhr.addEventListener("readystatechange", function() {
if(this.readyState === 4) {
console.log(this.responseText);
}
});
xhr.open("POST", "https://localhost:44328/CommodityTypes/PostData");
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(data);
I'm calling a POST controller action using fetch, but in the controller, the body appears to be null.
Here's my fetch code snippet - this is in a .net core Vue project. It's a typescript file.
var data = JSON.stringify(this.newProduct);
console.log(data)
fetch('api/Product/AddNewProduct', {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
And here's the request (and payload) as I can see it in Firefox:
But in my .net core backend, when the API gets hit, I can't seem to get the value of the body or anything in the request.
[HttpPost("[action]")]
public IActionResult AddNewProduct([FromBody] string body)
{
Product newProduct;
try
{
/*Added the below snippet for testing, not sure if actually necessary */
using (var reader = new StreamReader(Request.Body))
{
var requestBody = reader.ReadToEnd();
// Do something
}
//Convert the request body to an object
newProduct = JsonConvert.DeserializeObject<Product>(body);
}
catch (Exception e)
{
return new BadRequestResult();
}
Here, in my debugger, both body and requestBody are null. Any ideas?
.NET isn't seeing you passing a string, it sees a JSON because you pass the Content-Type header of application/json so it will try to deserialize it and map it to your request object. In your case, because your parameter is string body the parser tries to map the JSON object to your string and fails - so it passes null.
You can try and change the request to pass text/plain as the content-type (or remove the content-type header) or change your API parameter to the object you are sending:
public IActionResult AddNewProduct([FromBody] Product newProduct)
{
...
}
Add to headers Accept
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
The following is an example how i am posting data to a controller action in MVC.The data is perfectly populating to my model object and working fine.
var data={};
data.SearchText = 'abc'
data.SearchText1 = 'abcd'
var contentType = 'application/json; charset=utf-8';
$.ajax({
type: 'POST',
cache: false,
contentType: contentType,
url: User/_UserList,
data: JSON.stringify(data),
success: successHandlerFunction,
complete: completeHandlerFunction
});
[HttpPost]
public JsonResult _UserList(SearchViewModel model)
{
var users= "" ; //Get Data from DB ;
return Json(users, JsonRequestBehavior.AllowGet);
}
But in case of an exception i am trying to fetch value of model
public void OnException(ExceptionContext filterContext)
{
//filterContext.HttpContext.Request.Form -- is not giving any value
//filterContext.HttpContext.Request.QueryString -- is also having no value
//filterContext.HttpContext.Request.Params -- also no value about model
}
Can anybody give me a clue why the above piece of code not working
Sorry - can't comment your answer, so placing a new answer.
The solution is right - you should make the contentType as 'application/x-www-form-urlencoded'. The reason is clearly described here: HttpRequest.Form Property
The Form property is populated when the HTTP request Content-Type value is either "application/x-www-form-urlencoded" or "multipart/form-data".
As for 'JSON.stringify(data),' - that will make one string from the dictionary actually and in this case HttpRequest.Form cannot be populated as it requires a dictionary, basically, just 'data'.
By Changing the contentType from 'application/json; charset=utf-8' to
'application/x-www-form-urlencoded' the HttpContext.Request.Form started populating the value.We also need to remove the JSON.stringify while passing the data.
If we post the data with contentType 'application/json; charset=utf-8' .And from the request object we need to retrieve the Json Contents then we can use the following piece of code
private string GetJsonContents(System.Web.HttpRequestBase Request)
{
string JsonContents = string.Empty;
try
{
using (Stream receiveStream = Request.InputStream)
{
using (StreamReader readStream = new StreamReader(receiveStream))
{
receiveStream.Seek(0, System.IO.SeekOrigin.Begin);
JsonContents = readStream.ReadToEnd();
}
}
}
catch
{
}
return JsonContents;
}
Then we can use the following code to get the JSON Object
string requestData = GetJsonContents(HttpContext.Current.Request);
dynamic jsonObject = JsonConvert.DeserializeObject<dynamic>(requestData);//using Newtonsoft dll
This has been driving me nuts. I have a page where I need to make a JSON post to a controller, it will process it and return an excel file for download. So far it appears to be running correctly, but when it returns to the ajax call, I get an parsererror and the message "Unexpected token P." I have tried so many different configurations and call methods (standard MVC ActionRequest to WebApi post) and none of them change. Here is the code I'm running.
JavaScript:
var treatmentplan = {"PlanRecordStatusId":"1","PlanRecordDateBegin":"","PlanRecordDateEnd":"","ClientClaimNumber":"","PatientNumber":0,"requestAction":3};
$.ajax({
//global: true,
//url: '/home/ExcelRpt',
url: '/api/TreatmentPlanExcel',
type: 'POST',
dataType: 'json',
data: treatmentplan,
//contentType: 'application/json; charset=utf-8',
success: function (data) {
//var msg = data.Message;
//$('#results').html(msg);
$("#tmpFrame").attr('src', 'URL-TO-EXCEL-FILE');
}
, error: function (jqXHR, exception, error) {
if (jqXHR.status === 0) {
alert('Not connect.n Verify Network.');
} else if (jqXHR.status == 404) {
alert('Requested page not found. [404]');
} else if (jqXHR.status == 500) {
alert('Internal Server Error [500].');
} else if (exception === 'parsererror') {
alert('Requested JSON parse failed.');
} else if (exception === 'timeout') {
alert('Time out error.');
} else if (exception === 'abort') {
alert('Ajax request aborted.');
} else {
alert('Uncaught Error.n' + jqXHR.responseText);
}
$('#log').html(error.message);
}
});
Here is the C# code (both WebApi and MVC controller version), I am not going to include my ToExcel extension, I know this part works it's just a matter of getting it to download when it's returned. It is reaching this code, generates data and returning. I just need to see what the heck is going on. If there is a prefered way of making the call, let me know as well (WebApi or MVC)
C#
Web Api version
public HttpResponseMessage Post(TreatmentPlanRequest tpRequest) {
tpRequest.Verify();
List<TreatmentPlan> tpl = DataAccess.GetReportDap(tpRequest).ToList();
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var package = tpl.ToExcel("TreatmentReport");
var fileStream = new MemoryStream();
package.SaveAs(fileStream);
fileStream.Position = 0;
result.Content = new StreamContent(fileStream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return result;
}
Here is the MVC controller version
[HttpPost]
public ActionResult ExcelRpt(TreatmentPlanRequest tpRequest) {
tpRequest.Verify();
List<TreatmentPlan> tpl = DataAccess.GetReportDap(tpRequest).ToList();
var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var package = tpl.ToExcel("TreatmentReport");
var fileStream = new MemoryStream();
package.SaveAs(fileStream);
fileStream.Position = 0;
var fsr = new FileStreamResult(fileStream, contentType);
fsr.FileDownloadName = "TreatmentReport";
return fsr;
}
From here, I have no clue as to why this isn't working. I have searched high and low on Google on how to do this in MVC (I use to do this with web forms and never had an issue). I am guessing my issue is on the return or
Change your Success method to open a new window with the URL instead of setting a frame in the current window.
So this:
$("#tmpFrame").attr('src', 'URL-TO-EXCEL-FILE');
becomes:
window.open('URL-TO-EXCEL-FILE');
In the vast majority of cases, this should do exactly what you're looking for. Occasionally, depending on specific browser settings, users may get a "Popup Blocked" message, but that rarely happens in this scenario in the apps that I've worked with.
EDIT:
After additional clarification, there is a second issue. Data returned from the server must be in the same format as the .ajax() method is expecting it, in this case 'JSON'. Instead of returning a FileStreamResult from your Action, try returning a JSON object which has the URL you'll need to call to generate the Excel file.
return Json(new { URL = 'URL-TO-EXCEL-FILE' });
Then, follow the suggestion in my original response.
I would like to thank Kris Hatcher for the solution on this. He suggested making two ActionResults. One that builds a query string from the parameters of the initial request. It returns a full URL with the parameters. It then does a Window.Open() using the returned url.
With all the examples I found, this was the only one that worked for me. Here's how the code works.
JavaScript:
function TestWebApiReport() {
var reportData = GetReport();
$.ajax({
url: '/home/ExcelResults'
, data: reportData
, type: 'POST'
, dataType: 'json'
, success: function (data) {
window.open(data.URL);
}, error: function (jqXHR, exception, error) {
alert("GRRRRRR!!!")
}
});
}
It creates the JSON data, then posts it to a JsonResult. Here's the controller code.
C#
[HttpPost]
public JsonResult ExcelResults(ReportRequest tpRequest) {
StringBuilder sb = new StringBuilder();
bool firstIn = true;
sb.AppendFormat("{0}/Home/ExcelRpt", Request.Url.Scheme + "://" + Request.Url.Authority);
foreach (var prop in tpRequest.GetType().GetProperties()) {
if (prop.GetValue(tpRequest, null) != null) {
if (firstIn) {
sb.AppendFormat("?");
firstIn = false;
} else {
sb.AppendFormat("&");
}
sb.AppendFormat("{0}={1}", prop.Name, prop.GetValue(tpRequest, null));
}
}
return Json(new { URL = sb.ToString() });
}
You go back to the JavaScript, you'll see the return data uses the URL to do a Window.Open(). Then the excel file is created. Here's the last call (ActionResult).
[HttpGet]
public ActionResult ExcelRpt(ReportRequest tpRequest) {
if (tpRequest.requestAction != RequestAction.Report) {
throw new Exception("Did not use action request type of 'Report'.");
}
tpRequest.requestAction = RequestAction.Report;
List<Report> tpl = DataAccess.GetReportDap(tpRequest).ToList();
var contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var package = tpl.ToExcel("Report");
var fileStream = new MemoryStream();
package.SaveAs(fileStream);
fileStream.Position = 0;
var fsr = new FileStreamResult(fileStream, contentType);
fsr.FileDownloadName = "TreatmentReport.xlsx";
return fsr;
}
ReportRequest is a class I have and ToExcel(), I extended the List item. Now this works pretty well.
I am using Kendo UI upload control. I have defined the Kendo UI upload like this:
<input type="file" name="resume" />
$("#file").kendoUpload({
async: {
saveUrl: "/Home/SaveResume",
autoUpload: true
},
complete: function (e)
{
// here i want to get the text that is returned from the controller
}
});
The controller code is like:
public ActionResult SaveResume(HttpPostedFileBase resume)
{
var text;
// code for the file to convert to text and assign it to text
return Json(text, JsonRequestBehavior.AllowGet);
}
After returning the code I want to retrieve the code in complete function. How can I do that?
You can get the response to success function like this
function onSuccess(e)
{
var response = e.response.data();
}
where the return json could be
return Json(new { data = text }, "text/plain");
If you just passing a string back you should be able to do:
function onSuccess(e) {
var text = e.XMLHttpRequest.responseText;
}
You could also pass back a more complex object, if required:
// Object
public class MyObject
{
public int ID { get; set; }
public string Text { get; set; }
}
// Controller Action
public virtual ActionResult Upload(HttpPostedFileBase file)
{
return this.Json(new MyObject(), "text/plain");
}
// Javascript Handler
function onSuccess(e) {
var response = jQuery.parseJSON(e.XMLHttpRequest.responseText);
var id = response.ID;
var text = response.Text;
}
I'll add my answer alongside the other valid answers here. First though you will want to get the returned response in the success function instead of the complete function:
$("#files").kendoUpload({
async: {
saveUrl: url,
removeUrl: removeUrl,
autoUpload: true
},
select: onFileSelect, // function for when a file is selected
success: onFileSuccess, // function that returns response after upload
complete: onFileComplete, // function after success
remove: onFileRemove, // function for when a file is removed
});
The on success function returns an object (normally people name it e)
function onFileSuccess(e) {
console.log("e.response", e.response);
console.log("e.operation", e.operation);
console.log("e.XMLHttpRequest.status", e.XMLHttpRequest.status);
//e.operation is upload or remove
if (e.operation === "upload") {
// a file was added, get the response
var fileid = e.response;
} else {
// Do something after a file was removed
}
}
My console.log entries return this data:
console.log values
This is how I return my data from the server:
public HttpResponseMessage InsertTempFile()
{
HttpPostedFile file = System.Web.HttpContext.Current.Request.Files[0];
//........
// Code that adds my file to the database
// and generates a new primary key for my file
//.........
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent(myNewId.ToString());
return response;
}
The response.Content returns my new Id in e.response
The HttpStatusCode.Ok returns my status of 200. There's a bunch of other data that is returned as well if you inspect the response.
Note that to use HttpResponseMessage and HttpStatuseCode you need to include the following namespaces in your class:
using System.Net.Http;
using System.Net;