miércoles, 13 de junio de 2012

Introducción a Knockout (VI) - Insertando datos con ASP.NET MVC4 WebApi

En el anterior artículo de knockout vimos como podíamos llamadas AJAX para cargar nuestra UI. Hoy vamos a ver como podemos hacer el mantenimiento de nuestras entidades también de una forma sencilla. Lo primero será extender nuestro repositorio de datos para añadir cierta lógica para el almacenamiento de nuestros datos.

IRepository
public interface IBookRepository
{
  IEnumerable<Book> Get();
  IEnumerable<Book> GetList(string search);
  Book Add(Book item);
  void Remove(int id);
}
BookRepository
public class BookRepository : IBookRepository
{
    private List<Book> products = new List<Book>();
    private int _nextId = 1;

    public BookRepository()
    {
        Add(new Book
        {
            Id = 1,
            Title = "Introducing Microsoft Sql Server 2012"
        });
        Add(new Book
        {
            Id = 2,
            Title = "Introducing Windows Server 2008 R2"
        });
        Add(new Book
        {
            Id = 3,
            Title = "Visual Studio 2010"
        });
        Add(new Book
        {
            Id = 4,
            Title = "Programming Windows Phone 7"
        });
        Add(new Book
        {
            Id = 5,
            Title = "Visual Studio 2008"
        });
        Add(new Book
        {
            Id = 6,
            Title = "Microsoft .NET 4.0"
        });
        Add(new Book
        {
            Id = 7,
            Title = "ASP.NET 4.0"
        });
    }

    public IEnumerable<Book> Get()
    {
        return products;
    }

    public IEnumerable<Book> GetList(string search)
    {
        return products.Where(m => m.Title.Contains(search));
    }

    public Book Add(Book item)
    {
        item.Id = _nextId++;
        products.Add(item);
        return item;
    }

    public void Remove(int id)
    {
        products.RemoveAll(p => p.Id == id);
    }
}
BookController
public class BookController : ApiController
{
    static IBookRepository repository = new BookRepository();

    // GET /api/book
    public IEnumerable Get(string search)
    {
        return (string.IsNullOrEmpty(search) ? repository.Get() : repository.GetList(search));
    }

    // POST /api/book/
    public Book Post(Book model)
    {
        return repository.Add(new Book() { Title = model.Title });
    }

    // DELETE /api/values/5
    public void Delete(int id)
    {
        repository.Remove(id);
    }
}

Bien, con esto ya podemos irnos a la parte de cliente para ver como haríamos. La verdad es que es bastante fácil y lo único que debemos hacer es extraer los valores de las propiedades de nuestro modelo para formar un objeto JSON que pasaremos vía POST a nuestra API.

De esa manera nuestra interfaz y nuestro modelo quedarían así
<h2>Ejemplo de uso de Knockout</h2>
<h3>Insertar un libro</h3>
<div>
  Título del libro <input type="text" data-bind="value: title" /><input type="button" data-bind="click: onsave" value="Save" />
</div>

<h3>Listado de libros</h3>
<div id="search">
  <input type="text" data-bind="value: search" />
  <input type="button" data-bind="click: onsearch" value="Search" />
</div>

<!-- ko if: books().length == 0 -->
<div data-bind="if: books().length == 0">
  No hay resultados que mostrar
</div>
<!-- /ko -->

<!-- ko ifnot: books().length == 0 -->
<ul data-bind="foreach: books">
  <li ><span data-bind="text: $data.Title"></span> | <span data-bind="click: $parent.ondelete" style="cursor: pointer">[x]</span></li>
</ul>
<!-- /ko -->
Vemos que hemos añadido una pequeña sección para añadir el título de un nuevo libro, mientras que el resto de la interfaz permanece igual respecto al ejemplo del artículo anterior.
<script type="text/javascript" language="javascript">
    function ViewModel() {
     var self = this;

     self.title = ko.observable('');
            self.search = ko.observable('');
     self.books = ko.observableArray([]);

     self.onsave = function () {
      var title = self.title();
   
      if (title == '')
      {
       alert('El título es obligatorio');
       return; 
      }
   
      $.ajax({
       type: 'POST',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")",
       data: { Title: title },
       success: function (pReturn) {
        self.books.push(pReturn);
       }
      });
     }

     self.onsearch = function () {
      var search = self.search();
      $.ajax({
       type: 'GET',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")" + "/get/" + search,
       data: { },
       success: function (pReturn) {
        self.books(pReturn);
       }
      });
     }

     self.ondelete = function (e) {   
      $.ajax({
       type: 'DELETE',
       dataType: "json",
       contextType: "application/json; charset=utf-8",
       url: "@Url.Action("book", "api")",
       data: { id: e.Id },
       success: function (pReturn) {
        self.books.remove(e);
       }
      });
     }

     self.onsearch();
    }
    ko.applyBindings(new ViewModel()); 
</script>
Vemos que el método onsave extraemos el valor de la propiedad observable que luego usaremos para formar el JSON que pasaremos en la llamada. Para modelos más complejos podríamos serializar con el método toJSON todo el modelo de la siguiente manera:
self.onsave = function () { 
        var data = ko.toJSON(self);
   
 if (data.title == '')
 {
  alert('El título es obligatorio');
  return; 
 }
   
 $.ajax({
  type: 'POST',
  dataType: "json",
  contextType: "application/json; charset=utf-8",
  url: "@Url.Action("book", "api")",  
                data: data,
  success: function (pReturn) {
   self.books.push(pReturn);
  }
 });
}
OJO, porque este ejemplo "no funciona" porque en la serialización a JSON del modelo también se está incluyendo la propiedad books, que incluye la lista de libros que se está mostrando y esto provoca que el mapeo del modelo se haga incorrectamente en el método POST de la API. Lo he comentado para que sirva de referencia en modelos más complejos.

Otra opción es usar dos modelos diferentes en la misma vista, pero eso lo dejaremos para otro artículo que este con tanto código se ha extendido un poco.

Happy coding!

No hay comentarios:

Publicar un comentario