Webapi - Tryupdatemodel or Updatemodel in an Apicontroller

Asked

Viewed 385 times

1

I have the following situation, a Person class, with properties Id, Name, Birth, I want to implement a page that can update only the Person Name. My implementation should take a person by their id, and change only the Name field.

I made the implementation below in MVC, but how could I do the same with Webapi, since I don’t have Tryupdatemodel or Updatemodel. I implement functionality with this routine to facilitate generalization in the future.

Controller Code in MVC:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public JsonResult SetNome()
    {
        var sample = Pessoa.FindById(12);
        TryUpdateModel<Pessoa>(sample);
        return Json("sucesso");
    }
}

public class Pessoa
{
    [Required]
    public int Id { get; set; }

    [Required]
    public string Nome { get; set; }

    [Required]
    public DateTime? Nascimento { get; set; }

    public static Pessoa FindById(int id)
    {
        return new Pessoa() { Id = 12, Nome = "Meu Nome", Nascimento = Convert.ToDateTime("1989-01-31") };
    }
}

View code:

<!DOCTYPE html>
<html>
<head>
    <script src="https://code.angularjs.org/1.4.3/angular.js"></script>
    <script>

        var app = angular.module("MainModule", []);

        var MainController = function ($scope, $http) {

            var url = 'http://localhost:47107/home/setnome';
            //var url = 'http://localhost:47107/api/values';

            $scope.pessoa = {};
            $scope.pessoa.nome = "xpto";

            $scope.enviar = function () {

                $http({
                    url: url,
                    method: "POST",
                    data: $scope.pessoa
                })
                .then(function (response) {
                    // success
                    console.log(JSON.stringify(response));
                },
                function (response) {
                    // failed
                    console.log(JSON.stringify(response));
                });
            }
        };

        app.controller("MainController", MainController);

    </script>
</head>

<body ng-app="MainModule">
    <div ng-controller="MainController">
        <input id="pessoa.nome" ng-model="pessoa.nome" />
        {{pessoa.nome}}
        <button ng-click="enviar()">Enviar</button>
    </div>
</body>
</html>

Current code in Webapi:

public class ValuesController : ApiController
{
    public void Post([FromBody]Pessoa pessoa)
    {
        var sample = Pessoa.FindById(12);

        sample.Nome = pessoa.Nome;
    }
}

I wanted to have to abstract the Name field, as it was done in MVC, with Updatemodel, it identifies which fields were sent by View and fills only them, the others it keeps original. With Webapi I have to do this manually.

2 answers

1

Sharing knowledge with anyone who has the same problem. Recalling that the purpose of this function is not to validate the entity and just fill (complement) an entity coming from the database with the data that came from the view.

    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)
    {
        //faz um comportamento similar ao do UpdateModel do Controller, onde sobreescreve as propriedades Enumarables,
        //caso no json a mesma esteja preenchida, se ela estiver vazia (não nula), sobreescreve também a não ser que,
        //o parametro overrideForEmptyList seja false
        var newValues = JsonConvert.DeserializeObject<T>(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);            
    }
  • Here: if (isEnumerable && property.PropertyType != typeof(string)) also compare with Decimal and DateTime. These classes are also considered non-priminal.

  • Another thing: this one: property.SetValue(original, null); has a poor performance. About 200ms per property set. If you use extensively, I recommend using the Fastmember, which lowers the time between 40 and 50ms per property.

  • Hi Gypsy, the "if" I want to check if the property is a Ienumerable<>, as string is a Ienumarable and I do not want her in my routine, I put her in condition. I’m not putting you on the spot because you’re a primitive guy.

  • 1

    When to the Setvalue of the Property, really it is slow, as well as the routine by complete, because it uses Reflection, but only the first execution, the second execution was instantaneous. I measured the time and the complete routine runs at 129ms on the first call, the following calls are 0ms. I do not know this library Fastmember, I will take a look. Thanks for the tips.

  • 1

    Last thing: var newValues = JsonConvert.DeserializeObject<Pessoa>(json);. Would not be T instead of Pessoa?

  • That’s right, I made a mistake there. Thank you Gypsy!

Show 1 more comment

1


Ricardo, I solve this problem in a different way.

Initially, my Action or Operation does not receive an Entity/Model, but a DTO.

Receiving a DTO, I would do this treatment, that you want, in my repository.

Example of a Repository method with this feature:

public void Alterar(TDTO DTO)
    {           
            var entidadeSemAlteracao = Context.Set<Compra>().Find(DTO.sID);
            DTO.ToEntity(entidadeSemAlteracao);
            Context.SaveChanges();            
    }

The Toentity is an extension on my DTO, calling the Automapper.

public static TDestino ToEntity<TOrigem, TDestino>(this TOrigem DTO, TDestino entidadeDestino) where TDestino : EntidadeBase
    {
        var result = Mapper.Map(DTO, entidadeDestino);
        return result;
    }

Everything happens when the extension is called, which will leave the properties as Modified and the Savechanges() will change only these properties.

What do you think?

That’s what you wanted?

  • Very good Henryle solution, although I am trying to avoid using Automapper in this project as it is an overhead too much work.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.