前言 上一篇博客中我們可以得知通過Swagger插件可以很方便的提供給介面開發者線上調試,但是實際上Swagger附帶的功能還有很多, 比如使用NSwag生成客戶端調用代碼,進一步解放介面開發者。 NSwag NSwag是一個發佈在GitHub上的開源項目,它可以根據Swagger說明頁上的swag ...
前言
上一篇博客中我們可以得知通過Swagger插件可以很方便的提供給介面開發者線上調試,但是實際上Swagger附帶的功能還有很多,
比如使用NSwag生成客戶端調用代碼,進一步解放介面開發者。
NSwag
NSwag是一個發佈在GitHub上的開源項目,它可以根據Swagger說明頁上的swagger.json文件生成C#、TypeScript客戶端代碼。
NSwag的項目地址:https://github.com/RSuter/NSwag
Nswag提供4種代碼生成方法
1、使用 NSwagStudio,這是一款 Windows 桌面應用,用於在 C# 和 TypeScript 中為 API 生成客戶端代碼。
2、使用 NSwag.CodeGeneration.CSharp 或 NSwag.CodeGeneration.TypeScript NuGet 包在項目中執行代碼生成。
3、使用命令行中的 NSwag。
4、使用 NSwag.MSBuild NuGet 包。
這裡推薦使用NSwagStudio,可以從GitHub上下載該工具,地址:https://github.com/RSuter/NSwag/wiki/NSwagStudio
下載後開始安裝,安裝完畢後打開NSwagStudio,如下圖,在左側選擇Tab頁菜單里選擇Documents
如上圖框框選中的幾點,我們需要留意。其中Swagger Specification URL就是我們WebAPI的swagger.json的線上地址。
如果點擊【Create local Copy 】按鈕時你的WebAPI未線上則代碼生成工具會彈出錯誤對話框,如下圖:
所以採取讀取Swagger Specification URL 方式進行生成代碼的前提條件是你必須保證swagger.json文件能線上讀取!
其次你可以選擇RunTime(運行時),這裡應該是服務端WebAPI的運行時(畢竟TypeScript是不關心你服務端是.NET Core還是.NET Framework).
因為我的環境是.NET Core2.1,所以這裡選擇NET Core21。
接下來,點擊【Create local Copy】按鈕,點擊後NSwagStudio會與WebAPI服務端進行交互,成功後會將swagger.json文件格式化到左邊的文本編輯器中,如下圖:
此時,你可以在右側的Outputs中勾選你需要輸出的文件格式,這裡我選擇TypeScript和CSharp Client,
這個地方有個C# WebAPI Controller,我有點納悶,我都有swagger.json文件了絕壁是已經存在webapi了,
沒有必要反向再去生成一遍webapi的控制器啦。 不懂,反正只管生成客戶端代碼就好。
我們勾選好後下麵就會出現相應的輸出配合頁面,如下圖:
我們選擇CSharp Client頁面,該頁面左側分為Setting和Output兩個頁面,Setting頁可以對輸出的cs文件進行配置,如命名空間、類名稱、輸出文件路徑等等(很多配置我也不會....)
我們點擊【Generate Outputs】後NSwagStudio會根據配置生成客戶端操作類,在Output頁面即可檢查,檢查無誤後再點擊【Generate Files】可將類文件導出到配置的輸出目錄。
TypeScript亦是如此,同時NSwagStudio也可支持載入DLL反射生成,具體方法可根據官網操作(畢竟可以直接使用json文件線上生成沒必要再自己手工選擇dll..)
這裡截圖看SwagerUI頁和NSwagStudio生成後的客戶端cs文件
生成的客戶端C#代碼:
//---------------------- // <auto-generated> // Generated using the NSwag toolchain v11.17.19.0 (NJsonSchema v9.10.58.0 (Newtonsoft.Json v9.0.0.0)) (http://NSwag.org) // </auto-generated> //---------------------- namespace Test { #pragma warning disable // Disable all warnings [System.CodeDom.Compiler.GeneratedCode("NSwag", "11.17.19.0 (NJsonSchema v9.10.58.0 (Newtonsoft.Json v9.0.0.0))")] public partial class Client { private string _baseUrl = "http://localhost:58985"; private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings; public Client() { _settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(() => { var settings = new Newtonsoft.Json.JsonSerializerSettings(); UpdateJsonSerializerSettings(settings); return settings; }); } public string BaseUrl { get { return _baseUrl; } set { _baseUrl = value; } } protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// <summary>巴拉巴拉</summary> /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> public System.Threading.Tasks.Task ApiTestGetAsync() { return ApiTestGetAsync(System.Threading.CancellationToken.None); } /// <summary>巴拉巴拉</summary> /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> public async System.Threading.Tasks.Task ApiTestGetAsync(System.Threading.CancellationToken cancellationToken) { var urlBuilder_ = new System.Text.StringBuilder(); urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Test"); var client_ = new System.Net.Http.HttpClient(); try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { request_.Method = new System.Net.Http.HttpMethod("GET"); PrepareRequest(client_, request_, urlBuilder_); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); PrepareRequest(client_, request_, url_); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); try { var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); if (response_.Content != null && response_.Content.Headers != null) { foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; } ProcessResponse(client_, response_); var status_ = ((int)response_.StatusCode).ToString(); if (status_ == "200") { return; } else if (status_ != "200" && status_ != "204") { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); } } finally { if (response_ != null) response_.Dispose(); } } } finally { if (client_ != null) client_.Dispose(); } } /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> public System.Threading.Tasks.Task ApiTestPostAsync(string value) { return ApiTestPostAsync(value, System.Threading.CancellationToken.None); } /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> public async System.Threading.Tasks.Task ApiTestPostAsync(string value, System.Threading.CancellationToken cancellationToken) { var urlBuilder_ = new System.Text.StringBuilder(); urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Test"); var client_ = new System.Net.Http.HttpClient(); try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(value, _settings.Value)); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("multipart/form-data"); request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); PrepareRequest(client_, request_, urlBuilder_); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); PrepareRequest(client_, request_, url_); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); try { var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); if (response_.Content != null && response_.Content.Headers != null) { foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; } ProcessResponse(client_, response_); var status_ = ((int)response_.StatusCode).ToString(); if (status_ == "200") { return; } else if (status_ != "200" && status_ != "204") { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); } } finally { if (response_ != null) response_.Dispose(); } } } finally { if (client_ != null) client_.Dispose(); } } /// <summary>我是get測試</summary> /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> public System.Threading.Tasks.Task<string> ApiTestByIdGetAsync(int id) { return ApiTestByIdGetAsync(id, System.Threading.CancellationToken.None); } /// <summary>我是get測試</summary> /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> public async System.Threading.Tasks.Task<string> ApiTestByIdGetAsync(int id, System.Threading.CancellationToken cancellationToken) { if (id == null) throw new System.ArgumentNullException("id"); var urlBuilder_ = new System.Text.StringBuilder(); urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Test/{id}"); urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); var client_ = new System.Net.Http.HttpClient(); try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { request_.Method = new System.Net.Http.HttpMethod("GET"); request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); PrepareRequest(client_, request_, urlBuilder_); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); PrepareRequest(client_, request_, url_); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); try { var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); if (response_.Content != null && response_.Content.Headers != null) { foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; } ProcessResponse(client_, response_); var status_ = ((int)response_.StatusCode).ToString(); if (status_ == "200") { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); var result_ = default(string); try { result_ = Newtonsoft.Json.JsonConvert.DeserializeObject<string>(responseData_, _settings.Value); return result_; } catch (System.Exception exception_) { throw new SwaggerException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); } } else if (status_ != "200" && status_ != "204") { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); } return default(string); } finally { if (response_ != null) response_.Dispose(); } } } finally { if (client_ != null) client_.Dispose(); } } /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> public System.Threading.Tasks.Task ApiTestByIdPutAsync(int id, string value) { return ApiTestByIdPutAsync(id, value, System.Threading.CancellationToken.None); } /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> public async System.Threading.Tasks.Task ApiTestByIdPutAsync(int id, string value, System.Threading.CancellationToken cancellationToken) { if (id == null) throw new System.ArgumentNullException("id"); var urlBuilder_ = new System.Text.StringBuilder(); urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Test/{id}"); urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); var client_ = new System.Net.Http.HttpClient(); try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(value, _settings.Value)); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("multipart/form-data"); request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("PUT"); PrepareRequest(client_, request_, urlBuilder_); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); PrepareRequest(client_, request_, url_); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); try { var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); if (response_.Content != null && response_.Content.Headers != null) { foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; } ProcessResponse(client_, response_); var status_ = ((int)response_.StatusCode).ToString(); if (status_ == "200") { return; } else if (status_ != "200" && status_ != "204") { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); } } finally { if (response_ != null) response_.Dispose(); } } } finally { if (client_ != null) client_.Dispose(); } } /// <summary>小魔仙</summary> /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> public System.Threading.Tasks.Task ApiTestByIdDeleteAsync(int id) { return ApiTestByIdDeleteAsync(id, System.Threading.CancellationToken.None); } /// <summary>小魔仙</summary> /// <returns>Success</returns> /// <exception cref="SwaggerException">A server side error occurred.</exception> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> public async System.Threading.Tasks.Task ApiTestByIdDeleteAsync(int id, System.Threading.CancellationToken cancellationToken) { if (id == null) throw new System.ArgumentNullException("id"); var urlBuilder_ = new System.Text.StringBuilder(); urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/Test/{id}"); urlBuilder_.Replace("{id}", System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); var client_ = new System.Net.Http.HttpClient(); try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { request_.Method = new System.Net.Http.HttpMethod("DELETE"); PrepareRequest(client_, request_, urlBuilder_); var url_ = urlBuilder_.ToString(); request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); PrepareRequest(client_, request_, url_); var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); try { var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); if (response_.Content != null && response_.Content.Headers != null) { foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; } ProcessResponse(client_, response_); var status_ = ((int)response_.StatusCode).ToString(); if (status_ == "200") { return; } else if (status_ != "200" && status_ != "204") { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); } } finally { if (response_ != null) response_.Dispose(); } } } finally { if (client_ != null) client_.Dispose(); } } private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) { if (value is System.Enum) { string name = System.Enum.GetName(value.GetType(), value); if (name != null) { var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); if (field != null) { var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) as System.Runtime.Serialization.EnumMemberAttribute; if (attribute != null) { return attribute.Value; } } } } else if (value is byte[]) { return System.Convert.ToBase64String((byte[]) value); } else if (value != null && value.GetType().IsArray) { var array = System.Linq.Enumerable.OfType<object>((System.Array) value); return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); } return System.Convert.ToString(value, cultureInfo); } } [System.CodeDom.Compiler.GeneratedCode("NSwag", "11.17.19.0 (NJsonSchema v9.10.58.0 (Newtonsoft.Json v9.0.0.0))")] public partial class SwaggerException : System.Exception { public int StatusCode { get; private set; } public string Response { get; private set; } public System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>> Headers { get; private set; } public SwaggerException(string message, int statusCode, string response, System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Exception innerException) : base(message, innerException) { StatusCode = statusCode; Response = response; Headers = headers; } public override string ToString() { return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); } } [System.CodeDom.Compiler.GeneratedCode("NSwag", "11.17.19.0 (NJsonSchema v9.10.58.0 (Newtonsoft.Json v9.0.0.0))")] public partial class SwaggerException<TResult> : SwaggerException { public TResult Result { get; private set; } public SwaggerException(string message, int statusCode, string response, System.Collections.Generic.Dictionary<string, System.Collections.Generic.IEnumerable<string>> headers, TResult result, System.Exception innerException) : base(message, statusCode, response, headers, innerException) { Result = result; } } }
結語
不得不贊嘆Swagger的強大,它的出現解放了多少程式員啊,後續但凡有客戶端碼農說不會調用哥的API,哥都可以直接給他代碼生成器生成的代碼,一個字:爽。