.net core

Zero to Azure Hero with ASP.NET Core, Blazor, Azure DevOps, Cognitive Services, SonarCloud and App Services - Part 2

In part 1 of this blog post series we introduced the application we will be building.

This post will cover building out our Twitter Sentiment Analysis functionality using C#, Asp.Net Core and xUnit.net for our tests.

To get started we will create a folder for our library and tests, open your command prompt/terminal and navigate to where you like to keep your project source then run the following command to create a new folder and navigate into that folder

mkdir TwitterSentiment
cd TwitterSentiment

Now we have root folder created, lets create the projects using the .NET Core CLI. We will use the dotnet new command to create a new class library and xUnit test project. You can find a full list of the .NET CLI commands here.

dotnet new classlib -o TwitterSentiment
dotnet new xunit -o TwitterSentiment.Tests

I always like to have a solution file added as well, making it easy to use with Visual Studio, however you can skip this step if you wish.

dotnet new sln
dotnet sln add TwitterSentiment
dotnet sln add TwitterSentiment.Tests

We want our Test project to have access to the TwitterSentiment project, to do this navigate into the test project folder

cd TwitterSentiment.Tests

Then run the following to add a project reference

dotnet add reference TwitterSentiment

We will also add two libraries to help with our tests, NSubstitute and Shouldly.

NSubstitute is a simple, easy to use mocking framework for .NET Core, you can find more information an documentation here.

Shoudly is a library that provides a nice readable syntax for your unit test assertions. More information can be found here

You could of course use any assertion or mocking approach you wish however I wanted to add them to show the typical libraries I would use when writing unit tests.

dotnet add package nsubstitute
dotnet add package shouldly

We also need to add two packages to our main TwitterSentiment project to enable us to use the new HttpClientFactory so we will navigate from our test project to our TwitterSentiment project folder then install the two packages

At the time of writing, the Azure DevOps build agent we will be using doesn't support ASP.NET Core 2.2 without extra steps in our pipeline, so for the sake of simplicity we will target .NET Core 2.1 for this project. You can see in our command below we pin our Microsoft.Extensions.Http library to version 2.1.1 to ensure compatibility

cd ../TwitterSentiment

dotnet add package Microsoft.AspNet.WebApi.Client
dotnet add package Microsoft.Extensions.Http --version 2.1.1

Lets build our projects to ensure everything looks good, you can build each project separately or use the solution file we created earlier.

cd ../ 
dotnet build TwitterSentiment.sln

That should be us now ready to start writing some code! First up a Twitter client to authenticate against the Twitter API and retrieve timeline tweets for a given username. Open Visual Studio Code (or your editor of choice) in the root of your project.

To open VS Code directly from the command line for the folder you are currently in you can use code . as I have done below

cd  ../
code .

Within our TwitterSentiment project, create a new class called TwitterClient.cs with the following code

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;

namespace TwitterSentiment
{
    public class TwitterClient
    {
        public HttpClient Client { get; }

        public TwitterClient(HttpClient client)
        {
            client.BaseAddress = new Uri("https://api.twitter.com/1.1/");
            Client = client;
        }

        public async Task<List<Tweet>> GetTimeline(string username)
        {
            var response = await Client.GetAsync($"statuses/user_timeline.json?include_rts=true&screen_name={username}&count=20");

            response.EnsureSuccessStatusCode();

            var tweets = await response.Content.ReadAsAsync<List<Tweet>>();

            return tweets;
        }
    }
}

In the above code we are using the concept of a Typed Client that enables us to leverage the new HttpClientFactory functionality that shipped as part of ASP.NET Core 2.1

Steve Gordon has created a fantastic blog that digs into this new feature which you can read here but the summary lifted from his post gives us a good idea of whats happening when we use the Typed client as we have above

Typed clients allow us to define custom classes which expect a HttpClient to be injected in via the constructor. These can be wired up within the DI system using extension methods on the IHttpClientBuilder or using the generic AddHttpClient method which accepts the custom type. Once we have our custom class, we can either expose the HttpClient directly or encapsulate the HTTP calls inside specific methods which better define the use of our external service.

Now that we have our TwitterClient created, next lets create the Tweet class that we will use for deserializing our Twitter timeline data into.

We only need the Text field however the API returns a lot more information, check the documentation to see what is available if you decide to build the application out further.

Create a Tweet.cs file alongside the TwitterClient.cs with the following code:

namespace TwitterSentiment
{
   public class Tweet
    {
        public string Id { get; set; }
        public string Text { get; set; }

        public Tweet(string id, string text)
        {
            Id = id;
            Text = text;
        }
    }
}

You can build the project again to ensure everything has been added correctly.

We can now add our first unit test to test the GetTimeline method.

Within the Test project, create a new class called TwitterClientShould.cs with the following code

using Newtonsoft.Json;
using NSubstitute;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Tests.Fakes;
using TwitterSentiment;
using Xunit;

namespace Tests
{
    public class TwitterClientShould
    {
        public TwitterClientShould()
        {
        }

        [Fact]
        public async Task Get_Tweets_For_Hastag_Using_TypedHttpClient()
        {
            var tweet1 = new Tweet("1", "This is a really negative tweet");
            var tweet2 = new Tweet("2", "This is a super positive great tweet");
            var tweet3 = new Tweet("3", "This is another really super positive amazing tweet");

            var tweets = new List<Tweet> { tweet1, tweet2, tweet3  };

            var fakeHttpMessageHandler = new FakeHttpMessageHandler(new HttpResponseMessage()
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent(JsonConvert.SerializeObject(tweets), Encoding.UTF8, "application/json")
            });

            var fakeHttpClient = new HttpClient(fakeHttpMessageHandler);

            var sut = new TwitterClient(fakeHttpClient);

            var result = await sut.GetTimeline("wholeschool");

            result.Count.ShouldBe(3);
        }
       
    }
}

The above unit test creates an HttpClient injected with a fake message handler containing 3 tweets as the message content. We then pass this handler to our TwitterClient and call the GetTimeline method. Finally we assert that we have 3 tweets returned using the Shouldy assertion library.

Next we need to add our fake message handler that we can inject into our HttpClient.

Create a folder called Fakes in your test project and within that folder a new class called FakeHttpMessageHandler.cs with the following code

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Tests.Fakes
{
    public class FakeHttpMessageHandler : DelegatingHandler
    {
        private HttpResponseMessage _fakeResponse;

        public FakeHttpMessageHandler(HttpResponseMessage responseMessage)
        {
            _fakeResponse = responseMessage;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return await Task.FromResult(_fakeResponse);
        }
    }
}

Inspiration for this Fake handler is from the following blog post, you can read more there on unit testing HttpClient in Asp.Net Core

http://anthonygiretti.com/2018/09/06/how-to-unit-test-a-class-that-consumes-an-httpclient-with-ihttpclientfactory-in-asp-net-core/

Your solution should now look like the following in VS Code

Lets build our Test project and try running our test to see if it passes, from our command line, ensure you are in the test project folder.

dotnet build
dotnet test

You should see a successful result like below

FirstBuild

We are almost ready to push our project to GitHub and setup our first CI/CD pipeline on Azure DevOps

Before we do that we need to add support to our client for handling oAuth against the Twitter API. This code has been lifted from the Asp.Net Samples repository on GitHub found here https://github.com/aspnet/samples/tree/master/samples/aspnet/HttpClient/TwitterSample

You need to add the two classes (OAuthBase.cs and OAuthMessageHandler.cs found in the oAuth folder). Create a folder called oAuth and add the two classes, updating the namespaces to match the rest of your project.

https://github.com/aspnet/samples/tree/master/samples/aspnet/HttpClient/TwitterSample/OAuth

There are a few changes needed in the OAuthMessageHandler.cs class.

We no longer need the HttpMessageHandler injected and we also want to replace the hard coded strings for keys and secrets to load from configuration instead.
In your OAuthMessageHandler.cs class, replace the following code

    public OAuthMessageHandler(HttpMessageHandler innerHandler)
        : base(innerHandler)
    {
    }

with

    private readonly string _consumerKey;
    private readonly string _consumerSecret;
    private readonly string _token;
    private readonly string _tokenSecret;

    private OAuthBase _oAuthBase = new OAuthBase();

    private readonly IConfiguration _config;

    public OAuthMessageHandler(IConfiguration config)
    {
        _config = config;

        _consumerKey = _config["ConsumerKey"];
        _consumerSecret = _config["ConsumerSecret"];
        _token = _config["Token"];
        _tokenSecret = _config["TokenSecret"];
    }

You can view the final OAuthMessageHandler code from my GitHub repo here

We will show how you can populate your Twitter keys later (and where you can get these keys from)

You should now be able to build and run your tests again. If everything looks good we will start building our CI/CD pipeline in part 3 of this blog post series.