Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task Extension to Cater App Wide Service Calls

I am working in xamarin and trying to consume all services using one method. For that I wrote a TaskExtension. So that from every page in the app I can call that extension method. This is to disable the buttons, show loading screen, response handling and to cater exception handling from one point. I am attaching my code below. Need your expert opinions on this solution

Here is my Extension Class

public static class TaskExtensions
{
    public static async Task<ResultViewModel<U>> ExecuteAsyncOperation<U>(this Task<HttpResponseMessage> operation, object sender = null)
    {
        ResultViewModel<U> resultModel = new ResultViewModel<U>();
        Button button = BeforeAsyncCall(sender);
        try
        {
            await BackgroundOperation(operation, resultModel);
        }
        catch (Exception ex)
        {
            resultModel.Status = HttpStatusCode.InternalServerError;
            resultModel.Errors = new List<string>() { "Some error occurred. Please try again." };
        }
        finally
        {
            AfterAsyncCall(button);

        }
        return resultModel;
    }
    static async Task BackgroundOperation<U>(Task<HttpResponseMessage> operation, ResultViewModel<U> resultModel)
    {
        HttpResponseMessage RawResult = await operation;
        var Response = await RawResult.Content.ReadAsStringAsync();
        resultModel.Status = RawResult.StatusCode;
        if (RawResult.IsSuccessStatusCode)
        {
            var responseObj = await Task.Run(() => JsonConvert.DeserializeObject<U>(Response));
            resultModel.Result = responseObj;
        }
        else
        {
            var responseErrorObj = await Task.Run(() => JsonConvert.DeserializeObject<ErrorModel>(Response));
            resultModel.Errors = new List<string>();
            foreach (var modelState in responseErrorObj.ModelState)
            {
                foreach (var error in modelState.Value)
                {
                    resultModel.Errors.Add(error.ToString());
                }
            }
        }
    }
    static Button BeforeAsyncCall(object sender)
    {
        Button button = null;
        if (sender != null)
            button = (Button)sender;
        if (button != null)
            button.IsEnabled = false;
        UserDialogs.Instance.ShowLoading("Loading", MaskType.Black);
        return button;
    }
    static void AfterAsyncCall(Button button)
    {
        UserDialogs.Instance.HideLoading();
        if (button != null)
            button.IsEnabled = true;
    }
}

Here is the Call to my Extension Method

ResultViewModel<TokenModel> response = await new Service(Settings.BaseUrl).Login(loginModel).ExecuteAsyncOperation<TokenModel>(sender);

ResultViewModel

public class ResultViewModel<T> 
    {
        public HttpStatusCode Status { get; set; }
        public T Result { get; set; }
        public List<string> Errors { get; set; }
    }

Async Method

public async Task<HttpResponseMessage> Login(LoginViewModel loginModel)
        {
            try
            {

                var dataList = new List<KeyValuePair<string, string>>();
                dataList.Add(new KeyValuePair<string, string>("grant_type", "password"));
                dataList.Add(new KeyValuePair<string, string>("username", loginModel.Email));
                dataList.Add(new KeyValuePair<string, string>("password", loginModel.Password));
                var request = new HttpRequestMessage()
                {
                    RequestUri = new Uri(this.BaseUrl + "token"),
                    Method = HttpMethod.Post,
                    Content = new FormUrlEncodedContent(dataList)
                };
                var authenticateResponse = await Client.SendAsync(request);
                return authenticateResponse;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

My questions is

1) Is this a good approach?

2) Can we improve it in terms of performance?

3) Am I using Async correctly?

like image 966
Safi Mustafa Avatar asked May 24 '18 17:05

Safi Mustafa


People also ask

How do I add an appservice extension to an application?

Right-click it in the Solution Explorer. Select Open With. Select XML (Text) Editor. Add the following AppService extension inside the <Application> element. This example advertises the com.microsoft.inventory service and is what identifies this app as an app service provider.

What is the Tasks app?

The Tasks app brings a cohesive task management experience to Microsoft Teams, integrating personal tasks powered by Microsoft To Do and team tasks powered by Planner in one place. Users can access Tasks as an app on the left side of Teams and as a tab in a channel within individual teams.

How does tasks work on the desktop and mobile clients?

If Tasks is installed on the Teams desktop client, users will also see it on their Teams web and mobile clients. The exception is guest users. It's important to know that guests can only access Tasks as an app from the Teams mobile client. Guests will see Tasks tabs on both Teams desktop and web clients. My tasks shows a user's individual tasks.

What does the example app service expect from the client app?

The example app service expects a command to indicate which of two actions to take. We get the index from the text box in the client app, and then call the service with the Item command to get the description of the item. Then, we make the call with the Price command to get the item's price.


1 Answers

1) Is this a good approach?

There is nothing wrong with this approach.

2) Can we improve it in terms of performance?

There shouldn't be any performance issues with using an extension method, but you can measure to be 100% certain. You are creating a boxing and unboxing situation with the object to button casting. Could you just use Button. Use ViewElement if you want to support multiple element types. Also, There are penalties for using async await, but they are minimal and necessary to not block the UI. You can increase performance by removing the need to recapture the context by adding .ConfigureAwait(false) to your tasks, but in your case you need context to re-enable the button. The use of dynamic seems unnecessary and does have some overhead.

3) Am I using Async correctly?

You don't have to await a Task if the method just returns a Task. You can await it from the calling method. This will reduce overhead on the compiler, and may increase performance. I haven't tested this out before though.

Extension

public static async Task<ResultViewModel<T>> ExecuteAsyncOperation<T>(this Task<HttpResponseMessage> operation, Button button)
{
    ResultViewModel<T> resultModel = new ResultViewModel<T>();
    try
    {
        if (button != null)
            button.IsEnabled = false;
        HttpResponseMessage RawResult = await operation;
        string Response = await RawResult.Content.ReadAsStringAsync();
        resultModel.Status = RawResult.StatusCode;

        if (RawResult.IsSuccessStatusCode)
        {
            var responseObj = JsonConvert.DeserializeObject<T>(Response);
            resultModel.Result = responseObj;
        }
        else
        {
            //create an error model instead of using dynamic I am guessing modelstate here
            List<ModelState> responseObj = JsonConvert.DeserializeObject<List<ModelState>>(Response);
            resultModel.Errors = new List<string>();
            foreach (ModelState modelState in responseObj)
            {
                foreach (var error in modelState.Errors)
                {
                    resultModel.Errors.Add(error.ToString());
                }
            }
        }
    }
    catch (Exception ex)
    {
        resultModel.Status = HttpStatusCode.InternalServerError;
        resultModel.Errors = new List<string>() { "Some error occurred. Please try again." };
    }
    finally
    {
        if (button != null)
            button.IsEnabled = true;
    }
    return resultModel;
}

Calling

var button = sender as Button; 
if (button != null)
{
    ResultViewModel<TokenModel> response = await new Service(Settings.BaseUrl).Login(loginModel).ExecuteAsyncOperation<TokenModel>(sender);
}

Request

public Task<HttpResponseMessage> Login(LoginViewModel loginModel)
{
    var dataList = new List<KeyValuePair<string, string>>();
    dataList.Add(new KeyValuePair<string, string>("grant_type", "password"));
    dataList.Add(new KeyValuePair<string, string>("username", loginModel.Email));
    dataList.Add(new KeyValuePair<string, string>("password", loginModel.Password));
    var request = new HttpRequestMessage()
    {
        RequestUri = new Uri(this.BaseUrl + "token"),
        Method = HttpMethod.Post,
        Content = new FormUrlEncodedContent(dataList)
    };
    return Client.SendAsync(request);
}
like image 186
ATerry Avatar answered Oct 12 '22 12:10

ATerry