This article will guide you through the process of creating a Generic HTTP service in C# that consumes a .NET Core Web API.
To simplify things, we will create a .NET Core WEB API (BookStoreWebApi) and a .NET Core Console (BookStoreConsole) application to implement a generic HTTP Service that consumes a C# CRUD API.
You can find the BookStoreWebApi and BookStoreConsole sample applications in the GitHub repo
To consume an ABP Framework API, have a look at the mobile .NET MAUI app and .NET Core Console app in the Repo.
Warning: The Generic Http Service created in this article is definitely not production-ready, as you typically will need to add Logging, Exception Handling, Retry Logic, and ...
- .NET 8.0 SDK
- VsCode, Visual Studio 2022 or another compatible IDE
First, open a terminal and create a .NET Core API with a BooksController with the standard CRUD endpoints.
dotnet new webapi --use-controllers -o BookStoreWebApi
Copy/paste the Data/Infra and Dtos folder of the BookstoreWebApi sample project into the root of the newly created API. In the Dtos folder you find the Data Transfer Objects for sending/receiving data to/from the API.
using BookStoreWebApi.Dtos.Books;
using BookStoreWebApi.Infra;
using Microsoft.AspNetCore.Mvc;
using static BookStoreWebApi.Data.BooksResolver;
namespace BookStoreWebApi.Controllers
{
[Route("api/app/book")]
[ApiController]
public class BooksController : ControllerBase
{
[HttpGet]
public PagedResultDto<BookDto> Get([FromQuery]GetBooksDto getBooksDto) => new() { Items = BookItems, TotalCount = BookItems.Count };
[HttpGet("{id}")]
public BookDto? Get(Guid id) => BookItems.FirstOrDefault(x => x.Id == id);
[HttpPost]
public BookDto Create([FromBody] CreateBookDto createBookDto)
{
BookItems.Add(new BookDto(createBookDto.Name,createBookDto.Type,createBookDto.Price, createBookDto.PublishDate, createBookDto.Id));
return BookItems.Single(x => x.Id == createBookDto.Id);
}
[HttpPut("{id}")]
public BookDto? Put(Guid id, [FromBody] UpdateBookDto updateBookDto)
{
var bookDto = BookItems.FirstOrDefault(x => x.Id == id);
if (bookDto == null) return bookDto;
bookDto.Name = updateBookDto.Name;
bookDto.Price = updateBookDto.Price;
bookDto.PublishDate = updateBookDto.PublishDate;
bookDto.Type = updateBookDto.Type;
return bookDto;
}
[HttpDelete("{id}")]
public void Delete(Guid id)
{
var bookDto = BookItems.FirstOrDefault(x => x.Id == id);
if (bookDto != null) BookItems.Remove(bookDto);
}
}
}
Press F5
to run the API. It will be hosted on https://localhost:xxxxx
.
Open a terminal and run the command below to create a new console app.
dotnet new console -o BookStoreConsole
Open a terminal in the root of the Console app
and install the Microsoft.Extensions.DependencyInjection
NuGet package.
dotnet add package Microsoft.Extensions.DependencyInjection
Create a Services/Http folder in the root of your console application.
Copy/Paste the Infra folder of the BookStoreConsole sample application into the Services/Http folder.
Create a IHttpService.cs interface with the standard CRUD method definitions in the Services/Http folder.
using BookStoreConsole.Services.Http.Infra;
namespace BookStoreConsole.Services.Http;
public interface IHttpService<T, in TC, in TU, in TG, in TD>
{
Task<ListResultDto<T>> GetListAsync(string uri, TG? getListRequestDto = default);
Task<ListResultDto<T>> UpdateAsync(string uri, TU updateInputDto);
Task<T> CreateAsync(string uri, TC createInputDto);
Task CreateManyAsync(string uri, IEnumerable<TC> createManyInputDto);
Task<T> GetAsync(string uri);
Task DeleteAsync(string uri, TD id);
}
Create a HttpService.cs class in the Http folder that implements the IHttpService interface
using System.Net.Http.Json;
using BookStoreConsole.Services.Http.Infra;
namespace BookStoreConsole.Services.Http;
public class HttpService<T, TC, TU, TL, TD> : HttpServiceBase<TL>, IHttpService<T, TC, TU, TL, TD>
where T : class
where TC : class
where TU : class
where TL : class
{
public async Task<ListResultDto<T>> GetListAsync(string uri, TL? getListRequestDto = default)
{
if (getListRequestDto == null) return new ListResultDto<T>();
var httpResponse = await (await GetHttpClientAsync()).Value.GetAsync(ComposeUri(uri, getListRequestDto));
httpResponse.EnsureSuccessStatusCode();
var json = await httpResponse.Content.ReadAsStringAsync();
if (json == "[]" || json.IsNullOrWhiteSpace()) return new ListResultDto<T>();
if (getListRequestDto is IPagedRequestDto)
{
var pagedResultDto = json.ToType<PagedResultDto<T>>();
return new PagedResultDto<T>(pagedResultDto.TotalCount,pagedResultDto.Items);
}
return new ListResultDto<T>(json.ToType<List<T>>());
}
public async Task<ListResultDto<T>> UpdateAsync(string uri, TU updateInputDto)
{
var httpResponse = await (await GetHttpClientAsync()).Value.PutAsJsonAsync($"{uri}", updateInputDto);
var json = await httpResponse.Content.ReadAsStringAsync();
if (json == "[]" || json.IsNullOrWhiteSpace()) return new ListResultDto<T>();
if (json.StartsWith("{") && json.EndsWith("}"))
return new ListResultDto<T>(new List<T> { json.ToType<T>() });
return new ListResultDto<T>(json.ToType<List<T>>());
}
public async Task<T> CreateAsync(string uri, TC createInputDto)
{
var httpResponse = await (await GetHttpClientAsync()).Value.PostAsJsonAsync(uri, createInputDto);
return (await httpResponse.Content.ReadAsStringAsync()).ToType<T>();
}
public async Task CreateManyAsync(string uri, IEnumerable<TC> createInputDto)
{
var httpResponse = await (await GetHttpClientAsync()).Value.PostAsJsonAsync($"{uri}/many", createInputDto);
}
public async Task<T> GetAsync(string uri)
{
var httpResponse = await (await GetHttpClientAsync()).Value.GetAsync(uri);
return (await httpResponse.Content.ReadAsStringAsync()).ToType<T>();
}
public async Task DeleteAsync(string uri, TD id)
{
var httpResponse = await (await GetHttpClientAsync()).Value.DeleteAsync($"{uri}/{id}");
}
}
Create a Books folder in the Services folder of your project.
Copy/Paste the Services/Books/Dtos folder of the BookStoreConsole sample application into the Services/Books folder.
Create an IBookService.cs interface in the Services/Books folder.
using BookStoreConsole.Services.Books.Dtos;
namespace BookStoreConsole.Services.Books;
public interface IBookService
{
Task<IEnumerable<BookDto>> GetBooksAsync();
Task<BookDto?> CreateBookAsync(CreateBookDto bookDto);
// Find other method definitions in the BookStoreConsole sample project ...
}
Create a BookService class in the Services/Books folder. The BookService class gets the correct HttpService via Constructor Dependency Injection.
using BookStoreConsole.Services.Books.Dtos;
using BookStoreConsole.Services.Http;
namespace BookStoreConsole.Services.Books;
public class BookService(
IHttpService<BookDto, CreateBookDto, UpdateBookDto, GetBooksDto, Guid> httpService)
: IBookService
{
// REPLACE <the-api-port-number-here> with the port number the API is running on !!!
const string BookApiUrl = "https://localhost:<the-api-port-number-here>/api/app/book";
public async Task<IEnumerable<BookDto>> GetBooksAsync()
=> (await httpService.GetListAsync($"{BookApiUrl}", new GetBooksDto())).Items;
public async Task<BookDto?> CreateBookAsync(CreateBookDto bookDto)
=> await httpService.CreateAsync($"{BookApiUrl}", bookDto);
// Find other methods in the BookStoreConsole sample project ...
}
Copy/Paste the content below in the Program.cs file and hit F5
to run the console app.
using BookStoreConsole.Services.Books;
using BookStoreConsole.Services.Books.Dtos;
using BookStoreConsole.Services.Http;
using Microsoft.Extensions.DependencyInjection;
// First set up the Dependency Injection System to register the Book Http Service and the BookService
var services = new ServiceCollection();
// Register the Book HttpService to the Dependency Injection system
services.AddTransient<IHttpService<BookDto, CreateBookDto, UpdateBookDto, GetBooksDto, Guid>,
HttpService<BookDto, CreateBookDto, UpdateBookDto, GetBooksDto, Guid>>();
// Register the BookService to the Dependency Injection system
services.AddTransient<IBookService, BookService>();
// Get the BookService from the Dependency Injection System
// The Book service becomes via its constructor the Book HttpService (Constructor Dependency Injection) and is ready to use.
var bookService = services.BuildServiceProvider().GetRequiredService<IBookService>();
// Create a book
var createdBook = await bookService.CreateBookAsync(new CreateBookDto("New Book3", BookType.Adventure, DateTime.Now, 10.0f));
// Get a list of books => The result should be a list of 3 books.
var books = await bookService.GetBooksAsync();
Console.ReadLine(); // Set here a breakpoint to see the results
When you have a working use case, the Generic Book HTTP Service, things get a lot easier because the heavy lifting is done.
Imagine, you have another use case where you need to Get or Create Authors from the API.
The only things you need to do are:
namespace BookStoreWebApi.Dtos.Authors;
public class AuthorDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTime BirthDate { get; set; }
public string? ShortBio { get; set; }
}
public interface IAuthorService
{
Task<IEnumerable<AuthorDto>> GetAuthorsAsync();
Task<AuthorDto?> CreateAuthorAsync(CreateAuthorDto bookDto);
// other method definitions here
}
public class AuthorService : IAuthorService
{
// implementation here
}
services.AddTransient<IHttpService<AuthorDto, CreateAuthorDto, UpdateAuthorDto, GetAuthorsDto, Guid>,
HttpService<AuthorDto, CreateAuthorDto, UpdateAuthorDto, GetAuthorsDto, Guid>>();
services.AddTransient<IAuthorService, AuthorService>();
await authorService.CreateAuthorAsync(new CreateAuthorDto(){ ... });
var authors = await authorService.GetAuthorsAsync();
Get the source code on GitHub.
Enjoy and have fun!