77

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: 2012年3月19日 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;
 }
}
asked Mar 19, 2012 at 20:50
6
  • 1
    Can you not just return the string, you would need to change the return type to string. Commented Mar 19, 2012 at 20:53
  • What are you using to make the Ajax call (jQuery, custom, dojo)? Could you provide that code? Commented Mar 19, 2012 at 20:54
  • I don't see you using this path variable anywhere in your controller action. Where are you reading the contents of the file? All you do is return a JSON with the contents of the Properties.Settings.Default.EmptyBatchSearchUpdate property. Also were you aware that you cannot read a file while another thread is writing to it? At least this cannot happen in a safe way. You might very quickly run into race conditions. So I think that your design is flawed from the beginning. Commented Mar 19, 2012 at 20:56
  • @Paul I am using Ajax, I will post it in a minute. The code above is edited, I will change it in a sec. The return value seen in the fiddler return accurately represents properly escaped C# string of the JSON object I am attempting to respond with. Commented Mar 19, 2012 at 21:03
  • 1
    Yeah I have already encountered problems with that, and have taken steps to prevent it in the Windows Service. Thanks! Commented Mar 19, 2012 at 21:19

6 Answers 6

158

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.

Liam
30k28 gold badges140 silver badges204 bronze badges
answered Mar 19, 2012 at 21:01
5
  • 4
    +1: You also need to set the result's ContentType property to "application/json", since that's something the JsonResult does automatically. Commented Mar 19, 2012 at 21:09
  • @StriplingWarrior good point, I will update my code example to use the other Content method overload. Commented Mar 19, 2012 at 21:13
  • 1
    It is worth noting that Content is returning ContentResult where Json is returning JsonResult. Commented Apr 24, 2018 at 10:36
  • There's also a signature with encoding: return this.Content(returntext, "application/json", System.Text.Encoding.UTF8); Commented Mar 4, 2022 at 10:45
  • if no encoding is set, the encoding type won't be written. Commented Jul 4, 2023 at 7:34
48

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);
}
 
answered Mar 19, 2012 at 21:07
2
  • It is, although its biggest benefit is re-use. I only needed this for a single response. If however in the future, I needed it in other places, I would totally go the route of this answer. Commented Apr 3, 2015 at 2:20
  • The return type should be ActionResult. With JsonResult does not compile. With ActionResult works. Commented Feb 13, 2023 at 20:58
8

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.

answered Jun 12, 2017 at 16:59
2

Use the following code in your controller:

return Json(new { success = string }, JsonRequestBehavior.AllowGet);

and in JavaScript:

success: function (data) {
 var response = data.success;
 ....
}
Cody Gray
246k53 gold badges508 silver badges588 bronze badges
answered Apr 3, 2019 at 21:40
1
  • 1
    Thank you for the new answer, but that doesn't really fit the original question. I had pre-generated JSON data that I wanted to return, not something I wanted represented as a string value of a JSON object. Also, note that in the question I am in an action that is marked explicitly as an HTTP Post. AllowGet will do nothing for that. Commented Apr 4, 2019 at 20:27
0

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);
answered Apr 24, 2018 at 10:53
0

Please try this in action method to return jsonresult

 public JsonResult get_registration(NameValue[] formVars)
 {
 try
 {
 JsonString = System.IO.File.ReadAllText(Server.MapPath(@"~/App_Data/sample.json"));
 }
 catch (Exception ex)
 {
 JsonString = ex.Message.ToJsonError("Exception");
 }
 return Json(new { data = JsonString });
 }
answered Jun 13, 2024 at 8:24

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.