OneOf Package to Handle Multiple Return Types in .NET Core

Introduction

In .NET Core, the "OneOf" library provides a simple and effective way to work with discriminated unions (sum types) in C#. It allows you to define types that can hold values of different types but only one value at a time, making it useful for representing scenarios with multiple potential outcomes or states.

The tools which I have used for this tutorial are below.

  1. Visual Studio 2022 Community Edition
  2. .NET 6.0
  3. OneOf NuGet Package
  4. Web API

Here we are going learn how you can use "OneOf" as a return type in a .NET Core Web API:

The source code can be downloaded from GitHub

Step 1. Create a Web API Project targeting .NET 6.0 framework.

Step 2. Install the “OneOf” NuGet Package

Step 3. Define API endpoints that return a “OneOf” type.

Let us look at the sample code.

using Microsoft.AspNetCore.Mvc;
using OneOf;

namespace OneOfTutorial.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SampleController : ControllerBase
    {
        /// <summary>
        /// returnType : default, error
        /// </summary>
        /// <param name="returnType"></param>
        /// <returns></returns>
        [HttpGet("data/{returnType}")]
        public IActionResult GetData([FromRoute] string returnType ="default")
        {

            var data = GetDataByType(returnType);           
            return Ok(data.Value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="recordType"></param>
        /// <returns></returns>
            private OneOf<MyDataModel,ErrorViewModel> GetDataByType(string recordType)
            {

                try
                {
                    if (recordType == "error")
                        throw new Exception("Returning Error View Model");
                    var data = new MyDataModel
                    {
                        Id = 1,
                        Name = "default message"
                    };
                    return data;
                }
                catch (Exception ex)
                {
                    // Handle errors and return ErrorViewModel
                    var errorViewModel = new ErrorViewModel
                    {
                        StatusCode = 500,
                        ErrorMessage = ex.Message
                    };
                    return errorViewModel;

                }
            }             
        }

        public class MyDataModel
        {
            public int Id { get; set; }
            public string? Name { get; set; }
        }

        public class ErrorViewModel
        {
            public int StatusCode { get; set; }
            public string? ErrorMessage { get; set; }
        }    
}

This code defines a SampleController class that inherits from ControllerBase, and it contains two action methods: GetData and GetDataByType. The controller demonstrates how to use the OneOf type in a .NET Core Web API to handle different response types based on a specified returnType.

  1. GetData Action Method: This action method is an HTTP GET endpoint that takes a returnType as a parameter from the route (e.g., "/data/default" or "/data/error"). The GetData method then calls the private GetDataByType method to get the data based on the returnType. It returns an IActionResult, and in this case, it returns an OkObjectResult with the value extracted from the OneOf<MyDataModel, ErrorViewModel> response.
  2. GetDataByType Private Method: This private method is called by the GetData action method and takes a recordType parameter that determines the type of data to return. If the recordType is "error", the method throws an exception to simulate an error scenario. It then returns an ErrorViewModel wrapped in the OneOf type. If the recordType is not "error", the method creates a MyDataModel instance with default values, and it returns the MyDataModel wrapped in the OneOf type.
  3. MyDataModel and ErrorViewModel Classes: These are two simple model classes representing the data and the error response, respectively. They are used to demonstrate the OneOf usage.

To summarize, the SampleController class defines two endpoints. The GetData endpoint takes a returnType parameter, calls the GetDataByType method to get the data, and then returns the data as an OkObjectResult. The GetDataByType method determines the type of data to return based on the recordType parameter, either a MyDataModel instance or an ErrorViewModel instance, wrapped in the OneOf<MyDataModel, ErrorViewModel> type. This allows the client to handle different response types gracefully based on the specified returnType.

Here's how a potential usage of this method might look like.

// Example usage of the method
var result = GetDataByType("error");

if (result.IsT0) // Check if the result is of type MyDataModel
{
    MyDataModel data = result.AsT0; // Extract the MyDataModel value
    // Handle the data as needed
}
else if (result.IsT1) // Check if the result is of type ErrorViewModel
{
    ErrorViewModel error = result.AsT1; // Extract the ErrorViewModel value
    // Handle the error as needed
}

By using the "OneOf" library, you can easily work with discriminated unions in a type-safe and concise manner, improving the readability and maintainability of your code.