RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.78k stars 1.29k forks source link

SwaggerToCSharpClientGenerator: support of JsonPatchDocument<T> (HttpPatch) #1246

Open ptr1120 opened 6 years ago

ptr1120 commented 6 years ago

Hello,

when having a HttpPatch rest api method with a Microsoft.AspNetCore.JsonPatch.JsonPatchDocument<T> as argument the csharp client generation creates a custom poco type like JsonPatchDocumentOfMyT . Since JsonPatchDocument provides a fluent way to define the patch operations like for example patchDoc.Replace(o => o.StringProperty, "B"); the generated custom poco is not very helpful.

Is there a way to exclude generic types from poco generation (such that the Microsoft.AspNetCore.JsonPatch.JsonPatchDocument<T> is kept in the client method signature)? Or, where can I preferably control the generation/mapping of such action method parameters in using the NSwag.CodeGeneration.CSharp?

Best regards, peter

RicoSuter commented 6 years ago

For that you need to

ptr1120 commented 6 years ago

Thanks for your response. I do not get further with "type to an any class (empty but still with a name)". I always get a "Could not find the JSON path of a referenced schema: Manually referenced schemas must be added to the 'Definitions' of a parent schema.". This is my current approach:

class JsonPatchDocumentMapper : ITypeMapper
{
    public async Task GenerateSchemaAsync(JsonSchema4 schema, TypeMapperContext context)
    {
        // add the inner type to definitions
        var argument = context.Type.GenericTypeArguments[0];
        var argumentSchema = await context.JsonSchemaGenerator.GenerateAsync(argument,
            context.JsonSchemaResolver);
        schema.Definitions.Add(argument.Name, argumentSchema);
        // map JsonPatchDocument to any type
        schema.Reference = new JsonSchema4() { Title = "Foo" };
    }

    public Type MappedType { get; } = typeof(JsonPatchDocument<>);
    public bool UseReference { get; }
}

Any help would be appreciated.

Best regards, peter

RicoSuter commented 6 years ago

I think you can just use the ObjectTypeMapper and map it to an empty schema of type Object.

RicoSuter commented 6 years ago

https://github.com/RSuter/NJsonSchema/wiki/Type-Mappers#object-type-mapper

rebeccapowell commented 6 years ago

Hi @ptr1120 . It seems like we are having similar issues. Did you get anywhere with this?

This is my issue: https://github.com/RSuter/NSwag/issues/1410

The generator output the schema in such a way that the operations root is expected, whereas the request simply needs to be an array of operations. Have you found a way around that?

greenmooseSE commented 4 years ago

I am also struggling with this, trying to create a patch according from the example in MS docs with an empty WebApi .netCore 3.0 project.

My action is specified as:

        [HttpPatch]
        public WeatherForecast Patch([FromBody] 
            [JsonSchemaType(typeof(JsonPatchDocument<WeatherForecast>))]
            JsonPatchDocument<WeatherForecast> patchDoc)
        {

First, if I use default values for Api and the CSharpClientGenerator, I get same described in this issue (i.e. a type named JsonPatchDocumentOfWeatherForecast intead of JsonPatchDocument<WeatherForecast>).

If I instead use below to configure swagger generation:

            //add the Swagger services
            services.AddSwaggerDocument(settings =>
            {
                settings
                    .TypeMappers
                    .Add(new ObjectTypeMapper(typeof(JsonPatchDocument<WeatherForecast>), new JsonSchema
                    {
                        Type = JsonObjectType.Object,
                    }));
            });

I get a swagger json file like:

"operationId": "WeatherForecast_Patch",
        "consumes": [
          "application/json-patch+json",
          "application/json",
          "text/json",
          "application/*+json"
        ],
        "parameters": [
          {
            "name": "patchDoc",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/JsonPatchDocumentOfWeatherForecast"
            },
            "x-nullable": false
          }
//...
"JsonPatchDocumentOfWeatherForecast": {
      "type": "object",
      "additionalProperties": {}
    }

For the CSharpClientGenerator this generates below code.

        public System.Threading.Tasks.Task<WeatherForecast> PatchAsync(System.Collections.Generic.IDictionary<string, object> patchDoc)
        {
            return PatchAsync(patchDoc, System.Threading.CancellationToken.None);
        }
//...
    public partial class JsonPatchDocumentOfWeatherForecast : System.Collections.Generic.Dictionary<string, object>
    {

    }

Excluding type JsonPatchDocumentOfWeatherForecast in the generator does not help since then I only end up with the System.Collections.Generic.IDictionary<string, object> patchDoc parameter for the patch action.

Any more hints of how to proceed? Thanks.

statler commented 4 years ago

Can we please have an example of how this should work?

marinasundstrom commented 3 years ago

To me it seems that if we want the JsonPatchDocument<T> class to work with NSwag we also need an OpenAPI extension for generics type arguments, but @RicoSuter has said that he does not want to implement any non-standard features.

https://github.com/RicoSuter/NSwag/issues/704 https://github.com/RicoSuter/NJsonSchema/issues/23

marinasundstrom commented 3 years ago

Btw. You could write a processor, like this one for Swashbuckle: https://michael-mckenna.com/swagger-with-asp-net-core-3-1-json-patch/

hexxone commented 2 years ago

For those who still have this problem, I have found a workaround, even if it requires manual adjustment for each case.

  1. Publish your DTO types (where the Generics are) as separate package

  2. Reference your DTO's in the Project where you use the Generated C# Client

  3. Manually Add all wrongly generated Generics (e.g. ResponseOfFoo,ResponseOfBar,ResponseOfFooBar) to excludedTypeNames in your .nswag-file. This can be tedious depending on your project size but in my case this part will probably not change a lot in the future... And there may be a way to grab these automatically if you really need to.

  4. Tell NSwag to use your custom Generics from your DTO's by adding the required imports to additionalNamespaceUsages in your .nswag-file

  5. after NSwag Client generation, call MakeGenericAgain. I simply do this in my .csproj like so:

    <PropertyGroup>
    <RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
    </PropertyGroup>
    
    <Target Name="NSwag" AfterTargets="PostBuildEvent" Condition=" '$(NO_RECURSE)' != 'true' ">
    <Exec WorkingDirectory="$(ProjectDir)" EnvironmentVariables="NO_RECURSE=true" Command="$(NSwagExe_Net60) run ./nswag.nswag /variables:Configuration=$(Configuration)" />
    </Target>
    
    <Target Name="MakeItGenericAgain" AfterTargets="NSwag" Condition=" '$(NO_RECURSE)' != 'true' "> 
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool run makeGenericAgain -f &quot;./Client/csharp/Client.cs&quot;" />
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool run makeGenericAgain -f &quot;./Client/controller/Client.cs&quot;" />
    <Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool run makeGenericAgain -f &quot;./Client/ts/index.ts&quot;" />
    </Target> 

And you can basically do the same thing for TypeScript.

Hope this helps - Cheers :)

ThomasVague commented 1 year ago

My problem is a little different. Instead of getting a generated type like for example JsonPatchDocumentOfWeatherForecast , I get an IEnumerable of Operation as parameter instead.

image

image