# MVC — AcBinary formatters

> ⚠️ **TEMPORARILY DISABLED** — formatter sources block-commented (`/* ... */`) and `Microsoft.AspNetCore.App` FrameworkReference removed from `AyCode.Services.csproj` (downstream net10.0 Hybrid client conflict on `Microsoft.AspNetCore.Mvc` namespace). Re-enable when split into a separate NuGet package / solution. Description below documents the intended state.

ASP.NET Core MVC `InputFormatter` / `OutputFormatter` pair for the AcBinary wire format. Works in controller-based MVC and Minimal API on .NET 9+. The wire payload is the raw `byte[]` produced by `AcBinarySerializer.Serialize(value, opts)` — bit-compatible with the single-shot byte[] API; no MVC-specific envelope.

> **Code:** `AyCode.Services/Mvc/` (`AcBinaryInputFormatter`, `AcBinaryOutputFormatter`, `AcBinaryMvcBuilderExtensions`)
> **Binary serializer:** `../../../AyCode.Core/AyCode.Core/docs/BINARY/README.md`

## Registration

```csharp
// Program.cs
builder.Services.AddControllers()
    .AddAcBinaryFormatters(opts => {
        opts.UseGeneratedCode = true;
    });
```

`AddAcBinaryFormatters` inserts both formatters at **index 0** of `MvcOptions.InputFormatters` / `OutputFormatters` — AcBinary wins content-negotiation when the client's `Accept` header allows.

## Media Type

`application/vnd.acbinary` (vendor tree, registered with the IANA pattern but not yet IANA-listed). The same media type is sent on both request (`Content-Type`) and response.

Override via `SupportedMediaTypes.Add(...)` on a custom formatter instance if a project-specific type is needed.

## Request flow (InputFormatter)

```
HttpContext.Request.Body                          (Stream)
  → PipeReader.Create(Body)                       (PipeReader)
  → drain-loop on calling thread:
       while (true) {
           result = await reader.ReadAsync(ct);
           foreach (segment in result.Buffer) input.Feed(segment.Span);
           reader.AdvanceTo(result.Buffer.End);
           if (result.IsCompleted) break;
       }
       input.Complete();
  ↑ background Task.Run feeds AcBinaryDeserializer.Deserialize(input, ModelType, opts)
  → ModelType instance → InputFormatterResult.Success
```

The drain-loop is **inline** in the formatter — the serializer surface ends at `AsyncPipeReaderInput`. Any I/O-specific draining (PipeReader, NamedPipe, FileStream, custom transport) is the consumer's responsibility.

## Response flow (OutputFormatter)

```
HttpContext.Response.Body                         (Stream)
  → PipeWriter.Create(Body)                       (PipeWriter)
  → AcBinarySerializer.SerializeChunked(value, ObjectType, writer, opts)
       (raw mode — pure AcBinary bytes, no [201][UINT16] framing)
  → await pipeWriter.CompleteAsync()
```

`SerializeChunked` (not `SerializeChunkedFramed`) — the wire is a single self-contained AcBinary blob, identical to `Serialize(value, opts) → byte[]`. No multiplexed framing on the HTTP body.

## Error model

Deserialization failure → `ModelState.TryAddModelError(ModelName, ex.Message)` → `InputFormatterResult.Failure()`. ASP.NET pipeline emits `400 Bad Request` with `application/problem+json` (RFC 7807) — **not** an AcBinary-encoded error. The client reads the error body as JSON.

`OperationCanceledException` (when `RequestAborted` is signalled) is rethrown so the pipeline aborts the response cleanly.

## Cancellation

`HttpContext.RequestAborted` flows into both formatters. The InputFormatter passes it to `PipeReader.ReadAsync` and `Task.Run`; the OutputFormatter calls `cancellationToken.ThrowIfCancellationRequested()` after `CompleteAsync`. Mid-request abort releases all pooled resources via the `using` and `finally` blocks.

## What the formatter does NOT include

- **No Stream-async API in the binary core** — `AcBinarySerializer` has no `SerializeAsync(Stream, T)` method. The formatter is the wrapper. (See `BINARY_TODO.md#accore-bin-t-t8k3` — parked.)
- **No options thread-safety guard** — `AcBinarySerializerOptions` is currently mutable; if registered as a DI singleton with `Configure<>` it is fine because `Configure` is read-only at runtime, but raw-shared mutable instances across concurrent requests are unsafe. (See `BINARY_ISSUES.md#accore-bin-i-l8n5` and `BINARY_TODO.md#accore-bin-t-b7h4`.)
- **No OpenAPI metadata helpers** — `Microsoft.AspNetCore.OpenApi` / `Swashbuckle.AspNetCore` pick up `SupportedMediaTypes` automatically; no extra integration needed.

## Future work

The formatter currently lives in `AyCode.Services` (alongside the SignalR transport). The intent is to extract it into its own NuGet package — `AyCode.AspNetCore.Mvc.Formatters.AcBinary` — when the binary serializer is moved to a dedicated solution. No code change required at extraction time; only project-file split.
