Daniel
Daniel

Reputation: 3131

How to write a generic WebAPI Put method against Entity Framework that works with child lists?

I am tinkering with WebAPI to create a generic implementation for entity framework. I am able to implement most of the methods just fine, but am finding PUT to be tricky in non-trivial cases. The implementation most commonly found online works for simple entities:

    [HttpPut]
    [ActionName("Endpoint")]
    public virtual T Put(T entity)
    {
        var db = GetDbContext();
        var entry = db.Entry(entity);
        entry.State = EntityState.Modified;
        var set = db.Set<T>();            
        set.Attach(entity);                     
        db.SaveChanges();
        return entity;
    }

...but does not delete or update child lists:

    public class Invoice
    {
         ...
         public virtual InvoiceLineItem {get; set;} //Attach method doesn't address these
    }

In an MVC Controller, you could simply use "UpdateModel" and it would add/update/delete children as needed, however that method is not available on ApiController. I understand that some code would be necessary to get the original item from the database, and that it would need to use Include to get the child lists, but can't quite figure out the best way to replicate UpdateModel's functionality:

    [HttpPut]
    [ActionName("Endpoint")]
    public virtual T Put(T entity)
    {
        var db = GetDbContext();
        var original = GetOriginalFor(entity); 
        //TODO: Something similar to UpdateModel(original), such as UpdateModel(original, entity);                  
        db.SaveChanges();
        return original;
    }

How can I implement UpdateModel OR somehow implement Put in such a way that it will handle child lists?

Upvotes: 3

Views: 963

Answers (1)

Ricardo Carvalho
Ricardo Carvalho

Reputation: 223

The routine dont validate entity, but fill the pre-existent entity.

    protected virtual void UpdateModel<T>(T original, bool overrideForEmptyList = true)
    {
        var json = ControllerContext.Request.Content.ReadAsStringAsync().Result;
        UpdateModel<T>(json, original, overrideForEmptyList);
    }

    private void UpdateModel<T>(string json, T original, bool overrideForEmptyList = true)
    {
        var newValues = JsonConvert.DeserializeObject<Pessoa>(json);            
        foreach (var property in original.GetType().GetProperties())
        {
            var isEnumerable = property.PropertyType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));

            if (isEnumerable && property.PropertyType != typeof(string))
            {
                var propertyOriginalValue = property.GetValue(original, null);
                if (propertyOriginalValue != null)
                {
                    var propertyNewValue = property.GetValue(newValues, null);

                    if (propertyNewValue != null && (overrideForEmptyList || ((IEnumerable<object>)propertyNewValue).Any()))
                    {
                        property.SetValue(original, null);
                    }
                }
            }
        }

        JsonConvert.PopulateObject(json, original);
    }

    public void Post()
    {           
        var sample = Pessoa.FindById(12);
        UpdateModel(sample);            
    }

Upvotes: 1

Related Questions