Partial Resource Updates in ASP.NET Core: JSON Patch vs Nullable DTO vs Custom Update Strategy β Enterprise Decision Guide

Search for a command to run...

No comments yet. Be the first to comment.
Most of the .NET conversation about the Model Context Protocol is about building servers: expose your API as MCP tools, point Claude or VS Code at it, done. That is the half that gets written about. T

The first RAG system I shipped answered beautifully about concepts and failed completely on part numbers. Ask it "how do I reset a stuck deployment" and it nailed the answer. Ask it "what does error P

AutoMapper has been the default object mapper in .NET for over a decade, and for most of that time nobody thought about it. Version 15.0.0 changed that. It ships under a dual license now, with a free

You built the pipeline. Documents are chunked, embedded, and sitting in a vector store. Retrieval returns results. And your RAG answers still hallucinate in .NET, confidently telling users about a stu

You upgraded a working API to .NET 10, hit build, and Swashbuckle fell apart. Missing namespaces, OpenApiSchema refusing to compile, AddSecurityRequirement complaining about a delegate it never wanted

Coding Droplets
304 posts
Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.
From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.
Partial resource updates are one of the most quietly contentious design decisions in ASP.NET Core API development. Every team hits the question eventually: a client sends a PATCH request to update two fields on a record that has twenty. What is the right server-side strategy? The answer shapes your API contract, your validation pipeline, your EF Core mapping, and how your consumers reason about state.
Three strategies dominate in practice: JSON Patch (RFC 6902), nullable DTO partial updates, and custom explicit update commands. Each solves the problem differently, and each carries trade-offs that only become visible under production load, evolving clients, and enterprise compliance requirements.
π Want implementation-ready .NET source code you can drop straight into your project? Join Coding Droplets on Patreon for exclusive tutorials, premium code samples, and early access to new content. π https://www.patreon.com/CodingDroplets
Full replacement with PUT is simple: the client sends the entire resource, the server replaces the entity. No ambiguity. The complexity arrives the moment you allow clients to send only part of a resource.
The core problem is intent disambiguation. When a field is absent from a PATCH payload, it could mean two things:
These two interpretations require fundamentally different mechanics. The asp.net core partial update strategy you choose determines how you resolve this ambiguity at every layer of your stack β from the HTTP contract down to the database write.
JSON Patch is an RFC-standardised format for describing a sequence of operations to apply to a JSON document. Instead of sending a modified resource, the client sends a list of operations: add, remove, replace, move, copy, and test.
ASP.NET Core supports JSON Patch through the Microsoft.AspNetCore.JsonPatch package (which still depends on Newtonsoft.Json as of .NET 10). An endpoint accepts a JsonPatchDocument<TModel> parameter and calls .ApplyTo() to mutate the target object before persisting.
JSON Patch is the right choice when your API surfaces a resource that clients need to manipulate with granular, operation-level control. It is particularly strong when:
test operation enables optimistic concurrency at the payload level).The test operation deserves special attention in enterprise contexts. It allows the client to assert the current value of a field before the replace operation is applied β if the assertion fails, the entire patch document is rejected. This is a lightweight form of client-side optimistic concurrency that does not require ETags or version columns.
JSON Patch carries real costs that are easy to underestimate.
The Newtonsoft.Json dependency is the most immediate friction point. In a modern ASP.NET Core 10 application standardised on System.Text.Json, adding JSON Patch pulls in the older serialisation stack, increases package surface, and creates a dual-serializer problem in projects that rely on custom System.Text.Json converters. A community SystemTextJson-based JSON Patch implementation exists but is not officially supported.
Validation is the second pressure point. When you apply a patch document to a model and then validate the result, you are validating after the fact rather than at the boundary. A missing required field introduced by a remove operation may only surface as a model state error after the mutation has already happened in memory, making the code harder to reason about.
Performance profiling on high-frequency PATCH endpoints also consistently shows that the JSON Patch operation pipeline introduces non-trivial per-request overhead at scale compared to direct DTO binding.
The most common mistake teams make with JSON Patch is skipping the DTO mapping layer. Applying the patch document directly to a domain entity, persisted object, or EF Core-tracked entity conflates the API contract with the data model. Any change to the domain entity immediately bleeds into the patch path contract. Always apply the patch to a dedicated API DTO, then map from DTO to entity.
A second anti-pattern is over-trusting the patch document path. Without a whitelist of allowed paths, a client can attempt to patch computed, internal, or security-sensitive fields. The ApplyTo method does not enforce path restrictions by default. Implement explicit path validation before applying.
The nullable DTO strategy replaces the RFC-based approach with a simpler convention: every field in the update DTO is nullable. A null value means "do not change this field." A non-null value means "replace this field with the provided value."
This is the most widely adopted pattern in internal enterprise APIs because it maps naturally to how EF Core change tracking works and how developers already think about updates.
The convention is intuitive: the client omits or sends null for fields it does not wish to update. The server checks each field: if non-null, apply the change; if null, skip. For fields where null is a valid business value β such as optional profile fields β you need a Nullable<T> wrapper (Optional<T> or a custom Patchable<T> type) that distinguishes between "not present" and "set to null."
This strategy is the natural fit for the majority of enterprise internal APIs where:
UPDATE statements rather than full-entity updates.The nullable DTO approach also integrates cleanly with MediatR commands and CQRS-style architectures. The update command becomes a direct, typed representation of intent β far easier to unit-test than a JSON Patch pipeline.
If your team uses Chapter 11 of the ASP.NET Core Web API: Zero to Production course (Clean Architecture, CQRS, MediatR), nullable update commands fit naturally into that pattern β each partial update maps to an explicit command with only the fields that matter for that operation.
The limitation surfaces when a field has a valid null business state. Consider a notes field that a user explicitly wants to clear. If null means "skip," how does the client express "set to null"?
The idiomatic .NET solution is Optional<T> β a value wrapper that distinguishes between three states: not provided, provided-as-null, and provided-with-value. Libraries like StronglyTypedId or a simple custom generic type provide this. The cost is a more complex DTO structure and custom serialisation handling. For most enterprise APIs where truly nullable fields are rare, this overhead is not worth the complexity.
Mixing nullable-means-skip with nullable-means-null in the same DTO is the most dangerous anti-pattern. Once the convention breaks down for a single field, the contract becomes ambiguous, and downstream consumers lose the ability to rely on the pattern.
Performing updates without targeting is the second common mistake. If your service layer simply maps the entire DTO onto the entity β including all null fields β you will accidentally overwrite values instead of skipping them. Always iterate over non-null properties explicitly, or use a mapping convention that respects the null-means-skip contract.
The third approach abandons the generic partial update concept entirely. Instead of one broad PATCH endpoint that handles any subset of fields, you model each update operation as a discrete, purpose-built endpoint or command.
Want to change a user's email? That is a PATCH /users/{id}/email endpoint accepting only a verified email payload. Want to change their role? That is PATCH /users/{id}/role. The partial update problem disappears because each endpoint is scoped to exactly the fields it modifies.
This strategy is the correct choice when:
An UpdateUserEmailCommand is far more expressive than a PatchUserRequest with email set. It allows you to enforce email verification before the change, fire an EmailChangedDomainEvent, and write a typed audit log entry β none of which are expressible through a generic patch mechanism without external logic.
Custom command endpoints scale poorly across large, flat resources with many independently updatable fields. A resource with twenty independent fields β common in configuration-heavy or CMS-style APIs β would require twenty PATCH endpoints. The developer experience degrades, and API documentation becomes repetitive.
The correct mitigation is to group semantically related fields into named operations rather than treating each field atomically. "Update contact details" groups name, phone, and address. "Update security settings" groups MFA flags, login timeout, and IP whitelist. If no natural grouping emerges, the resource itself is probably poorly modelled.
Yes, and it is worth naming. Many well-run enterprise teams use a hybrid: nullable DTO for standard read/update resources with low domain complexity, custom commands for operations that carry significant business rules or audit requirements, and JSON Patch only on purpose-built collaboration or document-style endpoints where the RFC format genuinely adds value.
The pattern is not a compromise. It is a deliberate mapping of mechanism to use case.
| Factor | JSON Patch | Nullable DTO | Custom Commands |
|---|---|---|---|
| Client sophistication | Requires RFC-compliant clients | Simple convention | Simple, purpose-built |
| Audit / compliance | Operation-level audit possible | Field-level audit requires extra logic | Best β each command is typed and auditable |
| Validation | Post-apply, harder to integrate | Pre-apply, works with standard pipeline | Full pre-validation per command |
| Null disambiguation | Built-in (absence vs. remove op) | Requires Optional<T> for nullable fields |
N/A β each command is explicit |
| EF Core integration | Complex β apply then map | Natural β map non-nulls | Natural β targeted updates |
| System.Text.Json compatibility | Friction (.NET 10) | Full compatibility | Full compatibility |
| API surface growth | Single endpoint | Single endpoint | Grows with operations |
| Test complexity | High | Medium | Low |
Start by answering one question: does this update carry domain semantics, or is it a mechanical field replacement?
If mechanical β a user updating their own profile fields with no business rules β nullable DTO is the pragmatic choice. If the update has rules, events, or compliance requirements, custom commands are the defensible choice. If your API is document-like and your consumers are sophisticated external integrators, JSON Patch earns its complexity budget.
A practical tiebreaker: audit trail requirements. If your organisation requires logging which fields changed, when, by whom, and to what value, both nullable DTO and JSON Patch require additional instrumentation after the fact. Custom commands log intent at the call site β the code is the audit trail.
Not officially. The Microsoft.AspNetCore.JsonPatch package still depends on Newtonsoft.Json. Community alternatives exist but are not first-party supported. If your project is committed to System.Text.Json, nullable DTO or custom commands avoid the dependency entirely.
Yes. ExecuteUpdate with nullable DTO partial updates is one of the most efficient combinations available. Build the update call by checking each nullable field and chaining property setters conditionally. This generates a single targeted UPDATE statement without loading the entity first.
Generally no. Aggregate roots should be mutated through domain methods that enforce invariants, not through generic operation sequences. Use custom commands that call aggregate methods directly.
OData provides its own partial update mechanism via Delta<T>, which tracks which properties have been explicitly set. It is a strong option if you are already committed to the OData protocol. If you are not, it adds significant framework overhead for a single use case.
β Prefer a one-time tip? Buy us a coffee β every bit helps keep the content coming!
What is the difference between PUT and PATCH in ASP.NET Core? PUT replaces the entire resource β the client sends all fields, and the server overwrites the entity completely. PATCH sends a partial update β only the fields the client wants to change. The risk with PUT is that a client that sends only some fields may accidentally blank the others.
When should I use JSON Patch instead of a nullable DTO in ASP.NET Core?
Use JSON Patch when your clients are sophisticated external consumers who need RFC-compliant operation sequences, or when your resource behaves like a document that requires fine-grained operation semantics like test, move, or copy. For internal APIs with internal clients, nullable DTO is simpler and easier to validate.
How do I handle the case where null means "clear this field" in a partial update DTO?
Use a wrapper type like Optional<T> that has three states: not provided, provided-as-null, and provided-with-value. This requires a custom JSON converter but solves the null disambiguation problem cleanly without switching to JSON Patch.
Does ASP.NET Core JSON Patch support System.Text.Json in .NET 10?
Not officially. The Microsoft.AspNetCore.JsonPatch package still requires Newtonsoft.Json. If your application standardises on System.Text.Json, consider nullable DTO or custom update commands to avoid the dual-serializer overhead.
What is the most enterprise-safe strategy for partial updates in regulated industries? Custom explicit update commands. Each command is a typed, purpose-built operation that can carry its own validation, authorisation checks, audit events, and domain logic. In regulated environments like finance or healthcare, the ability to individually log, test, and authorise each distinct update operation is non-negotiable.
Can I mix strategies across endpoints in the same ASP.NET Core API? Yes, and it is often the right call. Use nullable DTOs for low-complexity profile and configuration updates, custom commands for operations with business rules or compliance requirements, and JSON Patch only where the RFC format genuinely adds client value. The key is to be deliberate and document the pattern per-endpoint rather than defaulting to one approach across the board.
How does FluentValidation integrate with nullable DTO partial updates?
Naturally. Define validators for the update DTO with null-conditional rules: RuleFor(x => x.Email).NotEmpty().EmailAddress().When(x => x.Email != null). FluentValidation applies rules only when the field is present, which matches the nullable-means-skip contract exactly.