.NET Core 2.1 WebAPI 根据Swagger.json自动生成客户端代码

2018 年 7 月 17 日 DotNet

(点击上方蓝字,可快速关注我们)


来源:长沙大鹏

cnblogs.com/hunanzp/p/9297361.html


前言


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,哥都可以直接给他代码生成器生成的代码,一个字:爽。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

登录查看更多
0

相关内容

【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
190+阅读 · 2020年6月29日
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
56+阅读 · 2020年6月26日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
160+阅读 · 2020年5月14日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
Keras作者François Chollet推荐的开源图像搜索引擎项目Sis
专知会员服务
29+阅读 · 2019年10月17日
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
Linux挖矿病毒的清除与分析
FreeBuf
14+阅读 · 2019年4月15日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
基于Web页面验证码机制漏洞的检测
FreeBuf
7+阅读 · 2019年3月15日
Python | Jupyter导出PDF,自定义脚本告别G安装包
程序人生
7+阅读 · 2018年7月17日
Feature Selection Library (MATLAB Toolbox)
Arxiv
7+阅读 · 2018年8月6日
Bidirectional Attention for SQL Generation
Arxiv
4+阅读 · 2018年6月21日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
98+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
190+阅读 · 2020年6月29日
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
56+阅读 · 2020年6月26日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
160+阅读 · 2020年5月14日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
Keras作者François Chollet推荐的开源图像搜索引擎项目Sis
专知会员服务
29+阅读 · 2019年10月17日
相关资讯
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
Linux挖矿病毒的清除与分析
FreeBuf
14+阅读 · 2019年4月15日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
基于Web页面验证码机制漏洞的检测
FreeBuf
7+阅读 · 2019年3月15日
Python | Jupyter导出PDF,自定义脚本告别G安装包
程序人生
7+阅读 · 2018年7月17日
Top
微信扫码咨询专知VIP会员