Skip to content

Instantly share code, notes, and snippets.

@benfoster
Forked from tugberkugurlu/person.md
Created October 9, 2012 10:03
Show Gist options
  • Save benfoster/3857748 to your computer and use it in GitHub Desktop.
Save benfoster/3857748 to your computer and use it in GitHub Desktop.

Assuming you have the following object:

public class Person { 
    
    public string Name { get; set; }
    public string Surname { get; set; }
    public int Age { get; set; }
}

and got a PATCH request as below:

[{"replace": "/name", "value": "tugberk"}]

and assuming we have a json-patch formatter. Wouldn't you get the following result?

public class PeopleController : ApiController { 

    //new Person { Name = "tugberk", Surname = null, Age = 0 }
    public Person PatchPerson(Person person) { 
        
        //...
    }
}

My suggestion:

  1. Create an interface for IPatchCommand that adheres to JSON Patch specification

  2. Create a media type formatter than can translate the request body into an IPatchCommand. The IPatchCommand contains a list of PatchOperation's to apply to an object (as per the spec)

  3. In the controller, create a new instance of a patch and map the current values from the domain object (we can use AutoMapper for this).

  4. Pass the patch object and patch command to a patch service that understands how to translate a PatchOperation into an Action that is applied to the patch object. Since the patch objects should just have simple public properties, this shouldn't be too difficult.

  5. Finally, pass the "updated" patch object into the domain layer (either directly or perhaps by a patching handler). This makes domain operations explicit - it would just be too damn hard to automatically update every domain object directly.

     public class PatchOperation {
     	public OperationType Type { get; set; }
     	public string Path { get; set; }
     	public string Value { get; set; }
     	
     	public enum OperationType {
     		Test,
     		Remove,
     		Add,
     		Replace,
     		Move,
     		Copy
     	}
     }
     
     public interface IPatchCommand {
     	public IEnumerable<PatchOperation> Operations {get; set;}
     }
     
     // Domain object, no public setters
     public class Customer {
     	
     	public string FirstName {get;set;}
     	public string LastName {get;set;}
     	
     	public Customer(string first, string last) {
     		First = first;
     		Last = last;
     	}
     	
     	public void Patch(CustomerPatch patch) {
     		First = patch.FirstName;
     		Last = patch.LastName;
     	}
     	
     }
     
     // Patch object (like a view model, for patches)
     
     public class CustomerPatch {
     	public string FirstName {get;set;}
     	public string LastName {get;set;}
     }
     
     public class CustomersController {
     	
     	public HttpResponseMessage Patch(int id, CustomerPatchCommand command) {
     		
     		var customer = session.Load<Customer>(command.Id);
     		
     		// initialize the patch with the current values		
     		var patch = 
     			patchService.CreatePatch(Mapper.Map<CustomerPatch>(customer), command);
     			
     		// pass patch to domain object (or a handler, to perform the actual update)
     		customer.ApplyPatch(patch);
     		
     		// save
     		session.SaveChanges();
     		
     		return Request.CreateResponse(HttpStatusCode.OK);
     	}
     }
     
     public class PatchService {
     
     	public TPatchCommand CreatePatch<TPatch, TPatchCommand>(TPatch patch, TPatchCommand command) where TPatchCommand : IPatchCommand {
     	
     		// translate command.Operations into a actions that are
     		// applied to the patch object
     		UseMagicToUpdatePatchFromPatchCommand(patch, command);
     	
     		return patch;
     	}
     
     }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment