Compilación de vistas HTML con plantillas de Razor

En el mundo del desarrollo móvil, el término "aplicación híbrida" normalmente hace referencia a una aplicación que presenta algunas (o todas) de sus pantallas como páginas HTML dentro de un control de visor web hospedado.

Hay algunos entornos de desarrollo que permiten compilar la aplicación móvil completamente en HTML y JavaScript, pero esas aplicaciones pueden sufrir problemas de rendimiento al intentar lograr efectos complejos de procesamiento o interfaz de usuario y también están limitados en las características de la plataforma a las que pueden acceder.

Xamarin ofrece lo mejor de ambos mundos, especialmente cuando se usa el motor de plantillas HTML de Razor. Con Xamarin tiene la flexibilidad de crear vistas HTML con plantilla multiplataforma que usan JavaScript y CSS, pero también tiene acceso completo a las API de la plataforma subyacente y procesamiento rápido mediante C#.

En este documento se explica cómo usar el motor de plantillas de Razor para crear vistas HTML+JavaScript+CSS que se pueden usar en plataformas móviles mediante Xamarin.

Uso de vistas web mediante programación

Antes de obtener información sobre Razor en esta sección se explica cómo usar vistas web para mostrar contenido HTML directamente, específicamente contenido HTML generado dentro de una aplicación.

Xamarin proporciona acceso completo a las API de la plataforma subyacente en iOS y Android, por lo que es fácil crear y mostrar HTML mediante C#. A continuación se muestra la sintaxis básica de cada plataforma.

iOS

Mostrar HTML en un control UIWebView en Xamarin.iOS también toma solo unas pocas líneas de código:

var webView = new UIWebView (View.Bounds);
View.AddSubview(webView);
string contentDirectoryPath = Path.Combine (NSBundle.MainBundle.BundlePath, "Content/");
var html = "<html><h1>Hello</h1><p>World</p></html>";
webView.LoadHtmlString(html, NSBundle.MainBundle.BundleUrl);

Consulte las recetas de iOS UIWebView para obtener más información sobre el uso del control UIWebView.

Android

La visualización de HTML en un control WebView mediante Xamarin.Android se realiza en solo unas pocas líneas de código:

// webView is declared in an AXML layout file
var webView = FindViewById<WebView> (Resource.Id.webView);

// enable JavaScript execution in your html view so you can provide "alerts" and other js
webView.SetWebChromeClient(new WebChromeClient());

var html = "<html><h1>Hello</h1><p>World</p></html>";
webView.LoadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null);

Consulte las recetas de Android WebView para obtener más información sobre el uso del control WebView.

Especificar el directorio base

En ambas plataformas hay un parámetro que especifica el directorio base de la página HTML. Esta es la ubicación del sistema de archivos del dispositivo que se usa para resolver referencias relativas a recursos como imágenes y archivos CSS. Por ejemplo, etiquetas como

<link rel="stylesheet" href="style.css" />
<img src="monkey.jpg" />
<script type="text/javascript" src="jscript.js">

hacen referencia a estos archivos: style.css, monkey.jpg y jscript.js. La configuración del directorio base indica a la vista web dónde se encuentran estos archivos para que se puedan cargar en la página.

iOS

La salida de la plantilla se representa en iOS con el siguiente código de C#:

webView.LoadHtmlString (page, NSBundle.MainBundle.BundleUrl);

El directorio base se especifica como NSBundle.MainBundle.BundleUrl que hace referencia al directorio en el que está instalada la aplicación. Todos los archivos de la carpeta Resources se copian en esta ubicación, como el archivo style.css que se muestra aquí:

iPhoneHybrid solution

La acción de compilación para todos los archivos de contenido estático debe ser BundleResource:

iOS project build action: BundleResource

Android

Android también requiere que se pase un directorio base como parámetro cuando se muestran cadenas HTML en una vista web.

webView.LoadDataWithBaseURL("file:///android_asset/", page, "text/html", "UTF-8", null);

La cadena especial file:///android_asset/ hace referencia a la carpeta Assets de Android de la aplicación, que se muestra aquí que contiene el archivo style.css.

AndroidHybrid solution

La acción de compilación para todos los archivos de contenido estático debe ser AndroidAsset.

Android project build action: AndroidAsset

Llamada a C# desde HTML y JavaScript

Cuando se carga una página HTML en una vista web, trata los vínculos y formularios como lo haría si la página se cargara desde un servidor. Esto significa que si el usuario hace clic en un vínculo o envía un formulario, la vista web intentará navegar al destino especificado.

Si el vínculo es a un servidor externo (por ejemplo, google.com), la vista web intentará cargar el sitio web externo (suponiendo que haya una conexión a Internet).

<a href="http://google.com/">Google</a>

Si el vínculo es relativo, la vista web intentará cargar ese contenido desde el directorio base. Obviamente, no se requiere ninguna conexión de red para que funcione, ya que el contenido se almacena en la aplicación en el dispositivo.

<a href="somepage.html">Local content</a>

Las acciones del formulario siguen la misma regla.

<form method="get" action="http://google.com/"></form>
<form method="get" action="somepage.html"></form>

No va a hospedar un servidor web en el cliente. Sin embargo, puede usar las mismas técnicas de comunicación de servidor empleadas en los patrones de diseño dinámico actuales para llamar a los servicios a través de HTTP GET y controlar las respuestas de forma asincrónica emitiendo JavaScript (o llamando a JavaScript ya hospedado en la vista web). Esto le permite pasar fácilmente datos del código HTML a C# para su procesamiento y, a continuación, volver a mostrar los resultados en la página HTML.

Tanto iOS como Android proporcionan un mecanismo para que el código de aplicación intercepte estos eventos de navegación para que el código de la aplicación pueda responder (si es necesario). Esta característica es fundamental para crear aplicaciones híbridas porque permite que el código nativo interactúe con la vista web.

iOS

El evento ShouldStartLoad en la vista web de iOS se puede invalidar para permitir que el código de la aplicación controle una solicitud de navegación (por ejemplo, un clic de vínculo). Los parámetros del método proporcionan toda la información

bool HandleShouldStartLoad (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {
    // return true if handled in code
    // return false to let the web view follow the link
}

y, a continuación, asignan el controlador de eventos:

webView.ShouldStartLoad += HandleShouldStartLoad;

Android

En Android, simplemente use la subclase WebViewClient e implemente código para responder a la solicitud de navegación.

class HybridWebViewClient : WebViewClient {
    public override bool ShouldOverrideUrlLoading (WebView webView, IWebResourceRequest request) {
        // return true if handled in code
        // return false to let the web view follow the link
    }
}

y, a continuación, establezca el cliente en la vista web:

webView.SetWebViewClient (new HybridWebViewClient ());

Llamada a JavaScript desde C#

Además de indicar a una vista web que cargue una nueva página HTML, el código de C# también puede ejecutar JavaScript en la página mostrada actualmente. Se pueden crear bloques de código JavaScript completos mediante cadenas de C# y ejecutarse, o bien puede crear llamadas de método a JavaScript que ya están disponibles en la página a través de etiquetas script.

Android

Cree el código JavaScript que se va a ejecutar y, a continuación, prefijo con "javascript:" e indique a la vista web que cargue esa cadena:

var js = "alert('test');";
webView.LoadUrl ("javascript:" + js);

iOS

Las vistas web de iOS proporcionan un método específicamente para llamar a JavaScript:

var js = "alert('test');";
webView.EvaluateJavascript (js);

Resumen

En esta sección se han introducido las características de los controles de vista web en Android e iOS que permiten compilar aplicaciones híbridas con Xamarin, entre las que se incluyen:

  • La capacidad de cargar HTML a partir de cadenas generadas en el código,
  • La capacidad de hacer referencia a archivos locales (CSS, JavaScript, Imágenes u otros archivos HTML),
  • La capacidad de interceptar solicitudes de navegación en código de C#,
  • La capacidad de llamar a JavaScript desde código de C#.

En la sección siguiente se presenta Razor, lo que facilita la creación del código HTML para usarlo en aplicaciones híbridas.

¿Qué es Razor?

Razor es un motor de plantillas que se introdujo con ASP.NET MVC, originalmente para ejecutarse en el servidor y generar HTML para que se sirva a los exploradores web.

El motor de plantillas de Razor amplía la sintaxis HTML estándar con C# para que pueda expresar el diseño e incorporar fácilmente hojas de estilos CSS y JavaScript. La plantilla puede hacer referencia a una clase Model, que puede ser cualquier tipo personalizado y a cuyas propiedades se puede acceder directamente desde la plantilla. Una de sus principales ventajas es la capacidad de mezclar fácilmente la sintaxis HTML y C#.

Las plantillas de Razor no se limitan al uso del lado servidor, también se pueden incluir en aplicaciones de Xamarin. El uso de plantillas de Razor junto con la capacidad de trabajar con vistas web permite crear sofisticadas aplicaciones híbridas multiplataforma con Xamarin mediante programación.

Conceptos básicos de la plantilla de Razor

Los archivos de plantilla de Razor tienen una extensión de archivo .cshtml. Se pueden agregar a un proyecto de Xamarin desde la sección Plantillas de texto del cuadro de diálogo Nuevo archivo:

New File - Razor Template

A continuación se muestra una plantilla de Razor simple (RazorView.cshtml).

@model string
<html>
    <body>
    <h1>@Model</h1>
    </body>
</html>

Observe las siguientes diferencias respecto a un archivo HTML normal:

  • El símbolo @ tiene un significado especial en las plantillas de Razor, indica que se va a evaluar la siguiente expresión de C#.
  • La directiva @model siempre aparece como la primera línea de un archivo de plantilla de Razor.
  • La directiva @model debe ir seguida de un tipo. En este ejemplo se pasa una cadena simple a la plantilla, pero esto podría ser cualquier clase personalizada.
  • Cuando se hace referencia a @Model a lo largo de la plantilla, proporciona una referencia al objeto pasado a la plantilla cuando se genera (en este ejemplo será una cadena).
  • El IDE generará automáticamente una clase parcial para plantillas (archivos con la extensión .cshtml). Puede ver este código, pero no debe editarse. RazorView.cshtml La clase parcial se denomina RazorView para que coincida con el nombre del archivo de plantilla .cshtml. Es este nombre el que se usa para hacer referencia a la plantilla en código de C#.
  • Las instrucciones @using también se pueden incluir en la parte superior de una plantilla de Razor para incluir espacios de nombres adicionales.

A continuación, se puede generar la salida HTML final con el siguiente código de C#. Tenga en cuenta que especificamos el modelo para que sea una cadena "Hola mundo" que se incorporará a la salida de la plantilla representada.

var template = new RazorView () { Model = "Hello World" };
var page = template.GenerateString ();

Esta es la salida que se muestra en una vista web en el simulador de iOS y Android Emulator:

Hello World

Más sintaxis de Razor

En esta sección vamos a presentar algunas sintaxis básicas de Razor para ayudarle a empezar a usarlo. Los ejemplos de esta sección rellenan la siguiente clase con datos y lo muestran mediante Razor:

public class Monkey {
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public List<string> FavoriteFoods { get; set; }
}

Todos los ejemplos usan el código de inicialización de datos siguiente

var animal = new Monkey {
    Name = "Rupert",
    Birthday=new DateTime(2011, 04, 01),
    FavoriteFoods = new List<string>
        {"Bananas", "Banana Split", "Banana Smoothie"}
};

Mostrar las propiedades del modelo

Cuando el modelo es una clase con propiedades, se hace referencia fácilmente a ella en la plantilla de Razor, como se muestra en esta plantilla de ejemplo:

@model Monkey
<html>
    <body>
    <h1>@Model.Name</h1>
    <p>Birthday: @(Model.Birthday.ToString("d MMMM yyyy"))</p>
    </body>
</html>

Esto se puede representar en una cadena mediante el código siguiente:

var template = new RazorView () { Model = animal };
var page = template.GenerateString ();

La salida final se muestra aquí en una vista web en el simulador de iOS y Android Emulator:

Rupert

Instrucciones de C#

C# más complejo se puede incluir en la plantilla, como las actualizaciones de la propiedad Model y el cálculo Age en este ejemplo:

@model Monkey
<html>
    <body>
    @{
        Model.Name = "Rupert X. Monkey";
        Model.Birthday = new DateTime(2011,3,1);
    }
    <h1>@Model.Name</h1>
    <p>Birthday: @Model.Birthday.ToString("d MMMM yyyy")</p>
    <p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
    </body>
</html>

Puede escribir expresiones complejas de C# de una sola línea (como dar formato a la antigüedad) rodeando el código con @().

Se pueden escribir varias instrucciones de C# en torno a ellas con @{}.

Instrucciones If-else

Las ramas de código se pueden expresar con @if como se muestra en este ejemplo de plantilla.

@model Monkey
<html>
    <body>
    <h1>@Model.Name</h1>
    <p>Birthday: @(Model.Birthday.ToString("d MMMM yyyy"))</p>
    <p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
    <p>Favorite Foods:</p>
    @if (Model.FavoriteFoods.Count == 0) {
        <p>No favorites</p>
    } else {
        <p>@Model.FavoriteFoods.Count favorites</p>
    }
    </body>
</html>

Bucles

También se pueden agregar construcciones de bucle como foreach. El prefijo @ se puede usar en la variable de bucle (@food en este caso) para representarlo en HTML.

@model Monkey
<html>
    <body>
    <h1>@Model.Name</h1>
    <p>Birthday: @Model.Birthday.ToString("d MMMM yyyy")</p>
    <p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
    <p>Favorite Foods:</p>
    @if (Model.FavoriteFoods.Count == 0) {
        <p>No favorites</p>
    } else {
        <ul>
            @foreach (var food in @Model.FavoriteFoods) {
                <li>@food</li>
            }
        </ul>
    }
    </body>
</html>

La salida de la plantilla anterior se muestra en ejecución en el simulador de iOS y Android Emulator:

Rupert X Monkey

En esta sección se han tratado los conceptos básicos del uso de plantillas de Razor para representar vistas sencillas de solo lectura. En la sección siguiente se explica cómo crear aplicaciones más completas con Razor que pueden aceptar entradas de usuario e interoperar entre JavaScript en la vista HTML y C#.

Uso de plantillas de Razor con Xamarin

En esta sección se explica cómo usar la compilación de su propia aplicación híbrida mediante las plantillas de solución en Visual Studio para Mac. Hay tres plantillas disponibles en la ventana Archivo > Nueva > Solución...:

  • Android > Aplicación > Aplicación Android WebView
  • iOS > Aplicación > Aplicación WebView
  • Proyecto ASP.NET MVC

La ventana Nueva solución tiene este aspecto para proyectos de iPhone y Android: la descripción de la solución en la derecha resalta la compatibilidad con el motor de plantillas de Razor.

Creating iPhone and Android solutions

Tenga en cuenta que puede agregar fácilmente una plantilla de Razor .cshtml a cualquier proyecto de Xamarin existente, no es necesario usar estas plantillas de solución. Los proyectos de iOS no requieren Storyboard para usar Razor tampoco; simplemente agregue un control UIWebView a cualquier vista mediante programación y puede representar plantillas de Razor completas en código de C#.

A continuación se muestra el contenido de la solución de plantilla predeterminada para proyectos de iPhone y Android:

iPhone and Android templates

Las plantillas proporcionan una infraestructura de aplicaciones lista para usar para cargar una plantilla de Razor con un objeto de modelo de datos, procesar la entrada del usuario y comunicarse con el usuario a través de JavaScript.

Las partes importantes de la solución son:

  • Contenido estático, como el archivo style.css.
  • Archivos de plantilla .cshtml de Razor como RazorView.cshtml.
  • Clases de modelo a las que se hace referencia en las plantillas de Razor, como ExampleModel.cs.
  • La clase específica de la plataforma que crea la vista web y representa la plantilla, como MainActivity en Android y iPhoneHybridViewController en iOS.

En la sección siguiente se explica cómo funcionan los proyectos.

Contenido estático

El contenido estático incluye hojas de estilos CSS, imágenes, archivos JavaScript u otro contenido que se puede vincular desde o hacer referencia a él mediante un archivo HTML que se muestra en una vista web.

Los proyectos de plantilla incluyen una hoja de estilos mínima para mostrar cómo incluir contenido estático en la aplicación híbrida. Se hace referencia a la hoja de estilos CSS en la plantilla de la siguiente manera:

<link rel="stylesheet" href="style.css" />

Puede agregar cualquier hoja de estilos y archivos JavaScript que necesite, incluidos marcos como JQuery.

Plantillas de Razor cshtml

La plantilla incluye un archivo .cshtml de Razor que tiene código escrito previamente para ayudar a comunicar datos entre HTML/JavaScript y C#. Esto le permitirá crear aplicaciones híbridas sofisticadas que no solo muestren datos de solo lectura del modelo, sino que también acepten la entrada de usuario en el código HTML y la devuelvan al código de C# para su procesamiento o almacenamiento.

Representación de la plantilla

Al llamar a GenerateString en una plantilla se representa HTML listo para mostrarse en una vista web. Si la plantilla usa un modelo, se debe proporcionar antes de la representación. En este diagrama se muestra cómo funciona la representación, no que la vista web resuelve los recursos estáticos en tiempo de ejecución, mediante el directorio base proporcionado para buscar los archivos especificados.

Razor flowchart

Llamada a código de C# desde la plantilla

La comunicación desde una vista web representada que realiza una llamada a C# se realiza estableciendo la dirección URL de la vista web y, a continuación, interceptando la solicitud en C# para controlar la solicitud nativa sin volver a cargar la vista web.

Se puede ver un ejemplo de cómo se controla el botón de RazorView. El botón tiene el código HTML siguiente:

<input type="button" name="UpdateLabel" value="Click" onclick="InvokeCSharpWithFormValues(this)" />

La función de JavaScript InvokeCSharpWithFormValues lee todos los valores del formulario HTML y establece location.href para la vista web:

location.href = "hybrid:" + elm.name + "?" + qs;

Esto intenta navegar por la vista web a una dirección URL con un esquema personalizado (por ejemplo, hybrid:)

hybrid:UpdateLabel?textbox=SomeValue&UpdateLabel=Click

Cuando la vista web nativa procesa esta solicitud de navegación, tenemos la oportunidad de interceptarla. En iOS, esto se hace controlando el evento HandleShouldStartLoad de UIWebView. En Android, simplemente con la subclase WebViewClient que se usa en el formulario e invalidamos ShouldOverrideUrlLoading.

Los elementos internos de estos dos interceptores de navegación son esencialmente los mismos.

En primer lugar, compruebe la dirección URL que la vista web está intentando cargar y, si no comienza con el esquema personalizado (hybrid:), permita que la navegación se produzca como normal.

Para el esquema de dirección URL personalizado, todo en la dirección URL entre el esquema y "?" es el nombre del método que se va a controlar (en este caso, "UpdateLabel"). Todo lo que se encuentra en la cadena de consulta se tratará como los parámetros de la llamada al método:

var resources = url.Substring(scheme.Length).Split('?');
var method = resources [0];
var parameters = System.Web.HttpUtility.ParseQueryString(resources[1]);

UpdateLabel en este ejemplo realiza una cantidad mínima de manipulación de cadenas en el parámetro de cuadro de texto (antepone “C# indica” a la cadena) y, a continuación, llama a la vista web.

Después de controlar la dirección URL, el método anula la navegación para que la vista web no intente terminar de navegar a la dirección URL personalizada.

Manipulación de la plantilla desde C#

La comunicación con una vista web HTML representada desde C# se realiza mediante una llamada a JavaScript en la vista web. En iOS, esto se realiza mediante una llamada a EvaluateJavascript en UIWebView:

webView.EvaluateJavascript (js);

En Android, JavaScript se puede invocar en la vista web cargando JavaScript como una dirección URL mediante el esquema de dirección URL de "javascript:":

webView.LoadUrl ("javascript:" + js);

Creación de una aplicación verdaderamente híbrida

Estas plantillas no hacen uso de controles nativos en cada plataforma: toda la pantalla se rellena con una sola vista web.

HTML puede ser ideal para crear prototipos y mostrar las cosas que mejor se le dan a la web, como el texto enriquecido y el diseño adaptable. Sin embargo, no todas las tareas son adecuadas para HTML y JavaScript: el desplazamiento a través de listas largas de datos, por ejemplo, funciona mejor con controles nativos de la interfaz de usuario (como UITableView en iOS o ListView en Android).

Las vistas web de la plantilla se pueden aumentar fácilmente con controles – específicos de la plataforma simplemente editan MainStoryboard.storyboard mediante Xcode en un Equipo Mac o el Resources/layout/Main.axml en Android.

Ejemplo de RazorTodo

El repositorio de RazorTodo contiene dos soluciones independientes para mostrar las diferencias entre una aplicación completamente controlada por HTML y una aplicación que combina HTML con controles nativos:

  • RazorTodo: aplicación completamente controlada por HTML mediante plantillas de Razor.
  • RazorNativeTodo: usa controles de vista de lista nativos para iOS y Android, pero muestra la pantalla de edición con HTML y Razor.

Estas aplicaciones de Xamarin se ejecutan en iOS y Android, utilizando bibliotecas de clases portables (PCL) para compartir código común, como las clases de base de datos y modelo. Las plantillas .cshtml de Razor también se pueden incluir en la PCL para que se compartan fácilmente entre plataformas.

Ambas aplicaciones de ejemplo incorporan las API de uso compartido de Twitter y de texto a voz de la plataforma nativa, lo que demuestra que las aplicaciones híbridas con Xamarin siguen teniendo acceso a toda la funcionalidad subyacente de las vistas basadas en plantillas de Razor HTML.

La aplicación RazorTodo usa plantillas de Razor HTML para la lista y editar vistas. Esto significa que podemos compilar la aplicación casi completamente en una biblioteca de clases portable compartida (incluida la base de datos y las plantillas de Razor .cshtml). Las capturas de pantalla siguientes muestran las aplicaciones iOS y Android.

RazorTodo

La aplicación RazorNativeTodo usa una plantilla de Razor HTML para la vista de edición, pero implementa una lista de desplazamiento nativa en cada plataforma. Esto proporciona una serie de ventajas, entre las que se incluyen:

  • Rendimiento: los controles de desplazamiento nativos usan la virtualización para garantizar un desplazamiento rápido y fluido incluso con listas de datos muy largas.
  • Experiencia nativa: los elementos de interfaz de usuario específicos de la plataforma se habilitan fácilmente, como la compatibilidad con índices de desplazamiento rápido en iOS y Android.

Una ventaja clave de la creación de aplicaciones híbridas con Xamarin es que puede empezar con una interfaz de usuario completamente controlada por HTML (como el primer ejemplo) y, a continuación, agregar funcionalidad específica de la plataforma cuando sea necesario (como se muestra en el segundo ejemplo). A continuación se muestran las pantallas de lista nativa y las pantallas de edición de Razor HTML en iOS y Android.

RazorNativeTodo

Resumen

En este artículo se han explicado las características de los controles de vista web disponibles en iOS y Android que facilitan la creación de aplicaciones híbridas.

A continuación, se explicó el motor de plantillas de Razor y la sintaxis que se puede usar para generar HTML fácilmente en aplicaciones de Xamarin mediante archivos de plantilla de Razor .cshtml. También se describen las plantillas de solución de Visual Studio para Mac que permiten empezar a compilar rápidamente aplicaciones híbridas con Xamarin.

Por último, introdujo los ejemplos de RazorTodo que muestran cómo combinar vistas web con interfaces de usuario nativas y API.