مسیر C#/.NET و آداپتورهای پشته · فصل ۱ — Modern C#، ASP.NET Core، EF Core و Testing · 328
.NET Testing, Diagnostics and Hosting
Build a bounded .NET service readiness pack that separates unit from integration evidence, executes critical requests through WebApplicationFactory/TestServer, declares host/environment/configuration and dependency lifetimes, instruments logs/metrics/traces through .NET observability primitives, captures failure diagnostics without leaking sensitive data, verifies startup/shutdown and health behavior in an isolated fixture, and gates hosting readiness on reproducible tests plus operational evidence rather than on local startup alone. میتواند «.NET Testing, Diagnostics و Hosting» را در یک سرویس .NET محدود اجرا و با تست/diagnostics مناسب verify کند.
Lesson summary
Build a bounded .NET service readiness pack that separates unit from integration evidence, executes critical requests through WebApplicationFactory/TestServer, declares host/environment/configuration and dependency lifetimes, instruments logs/metrics/traces through .NET observability primitives, captures failure diagnostics without leaking sensitive data, verifies startup/shutdown and health behavior in an isolated fixture, and gates hosting readiness on reproducible tests plus operational evidence rather than on local startup alone.
میتواند «.NET Testing, Diagnostics و Hosting» را در یک سرویس .NET محدود اجرا و با تست/diagnostics مناسب verify کند.
This lesson produces
میتواند «.NET Testing, Diagnostics و Hosting» را در یک سرویس .NET محدود اجرا و با تست/diagnostics مناسب verify کند.
Who it is for
Foundation
Use a field map, one worked scenario and explicit state vocabulary before independent construction.
Repair
Identify the failed evidence dimension, use the smallest mutated fixture and rerun only the affected gate.
Experienced
Compress definitions; require trade-off defense, counterexample analysis and changed-context transfer.
Prerequisites
Green tests, failed startup, unexplained timeouts
A small service returns a dependency's response. Its unit tests pass, and it runs on your machine. In a new environment, the service name is missing from configuration and startup fails. After you repair that setting, a slow dependency produces an error response. The log says “timeout,” but you cannot connect the message to the request or the operation that failed.
Your deliverable is the .NET Service Verification, Observability and Hosting Readiness Pack v1. It answers a specific question: for this revision, configuration and environment, which behaviors have evidence, and which remain unverified?
Plan for 60 active minutes: 10 on the model and test boundary, 15 on the example and code, 15 on guided practice, 15 on independent transfer, and 5 defending your decision. SDK installation and the supplementary .NET execution take additional time.
The core exercise runs in Python against synthetic records. Those records are not captured .NET executions. A C# project and integration tests are included, but their execution by the publisher is UNVERIFIED in this version. Completing the portable exercise demonstrates evidence analysis. Verifying the service requires results from a real execution.
This lesson is for readers who know C# and the basic structure of an ASP.NET Core API and need a defensible hosting decision beyond local startup.
Database migrations, Kubernetes or Kafka installation, cloud-provider selection and live deployment are outside this exercise.
Choose your entry point
The .NET foundation lesson is required. Familiarity with data access and migrations helps, but this exercise uses no database.
Before continuing, explain how an HTTP request reaches a handler, what dependency injection supplies to that handler, and whether calling a method directly traverses the same path.
If you need a bridge: the host brings application resources together; the request pipeline connects HTTP input to an operation; dependency injection creates the objects that operation needs. Calling a method can test its logic without proving that a route exists, configuration binds correctly, or a dependency can be constructed. Name one possible defect in each of those three areas before proceeding.
For a foundation route, work through each column of the example. For a repair route, identify the first invalid evidence dimension and repair that dimension. Experienced readers may start with independent practice; the evidence requirements and transfer task remain the same.
Readiness depends on connected evidence
A unit test examines a bounded piece of code in isolation. An integration test examines components working together. In ASP.NET Core, WebApplicationFactory<Program> can bootstrap the system under test, or SUT, and send HTTP requests through its application pipeline. Integration testing guidance
Our example replaces the external dependency with a synthetic implementation. Routes, DI, configuration and application responses are within the test boundary. The real dependency's network, TLS, reverse proxy and target host are outside it. A test name containing “integration” does not enlarge that boundary.
The accompanying flow has three exits: complete evidence supports bounded readiness; negative evidence requires repair and another execution; absent evidence requires HOLD. Its text equivalent is: bind revision and environment, examine critical tests, check configuration and dependency lifetimes, verify startup and shutdown, then inspect the injected failure. A green unit test provides no shortcut to the final decision.
This readiness gate is a MAKNOW exercise design, not a Microsoft certification. HOSTING_READY on a synthetic input means only that this fixture satisfies the declared rules.
Turn each question into an inspectable record
| Question | Required evidence | Insufficient substitute |
|---|---|---|
| What was tested? | Test ID, level, SUT, input state, expectation and observation | Total number of green tests |
| Where did the setting come from? | Environment, key and configuration source | A screenshot from a previous run |
| When is a dependency created? | Declared lifetime and scope validation | A type registration alone |
| Does the lifecycle behave correctly? | Startup, health and shutdown captures | A startup message alone |
| Can this failure be explained? | Related log and trace, metric and diagnostic capture | A generic error message |
| What does the decision apply to? | Revision, environment, context ID, input digest and reviewer | A copied local decision |
UNKNOWN means a value is not known. UNVERIFIED means the required check has not been executed or recorded. STALE means evidence no longer belongs to the relevant time or context. FAILED means an observation conflicts with a valid expectation. REVIEW_REQUIRED records the need for review. Each blocks the aggregate decision with HOLD. SECRET_EXPOSURE_RISK is a specific blocking cause that other successful tests cannot cancel.
Configuration provenance matters. ASP.NET Core's environment-variable mapping connects Service__Name to hierarchical key Service:Name. This fixture requires Service:Name and Dependency:TimeoutMs. The 100, 20 and 250 millisecond values used below are exercise choices, not platform recommendations. ASP.NET Core configuration
From valid configuration to a traceable failure
The Generic Host brings configuration, DI, logging and hosted-service lifetime management together. Its presence does not prove that your settings are valid. Generic Host
The supplied project binds related settings to options classes. Startup validation exposes a missing service name or invalid timeout:
builder.Services.AddOptions<ServiceOptions>()
.BindConfiguration("Service")
.Validate(o => !string.IsNullOrWhiteSpace(o.Name), "Service name required")
.ValidateOnStart();
ValidateOnStart moves validation to application startup. Report the violated rule without printing its value or dumping configuration. Supply test settings to the host before the application consumes the options. Options pattern
A scoped dependency typically has one instance per request scope; a singleton is shared. Here the synthetic dependency is scoped and telemetry instruments are shared. Capturing a scoped dependency directly inside a singleton breaks that lifetime boundary, so scope validation is enabled in the test host. This is a sample-specific choice, not a universal service design. Dependency lifetimes
Keep the diagnostic questions separate. A log describes an event. A metric aggregates counts or measurements. A trace connects related operations for a request. .NET provides APIs including ILogger, Meter and ActivitySource/Activity. Instrumentation in source code does not prove that data was collected or exported. Diagnostics, OpenTelemetry observability
For our injected timeout, log event 1002 must contain the operation's trace ID. Metric lesson.dependency.timeouts increases by one with only the bounded tag operation=dependency. Do not create a metric label for every request ID; request correlation belongs in logs and traces. No cloud collector is configured here. Tests receive data through in-memory listeners and a test logger.
Our health contract asks two questions: can the process respond, and is its required dependency ready? Normal readiness returns 200, unavailable-dependency readiness returns 503, and liveness during that dependency failure returns 200. These endpoint meanings and codes belong to this sample's contract. They do not prove a real load balancer's behavior.
Record shutdown separately: request it, observe the stopping signal and record host completion. A shutdown callback does not establish that every in-flight request drained. That stronger claim needs a long-running request, a deadline and a test of the target host's behavior.
Read a test as preparation, action and assertion
This excerpt comes from the normal-request test in ServiceTests.cs. ServiceFactory is the accompanying class that configures the test host:
using var factory = new ServiceFactory();
using var client = factory.CreateClient();
using var response = await client.GetAsync("/sample");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
using var payload = JsonDocument.Parse(
await response.Content.ReadAsStringAsync());
Assert.Equal("synthetic-ok",
payload.RootElement.GetProperty("value").GetString());
Preparation supplies the Testing environment and in-memory settings. The action sends GET /sample through the application; it does not call ReadAsync directly. The assertions examine both the status code and a meaningful body value. A mistyped route or an unrelated body returned with HTTP 200 should be exposed by those checks. These are expectations encoded in the test source; this version has no actual execution receipt.
The invalid-options test asks a different question. It does not expect an HTTP error response: creating the client starts the host, and options validation should reject startup there. Any exception is insufficient. The test checks for OptionsValidationException, so a missing file or broken SDK cannot count as a successful configuration test. This is why test identity, input state and expected failure type are separate evidence fields.
Follow the timeout through the handler
The handler creates a deadline for the dependency operation and links it to request cancellation. In the injected example, a 250 ms delay exceeds the 100 ms deadline. The deadline-cancellation path marks the span as an error, increments the counter, records event 1002 and returns HTTP 504. The catch filter excludes an already cancelled request; that event must not automatically be described as a dependency timeout.
Read the evidence in order: failure-producing input, response, span and log from that operation, then metric change. An aggregate count cannot identify the failed request on its own. Conversely, matching identifiers without captured evidence cannot establish that a failure ran. The exercise records are deliberately synthetic and test the decision procedure only.
The accompanying /ready endpoint reads the synthetic Dependency:Available flag. It sends no request to a real dependency. It teaches the distinction between the two health contracts. Establishing a real dependency's health requires an appropriate probe and observations from the target context.
Worked example: an expected timeout can pass a test
fixtures/baseline.json contains invented evidence for environment Testing, context local-r1. The timeout is 100 ms, normal delay is 20 ms and injected delay is 250 ms. Read expectations before deciding:
| Check | Expectation | Synthetic observation | Interpretation |
|---|---|---|---|
| Normal request | HTTP 200 | HTTP 200 | This path met its expectation |
| Injected timeout | HTTP 504 | HTTP 504 | Error handling matched the contract |
| Required setting removed | Startup rejected | Startup rejected | Invalid startup was prevented |
| Normal lifecycle | Recorded startup and shutdown | Both PASS | Only this lifecycle was covered |
| Timeout visibility | Matching log/trace IDs, metric +1 | Present | This synthetic failure is traceable |
First, match all capture identities to local-r1. Next check settings, the three critical tests, health and shutdown. Finally examine diagnostics for forbidden content. No declared blocker remains, so the example produces HOSTING_READY with scope SYNTHETIC_ONLY. It makes no claim about an executed service or actual deployment.
Why did HTTP 504 not fail the test? The failure-injection test expected that controlled response. HTTP 504 on the normal path would mean something different. A timeout log alone does not determine test success: input state, expectation and observation must stay together.
Now remove only Service:Name. Even if startup_state=PASS remains in the record, the new input lacks a required startup condition. The previous decision no longer applies. Record CONFIG_MISSING with dimension state FAILED and aggregate HOLD. Repair the configuration source and recapture startup. Editing a PASS label does not generate evidence. In the full CSV, lifecycle and telemetry columns are shared pack-level evidence repeated beside test rows. Normal startup and shutdown belong to the NORMAL scenario; missing_config tests a separate invalid-startup attempt. Repeated columns do not imply successful startup of that invalid scenario or a separately captured trace for every test.
Turn a raw observation into a defensible issue
Inspect the two decisive values before choosing a result. From learner, this command prints observations without making a decision:
python -c 'import json; x=json.load(open("fixtures/missing_env.json")); print(x["configuration"]["values"].get("Service:Name")); print(x["lifecycle"]["startup_state"])'
This file prints None, then PASS. Explain the conflict: the current required setting is absent, while the startup label still comes from the previous record. The visible rule is CONFIG_MISSING. The decision portion of your response is therefore:
{
"readiness_state": "HOLD",
"issues": [{"code": "CONFIG_MISSING", "state": "FAILED"}],
"evidence_refs": ["/configuration/values", "/lifecycle/startup_state"]
}
This is only the decision portion. The complete submission also needs identity, input and policy digests, evaluation date and three explanations. In rationale, describe the conflict. In minimal_repair, name the configuration source to repair and the startup evidence to recapture. In review_trigger, identify a configuration or environment change. “Fixed” is insufficient because it identifies neither the action nor the new observation. Use this guided example to learn the procedure; the transfer problem contains several different dimensions.
Time also belongs to the evidence. Evaluating the same baseline after October 18, 2026 makes its version review overdue. Unchanged test records do not refresh it. Record September 12 explicitly when reproducing this historical example; use the actual evaluation date for a present-day decision.
Five shortcuts that manufacture confidence
| Shortcut | Decisive evidence | Required action |
|---|---|---|
| Missing setting, old green startup label | Current required-key value | Repair configuration and capture startup again |
| Timeout log with no matching trace | IDs from the same execution | Repair correlation and inject the failure again |
| Replace a route test with a mocked method call | Test level and test-host references | Restore a request through the application |
| Print a token to improve diagnostics | Actual diagnostic content | Remove forbidden data and recheck |
| Declare readiness without a shutdown record | End-of-lifecycle capture | Preserve UNVERIFIED until evidence exists |
Do not resolve conflicting IDs by manually assigning a log to a trace. The cause might be context propagation, listener configuration or a capture from another run. Isolate the cause and reproduce the failure after repair.
The exposure fixture contains a synthetic canary, not a real credential. Failing to find that one string does not prove comprehensive redaction. Forbidden-field inspection and a review of the captured data boundary are also required. In an actual service, a memory dump or full exception can contain sensitive data; define the capture boundary beforehand. Removing a required test from the list, or changing both expectation and observation from 200 to 504, does not repair a failure. The visible fixed contract in policy.json identifies the three critical tests and their expectations. Malformed decisive input or a changed fixed exercise contract produces INPUT_INVALID, dimension state UNKNOWN and aggregate HOLD. Obtain a complete, inspectable record first.
Defend the boundary, not the test count
Evaluate this proposal: “Replace the real dependency with a fake in every test so the suite runs faster.” That fixture can help test routing and controlled failures. It cannot establish the real dependency's contract, network latency or target environment. A defensible answer names both the gained coverage and the remaining gap.
Then identify what invalidates acceptance: a changed SUT revision, configuration source, response expectation, telemetry export path or hosting profile requires new evidence for the affected dimension. A larger test count cannot repair a context mismatch.
Put the check at the right boundary
| Method | Useful coverage here | Limitation |
|---|---|---|
| Unit test | Bounded logic and many input variants | Does not execute route and host |
| WebApplicationFactory with a fake | Request pipeline, settings, DI, controlled failure | Does not prove the real dependency |
| Local process test | Process lifecycle and environment variables | Target hosting may differ |
| Disposable target-profile execution | That hosting profile's contract | More time and setup; context must be recorded |
| Logs, metrics and traces | Explain observed behavior | Do not replace explicit assertions |
For a missing setting, run the smallest startup test first. For a timeout, begin with an HTTP request against a controlled dependency. Increasing log volume indiscriminately can add cost and captured data. Collect enough related evidence to answer the diagnostic question.
Build your own decision
Use the lab package. Python 3.10+ and its standard library are sufficient; no provider account is needed. From the learner directory:
python run_checks.py --inventory
python run_checks.py --template fixtures/missing_env.json submission.json
python run_checks.py --check fixtures/missing_env.json submission.json
The second command supplies input identity and its digest while leaving the decision empty. Read policy.json, then fill in the decision, issues with code and state, JSON Pointer evidence references, rationale, minimal repair and review trigger. /configuration/values is one example of a reference. Do not copy sensitive values into your explanation.
Solve missing_env using the worked example. Then analyze uncorrelated_timeout, mocked_unit, secret_exposure and missing_shutdown without supplied decisions. The public checker examines structure, input binding and basic response consistency. Its success message alone does not establish semantic correctness; a separate semantic review is required.
For independent work, open transfer_changed.json. The environment changes to Staging, the profile becomes a disposable container, normal delay rises to 180 ms, the service name is absent from the new source, and prior captures still identify local-r1. Find the complete set of blocking causes without copying the baseline decision. Adding the service name alone cannot repair latency or stale captures.
For an optional execution extension, follow dotnet/README.en.md. It is separate from completion of the core Python exercise; any claim about actually running the service needs its own execution receipt. Its tests target the normal response, timeout, invalid startup, telemetry, health and shutdown. After a real run, record the SDK version, TRX results and fresh captures. Do not rename synthetic fixtures as real execution reports. Add --as-of 2026-09-12 to the template command to reproduce the fixed historical example. Without that option, the template records today's date in evaluated_on. It also records policy_sha256; a response made under old rules must not silently be graded under changed rules.
Use evidence_field_map.csv to locate all 21 evidence dimensions. Examine the corresponding row for each critical test, then the shared lifecycle and telemetry records. The decision belongs in your submission and is deliberately absent from starter fixtures.
For transfer, separate a proposed repair from an observed repair. You may propose a correct configuration source or a different deadline, but extending the deadline can also change the response expectation and injected-failure behavior. The affected tests need new observations bound to the new context. Editing an old capture's context_id is not a rerun. If you simulate an instructional assumption, work on a copy and preserve its synthetic status.
Make the result reproducible
Each decision must bind the fixture ID and digest, execution context, SYNTHETIC_ONLY scope, complete issue set and decisive field references. A hold also needs a minimal repair and next review condition. The digest identifies input bytes; it does not establish their truth.
The accompanying CSV provides the complete evidence table: test and level, SUT and test host, fixture state, environment and configuration source, secret boundary, dependency lifetime, startup/shutdown/health, log/metric/trace and correlation, failure and capture, hosting profile, decision and reviewer. Preserve unknown values explicitly.
Keep an actual execution record separately: timestamp, code revision, SDK/runtime version, exact command, exit code, test names and counts, expectations and observations, and sanitized diagnostic captures. A missing SDK or failed restore means UNVERIFIED, not PASS. Passing an in-memory test is still not live-deployment authorization. In the learner bundle, open resources: NET-004-T1.csv is the complete evidence table, rubric.md contains the assessment criteria, and source_links.md provides teaching and dependency-version sources.
Assessment and transfer
- Explain the unit-test versus HTTP-pipeline boundary for this service and name one behavior outside the in-memory test.
- Build the baseline readiness pack and explain why HTTP 504 can be the correct failure-injection expectation.
- Which current field invalidates a successful-startup claim when configuration is removed? Record consequence, state and minimal repair.
- Rebuild the decision for
transfer_changed. Identify the configuration, response expectation and captures that need rechecking. A copied baseline result is insufficient.
Educational acceptance requires five dimensions: correct scope, exact evidence binding, treatment of negative evidence, a bounded decision and independently recomputed transfer. A strong average score cannot compensate for secret exposure or unsupported success. The supplied rubric expands these criteria.
AI may organize and compare records. It may not invent test results, captures, owner approval or host execution. You must be able to explain the decision without a tool's hidden reasoning. The next curriculum lesson, Kubernetes and Kafka, examines the separate question of technology fit.
Sources and recheck date
The API baseline is .NET/ASP.NET Core 10. The primary Microsoft references for integration testing, diagnostics, OpenTelemetry and Generic Host were rechecked on September 12, 2026. Options, DI and configuration details use the official pages linked in the relevant sections.
The next planned review is October 18, 2026. Material changes to APIs, target versions or environment rules trigger an earlier review. Timeout thresholds, decision codes, forbidden fields and acceptance rules are local exercise design. An explanation of a platform mechanism is not evidence that this service is ready.
Assessment
There is no answer key in this lesson pack; answer it yourself and mark honestly. "Needs review" sends the lesson to your review queue.
Assessment rubric
هر ردیف باید کافی باشد؛ نمرهٔ میانگین جای شرط بحرانی را نمیگیرد. / Every dimension must be sufficient; an average cannot override a critical failure.
| معیار / Dimension | کافی / Sufficient | ناکافی / Insufficient |
|---|---|---|
| مرز / Scope | تفکیک unit، pipeline، host و وابستگی واقعی / Distinguishes unit, pipeline, host and real dependency | تست در حافظه را deployment مینامد / Claims deployment from an in-memory test |
| اتصال شواهد / Binding | شناسه، هش ورودی و قواعد، تاریخ ارزیابی، محیط و ارجاع دقیق / Exact identity, input and policy digests, evaluation date, environment and field references | نتیجهٔ قدیمی یا ارجاع نامعتبر / Copied result or invalid reference |
| شاهد منفی / Negative evidence | تمام علتهای توقف و نوع وضعیت / Complete blockers and typed states | افشا یا فقدان داده را نادیده میگیرد / Ignores exposure or missing data |
| تصمیم / Decision | دامنهٔ مصنوعی، تعمیر و محرک بازبینی / Synthetic scope, repair and review trigger | موفقیت بیشاهد / Unsupported success |
| انتقال / Transfer | محاسبهٔ دوبارهٔ تنظیم، تأخیر و زمینه / Rechecks settings, latency and context | کپی baseline یا تعمیر فقط یک علت / Copies baseline or repairs only one cause |
قبولی بخش Python اثبات اجرای C# نیست. / Passing Python does not prove C# execution. بازبین باید بتواند استدلال را از فایلهای قابلدیدن بازسازی کند. / A reviewer must reconstruct the reasoning from visible files.
References & freshness
Case studies
Lab files
Requirements: Python 3.10+, standard library only. All fixture records are synthetic.
- Run
python run_checks.py --inventory. - Read
policy.jsonand the English lesson. - Run
python run_checks.py --template fixtures/missing_env.json submission.json. - Supply your decision, issues, evidence pointers and reasoning.
- Run
python run_checks.py --check fixtures/missing_env.json submission.json.
STRUCTURE_PASS_SEMANTICS_UNREVIEWED proves structural checks only. Semantic evaluation and an evidence-based explanation are still required. No final decision is prefilled. Existing output files are not overwritten.
After the guided case, analyze the negative cases and transfer_changed. stale_review and failed_request provide additional practice. Preserve shipped inputs and explain repairs in your answer. Use a separate copy for exploratory mutations; never treat edited records as a real execution.
See dotnet/README.en.md for the supplementary C# run. Python success proves neither C# tests nor real hosting.
The template records evaluation date and policy digest. Add --as-of 2026-09-12 to --template for the historical example; the default is today. evidence_field_map.csv locates all 21 dimensions. Do not erase the fixed policy contract; malformed evidence remains UNKNOWN/HOLD.
Accompanying resources: CSV, rubric, source links.