Skip to content

Architecture

SAIF.Agency.Api is a conventional three-layer ASP.NET Web API 2 service hosted in IIS. Controllers bind and authorise the request, a service layer holds the agency rules, and NHibernate maps the AGENCY schema behind an IUnitOfWork scoped to the request. Autofac is the composition root and is registered before routing during Application_Start, so every controller is constructor-injected and no controller resolves a service itself.

Hosting model

The service is hosted in IIS. Global.asax instantiates AgencyApiApplication : System.Web.HttpApplication, whose Application_Start runs the App_Start configuration classes, including WebApiConfig.Register(HttpConfiguration).

Aspect Finding
Host IIS (System.Web.HttpApplication) — no OWIN, no WCF, no self-host
Application class AgencyApiApplication (src/SAIF.Agency.Api/Global.asax.cs)
Startup call chain Application_Start → AutofacConfig.Register → GlobalConfiguration.Configure(WebApiConfig.Register) → SwaggerConfig.Register → ConfigureOpenTelemetry
Virtual directory / app pool Site agencyapi, virtual directory /agency, application pool AgencyApiPool (.NET CLR v4.0, Integrated)
Target framework .NET Framework 4.8

Request pipeline

flowchart TD
    CLIENT["Client request<br/>SAIF.Agency.Api.Client or agency portal"] --> IIS["IIS + AgencyApiApplication<br/>Windows authentication"]
    IIS --> HANDLER["AgencyApiCorrelationHandler<br/>DelegatingHandler"]
    HANDLER --> ROUTE["Attribute routing<br/>MapHttpAttributeRoutes()"]
    ROUTE --> AUTH["SaifAuthorize filter<br/>role check"]
    AUTH --> CTRL["AgenciesController : AgencyApiBase"]
    CTRL --> SVC["AgencyService<br/>business rules"]
    SVC --> UOW["NHibernate IUnitOfWork<br/>IUnitOfWorkFactory('oracle')"]
    UOW --> DB[(Oracle AGENCY schema)]
    CTRL --> ERR["AgencyApiExceptionFilter<br/>global exception filter"]

The service makes no downstream HTTP calls, so no SAIF.Web.API.Clients node appears in the pipeline: every response is assembled from the AGENCY schema.

Layer responsibilities

Layer Type Responsibility
AgencyApiApplication System.Web.HttpApplication Application start-up and configuration wiring
App_Start\WebApiConfig.cs Configuration Attribute route mapping, XML formatter removal, JSON serializer settings, registration of AgencyApiCorrelationHandler and AgencyApiExceptionFilter
AgenciesController AgencyApiBase : ApiController Request binding, role checks, delegation to the service layer, result construction
AgencyService Service Agency lookup, search filtering, roster assembly, and contact update rules (BR-001 to BR-008)
AgencyRepository NHibernate Persistence through IUnitOfWork against the AGENCY schema

Routing configuration

Aspect Finding
MapHttpAttributeRoutes() called Yes
Convention route templates None — the default api/{controller}/{id} route is commented out in WebApiConfig.Register
Route prefix / versioning [RoutePrefix("v1/agencies")], [RoutePrefix("v1/agents")], [RoutePrefix("v1/appointments")]; controllers live under Controllers\v1\
Controller folder layout Controllers\AgencyApiBase.cs at the root, versioned controllers in Controllers\v1\

Base controller

All three controllers derive from AgencyApiBase : ApiController rather than from ApiController directly. The base class resolves the caller's Windows identity into an AgencyCaller record used for audit stamping, exposes NotFoundForAgency(agencyNumber) and ValidationFailure(result) helpers that produce the service's standard ApiErrorDto body, and writes an ILogService entry carrying the correlation id added by AgencyApiCorrelationHandler. Every derived controller inherits that behaviour, so error bodies and audit attribution are identical across the API.

Controller Base Inherited behavior
AgenciesController AgencyApiBase Caller resolution, ApiErrorDto construction, correlated request logging
AgentsController AgencyApiBase Caller resolution, ApiErrorDto construction, correlated request logging
AppointmentsController AgencyApiBase Caller resolution, ApiErrorDto construction, correlated request logging

Composition root

Field Value
Project SAIF.Agency.Api
File src/SAIF.Agency.Api/App_Start/AutofacConfig.cs
Entry method AutofacConfig.Register
Invoked from AgencyApiApplication.Application_Start, first statement, before GlobalConfiguration.Configure(WebApiConfig.Register)
Resolver AutofacWebApiDependencyResolver assigned to GlobalConfiguration.Configuration.DependencyResolver
Autofac version 4.9.4 (Autofac.WebApi2 4.3.1)
Additional registration files src/SAIF.Agency.Api/DependencyModules/AgencyServiceModule.cs

AutofacConfig.Register builds a single ContainerBuilder. It calls RegisterCommonDependencies("agencyDb", "AGENCY") from SAIF.Web.Mvc.Extensions, which supplies ILogService, the Environment API configuration reader, and the NHibernate session factory configured for the named Oracle database and schema; the FluentNHibernate class maps are picked up by scanning the SAIF.Agency.Api.Data assembly. It then registers the API's own controllers with RegisterApiControllers(Assembly.GetExecutingAssembly()), loads AgencyServiceModule for the service and repository registrations, and finally builds the container and assigns the resolver.

Registration Lifetime Resolved by Purpose
IAgencyService → AgencyService InstancePerRequest AgenciesController Agency lookup, search, roster, and contact update rules
IAgentService → AgentService InstancePerRequest AgentsController, AgencyService Agent lookup and appointment filtering
IAppointmentService → AppointmentService InstancePerRequest AppointmentsController Appointment reads and creation
IAgencyRepository → AgencyRepository InstancePerRequest AgencyService NHibernate queries and writes against the AGENCY schema
IContactValidator → ContactValidator InstancePerLifetimeScope AgencyService Email and phone validation supplied by SAIF.Framework.Validation
IUnitOfWorkFactory("oracle") SingleInstance IUnitOfWork resolution NHibernate unit of work factory for the Oracle AGENCY schema
IUnitOfWork InstancePerRequest AgencyRepository Session and transaction scope for one request
ILogService SingleInstance AgencyApiBase, AgencyApiExceptionFilter Structured application logging

Error handling

Mechanism Registered in Scope Behavior
AgencyApiExceptionFilter : ExceptionFilterAttribute src/SAIF.Agency.Api/App_Start/WebApiConfig.cs (config.Filters.Add) Global Maps AgencyNotFoundException to 404 Not Found, AgencyValidationException to 400 Bad Request, AgencyStateConflictException and StaleAgencyVersionException to 409 Conflict, and anything else to 500 Internal Server Error. Writes an ApiErrorDto carrying code, message, and the correlation id, and logs the full exception through ILogService.
AgencyApiCorrelationHandler : DelegatingHandler src/SAIF.Agency.Api/App_Start/WebApiConfig.cs (config.MessageHandlers.Add) Global Adds or echoes the X-Correlation-Id header on every request and response so a failure can be traced from a consumer's log to this service's log. Does not translate exceptions.
Per-action try/catch src/SAIF.Agency.Api/Controllers/v1/AgenciesController.cs (UpdateAgencyContact) Per-action Catches StaleAgencyVersionException to add the current version to the 409 body before rethrowing for the filter to format.

A caller never receives exception detail. ApiErrorDto carries a stable error code, a message written for the caller, and the correlation id; the stack trace and the Oracle error text stay in the ILogService entry.

API documentation surface

Aspect Finding
Swagger / Swashbuckle Active (SwaggerConfig.Register called from Application_Start)
Exposed route /swagger/ui/index
Package and version Swashbuckle 5.6.0

Telemetry

Aspect Finding
Instrumentation ConfigureOpenTelemetry(o => o.Owner = "Agency Systems Team")
Registered in src/SAIF.Agency.Api/Global.asax.cs

Design constraints

  • Attribute routing only: a new endpoint is invisible unless it carries a [Route] attribute, because no convention route exists to fall back on.
  • Every request runs inside one IUnitOfWork. A controller action that needs two independent transactions has no supported way to express that.
  • Controllers must derive from AgencyApiBase; a controller derived directly from ApiController would produce a different error body and would not stamp the correlation id.
  • The service holds no cache. Every read is an Oracle round trip, which keeps agency data current at the cost of load on the AGENCY schema.

Integration points

System Type Purpose
Oracle AGENCY schema Database Agency, agent, appointment, and contact audit records
Environment API Configuration service Supplies the agencyDb connection details and environment-specific settings at start-up

Sources

  • src/SAIF.Agency.Api/Global.asax.cs
  • src/SAIF.Agency.Api/App_Start/AutofacConfig.cs
  • src/SAIF.Agency.Api/App_Start/WebApiConfig.cs
  • src/SAIF.Agency.Api/App_Start/SwaggerConfig.cs
  • src/SAIF.Agency.Api/DependencyModules/AgencyServiceModule.cs
  • src/SAIF.Agency.Api/Controllers/AgencyApiBase.cs
  • src/SAIF.Agency.Api/Filters/AgencyApiExceptionFilter.cs
  • src/SAIF.Agency.Api/Handlers/AgencyApiCorrelationHandler.cs
  • src/SAIF.Agency.Api/packages.config