Skip to content

Commit

Permalink
[csharp-netcore] Adding generic host library (#10627)
Browse files Browse the repository at this point in the history
* added generichost library

* added templates

* added an event, improved docs, added logging

* adding event args file

* fixed hard coded package name

* added an AddTokens overload for a single token

* changed api clients to singletons to support the event registration

* build samples

* log exceptions while executing api responded event

* nrt bug fixes, dangling comma fix

* resolving comments

* removed debugging lines

* refactored token provider

* rate limit provider now default

* updated readme, added ConfigureAwait(false)

* DI fixes

* removed a hard coded project name

* fixed nrt bugs

* improved NRT and .net 3.1 support

* renamed projectName to apiName, added cli option

* trying to avoid conflict

* set GenerateAssemlbyInfo to true

* created docs/scripts folder

* moved ApiTestsBase.cs to not get overwritten

* test fixes and improvements

* fixed licenseId bug, updated readme

* build samples

* export docs

* removed new language features

* added support for .net standard 2.0

* added git_push.ps1

* fixed bug in git_push.sh due to the new directory, prompting user for commit message

* moved documentation folders

* fixed bug when apiKey in query

* bug fix
  • Loading branch information
devhl-labs committed Jan 28, 2022
1 parent aed513f commit 24366be
Show file tree
Hide file tree
Showing 40 changed files with 3,011 additions and 29 deletions.
3 changes: 2 additions & 1 deletion docs/generators/csharp-netcore.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ These options may be applied as additional-properties (cli) or configOptions (pl
| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|apiName|Must be a valid C# class name. Only used in Generic Host library. Default: Api| |Api|
|caseInsensitiveResponseHeaders|Make API response's headers case-insensitive| |false|
|conditionalSerialization|Serialize only those properties which are initialized by user, accepted values are true or false, default value is false.| |false|
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
|interfacePrefix|Prefix interfaces with a community standard or widely accepted prefix.| |I|
|library|HTTP library template (sub-template) to use|<dl><dt>**httpclient**</dt><dd>HttpClient (https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient) (Experimental. May subject to breaking changes without further notice.)</dd><dt>**restsharp**</dt><dd>RestSharp (https://github.com/restsharp/RestSharp)</dd></dl>|restsharp|
|library|HTTP library template (sub-template) to use|<dl><dt>**generichost**</dt><dd>HttpClient with Generic Host dependency injection (https://docs.microsoft.com/en-us/dotnet/core/extensions/generic-host) (Experimental. Subject to breaking changes without notice.)</dd><dt>**httpclient**</dt><dd>HttpClient (https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient) (Experimental. Subject to breaking changes without notice.)</dd><dt>**restsharp**</dt><dd>RestSharp (https://github.com/restsharp/RestSharp)</dd></dl>|restsharp|
|licenseId|The identifier of the license| |null|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |PascalCase|
|netCoreProjectFile|Use the new format (.NET Core) for .NET project files (.csproj).| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class CodegenConstants {
public static final String SKIP_FORM_MODEL = "skipFormModel";
/* /end System Properties */

public static final String API_NAME = "apiName";

public static final String API_PACKAGE = "apiPackage";
public static final String API_PACKAGE_DESC = "package for generated api classes";

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Name | Type | Description | Notes
{{/responses}}
{{/responses.0}}

[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
[[Back to top]](#) [[Back to API list]](../{{#useGenericHost}}../{{/useGenericHost}}README.md#documentation-for-api-endpoints) [[Back to Model list]](../{{#useGenericHost}}../{{/useGenericHost}}README.md#documentation-for-models) [[Back to README]](../{{#useGenericHost}}../{{/useGenericHost}}README.md)

{{/operation}}
{{/operations}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <auto-generated>
{{>partial_header}}
{{#nullableReferenceTypes}}#nullable enable{{/nullableReferenceTypes}}

using System;

namespace {{packageName}}.Client
{
/// <summary>
/// API Exception
/// </summary>
{{>visibility}} class ApiException : Exception
{
/// <summary>
/// The reason the api request failed
/// </summary>
public string{{#nullableReferenceTypes}}?{{/nullableReferenceTypes}} ReasonPhrase { get; }

/// <summary>
/// The HttpStatusCode
/// </summary>
public System.Net.HttpStatusCode StatusCode { get; }

/// <summary>
/// The raw data returned by the api
/// </summary>
public string RawContent { get; }

/// <summary>
/// Construct the ApiException from parts of the reponse
/// </summary>
/// <param name="reasonPhrase"></param>
/// <param name="statusCode"></param>
/// <param name="rawContent"></param>
public ApiException(string{{#nullableReferenceTypes}}?{{/nullableReferenceTypes}} reasonPhrase, System.Net.HttpStatusCode statusCode, string rawContent) : base(reasonPhrase ?? rawContent)
{
ReasonPhrase = reasonPhrase;
StatusCode = statusCode;
RawContent = rawContent;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// <auto-generated>
{{partial_header}}
{{#nullableReferenceTypes}}#nullable enable{{/nullableReferenceTypes}}

using System;

namespace {{packageName}}.Client
{
/// <summary>
/// A token constructed from an apiKey.
/// </summary>
public class ApiKeyToken : TokenBase
{
private string _raw;
/// <summary>
/// Constructs an ApiKeyToken object.
/// </summary>
/// <param name="value"></param>
/// <param name="prefix"></param>
/// <param name="timeout"></param>
public ApiKeyToken(string value, string prefix = "Bearer ", TimeSpan? timeout = null) : base(timeout)
{
_raw = $"{ prefix }{ value }";
}

/// <summary>
/// Places the token in the cookie.
/// </summary>
/// <param name="request"></param>
/// <param name="cookieName"></param>
public virtual void UseInCookie(System.Net.Http.HttpRequestMessage request, string cookieName)
{
request.Headers.Add("Cookie", $"{ cookieName }=_raw");
}

/// <summary>
/// Places the token in the header.
/// </summary>
/// <param name="request"></param>
/// <param name="headerName"></param>
public virtual void UseInHeader(System.Net.Http.HttpRequestMessage request, string headerName)
{
request.Headers.Add(headerName, _raw);
}

/// <summary>
/// Places the token in the query.
/// </summary>
/// <param name="request"></param>
/// <param name="uriBuilder"></param>
/// <param name="parseQueryString"></param>
/// <param name="parameterName"></param>
public virtual void UseInQuery(System.Net.Http.HttpRequestMessage request, UriBuilder uriBuilder, System.Collections.Specialized.NameValueCollection parseQueryString, string parameterName)
{
parseQueryString[parameterName] = Uri.EscapeDataString(_raw).ToString(){{#nullableReferenceTypes}}!{{/nullableReferenceTypes}};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Net;

namespace {{packageName}}.Client
{
/// <summary>
/// Useful for tracking server health.
/// </summary>
public class ApiResponseEventArgs : EventArgs
{
/// <summary>
/// The time the request was sent.
/// </summary>
public DateTime RequestedAt { get; }
/// <summary>
/// The time the response was received.
/// </summary>
public DateTime ReceivedAt { get; }
/// <summary>
/// The HttpStatusCode received.
/// </summary>
public HttpStatusCode HttpStatus { get; }
/// <summary>
/// The path requested.
/// </summary>
public string Path { get; }
/// <summary>
/// The elapsed time from request to response.
/// </summary>
public TimeSpan ToTimeSpan => this.ReceivedAt - this.RequestedAt;

/// <summary>
/// The event args used to track server health.
/// </summary>
/// <param name="requestedAt"></param>
/// <param name="receivedAt"></param>
/// <param name="httpStatus"></param>
/// <param name="path"></param>
public ApiResponseEventArgs(DateTime requestedAt, DateTime receivedAt, HttpStatusCode httpStatus, string path)
{
RequestedAt = requestedAt;
ReceivedAt = receivedAt;
HttpStatus = httpStatus;
Path = path;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// <auto-generated>
{{>partial_header}}
{{#nullableReferenceTypes}}#nullable enable{{/nullableReferenceTypes}}

using System;
using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;

namespace {{packageName}}.Client
{
/// <summary>
/// Provides a non-generic contract for the ApiResponse wrapper.
/// </summary>
public interface IApiResponse
{
/// <summary>
/// The data type of <see cref="Data"/>
/// </summary>
Type ResponseType { get; }

/// <summary>
/// Gets or sets the status code (HTTP status code)
/// </summary>
/// <value>The status code.</value>
HttpStatusCode StatusCode { get; }

/// <summary>
/// The raw content of this response
/// </summary>
string RawContent { get; }
}

/// <summary>
/// API Response
/// </summary>
{{>visibility}} partial class ApiResponse<T> : IApiResponse
{
#region Properties
/// <summary>
/// The deserialized content
/// </summary>
{{! .net 3.1 does not support unconstrained nullable T }}
public T{{#nullableReferenceTypes}}{{^netcoreapp3.1}}?{{/netcoreapp3.1}}{{/nullableReferenceTypes}} Content { get; set; }

/// <summary>
/// Gets or sets the status code (HTTP status code)
/// </summary>
/// <value>The status code.</value>
public HttpStatusCode StatusCode { get; }

/// <summary>
/// The content of this response
/// </summary>
public Type ResponseType
{
get { return typeof(T); }
}

/// <summary>
/// The raw data
/// </summary>
public string RawContent { get; }

/// <summary>
/// The IsSuccessStatusCode from the api response
/// </summary>
public bool IsSuccessStatusCode { get; }

/// <summary>
/// The reason phrase contained in the api response
/// </summary>
public string{{#nullableReferenceTypes}}?{{/nullableReferenceTypes}} ReasonPhrase { get; }

/// <summary>
/// The headers contained in the api response
/// </summary>
public System.Net.Http.Headers.HttpResponseHeaders Headers { get; }

#endregion Properties

/// <summary>
/// Construct the reponse using an HttpResponseMessage
/// </summary>
/// <param name="response"></param>
/// <param name="rawContent"></param>
public ApiResponse(System.Net.Http.HttpResponseMessage response, string rawContent)
{
StatusCode = response.StatusCode;
Headers = response.Headers;
IsSuccessStatusCode = response.IsSuccessStatusCode;
ReasonPhrase = response.ReasonPhrase;
RawContent = rawContent;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{{>partial_header}}
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Microsoft.Extensions.Hosting;
using {{packageName}}.Client;{{#hasImport}}
using {{packageName}}.{{modelPackage}};{{/hasImport}}


{{{testInstructions}}}


namespace {{packageName}}.Test.Api
{
/// <summary>
/// Base class for API tests
/// </summary>
public class ApiTestsBase
{
protected readonly IHost _host;
public ApiTestsBase(string[] args)
{
_host = CreateHostBuilder(args).Build();
}

public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args)
.Configure{{apiName}}((context, options) =>
{
{{#hasApiKeyMethods}}ApiKeyToken apiKeyToken = new ApiKeyToken(context.Configuration["<token>"], timeout: TimeSpan.FromSeconds(1));
options.AddTokens(apiKeyToken);
{{/hasApiKeyMethods}}{{#hasHttpBearerMethods}}
BearerToken bearerToken = new BearerToken(context.Configuration["<token>"], timeout: TimeSpan.FromSeconds(1));
options.AddTokens(bearerToken);
{{/hasHttpBearerMethods}}{{#hasHttpBasicMethods}}
BasicToken basicToken = new BasicToken(context.Configuration["<username>"], context.Configuration["<password>"], timeout: TimeSpan.FromSeconds(1));
options.AddTokens(basicToken);
{{/hasHttpBasicMethods}}{{#hasHttpSignatureMethods}}
HttpSigningConfiguration config = new HttpSigningConfiguration("<keyId>", "<keyFilePath>", null, new List<string>(), HashAlgorithmName.SHA256, "<signingAlgorithm>", 0);
HttpSignatureToken httpSignatureToken = new HttpSignatureToken(config, timeout: TimeSpan.FromSeconds(1));
options.AddTokens(httpSignatureToken);
{{/hasHttpSignatureMethods}}{{#hasOAuthMethods}}
OAuthToken oauthToken = new OAuthToken(context.Configuration["<token>"], timeout: TimeSpan.FromSeconds(1));
options.AddTokens(oauthToken);{{/hasOAuthMethods}}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <auto-generated>
{{partial_header}}
{{#nullableReferenceTypes}}#nullable enable{{/nullableReferenceTypes}}

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace {{packageName}}.Client
{
/// <summary>
/// A token constructed from a username and password.
/// </summary>
public class BasicToken : TokenBase
{
private string _username;
private string _password;
/// <summary>
/// Constructs a BasicToken object.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="timeout"></param>
public BasicToken(string username, string password, TimeSpan? timeout = null) : base(timeout)
{
_username = username;
_password = password;
}

/// <summary>
/// Places the token in the header.
/// </summary>
/// <param name="request"></param>
/// <param name="headerName"></param>
public virtual void UseInHeader(System.Net.Http.HttpRequestMessage request, string headerName)
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", {{packageName}}.Client.ClientUtils.Base64Encode(_username + ":" + _password));
}
}
}
Loading

0 comments on commit 24366be

Please sign in to comment.