Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/build-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ on:
- "cloud-native/polly-resilience/**"
- "csharp-language/csharp-12-features/**"
- "csharp-language/high-performance-memory-management/**"
- "csharp-language/modern-patterns-result-pipeline/**"
- ".github/workflows/build-samples.yml"

pull_request:
Expand All @@ -44,6 +45,7 @@ on:
- "cloud-native/polly-resilience/**"
- "csharp-language/csharp-12-features/**"
- "csharp-language/high-performance-memory-management/**"
- "csharp-language/modern-patterns-result-pipeline/**"
- ".github/workflows/build-samples.yml"

workflow_dispatch:
Expand Down Expand Up @@ -894,3 +896,100 @@ jobs:
)"

test "${line_count}" = "6"

test-result-pipeline:
name: Test C# Result pipeline sample
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Install .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"

- name: Restore
run: >
dotnet restore
csharp-language/modern-patterns-result-pipeline/ResultPipelineLab.slnx

- name: Build
run: >
dotnet build
csharp-language/modern-patterns-result-pipeline/ResultPipelineLab.slnx
--configuration Release
--no-restore

- name: Test
run: >
dotnet test
csharp-language/modern-patterns-result-pipeline/ResultPipelineLab.slnx
--configuration Release
--no-build

- name: Verify explicit C# language version
shell: bash
run: |
app_version="$(
dotnet msbuild \
csharp-language/modern-patterns-result-pipeline/src/ResultPipelineLab/ResultPipelineLab.csproj \
-getProperty:LangVersion
)"

test_version="$(
dotnet msbuild \
csharp-language/modern-patterns-result-pipeline/tests/ResultPipelineLab.Tests/ResultPipelineLab.Tests.csproj \
-getProperty:LangVersion
)"

echo "Application LangVersion: ${app_version}"
echo "Test LangVersion: ${test_version}"

test "${app_version}" = "12.0"
test "${test_version}" = "12.0"

- name: Run deterministic Result pipeline sample
shell: bash
run: |
output="$(
dotnet run \
--project csharp-language/modern-patterns-result-pipeline/src/ResultPipelineLab/ResultPipelineLab.csproj \
--configuration Release \
--no-build
)"

printf '%s\n' "${output}"

printf -v expected '%s\n' \
'C# 12 Result Pipeline Lab' \
'Success: imported=2, writes=1' \
'Products: Widget Pro | Travel Mug' \
'Failure: code=VALIDATE_BATCH_FAILED, writes=0' \
'Public message: 1 record(s) failed validation.'

expected="${expected%$'\n'}"

if [[ "${output}" != "${expected}" ]]; then
echo "Console output did not match the expected output."

diff \
--unified \
<(printf '%s\n' "${expected}") \
<(printf '%s\n' "${output}") \
|| true

exit 1
fi

line_count="$(
printf '%s\n' "${output}" |
wc --lines |
tr --delete ' '
)"

test "${line_count}" = "5"
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Each sample folder contains a focused implementation of one tutorial topic. The
| [`cloud-native/polly-resilience`](cloud-native/polly-resilience/) | Focused .NET 10 Polly v8 catalog sample demonstrating explicit stale-cache fallback, timeout-driven degradation, outbound concurrency isolation, strategy event counters, and deterministic integration testing | [Polly Resilience (Polly v8): Timeouts, Retries, Circuits, Bulkheads, Hedging](https://www.dotnet-guide.com/tutorials/cloud-native/polly-resilience/) |
| [`csharp-language/csharp-12-features`](csharp-language/csharp-12-features/) | Focused .NET 10 console lab locked to C# 12.0, demonstrating primary constructors with explicit properties, collection expressions and spreads, default lambda parameters, tuple-type aliases, deterministic output, and unit tests | [C# 12 Language Features: Primary Constructors, Collections & More](https://www.dotnet-guide.com/tutorials/csharp-language/csharp-12-features/) |
| [`csharp-language/high-performance-memory-management`](csharp-language/high-performance-memory-management/) | Focused .NET 10 UTF-8 ingestion sample demonstrating PipeReader framing, segmented ReadOnlySequence handling, span-based numeric parsing, bounded MemoryPool ownership, strict input validation, deterministic output, and tests | [High-Performance C#: Span<T>, Memory<T>, SIMD & Pipelines](https://www.dotnet-guide.com/tutorials/csharp-language/high-performance-memory-management/) |
| [`csharp-language/modern-patterns-result-pipeline`](csharp-language/modern-patterns-result-pipeline/) | Focused .NET 10 console companion locked to C# 12.0, demonstrating a custom Result type, safe and diagnostic errors, Map/Bind/BindAsync composition, invariant text-import validation, short-circuit persistence, cancellation, deterministic output, and tests | [C# 12 Functional Patterns: Result Type, Error Handling & Composable Pipeline Testing](https://www.dotnet-guide.com/tutorials/csharp-language/modern-patterns-result-pipeline/) |

## Companion articles
- [Common Microsoft.Extensions.AI mistakes](https://www.dotnet-guide.com/articles/dotnet-ai/microsoft-extensions-ai-common-mistakes/)
Expand Down Expand Up @@ -309,6 +310,29 @@ tutorials/
| | `-- TestSupport/
| | |-- ChunkedReadStream.cs
| | `-- SegmentedSequence.cs
| |-- modern-patterns-result-pipeline/
| | |-- ResultPipelineLab.slnx
| | |-- README.md
| | |-- src/
| | | `-- ResultPipelineLab/
| | | |-- ResultPipelineLab.csproj
| | | |-- Program.cs
| | | |-- Core/
| | | | |-- Result.cs
| | | | `-- ResultExtensions.cs
| | | |-- Models/
| | | | `-- ImportModels.cs
| | | |-- Persistence/
| | | | `-- InMemoryProductStore.cs
| | | `-- Pipeline/
| | | |-- ImportPipeline.cs
| | | |-- ParseStage.cs
| | | |-- TransformStage.cs
| | | `-- ValidateStage.cs
| | `-- tests/
| | `-- ResultPipelineLab.Tests/
| | |-- ResultPipelineLab.Tests.csproj
| | `-- ResultPipelineTests.cs
|-- aspnet-core/
| |-- api-security-in-practice/
| | |-- ApiSecurityMinimal.slnx
Expand Down
212 changes: 212 additions & 0 deletions csharp-language/modern-patterns-result-pipeline/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# C# 12 Result Pipeline Lab

A focused .NET 10 console companion demonstrating typed expected failures,
`Map`, `Bind`, `Match`, one async persistence boundary, invariant validation,
safe error projection, short-circuit behavior, cancellation, deterministic
output, and tests.

## Full tutorial

[C# 12 Functional Patterns: Result Type, Error Handling & Composable Pipeline Testing](https://www.dotnet-guide.com/tutorials/csharp-language/modern-patterns-result-pipeline/)

## Framework and language note

The tutorial is written for C# 12 with the .NET 8 generation.

This companion targets .NET 10 because that is the DOTNET GUIDE repository's
current SDK, but both projects explicitly set:

```xml
<LangVersion>12.0</LangVersion>
```

The sample uses an explicit fallback arm in every switch expression over
`Result<T>`.

C# 12 does not infer a closed hierarchy merely because the base constructor is
private and the known cases are nested and sealed.

## Pipeline

```text
restricted text
-> Parse
-> Bind Validate
-> Map Transform
-> BindAsync Persist
-> Match at the edge
```

Expected parse, validation, and configured storage failures are returned as
`Result<T>.Failure`.

Caller cancellation and programming defects remain exceptions.

## Input format

```text
Name,Price,Category,Stock
Widget Pro,9.99,Electronics,50
```

This is a deliberately restricted comma-delimited format.

It does not support:

- quoted fields;
- commas inside values;
- escaped quotes;
- embedded newlines;
- locale-specific decimal separators.

Use a reviewed CSV library when those capabilities are required.

## Error projection

`PipelineError` contains:

```text
Code
Message
Detail
```

`ToPublic()` returns only:

```text
Code
Message
```

`ToDiagnostic(stage)` includes internal detail.

The existence of two projections does not automatically make debug detail safe.

Do not serialize or ship diagnostic detail to external consumers without a
reviewed redaction and sink policy.

## Map and Bind

Use `Map` when validated input is transformed without an expected failure
result.

Use `Bind` when the next stage returns its own `Result`.

Use `BindAsync` to connect the prepared synchronous result to the asynchronous
persistence boundary.

The callbacks can still throw programming exceptions.

## Validation

Numeric values are parsed with `CultureInfo.InvariantCulture`.

Parsed values are carried forward in `ValidatedProduct`, so transformation does
not parse the same text again.

All invalid rows are collected into internal detail.

This sample is fail-whole-batch, not partial-success import.

## Persistence

The in-memory store has an asynchronous-shaped API for composition.

It supports:

- deterministic success;
- deterministic expected rejection;
- cancellation.

It is not a database simulation or throughput benchmark.

## Prerequisite

- .NET 10 SDK

## Restore, build, test, and run

```powershell
dotnet restore `
.\ResultPipelineLab.slnx

dotnet build `
.\ResultPipelineLab.slnx `
--configuration Release `
--no-restore

dotnet test `
.\ResultPipelineLab.slnx `
--configuration Release `
--no-build

dotnet run `
--project .\src\ResultPipelineLab\ResultPipelineLab.csproj `
--configuration Release `
--no-build
```

## Expected output

```text
C# 12 Result Pipeline Lab
Success: imported=2, writes=1
Products: Widget Pro | Travel Mug
Failure: code=VALIDATE_BATCH_FAILED, writes=0
Public message: 1 record(s) failed validation.
```

## Project structure

```text
ResultPipelineLab.slnx
README.md
src/
`-- ResultPipelineLab/
|-- ResultPipelineLab.csproj
|-- Program.cs
|-- Core/
| |-- Result.cs
| `-- ResultExtensions.cs
|-- Models/
| `-- ImportModels.cs
|-- Persistence/
| `-- InMemoryProductStore.cs
`-- Pipeline/
|-- ImportPipeline.cs
|-- ParseStage.cs
|-- TransformStage.cs
`-- ValidateStage.cs
tests/
`-- ResultPipelineLab.Tests/
|-- ResultPipelineLab.Tests.csproj
`-- ResultPipelineTests.cs
```

## Deliberately omitted

- third-party Result packages;
- FluentAssertions;
- structured logging providers;
- databases;
- ASP.NET Core;
- file uploads;
- full CSV behavior;
- partial success;
- retries;
- exception swallowing.

## Verification

- Companion target framework: .NET 10
- Explicit language version: C# 12.0
- Application NuGet dependencies: none
- External services required: none
- Expected tests: 10
- Expected console lines: 5
- Last reviewed: 2026-08-05

This sample teaches composition and failure semantics. A production Result
abstraction requires decisions about nullability, equality, error accumulation,
serialization, observability, cancellation, exception translation, API
contracts, and team conventions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/ResultPipelineLab/ResultPipelineLab.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ResultPipelineLab.Tests/ResultPipelineLab.Tests.csproj" />
</Folder>
</Solution>
Loading
Loading