Nabs.Launchpad.Core.Apis 10.0.215

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Nabs.Launchpad.Core.Apis --version 10.0.215
                    
NuGet\Install-Package Nabs.Launchpad.Core.Apis -Version 10.0.215
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nabs.Launchpad.Core.Apis" Version="10.0.215" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nabs.Launchpad.Core.Apis" Version="10.0.215" />
                    
Directory.Packages.props
<PackageReference Include="Nabs.Launchpad.Core.Apis" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nabs.Launchpad.Core.Apis --version 10.0.215
                    
#r "nuget: Nabs.Launchpad.Core.Apis, 10.0.215"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nabs.Launchpad.Core.Apis@10.0.215
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nabs.Launchpad.Core.Apis&version=10.0.215
                    
Install as a Cake Addin
#tool nuget:?package=Nabs.Launchpad.Core.Apis&version=10.0.215
                    
Install as a Cake Tool

Nabs Launchpad Core APIs Library

The Nabs Launchpad Core APIs library provides foundational components for building robust, production-ready Web APIs with ASP.NET Core. This library includes base controllers, result pattern integration, and standardized response handling for modern .NET applications.

Key Features

  • Base Controller Classes: Pre-configured controller base classes following best practices
  • Result Pattern Integration: Built-in support for Ardalis.Result pattern
  • Standardized Responses: Consistent API response formatting
  • ASP.NET Core Integration: Full compatibility with ASP.NET Core framework
  • Minimal API Support: Works seamlessly with both controller-based and minimal APIs
  • Error Handling: Structured error responses for better client experience

Core Components

NabsControllerBase

A base controller class that extends ControllerBase and provides a foundation for building API controllers with standardized patterns and response handling.

Usage Examples

Basic API Controller

using Microsoft.AspNetCore.Mvc;
using Nabs.Launchpad.Core.Apis;
using Ardalis.Result;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : NabsControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var products = await _productService.GetAllAsync();
        return Ok(products);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetById(Guid id)
    {
        var result = await _productService.GetByIdAsync(id);
        
        if (result.IsSuccess)
        {
            return Ok(result.Value);
        }
        
        return NotFound(result.Errors);
    }

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] ProductDto product)
    {
        var result = await _productService.CreateAsync(product);
        
        if (result.IsSuccess)
        {
            return CreatedAtAction(
                nameof(GetById), 
                new { id = result.Value.Id }, 
                result.Value);
        }
        
        return BadRequest(result.Errors);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> Update(Guid id, [FromBody] ProductDto product)
    {
        var result = await _productService.UpdateAsync(id, product);
        
        if (result.IsSuccess)
        {
            return Ok(result.Value);
        }
        
        return result.Status switch
        {
            ResultStatus.NotFound => NotFound(result.Errors),
            _ => BadRequest(result.Errors)
        };
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(Guid id)
    {
        var result = await _productService.DeleteAsync(id);
        
        if (result.IsSuccess)
        {
            return NoContent();
        }
        
        return result.Status switch
        {
            ResultStatus.NotFound => NotFound(result.Errors),
            _ => BadRequest(result.Errors)
        };
    }
}

Using Result Pattern

[ApiController]
[Route("api/[controller]")]
public class OrdersController : NabsControllerBase
{
    private readonly IOrderService _orderService;

    public OrdersController(IOrderService orderService)
    {
        _orderService = orderService;
    }

    [HttpPost]
    public async Task<IActionResult> PlaceOrder([FromBody] OrderRequest request)
    {
        var result = await _orderService.PlaceOrderAsync(request);
        
        return result.Status switch
        {
            ResultStatus.Ok => Ok(result.Value),
            ResultStatus.Invalid => BadRequest(result.ValidationErrors),
            ResultStatus.NotFound => NotFound(result.Errors),
            ResultStatus.Forbidden => Forbid(),
            ResultStatus.Unauthorized => Unauthorized(),
            _ => StatusCode(500, "An error occurred")
        };
    }

    [HttpGet("{id}/status")]
    public async Task<IActionResult> GetOrderStatus(Guid id)
    {
        var result = await _orderService.GetOrderStatusAsync(id);
        
        if (!result.IsSuccess)
        {
            return NotFound(new { message = "Order not found", orderId = id });
        }
        
        return Ok(result.Value);
    }
}

RESTful API with CRUD Operations

[ApiController]
[Route("api/[controller]")]
public class CustomersController : NabsControllerBase
{
    private readonly ICustomerService _customerService;
    private readonly ILogger<CustomersController> _logger;

    public CustomersController(
        ICustomerService customerService,
        ILogger<CustomersController> logger)
    {
        _customerService = customerService;
        _logger = logger;
    }

    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<CustomerDto>), StatusCodes.Status200OK)]
    public async Task<IActionResult> GetAll(
        [FromQuery] int page = 1, 
        [FromQuery] int pageSize = 10)
    {
        _logger.LogInformation("Fetching customers: page {Page}, size {PageSize}", page, pageSize);
        
        var result = await _customerService.GetPagedAsync(page, pageSize);
        return Ok(result);
    }

    [HttpGet("{id}")]
    [ProducesResponseType(typeof(CustomerDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetById(Guid id)
    {
        var result = await _customerService.GetByIdAsync(id);
        
        if (result.IsSuccess)
        {
            return Ok(result.Value);
        }
        
        _logger.LogWarning("Customer not found: {CustomerId}", id);
        return NotFound();
    }

    [HttpPost]
    [ProducesResponseType(typeof(CustomerDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Create([FromBody] CreateCustomerRequest request)
    {
        var result = await _customerService.CreateAsync(request);
        
        if (result.IsSuccess)
        {
            return CreatedAtAction(
                nameof(GetById), 
                new { id = result.Value.Id }, 
                result.Value);
        }
        
        return BadRequest(result.Errors);
    }

    [HttpPut("{id}")]
    [ProducesResponseType(typeof(CustomerDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Update(Guid id, [FromBody] UpdateCustomerRequest request)
    {
        var result = await _customerService.UpdateAsync(id, request);
        
        return result.Status switch
        {
            ResultStatus.Ok => Ok(result.Value),
            ResultStatus.NotFound => NotFound(),
            ResultStatus.Invalid => BadRequest(result.ValidationErrors),
            _ => BadRequest(result.Errors)
        };
    }

    [HttpDelete("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Delete(Guid id)
    {
        var result = await _customerService.DeleteAsync(id);
        
        if (result.IsSuccess)
        {
            return NoContent();
        }
        
        return NotFound();
    }
}

Handling Validation

[ApiController]
[Route("api/[controller]")]
public class UsersController : NabsControllerBase
{
    private readonly IUserService _userService;
    private readonly IValidator<CreateUserRequest> _validator;

    public UsersController(
        IUserService userService,
        IValidator<CreateUserRequest> validator)
    {
        _userService = userService;
        _validator = validator;
    }

    [HttpPost]
    public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest request)
    {
        // Validate request using FluentValidation
        var validationResult = await _validator.ValidateAsync(request);
        
        if (!validationResult.IsValid)
        {
            return BadRequest(validationResult.Errors);
        }
        
        var result = await _userService.CreateUserAsync(request);
        
        if (result.IsSuccess)
        {
            return CreatedAtAction(
                nameof(GetUser), 
                new { id = result.Value.Id }, 
                result.Value);
        }
        
        return BadRequest(result.Errors);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(Guid id)
    {
        var result = await _userService.GetUserAsync(id);
        
        if (result.IsSuccess)
        {
            return Ok(result.Value);
        }
        
        return NotFound();
    }
}

API Versioning

[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ItemsControllerV1 : NabsControllerBase
{
    private readonly IItemService _itemService;

    public ItemsControllerV1(IItemService itemService)
    {
        _itemService = itemService;
    }

    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var items = await _itemService.GetAllAsync();
        return Ok(items);
    }
}

[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ItemsControllerV2 : NabsControllerBase
{
    private readonly IItemServiceV2 _itemService;

    public ItemsControllerV2(IItemServiceV2 itemService)
    {
        _itemService = itemService;
    }

    [HttpGet]
    public async Task<IActionResult> GetAll([FromQuery] ItemFilter filter)
    {
        var items = await _itemService.GetAllAsync(filter);
        return Ok(items);
    }
}

Authentication & Authorization

using Microsoft.AspNetCore.Authorization;

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class SecureController : NabsControllerBase
{
    private readonly ISecureService _secureService;

    public SecureController(ISecureService secureService)
    {
        _secureService = secureService;
    }

    [HttpGet("public")]
    [AllowAnonymous]
    public IActionResult GetPublicData()
    {
        return Ok(new { message = "This is public data" });
    }

    [HttpGet("private")]
    public async Task<IActionResult> GetPrivateData()
    {
        var userId = User.FindFirst("sub")?.Value;
        var data = await _secureService.GetUserDataAsync(userId);
        return Ok(data);
    }

    [HttpGet("admin")]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> GetAdminData()
    {
        var data = await _secureService.GetAdminDataAsync();
        return Ok(data);
    }

    [HttpPost("policy-protected")]
    [Authorize(Policy = "RequireSpecificClaim")]
    public async Task<IActionResult> PolicyProtectedEndpoint([FromBody] SensitiveRequest request)
    {
        var result = await _secureService.ProcessSensitiveDataAsync(request);
        return Ok(result);
    }
}

File Upload/Download

[ApiController]
[Route("api/[controller]")]
public class FilesController : NabsControllerBase
{
    private readonly IFileService _fileService;

    public FilesController(IFileService fileService)
    {
        _fileService = fileService;
    }

    [HttpPost("upload")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Upload(IFormFile file)
    {
        if (file == null || file.Length == 0)
        {
            return BadRequest("No file uploaded");
        }

        var result = await _fileService.SaveFileAsync(file);
        
        if (result.IsSuccess)
        {
            return Ok(new { fileId = result.Value.Id, fileName = result.Value.Name });
        }
        
        return BadRequest(result.Errors);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Download(Guid id)
    {
        var result = await _fileService.GetFileAsync(id);
        
        if (!result.IsSuccess)
        {
            return NotFound();
        }

        var file = result.Value;
        return File(file.Content, file.ContentType, file.FileName);
    }
}

API Reference

NabsControllerBase

Class Definition
public class NabsControllerBase : ControllerBase

A base controller class that extends ASP.NET Core's ControllerBase to provide a foundation for building API controllers with standardized patterns.

Inheritance:

  • ControllerBase (ASP.NET Core)

Usage: All API controllers should inherit from NabsControllerBase to maintain consistency across the application.

[ApiController]
[Route("api/[controller]")]
public class MyController : NabsControllerBase
{
    // Controller implementation
}

Architecture and Design Patterns

Result Pattern Integration

The library is designed to work seamlessly with the Ardalis.Result pattern, which provides a standardized way to return success/failure results from service methods.

Benefits:

  • Explicit error handling without exceptions
  • Type-safe return values
  • Standardized error information
  • Better API response consistency

RESTful Design

The library encourages RESTful API design following these principles:

  • Resource-based URLs: /api/products, /api/orders
  • HTTP verb semantics: GET (read), POST (create), PUT (update), DELETE (remove)
  • Proper status codes: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 404 Not Found
  • HATEOAS-ready: Support for hypermedia links (optional)

Controller Organization

Recommended Structure:

Controllers/
├── ProductsController.cs      # Product management
├── OrdersController.cs         # Order processing
├── CustomersController.cs      # Customer management
└── UsersController.cs          # User management

Configuration Best Practices

Program.cs Setup

var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Add your services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddScoped<IOrderService, OrderService>();

var app = builder.Build();

// Configure middleware
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

await app.RunAsync();

Response Caching

[ApiController]
[Route("api/[controller]")]
public class CachedController : NabsControllerBase
{
    [HttpGet]
    [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any)]
    public async Task<IActionResult> GetCachedData()
    {
        var data = await GetDataAsync();
        return Ok(data);
    }
}

CORS Configuration

// In Program.cs
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll",
        builder =>
        {
            builder.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        });
});

app.UseCors("AllowAll");

Error Handling

Global Exception Handling

// Create a middleware for global exception handling
public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(
        RequestDelegate next,
        ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred");
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;

        var response = new
        {
            status = context.Response.StatusCode,
            message = "An error occurred processing your request.",
            detail = exception.Message
        };

        return context.Response.WriteAsJsonAsync(response);
    }
}

// Register in Program.cs
app.UseMiddleware<ExceptionHandlingMiddleware>();

Controller-Level Error Handling

[ApiController]
[Route("api/[controller]")]
public class RobustController : NabsControllerBase
{
    private readonly ILogger<RobustController> _logger;

    public RobustController(ILogger<RobustController> logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id)
    {
        try
        {
            var result = await GetDataAsync(id);
            
            if (result.IsSuccess)
            {
                return Ok(result.Value);
            }
            
            return NotFound();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error retrieving data for {Id}", id);
            return StatusCode(500, "An error occurred");
        }
    }
}

Best Practices

1. Use Async/Await Consistently

// Good
[HttpGet]
public async Task<IActionResult> GetAll()
{
    var items = await _service.GetAllAsync();
    return Ok(items);
}

// Avoid
[HttpGet]
public IActionResult GetAll()
{
    var items = _service.GetAll(); // Blocking call
    return Ok(items);
}

2. Implement Proper Status Codes

// 200 OK - Successful GET/PUT
return Ok(data);

// 201 Created - Successful POST
return CreatedAtAction(nameof(GetById), new { id }, data);

// 204 No Content - Successful DELETE
return NoContent();

// 400 Bad Request - Invalid input
return BadRequest(errors);

// 404 Not Found - Resource not found
return NotFound();

// 500 Internal Server Error - Unhandled error
return StatusCode(500, "Internal server error");

3. Use DTOs for Request/Response

// Request DTO
public record CreateProductRequest(string Name, decimal Price, string Category);

// Response DTO
public record ProductDto(Guid Id, string Name, decimal Price, string Category, DateTime CreatedAt);

[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProductRequest request)
{
    var result = await _service.CreateAsync(request);
    return CreatedAtAction(nameof(GetById), new { id = result.Value.Id }, result.Value);
}

4. Add XML Documentation

/// <summary>
/// Gets all products from the catalog
/// </summary>
/// <param name="page">Page number (default: 1)</param>
/// <param name="pageSize">Items per page (default: 10)</param>
/// <returns>A list of products</returns>
/// <response code="200">Returns the list of products</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<ProductDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
{
    var products = await _service.GetPagedAsync(page, pageSize);
    return Ok(products);
}

5. Implement Input Validation

public record CreateProductRequest
{
    [Required]
    [StringLength(100, MinimumLength = 3)]
    public string Name { get; init; }

    [Range(0.01, 999999.99)]
    public decimal Price { get; init; }

    [Required]
    public string Category { get; init; }
}

Testing

Unit Testing Controllers

public class ProductsControllerTests
{
    private readonly Mock<IProductService> _mockService;
    private readonly ProductsController _controller;

    public ProductsControllerTests()
    {
        _mockService = new Mock<IProductService>();
        _controller = new ProductsController(_mockService.Object);
    }

    [Fact]
    public async Task GetById_ExistingId_ReturnsOk()
    {
        // Arrange
        var productId = Guid.NewGuid();
        var product = new ProductDto(productId, "Test Product", 99.99m, "Electronics", DateTime.UtcNow);
        _mockService.Setup(s => s.GetByIdAsync(productId))
            .ReturnsAsync(Result<ProductDto>.Success(product));

        // Act
        var result = await _controller.GetById(productId);

        // Assert
        var okResult = Assert.IsType<OkObjectResult>(result);
        var returnedProduct = Assert.IsType<ProductDto>(okResult.Value);
        Assert.Equal(productId, returnedProduct.Id);
    }

    [Fact]
    public async Task GetById_NonExistingId_ReturnsNotFound()
    {
        // Arrange
        var productId = Guid.NewGuid();
        _mockService.Setup(s => s.GetByIdAsync(productId))
            .ReturnsAsync(Result<ProductDto>.NotFound());

        // Act
        var result = await _controller.GetById(productId);

        // Assert
        Assert.IsType<NotFoundObjectResult>(result);
    }
}

Integration Testing

public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;

    public ApiIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsSuccessStatusCode()
    {
        // Act
        var response = await _client.GetAsync("/api/products");

        // Assert
        response.EnsureSuccessStatusCode();
        Assert.Equal("application/json; charset=utf-8", 
            response.Content.Headers.ContentType?.ToString());
    }
}

Dependencies

  • Microsoft.AspNetCore.App: Core ASP.NET Core framework reference
  • Ardalis.Result: Result pattern implementation for consistent error handling

Integration with Other Libraries

With Nabs.Launchpad.Core.Persistence

[ApiController]
[Route("api/[controller]")]
public class DataController : NabsControllerBase
{
    private readonly IDbContextFactory<AppDbContext> _contextFactory;

    public DataController(IDbContextFactory<AppDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    [HttpGet]
    public async Task<IActionResult> GetData()
    {
        await using var context = await _contextFactory.CreateDbContextAsync();
        var data = await context.Entities.ToListAsync();
        return Ok(data);
    }
}

With Nabs.Launchpad.Core.Gateway

Controllers can be accessed through the API Gateway using YARP routing:

{
  "ReverseProxy": {
    "Routes": {
      "api-route": {
        "ClusterId": "api-cluster",
        "Match": {
          "Path": "/api/{**catch-all}"
        }
      }
    }
  }
}

With Nabs.Launchpad.Core.Serialisation

[ApiController]
[Route("api/[controller]")]
public class SerializationController : NabsControllerBase
{
    [HttpPost]
    public IActionResult ProcessJson([FromBody] JsonElement data)
    {
        var json = DefaultJsonSerializer.Serialize(data);
        // Process JSON
        return Ok(json);
    }
}
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.