I was upgrading a quite mature ASP.NET Core 1.0 project to ASP.NET Core 2.0 today and ran into an odd issue which took me a good half hour to track down. After working my way through the various breaking changes to things such as authorisation I was pretty much at the point where my project was compiling again.
My last change was to fix-up the migrations in the project due to changes in the NpgSql project as detailed in their migration documentation. The last step there is to run in a dummy migration to the project. I attempted to run in the following command…
dotnet ef migrations add "dummy migration"
This started to run and then threw an error and dumped out a stack trace. I won’t include it all here but the higher frames are as follows…
StructureMap.Building.StructureMapBuildException: Failure while building 'Lambda: Invoke(value(StructureMap.ContainerExtensions+>c__DisplayClass9_0).descriptor.ImplementationFactory, IContext.GetInstance())', check the inner exception for details 1.) Lambda: Invoke(value(StructureMap.ContainerExtensions+<>c__DisplayClass9_0).descriptor.ImplementationFactory, IContext.GetInstance()) 2.) Instance of DbContextOptions<ReportingDbContext> (System.Object) 3.) Container.GetInstance(DbContextOptions<ReportingDbContext>) 4.) Lambda: Invoke(value(StructureMap.ContainerExtensions+<>c__DisplayClass9_0).descriptor.ImplementationFactory, IContext.GetInstance()) 5.) Instance of Microsoft.EntityFrameworkCore.DbContextOptions (System.Object) 6.) All registered children for IEnumerable<DbContextOptions> 7.) Instance of IEnumerable<DbContextOptions> 8.) Container.GetInstance(IEnumerable<DbContextOptions>) ---> System.ArgumentNullException: Value cannot be null. Parameter name: connectionString at Microsoft.EntityFrameworkCore.Utilities.Check.NotEmpty(String value, String parameterName) at Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsExtensions.UseNpgsql(DbContextOptionsBuilder optionsBuilder, String connectionString, Action`1 NpgsqlOptionsAction)
The key part of the error is “Value cannot be null. Parameter name: connectionString”.
In principle this would seem like a fairly obvious problem. I must have messed something up when performing the configuration changes as part of the upgrade to ASP.NET Core 2.0. However, if I ran the application from Visual Studio, I could see the database being created and migrated (we have a Database.Migrate() call in our code). So this would suggest that in fact the configuration was working and the connection string was being read in. Odd!
At this stage I resorted to Google and found a few similar sounding issues and StackOverflow posts. However they all seemed to suggest that indeed it was due to bad configuration of some form or another. Eventually I decided to double check my csproj file reference. During the migration I’d used the NuGet package manager to upgrade the packages after first editing my csproj to use netcoreapp2.0.
Here’s an abridged example of the csproj file:
Do you see the issue?
It took me a couple of parses to spot something suspicious. At the bottom the file includes a DotNetCliToolReference to bring in the Entity Framework command line tooling. It was set to version 1.0.0 which didn’t seem quite right given that I was now on 2.0.x of most other Microsoft packages. A quick check on Nuget for the latest version showed that there was a 2.0.1 available.
I edited my csproj to change the version, saved it and tried running my migration command again. Success! This time the migration process ran as expected.
It’s easily missed (or at least it was for me) since I’d relied on the Nuget Package Manager to save me some time when upgrading my packages. This tool reference however is not shown there and is something I needed to manually update. Hopefully this saves other people scratching there heads over this error message in the future!
In my last post – An introduction to HttpClientFactory – I explained some of the reasons behind the creation of the feature. We looked at what problems it helps solve and then followed a very basic example showing how it can be used in a WebAPI application. In this post I want to dive into two other ways we can make use of it; returning named clients and typed clients.
IMPORTANT NOTE: the features shown here require the current nightly builds of the SDK and the .NET Core and ASP.NET Core libraries. I won’t cover how to get those in this post. Treat this as an early preview of how the feature will work so that you can begin planning where and how you will use it once 2.1 is publicly available. Unless you have an urgent need to try this out today, I’d recommend waiting until the 2.1 previews are released, hopefully within the next month or so.
This should all be correct as of ASP.NET Core 2.1 RC1
In the first post, I demonstrated how we could use the HttpClientFactory to get a basic HttpClient instance. That’s fine when you just need to make a quick request from a single place in your code. Often though you’ll want to make multiple requests to the same service, from multiple places in your code.
HttpClientFactory makes this slightly easier by providing the concept of named clients. With named clients, you can create a registration which includes some specific configuration that will be applied when creating the HttpClient. You can register multiple named clients which can each come pre-configured with different settings.
To make this slightly more concrete, let’s look at an example. In my Startup.ConfigureServices method I’ll use a different overload of the AddHttpClient extension method which accepts two additional parameters. A name and an Action delegate taking a HttpClient. My ConfigureServices looks like this:
The first string parameter is the name used for this client registration. The Action<HttpClient> delegate allows us to configure our HttpClient when it is constructed for us. This is pretty handy as we can predefine a base address and some known request headers for example. When we ask for a named client, a new one is created for us and it’ll have this configuration applied each time.
To use this we can ask for a client by name when calling CreateClient as follows:
In this example, we now have an instance of a HttpClient which has the base address set, so our GetStringAsync method can pass in the relative URI to follow the base address.
This named approach gives us some control over the configuration applied to the HttpClient which we receive. I’m not a huge fan of the magic strings here so if I were using named clients I’d likely have a static class containing string constants for the names of the clients. Something like this:
When registering (or requesting) a client we can then use the static class values, instead of the magic string:
This is pretty nice, but we can go a step further and look at using a custom typed client instead.
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. This approach also means we no longer have magic strings and seems quite reasonable.
Let’s look at a basic example. We’ll start by defining our custom typed client class:
This class needs to accept a HttpClient as a parameter on its constructor. For now, we’ve set a public property with the instance of the HttpClient.
We then need to register this in ConfigureServices as follows:
We pass our MyGitHubClient type as the generic argument to AddHttpClient. This will be registered with a transient scope in DI. As our custom typed class accepts a HttpClient this will be wired up within the factory to create an instance with the appropriately configured HttpClient injected in. We can now update our controller to accept our typed client instead of an IHttpClientFactory:
Since our custom typed client exposes its HttpClient as a property we can use that to make HTTP calls directly.
Encapsulating the HttpClient
The final example I want to look at in this post is a case where we want to encapsulate the HttpClient entirely. This approach is most likely used when we want to define methods which handle specific calls to our endpoint. At this point, we could also encapsulate the validation of the response and deserialisation within each method so that it is handled in a single place.
In this case, we’ve stored the HttpClient that gets injected at construction in a private readonly field. Instead of dependants of this class accessing the HttpClient directly, we have provided a GetRootDataLength method which performs the HTTP call and returns the length of the response. A trivial example but you get the idea!
I also updated the typed client to inherit from an interface. We can now update the controller to accept and consume the interface as follows:
We can now call the GetRootDataLength method as defined on our interface, without needing to interact with a HttpClient directly. Where this really shines is testing, we can now easily mock our IMyGitHubClient when we want to test this controller. Testing HttpClient in the past was a bit of a pain and took more lines of code than I generally like to provide a suitable mock.
To register this in our DI container our call in ConfigureServices becomes:
The AddHttpClient has a signature which accepts two generic arguments and wires up DI appropriately.
In this post, we’ve explored some of the more advanced ways we can use the HttpClientFactory feature which allows us to create different HttpClient instances with specific named configurations. We then looked at the option of using typed clients which extends this to further support implementing our own classes, which accept a HttpClient instance. We can either expose that HttpClient directly or encapsulate the calls to the remote endpoint within this class.
In the next post we’ll take a look at another pattern we can use to apply an “outgoing request middleware” approach using DelegatingHandlers.
A new HttpClientFactory feature is coming in ASP.NET Core 2.1 which helps to solve some common problems that developers may run into when using HttpClient instances to make external web requests from their applications.
This blog post has been in the works since mid-October 2017, which was when I first noticed the new HttpClientFactory repository appear on GitHub. I was intrigued by its appearance and wondered what the ASP.NET team were up to, so I went diving into the available code that the repo contained at the time. I’ve then kept an eye on it ever since, watching as the team evolved the feature by reading the commits, issues and pull request discussions.
Recently the feature has started to be talked about more openly and was included in a recent talk by Damian Edwards and David Fowler at NDC London. In fact on the day of writing this introduction it’s been shown on both Jeff Fritz’s livestream show and the ASP.NET Community Standup. The opinion of Ryan Nowak, one of the main ASP.NET developers for the feature, is that it’s reasonably stable to begin writing about it now.
NOTE: Please bear in mind that this post is written prior to the official preview release of .NET Core 2.1 by using the nightly builds of ASP.NET Core 2.1 and the .NET Core SDK. Therefore, things may change before and during the public previews (hopefully we’ll get these within the next month) and also before the final release of 2.1 based on feedback received from those previews.
What is HttpClientFactory?
In the words of the ASP.NET Team it is “an opinionated factory for creating HttpClient instances” and is a new feature coming with the release of ASP.NET Core 2.1. Depending on your past experience using HttpClient, you may or may not be aware of some of the pitfalls that can be encountered, sometimes without even being aware that you have a problem.
The first issue is when you create too many HttpClients within your code which can in turn create two problems…
It’s inefficient as each one will have its own connection pool for the remote server. This means you pay the cost of reconnecting to that remote server for every client you create.
The bigger problem you can have if you create a lot of them is that you can run into socket exhaustion where you have basically used up too many sockets too fast. There is a limit on how many sockets you can have open at one time. When you dispose of the HttpClient, the connection it had open remains open for up to 240 seconds in a TIME_WAIT state (in case any packets from the remote server still come through).
HttpClient implements IDisposable and this often leads developers to follow the normal pattern when using an IDisposable object, creating it within a using block. This ensures that the object is properly disposed of once you’re done with it and it has gone out of scope. If you want to read more about this, it is well documented by the ASP.NET Monsters in their post “You’re using HttpClient wrong and it’s destablizing your software”.
A preferred approach therefore it to reuse HttpClient instances so that connections can also be reused. HttpClient is a mutable object but as long as you are not mutating it, it is actually thread safe and can be shared. A common approach is therefore to register it as a singleton with a DI framework or to create a wrapper around it which holds a static instance.
However, this creates a new problem. Using a single HttpClient in this way will keep connections open and not respect the DNS Time To Live (TTL) setting. Now the connections will never get DNS updates so the server you are talking to will never have its address updated. This is entirely possible in some situations where you are balancing over many hosts that may go away over time or perhaps rolling out new services using blue/green deployments. If the server is gone, the IP your connection is using may no longer respond to requests that you make through the single HttpClient. You can read more about this issue at “Singleton HttpClient? Beware of this serious behaviour and how to fix it” and “Singleton HttpClient doesn’t respect DNS changes”.
HttpClientFactory is designed to help start solving these problems and provides a new mechanism to create HttpClient instances that are properly managed for us behind the scenes. It will “do the right thing” for us and we can focus on other things! While the above problems are mentioned in reference to HttpClient, in fact the source of the issues actually occurs on the HttpClientHandler, which is used by HttpClient. The HttpClientFactory manages the lifetime of the handlers so that we have a pool of them which can be reused, while also rotating them so that DNS doesn’t get stale.
The expensive part of using HttpClient is actually creating the HttpClientHandler and the connection. Having these pooled in this manner means we can get more efficient use of the connections on our system. When you use the HttpClientFactory to request a HttpClient, you do in fact get a new instance each time, which means we don’t have to worry about mutating it’s state. This HttpClient may (or may not) use an existing HttpClientHandler from the pool and therefore use an existing open connection.
By default, each new HttpClientHandler (which derives from HttpMessageHandler) will be created with an active lifetime of 2 minutes. This can be controlled on a per named client basis when creating it’s handler chain. Once the lifetime is reached, the handler will not be immediately be disposed of and will instead be placed into the expired pool. Any clients depending on the original handler chain can continue using it without any issues. There is a background job checking the expired pool to see if all references for the handler have gone out of scope, at which point it can then be disposed of. Any new requests for a new client once the handler chain has been expired will get a new handler chain.
This works reasonably well, but there are other things underway on the .NET Core side which might improve the situation further. The .NET Core team are working on a new ManagedHandler which should manage DNS more correctly and in principle can be kept around for longer, meaning connections can be shared even more efficiently. This new handler is also being designed to function more consistently across the different operating systems. Until that work is completed (which might be in the 2.1 time frame) the pooling of handlers above is a reasonable workaround.
How to use HttpClientFactory
IMPORTANT NOTE: The features and code samples shown here require the current nightly builds of the SDK and the .NET Core and ASP.NET Core libraries. I won’t cover how to get setup to use those in this post. Treat this as an early preview of how the feature will work so that you can begin planning why, where and how you will use it once 2.1 is publicly available. Unless you have an urgent need to try this out today, I’d recommend waiting until the 2.1 previews are released, hopefully within the next month or so.
In this post I’ll concentrate on one of the most basic ways to get started with the HttpClientFactory. For this example, we’ll start by creating a simple WebAPI project and then edit the csproj file to upgrade it to use the new .NET Core and ASP.NET Core 2.1 bits. First we need to set it to be based on netcoreapp2.1 (not yet in official preview) and then include two packages which we’ll need. For this post I’m pinning those to specific preview nightly build versions available on the ‘dev’ MyGet feeds. After doing this our project file looks like this:
Next we need to head over to our Startup.cs file and register a service. The HttpClientFactory includes various ServiceCollection extensions. The one we’ll use for this example is:
Behind the scenes this will register a few required services, one of which will be an implementation of IHttpClientFactory. Next we’ll update the default ValuesController to make use of this feature:
Here we are first adding a dependency on IHttpClientFactory which will be injected into our controller by the DI system. The IHttpClientFactory allows us to ask for and receive a HttpClient instance.
Within our Get action we are then using the HttpClientFactory to create a client. Behind the scenes the HttpClientFactory will create a new HttpClient for us. But wait, didn’t I say earlier that using a new HttpClient for each request is bad? Indeed I did; but in fact that was a little bit of misdirection. The HttpClient itself is not really the problem, it’s the HttpClientHandler which it uses to make the HTTP calls that is the actual issue. It’s this which opens the connections to the external services that will then remain open and block sockets, even in the main HttpClient is disposed of.
HttpClientFactory pools these HttpClientHandler instances and manages their lifetime in order to solve some of the issues I mentioned earlier. Each time we ask for a HttpClient, we get a new instance, which may (or may not) use an existing HttpClientHandler. The HttpClient itself it not too heavy to construct so this is okay.
Once created the HttpClientHandlers are pooled and held around for around 2 minutes by default. This means that any new requests for CreateClient may share a handler and therefore the connections also. While a HttpClient lives, it’s handler will remain available and again this will share the connection.
After the two minutes, each HttpClientHandler is marked as expired. The expired state simply marks them so that they are no longer used when creating any new HttpClient instances. They are not immediately disposed however, as other HttpClient instances may be using them. The HttpClientFactory uses a background service which monitors the expired handlers and once they are no longer referenced, can then dispose of them properly, allowing their connections to be closed also.
This pooling feature helps reduce the risk of socket exhaustion and the refreshing process helps solve the DNS update problem by ensuring we don’t have long lived instances of HttpClientHandlers and connections hanging around. It’s a reasonable compromise which is managed for us by making use of the HttpClientFactory feature.
I’ll leave it there for this introductory post. In future posts I’ll dive into some of the more advanced ways we can use HttpClientFactory as there’s some nice features so show off. We’ll look at how we can create named HttpClient instances with configuration and also creating our own typed clients. This is where the feature will really begin to shine. Hopefully you’ll have seen, even in this basic example, how it improves use cases where you have a requirement to make HTTP calls in the most correct and efficient way. We don’t need to think about how we manage the lifetime of the clients or worry about running into DNS issues. I’m looking forward to using this in production once ASP.NET Core 2.1 is released.
On Saturday (20th January) we held a special .NET South East event, spending the day ‘coding for the greater good’ on the Humanitarian Toolbox allReady project. We were very excited to be joined by Richard Campbell, one of the co-founders of Humanitarian Toolbox and co-host of the popular .NET Rocks Podcast. A team of 19 volunteers joined us to contribute towards the project during the day, all first time contributors to the project. For many, this was also their first time working on an open source project and GitHub.
The possibility of running a codeathon came together quite recently and once I was able to arrange with Richard for him to join us, I kicked the planning into high gear. The first problem when trying to host an event like this is usually finding a suitable venue. In my case this was not a problem as Madgex, my employer, are very supportive of these events and were immediately open to the idea of hosting it. We have three neighbouring meeting rooms which can be opened up into one larger space. We do this for our monthly .NET South East meetups too and it creates a very reasonable working area.
We picked the date around Richard’s travel plans for NDC London. Richard and Carl were recording their shows at the event and Richard had a day spare following the conference which was ideal for hosting the codeathon. I created a meetup event to allow people to begin reserving their place at the codeathon. Initially I set a limit of 14 spaces until I’d had chance to fully assess the logistics of running the event. In the end we raised this to 18 spaces.
So we now had a date and a venue planned; the final thing I started to put into place was food. I wanted to ensure that our contributors didn’t run out of steam too early in the day and as we all know, the best fuel for developers is pizza!! Madgex kindly agreed to pay for the pizzas and also for some post event catering to re-fuel before our regular meetup began.
Organising GitHub Issues
After the Christmas holidays I started to organise the issues in the project. allReady has evolved over the last few years into a quite large application. As a result, the complexity level for those starting out with the project has increased. When running a codeathon I am conscious that for some people, everything may be new and so I wanted to try to make the barrier of entry as low as possible. Part of this includes having a good range of smaller issues that can be easily tackled by first-timers.
Fortunately, one of the things we had recently been focusing on is the need to improve the user experience (UX) of the application. The application is very much functional, but not always intuitive for a new user. As a result of this I spoke to a friend of mine, Chris, a UX designer working in Brighton. We spent a couple of hours reviewing the application and during that session he found a number of small, but neccesary changes we could make to give us some quick win improvements to the UX.
After that meeting I came away with a lot of subtle changes that I was able to create issues for; perfect for the codeathon. These may not see very exciting on the surface but they will really help improve the usability of the application. They were also small enough that they can easily be tackled in one day as someone new to the project finds their feet. I hoped these could be good gateway issues before people started to tackle more complex feature requirements.
In the week leading up to the event I spent time organising the final logistics, such as ensuring we would have sufficient power connections for all of the laptops. I was also able to increase the RSVP limit as we wanted to make the most of the opportunity and allow as many members of our community to contribute. I also reached out to some local charities to see if they would be interested in attending the event to see how the application may be able to help with their own requirements.
I am very thankful to some of my colleagues at Madgex who were also helping to make sure we had everything in place ready for the weekend. This was particularly useful as I was at the NDC London conference the three days prior to the event so couldn’t be there in person to perform any last minute preparations.
On the day of the codeathon I set off by train to Brighton. Once there, my first stop was to pick up Richard from his hotel. Dan, the organiser of .NET Oxford was also staying at the same hotel and would be joining us for the event. It was great to finally meet Dan in person after many months of chatting via Twitter about running our user groups.
Together, the three of us set off for the Madgex office. One of my colleagues, Chris was already there and had been helping out by letting the early arrivals into the building.
The next 45 minutes to an hour were spent organising the meeting room space. Again, my colleagues had been very helpful, having set up a few things the day before. We just needed to ensure we had the suitable power points for the numerous devices we’d be running on the day. Ricky, our IT support technician at Madgex had kindly come in on his weekend to help with the setup and to be on hand for any technical challenges.
The setup went very smoothly and soon we had most of volunteers for the day settled in. Nearly everyone had been able to get the project cloned onto their laptops and tested prior to the event. This is extremely useful and meant that we were ready to start the event and begin coding with very little delay. At codeathons this really does help with the productivity as we can focus on code, rather than machine preparation.
At about 9:20am we were all ready to begin. We started with a short introduction from Richard who shared the history behind Humanitarian Toolbox and the goals of the allReady project we would be working on. Our volunteers listened with rapt attention and it was fantastic to have Richard with us to share his passion for the charity he has founded.
After Richard finished his introduction, I spent a few minutes speaking about the technical stack and the basic flow for working on issues and submitting pull requests. I find it useful to review this flow before starting, especially when we have some first time open source contributors in the room. In hindsight I probably needed to mention a few other things to make it easier for people to get going which I’ll include in my slide deck for future events.
With the introductions complete, we commenced coding. Everyone was heads down very quickly, choosing issues to work on and producing code to address them. As expected, there were quite a few questions along the way and I hope I was able to help everyone get started without too much of a delay. There’s a lot to take on and learn at an event like this and I thank everyone for being very patient as I worked through the various questions.
In under 1 hour we had had our first pull request to the project and after that the flood gates opened and more streamed in. I did my best to keep up with the requests, performing code reviews before merging them into the project. Our AppVeyor account was struggling under the load a little. Each pull request to the project triggers a build on the AppVeyor system. This is very useful to be able to verify that the code included in the PR builds and that the tests all pass. As we had many PR’s coming in concurrently, it did start to creak at the seams a little. Worth noting for future events to see if we can increase the capacity of the account.
We had a brief lunchtime break for pizza at around 12:45pm, which was a good chance for people to break from their screens and chat about the progress so far. The morning had flown past at a great rate and already the team had achieved a great deal. The team were eager to get going again and before long were back at their laptops, working on the next set of pull requests!
During the afternoon we had organised a series of User Experience (UX) user testing sessions. We are conscious that the project has been developed mostly by backend developers and as a result, while functional, the User Interface (UI) and UX of the project leave something to be desired. This is now a focus on the project to see what we can change and improve to make it as easy to use as possible. A big thanks to my friend Chris for joining us to run the user testing sessions and to our willing test subjects, Jenny, Zen and my wife Rhiannon. It proved really useful and we now have a number of good suggestions from Chris for changes that we can make to resolve some difficulties identified during the testing.
The afternoon seemed to go even faster than the morning, with more PRs being made as people became more familiar with the project and the workflow. By 5pm we had made significant progress and it was time to wrap up the event. We concluded with some sandwiches provided from a local caterer which were very welcome after our busy afternoon.
Including myself and Richard we had 17 people working on the project all day, and a further 4 contributors for the additional work being done in the afternoon to perform the UX testing. This was a really great turnout and it meant we could get through a lot of work in a relatively short space of time. I can’t thank everyone enough for coming along and helping to make the day such a success. On reflection, the number we had was just about right. Any more would have been harder for me to support without delaying people.
In total we had 30 pull requests opened during the event. That’s thirty issues within the project being addressed and fixed which is pretty incredible. That may be close to a record for a single day Humanitarian Toolbox event! I was able to review and merge 18 of those during the day as well, which means that code is already active in the project. I will endeavour to get through the reviews of the remaining 12 PRs as soon as possible.
How can you help?
If you like the sound of this project and this style of event, we’d love for people to join us in contributing to allReady. For those that took part in the codeathon, we hope many will continue contributing to the project too. During the next couple of months there is a global Microsoft MVP (Most Valuable Professional) event running which is a virtual codeathon to get Microsoft MVPs from around the world contributing to the project. I am helping to organise and run that event and hope to see lots of activity on the project leading up to the Global MVP summit event in March.
This is a great time for newcomers to join the project as we have lots of experts on hand to support you and help you get started. The best place to start is to visit the GitHub project repository. From there you can view the open issues and jump in wherever you feel comfortable. If you need support, just let us know and we can help out.
I have started a series of videos explaining how you can get started with open source contributions and showing the technical steps. You can view these on my YouTube channel.
I’d like to wrap up with another huge thank you to everyone who helped in some way with organising this event and especially to those contributors who took part during the day. It was a great showing from the community and I hope everyone enjoyed the day as much as I did. The aim of the event was to introduce people to the project and get them past the learning curve for contributing to an open source project. A big thanks to Madgex for supporting the event with the use of their meeting rooms, as well as for providing some food to keep the troops fuelled up. In my opinion it was a huge success and I hope that we can arrange future events to continue the good work from everyone who contributed.
The first step when contributing to a project is to visit the project site and find an issue you would like to work on and which you think is suitable for your skill set. From the project homepage on GitHub you can click the Issues tab to navigate to a list of the open issues.
As a first time contributor you will ideally want to find something small and relatively straightforward to use as a nice entry into the project, before trying to tackle larger more complex issues. Don’t try to dive in too deep on your first few contributions!
Many projects will label their issues and this is often a good way to filter down the issues list to ones that you might want to work on. On the allReady project we have a “good first issue” label and we also use the “upforgrabs” label convention. “upforgrabs” is a label projects can use to highlight available issues, usually ones which are good for new contributors. There is a master site which scrapes these and provides a way to find issues that you can contribute to across many projects on GitHub.
To view a list of the available labels you can click on the “Labels” button in the UI
From the labels view, you can scroll and find a label to work on.
From this list for allReady, “good first issue” sounds like a reasonable candidate for newcomers so we’ll click on that. This will result in a filtered view of issues, showing only those which have this label applied to them.
In this example, there is one issue now showing. We can click on that issue to view more detail and to determine if it’s something we’d like to work on.
The issue details page provides the full information about the issue. Usually the top comment will include details of the bug or the feature that is needed. Issues can be raised by anyone and as a result, the level of detail may not always be sufficient to understand the problem or requirement. On allReady, the project owners and core contributors try to view new issues and triage them. This involves verifying the issue being reported is valid and where necessary, providing some further details or guidance. If it’s not clear what is needed from an issue, you can leave a comment to ask questions about it. If you have an idea for a solution, but want to run it past the project team before starting, work, you can leave a comment for that too. Issues are a good place for open discussions like this.
In this example, the requirement is quite clear and the solution should be very simple; we’d like to have a go at working on this issue. It’s good practice and etiquette to leave a comment an any issues you plan to work on so that other people know it’s no longer available. Having two people spending their time on the same issue can be frustrating. It’s also worth pointing out that you should check for any comments indicating that someone else is already working on an issue before you pick it up yourself!
Working on an Issue
When beginning work on an issue locally, the first thing you’ll need to do is to create a branch for that piece of work. There are many Git UI tools that allow you to create a branch, for this demo we’ll use the command line. To create and checkout a branch you can use a single command.
git checkout -b <branchname>
This command allows us to specify a name for our new branch and immediately check it out so we can work on it. The naming convention you use for your branches is up to you. They will live in your clone and fork of the project. Bare in mind, once pushed to your public fork and when you later submit a pull request, they will be public. I tend to use the issue number for my branch names. Personally I find this works quite well since the branch names are short and I can easily lookup the issue details on GitHub to see the requirements. In this case, the issue we’ve selected is issue #2204 so I’ll use that for my new branch name.
git checkout -b 2204
Once we are on our new branch we can make changes to the code which address the issue. I won’t show that here, but for this issue I opened up the markdown file and made the appropriate fix to remove the duplicated text using VS Code. You can use any tools you like at this stage to work on the code and files.
Once we have made the required changes that address a particular issue, we need to commit that code to our branch. We can use the “git status” command to view the changes since our last commit.
In our example above, only one file has changed. We then use the “git add” command to stage the changes for the next commit. As we have one modified file we can use the following command:
git add .
This stages any new or modified files from our working tree.
Next we will commit our staged changes using the “git commit” command. In this case we can use the following example:
git commit -m "Fixed duplicate text"
The -m option allows us to specify a message for our commit. It’s good practice to try and provide a succinct, but descriptive message for your commits. This helps a reviewer understand at a high level what was addressed in each commit.
At this point we have made and committed out changes local to our development machine. Our final step is to push the changes to our fork of the allReady repository up on GitHub. We can do that using the “git push” command. We need to specify the name of the remote that we want to push to and the name of the branch we want to push up. In our example, the command looks like this:
git push origin 2204
This pushes our local 2204 branch to the origin remote, which is the fork of the allReady project which we created on GitHub.
At this stage we have selected an issue to work on and begun that work locally on a new branch of the code. Once we had completed our changes we are able to commit those and push them up to our fork of the code on GitHub. In the next post we’ll look at how we create a pull request in order to submit our change to the project for inclusion.
If you are a visual learner, then I have a video which covers the topics in this post available up on my YouTube Channel