Development of Nail SDK Based on C# (1) --Reconstruction and optimization of official SDK

In the past time, contacting a customer who likes nailing and has widely used nails in the internal scene for the internal management of the factory, such as nail attendance, daily approval, nail projection, nail access control and so on, I realized that nails have been widely used in the enterprise, so I went back to study some business scope of nailing and its SDK opening. Work. Nail official SDK provides a lot of encapsulation, but compared with Java, the. NET version has been changing. Previous research on nail C # version of SDK found that some problems reflected to nail developers, basically can not be solved and responded to, and when using official SDK, some data can not be normally obtained (such as role information), and officials. When Fang's SDK was used, he felt that the code was bulky, so the idea of rebuilding the official SDK was born. This series of essays will analyze and reconstruct the scope of the nail SDK, and share the effect and fun in the use process.

1. Introduction of nails

DingTalk is a free communication and collaboration multi-terminal platform built by Alibaba Group for Chinese enterprises. It provides PC, Web and mobile versions to support file transfer between mobile phones and computers. Nail is a free mobile office platform built by Ali Group specially for Chinese enterprises to help them communicate more efficiently and safely.

 

2. Some problems or deficiencies in the use of official SDK nails

Generally, when we develop, we tend to use existing wheels instead of reinventing them. But if the wheel really doesn't fit or has a better idea, it's okay to put in some effort.

When using the original nail SDK, the following problems were found.

1) Some SDK data can not be serialized into normal attributes due to parameters or other problems, such as the information part of the corner Israel table (which was later fixed).

2) The code using SDK object is too bulky, some immobilized parameters need to be passed in during the use process, which is not necessary and adds a lot of calling code.

3) For the part of JSON serialization, the standard scheme of JSON.NET (Newtonsoft.Json.dll) is not adopted, but the self-defined JSON parsing class is used, which results in the complicated parsing process of the nail SDK.

4) The design of the whole nail SDK is too complex to be modified easily.

5) Other reasons for being unaccustomed

In order to avoid large-scale changes leading to changes in the use of the whole interface, I try to retain the use of nail interface in the reconstruction process, hoping that users can seamlessly dock the nail SDK interface I reconstructed, so I try to simplify the nail SDK design process, as far as possible compatible with the use of the interface.

And since I introduced Json.NET object standard serialization and deserialization, I found that the code really simplified a lot, which provided a very convenient refactoring work.

Let's compare the usage code of the original nail SDK interface with the usage code of the reconstructed nail SDK.

            IDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
            OapiGettokenRequest request = new OapiGettokenRequest();
            request.Corpid = corpid;
            request.Corpsecret = corpSecret;
            request.SetHttpMethod("GET");
            OapiGettokenResponse response = client.Execute(request);
            return response;

The above code is the usage code of the standard official SDK, an interface for obtaining token information.

In fact, when the DefaultDingTalkClient is initialized and ready to use OapiGettokenRequest to get the reply object, we can encapsulate the URL (https://oapi.dingtalk.com/gettoken) in the request. We can look for the URL when we don't need to use it, and when corresponding to OapiGettokenRequest request, the data submission method POST or GET should also be correct. It's settled, no need for users to set up better.

In the case of fewer user parameters, constructors can be used to pass in order to reduce the number of lines of code.

Then we can further reduce the number of lines of code invoked by extending the function.

Let's take a look at the call process after I refactored the code and simplify it to two lines of code:

            var request = new OapiGettokenRequest(corpid, corpSecret);
            var response = new DefaultDingTalkClient().Execute(request);

With the help of extension functions, we can also simplify it to a single line of code, as shown below.

var token = new OapiGettokenRequest(corpid, corpSecret).Execute();

For the first N lines of code, the effect is the same as the current one. That's what I want: simplicity is beautiful.

For multiple Request calls, we can also reuse the DingTalkClient object, as shown in the following code.

            var client = new DefaultDingTalkClient();

            var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
            var token = client.Execute(tokenRequest);

            if (token != null && !token.IsError)
            {
                string id = "1";
                var request = new OapiDepartmentListRequest(id);
                var dept = client.Execute(request, token.AccessToken);
                 ...................

Of course, because of the request object and the response object, I still retained the original object name, but used a JSON.NET-based way to re-process the definition of the object.

For example, for Token's request and reply object, the original Token reply object definition is as follows

    /// <summary>
    /// OapiGettokenResponse.
    /// </summary>
    public class OapiGettokenResponse : DingTalkResponse
    {
        /// <summary>
        /// access_token
        /// </summary>
        [XmlElement("access_token")]
        public string AccessToken { get; set; }

        /// <summary>
        /// errcode
        /// </summary>
        [XmlElement("errcode")]
        public long Errcode { get; set; }

        /// <summary>
        /// errmsg
        /// </summary>
        [XmlElement("errmsg")]
        public string Errmsg { get; set; }

        /// <summary>
        /// expires_in
        /// </summary>
        [XmlElement("expires_in")]
        public long ExpiresIn { get; set; }

    }

I used JSON.NET-based tags instead of XmlElement tags and simplified some base class attributes. In this way, Json's attribute name is lowercase, but when we convert it to the corresponding entity class, its attribute can be converted to. NET standard Pascal style attribute name.

    /// <summary>
    /// Enterprise internal development acquisition access_token Response.
    /// </summary>
    public class OapiGettokenResponse : DingTalkResponse
    {
        /// <summary>
        /// Open application token
        /// </summary>
        [JsonProperty(PropertyName ="access_token")]
        public string AccessToken { get; set; }

        /// <summary>
        /// Failure time
        /// </summary>
        [JsonProperty(PropertyName ="expires_in")]
        public long ExpiresIn { get; set; }
    }

So when I reconstruct these response classes, all I need is some replacement work.

For the data request class, I add an IsPost attribute to the base class to identify whether it is POST mode or not, otherwise it is GET mode of HTTP data request mode.

Then the submitted PostData data is constructed according to the parameters and the attributes of IsPost.

If I modify the original BaseDingTalkRequest base class object code for the following code.

    /// <summary>
    /// Basics TOP Request class, which stores some common request parameters.
    /// </summary>
    public abstract class BaseDingTalkRequest<T> : IDingTalkRequest<T> where T : DingTalkResponse
    {   
        /// <summary>
        /// Constructor
        /// </summary>
        public BaseDingTalkRequest()
        {
            this.IsPost = true;
        }

        /// <summary>
        /// Parametric constructor
        /// </summary>
        /// <param name="serverurl">request URL</param>
        /// <param name="isPost">Whether it is POST mode</param>
        public BaseDingTalkRequest(string serverurl, bool isPost) 
        {
            this.ServerUrl = serverurl;
            this.IsPost = isPost;
        }


        /// <summary>
        /// Submitted data or additional strings
        /// </summary>
        public string PostData 
        {
            get
            {
                string result = "";
                var dict = GetParameters();
                if(dict != null)
                {
                    if (IsPost)
                    {
                        result = dict.ToJson();
                    }
                    else
                    {
                        //return string.Format("corpid={0}&corpsecret={1}", corpid, corpsecret);
                        foreach (KeyValuePair<string, object> pair in dict)
                        {
                            if (pair.Value != null)
                            {
                                result += pair.Key + "=" + pair.Value + "&";
                            }
                        }
                        result = result.Trim('&');
                    }
                }
                return result;
            }
        }

        /// <summary>
        /// Whether POST Way (otherwise) GET Mode)
        /// </summary>
        public virtual bool IsPost { get; set; }

        /// <summary>
        /// Connect URL,Replace DefaultDingTalkClient Of serverUrl
        /// </summary>
        public virtual string ServerUrl { get; set; }


        /// <summary>
        /// POST Obtain GET List of parameters
        /// </summary>
        /// <returns></returns>
        public virtual SortedDictionary<string, object> GetParameters() { return null; }

    }

For request objects such as Request requesting Token, we can inherit this base class, as shown in the following code.

    /// <summary>
    /// Enterprise internal development acquisition Token Request
    /// </summary>
    public class OapiGettokenRequest : BaseDingTalkRequest<OapiGettokenResponse>
    {
        public OapiGettokenRequest()
        {
            this.ServerUrl = "https://oapi.dingtalk.com/gettoken";
            this.IsPost = false;
        }

        public OapiGettokenRequest(string corpid, string corpsecret) : this()
        {
            this.Corpid = corpid;
            this.Corpsecret = corpsecret;
        }

        /// <summary>
        /// enterprise Id
        /// </summary>
        public string Corpid { get; set; }

        /// <summary>
        /// Certificate keys for enterprise applications
        /// </summary>
        public string Corpsecret { get; set; }

        public override SortedDictionary<string, object> GetParameters()
        {
            SortedDictionary<string, object> parameters = new SortedDictionary<string, object>();
            parameters.Add("corpid", this.Corpid);
            parameters.Add("corpsecret", this.Corpsecret);

            return parameters;
        }
    }

This request class also determines the URL of the request and the mode of data request (GET, POST), so that when calling, these parameters need not be specified again, especially in the case of repeated calls, it simplifies a lot.

Through these definitions, we should understand my idea of reconstructing the whole nail SDK, which is basically based on the principle of encapsulating details as much as possible and simplifying the use of code.

The whole idea is based on the official SDK nail.

For DefaultDingTalkClient, the core class of nail SDK, we make a lot of modification and refactoring to simplify the original code (from 430 lines to 90 lines), and achieve the same function.

The main logic is that we use JSON.NET's standardized serialization method to reduce the complicated serialization processing of nail SDK, and the previous use of PostData, IsPost attributes is also to simplify the processing of requests.

        /// <summary>
        /// implement TOP Privacy API Request.
        /// </summary>
        /// <typeparam name="T">Domain object</typeparam>
        /// <param name="request">Concrete TOP API request</param>
        /// <param name="accessToken">User session code</param>
        /// <param name="timestamp">Request timestamp</param>
        /// <returns>Domain object</returns>
        public T Execute<T>(IDingTalkRequest<T> request, string accessToken, DateTime timestamp) where T : DingTalkResponse
        {
            string url = this.serverUrl;
            //If it is already set up, the Request Mainly
            if(!string.IsNullOrEmpty(request.ServerUrl))
            {
                url = request.ServerUrl;
            }

            if (!string.IsNullOrEmpty(accessToken))
            {
                url += string.Format("?access_token={0}", accessToken);
            }

            string content = "";
            HttpHelper helper = new HttpHelper();
            helper.ContentType = "application/json";
            content = helper.GetHtml(url, request.PostData, request.IsPost);

            T json = JsonConvert.DeserializeObject<T>(content);
            return json;
        }

 

3. Using reconstructed nail SDK

1) Calls to refactor code encapsulation

In order to introduce the usage of the reconstructed nail SDK, I wrote several functional test interfaces.

The operation code for obtaining Token is shown below.

        private void btnGetToken_Click(object sender, EventArgs e)
        {
            //Get access Token
            var request = new OapiGettokenRequest(corpid, corpSecret);
            var response = new DefaultDingTalkClient().Execute(request);
            Console.WriteLine(response.ToJson());
        }

Processing codes for department information and detailed information are shown below.

        private void btnDept_Click(object sender, EventArgs e)
        {
            var client = new DefaultDingTalkClient();

            var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
            var token = client.Execute(tokenRequest);

            if (token != null && !token.IsError)
            {
                Console.WriteLine("Access to departmental information");

                string id = "1";
                var request = new OapiDepartmentListRequest(id);
                var dept = client.Execute(request, token.AccessToken);
                if (dept != null && dept.Department != null)
                {
                    Console.WriteLine(dept.Department.ToJson());

                    Console.WriteLine("Get Departmental Details");
                    foreach (var item in dept.Department)
                    {
                        var getrequest = new OapiDepartmentGetRequest(item.Id.ToString());
                        var info = client.Execute(getrequest, token.AccessToken);
                        if (info != null)
                        {
                            Console.WriteLine("Departmental details:{0}", info.ToJson());

                            Console.WriteLine("Getting Department User Information");
                            var userrequest = new OapiUserListRequest(info.Id);
                            var list = client.Execute(userrequest, token.AccessToken);
                            if (list != null)
                            {
                                Console.WriteLine(list.ToJson());

                                Console.WriteLine("Get detailed user information");
                                foreach (var userjson in list.Userlist)
                                { 
                                    var get = new OapiUserGetRequest(userjson.Userid);
                                    var userInfo = client.Execute(get, token.AccessToken);

                                    if (userInfo != null)
                                    {
                                        Console.WriteLine(userInfo.ToJson());
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("Error handling:{0}", token.ErrMsg);
            }
        }

From the above code, we can see that the processing of Request requests is much simpler, without having to enter annoying URL information, and whether GET or POST mode.

The processing of getting roles is shown below.

        private void btnRole_Click(object sender, EventArgs e)
        {
            var client = new DefaultDingTalkClient();

            var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
            var token = client.Execute(tokenRequest);

            if (token != null && !token.IsError)
            {
                Console.WriteLine("Obtaining role information");

                var request = new OapiRoleListRequest();
                var result = client.Execute(request, token.AccessToken);
                if (result != null && result.Result != null && result.Result.List != null)
                {
                    Console.WriteLine("Role information:{0}", result.Result.List.ToJson());

                    foreach (var info in result.Result.List)
                    {
                        Console.WriteLine("Role Group Information:{0}", info.ToJson());

                        Console.WriteLine("Get role details");
                        foreach (var roleInfo in info.Roles)
                        {
                            var roleReq = new OapiRoleGetroleRequest(roleInfo.Id);
                            var detail = client.Execute(roleReq, token.AccessToken);
                            if (detail != null && detail.Role != null)
                            {
                                Console.WriteLine("Role details:{0}", detail.Role.ToJson());
                            }
                        }
                    }
                }
            }
        }

The information obtained is output in the output form of VS.

 

2) Simplify the code with extended functions

From the above code, we can see that DefaultDingTalkClient is still a bit bloated, and we can also optimize requests through extension functions. The following code

                var client = new DefaultDingTalkClient();
                var tokenRequest = new OapiGettokenRequest(corpid, corpSecret);
                var token = client.Execute(tokenRequest);

If we implement it through extension functions, the code can be further simplified, as shown below.

var token = new OapiGettokenRequest(corpid, corpSecret).Execute();

For the encapsulation of extension function, we just add the corresponding interface IDingTalkRequest to the extension function, as shown in the following code.

 

The above is the process of my overall reconstruction of the nail SDK. Because I need to convert all the classes of Request and Response into the content I need, I need to deal with all the classes in a unified way. I need to refer to the official URL, POST/GET mode for each Request class. At the same time, I need to replace the JSON.NET logo and modify the corresponding content. The amount is not small, but for the overall development of nails in the later period, I think it should be worthwhile to pay for this.

I categorize the definite Request and Response of different business scopes, put different business scopes in different directories, and retain the class names of the original Request and Response objects. The whole solution is shown below.

Keywords: C# SDK JSON Attribute Mobile

Added by kevinkorb on Thu, 16 May 2019 18:51:46 +0300