Crear manualmente proxies de Hubs Signalr

SignalRCuando desde un cliente javascript consumimos los servicios suministrados por un Hub de SignalR, lo habitual es usemos los proxies generados automáticamente, para lo que solemos incluir en nuestra página una referencia al script “/Signalr/Hubs” según la ruta por defecto.

Sin embargo, puede haber casos en los que no nos interesa este comportamiento y preferimos generarlo de forma manual para, por ejemplo, incluirlo en un bundle o distribuir el archivo a través de una CDN. Veamos cómo conseguirlo.

Los desarrolladores de SignalR han creado una herramienta de línea de comandos que, entre otras cosas, nos facilitará mucho esta tarea, por lo que lo primero que tenemos que hacer es descargarla e instalarla a través de Nuget:

PM> Install-Package microsoft.aspnet.signalr.utils 
[...]
Successfully installed 'Microsoft.AspNet.SignalR.Utils 1.0.1'.

Por supuesto, este primer paso podrías hacerlo también usando el interfaz gráfico, pero mejor que lo hagas ya desde la consola Nuget porque de todas formas nos va a hacer falta en el siguiente paso.

A continuación hay que ejecutar en la consola el siguiente comando:

PM> signalr.exe ghp /o:MyProjectscriptshubs.js
SignalR Utility Version: 1.0.0.0
Creating temp directory 
  C:UsersJmAguilarAppDataLocalTemp532fbeb3-55c9-44cb-ac94-3f831318ce89

Incluir script en el proyectoComo seguro podéis intuir, el parámetro /o permite indicar la ruta hacia el archivo que será generado. Es relativa a la solución, por esta razón hay que comenzarla desde la carpeta donde se encuentra el proyecto.

El parámetro «ghp» simplemente viene de «Generate Hub Proxies».

Al finalizar el proceso tendremos en la carpeta indicada el archivo, cuyo contenido será idéntico al obtenido al descargar desde la vista el script generado en /Signalr/Hubs. Eso sí, no aparecerá en Visual Studio hasta que lo incorporemos explícitamente al proyecto.

Hecho esto, ya podemos referenciarlo desde nuestras páginas:

1
<script src="Scripts/hubs.js"></script>

Podemos también introducir la generación de proxies en el proceso de build, asegurando así que el script siempre estará sincronizado con el resto de componentes. La única precaución a tener en cuenta es que la ruta del archivo generado, indicada en el parámetro /o, debe ser absoluta.

Por último, comentar que también es posible desactivar del servidor SignalR la generación de proxies, lo cual puede ser interesante si nuestros clientes estamos seguros de que no van a necesitarlo (por ejemplo, si tenemos sólo clientes .NET, o si hemos pregenerado los archivos de script), para evitar que usuarios del lado oscuro puedan obtener demasiada información sobre los Hubs y operaciones disponibles en servidor. En este caso, basta con indicar esta configuración en el momento de mapear los hubs, justo al arrancar la aplicación:

1
2
3
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs(hubConfiguration);

A partir de este momento, el script descargado a través de la dirección Signalr/hubs simplemente lanzará una excepción indicando que esta característica ha sido deshabilitada.

Publicado en Variable not found.

Evento en Sevilla: desarrollo de videojuegos multiplataforma con Wave Engine

Wave EngineSin duda, el desarrollo de videojuegos es una de las áreas más atractivas e interesantes a las que podemos aspirar dedicarnos los desarrolladores. Y aunque hoy en día parece estar especialmente de moda, no es algo nuevo; muchos de nosotros empezamos a interesarnos por la programación intentando programar nuestros propios juegos.

Por este motivo espero con especial impaciencia la próxima charla organizada por Cartuja.NET, que tratará sobre el desarrollo de videojuegos, pero con con un enfoque muy alineado con las necesidades actuales: crear videojuegos multiplataforma, usando para ello el motor Wave Engine.

Será el próximo jueves 23 de mayo a las 19:30h, con una duración estimada de dos horas, en el Clouding Point/Centro demostrador TIC de Sevilla, que podéis encontrar en la siguiente dirección:

C/Biología, 12, Edificio Vilamar 2, 3ª Planta
Parque Empresarial Nuevo Torneo
41015 Sevilla

Como siempre, la asistencia es gratuita; lo único que tenéis que hacer es registraros en el sitio del evento en Eventbrite 🙂

Descripción del evento

El desarrollo de aplicaciones para dispositivos móviles es un área que gana adeptos y suma peso día a día. Entre las aplicaciones destacadas, más descargadas y que aportan grandes beneficios contamos con los juegos. Dada la variedad de dispositivos, SDKs, herramientas y lenguajes a aprender, sacar el máximo partido a cada plataforma con eficacia y rapidez es una tarea complicada.  Wave Engine es un motor multiplataforma en 3D para facilitar la adaptación de los juegos móviles a cualquier plataforma (Android, iOS, Windows Phone y Windows 8). Incluye multitud de herramientas que facilitan tareas importantes como la gestión de publicidad, analítica del juego, etc.

En este evento se realizará una introducción al engine donde se mostrarán todas sus  posibilidades.

Ponentes

  • Marcos Cobeña (LIGHTYEAR): Developer Advisor en Plain Concepts.
  • David Ávila (WOODY): Software Developer Advisor Plain Concepts.

Salvo catástrofe bíblica, u otros impedimientos de fuerza mayor, nos vemos por allí 🙂

Generar archivos Excel como un señor con ClosedXml

 

Anónimo
Venga, lo confieso: yo también he generado desde mis aplicaciones contenidos HTML y los he enviado al cliente en un archivo con extensión XLS, incluso modificando el content-type, para que pareciera un documento de hoja de cálculo. Durante años. Y también le he dicho a mis clientes que el molesto mensaje que aparece al abrirlo desde Excel, el que indica que el contenido del archivo no coincide con la extensión del mismo, es algo normal.

Pero esto se acabó desde que descubrí ClosedXML, un magnífico componente para .NET basado en el estándar OpenXML que permite la generación de archivos Excel “de verdad”, con formato, estilos, fórmulas, rangos, filtros, y casi todo lo que se nos pueda ocurrir.

ClosedXMLClosedXML, proyecto iniciado por Manuel de León y distribuido bajo licencia MIT, se aleja de la verbosidad y amplitud de alcance del Open XML SDK de Microsoft, ofreciendo un API mucho más natural e intuitivo exclusivamente diseñado para crear y manipular documentos Excel. De hecho, el nombre ClosedXML lo eligió después de conocer el SDK oficial y pensar “si es así como se trabaja con Open XML, preferiría utilizar algo que estuviera cerrado”, en referencia a la complejidad que el primero supone.

Dado que se basa en Open XML, para abrir los archivos generados se necesita Excel 2007 o una versión posterior, aunque creo que ocho años después de su aparición ya podríamos considerar que es un mínimo bastante razonable 😉

Instalación del componente

Como de costumbre, la instalación de ClosedXML la vamos a realizar a través de Nuget:

PM> Install-Package ClosedXML
Attempting to resolve dependency 'DocumentFormat.OpenXml (≥ 1.0)'.
Successfully installed 'ClosedXML 0.68.1'.
Successfully added 'DocumentFormat.OpenXml 1.0' to ClosedXmlDemo.Model.
Successfully added 'ClosedXML 0.68.1' to ClosedXmlDemo.Model.

No me canso de repetirlo: Nuget, ¿dónde has estado todos estos años? 😉

Creación y salvado de un documento Excel básico (Desktop, Webforms, MVC)

La creación y salvado a disco de un documento Excel es absolutamente trivial. Basta con instanciar un objeto de la clase XLWorkBook, añadir una nueva hoja su colección de Worksheets, e introducir valores en ella a través de su propiedad Cell, como podemos observar en el código genérico mostrado a continuación:

1
2
3
4
var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Sheet 1");
worksheet.Cell(1, 1).Value = "Hello, world!";
workbook.SaveAs("c:\temp\excel.xlsx");

De esta forma tan simple, en c:tempexcel.xlsx tendremos lo siguiente:
Hoja de cálculo generada con ClosedXML
Si estamos desarrollando una aplicación web, cuando generamos un archivo Excel lo habitual es que lo enviemos al usuario como documento adjunto para que lo descargue y guarde en su equipo, lo que implica modificar el content-type y añadir un encabezado content-disposition.

En este caso de usar WebForms, el código a emplear es, poco más o menos, el siguiente:

1
2
3
4
5
6
7
8
9
10
11
Response.Clear();
Response.ContentType =
     "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
Response.AddHeader("content-disposition", "attachment;filename="Demo.xlsx"");
 
using (var memoryStream = new MemoryStream())
{
    workbook.SaveAs(memoryStream);
    memoryStream.WriteTo(Response.OutputStream);
}
Response.End();

Observad que no estamos salvando el workbook  sobre un MemoryStream, que luego volcamos al stream de salida para que viaje al cliente. Este doble paso, en cualquier caso bastante sencillo de implementar, se debe a que ClosedXML requiere para guardar el archivo que el stream de salida sea de avance y retroceso, y es algo que OutputStream no cumple.

En caso de tratarse de ASP.NET MVC, el código es prácticamente el mismo, aunque lo correcto sería implementarlo en un ActionResult personalizado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ExcelResult: ActionResult
{
    private readonly XLWorkbook _workbook;
    private readonly string _fileName;
 
    public ExcelResult(XLWorkbook workbook, string fileName)
    {
        _workbook = workbook;
        _fileName = fileName;
    }
 
    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.Clear();
        response.ContentType = "application/vnd.openxmlformats-officedocument."
                             + "spreadsheetml.sheet";
        response.AddHeader("content-disposition",
                           "attachment;filename=""+ _fileName +".xlsx"");
 
        using (var memoryStream = new MemoryStream())
        {
            _workbook.SaveAs(memoryStream);
            memoryStream.WriteTo(response.OutputStream);
        }
        response.End();
    }
}

Y ya podríamos usarlo directamente desde un controlador:

1
2
3
4
5
6
7
8
public ActionResult GenerateExcel()
{
    // Generate the workbook...
    var workbook = ClosedXmlDemoGenerator.GenerateWorkBook();
 
    // ... and return it to the client
    return new ExcelResult(workbook, "demo");

Establecer valores en las celdas

Las celdas podemos referenciarlas utilizando la propiedad Cell de los objetos IXLWorksheet, y podemos hacerlo indicando su número de fila y columna o mediante el nombre usado normalmente en el mismo Excel:

1
2
worksheet.Cell(1, 1).Value = "Hello, world!";
worksheet.Cell("A2").Value = "How are you?";

Establecer valores de celdas con ClosedXML
Por supuesto, los valores pueden ser de todo tipo, de hecho la propiedad Value que establecemos es de tipo object, aunque obviamente sólo serán reconocidos los tipos habituales de Excel:

1
2
3
4
5
6
7
8
9
10
11
12
worksheet.Cell("A2").Value = "Text";
worksheet.Cell("B2").Value = "Hi!!";
worksheet.Cell("A3").Value = "Integer";
worksheet.Cell("B3").Value = 3;
worksheet.Cell("A4").Value = "Decimal";
worksheet.Cell("B4").Value = 3.5;
worksheet.Cell("A5").Value = "Boolean";
worksheet.Cell("B5").Value = true;
worksheet.Cell("A6").Value = "DateTime";
worksheet.Cell("B6").Value = DateTime.Now;
worksheet.Cell("A7").Value = "Object";
worksheet.Cell("B7").Value = new InvoiceDetails();

Tipos de datos en ClosedXML
También podemos crear rangos y establecerles valores de forma directa:

1
worksheet.Range("A1:D5").Value = "Hi!";

Valores de rangos en ClosedXML
E incluso podemos asignar directamente colecciones de datos como objetos de tipo DataTable o IEnumerable<T>:

1
2
3
4
5
6
worksheet.Cell("A1").Value = new[]
             {
                  new { Id=1, Name="John", Age = 42},
                  new { Id=2, Name="Peter", Age = 23},
                  new { Id=3, Name="Mary", Age = 32},
             };

Uso de conjuntos de datos en ClosedXML

Formato de celdas

ClosedXML pone a nuestra disposición un rico conjunto de propiedades y métodos para dar formato a las celdas o a rangos de ellas. La sintaxis fluida que podemos utilizar facilita mucho el descubrimiento de las posibilidades de formateo, y la implementación de un código muy limpio y comprensible:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
worksheet.Range("B2:E5")
         .SetValue("Hi!")
         .Style.Border.SetOutsideBorder(XLBorderStyleValues.Dotted);
 
worksheet.Range("B3:E5").Style
         .Font.SetFontSize(10)
         .Font.SetFontColor(XLColor.Gray)
         .Font.SetItalic(true);
 
worksheet.Range("B2:E2").Style
         .Font.SetFontSize(13)
         .Font.SetBold(true)
         .Font.SetFontColor(XLColor.White)
         .Fill.SetBackgroundColor(XLColor.Gray);

Formateo de celdas con ClosedXML
También podemos, por supuesto, establecer la alineación de celdas o rangos, unir celdas, o establecer el formato de visualización de sus valores. Vemos también, de paso, una forma más fluida de establecer los valores, usando SetValue():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
worksheet.Cell("A1")
         .SetValue(1234.56789)
         .Style.NumberFormat.SetFormat("#,##0.#0");
 
worksheet.Cell("A2")
         .SetValue(DateTime.Now)
         .Style.DateFormat.SetFormat("dd-mm-yyyy")
               .Alignment.SetVertical(XLAlignmentVerticalValues.Center);
 
worksheet.Range("A3:B4")
         .Merge()
         .SetValue("Merged cells")
         .Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center)
               .Alignment.TextRotation = 15; // Degrees

Formateo de celdas con ClosedXML

Uso de fórmulas

Esto es especialmente interesante, pues no había forma de conseguirlo cuando exportábamos archivos HTML, CSV o similares, en los que sólo importaba el valor de las celdas. Con ClosedXML tenemos total libertad para introducir fórmulas en las celdas; de esta forma, no sólo estaremos enviando al usuario un conjunto estático de datos, sino una completa hoja Excel que puede modificar (si nos interesa, claro) y usar para su trabajo:

1
2
3
4
5
6
7
8
9
10
11
12
13
worksheet.Cell("B2")
    .SetValue("Number")
    .Style.Font.SetBold(true);
 
for (int i = 1; i <= 5; i++)
{
    worksheet.Cell(3+i, 2).Value = i;
}
 
worksheet.Cell("A9").SetValue("Total");
worksheet.Cell("B9")
    .SetFormulaA1("=SUM(B3:B8)")
    .Style.Border.SetTopBorder(XLBorderStyleValues.Medium);

Fórmulas en ClosedXML

Otras características interesantes

ClosedXML soporta muchísimas funcionalidades adicionales a las descritas hasta el momento. Voy a citar algunas más que me han llamado la atención.

Por ejemplo, tenemos la posibilidad de añadir filtros y ordenación por columna a los datos, de manera que el usuario pueda realizar una selección de la información recibida:

1
2
3
4
5
6
7
8
9
10
11
worksheet.Cell("A1").Value = "Id";
worksheet.Cell("B1").Value = "Name";
worksheet.Cell("C1").Value = "Age";
worksheet.Cell("A2").Value = new[]
     {
          new { Id=1, Name="John", Age = 42},
          new { Id=2, Name="Peter", Age = 23},
          new { Id=3, Name="Mary", Age = 32},
          new { Id=4, Name="John", Age = 45},
     };
worksheet.RangeUsed().SetAutoFilter();

Filtros de datos con ClosedXML
Otro aspecto que puede ser interesante es la protección de celdas para que el usuario no pueda modificar sus valores. En el siguiente ejemplo, se protege la hoja completa con un password, de forma que no podrá ser editada, excepto las tres primeras columnas de la primera fila, que el usuario podrá editar con total libertad:

1
2
3
worksheet.Protect("1234"); // Locks the worksheet and sets the password
worksheet.Range("A1:C1")
    .Style.Protection.SetLocked(false); // Unlocks the range

Protección de hojas con ClosedXML
También, si nos interesa que el usuario edite valores de las celdas, es posible especificar restricciones en los datos de entrada, como su tipo, rango de valores permitidos, selección de valores desde desde un desplegable, etc.:

1
2
3
4
5
6
7
8
9
worksheet.Cell("A1").Value = "Digit 0-9:";
worksheet.Cell("B1").DataValidation.WholeNumber.Between(0, 9);
 
worksheet.Cell("A2").Value = "Two:";
worksheet.Cell("B2").DataValidation.WholeNumber.EqualTo(2);
 
worksheet.Cell("A3").Value = "Date:";
worksheet.Cell("B3").DataValidation.AllowedValues = XLAllowedValues.Date; // Only dates
worksheet.Cell("B3").DataValidation.ErrorMessage = "Only dates, please";

Validación de datos de entrada con ClosedXML
Incluso podemos añadir comentarios a celdas:

1
2
3
4
5
6
7
8
worksheet.Cell("B2").SetValue("TOTAL:")
    .Style.Font.SetBold(true)
          .Alignment.SetHorizontal(XLAlignmentHorizontalValues.Right);
 
worksheet.Cell("C2").Style.NumberFormat.SetFormat("#,##0.#0");
worksheet.Cell("C2").SetValue(9876543.21)
    .Comment.SetAuthor("jmaguilar")
    .AddText("Danger: this number seems odd!");

Añadir comentarios con ClosedXML

… y vamos a dejarlo aquí 😉

Bueno, pues tras este largo post espero que más o menos os haya quedado claro el uso y posibilidades de este magnífico componente, y que os hayan entrado muchas ganas de probarlo. Seguro cambiará la forma en que generáis los archivos Excel desde vuestras aplicaciones y abrirá nuevas posibilidades, hasta ahora difícilmente implementables usando otras alternativas.

También, recomendaros que no dejéis de leer la documentación, que es bastante extensa y detallada, donde podréis ver muchas más características que no he comentado para no hacer un post más interminable de lo que ya es 😉

Podéis descargar un proyecto de prueba desde mi Skydrive, donde veréis en funcionamiento la generación de archivos Excel desde una aplicación de consola, ASP.NET Webforms, y MVC.

Publicado en Variable not found.