Automating the use of APIs with .NET

Buse Nur Şahin
4 min readMay 9, 2024

--

How can we automate converting a JSON object from an existing API source into a C# model? How can we automatically create the HttpClient class that this model will use to connect to the API?

Photo by Kristine Tumanyan on Unsplash

Hello! As .Net developers well know, scaffolding is a lifesaver in tasks like CRUD operations, Identity management, and using Entity Framework with a Database First approach. A look at the aspnet-codegenerator documentation shows that there’s almost nothing we can’t generate in the world of .Net Web Applications.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/tools/dotnet-aspnet-codegenerator?view=aspnetcore-7.0#arguments

In scenarios where we have database tables, we know how quickly a project can start up when we include identity in our setup, generate our models with db first, and auto-generate controller and view classes for CRUD operations based on the entities.

What happens if our data source is an API instead of a database?

Since our data source has changed, we need to switch from DB-First to API-First management. Instead of connecting to a database and reading tables and fields, we’ll need to create classes and attributes by understanding JSON result content. (There are tools developed for this, but I want to discuss the method I use here. In this article, we’ll work with a simple API response.)

API RESPONSE

Let’s say we provide the endpoint as an argument to our program. With the following code structure, we can get the API response and access the JSON object within the content.

HttpClientHelper clientHelper = new HttpClientHelper()
string jsonString = await clientHelper.GetJson(endPoint);

Deserializing the JSON object into a JObject.

Using Newtonsoft.Json, we parse the JSON text to make it more meaningful. Since we don’t know which type to parse it into — because our goal is to create this type — we parse it into a JObject so we can access class attributes and their types.

JArray jArray = JArray.Parse(jsonString);
JObject jsonObject = jArray.Children<JObject>().First();

Generating a class from the JObject attributes.

Essentially, creating a class means writing code to a file, which we can automate with I/O operations and a few settings.

For example, instead of manually typing each property, we can use a method that automatically creates a line for every attribute by reading the name and type from the JObject.

public class PropertyBuilder
{
public string GetProperty(Newtonsoft.Json.Linq.JProperty jToken)
{
string propertyName = char.ToUpper(jToken.Name[0]) + jToken.Name.Substring(1);
if (jToken.Value.Type == Newtonsoft.Json.Linq.JTokenType.Integer)
{
return $" public int {propertyName};";
}

return $" public {jToken.Value.Type} {propertyName};";
}
}

If you have experience with controller scaffolding in MVC, you’ll remember that the data comes from a database and progresses through a template using Entity Framework. Here, we’ve created our model classes, but we don’t yet have a layer to connect data to these models.

Extensible Client Structure

Here, our data source has changed and we’re not using an ORM. So, can’t we generate an intermediary layer to fetch data from this source? We need to set up an extensible structure so that it can handle basic CRUD operations whenever a new entity is added. (In this example, we’ll only implement the GetList operation.)

public interface IClientService<T> where T : class, new(
{
Task<IEnumerable<T>> GetAll(string endPoint);
})

Let’s implement this interface using the Generic Design Pattern.

public class ClientService<T> : IClientService<T> where T : class, new(
{
public async Task<IEnumerable<T>> GetAll(string endPoint)
{
HttpClient client = new HttpClient();
string responseBody = "";
using HttpResponseMessage response = await client.GetAsync(endPoint);
response.EnsureSuccessStatusCode();
responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<T>>(responseBody);
}
}

Generating a Client Service

When the model is created, we can generate a Client Service for that model, derived from the main Client Service and responsive to the GetAll method. This allows us to pull the API response and map it to our object in one move.

string endPoint = "https://api.thecatapi.com/v1/images/search";
string ns = "Cat.Web.Models";
string typeName = "Cat";

HttpClientHelper clientHelper = new HttpClientHelper();
string jsonString = await clientHelper.GetJson(endPoint);
JArray jArray = JArray.Parse(jsonString);
JObject jsonObject = jArray.Children<JObject>().First();

//JObject.Parse
string savePath = Path.Combine(Constants.TargetFolder, "RazorPageGenerator");
using (StreamWriter streamWriter = new StreamWriter(Path.Combine(savePath, $"{typeName}.cs")))
{
streamWriter.WriteLine($"using System;");
streamWriter.WriteLine($"using System.Linq;");
streamWriter.WriteLine();
streamWriter.WriteLine($"namespace {ns};");
streamWriter.WriteLine();
streamWriter.WriteLine($"public class {typeName}");
streamWriter.WriteLine("{");
var propertyBuilder = new PropertyBuilder();
foreach (var property in jsonObject.Properties())
{
var prop = propertyBuilder.GetProperty(property);
streamWriter.WriteLine(prop);
}

streamWriter.WriteLine("}");
}

using (StreamWriter streamWriter = new StreamWriter(Path.Combine(savePath, $"{typeName}ClientService.cs")))
{
streamWriter.WriteLine($"using System;");
streamWriter.WriteLine($"using System.Linq;");
streamWriter.WriteLine($"using {ns}.Services;");
streamWriter.WriteLine();
streamWriter.WriteLine($"namespace {ns};");
streamWriter.WriteLine();
streamWriter.WriteLine($"public class {typeName}ClientService : ClientService<{typeName}>");
streamWriter.WriteLine("{");
streamWriter.WriteLine("}");
}

The only thing left to do is to create Razor pages, which the aspnet-codegenerator handles.

After scaffolding the models and Client Services, using the code-generator to create Razor pages seems to move the project from ‘manually creating models’ to ‘launching with a few commands.’ What do you think?

Thanks for reading. Best regards

--

--

No responses yet