ASP.NET MVC: Múltiples botones en el mismo formulario

Seguramente os habréis encontrando en ocasiones en que, dentro de un mismo formulario, necesitamos alojar varios botones de acción sobre el mismo.

Existen varias opciones de conseguir este objetivo, pero mí preferida, como amante de client scripting, es con jQuery.

A continuación mostramos la vista con un formulario definido y dos botones de submit:

<% using (Html.BeginForm()) { %>
    <div>
        <%= Html.LabelFor(m=> m.Name) %>
        <%= Html.TextBoxFor(m => m.Name) %>
    </div>
    <div>
        <%= Html.LabelFor(m => m.Description)%>
        <%= Html.TextBoxFor(m => m.Description) %>
    </div>

    <input id="btnSave" type="submit" value="Guardar" />
    <input id="btnGetNextRow" type="submit" value="Siguiente registro" />
<% } %>

Por el momento, nada nuevo. A continuación vemos como enlazamos el evento click a los botones:

<script type="text/javascript">
    $(document).ready(function () {
        $("#btnSave").click(function () {
            var form = $(this).parent("form");
            form.attr('action', '<%= Url.RouteUrl(new { Controller = "Home", Action = "Save" }) %>');
            form.attr('method', 'post');
        });

        $("#btnGetNextRow").click(function () {
            var form = $(this).parent("form");
            form.attr('action', '<%= Url.RouteUrl(new { Controller = "Home", Action = "GetNextRow" }) %>');
            form.attr('method', 'get');
        });
    });
</script>

Obtenemos una referencia al formulario con la instrucción: $(this).parent(“form”) y a continuación cambiamos los atributos action y method del formulario. De esta forma, hacemos submit a la acción que nos interesa en cada momento utilizando GET o POST según convenga.

Read More

NHibernate: Paginando con DetachedCriteria

El propósito de este post es ver cómo podemos paginar de forma óptima en servidor mediante el objeto DetachedCriteria. El objetivo es evitar que nuestros controles de presentación hagan el trabajo sucio paginando en memoria los resultados al obtener todos los elementos de la base de datos en cada petición.

Por trivial que parezca, para poder mostrar los resultados de forma amigable al usuario en, por ejemplo un grid, la consulta debe devolver los elementos de la página en cuestión y el número total de elementos. Para ello se requieren dos consultas, una para los elementos y otra para el total, pero no suena muy práctico realizar dos consultas separadamente no? La gracia aquí está en agrupar las dos consultas en una y así optimizar el rendimiento. 

Nota: En caso de utilizar directamente un objeto de tipo ICriteria podríamos hacer uso directamente de las clausulas Future<T>  y FutureValue<T> que hacen internamente hacen uso de Multi Queries como haremos aquí. Más información en éste post de Ayende. 

Antes de nada… Que es DetachedCriteria? Pues no deja de ser un objeto criteria que definimos sin tener acceso a ISession. Esto nos permite definir nuestras consultas y poderlas ejecutar más tarde en cualquier otra parte dentro de un contexto de ISession. 

A continuación vemos una declaración típica para DetachedCriteria: 

// Creamos el objeto DetachedCriteria
var detachedCriteria = DetachedCriteria.For<Customer>("c");

// Agregamos filtros a la consulta
if (!string.IsNullOrEmpty(criteria.Name)
    && !string.IsNullOrWhiteSpace(criteria.Name))
{
    detachedCriteria.Add(Expression.Like("Name", criteria.Name, MatchMode.Anywhere));
}

Más adelante, en un contexto de ISession, tenemos el código para la paginación. Vemos los comentarios en cada línea: 

public PagedList<T> FindPaged(DetachedCriteria criteria, int startIndex, int pageSize)
{
    using (var session = GetSession())
    {
        // Clonamos nuestro DetachedCriteria
        DetachedCriteria itemsCriteria = CriteriaTransformer.Clone(criteria);
        // Especificamos los parámetros de paginación:
        // 1) El índice del primer elemento a obtener
        itemsCriteria.SetFirstResult(startIndex * pageSize);
        // 2) El número máximo de resultados
        itemsCriteria.SetMaxResults(pageSize);

        // Transformamos nuestra consulta original para que nos devuelva el número de elementos
        DetachedCriteria countCriteria = CriteriaTransformer.TransformToRowCount(criteria);

        // Creamos un objeto MultiCriteria para agrupar las dos consultas
        IMultiCriteria multiCriteria = session.CreateMultiCriteria();
        multiCriteria.Add(itemsCriteria);
        multiCriteria.Add(countCriteria);
        IList multiResult = multiCriteria.List();

        // En posición 0 de la lista tenemos los elementos paginados
        IList pagedElementsUntyped = multiResult[0] as IList;
        // Con la extensión Cast<T> obtenemos la lista genérica de resultados
        IEnumerable<T> pagedElements = pagedElementsUntyped.Cast<T>();

        // En posición 1 de la lista tenemos el total de elementos
        int totalCount = Convert.ToInt32(((IList)multiResult[1])[0]);

        // Finalmente devolvemos la clase PagedList que encapsula los dos resultados
        return new PagedList<T>(pagedElements, totalCount);
    }
}

La clase PagedList<T> es simplemente un contenedor de los resultados: 

public class PagedList<T>
{
    public IEnumerable<T> Items { get; protected set; }

    public int TotalItems { get; protected set; }

    public PagedList(IEnumerable<T> items, int totalItems)
    {
        this.Items = items;
        this.TotalItems = totalItems;
    }
}

  

A continuación mostramos como se termina haciendo la consulta a la base de datos meditante SQL Profiler: 

exec sp_executesql N'SELECT top 5 this_.Id as CU1_0_0_, this_.Name as CU2_0_0_ FROM Customers this_ WHERE this_.Name like @p0;
SELECT count(*) as y0_ FROM Customers this_ WHERE this_.Name like @p1;
',N'@p0 nvarchar(6),@p1 nvarchar(6)',@p0=N'%name%',@p1=N'%name%' 

En este pequeño post hemos visto como con IMultiCriteria y NHibernate podemos hacer paginaciones optimizadas en servidor para que nuestros controles de presentación no tengan que hacer los cálculos en memoria obteniendo todos los datos de la base de datos en cada petición.

Read More