Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Creating the C# Slack Client Library by Code Generation with Good Architecture in Three Days

5.00/5 (4 votes)
9 Jul 2022CPOL9 min read 11.8K  
A Slack API client library by C# and how to design the architecture by using various C# language feature
This article describes OAuth and callback integration, design request parameter and response data classes and method, how to create classes by code generation, how to handle pagination and how to handle refresh token automatically and do exception handling.

Introduction

This article describes the following:

  1. OAuth and webhook callback integration
  2. Design request parameter and response data classes and method
  3. How to create classes by code generation
  4. How to handle pagination
  5. How to handle refresh token automatically
  6. Exception handling

So, let's go!

Update Info for Version 6.1.0.0

  • IJsonConverter moved from static property to instance property.
  • IJsonConverter object must pass to SlackClient constructor.

Background

Last month, I had an opportunity to create a feature that uses Slack API. So I Googled for some library for Slack.

I found several libraries, but these are not good architecture. Some libraries use HttpClient directory, missing API, not intuitive method design.

So, I decided to create a Slack library.

Using the Code

I released the initial version of HigLabo.Net.Slack.

You can get it from Nuget by searching HigLabo.

To use this library, you must add references for the following:

  • HigLabo.Converter
  • HigLabo.Core
  • HigLabo.Mapper
  • HigLabo.Net.OAuth
  • HigLabo.Net.OAuth.NewtonsoftJson
  • HigLabo.Net.Slack

If you are interested in HigLabo.Mapper, here is an article. This is the fastest C# object mapper library in the world.

Add using HigLabo.Net.Slack namespace on top of your C# file.

This is a basic usage with access token:

C#
//Set JsonConverter that you want to use. 
//You can use a converter that is based on NewtonsoftJson
//Include that in HigLabo.Net.OAuth.NewtonsoftJson package.

//And call API what you want.
var cl = new SlackClient(new HigLabo.Net.OAuth.JsonConverter(), "access token");
var profile =  await cl.UsersProfileGetAsync(new UsersProfileGetParameter() 
               { User = "userId" });
Console.WriteLine(profile.Profile.Display_Name);
Console.WriteLine(profile.Profile.Real_Name);
Console.WriteLine(profile.Profile.Image_192);

I will describe about OAuth, token handling, API endpoint design, exception handling in the below chapter.

OAuthSetting Class for OAuth Authentication

In HigLabo.Net.OAuth package, you can see HigLabo.Net.OAuth.OAuthSetting class.

And you can also see HigLabo.Net.Slack.OAuthSetting class that inherits from HigLabo.Net.OAuth.OAuthSetting class.

HigLabo.Net.OAuth.OAuthSetting class provides basic feature for OAuth that will be used for other OAuth service such as Microsoft365, Google, Facebook workplace, Instagram, Twitter, discord and so on. (I will create another library in the future, maybe. Please wait!)

HigLabo.Net.OAuth.OAuthSetting class:

C#
namespace HigLabo.Net.OAuth
{
    public abstract class OAuthSetting 
    {
        public string ClientId { get; set; } = "";
        public string ClientSecret { get; set; } = "";
        public string RedirectUrl { get; set; } = "";

        public abstract string CreateUrl();
    }
}

This class provides simple property and abstract method. CreateUrl method will return authorization url for OAuth.

HigLabo.Net.Slack.OAuthSetting class provides actual implementation for CreateUrl method.

C#
namespace HigLabo.Net.Slack
{
    public enum AuthorizationScopeType
    {
        App,
        User,
    }
    public class OAuthSetting : HigLabo.Net.OAuth.OAuthSetting, IAuthorizationUrlBuilder
    {
        public AuthorizationScopeType ScopeType { get; set; }
        public List<Scope> ScopeList { get; init; } = new();

        public OAuthSetting(string clientId, string clientSecret, 
                            AuthorizationScopeType scopeType, string redirectUrl, 
                            IEnumerable<Scope> scopeList)
        {
            this.ClientId = clientId;
            this.ClientSecret = clientSecret;
            this.ScopeType = scopeType;
            this.RedirectUrl = redirectUrl;
            this.ScopeList.AddRange(scopeList);
        }
        public override string CreateUrl()
        {
            var scopeList = String.Join(",", 
                            this.ScopeList.Select(el => el.GetScopeName()));
            switch (this.ScopeType)
            {
                case AuthorizationScopeType.App:
                    return $"https://slack.com/oauth/v2/authorize?client_id=
                    {this.ClientId}&scope={WebUtility.UrlEncode(scopeList)}
                    &redirect_uri={this.RedirectUrl}";
                case AuthorizationScopeType.User:
                    return $"https://slack.com/oauth/v2/authorize?
                    client_id={this.ClientId}&user_scope=
                    {WebUtility.UrlEncode(scopeList)}&redirect_uri={this.RedirectUrl}";
                default:throw new SwitchStatementNotImplementException
                        <AuthorizationScopeType>(this.ScopeType);
            }
        }
    }
}

With these classes, you can easily create authentication process.

For example, in ASP.NET6, you add a link on .cshtml file like this:

C#
@{
    var setting = new HigLabo.Net.Slack.OAuthSetting("clientId", "ClientSecret", ....);
}
<a href="@setting.CreateUrl()">Add Slack account</a>

And add Controller to receive webhook from Slack.

C#
[HttpGet("/Slack/OAuthCallback")]
public async Task<IActionResult> Slack_OAuthCallback(String code)
{
    var setting = new HigLabo.Net.Slack.OAuthSetting("clientId", "ClientSecret", ....);
    var cl = new SlackClient(setting);
    var token = await cl.RequestCodeAsync(code);
    //Save access token to your database...
}

Now you can get access token and you can call Slack API endpoint via HigLabo.Net.Slack.SlackClient class.

IJsonConverter for json Handling

Before using SlackClient to call API, you must prepare for JsonConverter. In HigLabo.Net.OAuth package, you can see IJsonConverter interface.

C#
namespace HigLabo.Net.OAuth
{
    public interface IJsonConverter
    {
        string SerializeObject(object obj);
        T DeserializeObject<T>(String json);
    }
}

You can also see OAuthClient class that has IJsonConverter property and two methods, SerializeObject .DeserializeObject method.

C#
namespace HigLabo.Net.OAuth
{
    public class OAuthClient : HttpClient
    {
        public IJsonConverter? JsonConverter { get; set; }

        protected OAuthClient(IJsonConverter jsonConverter)
        {
            this.JsonConverter = jsonConverter;
        }

        //Other code.....

        protected string SerializeObject(object obj)
        {
            return JsonConverter.SerializeObject(obj);
        }
        protected T DeserializeObject<T>(String json)
        {
            return JsonConverter.DeserializeObject<T>(json);
        }
    }
}

By default, we provide a JsonConverter that uses NewtonsoftJson library. But if you want to use other JSON library, you implement IJsonConverter like this:

C#
namespace Your.Cool.Converters
{
    public class CoolJsonConverter : IJsonConverter
    {
        public string SerializeObject(object obj)
        {
            //Your implementation
        }
        public T DeserializeObject<T>(string json)
        {
            //Your implementation
        }
    }
}

And set to SlackClient constructor.

C#
var slackClient = new SlackClient(new Your.Cool.Converters.CoolJsonConverter(), 
                                  "access Token");

Then you don't have to add reference to HigLabo.Net.OAuth.NewtonsoftJson package.

Design Request and Response Classes for Slack and Future Other Services

Now it is a problem how to design API endpoint request and response.

Here is a concern before I implement it.

  1. I have been so lazy that I don't want to implement all endpoint by handy.
  2. I must be able to extend other OAuth API services. (M365, Google, FB workplace, Instagram, etc.)
  3. Intuitive class and method design.
  4. Easy to use for basic usage, fully customization for full feature to endpoint.

I'm a lazy man as a developer, I decided to create all endpoints by code generation. Fortunately, document of Slack API looks pretty well designed and pattern. It could be possible.

I searched other API, all services have these features.

  1. Endpoint URL and HTTP method
  2. Parameters
  3. Response JSON

I designed for Slack endpoint like this for https://slack.com/api/conversations.list endpoint.

C#
namespace HigLabo.Net.Slack
{
    public partial class ConversationsListParameter : IRestApiParameter, ICursor
    {
        string IRestApiParameter.ApiPath { get; } = "conversations.list";
        string IRestApiParameter.HttpMethod { get; } = "GET";
        public string Cursor { get; set; }
        public bool? Exclude_Archived { get; set; }
        public double? Limit { get; set; }
        public string Team_Id { get; set; }
        public string Types { get; set; }
    }
    public partial class ConversationsListResponse : RestApiResponse
    {
        //Some properties...
    }
    public partial class SlackClient
    {
        public async Task<ConversationsListResponse> 
               ConversationsListAsync(ConversationsListParameter parameter)
        {
        }
    }
}

The XXXParameter class has all value for API endpoint parameter as property. So, you can set all of value as you want to pass API endpoint. The name of parameter, response, method is XXXParameter, XXXResponse, XXXAsync(XXXParameter). It is intuitive and easy to find because it is pretty much same as API documentation. I think it looks good. I have not created library for other OAuth services, maybe the design will work as far as parameter and method.

This parameter design will provide you to handle all features of API by setting property. But in some cases, it is redundant. So we provide method overload that only passes the required field of API endpoint.

For example, https://slack.com/api/conversations.list endpoint require channel field.

C#
namespace HigLabo.Net.Slack
{
    public partial class ConversationsHistoryParameter : IRestApiParameter, ICursor
    {
        //Property definition...
    }
    public partial class ConversationsHistoryResponse : RestApiResponse
    {
    }
    public partial class SlackClient
    {
        public async Task<ConversationsHistoryResponse> 
                     ConversationsHistoryAsync(string channel)
        {
            var p = new ConversationsHistoryParameter();
            p.Channel = channel;
            return await this.SendAsync<ConversationsHistoryParameter, 
                         ConversationsHistoryResponse>(p);
        }
    }
}

Now you can call like this:

C#
var cl = new SlackClient("access token");
var res =  await cl.ConversationsHistoryAsync("channelId");
foreach (var message in res.Messages)
{
    //Do something...
}

Now my library achieves both requirements, it is easy to use and uses full feature to endpoint.

Code Generation by ConsoleApplication

After library architecture design, I create code generator of the above class.

You can see the project at:

The actual code is here:

Only 333 lines of code thankfully for HigLabo.CodeGenerator package.

HigLabo.CodeGenerator is a C# code generator that has is missing some of C# language features. But it works very well for me because I don't use all C# language features when generating code.

HigLabo.Net.Slack.CodeGenerator is a console program that read all Slack documentation and creates C# source code. Now I run HigLabo.Net.Slack.CodeGenerator and it creates some of code at HigLabo.Net.Slack folder. All source code in Method folder and Scope.cs file is generated by HigLabo.Net.Slack.CodeGenerator console program.

Image 1

This is the code of Scope.cs by code generation.

C#
using HigLabo.Core;

namespace HigLabo.Net.Slack
{
    public enum Scope
    {
        Admin,
        AdminAnalyticsRead,
        AdminAppsRead,

//Other scopes....

        UsersRead,
        UsersReadEmail,
        UsersWrite,
        WorkflowStepsExecute,
    }
    public static class ScopeExtensions
    {
        public static string GetScopeName(this Scope value)
        {
            switch(value)
            {
                case Scope.Admin: return "admin";
                case Scope.AdminAnalyticsRead: return "admin.analytics:read";
                case Scope.AdminAppsRead: return "admin.apps:read";
                case Scope.AdminAppsWrite: return "admin.apps:write";
//Other code....
                case Scope.UsersRead: return "users:read";
                case Scope.UsersReadEmail: return "users:read.email";
                case Scope.UsersWrite: return "users:write";
                case Scope.WorkflowStepsExecute: return "workflow.steps:execute";
                default: throw new SwitchStatementNotImplementException<Scope>(value);
            }
        }
    }
}

This is the code of ConversationsHistory.cs by code generation.

C#
namespace HigLabo.Net.Slack
{
    public partial class ConversationsHistoryParameter : IRestApiParameter, ICursor
    {
        string IRestApiParameter.ApiPath { get; } = "conversations.history";
        string IRestApiParameter.HttpMethod { get; } = "GET";
        public string Channel { get; set; }
        public string Cursor { get; set; }
        public bool? Include_All_Metadata { get; set; }
        public bool? Inclusive { get; set; }
        public string Latest { get; set; }
        public double? Limit { get; set; }
        public string Oldest { get; set; }
    }
    public partial class ConversationsHistoryResponse : RestApiResponse
    {
    }
    public partial class SlackClient
    {
        public async Task<ConversationsHistoryResponse> 
                     ConversationsHistoryAsync(string channel)
        {
            var p = new ConversationsHistoryParameter();
            p.Channel = channel;
            return await this.SendAsync<ConversationsHistoryParameter, 
                   ConversationsHistoryResponse>(p, CancellationToken.None);
        }
        public async Task<ConversationsHistoryResponse> 
        ConversationsHistoryAsync(string channel, CancellationToken cancellationToken)
        {
            var p = new ConversationsHistoryParameter();
            p.Channel = channel;
            return await this.SendAsync<ConversationsHistoryParameter, 
                   ConversationsHistoryResponse>(p, cancellationToken);
        }
        public async Task<ConversationsHistoryResponse> 
               ConversationsHistoryAsync(ConversationsHistoryParameter parameter)
        {
            return await this.SendAsync<ConversationsHistoryParameter, 
                   ConversationsHistoryResponse>(parameter, CancellationToken.None);
        }
        public async Task<ConversationsHistoryResponse> 
               ConversationsHistoryAsync(ConversationsHistoryParameter parameter, 
               CancellationToken cancellationToken)
        {
            return await this.SendAsync<ConversationsHistoryParameter, 
                   ConversationsHistoryResponse>(parameter, cancellationToken);
        }
        public async Task<List<ConversationsHistoryResponse>> 
               ConversationsHistoryAsync(string channel, 
               PagingContext<ConversationsHistoryResponse> context)
        {
            var p = new ConversationsHistoryParameter();
            p.Channel = channel;
            return await this.SendBatchAsync(p, context, CancellationToken.None);
        }
        public async Task<List<ConversationsHistoryResponse>> 
               ConversationsHistoryAsync(string channel, 
               PagingContext<ConversationsHistoryResponse> context, 
               CancellationToken cancellationToken)
        {
            var p = new ConversationsHistoryParameter();
            p.Channel = channel;
            return await this.SendBatchAsync(p, context, cancellationToken);
        }
        public async Task<List<ConversationsHistoryResponse>> 
               ConversationsHistoryAsync(ConversationsHistoryParameter parameter, 
               PagingContext<ConversationsHistoryResponse> context)
        {
            return await this.SendBatchAsync
                   (parameter, context, CancellationToken.None);
        }
        public async Task<List<ConversationsHistoryResponse>> 
               ConversationsHistoryAsync(ConversationsHistoryParameter parameter, 
               PagingContext<ConversationsHistoryResponse> context, 
               CancellationToken cancellationToken)
        {
            return await this.SendBatchAsync(parameter, context, cancellationToken);
        }
    }
}

I generate some overload version with CancellationToken.

You may wonder about this overload.

C#
public async Task<List<ConversationsHistoryResponse>>
       ConversationsHistoryAsync(string channel, PagingContext
       <ConversationsHistoryResponse> context)
{
    var p = new ConversationsHistoryParameter();
    p.Channel = channel;
    return await this.SendBatchAsync(p, context, CancellationToken.None);
}
public async Task<List<ConversationsHistoryResponse>>
       ConversationsHistoryAsync(string channel,
       PagingContext<ConversationsHistoryResponse> context,
       CancellationToken cancellationToken)
{
    var p = new ConversationsHistoryParameter();
    p.Channel = channel;
    return await this.SendBatchAsync(p, context, cancellationToken);
}
public async Task<List<ConversationsHistoryResponse>>
       ConversationsHistoryAsync(ConversationsHistoryParameter parameter,
       PagingContext<ConversationsHistoryResponse> context)
{
    return await this.SendBatchAsync
           (parameter, context, CancellationToken.None);
}
public async Task<List<ConversationsHistoryResponse>>
       ConversationsHistoryAsync(ConversationsHistoryParameter parameter,
       PagingContext<ConversationsHistoryResponse> context,
       CancellationToken cancellationToken)
{
    return await this.SendBatchAsync(parameter, context, cancellationToken);
}

You notice these methods delegate to SendBatchAsync. It is a feature for pagination. I will explain it in the next chapter.

You will also notice that the generated response object has no property. Because I could not create these properties of response automatically from documentation. But thank you for partial class, I merge features by myself with auto generated class.

I created some classes in Entity folder and Response folder.

Image 2

This is a Profile class that I created by myself.

C#
namespace HigLabo.Net.Slack
{
    public class Profile
    {
        public String Title { get; set; }
        public String Phone { get; set; }
        public String Skype { get; set; }
        public String Avatar_Hash { get; set; }
        public String Status_Text { get; set; }
        public String Status_Emoji { get; set; }
        public String[] Status_Emoji_Display_Info { get; set; }
        public Int32 Status_Expiration { get; set; }
        public String Real_Name { get; set; }
        public String Display_Name { get; set; }
        public String Real_Name_Normalized { get; set; }
        public String Display_Name_Normalized { get; set; }
        public String EMail { get; set; }
        public String First_Name { get; set; }
        public String Last_Name { get; set; }
        public Boolean Is_Custom_Image { get; set; }
        public String Image_Original { get; set; }
        public String Image_24 { get; set; }
        public String Image_32 { get; set; }
        public String Image_48 { get; set; }
        public String Image_72 { get; set; }
        public String Image_192 { get; set; }
        public String Image_512 { get; set; }
        public String Image_1024 { get; set; }
        public String Team { get; set; }
        public String Status_Text_Canonical { get; set; }
    }
}

This is a UsersProfileGetResponse:

C#
namespace HigLabo.Net.Slack
{
    public partial class UsersProfileGetResponse
    {
        public Profile Profile { get; set; }
    }
}

You can get profile data like this:

C#
var cl = new SlackClient(new MyJsonConverter(), "access token");
var profile =  await cl.UsersProfileGetAsync(new UsersProfileGetParameter() 
               { User = "userId" });
Console.WriteLine(profile.Profile.Display_Name);
Console.WriteLine(profile.Profile.Real_Name);
Console.WriteLine(profile.Profile.Image_192);

Unfortunately, some of response class does not have implementation because I have enough time to implement it. But you can access actual JSON body text via RestApiResponse object like this:

C#
var cl = new SlackClient(new MyJsonConverter(), "access token");
var res = await cl.UsersProfileGetAsync();
var iRes = res as IRestApiResponse;
var bodyText = iRes.ResponseBodyText;

I decided to use explicit implementation of interfaces to prevent from occurring name collision to API response property.

The definition of IRestApiResponse and HigLabo.Net.OAuth.RestApiResponse is here.

C#
namespace HigLabo.Net.OAuth
{
    public interface IRestApiResponse
    {
        object Parameter { get; }
        HttpRequestMessage Request { get; }
        HttpStatusCode StatusCode { get; }
        Dictionary<String, String> Headers { get; } 
        string ResponseBodyText { get; } 
    }

    public class RestApiResponse : IRestApiResponse
    {
        private Object _Parameter = null; 
        private HttpRequestMessage _Request = null;
        private HttpStatusCode _StatusCode = HttpStatusCode.OK;
        private Dictionary<String, String> _Headers = new Dictionary<string, string>();
        private string _ResponseBodyText = "";

        object IRestApiResponse.Parameter
        {
            get { return _Parameter; }
        }
        HttpRequestMessage IRestApiResponse.Request
        {
            get { return _Request; }
        }
        HttpStatusCode IRestApiResponse.StatusCode
        {
            get { return _StatusCode; }
        }
        Dictionary<String, String> IRestApiResponse.Headers
        {
            get { return _Headers; }
        }
        string IRestApiResponse.ResponseBodyText
        {
            get { return _ResponseBodyText; }
        }

        public void SetProperty(object parameter, 
        HttpRequestMessage request, HttpResponseMessage response, string bodyText)
        {
            var res = response;
            _Parameter = parameter;
            _Request = request;
            _StatusCode = res.StatusCode;
            foreach (var header in res.Headers)
            {
                _Headers[header.Key] = header.Value.ToString() ?? "";
            }
            _ResponseBodyText = bodyText;
        }
    }
}

With HigLabo.Net.OAuth.RestApiResponse class, you can get parameter that used to call API endpoint. You can also get actual HttpRequestMessage, HttpHeader of response, and response body text.

I don't create AdminConversationsSearchResponse class but you use it by creating your own JSON parser or class.

C#
var cl = new SlackClient(new MyJsonConverter(), "access token");
var res = await cl.AdminConversationsSearchAsync();
var iRes = res as IRestApiResponse;
var json = iRes.ResponseBodyText;
var jo = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
//Work with JObject...
var o = Newtonsoft.Json.JsonConvert.DeserializeObject<MyResponseClass>(json);
//Work with MyResponseClass...

HigLabo.Net.Slack package provides parameter definition, handle pagination, automatically updates access token. Even though the missing response, you can only handle JSON parsing.

I think done is better than perfect.

After all code generation, SlackClient has only these codes.

I'm a super lazy man, I really am happy doing less work with code generation, all Slack API that has over 200 endpoints will be available by creating less than 120 lines of code for SlackClient and some entity and response classes.

Is it cool, right?

Help Your Contribution!

I really appreciate for your contribution. Here is the guide of contribution.

  1. Create missing response class in Response folder.
  2. If you need new entity, you add entity class to Entity folder.
  3. Send me PR via GitHub. (https://github.com/higty/higlabo)

I believe that HigLabo.Net.Slack makes the world better place. 😊

It is the power of open source.

Pagination with Cursor

I found that not only Slack API but also other M365, Google, Instagram, etc. provide feature for pagination. To prevent from overuse of API or network bandwidth, to add feature of pagination is the right thing for service provider.

But for the developer who uses these API, it will be a little bit redundant work to repeatedly call API.

So, I provide an easy way to call pagination API.

With HigLabo.Net.Slack library, you can get all conversation list only write some pieces of code like this:

C#
var cl = new SlackClient(new MyJsonConverter(), "access token");
var context = new PagingContext<ConversationsHistoryResponse>();
var responseList =  await cl.ConversationsHistoryAsync("channelId", context);

Slack says in documentation if cursor field is exists in parameter, you call API multiple times to get next page data. I generate overload method if API endpoint has cursor field.

I created these overload versions only when the API has cursor field. You can see UsersProfileGetAsync method does not have overload that call SendBatchAsync method in it.

You can interrupt during pagination. PagingContext<T> class will raise event per API call.

Here is the actual implementation of SendBatchAsync method in SlackClient class.

C#
public async Task<List<TResponse>> SendBatchAsync<TParameter, TResponse>
(TParameter parameter, PagingContext<TResponse> context, 
CancellationToken cancellationToken)
    where TParameter : IRestApiParameter, ICursor
    where TResponse : RestApiResponse, new()
{
    var p = parameter;
    var l = new List<TResponse>();
    while (true)
    {
        var res = await this.SendAsync<TParameter, TResponse>(p, cancellationToken);
        l.Add(res);

        context.InvokeResponseReceived
        (new ResponseReceivedEventArgs<TResponse>(this, l));
        if (context.Break) { break; }
        if (res.Response_MetaData == null || 
            res.Response_MetaData.Next_Cursor.IsNullOrEmpty()) { break; }
        p.Cursor = res.Response_MetaData.Next_Cursor;
    }
    return l;
}

You can see InvokeResponseReceived is called and pass current response list to event subscriber.

If you want to get all data from endpoint until the end of data, you only pass PagingContext<T> object to method. You can use Empty property of PagingContext<T> class.

If you want to stop some condition during pagination, you set PagingContext.Break property as true.

For example, Slack has a limit of record count such as 100 (I don't know the exact value...), your app wants to show 250 records, and 1000 records exists in Slack.

C#
var cl = new SlackClient(new MyJsonConverter(), "access token");
var context = new PagingContext<ConversationsHistoryResponse>();
context.ResponseReceived += (sender, e) => {
    if (e.ResponseList.Sum(el => el.Messages.Count) > 250)
    {
        e.Break = true;
    }
};
var responseList =  await cl.ConversationsHistoryAsync("channelId", context);
var messageList = responseList.SelectMany(el => el.Messages).Top(250);

e.ResponseList contains all response that receive on this API call. You can stop when the sum of message is larger than 250 and get 250 records by LINQ.

Other example, you want to search something first one record, you can use PagingContext<T> for it.

Update Access Token by Refresh Token and Save to Your Database

Slack required token rotation to improve your security. To update access token is little bit complex flow to implement on your own. HigLabo.Net.Slack provides an easy way to update your access token.

You only pass refresh token and OAuthSetting to constructor of SlackClient.

C#
var setting = new HigLabo.Net.Slack.OAuthSetting("clientId", "clientSecret", ...);
var cl = new SlackClient(new MyJsonConverter(), "access token", "refresh token", setting);
//Call API...

SlackClient automatically handles updating process of access token. Here is a code to update access token.

C#
private async Task<TResponse> GetResponseAsync<TResponse>(Func<Task<TResponse>> func)
{
    var isFirst = true;

    while (true)
    {
        try
        {
            return await func();
        }
        catch
        {
            if (isFirst == false) { throw; }
            isFirst = false;
        }
        var result = await this.UpdateAccessTokenAsync();
        this.AccessToken = result.Authed_User.Access_Token;
        this.RefreshToken = result.Authed_User.Refresh_Token;
        this.OnAccessTokenUpdated(new AccessTokenUpdatedEventArgs(result));
    }
}

You must only pass refresh token and OAuthSetting. It is easy to use. But you may want to know the new access token value when update is occurred. SlackClient has an event that will be triggered when access token updated.

This is a sample code to receive token updated.

C#
var setting = new HigLabo.Net.Slack.OAuthSetting("clientId", "clientSecret", ...);
var cl = new SlackClient(new MyJsonConverter(), 
         "access token", "refresh token", setting);
cl.AccessTokenUpdated += async (sender, e) =>
{
    var scl = sender as SlackClient
    if (r.Ok == true)
    {
        var AccessToken = scl.AccessToken;
        var RefreshToken = scl.RefreshToken;
        //Save to your database or else...
    }
};

It is pretty easy to understand and to use.

Exception Handling

SlackClient has a property whether throw exception or not.

C#
var setting = new HigLabo.Net.Slack.OAuthSetting("clientId", "clientSecret", ...);
var cl = new SlackClient(new MyJsonConverter(), 
         "access token", "refresh token", setting);
cl.IsThrowException = false;

The default value is true. When set to false, you must check the Ok property of RestApiResponse object to handle API error.

C#
var setting = new HigLabo.Net.Slack.OAuthSetting("clientId", "clientSecret", ...);
var cl = new SlackClient(new MyJsonConverter(), 
         "access token", "refresh token", setting);
cl.IsThrowException = false;
var res =  await cl.ConversationsHistoryAsync("channelId");
if (res.Ok == false)
{
    //Handle error
}

Conclusion

I enjoy code generation and design the architecture of this library. I will try to other services if I could have time to work with. I hope you also enjoy this article. And I wish that HigLabo.Net.Slack package makes your work easy.

Thank you for reading my article!

History

  • 1st July, 2022: Initial post
  • 10th July, 2022: Release 6.1.0.0 version that includes HigLabo.Net.Microsoft

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)