LambdaLuke Help

Refactor the API

Now we have a functioning get endpoint we will refactor the project to move the logic into appropriate files.
This will make the application more maintainable and easy to read.

Move the Model to a Model Folder

Create a folder within the project called 'Models'

Add a new class file to this folder and call it 'TaskDto'

Cut the model we created in the program.cs file and move it here

namespace CrudAppAPI.Models; public class TaskDto { public int Id { get; set; } public string Title { get; set; } = default!; public string? Description { get; set; } public DateTime? DueDate { get; set; } public bool Completed { get; set; } }

Move Logic to a Service

Create a folder within the project called 'Services'

Add a new class file to this folder and call it 'TaskService.cs'

We will inject our configuration settings into this service. Add the following to pass the config across

namespace CrudAppAPI.Services; public class TaskService { private readonly IConfiguration _config; public TaskService(IConfiguration config) { _config = config; } }

Add the method to return our data, we will call it GetAllTasksAsync. As this method is asynchronous it must return a Task<>, which will contain our IEnumerable of type TaskDto

We will need to add the using statement to import our TaskDto model

namespace CrudAppAPI.Services; using CrudAppAPI.Models; public class TaskService { private readonly IConfiguration _config; public TaskService(IConfiguration config) { _config = config; } public async Task<IEnumerable<TaskDto>> GetAllTasksAsync() { } }

Next, go back to the program.cs file and cut the entire contents of the endpoint method and paste it into our new GetTransactionsAsync method

We will also need to add the using statements required for the database connection, these can then be removed from the program.cs file

using System.Data; using CrudAppAPIV2.Models; using Dapper; using Microsoft.Data.SqlClient; namespace CrudAppAPIV2.Services; public class TaskService { private readonly IConfiguration _config; public TaskService(IConfiguration config) { _config = config; } public async Task<IEnumerable<TaskDto>> GetAllTasksAsync() { var connectionString = _config.GetConnectionString("DefaultConnection"); await using var connection = new SqlConnection(connectionString); await connection.OpenAsync(); var tasks = await connection.QueryAsync<TaskDto>( "GetTasks", commandType: CommandType.StoredProcedure ); return tasks; } }

Now there are just a few amendments that need to be made

We need to alter the connectionString variable to use the config we are injecting into the service

// var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); var connectionString = _config.GetConnectionString("DefaultConnection");

And last of all, we just need to simplify the return statement to just return tasks

using System.Data; using Microsoft.Data.SqlClient; using CrudAppAPI.Models; namespace CrudAppAPI.Services; public class TaskService { private readonly IConfiguration _config; public TaskService(IConfiguration config) { _config = config; } public async Task<List<TaskDto>> GetAllTasksAsync() { var tasks = new List<TaskDto>(); var connectionString = _config.GetConnectionString("DefaultConnection"); using var conn = new SqlConnection(connectionString); using var cmd = new SqlCommand("GetTasks", conn) { CommandType = CommandType.StoredProcedure }; await conn.OpenAsync(); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { tasks.Add(new TaskDto { Id = reader.GetInt32(0), Title = reader.GetString(1), Description = reader.IsDBNull(2) ? null : reader.GetString(2), DueDate = reader.IsDBNull(3) ? null : reader.GetDateTime(3), Completed = reader.GetBoolean(4) }); } return tasks; } }

Update the Program.cs file to add the Service, add it below the existing builder.services method (you again will also need to add the files using statement)

builder.Services.AddScoped<TaskService>();

Move the Endpoint to an Endpoints File

Create another folder within the project and call it Endpoints, add a new class file within called TaskEndpoints.cs

Make the class static

Add the following method called MapTransactionEndpoints to the class

namespace ExpenseTrackerAPI.Endpoints; public static class Endpoints { public static void MapTaskEndpoints(this WebApplication app) { } }

Then cut what is remaining of our get endpoint from the Program.cs file and paste it into this method

namespace CrudAppAPI.Endpoints; public class TaskEndpoints { public static void MapTaskEndpoints(this WebApplication app) { // GET /api/tasks app.MapGet("/api/tasks", async () => { }); } }

Pass in a parameter of 'TaskService service'

Create a variable called tasks that calls out the service method

Add a return statement that returns the tasks method results

using CrudAppAPI.Services; namespace CrudAppAPI.Endpoints; public static class TaskEndpoints { public static void MapTaskEndpoints(this WebApplication app) { app.MapGet("/api/tasks", async (TaskService service) => { var tasks = await service.GetAllTasksAsync(); return Results.Ok(tasks); }); } }

In the Program.cs file add the endpoint file to the app at the end just before app.Run() is called, You will also need again to import the file with a using statement

app.MapTaskEndpoints();

Final Program.cs File

That should be the refactoring completed, and the Program.cs file should look like this

using CrudAppAPI.Endpoints; using CrudAppAPI.Services; var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); builder.Services.AddScoped<TaskService>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); app.MapTaskEndpoints(); app.Run();

Create a Database Connection Factory

We will now create a database connection factory which we can then inject into our service methods. This keeps the database logic in one place and stops the logic from being tightly coupled to the service methods.

Add a new folder to the project and call it Data

Add a file to this directory and call it DbConnectionFactory.cs

using System.Data; using Microsoft.Data.SqlClient; namespace CrudAppAPIV2.Data; public interface IDbConnectionFactory { Task<IDbConnection> CreateConnectionAsync(); } public class SqlConnectionFactory : IDbConnectionFactory { private readonly IConfiguration _config; public SqlConnectionFactory(IConfiguration config) { _config = config; } public async Task<IDbConnection> CreateConnectionAsync() { var connection = new SqlConnection(_config.GetConnectionString("DefaultConnection")); await connection.OpenAsync(); return connection; } }

Update Your Service Class to Use the Factory. In your TaskService.cs file, replace this:

private readonly IConfiguration _config; public TaskService(IConfiguration config) { _config = config; }

with

private readonly IDbConnectionFactory _connectionFactory; public TaskService(IDbConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; }

Then replace

var connectionString = _config.GetConnectionString("DefaultConnection"); await using var connection = new SqlConnection(connectionString); await connection.OpenAsync();

with

using var connection = await _connectionFactory.CreateConnectionAsync();

Finally, register the IDbConnectionFactory and its implementation SqlConnectionFactory with the dependency injection container

Add this line to Program.cs

builder.Services.AddScoped<IDbConnectionFactory, SqlConnectionFactory>();
11 July 2025