2 hours ago
Sometimes an API contract says that a value can have more than one JSON type. Kubernetes has a practical example: can be an absolute number, such as , or a percentage, such as . Imagine an ASP.NET Core endpoint that exposes the same contract:
Depending on the deployment, the response is either or .
uses the native declaration introduced in C# 15 and .NET 11. A union is one named type that represents a value from a fixed list of case types. This union accepts an or a , but not a , a , or anything else. Union cases aren’t limited to classes in one hierarchy; they can include primitives, classes, interfaces, and nullable types.
Each case converts to the union directly without any casting:
It is also quite natural to use a statement or expression to handle the active case. Normal C# pattern matching works:
Notice that there is no fallback arm such as. The compiler knows every permitted case, so it checks that the handles them all. If another case is added to , existing switches that don’t handle it produce a warning.
For example, suppose a case is added to a union that previously contained only and , but its formatter isn’t updated:
The compiler identifies the missing case:
This feedback appears wherever the union is handled, so adding a case can’t silently leave an existing switch incomplete. That’s one of the main benefits of using a union instead of or an open hierarchy.
Before native unions, developers usually reached for, a shared base type, or a custom wrapper. accepts too much, and a base type can’t group unrelated existing types such as and . A custom wrapper can enforce the set, but it also needs its own construction and matching APIs. A native union keeps the contract in the method signature, works with regular C# patterns, and has built-in support in (STJ).
Other ways to model alternatives
Before choosing a union, it helps to separate it from two related features.
Polymorphism
Regular C# polymorphism models related types through inheritance. For example, and can derive from a common base class and share its members and behavior.
STJ has been able to serialize such a hierarchy with a discriminator. Mark the base type with and register each supported derived type with . The resulting JSON identifies the active type explicitly (see entry in the JSON):
STJ always works from that explicitly registered set; it doesn’t automatically include every type that might derive from the base in the future. If the C# base class remains open, the language doesn’t enforce the same set and a over it needs a fallback case.
Closed hierarchies
C# 15 adds support for closed class hierarchies. Adding the keyword to a base class prevents other assemblies from deriving directly from it. The compiler can then treat its known derived types as a complete list and check a for exhaustiveness:
A closed hierarchy is union-like because it represents a known set of alternatives. In C#, however, it is still an inheritance hierarchy: its cases derive from a base class and can share members and behavior. When you control the hierarchy, enforces the fixed set in the language and lets STJ infer the derived types instead of registering each one explicitly.
The modifier affects the C# type relationship; it doesn’t change JSON by itself. The serialization section below shows the default behavior first, followed by the opt-in polymorphic behavior.
Choosing a model for your API
A union isn’t always the best choice whenever an API has several possible shapes. Start with whether you control the case types and the JSON contract.
When you are designing a new API and all alternatives are classes you control, prefer a closed hierarchy with a JSON discriminator. For example,, , and can derive from . The discriminator makes the JSON self-describing, while lets the compiler check that every known event is handled.
Keep the base class open only when the model must remain extensible, such as an existing hierarchy designed for other assemblies to extend. STJ still requires every supported derived type to be registered explicitly, and callers need a fallback because the compiler can’t consider an open hierarchy exhaustive.
Choose a union when you must preserve an established discriminator-free contract, or when the cases can’t derive from one base class. This includes primitives and existing types you don’t control. A union preserves each case’s existing JSON shape and still gives callers a fixed set of types to handle.
That discriminator-free format is both a benefit and a tradeoff. Writing the active union case is straightforward. When reading, two cases can look the same in JSON and require explicit classification. For a new polymorphic contract you control, a closed hierarchy with a discriminator avoids that ambiguity.
Also consider how the contract will evolve. Adding a union case or a derived type to a closed hierarchy can produce warnings in existing exhaustive switches, immediately showing callers what they need to handle. An open hierarchy avoids that coupling by requiring a fallback from the start.
Closed-hierarchy serialization uses the same STJ polymorphism infrastructure described above. The modifier adds language-level guardrails, and lets STJ discover the derived types from those guardrails.
ASP.NET Core union support comes from STJ. Unions therefore work where ASP.NET Core uses STJ: JSON request and response bodies, SignalR‘s, and Blazor‘s JavaScript interop, persisted component state, and prerendered component parameters. They aren’t supported for query strings, route values, headers, or form fields. For more information, see Limitations.
Serializing and deserializing unions
A union type is declared with the keyword and a list of case types. The examples throughout this article use the following declarations:
STJ serializes a union value as its active case, with nothing added to represent the union itself. The union wrapper is unpacked and only the active case is written, using that case’s own JSON contract. There’s no envelope object, no field, and no discriminator of any kind:
STJ can select a union case automatically when the cases use different JSON types. For example, is unambiguous because a JSON boolean maps to and a JSON string maps to .
When multiple cases could match the same JSON value, STJ needs help choosing one. is ambiguous because both cases are JSON objects. If the payload follows an established discriminator-free contract that can’t be changed, the built-in structural classifier can distinguish object cases by their property names:
Structural classification tradeoffs
The structural classifier must scan the JSON object before STJ can deserialize it, so classification adds work proportional to the payload size. Its decision depends on the case property names, which means changing those shapes can change how existing payloads are classified or make them ambiguous. Prefer a closed hierarchy with a discriminator for new contracts you control.
If the cases can’t be distinguished by structure, an advanced scenario can supply a custom.
The opening endpoint is an output example. When that union is used as an HTTP request body, the web JSON settings also allow numbers to be read from strings, so a JSON string could match either case. Deserializing that contract requires explicit custom classification.
For an overview of STJ’s union support and classifier APIs, see What’s new in .NET libraries for .NET 11.
Serializing and deserializing closed hierarchies
The modifier doesn’t require polymorphic serialization. When code uses a concrete derived type, STJ serializes and deserializes it like any other type and doesn’t add a discriminator:
This round trip works because both the writer and reader use the concrete type. If an API instead uses the base type, the JSON needs to identify which derived type to create. Polymorphic serialization can be enabled on the closed base type:
STJ then infers the derived types in the closed hierarchy and uses their type names as discriminators. An endpoint can deserialize a request and serialize the active derived type back:
This JSON is deserialized as and serialized with the same discriminator:
The opt-in can instead be applied to a JSON pipeline with. The following sections use unions in their examples, but configured closed hierarchies flow through the same STJ-backed Minimal API, MVC, SignalR, and Blazor paths.
Minimal APIs
Unions work as request body parameters and as return types in both the runtime path () and the source-generated Request Delegate Generator (RDG). Behavior is identical across both.
Unions compose with the usual Minimal API return types. For example, a union can be returned asynchronously, wrapped in a nullable, or handed back through:
A union can also be a property of another model, an item streamed from an, or the body slot of an container. Union serialization also respects options configured through .
MVC controllers
Unions flow through the STJ input and output formatters, so controllers support them as action parameters and return types, including and results:
Union serialization and deserialization follow the rules described earlier. Controllers use just like Minimal APIs, so the input caveat in that section applies to controller actions too.
SignalR
forwards reads and writes to , so unions work as hub method parameters, return values, and stream items without any extra configuration:
On the read path, the parameter, return, or stream-item resolved from the invocation binder drives the union converter, including any classifier.
Unlike HTTP JSON binding in Minimal APIs and MVC, doesn’t treat a JSON token as ambiguous for numeric cases, so a union such as round-trips both the and cases without a classifier. Unions whose cases share the token, such as , are still ambiguous on read and require a classifier.
Unions are supported only with. The MessagePack and Newtonsoft.Json hub protocols don’t support unions, because their underlying serializers have no union support.
Blazor
Blazor works with unions in two ways, depending on whether a value stays in-process or crosses a serialization boundary. In-process component parameters are assigned directly and need no serialization, while JavaScript interop, persisted component state, and prerendered parameters serialize unions with and follow the same rules described earlier in this article.
Component parameters
A component parameter is set by direct assignment when a component is rendered from Razor markup or through. In-process rendering doesn’t serialize parameters, so a union parameter works with no extra configuration:
JavaScript interop
JavaScript interop through serializes arguments and return values with . This is useful when a JavaScript API already accepts a union-shaped contract. For example, accepts either a Boolean alignment shorthand or an options object:
The active union case is serialized using the representation expected by JavaScript: either a JSON Boolean or an options object. No union envelope or discriminator is added.
Persisted component state
serializes state with , so a union round-trips through and , including a union whose active case is .
Prerendering
When a component is prerendered with Blazor Server or Blazor WebAssembly, its parameters are serialized into the component marker with and deserialized when the component initializes on the client. Unions are supported across this boundary, including a union whose active case serializes to JSON , such as a that holds a null .
OpenAPI
The OpenAPI document represents a union as an schema, with one entry per case type:
Because a union case has no discriminator and is structurally identical to the standalone type, each case schema reuses the standalone component name. The and schemas referenced by are the same components that a standalone or endpoint produces. This differs from polymorphic types, whose derived schemas are lifted to prefixed component names because they carry a discriminator.
An endpoint can also produce multiple response types for the same status code and content type. The namespace preserves every declared response type, and the generated document emits an schema when several types share a content type:
The same support applies to MVC controllers that declare multiple attributes for one status code and content type.
Limitations
Union support requires. Binding sources that don’t route through STJ don’t support unions:
These sources bind a string token to a target type without JSON parsing, so there’s no reliable way to choose a union case. A single query value such as provides no way to know whether to bind , , , or another case. Because of this ambiguity, unions are intentionally not supported in these binding sources.
In Blazor, the same limitation applies to component parameters supplied from non-body sources, including and form binding with . These bind string or form values without JSON parsing, so they don’t support unions.
Parameter binding for unions from non-body sources is still being explored. If you have a scenario that needs it, the team is gathering feedback on the union parameter binding issue.
Summary
The two opening examples make the choice concrete. Kubernetes fits a union because it is an existing discriminator-free contract that accepts either an or a . fits a closed hierarchy because its cases form one related family and the JSON can identify each event with a discriminator.
Both models let the compiler check exhaustive switches. Choose between them based on the relationship between the alternatives and the JSON contract you need to preserve.
Additional resources
The post Use C# unions and closed hierarchies in ASP.NET Core appeared first on .NET Blog.
Code:
maxUnavailableCode:
2Code:
"25%"Code:
[code]public union IntOrString(int, string);
app.MapGet("/deployments/{name}/max-unavailable", IntOrString (string name) => Deployments.GetMaxUnavailable(name));[/code]Depending on the deployment, the response is either
Code:
2Code:
"25%"Code:
IntOrStringCode:
unionCode:
intCode:
stringCode:
boolCode:
DateTimeEach case converts to the union directly without any casting:
Code:
[code]IntOrString absolute = 2;
IntOrString percentage = "25%";[/code]It is also quite natural to use a
Code:
switchCode:
[code]static string Describe(IntOrString value) => value switch
{
int count => $"{count} pods",
string percentage => percentage,
};[/code]Notice that there is no fallback arm such as
Code:
_ => ...Code:
switchCode:
IntOrStringFor example, suppose a
Code:
stringCode:
boolCode:
decimalCode:
[code]public union SettingValue(bool, decimal, string);
static string Describe(SettingValue value) => value switch
{
bool enabled => enabled ? "enabled" : "disabled",
decimal number => number.ToString(),
// missing string case
};[/code]The compiler identifies the missing case:
Code:
[code]warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern 'string' is not covered.[/code]This feedback appears wherever the union is handled, so adding a case can’t silently leave an existing switch incomplete. That’s one of the main benefits of using a union instead of
Code:
objectBefore native unions, developers usually reached for
Code:
objectCode:
objectCode:
intCode:
stringCode:
System.Text.JsonOther ways to model alternatives
Before choosing a union, it helps to separate it from two related features.
Polymorphism
Regular C# polymorphism models related types through inheritance. For example,
Code:
CircleCode:
SquareCode:
ShapeSTJ has been able to serialize such a hierarchy with a discriminator. Mark the base type with
Code:
[JsonPolymorphic]Code:
[JsonDerivedType]Code:
$typeCode:
[code]{"$type":"circle","radius":5}[/code]STJ always works from that explicitly registered set; it doesn’t automatically include every type that might derive from the base in the future. If the C# base class remains open, the language doesn’t enforce the same set and a
Code:
switchClosed hierarchies
C# 15 adds support for closed class hierarchies. Adding the
Code:
closedCode:
switchCode:
[code]public closed record class PaymentEvent(string PaymentId);
public sealed record class PaymentInitiated(string PaymentId) : PaymentEvent(PaymentId);
public sealed record class PaymentAuthorized(string PaymentId, decimal Amount) : PaymentEvent(PaymentId);
public sealed record class PaymentFailed(string PaymentId, string Reason) : PaymentEvent(PaymentId);[/code]A closed hierarchy is union-like because it represents a known set of alternatives. In C#, however, it is still an inheritance hierarchy: its cases derive from a base class and can share members and behavior. When you control the hierarchy,
Code:
closedThe
Code:
closedChoosing a model for your API
A union isn’t always the best choice whenever an API has several possible shapes. Start with whether you control the case types and the JSON contract.
When you are designing a new API and all alternatives are classes you control, prefer a closed hierarchy with a JSON discriminator. For example,
Code:
PaymentInitiatedCode:
PaymentAuthorizedCode:
PaymentFailedCode:
PaymentEventCode:
closedKeep the base class open only when the model must remain extensible, such as an existing hierarchy designed for other assemblies to extend. STJ still requires every supported derived type to be registered explicitly, and callers need a fallback because the compiler can’t consider an open hierarchy exhaustive.
Choose a union when you must preserve an established discriminator-free contract, or when the cases can’t derive from one base class. This includes primitives and existing types you don’t control. A union preserves each case’s existing JSON shape and still gives callers a fixed set of types to handle.
That discriminator-free format is both a benefit and a tradeoff. Writing the active union case is straightforward. When reading, two cases can look the same in JSON and require explicit classification. For a new polymorphic contract you control, a closed hierarchy with a discriminator avoids that ambiguity.
Also consider how the contract will evolve. Adding a union case or a derived type to a closed hierarchy can produce warnings in existing exhaustive switches, immediately showing callers what they need to handle. An open hierarchy avoids that coupling by requiring a fallback from the start.
Closed-hierarchy serialization uses the same STJ polymorphism infrastructure described above. The
Code:
closedCode:
InferClosedTypePolymorphismASP.NET Core union support comes from STJ. Unions therefore work where ASP.NET Core uses STJ: JSON request and response bodies, SignalR‘s
Code:
JsonHubProtocolSerializing and deserializing unions
A union type is declared with the
Code:
unionCode:
[code]public union UnionIntString(int, string);
public union UnionBoolString(bool, string);
public union UnionNullableIntString(int?, string);
public record Cat(string Name, string Coat);
public record Dog(string Name, string Breed);
public union UnionPet(Cat, Dog);[/code]STJ serializes a union value as its active case, with nothing added to represent the union itself. The union wrapper is unpacked and only the active case is written, using that case’s own JSON contract. There’s no envelope object, no
Code:
$typeCode:
[code]JsonSerializer.Serialize(new UnionIntString(42)); // 42
JsonSerializer.Serialize(new UnionIntString("hello")); // "hello"
JsonSerializer.Serialize(new UnionPet(new Cat("Whiskers", "Tabby"))); // { "name": "Whiskers", "coat": "Tabby" }[/code]STJ can select a union case automatically when the cases use different JSON types. For example,
Code:
UnionBoolString(bool, string)Code:
boolCode:
stringWhen multiple cases could match the same JSON value, STJ needs help choosing one.
Code:
UnionPet(Cat, Dog)Code:
[code][JsonUnion(TypeClassifier = typeof(JsonUnionTypeStructuralClassifier))]
public union UnionPet(Cat, Dog);[/code]Structural classification tradeoffs
The structural classifier must scan the JSON object before STJ can deserialize it, so classification adds work proportional to the payload size. Its decision depends on the case property names, which means changing those shapes can change how existing payloads are classified or make them ambiguous. Prefer a closed hierarchy with a discriminator for new contracts you control.
If the cases can’t be distinguished by structure, an advanced scenario can supply a custom
Code:
JsonTypeClassifierThe opening
Code:
IntOrStringFor an overview of STJ’s union support and classifier APIs, see What’s new in .NET libraries for .NET 11.
Serializing and deserializing closed hierarchies
The
Code:
closedCode:
[code]var json = JsonSerializer.Serialize(new PaymentAuthorized("p-123", 42.5m), JsonSerializerOptions.Web);
var payment = JsonSerializer.Deserialize<PaymentAuthorized>(json, JsonSerializerOptions.Web);[/code]Code:
[code]{"paymentId":"p-123","amount":42.5}[/code]This round trip works because both the writer and reader use the concrete
Code:
PaymentAuthorizedCode:
PaymentEventCode:
[code][JsonPolymorphic(InferClosedTypePolymorphism = true)]
public closed record class PaymentEvent(string PaymentId);[/code]STJ then infers the derived types in the closed hierarchy and uses their type names as discriminators. An endpoint can deserialize a
Code:
PaymentEventCode:
[code]app.MapPost("/payment-event", (PaymentEvent paymentEvent) => paymentEvent);[/code]This JSON is deserialized as
Code:
PaymentAuthorizedCode:
[code]{"$type":"PaymentAuthorized","paymentId":"p-123","amount":42.5}[/code]The opt-in can instead be applied to a JSON pipeline with
Code:
JsonSerializerOptions.InferClosedTypePolymorphismMinimal APIs
Unions work as request body parameters and as return types in both the runtime path (
Code:
RequestDelegateFactoryCode:
[code]var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Request body: UnionBoolString is unambiguous (bool vs string), so it binds without a classifier.
app.MapPost("/flag", (UnionBoolString flag) => flag);
// Request body: UnionPet's cases are both objects, so it uses the built-in classifier shown earlier.
app.MapPost("/pet", ([FromBody] UnionPet pet) => TypedResults.Ok(pet));
// Return types: only the active case is serialized, and no classifier is needed to write.
app.MapGet("/value", () => new UnionIntString(42));
app.MapGet("/pet", () => new UnionPet(new Cat("Whiskers", "Tabby")));
app.Run();[/code]Unions compose with the usual Minimal API return types. For example, a union can be returned asynchronously, wrapped in a nullable, or handed back through
Code:
TypedResultsCode:
[code]app.MapGet("/maybe", () => new UnionNullableIntString((int?)null));
app.MapGet("/typed", () => TypedResults.Ok(new UnionPet(new Cat("Whiskers", "Tabby"))));[/code]A union can also be a property of another model, an item streamed from an
Code:
IAsyncEnumerable<T>Code:
[AsParameters]Code:
ConfigureHttpJsonOptionsMVC controllers
Unions flow through the STJ input and output formatters, so controllers support them as action parameters and return types, including
Code:
Task<TUnion>Code:
ValueTask<TUnion>Code:
[code][ApiController]
[Route("[controller]/[action]")]
[Produces("application/json")]
public class PetsController : ControllerBase
{
[HttpPost]
public UnionBoolString Echo([FromBody] UnionBoolString value) => value;
[HttpGet("{kind}")]
public UnionIntString Primitive(string kind) => kind switch
{
"value" => new UnionIntString(42),
_ => new UnionIntString("hi"),
};
}[/code]Union serialization and deserialization follow the rules described earlier. Controllers use
Code:
JsonSerializerDefaults.WebCode:
IntOrStringSignalR
Code:
JsonHubProtocolCode:
System.Text.JsonCode:
[code]public class ChatHub : Hub
{
// Union argument (client → server).
public Task Send(UnionIntString message) => Clients.All.SendAsync("Receive", message);
// Union return value (server → client).
public UnionPet GetPet() => new UnionPet(new Cat("Whiskers", "Tabby"));
// Union stream items (server → client).
public async IAsyncEnumerable<UnionIntString> Stream()
{
yield return 1;
yield return "two";
}
}[/code]On the read path, the parameter, return, or stream-item
Code:
TypeCode:
[JsonUnion]Unlike HTTP JSON binding in Minimal APIs and MVC,
Code:
JsonHubProtocolCode:
StringCode:
UnionIntString(int, string)Code:
intCode:
stringCode:
StartObjectCode:
UnionPet(Cat, Dog)Unions are supported only with
Code:
JsonHubProtocolBlazor
Blazor works with unions in two ways, depending on whether a value stays in-process or crosses a serialization boundary. In-process component parameters are assigned directly and need no serialization, while JavaScript interop, persisted component state, and prerendered parameters serialize unions with
Code:
System.Text.JsonComponent parameters
A component parameter is set by direct assignment when a component is rendered from Razor markup or through
Code:
RenderTreeBuilder.AddComponentParameterCode:
[code]<PetCard Pet="@(new UnionPet(new Cat("Whiskers", "Tabby")))" />[/code]Code:
[code]public class PetCard : ComponentBase
{
[Parameter]
public UnionPet Pet { get; set; }
}[/code]JavaScript interop
JavaScript interop through
Code:
IJSRuntimeCode:
System.Text.JsonCode:
Element.scrollIntoViewCode:
[code]public sealed record ScrollIntoViewOptions(string Behavior, string Block, string Inline);
public union ScrollIntoViewArgument(bool, ScrollIntoViewOptions);
private ValueTask ScrollAsync(
ElementReference element,
ScrollIntoViewArgument argument) =>
JS.InvokeVoidAsync("scrollElementIntoView", element, argument);
await ScrollAsync(target, false);
await ScrollAsync(target, new ScrollIntoViewOptions("instant", "start", "nearest"));[/code]Code:
[code]window.scrollElementIntoView = (element, argument) =>
element.scrollIntoView(argument);[/code]The active union case is serialized using the representation expected by JavaScript: either a JSON Boolean or an options object. No union envelope or discriminator is added.
Persisted component state
Code:
PersistentComponentStateCode:
System.Text.JsonCode:
PersistAsJsonCode:
TryTakeFromJsonCode:
nullPrerendering
When a component is prerendered with Blazor Server or Blazor WebAssembly, its parameters are serialized into the component marker with
Code:
System.Text.JsonCode:
nullCode:
UnionNullableIntStringCode:
int?OpenAPI
The OpenAPI document represents a union as an
Code:
anyOfCode:
[code]"Cat": {
"type": "object",
"properties": {
"name": { "type": "string" },
"coat": { "type": "string" }
}
},
"Dog": {
"type": "object",
"properties": {
"name": { "type": "string" },
"breed": { "type": "string" }
}
},
"UnionIntString": {
"anyOf": [
{ "type": "integer", "format": "int32" },
{ "type": "string" }
]
},
"UnionPet": {
"type": "object",
"anyOf": [
{ "$ref": "#/components/schemas/Cat" },
{ "$ref": "#/components/schemas/Dog" }
]
}[/code]Because a union case has no discriminator and is structurally identical to the standalone type, each case schema reuses the standalone component name. The
Code:
CatCode:
DogCode:
UnionPetCode:
CatCode:
DogCode:
$typeAn endpoint can also produce multiple response types for the same status code and content type. The
Code:
Microsoft.AspNetCore.Mvc.ApiExplorerCode:
anyOfCode:
[code]public record Tyrannosaurus(string Name, double BiteForceNewtons);
public record Triceratops(string Name, int HornCount);
public record Velociraptor(string Name, double TopSpeedKmh);
public union UnionDinosaur(Tyrannosaurus, Triceratops, Velociraptor);
app.MapGet("/any-of", () => Results.Ok())
.Produces<UnionPet>(StatusCodes.Status200OK, "application/json")
.Produces<UnionDinosaur>(StatusCodes.Status200OK, "application/json");[/code]The same support applies to MVC controllers that declare multiple
Code:
ProducesResponseTypeAttributeLimitations
Union support requires
Code:
System.Text.Json- Query string values
- Route values
- Header values
- Form fields
These sources bind a string token to a target type without JSON parsing, so there’s no reliable way to choose a union case. A single query value such as
Code:
?id=42Code:
intCode:
stringCode:
GuidIn Blazor, the same limitation applies to component parameters supplied from non-body sources, including
Code:
[SupplyParameterFromQuery]Code:
[SupplyParameterFromForm]Parameter binding for unions from non-body sources is still being explored. If you have a scenario that needs it, the team is gathering feedback on the union parameter binding issue.
Summary
The two opening examples make the choice concrete. Kubernetes
Code:
maxUnavailableCode:
intCode:
stringCode:
PaymentEventBoth models let the compiler check exhaustive switches. Choose between them based on the relationship between the alternatives and the JSON contract you need to preserve.
Additional resources
- C# union types (language reference)
- What’s new in ASP.NET Core in .NET 11
- JSON serialization and deserialization in .NET
- How to serialize polymorphic types with System.Text.Json
- Parameter binding in Minimal API applications
- How to create responses in Minimal API apps
The post Use C# unions and closed hierarchies in ASP.NET Core appeared first on .NET Blog.

