MergePatchDto 0.2.0

dotnet add package MergePatchDto --version 0.2.0
                    
NuGet\Install-Package MergePatchDto -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MergePatchDto" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MergePatchDto" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="MergePatchDto" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MergePatchDto --version 0.2.0
                    
#r "nuget: MergePatchDto, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MergePatchDto@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MergePatchDto&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=MergePatchDto&version=0.2.0
                    
Install as a Cake Tool

MergePatchDto

MergePatchDto is a NuGet package for .NET APIs that want DTO-shaped PATCH request bodies while still distinguishing omitted properties, explicit null, and explicit values. It keeps PATCH bodies as ordinary JSON objects instead of JSON Patch operation arrays.

It gives PATCH endpoints:

  • a typed allowlist of patchable fields
  • explicit null vs missing tracking
  • optional generated ApplyTo mapping

That lets a PATCH endpoint distinguish:

  • missing property: leave the target unchanged
  • explicit null: assign or clear the target value
  • explicit value: assign the target value

Install

dotnet add package MergePatchDto

Quick Start

Define a partial patch DTO and point it at the type it updates:

using MergePatch;
using YourApi.Models;

[MergePatch(typeof(Person))]
public partial class UpdatePersonPatch
{
    public string Name { get; set; } = "";
    public string? Email { get; set; }
    public string? Bio { get; set; }
}

The patch DTO is the allowlist: only properties declared on UpdatePersonPatch can be updated through this endpoint. The target type enables generated ApplyTo; it does not make every Person property patchable.

Use it in a normal PATCH endpoint after loading the entity being updated:

[HttpPatch("{id:guid}")]
public async Task<IActionResult> PatchPerson(
    Guid id,
    UpdatePersonPatch patch,
    CancellationToken cancellationToken)
{
    var person = await db.People.FindAsync([id], cancellationToken);

    if (person is null)
    {
        return NotFound();
    }

    patch.ApplyTo(person);

    await db.SaveChangesAsync(cancellationToken);

    return NoContent();
}

ApplyTo only applies fields that were present in the JSON body. For example:

{
  "bio": null
}

This clears Person.Bio and leaves every omitted property unchanged.

The same presence information is available directly when an endpoint needs custom logic:

if (patch.Has.Bio)
{
    person.Bio = patch.Bio;
}

Use C# nullability to model whether a field may be set to null; presence is tracked separately through the generated Has API. A property does not need to be nullable just to be optional in the request body. Omitted fields are skipped because Has is false. This patch DTO uses nullable properties where the API accepts explicit clearing.

For renamed target properties, accepted client metadata, or domain-specific update methods, use the mapping attributes described below.

Manual Updates

Generated ApplyTo is useful when patch properties map cleanly to a target type. When validation or domain logic needs to act only on fields sent by the client, use the generated Has API directly.

Targetless [MergePatch] DTOs are for endpoints that want presence tracking but own the update logic. Omit the target type:

using MergePatch;

[MergePatch]
public partial class ManualPersonPatch
{
    public bool? IsActive { get; set; }
    public string? AdminNote { get; set; }
}

MergePatchDto still generates the JSON converter and Has API, but no ApplyTo method:

var has = patch.Has;

if (has.IsActive)
{
    person.IsActive = patch.IsActive.GetValueOrDefault();
    person.DeactivatedAt = person.IsActive ? null : DateTimeOffset.UtcNow;
}

if (has.AdminNote)
{
    person.AdminNote = patch.AdminNote;
}

Mapping Attributes

By default, patch properties map to target properties with the same CLR name. If a target property should not be patchable, leave it off the DTO.

  • [PatchTo(nameof(Target.OtherName))] maps a patch property to a differently named target property.
  • [PatchIgnore] is for accepted DTO properties that should still be deserialized and presence-tracked, but excluded from generated ApplyTo.
  • [PatchUsing(nameof(Method))] calls custom domain logic when the patch property was provided.
  • Targets can be interfaces when the same patch shape should apply to multiple concrete types.
[MergePatch(typeof(Person))]
public partial class UpdatePersonPatch
{
    [JsonPropertyName("phone")]
    [PatchTo(nameof(Person.PhoneNumber))]
    public string? Phone { get; set; }

    [PatchIgnore]
    public string? RequestId { get; set; }

    [PatchUsing(nameof(ApplyAge))]
    public int? Age { get; set; }

    private static void ApplyAge(Person target, int? value)
    {
        target.SetAge(value);
    }
}

Supported custom apply signatures are:

private static void ApplyAge(Person target, int? value)
private static void ApplyAge(UpdatePersonPatch patch, Person target, int? value)

Target-specific mapping attributes are invalid on targetless patch types. Invalid patch shapes and mappings fail at build time with MPDxxx diagnostics.

JSON Behavior

MergePatchDto uses System.Text.Json for patch DTOs. It records top-level property presence, including properties sent as null.

Nested object properties are replacement values, not recursive document merges. Arrays are replacement values, not element-level merges.

Unknown JSON properties are ignored by default. To reject them:

[MergePatch(UnknownPropertyHandling = UnknownPropertyHandling.Reject)]
public partial class StrictPatch
{
    public string? Name { get; set; }
}

Compatibility

MergePatchDto supports SDK-style projects built with the .NET 8 SDK or newer. The runtime assembly targets netstandard2.0, but the source generator runs inside the consumer's C# compiler, so the supported compiler floor is the .NET 8 SDK / Roslyn 4.8.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 94 7/5/2026
0.1.0 89 7/5/2026