Nswag: nswag v13 can't handle text\plain and string response

Created on 2 Sep 2019  路  16Comments  路  Source: RicoSuter/NSwag

This is very old issue and it was fixed in v12, but appears again in v13: #701

Controller's attributes:

[Produces("text/plain")]
[ProducesResponseType(typeof(FileContentResult), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseMessage), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(ErrorResponseMessage), (int)HttpStatusCode.NotFound)]

Swagger:

"get": {
        "tags": [ "Binary" ],
        "summary": "Download binary as base64 string.",
        "operationId": "DownloadBinaryAsBase64",
        "consumes": [],
        "produces": [ "text/plain" ],
        "parameters": [
          {
            "name": "binaryId",
            "in": "path",
            "description": "Id of binary to fetch.",
            "required": true,
            "type": "string",
            "format": "guid"
          }
        ],
        "responses": {
          "200": {
            "description": "Content downloaded.",
            "schema": { "type": "string" }
          },
          "400": {
            "description": "Request contains wrong data.",
            "schema": { "$ref": "#/definitions/ErrorResponseMessage" }
          },
          "404": {
            "description": "Not Found",
            "schema": { "$ref": "#/definitions/ErrorResponseMessage" }
          }
        }

Produced code (part):

var status_ = ((int)response_.StatusCode).ToString();
if (status_ == "200") 
{
    var objectResponse_ = await ReadObjectResponseAsync<string>(response_, headers_).ConfigureAwait(false);
    return objectResponse_.Object;
}

ReadObjectResponseAsync always try to deserialize the string and of course it will fail.

Revert to v12.2.5 fix the issue.

done

Most helpful comment

Any idea when this patch will be released?

All 16 comments

https://github.com/RicoSuter/NSwag/pull/1976/files#diff-4cbdc640407fc6a649e91bcc43983584L17 :(

So its a regression of this pr?
https://github.com/RicoSuter/NSwag/pull/1976

/cc @Jehoel

@RicoSuter Did V12.2.5 specifically support text/plain or was it supported by coincidence? I note that there isn't a check like {% if response.IsPlainText %} in the .liquid templates to handle this case. I remember my changes were only to the "is expecting a JSON object response" code-path.

@Jehoel text/plain support was specifically added.

@et1975 ah, thank you - I overlooked that.

I鈥檒l work on a patch right away.

Here's my patch: https://github.com/RicoSuter/NSwag/pull/2587

I'd appreciate it if you can test and verify it @et1975 as I don't have any Swagger files to-hand with text/plain responses.

Any idea when this patch will be released?

I had to rollback to 13.2.0.0 to make it work

Thanks @FrancoisCamus. Rolling back to 13.2.0 "fixed" the problem for me.

The patch has been merged and was released as the 13.2.0 it seems.
Do you mean that newer versions are broken ? Is that really the same issue ?

Yes it seems like the same bug for me and that at least 13.6.2 is broken. I recerated the code again with 13.6.2 and 13.2.0 to compare it. In 13.6.2 the client code tries to parse the string with Newtonsoft.Json what leads to an exception.

My swagger.yaml looks like this

swagger: "2.0"
info:
  description: "This is a REST server via which data can be queried by the TMS."
  version: "1.0.0"
  title: "REST API for TMS"
host: localhost:8081 # TODO replace with docker parameter for base url
schemes:
  - http # TODO use protocol from docker parameter base url
basePath: "/"
paths:
...
  /topic/{topicId}/subject/de:
    get:
      summary: "Get subject by the topicId"
      operationId: "getDeSubjectByTopicId"
      tags:
        - Topic
      description: "Returns the subject from the passed topic id."
      produces:
      - "text/plain"
      parameters:
      - name: "topicId"
        in: "path"
        description: "ID of the topic"
        required: true
        type: "integer"
        format: "int32"
      responses:
        "200":
          description: "successful operation"
          schema:
            type: string
            example: subject
        "400":
          description: "Invalid ID supplied"
        "404":
          description: "Topic not found"
      security:
      - api_key: []
...

C# generated with 13.2.0:

public async System.Threading.Tasks.Task<string> GetDeSubjectByTopicIdAsync(int topicId, System.Threading.CancellationToken cancellationToken)
{
   ...
   if (status_ == "200") 
   {
      var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
      var result_ = (string)System.Convert.ChangeType(responseData_, typeof(string));
      return result_;
   }
   ...
}

C# generated with 13.6.2:

public async System.Threading.Tasks.Task<string> GetDeSubjectByTopicIdAsync(int topicId, System.Threading.CancellationToken cancellationToken)
{
   ...
   if (status_ == "200") 
   {
      var objectResponse_ = await ReadObjectResponseAsync<string>(response_, headers_).ConfigureAwait(false);
      return objectResponse_.Object;
   }
   ...
}

protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers)
{
    if (response == null || response.Content == null)
    {
        return new ObjectResponseResult<T>(default(T), string.Empty);
    }

    if (ReadResponseAsString)
    {
        var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        try
        {
            var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText, JsonSerializerSettings);
            return new ObjectResponseResult<T>(typedBody, responseText);
        }
        catch (Newtonsoft.Json.JsonException exception)
        {
            var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
            throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
        }
    }
    else
    {
        try
        {
            using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
            using (var streamReader = new System.IO.StreamReader(responseStream))
            using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
            {
                var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
                var typedBody = serializer.Deserialize<T>(jsonTextReader);
                return new ObjectResponseResult<T>(typedBody, string.Empty);
            }
        }
        catch (Newtonsoft.Json.JsonException exception)
        {
            var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
            throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
        }
    }
}

@ZukenHammer as far as I know my patch (that's merged) shouldn't cause this...

Can you tweak your .liquid templates on your local machine (and add C# comments) so we can see what's going on?

I installed swagger by npm and never read the .liquid templates or even seen them. I can try to build a minified sample project and share it with this error.

You can download templates from this repo inside a directory, and specify them in the nswag config : https://github.com/RicoSuter/NSwag/wiki/Templates

That way, you can change the template to try things.

Please test with the latest version of NSwag, this has been changed a bit and might solve your problem.

I tried with 13.9.3. I have "produces": ["text/plain"] in my swagger.json 2.0
I still got generated code like this:

if (status_ == 200)
                        {
                            var objectResponse_ = await ReadObjectResponseAsync<string>(response_, headers_).ConfigureAwait(false);
                            if (objectResponse_.Object == null)
                            {
                                throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
                            }
                            return objectResponse_.Object;
                        }

await ReadObjectResponseAsync<string>(....) will cause the problem.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

RawsomeGH picture RawsomeGH  路  4Comments

Maleki picture Maleki  路  4Comments

Rui90 picture Rui90  路  3Comments

akamyshanov picture akamyshanov  路  4Comments

PabloInNz picture PabloInNz  路  4Comments