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.