It's very easy to get started using Minimal Web APIs in .NET 6, but as the number of routes grows, your Program.cs file can easily become overwhelming to maintain. Instead of keeping all your app.Map*() methods in the Program.cs file, you should create a router class to separate your groups of app.Map() methods into. For example, if you have a set of CRUD routes for working with products and another set for working with customers, create a ProductRouter class and a CustomerRouter class. In this article, you're going to see how to move each group of Web APIs into their own router class to provide a much more consistent and maintainable way to create Minimal Web API calls.

A Sample Minimal Web API

Let's look at a simple Minimal Web API system that works with product data. You normally have a Product class with basic properties such as ProductID, Name, Color, and ListPrice, as shown in the following code snippet.

public partial class Product 
{
    public int ProductID { get; set; }
    public string Name { get; set; }
    public string Color { get; set; }
    public decimal ListPrice { get; set; }
}

In the Program.cs file, you write an app.MapGet() method to return a set of Product objects. In this example, I'm using hard-coded Product objects, whereas in a real application, you'd most likely use the Entity Framework to retrieve these from a database table.

// Get a collection of data
app.MapGet("/product", () => 
{
    return Results.Ok(new List<Product> 
    {
        new Product 
        {
            ProductID = 706,
            Name = "HL Road Frame - Red, 58",
            Color = "Red", 
            ListPrice = 1500.00m
        },
        new Product 
        {
            ProductID = 707,
            Name = "Sport-100 Helmet, Red",
            Color = "Red", 
            ListPrice = 34.99m
        }
    });
});

You then have the rest of your app.Map*() methods that retrieve a single product, post a new product, update an existing product and delete a product, as shown in Listing 1. For a great primer on Minimal APIs, check out Shawn Wildermuth's article entitled “Minimal APIs in .NET 6” in the Jan/Feb 2022 issue of CODE Magazine.

Listing 1: A very basic set of Minimal Web APIs

// GET a single row of data
app.MapGet("/product/{id:int}", (int id) =>
{
    // Simulate returning data from the
    // data store with the specified ID
    return Results.Ok(new Product 
    {
        ProductID = id,
        Name = "New Bicycle",
        Color = "Red",
        ListPrice = 1500.00m
    });
});

// INSERT new data
app.MapPost("/product", (Product prod) =>
{
    // TODO: Insert into data store

    // Return the new object
    return Results.Created($"/product/{prod.ProductID}", prod);
});

// UPDATE existing data
app.MapPut("/product/{id:int}", (int id, Product entity) =>
{
    IResult ret;

    // TODO: Look up data by the id
    Product current = Get(id);

    if (current!= null) 
    {
        // Update the entity with new data
        current.Name = entity.Name;
        current.Color = entity.Color;
        current.ListPrice = entity.ListPrice;
    
        // TODO: Update data store
    
        // Return the updated entity
        ret = Results.Ok(current);
    }
    else 
    {
        ret = Results.NotFound();
    }
    
    return ret;
});

// DELETE a single row
app.MapDelete("/product/{id:int}", (int id) =>
{
    IResult ret;

    // TODO: Look up data by the id
    Product entity = Get(id);
    if (entity != null) 
    {
        // TODO: Delete data from the data store

        // Return NoContent
        ret = Results.NoContent();
    }
    else 
    {
        ret = Results.NotFound();
    }

    return ret;
});

This is a lot of code in the Program.cs file for just working with products. You can imagine how that code grows as you add the same CRUD logic for customers, employees, books, or whatever other tables you have in your database. Let's now look at how to make this code more manageable.

Create a Web API Project

To get the most out of this article, I suggest that you follow along with the steps as I outline them. You need to install .NET 6 on your computer, which you can get at https://dotnet.microsoft.com/en-us/download/dotnet/6.0. You also need either VS Code or Visual Studio. If you wish to use VS Code for your editor and your application creation, use the following section for guidance. If you wish to use Visual Studio 2022, skip to the next section for guidance.

Using VS Code

Open VS Code in the top-level folder where you normally create your projects (for example D:\MyVSProjects). Select Terminal > New Terminal from the menu. In the terminal window in VS Code, create a .NET 6 Web API app using the following dotnet command:

dotnet new webapi -minimal -n AdvWorksAPI

Select File > Open Folder... from the menu, navigate to the new AdvWorksAPI folder created by the above command and click the Select Folder button.

Add Required Assets

At the bottom right-hand corner of Visual Studio Code, you should see a warning bar appear (Figure 1). This tells you that you need to add some required assets. Click the Yes button.

Figure 1: Be sure to answer Yes when prompted to add required assets.
Figure 1: Be sure to answer Yes when prompted to add required assets.

This warning box can take a minute to appear; either be patient and wait for it, or you can run a build task by selecting the Terminal > Run Build Task... > build from the menu bar.

Save the Workspace

Click File > Save Workspace As... and give it the name AdvWorksAPI. Click the Save button to store this new workspace file on disk.

Using Visual Studio 2022

If you prefer to use Visual Studio 2022 for your development, start an instance of Visual Studio 2022 and select Create a new project from the first screen. Select the ASP.NET Core Web API project template and click the Next button. Set the Project name field to AdvWorksAPI and the Location field to the folder where you generally create your projects and click the Next button. From the Framework dropdown list (Figure 2) choose .NET 6.0 (Long-term support). From the Authentication type dropdown list choose None. Uncheck the Use controllers (uncheck to use minimal APIs) field and click the Create button.

Figure 2: Use Visual Studio 2022 to create a new ASP.NET Core Web API project.
Figure 2: Use Visual Studio 2022 to create a new ASP.NET Core Web API project.

Try It Out

Whether you've used VS Code or Visual Studio 2022, press F5 to build the Web API project and launch a browser. If you get a dialog box that asks if you should trust the IIS Express certificate, select Yes. In the Security Warning dialog that appears next, select Yes. If you get an error related to privacy and/or HTTPS. Open the \Properties\launchSettings.json file and remove the “https://...” from the applicationURL property.

If you're using Visual Studio 2022, you should be presented with a Swagger home page (Figure 3) that allows you to immediately try out the weather forecast API that's included as a sample in the Program.cs file. If you're using VS Code, you get a 404 error that the page cannot be found. Type in the URL http://localhost:nnnn/swagger/index.html (where nnnn is your port number) and the Swagger home page (Figure 3) should be displayed.

Figure 3: The Swagger home page allows you to try out your API calls.
Figure 3: The Swagger home page allows you to try out your API calls.

Create Entity Classes Folder

It's always a best practice to group similar classes into a namespace, a folder, and/or a class library project. Right-mouse click on the AdvWorksAPI project and add a new folder named EntityClasses. Create the Product.cs class in this new folder and add the following code to this new file. Feel free to create a separate class library to contain all your entity classes if you wish.

#nullable disable
namespace AdvWorksAPI 
{
    public partial class Product 
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Color { get; set; }
        public decimal ListPrice { get; set; }
    }
}

If you're wondering what the directive #nullable disable is at the top of the file, .NET 6 requires all empty strings to either be initialized in the constructor of the class or created as a nullable string. If you don't wish to use this behavior, include this directive at the top of your file.

Create Router Base Class

Any time you're going to create a set of classes that perform the same basic functionality, it's a great idea to create either an interface or a base class to identify those methods and properties that should be in each class. For each of the router classes, you should have a public property called UrlFragment that identifies the first part of the path to your API. In the example shown in Listing 1, the value /product was repeated many times in each of the app.Map*() methods. This is the value that you're going to put into the UrlFragment property. If you have another router class, Customer for example, you place the value /customer into this UrlFragment property.

At some point, you might wish to log messages or errors as your Web API methods are called. Include a protected property named Logger of the type ILogger in this base class. The property is to be injected into either the constructor of your router classes or injected into just those methods that need it.

A single public method, AddRoutes(), is needed in order to initialize the routes for router class. This method is called from the Program.cs file to initialize the routes you previously created in the Program.cs file. You're going to see the use of this method as you work your way through this article.

Right-click on the AdvWorksAPI project and add a new folder named Components. Add a new file named RouterBase.cs and add the code shown in the following code snippet.

#nullable disable
namespace AdvWorksAPI
{
    public class RouterBase 
    {
        public string UrlFragment;
        protected ILogger Logger;

        public virtual void AddRoutes(WebApplication app)
        {
        }
    }
}

Create Product Router Class

As mentioned before, it's a good idea to group similar classes together into a folder and/or a class library. Add a folder under which all your router classes are created. Right-click on the AdvWorksAPI project and add a new folder named RouterClasses. Add a new file named ProductRouter.cs file in this new folder and add the following code into this new file:

namespace AdvWorksAPI 
{
    public class ProductRouter : RouterBase 
    {
        public ProductRouter() 
        {
            UrlFragment = "product";
        }
    }
}

You can see that this ProductRouter class inherits from the RouterBase class. It sets the UrlFragment property to the value “product” because that's going to be used for the endpoint for all your mapping methods. Setting this property once helps you eliminate repeated code and gives you one place to change your route name should you desire.

Get All Products

Add a protected virtual method to the ProductRouter class named GetAll() to return a collection of Product objects, as shown in Listing 2. I'm using a hard-coded collection of Product objects here just so you can see the Minimal API in action without having to worry about connecting to a database.

Listing 2: The GetAll() method returns a collection of product objects.

/// <summary>
/// Get a collection of Product objects
/// </summary>
/// <returns>A list of Product objects</returns>
protected virtual List<Product> GetAll() 
{
    return new List<Product> 
    {
        new Product 
        {
            ProductID = 706,
            Name = "HL Road Frame - Red, 58",
            Color = "Red",
            ListPrice = 1500.0000m
        },
        new Product 
        {
            ProductID = 707,
            Name = "Sport-100 Helmet, Red",
            Color = "Red",
            ListPrice = 34.9900m
        },
        new Product 
        {
            ProductID = 708,
            Name = "Sport-100 Helmet, Black",
            Color = "Black",
            ListPrice = 34.9900m
        },
        new Product 
        {
            ProductID = 709,
            Name = "Mountain Bike Socks, M",
            Color = "White",
            ListPrice = 9.5000m
        },
        new Product 
        {
            ProductID = 710,
            Name = "Mountain Bike Socks, L",
            Color = "White",
            ListPrice = 9.5000m
        }
    };
}

Create a Get() Method to Return the IResult

Next, create a method named Get() that returns an IResult object because that's what's expected from a Minimal API. The Get() method uses the Results.Ok() method to return a status code of 200, signifying that the method was successful. The list of Product objects is returned to the calling program, wrapped within this result object.

/// <summary>
/// GET a collection of data
/// </summary>
/// <returns>An IResult object</returns>
protected virtual IResult Get() 
{
    return Results.Ok(GetAll());
}

Create Method to Add Product Routes

You need to inform the Web API engine that this Get() method is an endpoint. To accomplish this, override the AddRoutes() method in the ProductRouter class as shown in the following code snippet:

/// <summary>
/// Add routes
/// </summary>
/// <param name="app">A WebApplication object</param>
public override void AddRoutes(WebApplication app)
{
    app.MapGet($"/{UrlFragment}", () => Get());
}

The AddRoutes() method calls the app.MapGet() method using the WebApplication app variable passed in from the Program.cs file. The first parameter to the MapGet() method is the route name the user sends the request to, such as http://localhost:nnnn/product or http://localhost:nnnn/customer.

Use string interpolation to build this endpoint from the UrlFragment property. The second parameter to app.MapGet() is the method to produce some result. This method returns the IResult object from the Get() method you just wrote. If an IResult isn't returned from the method, the app.MapGet() method automatically wraps the return value into a Result.Ok() object.

Modify Program.cs to Call the AddRoutes() Method

Now that you've created your ProductRouter class, it's time to try it out. Open the Program.cs file and remove all variables and methods related to the weather forecast API. Toward the bottom of the file, just above the app.Run() method call, add the code to instantiate the ProductRouter class and call the AddRoutes() method. Make sure to pass in the instance of the WebApplication object contained in the app variable.

//*********************************************
// Add Product Routes
//*********************************************
new ProductRouter().AddRoutes(app);

Try It Out

Run the application and, on the Swagger home page, click on the Get button for the /product path. Click on the Try it Out button and click on the Execute button. You should see the list of products you created in the Get() method appear as JSON in the Response body section of the Swagger page, as shown in Figure 4.

Figure 4: The product route now appears on the Swagger page.
Figure 4: The product route now appears on the Swagger page.

Congratulations! You're now on your way to creating a more maintainable approach to Minimal API development. Let's add more functionality in the ProductRouter class to give you a complete CRUD API.

Get a Single Product

In addition to retrieving all products, you're most likely going to need to retrieve a single product. Add an overload of the Get() method to the ProductRouter class that accepts a single integer value named id. Use this id variable to search in the Product collection for where the id value matches one of the ProductId property values. The Product object that's located is returned from this method wrapped within the Results.Ok() object. If the id value isn't found, a Results.NotFound() is returned that's reported as a 404 Not Found status code back to the calling program.

/// <summary>
/// GET a single row of data
/// </summary>
/// <returns>An IResult object</returns>
protected virtual IResult Get(int id) 
{
    // Locate a single row of data
    Product? current = GetAll().Find(p => p.ProductID == id);
    if (current != null) 
    {
        return Results.Ok(current);
    }
    else 
    {
        return Results.NotFound();
    }
}

Modify AddRoutes() Method

Now that you have the new method to retrieve a single product, you need to add this new route. Locate the AddRoutes() method and a call to app.MapGet(), as shown in the code below. The first parameter is built using the UrlFragment property, followed by a forward slash (/) and then you need the variable name and data type enclosed within curly braces, so it looks like {id:int}. Because you're using string interpolation, you must escape the curly braces by adding an extra opening and closing brace around this value.

public override void AddRoutes(WebApplication app)
{
    app.MapGet($"/{UrlFragment}", () => Get());
    app.MapGet($"/{UrlFragment}/{{id:int}}",(int id) => Get(id));
}

Try It Out

Run the application and, on the Swagger home page, click on the Get button for the /product/{id} path. Click on the Try it Out button, type in the number 706 into the id field (Figure 5) and click on the Execute button. You should see the first product from the product collection appear as JSON in the Response body section of the Swagger page. Type in an invalid number into the id field, such as 999, and you should see a 404 Not Found status code returned.

Figure 5: Swagger allows you to enter an ID to call the Get(id) method.
Figure 5: Swagger allows you to enter an ID to call the Get(id) method.

Insert a Product

Now that you have a way to read data from your Web API, it's time write code to insert, update, and delete data. Write a method in the ProductRouter class named Post() to which you pass a Product object to insert into your data store. Obviously, you don't have a data store in this simple example, but I'm sure you can easily extrapolate how this would work when using the Entity Framework. To simulate this process, calculate the maximum ProductID used in the collection, add one to this value, and assign this to the ProductID property of the Product object passed in. This is like what happens if you have an identity property on the ProductID field in a SQL Server Product table.

From a POST method, it's standard practice to return a 201 Created status code when a new value is added to the data store. Use the Results.Created() method passing in two parameters to return this 201 status code. The first parameter is the endpoint that can be used to retrieve the newly created object, for example, /product/711. The second parameter is the actual entity with any data that has been changed by performing the insert into the data store.

/// <summary>
/// INSERT new data
/// </summary>
/// <returns>An IResult object</returns>
protected virtual IResult Post(Product entity) 
{
    // Generate a new ID
    entity.ProductID = GetAll().Max(p => p.ProductID) + 1;

    // TODO: Insert into data store

    // Return the new object created
    return Results.Created($"/{UrlFragment}/{entity.ProductID}", entity);
}

Just as you did with the previous two methods you created, call the appropriate app.Map*() method to register this new endpoint. Locate the AddRoutes() method and a call to the app.MapPost() method, as shown in the following code snippet.

public override void AddRoutes(WebApplication app)
{
    app.MapGet($"/{UrlFragment}", () => Get());
    app.MapGet($"/{UrlFragment}/{{id:int}}", (int id) => Get(id));
    app.MapPost($"/{UrlFragment}", (Product entity) => Post(entity));
}

Try It Out

Run the application and, on the Swagger home page, click on the POST button for the /product path. Click on the Try it Out button and you should see a Request body field appear with some basic JSON for you to enter some data into. Type in the following JSON:

{
    "productID": 0,
    "name": "A New Product",
    "color": "White",
    "listPrice": 20
}

Click on the Execute button and you should see the Response body appear with the following JSON:

{
    "productID": 711,
    "name": "A New Product",
    "color": "White",
    "listPrice": 20
}

This method simulates adding data to a database, such as SQL Server, and the productID property is assigned a new value from the database. The complete object is then passed back to you along with the HTTP status code of 201: Created.

Update a Product

The public interface for updating an entity through a Web API method is to pass in the ID of the object to update along with the object itself. Add a method to the ProductRouter class named Put() that accepts an integer ID variable and a Product entity variable, as shown in Listing 3.

Listing 3: The Put() method updates an existing product with the new data passed in.

/// <summary>
/// UPDATE existing data
/// </summary>
/// <returns>An IResult object</returns>
protected virtual IResult Put(int id, Product entity) 
{
    IResult ret;
    
    // Locate a single row of data
    Product? current = GetAll().Find(p => p.ProductID == id);
    
    if (current != null) 
    {
        // TODO: Update the entity
        current.Name = entity.Name;
        current.Color = entity.Color;
        current.ListPrice = entity.ListPrice;
    
        // TODO: Update the data store
    
        // Return the updated entity
        ret = Results.Ok(current);
    }
    else 
    {
        ret = Results.NotFound();
    }
    
    return ret;
}

This method first looks up the Product object in the Products collection. If the product is found, a Project object is returned into the current variable. If the product is not found, a null value is assigned to the current variable. Check to see if the current variable is not null, and if not, update the properties of the current variable with the values passed via the entity variable. This process is, of course, just a single line of code when using the Entity Framework, but for our purposes here, you need the three lines of code to move the data from one object to another. You then return the updated entity in the Results.Ok() method. If the current variable is null, inform the calling program that the object was not located by returning the value from the Results.NotFound() method, which returns the 404 Not Found.

As you have done previously, locate the AddRoutes() method and add a call to the app.MapPut() method. The first parameter to MapPut() is exactly the same as for the MapGet() method. In the lambda expression, two parameters are accepted into this anonymous function, the id and the entity. The integer value is expected to come from the URL line, and the Product object is expected to be received from the body of the API call.

public override void AddRoutes(WebApplication app)
{
    app.MapGet($"/{UrlFragment}", () => Get());
    app.MapGet($"/{UrlFragment}/{{id:int}}", (int id) => Get(id));
    app.MapPost($"/{UrlFragment}", (Product entity) => Post(entity));
    app.MapPut($"/{UrlFragment}/{{id:int}}", (int id, Product entity) => 
        Put(id, entity));
}

Try It Out

Run the application and on the Swagger home page, click on the PUT button for the /product/{id} path. Click on the Try it Out button and enter a number such as 710 into the id field. In the Request body field modify the JSON to look like the following.

{
    "productID": 710,
    "name": "A Changed Product",
    "color": "Red",
    "listPrice": 1500.00
}

Click on the Execute button and you should see the Response body appear with the same JSON you entered above. If you then modify the ID field to be a bad product ID, such as 999, and click the Execute button, you should see the 404 status code returned.

Delete a Product

The last method to write to complete the CRUD logic is one to delete a product. Add a method named Delete() that accepts an integer value, id, to the ProductRouter class, as shown in Listing 4. This method first looks up the Product object in the Products collection using the value passed to the id variable. If the product is found, a Project object is returned into the current variable. If the product is not found, a null value is assigned to the current variable. Check to see if the current variable is not null, and if not, remove the object from the collection, which, in this case, simulates removing the product row from the data store.

Listing 4: The Delete() method removes a single product from the data store.

/// <summary>
/// DELETE a single row
/// </summary>
/// <returns>An IResult object</returns>
protected virtual IResult Delete(int id) 
{
    IResult ret;

    // Locate a single row of data
    Product? current = GetAll().Find(p => p.ProductID == id);

    if (current != null) 
    {
        // TODO: Delete data from the data store
        GetAll().Remove(current);

        // Return NoContent
        ret = Results.NoContent();
    }
    else 
    {
        ret = Results.NotFound();
    }

    return ret;
}

The most common HTTP status to return in response to a successful call to the DELETE verb is a 204 No Content. You can return this status code with a call to the Results.NoContent(). If the product object is not found because the ID passed in doesn't exist in the data store, a 404 Not Found status is returned by calling the Results.NotFound() method.

To register this new route, locate the AddRoutes() method and add a call to the app.MapDelete() method. The first parameter to MapDelete() is the same as for the MapGet() and MapPut() methods.

public override void AddRoutes(WebApplication app)
{
    app.MapGet($"/{UrlFragment}", () => Get());
    app.MapGet($"/{UrlFragment}/{{id:int}}", (int id) => Get(id));
    app.MapPost($"/{UrlFragment}", (Product entity) => Post(entity));
    app.MapPut($"/{UrlFragment}/{{id:int}}", (int id, Product entity) => 
        Put(id, entity));
    app.MapDelete($"/{UrlFragment}/{{id:int}}", (int id) => Delete(id));
}

Try It Out

Run the application and on the Swagger home page, click on the DELETE button for the /product/{id} path. Click on the Try it Out button and enter a number such as 710 into the id field. Click on the Execute button and you should see the code returned is 204 No Content. Change the id field to an invalid product ID such as 999 and click on the Execute button. You should now see the 404 Not Found status code returned.

Add Logging to Product Router

An ILogger property was added to the RouterBase class at the beginning of this article. Let's now use that property to perform some logging to the console window from the Get() method in the ProductRouter.cs file. Open the ProductRouter.cs file and use dependency injection to inject an ILogger object into the constructor, as shown in the following code snippet.

public ProductRouter(ILogger<ProductRouter> logger) 
{
    UrlFragment = "product";
    Logger = logger;
}

Locate the Get() method and call the Logger.LogInformation() method to log a message to any log listeners you set up.

protected virtual IResult Get() 
{
    // Write a log entry
    Logger.LogInformation("Getting all products");
    // REST OF THE CODE HERE
}

Add Logging as a Service

Open the Program.cs file and just after the code that creates the builder, WebApplication.CreateBuilder(args), add the following lines of code to add logging to the list of services that can be injected.

// Add Logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();

Just after these calls to register logging as a service, add code to make your ProductRouter class a service as well.

// Add "Router" classes as a service
builder.Services.AddScoped<RouterBase, ProductRouter>();

Dependency injection doesn't just happen because you add the ILogger<ProductRouter> to the constructor of the ProductRouter class. It happens when you let the ASP.NET engine take care of creating all instances of services. This means that you can't create an instance of the ProductRouter class like you did earlier in this article. Go to the bottom of the Program.cs file and remove the lines of code where you created a new ProductRouter, remove the call to app.Run() as well.

//*********************************************
// Add Product Routes
//*********************************************
new ProductRouter().AddRoutes(app);
app.Run()

To ensure that your ProductRouter class, or any other classes that inherit from your RouterBase class, can participate in dependency injection, you need to create a service scope by calling the app.Services.CreateScope() method, as shown in Listing 5. Wrapped within a using block, retrieve a list of services that are of the type RouterBase. Loop through each of those services and invoke each ones' AddRoute() method. Finally, call the app.Run() method in order to ensure that the entire set of registered endpoints are all running within the same application scope.

Listing 5: Create a loop to invoke all Router Classes' AddRoutes() method.

//*************************************
// Add Routes from all "Router Classes"
//*************************************
using (var scope = app.Services.CreateScope())
{
    // Build collection of all RouterBase classes
    var services = scope.ServiceProvider.GetServices<RouterBase>();

    // Loop through each RouterBase class
    foreach (var item in services)
    {
        // Invoke the AddRoutes() method to add the routes
        item.AddRoutes(app);
    }

    // Make sure this is called within the application scope
    app.Run();
}

Try It Out

Run the application and on the Swagger home page, click on the Get button for the /product path. Click on the Try it Out button, then click on the Execute button. You should then see the list of products you created in the Get() method appear as JSON in the Response body section of the Swagger page. Look in the Console window and you should see the message “Getting all products” has appeared.

Create a Customer Router Class

The RouterBase and ProductRouter classes provides a nice design pattern that you can use to build any router class for any other CRUD logic you require. Let's create a Customer class and a CustomerRouter class to work with customer data. Because of the generic code you just wrote to invoke the AddRoutes() method, all you have to do is to create a new router class that inherits from the RouterBase class and all new routes will be automatically registered when the application starts.

Create a Customer Class

Right mouse-click on the EntityClasses folder and add a new file named Customer.cs. Into this file, add the following code to create a new Customer class with several properties.

#nullable disable
namespace AdvWorksAPI 
{
    public partial class Customer 
    {
        public int CustomerID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string CompanyName { get; set; }
        public string EmailAddress { get; set; }
    }
}

Create a Customer Router Class

Expand the RouterClasses folder, copy the ProductRouter.cs file and paste it right back into the RouterClasses folder. Rename the file as CustomerRouter.cs. Open the CustomerRouter.cs file and perform a case-sensitive search and replace within this document, changing all instances of Product with Customer. Next, perform a case-sensitive search and replace and change all instances of product with customer. Scroll down and locate the GetAll() method and fix up the hard-coded data to look like that shown in Listing 6.

Listing 6: Create a hard-coded collection of customer objects.

private List<Customer> GetAll() 
{
    return new List<Customer> 
    {
        new Customer 
        {
            CustomerID = 1,
            FirstName = "Orlando",
            LastName = "Gee",
            CompanyName = "A Bike Store",
            EmailAddress = "orlando0@adventure-works.com",
        },
        new Customer 
        {
            CustomerID = 2,
            FirstName = "Keith",
            LastName = "Harris",
            CompanyName = "Progressive Sports",
            EmailAddress = "keith0@adventure-works.com",
        },
        new Customer 
        {
            CustomerID = 3,
            FirstName = "Donna",
            LastName = "Carreras",
            CompanyName = "Advanced Bike Components",
            EmailAddress = "donna0@adventure-works.com",
        },
        new Customer 
        {
            CustomerID = 4,
            FirstName = "Janet",
            LastName = "Gates",
            CompanyName = "Modular Cycle Systems",
            EmailAddress = "janet1@adventure-works.com",
        },
        new Customer 
        {
            CustomerID = 5,
            FirstName = "Lucy",
            LastName = "Harrington",
            CompanyName = "Metropolitan Sports Supply",
            EmailAddress = "lucy0@adventure-works.com",
        }
    };
}

Next, scroll down and locate the Put() method and within the if (current != null) statement, modify the lines of code that are showing errors. The new lines of code should look like the following:

current.FirstName = entity.FirstName;
current.LastName = entity.LastName;
current.CompanyName = entity.CompanyName;
current.EmailAddress = entity.EmailAddress;

Finally, register this new CustomerRouter class as a service by opening the Program.cs file and modify the code towards the top to look like the following:

// Add your "Router" classes as services
builder.Services.AddScoped<RouterBase, ProductRouter>();
builder.Services.AddScoped<RouterBase, CustomerRouter>();

Try It Out

Run the application and, on the Swagger home page, all the customer and product endpoints are now displayed as shown in Figure 6. Go ahead and try out any of the customer routes to verify that they work as expected.

Figure 6: Just by adding a new RouterBase class as a service, all endpoints are registered.
Figure 6: Just by adding a new RouterBase class as a service, all endpoints are registered.

Summary

In this article, you learned how to make Minimal API applications more maintainable. By using a good base class, called RouterBase, you have a good start of a design pattern for your Web API CRUD logic. By moving all your routes into separate classes, you prevent your Program.cs file from growing out of control. Because you inherit from the RouterBase class, you write code one time in the Program.cs file to instantiate all router classes and register their routes. Creating additional router classes for objects such as employees, cars, sales orders, etc. is as simple as copying the ProductRouter class, searching for all instances of “product”, and replacing with “employee,” “car,” etc.