Fetching GitHub content from C#
One of the many advantages of storing project assets on GitHub is that you can access them both manually and programmatically from anywhere. At elmah.io, we are using this for documentation, code snippets, and more. Instead of placing documents across multiple systems, we version them right alongside the code that uses them. Then, our applications retrieve the files at runtime through the GitHub API. In this post, I'll show the process of integrating with GitHub from C#. The example fetches Markdown files and converts them to HTML, but the file types could be anything.

Let's jump straight down into some code. To demonstrate how to fetch data from GitHub, I'll build a small service that generates a nice HTML changelog based on Markdown files in a GitHub repository.
GitHub exposes file contents through its REST API, where you make requests similar to this to fetch a file:
GEThttps://api.github.com/repos/{owner}/{repo}/contents/{path}
The response includes metadata and a Base64‑encoded content property. To access private repositories or increase rate limits, you need a personal access token (PAT). In the example below, I'm hardcoding a PAT, but never do that in real code. Use environment variables, secrets, or similar.
To communicate with the GitHub API, we start by registering a new HttpClient. For this example, I'm using an ASP.NET Core website, so this goes in the Program.cs file.
builder.Services.AddHttpClient("GitHub", client =>
{
client.BaseAddress =
new Uri("https://api.github.com/repos/myord/myrepo/contents/");
client.DefaultRequestHeaders.UserAgent.ParseAdd("MyGitHubClient/1.0");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("token", "MyToken");
client.Timeout = TimeSpan.FromSeconds(10);
});
I also register a memory cache to cache results and avoid hitting GitHub's rate limit, and to improve performance:
builder.Services.AddMemoryCache();
Finally, I register a GitHubContentService. This class hasn't been developed yet, but we'll do that next:
builder.Services.AddTransient<GitHubContentService>();
Let's implement the content service for communicating with GitHub. The service fetches files, decodes them, and caches the results. We also need to list available files in a directory. Here's a simple implementation:
using System.Text.Json;
using System.Text;
using Markdig;
using Microsoft.Extensions.Caching.Memory;
public class GitHubContentService(IHttpClientFactory clientFactory, IMemoryCache cache)
{
public async Task<string?> GetFileHtmlAsync(string path)
{
if (cache.TryGetValue(path, out string html))
{
return html;
}
var client = clientFactory.CreateClient("GitHub");
var response = await client.GetAsync(path);
if (!response.IsSuccessStatusCode) return null;
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
var base64 = doc.RootElement.GetProperty("content").GetString();
var markdown = Encoding.UTF8.GetString(Convert.FromBase64String(base64!));
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
html = Markdig.Markdown.ToHtml(markdown, pipeline);
cache.Set(path, html, TimeSpan.FromHours(1));
return html;
}
public async Task<List<string>> ListFilesAsync(string directory)
{
var client = clientFactory.CreateClient("GitHub");
var response = await client.GetAsync(directory);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var docs = JsonDocument.Parse(json);
return docs
.RootElement
.EnumerateArray()
.Select(e => e.GetProperty("name").GetString()!)
.Where(name => name.EndsWith(".md"))
.OrderByDescending(n => n)
.ToList();
}
}
The GetFileHtmlAsync method fetches a file, decodes the Base64-encoded content, and converts it from Markdown to HTML using Markdig. The result is cached to reduce calls to GitHub. ListFilesAsync lists all Markdown files in a directory, allowing you to build an index page.
Before building the code, you will need to install the Markdig NuGet package or replace it with the Markdown package of your choice:
dotnet add package Markdig
With the content service in place, we can start building our release note feature. Let's imagine we keep release notes in a release-notes folder in a GitHub repository. Each release is a Markdown file named by date, like 2025-11-11.md (ISO 8601, of course). The same pattern can be applied to guides, code samples, or documentation.
In the ASP.NET Core application, add a new NotesController to display all release notes and individual entries:
using Microsoft.AspNetCore.Mvc;
public class NotesController(GitHubContentService service) : Controller
{
public async Task<IActionResult> Index()
{
var files = await service.ListFilesAsync("release-notes");
return View(files);
}
public async Task<IActionResult> Show(string id)
{
var html = await service.GetFileHtmlAsync($"release-notes/{id}");
if (html == null)
{
return NotFound();
}
ViewData["Title"] = id.Replace(".md", "");
return View((object)html);
}
}
The controller has one endpoint to show the list of release notes and another to show a single release.
In the Index.cshtml view, display a list of available release notes:
@model IEnumerable<string>
<h1>Release Notes</h1>
<ul>
@foreach (var file in Model)
{
<li><a asp-action="Show" asp-route-id="@file">@file.Replace(".md", "")</a></li>
}
</ul>
In the Show.cshtml view, render the HTML:
@model string
<h1>@ViewData["Title"]</h1>
<div class="markdown-body">@Html.Raw(Model)</div>
By wrapping the content in a markdown-body div and including GitHub's Markdown CSS, you ensure the release notes look polished.
That's it. When navigating to the /notes URL we see:

And when clicking on one of the release notes, the details are shown:

Although I used release notes as an example, the same service can fetch any file from your repository. On elmah.io, we fetch documentation and snippets from Microsoft's GitHub repositories to show inline documentation in the log message inspector. Another use case could be rendering blog posts from markdown files, similar to how we generated the release notes.
Remember to include caching, since GitHub imposes rate limits. Caching data will also make your code work, even though the GitHub API is down or unavailable from your server.
Fetching content from GitHub via C# is straightforward and powerful. Using a combination of HttpClient, a small helper service, and a Markdown converter like Markdig, your application can serve dynamic content in just a few lines of code. Whether you're generating automatic release notes or showing internal documentation, integrating with GitHub opens up a lot of possibilities.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website