swagger-api / swagger-codegen

swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition.
http://swagger.io
Apache License 2.0
16.91k stars 6.03k forks source link

C# Client - Unable to dynamically modify User-Agent at runtime #4961

Closed MMirabito closed 7 years ago

MMirabito commented 7 years ago

I am noticing that when I try to modify the UserAgent in a C# client it’s not sticking, it's always reverting back to the original generated value "Swagger-Codegen/1.0.0/csharp"

Configuration con = new Configuration(); con.ApiClient = new ApiClient("http://localhost:8080/MyService/api"); con.AddDefaultHeader("Authorization", "Basic xxxxxxxxxxxxxx");

Debug.Print(con.UserAgent); // --> Swagger-Codegen/1.0.0/csharp con.UserAgent = "MyAgent"; var apiClient = new MyService(con); Debug.Print(con.UserAgent); // --> MyAgent

As I am tracing through the code I belie the problem might be inside the ApiClient class in the CallApi method. I am thinking that the RestClinet.UserAgent is referencing the Class Configuration.UserAgent instead of the instance that was passed to the MyService(con)?

   public Object CallApi(
        String path, RestSharp.Method method, Dictionary<String, String> queryParams, Object postBody,
        Dictionary<String, String> headerParams, Dictionary<String, String> formParams,
        Dictionary<String, FileParameter> fileParams, Dictionary<String, String> pathParams,
        String contentType)
    {
        var request = PrepareRequest(
            path, method, queryParams, postBody, headerParams, formParams, fileParams,
            pathParams, contentType);

        // set timeout
        RestClient.Timeout = Configuration.Timeout;
        // set user agent
        RestClient.UserAgent = Configuration.UserAgent;

        InterceptRequest(request);
        var response = RestClient.Execute(request);
        InterceptResponse(request, response);

        return (Object) response;
    }

Thanks in advance for any guidance max

wing328 commented 7 years ago

@MMirabito thanks for reporting the issue. As a workaround, what about using the following option to hard code the user agent in the auto-generated C# code?

        --http-user-agent <http user agent>
            HTTP user agent, e.g. codegen_csharp_api_client, default to
            'Swagger-Codegen/{packageVersion}}/{language}'
MMirabito commented 7 years ago

@wing328 sorry for my delayed response I was "sequestered " to work on another project and today I finally was able to pull away. I pulled V2.2.3 and did an mvn build but the same issue appears to be persisting the user-agent is not being updated if I set it within the configuration object at runtime – am I doing something wrong?

I tried --http-user-agent and its working. However, what we would really like to do is to be able to set the user-agent dynamically. The reason is that in our use-case we generate the C# client and redistribute the client as a DLL. Then applications within our organization are using the DLL to invoke the services and it would be more flexible in our opinion if we could just let the application consumers set it at runtime – the same way we do for our Java clients.

Thanks again, max

MMirabito commented 7 years ago

As follow as I looked at the code generated in c# the constructor of MyServiceApi that accepts the configuration object I noticed the following:

  1. The else statement distinctly sets the configuration: this.Configuration = configuration but the CallApi() as indicated in my initial post does not appear to be using that instance

  2. When I add this.Configuration.ApiClient.Configuration = this.Configuration as part of the else statement (in bold below) then the user-agent is now modified at runtime when I run it. However, I am not sure what side effect this may cause.

` public MyServiceApi(Configuration configuration = null) { if (configuration == null) // use the default one in Configuration this.Configuration = Configuration.Default; else { this.Configuration = configuration; this.Configuration.ApiClient.Configuration = this.Configuration; } ExceptionFactory = Io.Rs.User.Client.Configuration.DefaultExceptionFactory; // ensure API client has configuration ready if (Configuration.ApiClient.Configuration == null) { this.Configuration.ApiClient.Configuration = this.Configuration; } }

` Anyway I just wanted to share what I found while debugging.

Thanks again, max

MMirabito commented 7 years ago

just another note for anyone else that might be curious you can also set the user-agent in a config.json file then pass the name of the file on the command line. My config.json is a flows:

` { "sortParamsByRequiredFlag": true,

"packageName": "Io.Rs.MyService",

"httpUserAgent": "MyServiceConsumerName" } `

jimschubert commented 7 years ago

It looks like this is related to the Configuration.Default issue that I've been working to remove via #2858. The open PR (#3530) against that issue needs to be updated against current master.

wing328 commented 7 years ago

@MMirabito can you please try #3530 to see if it addresses the issue for you? Let me know if you need steps for testing the PR.

MMirabito commented 7 years ago

wing328 unless I am doing something wrong it does not appear to resolve the issue. As I was debugging the C# client generated with the 2.3.0 CLI I noticed

I am wondering what I am doing wrong this how I call the API via the C# client `

        madmax.User.Client.Configuration con = new madmax.User.Client.Configuration();

        con.ApiClient = new madmax.User.Client.ApiClient("http://localhost:8080/UserService/api");
        con.AddDefaultHeader("Authorization", authorizationToken);

        con.UserAgent = "MadMax";
        var apiClient = new madmax.User.Api.UserProfileApi(con);

        Debug.Print("+ ===================================================== +");
        Debug.Print("+ Ping                                                  +");
        Debug.Print("+ ===================================================== +");
        Debug.WriteLine(apiClient.Ping());

`

For the time being I have a workaround by adding my custom header and forgo using the UserAgent
con.AddDefaultHeader("service-consumer", consumerName);

it's just weird because in the Java client I can set the userAgent at runtime and it sticks.

BTW I downloaded the source tagged 2.3.0 then built it using MVN and grabbed the CLI jars that was generated, is that the correct way of doing it?

Thanks, max

jimschubert commented 7 years ago

@MMirabito I rebased the previously mentioned branch against 2.3.0 and opened a new PR. Would you mind checking out #5740?

The refactoring I did should remove all questions about the Configuration object and what/when/where it is being used. I introduced a new partial GlobalConfiguration, so you can define shared configuration at compile time. This is only exposed via Configuration.Default.

The problem with ApiClient should be resolved now. Previously there were three constructors, two with defaulted parameters and one empty constructor. From my testing, it seemed non-deterministic which constructor was chosen on Mono and .NET for new ApiClient() instances. Now, it should be very clear that new ApiClient() uses the global configuration. new ApiClient(baseUrl) creates a new configuration pointing to that url, and new ApiClient(config) creates a client with per-request configuration.

Possibly a breaking change, but ApiClient.Configuration now returns IReadableConfiguration. Although this doesn't technically prevent users from resetting configuration properties in a threaded environment, it indicates the intention to the consumer that you should only get values and not set them.

I tried to cause as little needed code change to use this refactored code. Check out the changes to api classes in the PR to see the one or two places that should require changes.

wing328 commented 7 years ago

@MMirabito Please try the 2.3.0 branch (which has the C# API client refactored) and see if the same problem occurs. Thanks.