Web Api: recommended way to return json string

醉酒当歌 提交于 2019-11-28 11:21:32

Create custom implementation. The framework is extensible via the IHttpActionResult.

The following creates a custom result and extension method...

public static class JsonStringResultExtension {
   public static CustomJsonStringResult JsonString(this ApiController controller, string jsonContent, HttpStatusCode statusCode = HttpStatusCode.OK) {
        var result = new CustomJsonStringResult(controller.Request, statusCode, jsonContent);
        return result;
    }

    public class CustomJsonStringResult : IHttpActionResult {
        private string json;
        private HttpStatusCode statusCode;
        private HttpRequestMessage request;

        public CustomJsonStringResult(HttpRequestMessage httpRequestMessage, HttpStatusCode statusCode = HttpStatusCode.OK, string json = "") {
            this.request = httpRequestMessage;
            this.json = json;
            this.statusCode = statusCode;
        }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) {
            return Task.FromResult(Execute());
        }

        private HttpResponseMessage Execute() {
            var response = request.CreateResponse(statusCode);
            response.Content = new StringContent(json, Encoding.UTF8, "application/json");
            return response;
        }
    }
}

...that can then be applied to ApiController derived classes. Greatly simplifying previous calls to

return this.JsonString(jsonUtilizadores); //defaults to 200 OK

or with desired HTTP status code

return this.JsonString(jsonUtilizadores, HttpStatusCode.BadRequest);

Set your Web Api to return JSON Format:

    public static void Register(HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        // Force to ignore Request Content Type Header and reply only JSON
        config.Formatters.Clear();
        config.Formatters.Add(new JsonMediaTypeFormatter());

        var corsAttr = new EnableCorsAttribute("*", "*", "*");
        config.EnableCors(corsAttr);
    }

and then return response like this:

        [HttpGet]
    [Route("{taskId}/list")]
    public IHttpActionResult GetTaskDocuments(string taskId)
    {
        var docs = repository.getTaskDocuments(taskId);
        if (docs != null)
        {
            return Ok(docs);
        }
        else
        {
            return Ok(new ResponseStatus() { Status = Constants.RESPONSE_FAIL, Message = repository.LastErrorMsg });
        }
    }

Where ResponseStatus is next class:

 public class ResponseStatus
    {
        public string Status { get; set; }
        public string Message { get; set; }
    }

Leave the response format to the content negotiation in order to be REST compliant, the client should decide what format it wants.

In your web API action, you should just return Ok(your object) and web API will see what is the best format to return it.

If you need to omit other formatters, then just remove the other MediaTypeFormatter objects just as Nkosi advised.

greg

Here is @Nkosi's answer translated to VB.Net.

I am building a WebAPI2 backend to support TourOfHeroes.

I imagine case where I have a 'heavy' object and I dont want to return all properties to all views. Imagine Hero had ~30 columns and I only need Id and Name for the Heroes.component.ts....

I dont want to additionally create an object to represent each view just so I can JSON serialize them... DRY?

So, here are the 2 classes that @NKosi posted. Not sure the shared class is necessary, I just implemented as @NKosi wrote it.

Imports System.Net.Http
Imports System.Web.Http
Imports System.Net
Imports System.Threading
Imports System.Threading.Tasks

Public Class JsonStringResultExtension

    Public Shared Function JSONString(Controller As ApiController,
                                                Optional jsonContent As String = "",
                                             Optional statusCode As HttpStatusCode = HttpStatusCode.OK) As CustomJsonStringResult

        Dim result As New CustomJsonStringResult(Controller.Request, statusCode, jsonContent)
        Return result
        End Function

  End Class

    Public Class CustomJsonStringResult
        Implements IHttpActionResult

    Private json As String
    Private statusCode As HttpStatusCode
    Private request As HttpRequestMessage

    Public Sub New(httpRequestMessage As HttpRequestMessage, Optional statusCode As HttpStatusCode = HttpStatusCode.OK, Optional json As String = "")
        Me.request = httpRequestMessage
        Me.json = json
        Me.statusCode = statusCode
    End Sub

    Public Function ExecuteAsync(cancellationToken As CancellationToken) As Task(Of HttpResponseMessage) Implements IHttpActionResult.ExecuteAsync
        Return Task.FromResult(Execute())
    End Function

    Private Function Execute() As HttpResponseMessage
        Dim response = request.CreateResponse(statusCode)
        response.Content = New StringContent(json, Encoding.UTF8, "application/json")

        Return response
    End Function


End Class

I will also add that you have to the following to webApiConfig.vb or you will get complaints that you cant parse the HTML

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(New MediaTypeHeaderValue("text/html"))
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(New MediaTypeHeaderValue("application/json"))

Here is the webAPI controller call that supports the heroes.component.js Exactly like @Luis Abreu, my BOs are already returning JSON. I just want to return the correctly formed JSON as a string, as opposed to mapping my BO into another class here in the API for the sole purpose of serializing it...

 <HttpGet>
        <Route("")>
        Public Function GetAllHeroes() As CustomJsonStringResult

           'populate a list of heros however you want 
            dim ag as  new  HeroAg()
            Dim l As List(Of TOHbos.Hero) = ag.Items



            'This should be shoved down into a reusable class...ListToJSON
            Dim sb As New StringBuilder("")
            Dim first As Boolean = True
            sb.Append("[")
            For Each h As TOHbos.Hero In l

                If first = True Then
                    first = False
                Else
                    sb.Append(", ")
                End If
                 'decide which fields to include and generate a JSON document
                 h.decideWhichFieldsGetSerializedForThisView()
                sb.Append(h.JSON)
            Next
            sb.Append("]")

            Return JsonStringResultExtension.JSONString(Me, sb.ToString(), HttpStatusCode.OK)

        End Function

The next problem I ran into was that I was getting this error from the angular app in the browser console

 Http failure during parsing for localhost:4300/api/heroes

Using jsonLint.com ,i was able to figure out that my JSON was not quite correct. I wasn't putting the key names in double ticks, and i was putting values in single ticks. While this may be acceptable JSON, jsonLint complains, and I guess so does Angular rxjs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!