Estaba escuchando un podcast de HerdingCode (esto me da pie para un próximo post sobre los podcast que escucho, thanks to commute!) donde Phil Haack habla sobre Asp.Net MVC. Y aprovechando la salida del RC (Release Candidate) podemos discutir algunos temas. (Más info sobre el patrón MVC)
Ante la pregunta de cual es el elevator speech que Phil utilizaría para describir Asp.Net MVC para alguien que utiliza Asp.Net webforms, Phil contesta:
Si utilizás Webforms y estás felíz, entonces ignorá MVC. Si sos muy productivo con Webforms, MVC no va a reemplazar Webforms. Pero los beneficios de utilizar MVC son varios: nos pone nuevamente en control de nuestra aplicación (por ejemplo los controles de Webforms generan mucho markup que si no hacen lo que deseamos puede ser complicado), otro beneficio es que el patrón arquitectónico de MVC es muy bueno en lo que respecta a la separación de conceptos, lo que permite aplicaciones más mantenibles y testeables (valgan los neologismos). Temas que no estaban tan en boga en la creación de Webforms.
Un ejemplo
Una pequeña aplicación que me permite almacenar los gastos diarios (me agarró la paranoia).
El modelo lo forman categorías y gastos.
La persistencia del mismo es a través de Entity Framework (del cual no voy a empezar una discusión, porque Gus tiene muchos argumentos en contra)
Una parte del ExpenseController de ejemplo:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
try
{
var expenseToCreate = new Expense();
Category category = new Category();
UpdateModel(expenseToCreate, new[]{"Date", "Amount", "Description"});
UpdateModel(category, new[]{"CategoryId"});
expenseToCreate.Category = _db.Category
.Where(cate => cate.CategoryId == category
.CategoryId).First();
_db.AddToExpense(expenseToCreate);
_db.SaveChanges();
return RedirectToAction("Index", "Home");
}
catch (Exception exep)
{
return RedirectToAction("Error");
}
}
Esta es la acción de crear un nuevo gasto. Acá y por culpa de no poder utilizar el SelecList.SelectedValue, tuve que hacer algo más rebuscado.
Por último, una parte de la View que representa el Create:
<fieldset>
<legend>Fields</legend>
<p>
<label for="Date">
Date:</label>
<%= Html.TextBox("Date") %>
<%= Html.ValidationMessage("Date", "*") %>
</p>
<p>
<label for="Amount">
Amount:</label>
<%= Html.TextBox("Amount") %>
<%= Html.ValidationMessage("Amount", "*") %>
</p>
<p>
<label for="Description">
Description:</label>
<%= Html.TextBox("Description") %>
<%= Html.ValidationMessage("Description", "*") %>
</p>
<p>
<select size="5" name="CategoryId" id="CategoryId">
<option value="0" selected="selected">Sin seleccionar categoría</option>
<% foreach (Category category in (IList)ViewData["Categories"])
{ %>
<%= "<option value='" + category.CategoryId + "'>" +
category.SmallDescription + "</option>" %>
<%} %>
</select>
<br />
<%= Html.ActionLink("Crear Categoria nueva", "Create", "Categories") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
En la misma se ve como resolví el tema de no poder utilizar un Helper Method de la entidad Html (como Html.ListBox o Html.DropDownList) debido al problema del SelectList.
Es un pequeño ejemplo. Se encuentran muchos en internet, no intento hacer un tutorial, pero si les interesa el código no tienen más que pedirlo.
Felicidad
Siguiendo con el concepto de Phil Haack, aquellos que no éramos felices con Asp.Net Webforms, nos encontramos con la chance de encontrar la felicidad en éste camino de desarrollo.
Como seguir
El RC tiene todavía algunos problemas, por ejemplo tuve muchos problemas queriendo utilizar un SelectList (lista para Views) y obtener el SelectedValue. Nunca lo logré y tuve que utilizar otras formas. Tal vez sea problemas que se solucionen en la versión definitiva, hay que tener en cuenta que es un proyecto (MVC) con mucho tiempo y mucha gente muy interesante y capaz atrás. Seguimos atentos.
Tecnologías
TDD / DDD / IRepository / Patrones / NHibernate / NUnit / C# / Linq / Net 3.5 / BDO
Linq to SQL
Está muerto ?
Aparentemente Microsoft esta dejando de lado LinqToSql por su variedad en esteroides Entity Framework (voy a ver si puedo traducir este post al E/F). Un buen punto de partida sobre esta discusión es blog del buen Ayende (mucho tiempo con colombianos, ya digo "el buen", jaja) y en particular este post.
Helper?
Lo primero que debemos hacer es agregar al proyecto de implementación un ítem del tipo Linq to Sql classes. Y allí utilizando el editor crear el contexto con nuestras clases.
En el caso de la implementación para LinqToSql utilizamos en vez de un helper métodos en la implementación que nos proveen el DataContext y las tablas.
public class Repository<T> : IRepository<T> where T : class
{
private DomainDataContext domainDataContext = null;
public Repository()
{
domainDataContext = new DomainDataContext();
}
/// <summary>
/// Gets the table provided by the type T and returns for querying
/// </summary>
private Table<T> Table
{
get { return domainDataContext.GetTable<T>(); }
}
Tests de la implementación de LinqToSql
Testando los diferentes métodos del Repositorio.
[Test]
public void CanSave()
{
// Arrange
IRepository<Person> repository = new Repository<Person>();
ResetDB(repository);
Person person = new Person { FirstName = "Chapita", LastName = "Velazquez" };
// Act
repository.Save(person);
// Assert
Person personFromDB = repository
.GetAll()
.Where(p => p.FirstName == "Chapita" && p.LastName == "Velazquez")
.FirstOrDefault();
Assert.AreEqual(personFromDB.FirstName, person.FirstName);
Assert.AreEqual(personFromDB.LastName, person.LastName);
}
Una vez que sabemos que el test no funciona implementamos la funcionalidad.
public void Save(T item)
{
if (!Table.Contains(item))
{
Table.InsertOnSubmit(item);
}
domainDataContext.SubmitChanges();
}
Esto también nos muestra otro de los beneficios de TDD, y es la posibilidad de generar dise;os compactos, donde la funcionalidad es exclusivamente la necesaria para satisfacer el test.
Muestro la implementación del Find para tener una idea del uso extensivo de Linq en nuestra implementación.
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> expression)
{
return GetAll()
.Where(expression);
}
UPDATE: se puede bajar el código desde NetIRepository
Lo que sigue
En el próximo post me voy a ocupar un poco de como utilizar el repositorio desde servicios y por medio de ellos en las distintas tecnologías de presentación.
Tecnologías
TDD / DDD / IRepository / Patrones / NHibernate / NUnit / C# / Linq / Net 3.5 / BDO
Cómo manejo la persistencia de mi aplicación?
Tenemos la posibilidad de actualizarnos en temas de persistencia. Leyendo un artículo muy interesante de Rob Conery Crazy Talk: Reducing ORM Friction. En el mismo, Rob propone realizar la implementación de nuestra aplicación sin preocuparnos por los temas de mapeos con los ORM. Lo que nos propone es dediquemos al diseño a través del TDD (pensando en una orientación similar a la de DDD) de nuestra aplicación utilizando una implementación por medio de una base de datos orientada a objetos.
IRepository
Para ello y siguiendo con los conceptos de Domain Driven Design se elige el patrón Repository y se desarrolla la siguiente interfaz que nos va a permitir los distintos providers (implementaciones) que necesitamos para poder diseñar utilizando un modelo de Base de Datos Orientado a Objetos y luego implementar los providers que necesitemos para ir a producción. con el ORM de nuestra preferencia. En este caso vamos a hacer la implementación de nuestro ejemplo con las elecciones de Rob, BD4O como base de datos orientada a objetos y LinqToSql como ORM. Al final veremos un poco la discusión sobre la ¨muerte de LinqToSql¨. En un próximo post buceo en la implementación del provider con NHibernate y las problemáticas de recorrer una expresión Linq para generar un Criterio de filtro para NHibernate.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Hover.Repository
{
public interface IRepository<T>
{
IQueryable<T> GetAll();
IPagedList<T> GetPaged(int pageIndex, int pageSize);
IQueryable<T> Find(Expression<Func<T, bool>> expression);
void Save(T item);
void Delete(T item);
}
}
DB4O
La opción que presenta Rob para la implementación del provider contra Base de Datos Orientada a Objetos es DB4O.
Lo primero que implementaremos en un helper que nos va a servir como ayuda para la comunicación con el motor de DB4o.
Básicamente es un singleton que nos devuelve una instancia de container para intractuar con la base de datos. Acá Rob, remarca constantemente que estamos realizando una implementación para la etapa de desa rrollo, que no vamos a estar preocupados por perfomance (de ahí a no preocuparnos por el momento por le Singleton!, que ya es mala palabra).
Una vez que tenemos el helper podemos comenzar con los tests de las implementaciones del Repository.
[Test]
public void CanSave()
{
// Arrange
File
IRepository<Person> repository = new Repository<Person>();
.Delete(DB4OHelper.DBPath);
Person person = new Person { FirstName = "Chapita", LastName = "Velazquez" };
// Act
repository.Save(person);
// Assert
var result = from Person p in DB4OHelper.Container
where p.FirstName == "Chapita"
select p;
Assert.AreEqual(1, result.Count());
Person personFromDB = result
.FirstOrDefault();
Assert.AreEqual(person.FirstName, personFromDB.FirstName);
Assert.AreEqual(person.LastName, personFromDB.LastName);
// Just to chain the next test
DB4OHelper.CloseContainer();
}
El patrón que utilizamos para implementar TDD es AAA. Arrange / Act / Assert. Por lo tanto primero hay que realizar el setup Arrange, luego hacemos tdodo lo que nos interesa testear y por último realizamos los asserts. Por supuesto no olvidarse los conceptos de de TDD ROJO, VERDE, REFACTOR!.
Luego que nos de ROJO por los motivos esperados implementamos el método Save.
public void Save(T item)
{
DB4OHelper.Container.Store(item);
}
Les dejo como tarea testear, implementar y refactorizar los otros métodos.
Descargar el ejemplo
El ejemplo desarrollado se encuentra para descargar en google!!! En caso de no contar con Visual Studio 2008 se puede utilizar la versión express y correr los tests desde la consola de NUnit.
NetIRepository
Lo que sigue
En el próximo post voy a intentar implementar la misma interfaz del IRepository para utilizar primero desde LinqtoSQL y luego desde NHibernate, con los inconvenientes que tiene interfacer expresiones Linq con Criterios de NHibernate.
Luego inetntaré implementar los providers como servicios REST para usarlo desde Silverlight, WPF, Asp.Net MVC.
Etiquetas de Technorati:
TDD,
DDD,
IRepository,
Design Patterns,
NHibernate,
NUnit,
C#,
Linq,
Net 3.5,
BDO,
ORM