On this page

HTTP host and responses

The controller is DirectSharp application code using ordinary ASP.NET Core types. It translates requests into service calls and application outcomes into HTTP responses. The application service and repository stay independent of HTTP.

Controller and response handling

CreateCustomerRequest contains only editable input. The service chooses the customer's identifier and initial activation state.

import Global.System
import Global.System.Collections.Generic
import Global.System.Linq
import Global.Microsoft.AspNetCore.Mvc
import Global.Microsoft.AspNetCore.Routing
import Global.Microsoft.Extensions.Logging

public data CreateCustomerRequest
    Name as String
    Email as String
end data

public class CustomersController
    attributes ApiController(), Route("api/customers")
    inherits ControllerBase

    private readonly service as CustomerRegistrationService
    private readonly logger as ILogger <of CustomersController>

    constructor(registration as CustomerRegistrationService, log as ILogger <of CustomersController>)
        require registration
        require log
        service = registration
        logger = log
    end constructor

    public async action Get(id as Guid)
        returns IActionResult
        attributes HttpGet("{id:guid}")

        if id = Guid.Empty then
            let error = new Dictionary <of String, String>()
            error["error"] = "A customer identifier is required"
            return BadRequest(error)
        end if

        try
            let customer = await service.FindAsync(id)
            if customer is nothing then
                return NotFound()
            end if
            return Ok(customer)
        catch error as CustomerStorageException
            logger.LogError(error, "Customer lookup failed for {CustomerId}", id)
            return StorageFailure("Customer lookup failed")
        end try
    end action

    public async action Create(input as CreateCustomerRequest)
        returns IActionResult
        attributes HttpPost()

        let customer = new Customer()
        customer.Name = input.Name
        customer.Email = input.Email
        let validation = service.Check(customer)
        if not validation.IsValid then
            let errors = validation.Errors.GroupBy(.PropertyName).ToDictionary(
                group: group.Key,
                group: group.Select(.ErrorMessage).ToArray()
            )
            return BadRequest(new ValidationProblemDetails(errors))
        end if

        try
            let id = await service.RegisterAsync(customer)
            let location = new RouteValueDictionary()
            location["id"] = id
            return CreatedAtAction("Get", location, customer)
        catch error as CustomerStorageException
            logger.LogError(error, "Customer registration failed")
            return StorageFailure("Customer registration failed")
        end try
    end action

    private action StorageFailure(title as String)
        returns IActionResult
        let problem = new ProblemDetails()
        problem.Status = 500
        problem.Title = title
        return StatusCode(500, problem)
    end action
end class

The attribute declarations are normal CLR metadata interpreted by ASP.NET Core. They do not introduce a web-specific language or validation DSL. Constructor injection supplies the service and logger. Explicit async actions carry the standard cancellation context; their service calls forward that context to the repository. The framework supplies request cancellation through the CLR cancellation-token contract.

The response policy is visible in the actions: 400 for invalid input, 404 for an absent customer, 200 for a successful lookup and 201 for registration. Validation messages are grouped by property. CreatedAtAction uses the ordinary MVC action name Get and route values to produce the new customer's location.

Only CustomerStorageException is translated to a generic 500 response. Cancellation and unrelated programming failures propagate. SQL text and connection details stay out of the response. A database failure alone is not evidence that retrying would succeed.

Hosting and DI

ASP.NET Core hosts the controller and activates it through its ordinary constructor. Register the validator with a lifetime suitable for its dependencies, and give the repository and registration service request scope. A connection factory creates a fresh connection for each repository operation; the repository owns the returned connection.

The controller, validator, service and repository all remain ordinary application classes. Configuration, authentication, authorization and host startup are separate application concerns; see .NET integration for their relationship to application code.

Previous: persistence. Next: checks and policy changes.