How can I call an external API from a workflow task? - Orchard Core Nuggets

Gábor Domonkos's avatar
Orchard Core Nuggets, Workflows, Short codes, new content filters - This week in Orchard (22/05/2020)

You have several options to send an HTTP request to an external API in Orchard Core, but maybe you haven't tried the Http Request Task. Let's see quickly how you can hook up a workflow!

The HTTP Request Task comes from the HTTP Workflows Activities feature, so before doing anything, don't forget the enable that module. Now navigate to the Workflows option from the menu and hit Create Workflow Type to add your workflow.

JSONPlaceholder is a nice fake online REST API that you can use whenever you need some fake data. It comes with a set of 6 common resources and we are going to use the first one and making a GET HTTP request to get 100 user posts in a JSON format. Choose the Add Task button in the Workflow editor and select the HTTP Request one from the HTTP category.

The editor of the HTTP Request Task to make a GET request

Here you can see a nice editor where you can provide the details of your request. We added a custom title to our activity as Get 100 posts. The URL will be the URL provided by the JSONPlaceholder API. To get the posts we have to make a GET request. If you would like to add a new post, make a POST request to the same endpoint, and provide the body to send. Don't forget to handle the 201 HTTP response code, because this will be the number that will show you that you are good to go and the server faked that your content was created. You may notice that you can type Liquid code everywhere in this editor. Let's say that the title of our new post will be the name of our site. To do that, we can pass the {{ Site.SiteName }} Liquid expression.

The editor of the HTTP Request Task to make a GET request

As we mentioned, we get 201 if everything goes well. If the server returned with anything else, then something bad happened and we should handle that in our workflow of course. To handle the failed requests, we can use the Unhandled HTTP Status branch of the HTTP Request Task (we named in Create a new post) and notice the user somehow.

A workflow containing the HTTP Request Task

OK, it's not a useful workflow, because we are only dealing with the response codes, but not with the response body. But how can we use the response details? To find the answer we have to check the source code of the HTTP Request Task activity and take a look at the ExecutyAsync method. Here you can see that the code uses the LastResult of the workflowContext and the LastResult has a Body property where you can find the response body itself. The LastResult property of the WorkflowExecutionContext is an object, that means you can easily put everything into the LastResult, but it could be a little bit harder to get the content from it.

public override async Task<ActivityExecutionResultExecuteAsync(WorkflowExecutionContext workflowContextActivityContext activityContext)
        {
            using (var httpClient = new HttpClient())
            {
                var headersText = await _expressionEvaluator.EvaluateAsync(Headers, workflowContext);
                var headers = ParseHeaders(headersText);
 
                foreach (var header in headers)
                {
                    httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
                }
 
                var httpMethod = HttpMethod;
                var url = await _expressionEvaluator.EvaluateAsync(Url, workflowContext);
                var request = new HttpRequestMessage(new HttpMethod(httpMethod), url);
                var postMethods = new[] { HttpMethods.Patch, HttpMethods.Post, HttpMethods.Put };
 
                if (postMethods.Any(x => string.Equals(xhttpMethodStringComparison.OrdinalIgnoreCase)))
                {
                    var body = await _expressionEvaluator.EvaluateAsync(Body, workflowContext);
                    var contentType = await _expressionEvaluator.EvaluateAsync(ContentType, workflowContext);
                    request.Content = new StringContent(bodyEncoding.UTF8, contentType);
                }
 
                var response = await httpClient.SendAsync(requestHttpCompletionOption.ResponseContentRead);
                var responseCodes = ParseResponseCodes(HttpResponseCodes);
                var outcome = responseCodes.FirstOrDefault(x => x == (int)response.StatusCode);
 
                workflowContext.LastResult = new
                {
                    Body = await response.Content.ReadAsStringAsync(),
                    Headers = response.Headers.ToDictionary(x => x.Key),
                    StatusCode = response.StatusCode,
                    ReasonPhrase = response.ReasonPhrase,
                    IsSuccessStatusCode = response.IsSuccessStatusCode
                };
 
                return Outcomes(outcome != 0 ? outcome.ToString() : "UnhandledHttpStatus");
            }
        }

Imagine you are implementing a custom activity that creates content items based on the response body. For that, you need to get the JSON then serialize it to a typed class. That makes the data easier to work with. In the ExecutyAsync method of your custom activity you can get the JSON like:
var responseBody = workflowContext.LastResult.GetType().GetProperty("Body").GetValue(workflowContext.LastResult).ToString();

Here the responseBody variable will contain a JSON in a string. You may notice that the value of the title here is Orchard Core, which comes from the {{ Site.SiteName }} Liquid value.

The JSON Visualizer window of Visual Studio showing the value of the responseBody variable.

To deserialize the response to typed classes you can use the built-in Json.NET package in Orchard Core and for instance, use the JsonConvert.DeserializeObject method of it. And that's it, now do what you want with the data.

Did you like this post? It's part of our Orchard Core Nuggets series where we answer common Orchard questions, be it about user-facing features or developer-level issues. Check out the other posts for more such bite-sized Orchard Core tips and let us know if you have another question!

5 Comments

  • Wei said Reply

    Nice revealing post! Any way to handle a file transfer as discuss in the above issue?

    • Gábor Domonkos said Reply

      You can upload files to an external service by adding your own custom task to do the job for you. You can get the files from your media library using Liquid expressions and then you can work with them.
      But you can also download files from an external service to your media library for example in your custom activity.

  • Anasazi said Reply

    It would be nice to see you get all the 100 posts in the workflow, to see you using the loop and the create content with a list

    • Gábor Domonkos said Reply

      You can use a For Loop task or a While Loop task of course, but it could result in a little bit more difficult workflow type to construct. Because if you have a While Loop, you need to maintain a bool variable and set its value true or false based on if there are more items to get from the API. And for the For Loop, you need to know the number of items to get from the API. And for that, you need to get the number of items from the API before starting getting the items.
      I may implement a custom activity that is responsible to fire a background task that is responsible to get the posts there. I think that's the easiest one.

      • Weiyang said Reply

        Do you mind doing a demo for the foreach, for and while loop tasks?

Add a Comment