Git Product home page Git Product logo

autorest.csharp's Introduction

Default autorest --csharp generator change


⚠️ We have updated the default generator used by autorest --csharp to the new version V3, which uses @autorest/csharp package and it will have few side effects:

  • It will generate code based on the .NET SDK guidelines, which will be totally different than the code generated by V2 version.
  • The dotnet 7.0 or above is required.
  • If you still want to generate code based on V2 version, you can add --legacy flag to the command line to get the previous behavior.

C# code generator for AutoRest V3

Prerequisites

Build

  • dotnet build (at root)

Test

./eng/Generate.ps1 (at root in PowerShell Core)

This command tests your change across many swagger definitions and samples.

These arguments change the behavior:

  • -fast option skips Swagger -> YAML IL step. Much faster when only making codegen changes
  • -fast SWAGGER_NAME (where SWAGGER_NAME is replaced with the name of the swagger) to run only one case

You could run this command on local dev machine or ask github workflow run it against your branch. The steps to run workflow is:

  1. Go to github actions from your fork repo.
  2. Click workflow "Regenerate all code".
  3. Choose the branch you are working on, and click "Run workflow".
  4. After the workflow success, there will be a new commit to your branch

Regen workflow example image

dotnet test (at root)

Testing Details

autorest.testserver

autorest.testserver provides a platform for automated testing of the code generators.

It packages a bunch of test swagger files, along with a “mock” nodejs server.

The swagger files are compiled, and then run, which pings the mock server (to verify behavior). This tests both the Modeler 4 and language specific codegen.

This document contains some additional technical details.

cadl-ranch

cadl-ranch is similar to autorest.testserver, which provides a platform for automated testing too. The difference is the testing target of cadl-ranch is typespec generated SDK.

Sometimes when we are adding new tests in cadl-ranch, we might want to make sure the tests and corresponding mock APIs work well by applying these newly added/modified tests to autorest.csharp. To realize this, run .\eng\ApplyCadlRanch.ps1 {cadl-ranch path} or just run .\eng\ApplyCadlRanch.ps1 if cadl-ranch folder is at the same folder of autorest.csharp.

PR Merge Process

When the automatic PR is created for azure-sdk-for-net if there are any issues found all other changes to autorest.csharp are blocked until those issues are resolved. This means we should be certain that the changes we are making create the expected result in azure-sdk-for-net prior to merging our PR.

Preview changes in azure-sdk-for-net

This step could be done manually or automatically with below steps:

Go to Autorest Regen Preview pipeline, and click "Run Pipeline". In "Branch/tag", type in your PR ref, i.e. refs/pull/3215/head, or refs/pull/3215/merge. Click "Run". After the build finishes, there will be a PR starting with "Autorest Regen Preview" in azure-sdk-for-net.

Make sure changes in azure-sdk-for-net is expected before merging your autorest.csharp PR

Once this is done we want to ensure this regen PR is discoverable as well as the key stakeholders who would need to sign off mark this PR as either approved or request changes.

  • In the autorest.csharp PR description please add a line at the very top indicating which azure-sdk-for-net PR is the regeneration for this PR. An example would look like this PR example image
  • Next have any stakeholders that would need to review do so in this PR and mark it as approved or request changes.
  • Once all stakeholders review and approve of the PR we are ready for the next step of merging the autorest.csharp PR.

Merge azure-sdk-for-net PR

When the autorest.csharp PR gets merged there will be an automatic azure-sdk-for-net PR created under the azure-sdk fork but this PR will not contain any of the Export-API changes nor will it contain any custom changes to fix test cases / snippet references. It is therefore recommended that we merge this PR into our custom PR we made to validate there are no additional changes we didn't expect.

  • Undo the changes to the nuget config and Packages.Data.props if you have not already done so.
  • Add a note in the autogenerated PR that conflicts are being resolved in PR you created above so that people know where to follow the conversation. resolved conflicts example image
  • If you haven't done so add azure-sdk as an upstream and pull the branch that the automatic PR uses auto-update-autorest into your local azure-sdk-for-net branch.
    • git remote add azure-sdk https://github.com/azure-sdk/azure-sdk-for-net
    • git fetch azure-sdk
    • git pull azure-sdk auto-update-autorest
  • This should have zero conflicts and if it does something wasn't done correctly in the PR that was created to demonstrate the regeneration.
    • If there are conflicts our only option here is to resolve them, but will most require another review from the stakeholders most likely.
  • If there were no conflicts or after they are resolved push the changes to your branch and you can then merge the PR that was previous approved or re-approved in the case of conflicts.
  • The autogenerated PR can now simply be closed

Use in azure-sdk-for-net repo

Run dotnet build /t:GenerateCode in the directory that contains your .csproj file.

This executes these targets.

Refer also to azure-sdk-for-net/CONTRIBUTING.md for more details.

PR Integration with Azure SDK Repository

Merging a change in autorest.csharp will open a PR against azure-sdk-for-net with every project’s generated code staged for review.

Along with this, it also bumps the generator to the new version.

This bump is done here.

The generator is shipped as a NuGet package.

This way, every binding stays in lockstep with the current generator

Use outside of the azure-sdk-for-net repo

Use below command to generate code:

autorest --use:@autorest/[email protected] --input-file:FILENAME  --clear-output-folder:true --output-folder:DIRECTORY

Note:

  1. Use @autorest/csharp version v3.0.0-beta.20210210.4 or later.
  2. If you don't want to override the .csproj after the first generation, you can pass --skip-csproj flag with the autorest command.

For more details please refer these docs to generate code from your OpenAPI definition using AutoRest.

Debugging

The generator consists of two parts, first part consumes the input (swagger or TypeSpec) and produce an intermediate result which the second part consumes them to produce the generated code.

When the input is swagger files, the generator leverages the processes and pipelines from autorest, and its first part is the autorest plugin autorest.modelerfour which produces the file CodeModel.yaml and Configuration.json.

When the input is TypeSpec files, the first part is the autorest plugin @azure-tools/typespec-csharp which produces the file tspCodeModel.json and Configuration.json.

The second part of the generator is written in C# and consumes Configuration.json and CodeModel.yaml/tspCodeModel.json to produce the generated code.

This section shows how to debug the second part of the generator which takes Configuration.json and CodeModel.yaml/tspCodeModel.json as input.

Debugging the projects inside the autorest.csharp repo

The autorest.csharp repo contains quite a few sample projects and test projects to verify various of features of the generator. To debug these projects, you could just simply open the solution file AutoRest.CSharp.sln in Visual Studio and Visual Studio already has a corresponding debugging profile for each project which you could start debugging with.

Debugging the projects outside the autorest.csharp repo

To debug a project outside the autorest.csharp repo, in addition to the prerequisites you must do to let your project be able to use the generator, you also need to do the following depending on the input type of your project.

If your project is using the swagger files as input, and you use a markdown file (like readme.md or autorest.md) as the configuration of your autorest command, you need to add the following to your configuration md file

csharpgen:
  attach: true

This will attach the debugger to either an existing Visual Studio instance or opening a new Visual Studio instance to debug the generator.

If your project is using the swagger files as input, and you use the autorest command line to pass in the configurations, you need to add the following options to your autorest command:

--csharpgen.attach=true

to attach a debugger to the plugin process.

If your project is using the TypeSpec files as input, you need to add the following options to your command line

--option @azure-tools/typespec-csharp.debug=true

to attach a debugger to the plugin process.

If you need to use a locally built generator, you could use the following options to specify the path to the generator dll file:

--option @azure-tools/typespec-csharp.csharpGeneratorPath=/absolute/path/to/AutoRest.CSharp.dll

Usually you should find the AutoRest.CSharp.dll file here: [Root of autorest.csharp repo]/artifacts/bin/AutoRest.CSharp/Debug/net6.0/AutoRest.CSharp.dll.

Debugging the projects in the azure-sdk-for-net repo

There is a target defined in azure-sdk-for-net repo to generate the library code:

dotnet build /t:GenerateCode

In azure-sdk-for-net repo, to debug a project that uses swagger files as input, you need to add the following to your autorest.md file

csharpgen:
  attach: true

and then run dotnet build /t:GenerateCode and it will attach a debugger to the plugin process.

To debug a TypeSpec project inside azure-sdk-for-net repo, you could run

dotnet build /t:GenerateCode /p:typespecAdditionalOptions="debug=true"

to attach a debugger to the plugin process.

If you are trying to use a locally built generator dll and debug it, you should run

dotnet build /t:GenerateCode /p:typespecAdditionalOptions="csharpGeneratorPath=/absolute/path/to/AutoRest.CSharp.dll%3Bdebug=true"

where you must use %3B as escape of the semicolon to append multiple options.

Debugging transforms

Many customizations can be done as a transform in readme.md, however getting it right can be tricky.

One useful trick is to use $lib.log to output the state of the object either before of after transform:

  transform: >
    $['x-accessibility'] = "internal";
    $lib.log($);

Customizing the generated code

Make a model internal

Define a class with the same namespace and name as generated model and use the desired accessibility.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    internal partial class Model { }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
-    public partial class Model { }
+    internal partial class Model { }
}

Rename a model class

Define a class with a desired name and mark it with [CodeGenModel("OriginalName")]

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (NewModelClassName.cs)

namespace Azure.Service.Models
{
    [CodeGenModel("Model")]
    public partial class NewModelClassName { }
}

Generated code after (Generated/Models/NewModelClassName.cs):

namespace Azure.Service.Models
{
-    public partial class Model { }
+    public partial class NewModelClassName { }
}

Change a model or client namespace

Define a class with a desired namespace and mark it with [CodeGenModel("OriginalName")].

The same works for a client, if marked with [CodeGenClient("ClientName")].

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (NewModelClassName.cs)

namespace Azure.Service
{
    [CodeGenModel("Model")]
    public partial class Model { }
}

Generated code after (Generated/Models/NewModelClassName.cs):

- namespace Azure.Service.Models
+ namespace Azure.Service
{
    public partial class Model { }
}

Make model property internal

Define a class with a property matching a generated property name but with desired accessibility.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model
    {
        internal string Property { get; } 
    }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
    }
}

Rename a model property

Define a partial class with a new property name and mark it with [CodeGenMember("OriginalName")] attribute.

NOTE: you can also change a property to a field using this mapping.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model
    {
        [CodeGenMember("Property")]
        public string RenamedProperty { get; } 
    }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
+        // All original Property usages would reference a RenamedProperty
    }
}

Change a model property type

⚠️

NOTE: This is supported for a narrow set of cases where the underlying serialized type doesn't change

Scenarios that would work:

  1. String <-> TimeSpan (both represented as string in JSON)
  2. Float <-> Int (both are numbers)
  3. String <-> Enums (both strings)
  4. String -> Uri

Won't work:

  1. String <-> Bool (different json type)
  2. Changing model kinds

If you think you have a valid re-mapping scenario that's not supported file an issue.

⚠️

Define a property with different type than the generated one.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model
    {
        public DateTime Property { get; }
    }
}

Generated code after (Generated/Models/Model.Serializer.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
+        // Serialization code now reads and writes DateTime value instead of string  
    }
}

Preserve raw Json value of a property

Use the Change a model property type approach to change property type to JsonElement.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        public string Property { get; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model
    {
        public JsonElement Property { get; }
    }
}

Generated code after (Generated/Models/Model.Serializer.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        public string Property { get; }
+        // Serialization code now reads and writes JsonElement value instead of string  
    }
}

Changing member doc comment

Redefine a member in partial class with a new doc comment.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        /// Subpar doc comment
        public string Property { get; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model
    {
        /// Great doc comment
        public string Property { get; }
    }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        /// Subpar doc comment
-        public string Property { get; }  
    }
}

Customize serialization/deserialization methods

Changing how a property serializes or deserializes is done by CodeGenSerialization attribute. This attribute can be applied to a class or struct with a property name to change the serialization/deserialization method of the property.

Change the serialized name of a property

If you want to change the property name that serializes into the JSON or deserializes from the JSON, you could define your own partial class with the CodeGenMemberSerialization attribute.

For instance, we have a model class Cat with property Name and Color:

Generated code before:

// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

Add customized model:

// Cat.cs
namespace Azure.Service.Models
{
    [CodeGenSerialization(nameof(Name), "catName")] // add the property name, and the new serialized name
    public partial class Cat
    {
    }
}

Generated code after:

// Generated/Models/Cat.cs - no change

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
-           writer.WritePropertyName("name"u8);
+           writer.WritePropertyName("catName"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
-               if (property.NameEquals("name"u8))
+               if (property.NameEquals("catName"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Change the hierarchy of a property in the serialized JSON

If you want to change the layer of the property in the json, you can add all the elements in the json path of your property to the attribute using an array, the generator will generate the property into the JSON in the correct hierarchy.

NOTE: Introducing extra layers in serialized JSON only works for MPG and HLC models, does not work for DPG models.

For instance, we want to move Name property in the model Cat to make it serialized under property properties and rename to catName.

Generated code before:

// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Add customized model:

// Cat.cs
namespace Azure.Service.Models
{
    [CodeGenSerialization(nameof(Name), new string[] { "properties", "catName" })]
    public partial class Cat
    {
    }
}

Generated code after:

// Generated/Models/Cat.cs - no change

// Generated/Models/Model.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
-           writer.WritePropertyName("name"u8);
+           writer.WritePropertyName("properties"u8);
+           writer.WriteStartObject();
+           writer.WritePropertyName("catName"u8);
            writer.WriteStringValue(Name);
+           writer.WriteEndObject();
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            foreach (var property in element.EnumerateObject())
            {
-               if (property.NameEquals("name"u8))
+               if (property.NameEquals("properties"u8))
+               {
+                   foreach (var property in element.EnumerateObject())
+                   {
+                       if (property.NameEquals("catName"u8))
                        {
                            meow = property.Value.GetString();
                            continue;
                        }
+                   }
+                   continue;
+               }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Change the implementation of serialization/deserialization method of a property

If you want to change the implementation of serialization/deserialization method of a property, you could define your own hook methods and assign them to the CodeGenSerialization attribute.

The custom serialization method for this property is assigned by the SerializationValueHook property of the CodeGenSerialization attribute, and the custom deserialization method for this property is assigned by the DeserializationValueHook property of the CodeGenSerialization attribute.

The SerializationValueHook and DeserializationValueHook here are hook method names, and these methods should have the signature as below:

// serialization hook and serialization value hook
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SerializationMethodHook(Utf8JsonWriter writer)
{
    // write your own serialization logic here
}

// deserialization hook for required property
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void DeserializeSizeProperty(JsonProperty property, ref TypeOfTheProperty name)
{
    // write your own deserialization logic here
}
// deserialization hook for optional property
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void DeserializeSizeProperty(JsonProperty property, ref Optional<TypeOfTheProperty> name)
{
    // write your own deserialization logic here
}

Please use the nameof expression to avoid typo in the attribute. Also you could leave both the serialization value hook unassigned if you do not want to change the serialization logic, similar you could leave deserialization hook unassigned if you do not want to change the deserialization logic.

The [MethodImpl(MethodImplOptions.AggressiveInlining)] attribute is recommended for your hook methods to get optimized performance.

Please note that the generator will not check the signature of the hook methods you assigned to the attribute, therefore if the signature is not compatible, the generated library might not compile.

For instance, we have a model class Cat with property Name and Color, and we would like to change the way how Name property is serialized and deserialized.

Generated code before:

// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Add customized model:

// Cat.cs
namespace Azure.Service.Models
{
    [CodeGenSerialization(nameof(Name), SerializationValueHook = nameof(SerializeNameValue), DeserializationValue = nameof(DeserializeNameValue))]
    public partial class Cat
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SerializeNameValue(Utf8JsonWriter writer)
        {
            // this is the logic we would like to have for the value serialization
            writer.WriteStringValue(Name.ToUpper());
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void DeserializeNameValue(JsonProperty property, ref string name) // the type here is string since name is required
        {
            // this is the logic we would like to have for the value deserialization
            name = property.Value.GetString().ToLower();
        }
    }
}

Generated code after:

// Generated/Models/Cat.cs - no change

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
-           writer.WriteStringValue(Name);
+           SerializeNameValue(writer);
            if (Optional.IsDefined(Color))
            {
                writer.WritePropertyName("color"u8);
                writer.WriteStringValue(Color);
            }
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
-                   meow = property.Value.GetString();
+                   DeserializeNameValue(property, ref name);
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color, size);
        }
    }
}

Add a new property to the model with serialization/deserialization

If you want to add a new property to the model and also add the property into the serialization/deserialization methods, you could also use the CodeGenSerialization attribute to change its default serialized name, and serialization/deserialization methods.

Generated code before:

// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit the ctors for brevity */
        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            writer.WritePropertyName("color"u8);
            writer.WriteStringValue(Color);
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    name = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    color = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(name, color);
        }
    }
}

Add customized model:

[CodeGenSerialization(nameof(Size), "size")]
public partial class Cat
{
    public int? Size { get; set; }
}

Generated code after:

// Generated/Models/Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        /* omit other ctors for brevity */
-       internal Cat(string name, string color)
+       internal Cat(string name, string color, int? size)
        {
            Name = name;
            Color = color;
+           Size = size;
        }

        public string Name { get; set; }
        public string Color { get; set; }
    }
}

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat : IUtf8JsonSerializable
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            if (Optional.IsDefined(Color))
            {
                writer.WritePropertyName("color"u8);
                writer.WriteStringValue(Color);
            }
+           if (Optional.IsDefined(Size))
+           {
+               writer.WritePropertyName("size"u8);
+               writer.WriteNumberValue(Size);
+           }
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            if (element.ValueKind == JsonValueKind.Null)
            {
                return null;
            }
            string name = default;
            Optional<string> color = default;
+           Optional<int> size = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"u8))
                {
                    meow = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("color"u8))
                {
                    meow = property.Value.GetString();
                    continue;
                }
+               if (property.NameEquals("size"u8))
+               {
+                   size = property.Value.GetInt32();
+                   continue;
+               }
            }
            return new Cat(name);
        }
    }
}

You could also add the CodeGenSerialization attribute to the property to have your own serialization/deserialization logic of the new property. You might have to do this if the type of your new property is an object type or any type that our generator does not natively support.

NOTE: Adding property to serialization/deserialization methods currently only works for DPG.

Replace the entire serialization/deserialization method

If you want to replace the entire serialization/deserialization method, please use the Replace any generated member approach to replace serialization/deserialization method with a custom implementation.

Generated code before:

// Generated/Models/Cat.Serialization.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            if (Optional.IsDefined(Color))
            {
                writer.WritePropertyName("color"u8);
                writer.WriteStringValue(Color);
            }
            writer.WriteEndObject();
        }

        internal static Cat DeserializeCat(JsonElement element)
        {
            string color = default;
            string name = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("color"u8))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        continue;
                    }
                    color = property.Value.GetString();
                    continue;
                }
                if (property.NameEquals("name"u8))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        continue;
                    }
                    name = property.Value.GetString();
                    continue;
                }
            }
            return new Cat(id, name);
        }
    }
}

Add customized model:

// Cat.cs
namespace Azure.Service.Models
{
    public partial class Cat
    {
        // currently we have to use a full name to ensure this could be replaced
        void global:Azure.Core.IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("name"u8);
            writer.WriteStringValue(Name);
            // WORKAROUND: server never needs color, remove it in the customization code
            writer.WriteEndObject();
        }
            
        internal static Cat DeserializeCat(JsonElement element)
        {
            string color = default;
            string name = default;
            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("name"))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        continue;
                    }
                    name = property.Value.GetString();
                    continue;
                }
            }
            // WORKAROUND: server never sends color, default to black
            color = "black";
            return new Cat(name, color);
        }
    }
}

Generated code after:

Generated code won't contain the IUtf8JsonSerializable.Write or DeserializeCat method and the custom one would be used for deserialization.

Renaming an enum

Redefine an enum with a new name and all the members mark it with [CodeGenModel("OriginEnumName")].

NOTE: because enums can't be partial all values have to be copied

Generated code before (Generated/Models/Colors.cs):

namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green,
        Blue
    }
}

Add customized model (WallColors.cs)

namespace Azure.Service.Models
{
    [CodeGenModel("Colors")]
    public enum WallColors
    {
        Red,
        Green,
        Blue
    }
}

Generated code after (Generated/Models/Model.cs):

-namespace Azure.Service.Models
-{
-    public enum Colors
-    {
-        Red,
-        Green,
-        Blue
-    }
-}
+// Serialization code uses the new WallColors type name

Renaming an enum member

Redefine an enum with the same name and all the members, mark renamed member with [CodeGenMember("OriginEnumMemberName")].

NOTE: because enums can't be partial all values have to be copied but only the ones being renamed should be marked with an attributes

Generated code before (Generated/Models/Colors.cs):

namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green,
        Blue
    }
}

Add customized model (Colors.cs)

namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green,
        [CodeGenMember("Blue")]
        SkyBlue
    }
}

Generated code after (Generated/Models/Model.cs):

-namespace Azure.Service.Models
-{
-    public enum Colors
-    {
-        Red,
-        Green,
-        Blue
-    }
-}
+// Serialization code uses the new SkyBlue member name

Changing an enum to an extensible enum

Redefine an enum into an extensible enum by creating an empty struct with the same name as original enum.

Generated code before (Generated/Models/Colors.cs):

namespace Azure.Service.Models
{
    public enum Colors
    {
        Red,
        Green
    }
}

Add customized model (Colors.cs)

namespace Azure.Service.Models
{
    public partial struct Colors
    {
    }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
-    public enum Colors
-    {
-        Red,
-        Green
-    }
+    public readonly partial struct Colors : IEquatable<Colors>
+    {
+        private readonly string _value;

+        public Colors(string value)
+        {
+            _value = value ?? throw new ArgumentNullException(nameof(value));
+        }

+        private const string Red = "red";
+        private const string Green = "green";

+        public static Colors Red { get; } = new Colors(Red);
+        public static Colors Green { get; } = new Colors(Green);
+        public static bool operator ==(Colors left, Colors right) => left.Equals(right);
         ...
}

Make a client internal

Define a class with the same namespace and name as generated client and use the desired accessibility.

Generated code before (Generated/Operations/ServiceClient.cs):

namespace Azure.Service.Operations
{
    public partial class ServiceClient { }
}

Add customized model (Model.cs)

namespace Azure.Service.Operations
{
    internal partial class ServiceClient { }
}

Generated code after (Generated/Operations/ServiceClient.cs):

namespace Azure.Service.Operations
{
-    public partial class ServiceClient { }
+    internal partial class ServiceClient { }
}

Rename a client

Define a partial client class with a new name and mark it with [CodeGenClient("OriginalName")]

Generated code before (Generated/Operations/ServiceClient.cs):

namespace Azure.Service.Operations
{
    public partial class ServiceClient {}
}

Add customized model (Model.cs)

namespace Azure.Service.Operations
{
    [CodeGenClient("ServiceClient")]
    public partial class TableClient { }
}

Generated code after (Generated/Operations/ServiceClient.cs):

namespace Azure.Service.Operations
{
-    public partial class ServiceClient { }
+    public partial class TableClient { }
}

Replace any generated member

Works for model and client properties, methods, constructors etc.

Define a partial class with member with the same name and for methods same parameters.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        public Model()
        {  
            Property = "a";
        }

        public string Property { get; set; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model
    {
        internal Model()
        {
            Property = "b";
        }
    }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        public Model()
-        {  
-            Property = "a";
-        }
    }
}

Remove any generated member

Works for model and client properties, methods, constructors etc.

Define a partial class with [CodeGenSuppress("NameOfMember", typeof(Parameter1Type), typeof(Parameter2Type))] attribute.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
        public Model()
        {  
            Property = "a";
        }

        public Model(string property)
        {  
            Property = property;
        }

        public string Property { get; set; }
    }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    [CodeGenSuppress("Model", typeof(string))]
    public partial class Model
    {
    }
}

Generated code after (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model
    {
-        public Model(string property)
-        {  
-            Property = property;
-        }
    }
}

Change model namespace or accessibility in bulk

Generated code before:

namespace Azure.Service.Models
{
    public partial class Model1 {}
    public partial class Model2 {}
    public partial class Model3 {}
    public partial class Model4 {}
}

Add autorest.md transformation

directive:
  from: swagger-document
  where: $.definitions.*
  transform: >
    $["x-namespace"] = "Azure.Search.Documents.Indexes.Models"
    $["x-accessibility"] = "internal"

Generated code after:

-namespace Azure.Service.Models
+namespace Azure.Search.Documents.Indexes.Models
{
-    public partial class Model1 {}
+    internal partial class Model1 {}
-    public partial class Model2 {}
+    internal partial class Model2 {}
-    public partial class Model3 {}
+    internal partial class Model3 {}
-    public partial class Model4 {}
+    internal partial class Model4 {}
}

Change operation accessibility in bulk

Generated code before (Generated/Client.cs):

public virtual Response Operation(string body = null, CancellationToken cancellationToken = default)
public virtual async Task<Response> OperationAsync(string body = null, CancellationToken cancellationToken = default)

Add autorest.md transformation

directive:
- from: swagger-document
  where: $..[?(@.operationId=='Operation')]
  transform: >
    $["x-accessibility"] = "internal";

Generated code after (Generated/Client.cs):

internal virtual Response Operation(string body = null, CancellationToken cancellationToken = default)
internal virtual async Task<Response> OperationAsync(string body = null, CancellationToken cancellationToken = default)

Exclude models from namespace

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model { }
}

Add model-namespace in autorest.md

model-namespace: false
input-file: "swagger-document"

Generated code after (Generated/Models/Model.cs):

- namespace Azure.Service.Models
+ namespace Azure.Service
{
    public partial class Model { }
}

Extending a model with additional constructors

As with most customization, you can define a partial class for Models and extend them with methods and constructors.

Generated code before (Generated/Models/Model.cs):

namespace Azure.Service.Models
{
    public partial class Model { }
}

Add customized model (Model.cs)

namespace Azure.Service.Models
{
    public partial class Model {
        public Model(int x)
        {
        }
    }
}
Repository-specific pipeline configuration
# autorest-core version
version: 3.9.7
save-inputs: true
use: $(this-folder)/artifacts/bin/AutoRest.CSharp/Debug/net7.0/
clear-output-folder: true
public-clients: true
skip-csproj-packagereference: true

Management plane concepts and configurations

See the documentation here

autorest.csharp's People

Contributors

alexandersher avatar allenjzhang avatar annelo-msft avatar archerzz avatar arcturuszhang avatar arthurma1978 avatar azure-sdk avatar chamons avatar changlong-liu avatar christothes avatar chunyu3 avatar ellismg avatar fearthecowboy avatar fengzhou-msft avatar hallipr avatar heaths avatar isra-fel avatar joshlove-msft avatar live1206 avatar m-nash avatar mcgallan avatar mikeharder avatar miyanni avatar olydis avatar pakrym avatar pshao25 avatar rodgefu avatar shivangireja avatar weshaggard avatar yao725 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

autorest.csharp's Issues

[C#] AutoRest double-encodes special characters in XML comments, but only sometimes

When characters like > are used in Swagger descriptions, AutoRest will double-encode them, but only on property summaries, not parameter summaries.

For example, here is a description from Swagger:

"description": "A list of stemming rules in the following format: \"word => stem\", for example: \"ran => run\""

And here is the corresponding C# code:

     public partial class StemmerOverrideTokenFilter : TokenFilter
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the StemmerOverrideTokenFilter class.
         /// </summary>
         /// <param name="rules">A list of stemming rules in the following
         /// format: "word =&gt; stem", for example: "ran =&gt; run"</param>
         public StemmerOverrideTokenFilter(string name, IList<string> rules)
             : base(name)
         {
            ...
         }

         /// <summary>
         /// Gets or sets a list of stemming rules in the following format:
         /// "word =&amp;gt; stem", for example: "ran =&amp;gt; run"
         /// </summary>
         [JsonProperty(PropertyName = "rules")]
         public IList<string> Rules { get; set; }
     }

Note the double-encoding on the Rules property.

This occurs with the 20170110 nightly build of AutoRest.

Generated code should have GeneratedCodeAttribute on the generated classes

Using GeneratedCodeAttribute is the accepted way to allow C# tools (aka Visual Studio) know to handle that code differently. Currently, there is only the <auto-generated/> comment at the top of the file. But, this comment isn't recognized by most tooling.

Please add GeneratedCodeAttribute with the appropriate parameters (a string for the fully qualified generator class and a string for the version). For additional info, here is an old blog post, but the details should still apply: https://blogs.msdn.microsoft.com/codeanalysis/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute/

Edit: This other attribute, DebuggerNonUserCodeAttribute is also of note. But, this probably doesn't make sense here.

C# - Formdata x-mimetype extension for file uploads.

Hi,

The Swagger documentation (FAQ section) on file uploads states that we can use vendor extensions to specify for example the mime type of a form data's file (in my case I'd like to specify that I want to upload an image file).

image

I am indeed able to create my swagger.json containing the x-mimeType extension like this:

image
(I did try both formats: x-mimeType or x-mimetype)

However, the generated code still adds the content type of the file as application/octet-stream where I would've liked to see the image/* content type:

// Serialize Request
            string _requestContent = null;
            MultipartFormDataContent _multiPartContent = new MultipartFormDataContent();
            if (file != null)
            {
                StreamContent _file = new StreamContent(file);
                _file.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                FileStream _fileAsFileStream = file as FileStream;
                if (_fileAsFileStream != null)
                {
                    ContentDispositionHeaderValue _contentDispositionHeaderValue = new ContentDispositionHeaderValue("form-data");
                    _contentDispositionHeaderValue.Name = "file";
                    _contentDispositionHeaderValue.FileName = _fileAsFileStream.Name;
                    _file.Headers.ContentDisposition = _contentDispositionHeaderValue;
                }
                _multiPartContent.Add(_file, "file");
            }

It would be useful to be able to control this type using the mimeType extension as shown above.

Thanks and keep up the great work 👍

[C#] Should AutoRest emit link text for externalDocs url?

Currently AutoRest emits links like this in C# code:

/// <summary>
/// Defines the SKU of an Azure Search Service, which determines price tier
/// and capacity limits.
/// <see href="https://azure.microsoft.com/documentation/articles/search-sku-tier/" />
/// </summary>

The URL comes from the url property of externalDocs in the Swagger spec. However, when you add a description, no link text is added.

If a description of "Search SKUs" were added to the Swagger, the output should look like this instead:

/// <summary>
/// Defines the SKU of an Azure Search Service, which determines price tier
/// and capacity limits.
/// <see href="https://azure.microsoft.com/documentation/articles/search-sku-tier/">Search SKUs</see>
/// </summary>

However, there is a potential usability issue with including such link text: It will show up in Intellisense tooltips, which are best kept short.

Is there a better way to emit <see> links? Maybe put them in <remarks>? I'd like to hear others' thoughts on this.

Using Generated Client in Powershell context throws Method not found: 'Void Microsoft.Rest.ServiceClient...

Hoping for someone to give some insights.
I have tried with net471, net461 targets.

The client works and tests fine with XUNIT and compiles fine into normal console applications.
However when running within a Powershell Cmdlet it throws that error.

Powershell Info

PS C:\dev\dotnet\GoCloud.Net.Client> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.16299.431
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.16299.431
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1


PS C:\dev\dotnet\GoCloud.Net.Client> $PSVersionTable.PSCompatibleVersions

Major  Minor  Build  Revision
-----  -----  -----  --------
1      0      -1     -1
2      0      -1     -1
3      0      -1     -1
4      0      -1     -1
5      0      -1     -1
5      1      16299  431

Execution StackTrace

I included logging of the assembly, I am using the latest.

List of assemblies loaded in current appdomain:
file:///C:/dev/dotnet/GoCloud.Net.Client/GoCloud.Client.PowerShell/bin/Debug/net471/Microsoft.Rest.ClientRuntime.dll
v4.0.30319
2.3.11.0
Connect-GoCloudAccount : Method not found: 'Void Microsoft.Rest.ServiceClient`1.set_HttpClient(System.Net.Http.HttpClient)'.
At C:\dev\dotnet\GoCloud.Net.Client\GoCloud.Client.PowerShell\testModule.ps1:8 char:1
+ Connect-GoCloudAccount -ApplicationId "fe11e0f7-bdcf-470e-a404-dd6730 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Connect-GoCloudAccount], MissingMethodException
    + FullyQualifiedErrorId : System.MissingMethodException,GoCloud.Client.PowerShell.Commands.Common.ConnectGoCloudAccountCommand

Post File requires FileStream

Hi,

We have an API that takes a file from the customer, it also takes a file name etc. When we generate the code using Autorest it takes a Stream in the method, but tries to cast it to a FileStream.

If the developer passes a MemoryStream, then the method fails at:

FileStream _fileAsFileStream = file as FileStream;
                if (_fileAsFileStream != null)

Obviously this is because the MemoryStream might be from some in memory/db byte array or something.

What's the best way around this please? Looking for advice or suggestions.

Thanks in advance,
Ben

[C#] Method Naming

Hi,

I'm sorry if this is a redundant question but I was looking for hours a solution to this.

C# client generates well but I get method extremly long method names, example :

GetUserCompanyRolesByidorderByParamsparameterspageparameterspageSizeWithHttpMessagesAsync

Any good way to tweak the conventions ? This looks better to me :

GetUserCompanyRolesAsync

C#: Extensible enum serialization has NullReferenceException when the enum is an optional query param

I have an api which has an optional query param CapabilityGroup? include = default(CapabilityGroup?). When serializing this value, the generated code checks if (include != null) which is always false because extensible enums are structs that are never null.

        public async Task<AzureOperationResponse<LocationCapabilities>> ListByLocationWithHttpMessagesAsync(string locationName, CapabilityGroup? include = default(CapabilityGroup?), Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
        {

            // ... 

            if (include != null)
            {
                _queryParameters.Add(string.Format("include={0}", System.Uri.EscapeDataString(Rest.Serialization.SafeJsonConvert.SerializeObject(include, Client.SerializationSettings).Trim('"'))));
            }

When formatting, the extensible enum ToString() is implicitly called.

        /// <summary>
        /// Returns string representation for CapabilityGroup
        /// </summary>
        public override string ToString()
        {
            return UnderlyingValue.ToString();
        }

This throws null ref exception because UnderlyingValue is null.

type string, format uuid, min/maxLength for c# causing CS1061 compiler errors

This parameter:
{
"in": "query",
"name": "serviceCatalogId",
"description": "The identifier of the service catalog entity.",
"required": true,
"type": "string",
"format": "uuid",
"maxLength": 36,
"minLength": 36
},
where the method parameter is generated as:
...HttpMessagesAsync(System.Guid serviceCatalogId, ...

results in this validation:
if (serviceCatalogId.Length > 36)
{
throw new ValidationException(ValidationRules.MaxLength, "serviceCatalogId", 36);
}
if (serviceCatalogId.Length < 36)
{
throw new ValidationException(ValidationRules.MinLength, "serviceCatalogId", 36);
}

error CS1061: 'Guid' does not contain a definition for 'Length' and no extension method 'Length' accepting a first argument of type 'Guid' could be found (are you missing a using directive or an assembly reference?)

Please advise

`[JsonExtensionData]` only works with Dictionary<string, JToken>

Newtonsoft.Json.JsonException occurred
HResult=0x80131500
Message=Invalid extension data attribute on 'MyType'. Member 'AdditionalProperties' type must implement IDictionary<string, JToken>.
Source=Newtonsoft.Json
StackTrace:
at Newtonsoft.Json.Serialization.DefaultContractResolver.<>c.b__34_1(MemberInfo m)

The actual signature of that property is IDictionary<string,string> (which presumably autorest got out of the json schema of the swagger endpoint), but I don't see any evidence that JSON.Net supports anything other than JToken as the value type of the Dictionary.

I was using autorest 1.2.2 and NewtonSoft.Json 10.0.3

Enums as integers

I use Swashbuckle.AspNetCore to generate swagger definition.

I'm using C# client generator.

Enums work when my enums are strings but they don't when my enums are ints. Autorest doesn't generate enums for integers.

"type": {
    "format": "int32",
    "enum": [0,1],
    "type": "integer",
    "x-ms-enum": {
        "name": "Gender",
        "modelAsString": false
    }
},

It doesn't work even if I add values to x-ms-enum

It works when my enum looks like "enum": ["Male","Female"] but I wanted to just keep it as an integer.

Model names generated with namespace

Hi guys
I realized that autorest generates model names concatenating "namespace" based on swagger file...
For example:

My model:
TravelCTMOBT.Api.Models.v10.SeatMap.SeatMapSearchRequest

Model generated (autorest):
TravelCTMOBTApiModelsv10SeatMapSeatMapSearchRequest

I'd like to change it to a friendly name, is it possible?
It would be better if I organize my models like:

namespace Models.TravelCTMOBT.Api.Models.v10.SeatMap
{ public partial class SeatMapSearchRequest

Swagger File: https://lightning-api-dev.azurewebsites.net/swagger/docs/V10

I'm searching for a solution a couple of days, but I couldn't find anything...
Could you help me, please?

Thank you very much

Allow to add mandatory parameters for C# Client constructors

Background: Our service can run on many domain names or IP.

We are using this configuration:

x-ms-parameterized-host: {hostname}

Apparently, user must set hostname before using the client, otherwise they will get some exceptions. (Invalid URI)

var client = new XxxClient();
client.Hostname = "10.0.0.4";

But to give better experiences, we would like to make endpoint as the first parameter of constructor, so that Client user won't mistakenly forgot to change the default placeholder ("{hostname}" in the example).

var client = new XxxClient("10.0.0.4");

Maybe we can support a new x-ms-parameter-location, so the parameter will be in constructor
"x-ms-parameter-location": "ctor"

Error: Missing method in Microsoft.Rest.HttpOperationResponse when using C# generate client

I have been using autorest for some time in a classic Desktop .NET 4.6.1 webapp and in an Azure cloud service worker role with no issues, generating a C# proxy with Autorest to access an internal API based on swagger. Lately we've been migrating some of out internal libraries to .NETSTANDARD2.0 so I upgraded the host projects to use them (still targeting Net461 though) while upgrade a few other nuget dependencies as well. This change seems to have broken the runtime behaviour of the proxy. The project still builds fine but one method seem to be missing at runtime in Microsoft.Rest.ClientRuntime

[MissingMethodException: Méthode introuvable : 'System.Net.Http.HttpResponseMessage Microsoft.Rest.HttpOperationResponse.get_Response()'.] _XXX.Areas.XXX_.Controllers.<Details>d__1.MoveNext() +0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.Start(TStateMachine& stateMachine) +123
XXX.Areas.XXX.Controllers.XXXController.Details(String nodeKey) +251
lambda_method(Closure , ControllerBase , Object[] ) +139
System.Web.Mvc.Async.TaskAsyncActionDescriptor.BeginExecute(ControllerContext controllerContext, IDictionary2 parameters, AsyncCallback callback, Object state) +708 System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeAsynchronousActionMethod>b__35(AsyncCallback asyncCallback, Object asyncState) +46 System.Web.Mvc.Async.WrappedAsyncResultBase1.Begin(AsyncCallback callback, Object state, Int32 timeout) +169
System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAsynchronousActionMethod(ControllerContext controllerContext, AsyncActionDescriptor actionDescriptor, IDictionary2 parameters, AsyncCallback callback, Object state) +269 System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +110 System.Web.Mvc.Async.AsyncInvocationWithFilters.InvokeActionMethodFilterAsynchronouslyRecursive(Int32 filterIndex) +1076 System.Web.Mvc.Async.<>c__DisplayClass33.<BeginInvokeActionMethodWithFilters>b__31(AsyncCallback asyncCallback, Object asyncState) +100 System.Web.Mvc.Async.WrappedAsyncResultBase1.Begin(AsyncCallback callback, Object state, Int32 timeout) +169
System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeActionMethodWithFilters(ControllerContext controllerContext, IList1 filters, ActionDescriptor actionDescriptor, IDictionary2 parameters, AsyncCallback callback, Object state) +292
System.Web.Mvc.Async.<>c__DisplayClass21.b__19(AsyncCallback asyncCallback, Object asyncState) +1166
System.Web.Mvc.Async.WrappedAsyncResultBase1.Begin(AsyncCallback callback, Object state, Int32 timeout) +169 System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +455 System.Web.Mvc.Controller.<BeginExecuteCore>b__1c(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState) +42 System.Web.Mvc.Async.WrappedAsyncVoid1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +67
System.Web.Mvc.Async.WrappedAsyncResultBase1.Begin(AsyncCallback callback, Object state, Int32 timeout) +169 System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +901 System.Web.Mvc.Async.WrappedAsyncResultBase1.Begin(AsyncCallback callback, Object state, Int32 timeout) +169
System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +711
System.Web.Mvc.MvcHandler.b__4(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState) +93
System.Web.Mvc.Async.WrappedAsyncVoid1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +67 System.Web.Mvc.Async.WrappedAsyncResultBase1.Begin(AsyncCallback callback, Object state, Int32 timeout) +169
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +575
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +1028
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +134

Informations sur la version : Version Microsoft .NET Framework :4.0.30319; Version ASP.NET :4.7.2110.0

I am referencing this package version:
<package id="Microsoft.Rest.ClientRuntime" version="2.3.4" targetFramework="net461" />

One thing to note is that this only happens in the context of the web app I have upgraded, not in the cloud service (they obviously have different dependencies so maybe this is what keeps it from breaking).

I am not quite sure what causes this, I assume it is some kind of assembly binding issue/conflict, but I can't figure out a way to mitigate it. is this a supported scenario? Are there known conflicts with other nuget packages? Here are the list of packages we use:

<package id="Antlr" version="3.4.1.9004" targetFramework="net45" /> <package id="AutoMapper" version="4.1.1" targetFramework="net461" /> <package id="Azimut.CacheCollections" version="1.1.7-alpha4" targetFramework="net461" /> <package id="Azimut.FileFormats" version="1.1.7-alpha4" targetFramework="net461" /> <package id="CacheManager.Core" version="1.1.3.1" targetFramework="net461" /> <package id="CacheManager.Microsoft.Extensions.Caching.Memory" version="1.1.3.1" targetFramework="net461" /> <package id="CacheManager.Microsoft.Extensions.Configuration" version="1.1.3.1" targetFramework="net461" /> <package id="CacheManager.Serialization.Json" version="1.1.3.1" targetFramework="net461" /> <package id="CacheManager.SystemRuntimeCaching" version="0.9.0" targetFramework="net461" /> <package id="EntityFramework" version="6.1.2" targetFramework="net451" /> <package id="log4net" version="2.0.5" targetFramework="net461" /> <package id="MicosoftReportViewerWebForms_v11" version="1.0.1" targetFramework="net45" /> <package id="Microsoft.ApplicationInsights" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.0.7" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.JavaScript" version="1.0.4-build00190" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.Log4NetAppender" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.Web" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.WindowsServer" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.2.0" targetFramework="net461" /> <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net461" /> <package id="Microsoft.AspNet.Providers" version="1.1" /> <package id="Microsoft.AspNet.Providers.Core" version="1.2" targetFramework="net451" /> <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net461" /> <package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net45" /> <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net451" /> <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net451" /> <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net451" /> <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net451" /> <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net461" /> <package id="Microsoft.AspNet.WebPages.Data" version="3.2.3" targetFramework="net461" /> <package id="Microsoft.AspNet.WebPages.WebData" version="3.2.3" targetFramework="net461" /> <package id="Microsoft.Azure.KeyVault.Core" version="1.0.0" targetFramework="net461" /> <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.4" targetFramework="net461" /> <package id="Microsoft.Data.Edm" version="5.8.2" targetFramework="net461" /> <package id="Microsoft.Data.OData" version="5.8.2" targetFramework="net461" /> <package id="Microsoft.Data.Services.Client" version="5.8.2" targetFramework="net461" /> <package id="Microsoft.Extensions.Caching.Abstractions" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.Caching.Memory" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.Configuration" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.Configuration.Abstractions" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.Configuration.Binder" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.Options" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Extensions.Primitives" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.16.204221202" targetFramework="net451" /> <package id="Microsoft.jQuery.Unobtrusive.Ajax" version="3.2.2" targetFramework="net45" /> <package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.2.2" targetFramework="net45" /> <package id="Microsoft.Net.Compilers" version="2.0.1" targetFramework="net461" developmentDependency="true" /> <package id="Microsoft.ReportViewer.Common" version="10.0.40219.1" targetFramework="net45" /> <package id="Microsoft.ReportViewer.WebForms" version="10.0.40219.1" targetFramework="net45" /> <package id="Microsoft.Rest.ClientRuntime" version="2.3.4" targetFramework="net461" /> <package id="Microsoft.TeamFoundationServer.Client" version="14.83.2" targetFramework="net451" /> <package id="Microsoft.TeamFoundationServer.ExtendedClient" version="14.83.2" targetFramework="net451" /> <package id="Microsoft.VisualStudio.Services.Client" version="14.83.2" targetFramework="net451" /> <package id="Microsoft.VisualStudio.Services.InteractiveClient" version="14.83.2" targetFramework="net451" /> <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net4" /> <package id="Microsoft.Web.RedisSessionStateProvider" version="2.2.5" targetFramework="net461" /> <package id="Microsoft.WindowsAzure.ConfigurationManager" version="3.2.3" targetFramework="net461" /> <package id="MiniProfiler" version="3.2.0.157" targetFramework="net451" /> <package id="MiniProfiler.EF" version="2.1.0" targetFramework="net45" /> <package id="Modernizr" version="2.8.3" targetFramework="net45" /> <package id="MSBuild.Extension.Pack" version="1.8.0" targetFramework="net461" /> <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" /> <package id="ReportViewer.Common.10" version="1.0.0" targetFramework="net45" /> <package id="SlowCheetah" version="2.5.15" targetFramework="net451" /> <package id="StackExchange.Redis" version="1.2.6" targetFramework="net461" /> <package id="StackExchange.Redis.StrongName" version="1.2.6" targetFramework="net461" /> <package id="System.ComponentModel.EventBasedAsync" version="4.0.11" targetFramework="net461" /> <package id="System.Data.SqlClient" version="4.4.0" targetFramework="net461" /> <package id="System.Dynamic.Runtime" version="4.0.0" targetFramework="net461" /> <package id="System.IdentityModel.Tokens.Jwt" version="4.0.0" targetFramework="net451" /> <package id="System.Linq.Queryable" version="4.0.0" targetFramework="net461" /> <package id="System.Net.Requests" version="4.0.11" targetFramework="net461" /> <package id="System.Runtime.CompilerServices.Unsafe" version="4.4.0" targetFramework="net461" /> <package id="System.Spatial" version="5.8.2" targetFramework="net461" /> <package id="UniversalTypeConverter" version="1.0.2" targetFramework="net451" /> <package id="WebGrease" version="1.5.2" targetFramework="net45" /> <package id="WindowsAzure.ServiceBus" version="4.1.1" targetFramework="net461" /> <package id="WindowsAzure.Storage" version="7.2.1" targetFramework="net461" />

C#: <see> tags generated from externalDocs are missing link text

Currently AutoRest generates <see> tags without link text, which doesn't render correctly when translated directly to <a href> tags. For example, this Swagger (from here):

    "externalDocs": {
        "url": "https://docs.microsoft.com/rest/api/searchservice/Update-Data-Source"
    },

generates this <see> tag (from here):

/// <see href="https://docs.microsoft.com/rest/api/searchservice/Update-Data-Source" />

When publishing SDK docs to docs.microsoft.com, the <see> tag is translated into this:

<a href="https://docs.microsoft.com/rest/api/searchservice/Update-Data-Source"></a>

The missing text causes the link to fail to render. While this can and should be fixed on the doc publishing side, it could also help if AutoRest produces <see> tags with link text. The simplest solution would be to just copy the link itself, like this:

/// <see href="https://docs.microsoft.com/rest/api/searchservice/Update-Data-Source">https://docs.microsoft.com/rest/api/searchservice/Update-Data-Source</see>

This wouldn't require any changes to existing Swagger specs. In addition to this, AutoRest could include support for the description property of the externalDocs object, so that spec authors can specify their own link text.

Better message for exceptions

@olydis
Moving from here
It does make sense that "Operation returned an invalid status code" would imply that an actual "invalid" status code was returned (like -1) which is a bit confusing.
We could probably make these improvements in the feature for improved exceptions.

Ambiguous reference

I have a swagger.json that has a "path" definition. After generation with autorest, this creates a model "Path". This model is then confused with the System.IO.Path in the class that implements the ServiceOperations, because this includes

    using System.IO;
    using Models;

Is there any way of preventing this? Can I use a transform somehow?

How to use an autorest generated c# client with TestHost?

I'm trying to find out how to do integration tests, here is my approach:

        [SetUp]
        public void SetUp() {
            var host = new WebHostBuilder().UseStartup<Startup>();
            var server = new TestServer(host);
            var messageHandler = server.CreateHandler() as HttpClientHandler;
            _apiClient = new MyAPI(messageHandler);
            _httpClient = server.CreateClient();
        }

        [Test]
        public void AutoRest() {
            var value = _apiClient.ApiValuesGet(); // throws: Microsoft.Rest.HttpOperationException : Operation returned an invalid status code 'NotFound'
            TestContext.WriteLine(string.Join(",", value));
        }

        [Test] // works fine here...
        public async Task HttpClient() {
            var values = await _httpClient.GetStringAsync("/api/Values");
            TestContext.WriteLine(values);
            Assert.IsTrue(values.Contains("value1"));
        }

the whole file is here.
but that ist probably not the right way to setup an autorest client with a testhost...
Is there any documentation on this or an example?

Lists of enum's generating as nullable elements in C# clients (ie List<SomeEnum?>)

When outputting C#, autorest is generating Lists of nullable enum's eg List<SomeEnum?> instead of the expected List.

This issue occurs even when x-nullable: false and x-ms-enum are included in the metadata. The behavior is illogical, especially when tagged with x-nullable: false. The x-nullable element can't be included in the items[] array.

Example Swagger snippet:

{
   "SomeEnumList":{
      "type":"array",
      "items":{
         "enum":[
            "Unknown",
            "Cat",
            "Dog",
            "Cow"
         ],
         "type":"string",
         "x-ms-enum":{
            "name":"SomeEnum",
            "modelAsString":false
         }
      },
      "x-nullable":false
   }
}

Autorest version
Version: 2.0.4262
Node: v8.6.0

Generated C# services seem to depend on concrete client not interface of client?

I have noticed this a couple of times but someone else just asked why it happens so wanted to raise it here as it seems odd.

So lets say I have an Api (users) with 2 endpoints (GetUser, AddUser) in some controller (UserController) and it exposes a swagger endpoint (via swashbuckle) and I want to use autorest to generate the client.

Everything goes well and the client is generated for the whimsical example above and I then have:

  • IUsersApi.cs
  • UsersApi.cs
  • IUsers.cs
  • Users.cs

So IUsers lets say contains the calls for GetUser and AddUser which is fine, and I want to DI this stuff into my consuming apps controllers, so I do the following (using ninject syntax but pretend its whatever DI framework you like):

var myApiUri = // some uri referencing the api endpoint for that env
Bind<IUsersApi>().To<UsersApi>().WithConstructorArg(myApiUri);
Bind<IUsers>().To<Users>();

Then lets pretend I have a controller like so:

public class SomeController : Controller
{
   private IUsers _usersClient;
   
   public SomeController(IUsers usersClient)
   { _usersClient = usersClient; }
}

Everything looks good, I go to run the application and BOOM, it explodes because its trying to resolve UsersApi not IUsersApi... so if we crack open the Users implementation we see that its constructor looks like:

public partial class Users : IServiceOperations<UsersApi>, IUsers
{
	/// <summary>
	/// Initializes a new instance of the Users class.
	/// </summary>
	/// <param name='client'>
	/// Reference to the service client.
	/// </param>
	/// <exception cref="System.ArgumentNullException">
	/// Thrown when a required parameter is null
	/// </exception>
	public Documents(UsersApi client)
	{
		if (client == null)
		{
			throw new System.ArgumentNullException("client");
		}
		Client = client;
	}
}

Notice how the constructor takes a concrete type of UsersApi not IUsersApi... is this by design and if so why? as the interface is only used to create the implementation of the API, its not used within any of the controller/endpoints generated, and it feels like they should be using the interfaces not the concrete type.

Any thoughts?

Separate Generated code in directories

Currently C# generated code is split into following directory structure

  1. Generated -- has Operations code as well as Interfaces
  2. Generated\Model

Ask:
Generate code as below

  1. Generated\
  2. Generated\Interface
  3. Generated\Operations
  4. Generated\Model

Couple of advantages of doing this:

  1. Primarily code review and code organizations
  2. With current code generation mode, all files starting with I (for interface) are anyway grouped together in VS, so indirectly anyway they are grouped in a long list of files in VS Solution explorer, VS Code?
  3. Splitting it up into different directories help in collapsing VS Solution explorer nodes and helps in easily navigating the code base within VS or VS Code.
  4. Helps a lot when code reviewing (especially in code flow)

Generated C# code feedback

I was playing around with autorest and had some feedback and questions about the generated code:

Synchronous APIs

public static void ApiAttendeesByUsernameGet(this IConferencePlannerAPI operations, string username)
{
    operations.ApiAttendeesByUsernameGetAsync(username).GetAwaiter().GetResult();
}

It seems like extension methods are generated to do sync calls. Why is this the case? This shouldn't be the default (we should generally make it hard to write outgoing blocking API calls). Maybe this should be a flag.

Naming

ApiSpeakersByIdGetAsync vs GetApiSpeakersByIdAsync. Seems like its makes sense to put the verb first? - This is an issue with the swagger doc

Headers

Task<HttpOperationResponse> ApiAttendeesByUsernameGetWithHttpMessagesAsync(string username, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken));

This is the shape of API forces the caller to allocate a dictionary to specify custom headers. One alternative might be to expose an Action<HttpRequestMessage> or Func<HttpRequestMessage, Task> as a callback. That would let the caller modify the existing outgoing request rather than forcing them to allocate.

Generated API methods

Here's an example of the generated code for an outbound API request:

public async Task<HttpOperationResponse<AttendeeResponse>> ApiAttendeesByUsernameGetWithHttpMessagesAsync(string username, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
    if (username == null)
    {
        throw new ValidationException(ValidationRules.CannotBeNull, "username");
    }
    // Tracing
    bool _shouldTrace = ServiceClientTracing.IsEnabled;
    string _invocationId = null;
    if (_shouldTrace)
    {
        _invocationId = ServiceClientTracing.NextInvocationId.ToString();
        Dictionary<string, object> tracingParameters = new Dictionary<string, object>();
        tracingParameters.Add("username", username);
        tracingParameters.Add("cancellationToken", cancellationToken);
        ServiceClientTracing.Enter(_invocationId, this, "ApiAttendeesByUsernameGet", tracingParameters);
    }
    // Construct URL
    var _baseUrl = BaseUri.AbsoluteUri;
    var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/Attendees/{username}").ToString();
    _url = _url.Replace("{username}", System.Uri.EscapeDataString(username));
    // Create HTTP transport objects
    var _httpRequest = new HttpRequestMessage();
    HttpResponseMessage _httpResponse = null;
    _httpRequest.Method = new HttpMethod("GET");
    _httpRequest.RequestUri = new System.Uri(_url);
    // Set Headers


    if (customHeaders != null)
    {
        foreach(var _header in customHeaders)
        {
            if (_httpRequest.Headers.Contains(_header.Key))
            {
                _httpRequest.Headers.Remove(_header.Key);
            }
            _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value);
        }
    }

    // Serialize Request
    string _requestContent = null;
    // Send Request
    if (_shouldTrace)
    {
        ServiceClientTracing.SendRequest(_invocationId, _httpRequest);
    }
    cancellationToken.ThrowIfCancellationRequested();
    _httpResponse = await HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
    if (_shouldTrace)
    {
        ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse);
    }
    HttpStatusCode _statusCode = _httpResponse.StatusCode;
    cancellationToken.ThrowIfCancellationRequested();
    string _responseContent = null;
    if ((int)_statusCode != 200)
    {
        var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode));
        if (_httpResponse.Content != null) {
            _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
        }
        else {
            _responseContent = string.Empty;
        }
        ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
        ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);
        if (_shouldTrace)
        {
            ServiceClientTracing.Error(_invocationId, ex);
        }
        _httpRequest.Dispose();
        if (_httpResponse != null)
        {
            _httpResponse.Dispose();
        }
        throw ex;
    }
    // Create Result
    var _result = new HttpOperationResponse<AttendeeResponse>();
    _result.Request = _httpRequest;
    _result.Response = _httpResponse;
    // Deserialize Response
    if ((int)_statusCode == 200)
    {
        _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
        try
        {
            _result.Body = SafeJsonConvert.DeserializeObject<AttendeeResponse>(_responseContent, DeserializationSettings);
        }
        catch (JsonException ex)
        {
            _httpRequest.Dispose();
            if (_httpResponse != null)
            {
                _httpResponse.Dispose();
            }
            throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
        }
    }
    if (_shouldTrace)
    {
        ServiceClientTracing.Exit(_invocationId, _result);
    }
    return _result;
}
  • Handling responses - For responses that have a return type, we use the most inefficient JSON.NET APIs to deserialize the object. We convert the response into a single string and deserialize that string into the response type:

    if ((int)_statusCode == 200)
    {
        _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
        try
        {
            _result.Body = SafeJsonConvert.DeserializeObject<AttendeeResponse>(_responseContent, DeserializationSettings);
        }
        catch (JsonException ex)
        {
            _httpRequest.Dispose();
            if (_httpResponse != null)
            {
                _httpResponse.Dispose();
            }
            throw new SerializationException("Unable to deserialize the response.", _responseContent, ex);
        }
    }

    This should be using the Stream based APIs on HttpClient and the JsonReader APIs on
    JSON.NET.

  • We get the response content as a string for non 200 responses. Is there any reason we need to allocate a string here? Why not let the caller deal with that?

    var ex = new HttpOperationException(string.Format(...));
    if (_httpResponse.Content != null) {
        _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
    }
    else {
        _responseContent = string.Empty;
    }
    ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
    ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);

Other random feedback

We should consider directly returning the HttpOperationResponse<T> to the interface APIs. It would allow callers to read a response without forcing them to handle an exception for non 200 response codes.

/cc @fearthecowboy

C# generation should allow use of existing assembly for models

When generating the C# client it should be possible to prevent the automatic generation of models and instead allow reuse of classes from a provided assembly. This would allow a common assembly to be shared between the server and client.

For example, all my API's return a result class that derives from a common base class. This allows generic processing of all results because they have the same base class. I cannot do this with AutoRest because I end up with a flattened set of classes. I would have to use reflection to extract the common properties and this is obviously a very poor solution. The WCF client generator allows this shared assembly approach.

System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.

Not getting any useful information in the stacktrace. Cannot seem to figure out what exactly is causing the key not found exception.

output:

PS C:\Users\User\Documents\NSwagStuff> autorest readme.md --debug
AutoRest code generation utility [version: 2.0.4262; node: v8.9.4]
(C) 2018 Microsoft Corporation.
https://aka.ms/autorest
Network Enabled: true
Starting @microsoft.azure/autorest-core from C:\Users\User\.autorest\@microsoft.azure_autorest-core@2.0.4262
   Loading AutoRest core      'C:\Users\User\.autorest\@[email protected]\node_modules\@microsoft.azure\autorest-core\dist' (2.0.4262)
   Loading AutoRest extension '@microsoft.azure/autorest.csharp' (~2.2.51->2.2.57)
   Loading AutoRest extension '@microsoft.azure/autorest.modeler' (2.3.43->2.3.43)
DEBUG: swagger-document-override/md-override-loader - START
DEBUG: swagger-document/loader - START
DEBUG: pipeline-emitter - START
DEBUG: configuration-emitter - START
DEBUG: Emitting 'pipeline' at file:///C:/Users/User/Documents/NSwagStuff/GeneratedRest/pipeline
DEBUG: Emitting 'configuration' at file:///C:/Users/User/Documents/NSwagStuff/GeneratedRest/configuration
DEBUG: pipeline-emitter - END
DEBUG: configuration-emitter - END
DEBUG: swagger-document-override/md-override-loader - END
DEBUG: swagger-document/loader - END
DEBUG: swagger-document/individual/transform - START
DEBUG: swagger-document/individual/transform - END
DEBUG: swagger-document/individual/schema-validator - START
DEBUG: swagger-document/individual/schema-validator - END
DEBUG: swagger-document/individual/identity - START
DEBUG: swagger-document/individual/identity - END
DEBUG: swagger-document/compose - START
DEBUG: swagger-document/compose - END
DEBUG: swagger-document/transform-immediate - START
DEBUG: swagger-document/transform-immediate - END
DEBUG: swagger-document/transform - START
DEBUG: swagger-document/transform - END
DEBUG: swagger-document/identity - START
DEBUG: swagger-document/identity - END
DEBUG: openapi-document/openapi-document-converter - START
DEBUG: swagger-document/emitter - START
DEBUG: Emitting 'swagger-document' at file:///C:/Users/User/Documents/NSwagStuff/GeneratedRest/nswagproofswagger
DEBUG: swagger-document/emitter - END
DEBUG: openapi-document/openapi-document-converter - END
DEBUG: openapi-document/transform - START
DEBUG: openapi-document/transform - END
DEBUG: openapi-document/component-modifiers - START
DEBUG: openapi-document/component-modifiers - END
DEBUG: openapi-document/identity - START
DEBUG: openapi-document/identity - END
DEBUG: csharp/imodeler1 - START
DEBUG: openapi-document/emitter - START
DEBUG: Emitting 'openapi-document' at file:///C:/Users/User/Documents/NSwagStuff/GeneratedRest/nswagproofswagger
DEBUG: openapi-document/emitter - END
FATAL: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at System.ThrowHelper.ThrowKeyNotFoundException()
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at AutoRest.Modeler.SwaggerModeler.Build(ServiceDefinition serviceDefinition) in C:\Users\ci\AppData\Local\Temp\PUBLISHu1dw1\43_20171213T192246\autorest.modeler\src\SwaggerModeler.cs:line 128
   at AutoRest.Modeler.Program.<ProcessInternal>d__2.MoveNext() in C:\Users\ci\AppData\Local\Temp\PUBLISHu1dw1\43_20171213T192246\autorest.modeler\src\Program.cs:line 60
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NewPlugin.<Process>d__15.MoveNext()
FATAL: csharp/imodeler1 - FAILED
FATAL: Error: Plugin imodeler1 reported failure.
Process() cancelled due to exception : Plugin imodeler1 reported failure.
  Error: Plugin imodeler1 reported failure.

Readme.md:

# AutoRest swagger generation
> see https://aka.ms/autorest 

## config

yaml
input-file: nswagproofswagger.json
output-folder: ./GeneratedRest

csharp:
    namespace: NSwagProof.WebServiceTests.GeneratedClient
    sync-methods: all
    use-datetimeoffset: true

Swagger Json input:

NSwag Swagger Json

{
    "x-generator": "NSwag v11.16.1.0 (NJsonSchema v9.10.41.0 (Newtonsoft.Json v9.0.0.0))",
    "swagger": "2.0",
    "info": {
        "title": "NSwagProof.WebService",
        "version": "1.0.0"
    },
    "host": "localhost:8100",
    "schemes": [
        "http"
    ],
    "consumes": [
        "application/json"
    ],
    "produces": [
        "application/json"
    ],
    "paths": {
        "/animals": {
            "get": {
                "tags": [
                    "Animal"
                ],
                "summary": "Gets all animals",
                "operationId": "Animal_GetAllAnimals",
                "responses": {
                    "200": {
                        "x-nullable": true,
                        "description": "List of Animals",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Animal"
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Animal"
                ],
                "summary": "Adds an Animal",
                "operationId": "Animal_AddAnimal",
                "consumes": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "Animal",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/Animal"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "x-nullable": true,
                        "description": "Animal",
                        "schema": {
                            "$ref": "#/definitions/Animal"
                        }
                    }
                }
            }
        },
        "/animals/{id}": {
            "get": {
                "tags": [
                    "Animal"
                ],
                "summary": "Gets a specific animal",
                "operationId": "Animal_GetAnimal",
                "parameters": [
                    {
                        "type": "integer",
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "Animal's ID",
                        "format": "int32",
                        "x-nullable": false
                    }
                ],
                "responses": {
                    "200": {
                        "x-nullable": true,
                        "description": "Animal",
                        "schema": {
                            "$ref": "#/definitions/Animal"
                        }
                    }
                }
            },
            "put": {
                "tags": [
                    "Animal"
                ],
                "summary": "Updates an Animal",
                "operationId": "Animal_UpdateAnimal",
                "consumes": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "type": "integer",
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "Animal's ID",
                        "format": "int32",
                        "x-nullable": false
                    },
                    {
                        "name": "Animal",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/Animal"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "x-nullable": true,
                        "description": "Animal",
                        "schema": {
                            "$ref": "#/definitions/Animal"
                        }
                    }
                }
            }
        },
        "/test/ping": {
            "get": {
                "tags": [
                    "Test"
                ],
                "summary": "Pings server side and returns a 200",
                "operationId": "Test_Ping",
                "responses": {
                    "200": {
                        "x-nullable": true,
                        "description": "200 OK",
                        "schema": {
                            "type": "file"
                        }
                    }
                }
            }
        },
        "/test/post": {
            "post": {
                "tags": [
                    "Test"
                ],
                "summary": "Tests the Swagger body param attribute with a type not initially registered",
                "operationId": "Test_PostBodyTest",
                "consumes": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "RandomTestContainer",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "title": "RandomTestContainer",
                            "type": "object",
                            "description": "Random container object for a test",
                            "required": [
                                "Thing"
                            ],
                            "properties": {
                                "Thing": {
                                    "type": "integer",
                                    "description": "Thing that does things",
                                    "format": "int32"
                                }
                            }
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "x-nullable": true,
                        "description": "",
                        "schema": {
                            "type": "string"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "Animal": {
            "type": "object",
            "discriminator": "AnimalType",
            "description": "Animal Base Class",
            "required": [
                "NumberOfLegs",
                "IsDomesticated",
                "Diet",
                "IsExtinct",
                "AnimalType"
            ],
            "properties": {
                "Id": {
                    "type": "integer",
                    "description": "Identifier",
                    "format": "int32"
                },
                "Species": {
                    "type": "string",
                    "description": "Animals species scientific name"
                },
                "NumberOfLegs": {
                    "type": "integer",
                    "description": "Amount of legs the animal has",
                    "format": "int32"
                },
                "IsDomesticated": {
                    "type": "boolean",
                    "description": "States whether the animal has been domesticated as a pet"
                },
                "Diet": {
                    "description": "What type of food the animal eats",
                    "allOf": [
                        {
                            "$ref": "#/definitions/Diet"
                        }
                    ]
                },
                "IsExtinct": {
                    "type": "boolean",
                    "description": "Whether there are any of the same animal still alive"
                },
                "AnimalType": {
                    "type": "string"
                }
            }
        },
        "Diet": {
            "type": "string",
            "description": "What the animal eats",
            "x-enumNames": [
                "Omnivorous",
                "Carnivorous",
                "Herbivorous"
            ],
            "enum": [
                "Omnivorous",
                "Carnivorous",
                "Herbivorous"
            ]
        },
        "Cat": {
            "type": "object",
            "description": "Cat Class",
            "required": [
                "IsHairless"
            ],
            "properties": {
                "Breed": {
                    "type": "string",
                    "description": "What type of cat"
                },
                "IsHairless": {
                    "type": "boolean",
                    "description": "Whether the cat has hair or not"
                }
            },
            "allOf": [
                {
                    "$ref": "#/definitions/DomesticatedAnimal"
                }
            ]
        },
        "DomesticatedAnimal": {
            "type": "object",
            "discriminator": "AnimalType",
            "description": "Domesticated Animal Base Class",
            "required": [
                "Size",
                "AnimalType"
            ],
            "properties": {
                "Name": {
                    "type": "string",
                    "description": "Name of the Animal"
                },
                "Size": {
                    "description": "Size of the animal",
                    "allOf": [
                        {
                            "$ref": "#/definitions/Size"
                        }
                    ]
                },
                "Allergies": {
                    "type": "array",
                    "description": "Allergies the animal may have",
                    "items": {
                        "$ref": "#/definitions/Allergy"
                    }
                },
                "AnimalType": {
                    "type": "string"
                }
            },
            "allOf": [
                {
                    "$ref": "#/definitions/Animal"
                }
            ]
        },
        "Size": {
            "type": "string",
            "description": "Size of the Animal",
            "x-enumNames": [
                "Tiny",
                "Small",
                "Medium",
                "Large",
                "Huge"
            ],
            "enum": [
                "Tiny",
                "Small",
                "Medium",
                "Large",
                "Huge"
            ]
        },
        "Allergy": {
            "type": "object",
            "description": "Allergy Class",
            "properties": {
                "Name": {
                    "type": "string",
                    "description": "Name of the Allergy"
                },
                "DictionaryThing": {
                    "type": "object",
                    "description": "Dictionary to tests swagger stuff",
                    "additionalProperties": {
                        "type": "integer",
                        "format": "int32"
                    }
                }
            }
        },
        "Dog": {
            "type": "object",
            "description": "Dog Class",
            "required": [
                "IsAGoodBoy"
            ],
            "properties": {
                "Breed": {
                    "type": "string",
                    "description": "The type of dog"
                },
                "IsAGoodBoy": {
                    "type": "boolean",
                    "description": "Whether the dog is a good boy or not. (He is)"
                }
            },
            "allOf": [
                {
                    "$ref": "#/definitions/DomesticatedAnimal"
                }
            ]
        },
        "Chupacabra": {
            "type": "object",
            "description": "Chupacabra Class",
            "required": [
                "NumberOfGoatsSlaughtered"
            ],
            "properties": {
                "NumberOfGoatsSlaughtered": {
                    "type": "integer",
                    "description": "Number of goats the Chupacabra has slaughtered",
                    "format": "int32"
                }
            },
            "allOf": [
                {
                    "$ref": "#/definitions/Animal"
                }
            ]
        }
    }
}

Feature request: support uri format type for string.

When adding format: uri (standard swagger format) to a string property in a definition, the generated c# object still uses string.
Format: uuid works fine and generates Guid type, but for uri it keeps string type.

autorest --info
AutoRest code generation utility [version: 2.0.4245; node: v8.10.0]
(C) 2018 Microsoft Corporation.
https://aka.ms/autorest


Showing All Installed Extensions

 Type       Extension Name                           Version      Location
 core       @microsoft.azure/autorest-core           2.0.4245     C:\Users\guperrot\AppData\Local\Temp\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.csharp         2.2.57       C:\Users\guperrot\AppData\Local\Temp\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.modeler        2.3.43       C:\Users\guperrot\AppData\Local\Temp\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.modeler        2.3.44       C:\Users\guperrot\AppData\Local\Temp\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.nodejs         2.1.32       C:\Users\guperrot\AppData\Local\Temp\.autorest\@[email protected]

Definition:

  DataSubjectRightQueueInfo:
    type: object
    properties:
      sasUri:
        type: string
        format: uri
      queueName:
        type: string
      expiresAt:
        type: string
        format: date-time
    required:
      - sasUri
      - queueName
      - expiresAt

Generated code:

        /// <summary>
        /// </summary>
        [JsonProperty(PropertyName = "sasUri")]
        public string SasUri { get; set; }

Expected: public Uri SasUri

[C#] Autogenerating models without a description creates a class with no doc comments

Generating models via auto rest from a swagger, where the models do not have descriptions, generates model classes with no

xmlcomments attached. This can break code analysis for libraries.

Fix would be: when no description is offered, use a simple description, e.g.

/// <summary>
/// A MyObviousResponseModel
/// </summary>
public partial class MyObviousResponseModel

this could also be configured via an optional flag when running autorest

issues with using AutoRest client with Unity IL2CPP and UWP builds

Unity is the most widely used multiplatform development environment for creating games and VR/AR applications. I recently tried using AutoRest to generate client code for accessing an OpenAPI web service in my Unity application.

I was able to get this working by including the generated C# files, Microsoft.Rest.ClientRuntime.dll, and Newtonsoft.Json.dll in my Unity Assets folder. In order to fix CS1070: The type `System.Net.Http.DelegatingHandler' has been forwarded to an assembly that is not referenced I added -r:System.Net.Http.dll to the csc.rsp, gmcs.rsp, and mcs.rsp files.

The result worked in Unity with the Mono backend. (Edit > Project Settings > Player > Other Settings > Configuration > Scripting Backend = Mono. I also had Scripting Runtime Version and Api Compatibility Level set to .NET 4.6x)

However, I had trouble with:

  1. Scripting Backend = IL2CPP. When building and running an app that called my API the following error occurred: Unsupported internal call for IL2CPP:DynamicMethod::create_dynamic_method - System.Reflection.Emit is not supported.
  2. Universal Windows platform. When I switched platform to Universal Windows Platform in Build Settings, I was unable to build due to Reference Rewriter errors such as Error: type `Newtonsoft.Json.Serialization.IAttributeProvider` doesn't exist in target framework. It is referenced from Microsoft.Rest.ClientRuntime.dll at System.Boolean Microsoft.Rest.Serialization.JsonConverterHelper::IsJsonExtensionData(Newtonsoft.Json.Serialization.JsonProperty).

In my case, I can call the service from a PC server running on the Mono backend, so I should be all set. However, I'm opening this issue just to document the issues that I encountered using IL2CPP and UWP. Ideally, it would be possible to use AutoRest in both these circumstances. Unity is the most widely-used development tool for 3D applications such as games and virtual/mixed reality. Perhaps AutoRest would want to add official support or test cases for Unity development?

Null for post body object results in StatusCode 415 "MediaType not supported"

If I have a post method that has an option complex body object, it will result an exception that results in a status code 415 "MediaType not supported" being returned to client. If you use the swagger ui tool to do the same thing, the service is fine, because it still sends a body with a empty json object "{}". I think the AutoRest generated client should emulate the same behavior.

Flip interface for operation groups to declare the (current) extension methods

Currently, the interface declares the ugly, implementation specific details of service calls. All the "nice" methods are extension methods and thus not part of the interface. No one wants to re-implement the interface due to its rigidity (including us, would have been nice to have JsonRpc client implement the same interface, but unfortunately the signature talks about headers and HTTP responses). Making the interface declare the abstract, pretty methods would make implementations pluggable! That would allow better testing among other things. (note: I'm not suggesting that additional convenience methods shouldn't be provided using extension methods, but that the "core" method call should be an implementation agnostic one)
/cc @sergey-shandar @dsgouda

Returning 201 from POST gives error "Operation returned an invalid status code 'Created'"

Related SO:
https://stackoverflow.com/q/42634122/2781524

Calling a POST that:
return Created($"api/Value/{id}, id)
Which is a status code 201 will give caller exception:

Exception: Exception caught: 'Microsoft.Rest.HttpOperationException' in Program.exe ("Operation returned an invalid status code 'Created'"). Exception caught: 'Microsoft.Rest.HttpOperationException' in Program.exe ("Operation returned an invalid status code 'Created'") 13,495.67s [7568] Worker Thread

According to Mozilla status code 201 is used for POST

"The common use case of this status code is as the result of a POST request."

https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201

According to the SO post the offending code:

private static bool CheckResponseStatusCodeFailed<TBody, THeader>(
            AzureOperationResponse<TBody, THeader> initialResponse)
        {
            var statusCode = initialResponse.Response.StatusCode;
            var method = initialResponse.Request.Method;
            if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted ||
                (statusCode == HttpStatusCode.Created && method == HttpMethod.Put) ||
                (statusCode == HttpStatusCode.NoContent && (method == HttpMethod.Delete || method == HttpMethod.Post)))
            {
                return false;
            }
            return true;
    }

ApplicationTokenProvider depends on types that don't always exist

The ApplicationTokenProvider type exposes several API's that take in concrete instances of ClientCertificateAssertion. These APIs are used in common scenarios such as certificate-based authentication to Azure.

The ClientCertificateAssertion type has been removed from Microsoft.IdentityModel.Clients.ActiveDirectory packages newer than 3.0. However, the NuGet dependencies for Microsoft.Rest.ClientRuntime.Azure.Authentication allow some flavor to take dependencies on that library all they way up to 4.0. This allows developers to build apps that fail in very strange ways (see Issue 994 and Stack Overflow).

We need a better way of doing certificate-based login on modern (> 2.28) versions of Microsoft.IdentityModel.Clients.ActiveDirectory that works on all runtimes. It's possible to do this today via the overloads that take an IApplicationAuthenticationProvider, but that requires developers to write their own runtime-specific implementation of ClientCertificateAssertion as well as an IApplicationAuthenticationProvider that provides it. It's possible to crib a working solution together from sources floating around, but it's really non-obvious.

One scenario that provokes this issue is trying to bootstrap programmatic access to a Key Vault via a certificate. ApplicationTokenProvider.LoginSilentlyAsync is the most obvious way of authenticating via a certificate, and it's pretty easy to get that working against the 2.28 version of Microsoft.IdentityModel.Clients.ActiveDirectory. Then you add the Key Vault libraries to your application; those have a hard dependency on v3 of the ActiveDirectory client so you update your app to use that version and suddenly you have very strange issues -- builds fail, types appear to exist but actually don't, and it's very confusing,

Autorest csharp seems to be A LOT slower upon recent upgrade

A bit of a woolly query, but we have noticed a drastic speed change over the past couple of days with the autorest part of our build pipeline. Historically it used to take under a minute to complete but now it is taking more than a couple of minutes to complete and in some cases it can hang for > 10 mins.

I am struggling to narrow down what the problem is, but here is the version details we had previously which were pretty quick and had no problems.

 .\node_modules\.bin\autorest --info
AutoRest code generation utility [version: 2.0.4166]

(C) 2017 Microsoft Corporation.
https://aka.ms/autorest
Showing All Installed Extensions

 Type       Extension Name                     Version   location
 core       @microsoft.azure/autorest-core     2.0.4275  C:\...\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.csharp   2.2.57    C:\...\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.modeler  2.3.43    C:\...\.autorest\@[email protected]
 
Took 15s on local dev build (about 30% slower on CI)

Here is the version we are running now which seems a lot slower.

.\node_modules\.bin\autorest --info
AutoRest code generation utility [version: 2.0.4283; node: v8.9.3]
(C) 2018 Microsoft Corporation.
https://aka.ms/autorest


Showing All Installed Extensions

 Type       Extension Name                           Version      Location
 core       @microsoft.azure/autorest-core           2.0.4283     C:\...\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.csharp         2.3.80       C:\...\.autorest\@[email protected]
 extension  @microsoft.azure/autorest.modeler        2.3.55       C:\...\.autorest\@[email protected]
 
 Took 25s in our build (about 200% slower on CI build)

I noticed that there is some mention of some oldStringModel on the csharp generator but struggling to find much info on it, so can anyone point me in the direction as to what has changed which would cause the slowdown.

Here is what we run:

"node_modules/.bin/autorest" --csharp --input-file="http://localhost:5000/swagger/docs/v1" --namespace="SomeNamespace" --output-folder="SomeOutputFolder"

[odata] Cannot convert Newtonsoft.Json.Linq.JObject to System.String

I have some swagger specs generated from Swashbuckler. I have cleaned them up but when I try to generate C# using the command

autorest --input-file=smallest.json --payload-flattening-threshold=2 --verbose --debug --csharp --azure-arm

I get the error

FATAL: System.InvalidCastException: Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'System.String'.
   at AutoRest.Extensions.Azure.AzureExtensions.ParseODataExtension(CodeModel codeModel)
   at AutoRest.Extensions.Azure.AzureExtensions.NormalizeAzureClientModel(CodeModel codeModel)
   at AutoRest.CSharp.Azure.TransformerCsa.AutoRest.Core.ITransformer<AutoRest.CSharp.Azure.Model.CodeModelCsa>.TransformCodeModel(CodeModel cs)
   at Generator.<ProcessInternal>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NewPlugin.<Process>d__11.MoveNext()
FATAL: csharp/generate - FAILED
FATAL: Error: Plugin csharp reported failure.
Process() Cancelled due to exception : Plugin csharp reported failure.
Error: Plugin csharp reported failure.
    at C:\Users\jerobins.REDMOND\.autorest\plugins\autorest\1.2.2\node_modules\autorest-core\lib\pipeline\pipeline.js:90:19
    at Generator.next (<anonymous>)
    at fulfilled (C:\Users\jerobins.REDMOND\AppData\Roaming\npm\node_modules\autorest\lib\polyfill.min.js:14:62)
    at <anonymous>

Using the command

autorest --input-file=smallest.json --payload-flattening-threshold=2 --verbose --debug --csharp

No error and C# code is generated.

In addition, if I delete a portion of the swagger spec containing:

"x-ms-odata": {
      "type": "object"
}

Then both commands succeed. Should the type be something else?

I have attached a small example which exhibits the error.

smallest.txt

C# - Generated code has incorrect and missing <return> tags for XML comments

AutoRest-generated C# code sometimes uses <return> instead of <return/> in XML comments, and other times it omits these tags entirely. The end result for Azure .NET SDKs is that the generated documentation is incomplete.

For example, note the <return> tag here: https://github.com/Azure/azure-sdk-for-net/blob/ca1d3c0e2b4e4802f83c2a9d9767d25fa76a7739/src/SDKs/Search/Management/Management.Search/Generated/AdminKeysOperations.cs#L87

This should be <returns>. Also, the corresponding interface method doesn't have a <returns> tag at all: https://github.com/Azure/azure-sdk-for-net/blob/ca1d3c0e2b4e4802f83c2a9d9767d25fa76a7739/src/SDKs/Search/Management/Management.Search/Generated/IAdminKeysOperations.cs#L58

The end result is documentation that is incomplete. See: https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.management.search.iadminkeysoperations.getwithhttpmessagesasync?view=azure-dotnet#Microsoft_Azure_Management_Search_IAdminKeysOperations_GetWithHttpMessagesAsync_System_String_System_String_Microsoft_Azure_Management_Search_Models_SearchManagementRequestOptions_System_Collections_Generic_Dictionary_System_String_System_Collections_Generic_List_System_String___System_Threading_CancellationToken_

Note that there is no information about what the method returns, other than its type.

AutoRest should include well-formed <returns> tags in all methods that return a value.

Client API supporting gzip compression on response streams (Azure Search)

Initially posted here:
Azure/azure-sdk-for-net#3724

Just opening the discussion to enable end-to-end support for gzip compression for the .NET Client for Azure Search and any other service which supports Gzip Compression.

Sample calling code for Azure Search:

var response = await _indexClient.Documents.GetWithHttpMessagesAsync(id, new List() { "*" }, new SearchRequestOptions(), new Dictionary<string, List>()
{
{"Accept-Encoding", new List(){ "gzip", "deflate"}}
}, cancellationToken);

Its ugly, but it does instruct the client to bring down the data gzip'd.

The problem is that the .NET Client always assumes the response will be a string response, as per:

https://github.com/Azure/azure-sdk-for-net/blob/psSdkJson6/src/SDKs/Search/DataPlane/Microsoft.Azure.Search/GeneratedSearchService/DataSourcesOperations.cs#L296

The client needs to check for the Content-Encoding header, and if gzip, handle it appropriately.

Thanks.

-Rob

Duplicate File Generation

i tried to generate C# code for An Microsoft OPEN API for face detection
https://apis.guru/browse-apis/,
And i got the follwing error, Please help me in validating OPEN API specification.

<<

System.Exception: Duplicate File Generation: Face.cs
at AutoRest.Core.CodeGenerator.d__13.MoveNext() in /opt/vsts/work/1/s/autorest.common/src/CodeGenerator.cs:line 151
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at AutoRest.Core.CodeGenerator.d__12.MoveNext() in /opt/vsts/work/1/s/autorest.common/src/CodeGenerator.cs:line 121
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at AutoRest.CSharp.CodeGeneratorCs.d__141.MoveNext() in /opt/vsts/work/1/s/src/vanilla/CodeGeneratorCs.cs:line 86 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at AutoRest.CSharp.CodeGeneratorCs.<GenerateClientSideCode>d__12.MoveNext() in /opt/vsts/work/1/s/src/vanilla/CodeGeneratorCs.cs:line 63 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at AutoRest.CSharp.CodeGeneratorCs.<GenerateRestCode>d__19.MoveNext() in /opt/vsts/work/1/s/src/vanilla/CodeGeneratorCs.cs:line 155 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at AutoRest.CSharp.CodeGeneratorCs.<Generate>d__20.MoveNext() in /opt/vsts/work/1/s/src/vanilla/CodeGeneratorCs.cs:line 188 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at AutoRest.CSharp.Program.<ProcessInternal>d__4.MoveNext() in /opt/vsts/work/1/s/src/Program.cs:line 147 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at NewPlugin.d__20.MoveNext() in /opt/vsts/work/1/s/autorest.common/src/Plugins/NewPlugin.cs:line 163
FATAL: csharp/generate - FAILED
FATAL: Error: Plugin csharp reported failure.
Process() cancelled due to exception : Plugin csharp reported failure.
Error: Plugin csharp reported failure.

C#: When multipart/form-data has more than 1 part, only the first one is generated

using version: 2.0.4280

Here's a portion of my Swagger 2.0.. there are 4 parameters, the first two are "normal" inputs, but the last two are part of the multpart/form-data ..

            "consumes": [
                "multipart/form-data"
            ],
            "produces": [
                "application/json",
                "text/json",
                "application/xml",
                "text/xml"
            ],
            "parameters": [
                {
                    "name": "docid",
                    "in": "path",
                    "required": true,
                    "type": "string"
                },
                {
                    "name": "userid",
                    "in": "query",
                    "required": true,
                    "type": "string"
                },
                {
                    "name": "attachment",
                    "in": "formData",
                    "description": "PDF to upload.",
                    "required": true,
                    "type": "file"
                },
                {
                    "name": "doc_string",
                    "in": "formData",
                    "description": "Additional Document Data",
                    "required": true,
                    "type": "string"
                }
            ],

With the above, I get the following multi-part logic generated:

        MultipartFormDataContent _multiPartContent = new MultipartFormDataContent();
        if (attachment != null)
        {
            StreamContent _attachment = new StreamContent(attachment);
            _attachment.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            FileStream _attachmentAsFileStream = attachment as FileStream;
            if (_attachmentAsFileStream != null)
            {
                ContentDispositionHeaderValue _contentDispositionHeaderValue = new ContentDispositionHeaderValue("form-data");
                _contentDispositionHeaderValue.Name = "attachment";
                _contentDispositionHeaderValue.FileName = _attachmentAsFileStream.Name;
                _attachment.Headers.ContentDisposition = _contentDispositionHeaderValue;
            }
            _multiPartContent.Add(_attachment, "attachment");
        }

It's all well and good, but, nothing is generated for my doc_string parameter.. and if I swap the order and put the doc_string parameter ahead of my attachment, I get the correct code generated for doc_string, but then my attachment parameter is ignored.

Seems to support 1 part... that doesn't seem very "multipart-ish" ;-)

C#: DeserializeObject<object> is generated for 400/500 response when first response has no schema

This is the section of the swagger json:

{
  "/api/v1/users/{userGuid}/emails/{email}": {
    "delete": {
      "tags": [        "Emails"      ],
      "summary": "Delete one email",
      "operationId": "Emails_DeleteEmail",
      "consumes": [],
      "produces": [],
      "parameters": [
        {
          "name": "userGuid",
          "in": "path",
          "description": "user guid",
          "required": true,
          "type": "string"
        },
        {
          "name": "email",
          "in": "path",
          "required": true,
          "type": "string"
        }
      ],
      "responses": {
        "204": {
          "description": "Email deleted"
        },
        "400": {
          "description": "Bad request. See return for details.",
          "schema": {
            "$ref": "#/definitions/ValidationErrorResponse"
          }
        },
        "500": {
          "description": "Server error. See return for details.",
          "schema": {
            "$ref": "#/definitions/ErrorResponse"
          }
        }
      }
    }
  }
}

Please note that the first response has no schema. In the generated c# code, for 400 and 500 return types, this line was generated:

 _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject<**object**>(_responseContent, Client.DeserializationSettings);

instead of <ValidationErrorResponse> or <ErrorResponse>.

As soon as I add schema info to the first response, the last few response deserialization code will be generated correctly (correct data type is being used).

Currently this bug forces me to return a bool or integer for PUT/PATCH so that result can be deserialized to correct data type. However I really don't feel comfortable to set a return type (schema) for DELETE. Can this be fixed?

C# ignoring x-ms-error-response

I am trying to generate a client that throws an error on certain types but the Autorest generator seems to be ignoring the
"x-ms-error-response": true
property set under the response types that should throw exceptions.

Is there anything additional needed on the swagger.json to let autorest know what responses to throw on?

C# JWT authentication improvement

Hello
I am wondering why it is necessary to initialize TokenCredentials with a string when using JWT tokens.

The following works ( where Track3 is my generated client )

     private static async Task<Track3API> MakeGoodClient()
        {
            var tokenRequest = MakeTokenRequest();
            var credentials = new TokenCredentials("bearer token");
            var uri = MakeUri();
            var tokenClient = new Track3API(uri, credentials);
            var tokenResponse = await tokenClient.ApiRequestTokenPostWithHttpMessagesAsync(tokenRequest);
            var tokenContent = await tokenResponse.Response.Content.ReadAsStringAsync();
            var tokenString = JObject.Parse(tokenContent).GetValue("token").ToString();
            var creds2 = new TokenCredentials(tokenString);
            var client2 = new Track3API(uri, creds2);
            return client2;
        }
        private static Uri MakeUri()
        {
            var uri = new Uri("https://localhost:44348", UriKind.Absolute);
            return uri;
        }

following does not

         private static async Task<Track3API> MakeBadClient()
        {
            var tokenRequest = MakeTokenRequest();
            var uri = MakeUri();
            var tc = new Track3API(uri);
            var tokenResponse = await tc.ApiRequestTokenPostWithHttpMessagesAsync(tokenRequest);
            var tokenContent = await tokenResponse.Response.Content.ReadAsStringAsync();
            var tokenString = JObject.Parse(tokenContent).GetValue("token").ToString();
            var creds2 = new TokenCredentials(tokenString);
            var client2 = new Track3API(uri, creds2);
            return client2;
        }

When my test calls it I get

`Test Name:	TestMethod1
Test FullName:	CoreClientTest.MyTests.TestMethod1
Test Source:	C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs : line 16
Test Outcome:	Failed
Test Duration:	0:00:02.1286112

Result StackTrace:	
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.Tasks.RendezvousAwaitable1.GetResult()
   at System.Net.Http.WinHttpHandler.<StartRequest>d__105.MoveNext()
--- End of inner exception stack trace ---
    at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Rest.RetryDelegatingHandler.<>c__DisplayClass11_0.<<SendAsync>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Rest.RetryDelegatingHandler.<SendAsync>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at swagger.Track3API.<ApiRequestTokenPostWithHttpMessagesAsync>d__49.MoveNext() in C:\dev\vivtrack3api\CoreClient\GeneratedOld\Track3API.cs:line 2998
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CoreClientTest.MyTests.<MakeBadClient>d__5.MoveNext() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 77
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CoreClientTest.MyTests.<GetMyJob>d__1.MoveNext() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 31
--- End of inner exception stack trace ---
    at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at CoreClientTest.MyTests.TestMethod1() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 25
Result Message:	
Test method CoreClientTest.MyTests.TestMethod1 threw exception: 
System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.Http.WinHttpException: A connection with the server could not be established
Result StandardOutput:	
System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.Http.WinHttpException: A connection with the server could not be established
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.Tasks.RendezvousAwaitable`1.GetResult()
   at System.Net.Http.WinHttpHandler.<StartRequest>d__105.MoveNext()
   --- End of inner exception stack trace ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Rest.RetryDelegatingHandler.<>c__DisplayClass11_0.<<SendAsync>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Rest.RetryDelegatingHandler.<SendAsync>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at swagger.Track3API.<ApiRequestTokenPostWithHttpMessagesAsync>d__49.MoveNext() in C:\dev\vivtrack3api\CoreClient\GeneratedOld\Track3API.cs:line 2998
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CoreClientTest.MyTests.<MakeBadClient>d__5.MoveNext() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 77
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CoreClientTest.MyTests.<GetMyJob>d__1.MoveNext() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 31
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at CoreClientTest.MyTests.TestMethod1() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 20
---> (Inner Exception #0) System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.Http.WinHttpException: A connection with the server could not be established
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.Tasks.RendezvousAwaitable`1.GetResult()
   at System.Net.Http.WinHttpHandler.<StartRequest>d__105.MoveNext()
   --- End of inner exception stack trace ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Rest.RetryDelegatingHandler.<>c__DisplayClass11_0.<<SendAsync>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Rest.RetryDelegatingHandler.<SendAsync>d__11.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at swagger.Track3API.<ApiRequestTokenPostWithHttpMessagesAsync>d__49.MoveNext() in C:\dev\vivtrack3api\CoreClient\GeneratedOld\Track3API.cs:line 2998
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CoreClientTest.MyTests.<MakeBadClient>d__5.MoveNext() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 77
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CoreClientTest.MyTests.<GetMyJob>d__1.MoveNext() in C:\dev\vivtrack3api\CoreClientTest\UnitTest1.cs:line 31<---

Serializing anonymous objects correctly - which raises fundamental questions

Current behavior of the ClientRuntime

Serialize every property that has a public setter. Reasoning behind that: something marked as readOnly in Swagger is only ever received from the server, but not sent - so we only give them a private setter since there is no point for users to ever set them (might give illusion that value would be sent to server). The client runtime picked up this very "encoding" of intent in order to derive whether or not to serialize.

Problems

  • super obscure: we had huge fails in the past since we were not aware of the implicit behavior tied to the setters visibility. Most notably, we changed setter visibility from private to protected once (with valid intentions) and completely missed the fact that we just broke a load of SDKs
  • people cannot use anonymous objects: the C# compiler generates types with non-writable properties - so if there's an SDK with an object parameter/property, all we serialize for an anonymous object is {} - WHOOPS.

Fix 1

It's a one line fix in the ClientRuntime to check the declaring type of a property for being anonymous - in that case we would serialize it, regardless of accessors. But then again, that only adds to the obscureness and load of stuff people will one day pull out their hair for.

Fix 2

Redo that part of serialization, create an explicit (and Newtonsoft.JSON independent) [DoNotSerialize] attribute. Only look for that when deciding whether or not to serialize. Let generators put it on everything that's not supposed to be sent. That way, anonymous objects will automatically work as expected, and we're no longer risking to break anything when touching access modifiers.

Important note: We can't ever change the behavior of the current ContractResolver (that does the modifier magic), as otherwise old SDKs (that won't have [DoNotSerialize]) won't work with new client runtimes! So instead, we gotta create a new, simpler ContractResolver and generate serialization settings pointing to that instead of the old one. That way, backwards compatibility is maintained.

@shahabhijeet @hovsepm FYI

Initialize method generated does not understand Relative URL and throws exception (Csharp)

When generating from a swagger document that uses a relative URL for host, the initialize sets the base URL without saying that it should be UriKind.Relative or possibly UriKind.RelativeOrAbsolute

This causes an exception ""Invalid URI: The format of the URI could not be determined."

private void Initialize()
{
BaseUri = new System.Uri("/monarch/mapping");
..
}

should be:

private void Initialize()
{
BaseUri = new System.Uri("/monarch/mapping",UriKind.RelativeOrAbsolute);
..
}

fix Azure/autorest#2746 seems to be linked to this
as does Azure/autorest.modeler#40 by @olydis

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

Hello,
I get this error - "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory" when trying to generate a C# client for a swagger json using autorest..

Here are more details of the error:

<--- Last few GCs --->
[24540:000001C541A2B010] 490246 ms: Mark-sweep 1400.7 (1445.6) -> 1400.7 (1445.6) MB, 1555.0 / 0.0 ms last resort GC in old space requested

<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 00000200ADE25EC1
1: IdentitySourceMapping_ _[C:\Users\vsharma\AppData\Local\[email protected][email protected]\[email protected]\autorest-core\dist\lib\source-map\merging.js:~172] [pc=00000253338F84E2](this=00000068CE70D799 ,sourceYamlFileName=000003949696E199 <String[28]: mem:///60?code-model-v1.yaml>,sourceYamlAst=000003607D1FB309 <Object map =...

The autorest command I gave was:
autorest --input-file=restapi.json --csharp --output-folder=D:\Output\ --namespace=ApiClient --debug

I've used Autorest in the past to generate C# client but the json I used for that was smaller than the one I'm using now.. Its of 79350 lines json. Earlier, when I was successfully able to generate c# code, my json was of 58861 lines.

Has anyone run into this error before?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.