Windows Phone 8: Asociación de archivos y protocolos

Hola a todos!

Una de las novedades de Windows Phone 8 es la capacidad de asociar nuestra aplicación a tipos de archivos. De esta forma, cuando se intente acceder a un tipo de archivo para el cual nos hayamos registrado, se abrirá nuestra aplicación, si es la única que existe capaz de manejarlos o se mostrará una lista de aplicaciones disponibles.

Quizás en un primer momento pueda parecer algo que solo puede ser útil en ciertas condiciones muy específicas. Nada más lejos de la realidad, como vamos a ver a continuación.

Asociación de archivos

Al crear una asociación entre nuestra aplicación y un tipo de archivo, facilitamos que se lance automáticamente cuando el usuario quiera abrir un archivo en particular. El lanzamiento de nuestra aplicación puede producirse directamente desde el navegador, desde un correo electrónico, desde otra aplicación o incluso desde un archivo recibido por NFC o Bluetooth.

La asociación también determina el tipo de archivos que podemos leer desde el almacenamiento de la tarjeta SD extraíble, pero eso lo veremos en otro post.

Lo primero que necesitaremos para crear la asociación en nuestra aplicación a un tipo de archivo en concreto, son los logotipos a usar para mostrar el archivo en cuestión. En concreto necesitaremos tres imágenes: Una de 33×33 píxeles para mostrar si el archivo llega como adjunto en un correo electrónico. La segunda de 69×69 píxeles para mostrar en el hub de office. Por último necesitaremos otra de 176×176 píxeles para mostrar en la descarga del navegador.

image

Una vez que tengamos nuestros archivos de logotipo en los tres tamaños, podemos crear la asociación. Para ello tenemos que editar el archivo WMAppManifest.xml de nuestra aplicación, con el editor XML (Botón derecho Open With > XML (Text) Editor). Tendremos que añadir una sección llamada Extensions, justo debajo de Tokens. Dentro de esta sección crearemos un nodo FileTypeAssociation donde indicaremos el nombre, la TaskID asociada y el fragmento de Uri que se enviará a nuestra aplicación:

<Extensions>
  <FileTypeAssociation Name="geeksms file" TaskID="_default" NavUriFragment="fileToken=%s">
  </FileTypeAssociation>
</Extensions>

La propiedad Name se usará para mostrar el nombre del tipo de archivo. TaskID se refiere al TaskID de la sección Token, que define el arranque de nuestra aplicación, y que por defecto es _default. NavUriFragment indica el parámetro que se enviará a nuestra aplicación con el token del archivo, para poder abrirlo.

Una vez definidos estos tres valores, dentro de FileTypeAssociation incluiremos una sección Logos, donde indicaremos los tres logos que ya hemos incluido en nuestra aplicación:

<Extensions>
  <FileTypeAssociation Name="geeksms file" TaskID="_default" NavUriFragment="fileToken=%s">
    <Logos>
      <Logo Size="small" IsRelative="true">Assets/filetype_small.png</Logo>
      <Logo Size="medium" IsRelative="true">Assets/filetype_medium.png</Logo>
      <Logo Size="large" IsRelative="true">Assets/filetype_large.png</Logo>
    </Logos>
  </FileTypeAssociation>
</Extensions>

En cada logo indicamos su tamaño en la propiedad Size y en el contenido ponemos la ruta relativa, marcando a true la propiedad IsRelative. Debemos establecer los archivos como Content en su Build Action y Copy Always o Copy if newer en su propiedad Copy to Output Directory. A continuación solo nos queda indicar exactamente las extensiones de archivo que deseamos asociar, añadiendo dentro del nodo FileTypeAssociation la sección SupportedFileTypes:

<Extensions>
  <FileTypeAssociation Name="geeksms file" TaskID="_default" NavUriFragment="file=%s">
    <Logos>
      <Logo Size="small" IsRelative="true">Assets/filetype_small.png</Logo>
      <Logo Size="medium" IsRelative="true">Assets/filetype_medium.png</Logo>
      <Logo Size="large" IsRelative="true">Assets/filetype_large.png</Logo>
    </Logos>
    <SupportedFileTypes>
      <FileType ContentType="application/text">.geeksms</FileType>
    </SupportedFileTypes>
  </FileTypeAssociation>
</Extensions>

Dentro de la sección SupportedFileTypes incluimos un nodo FileType por cada extensión de archivo que deseemos asociar al tipo geeksms file. En este caso añadimos solo una, indicamos el tipo de contenido en su propiedad ContentType y la extensión, con el punto. Con esto hemos concluido la configuración de nuestro archivo asociado. Ahora tenemos que saber, de alguna forma, que nos están activando con un archivo asociado. Para ello usaremos una clase que herede de UriMapperBase. Siempre que nuestra aplicación se active desde un archivo asociado, recibiremos un enlace que contendrá el fragmento: “/FileTypeAssociation?” seguido del fragmento que hayamos indicado y el token del archivo. Lo que vamos a hacer es crear una clase nueva en nuestra aplicación, llamada FileUriMapper, que procese la Uri de lanzamiento de nuestra aplicación en busca del archivo. Si encuentra una asociación nos dirigirá a una página, de lo contrario iremos a otra:

public class FileUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        string tempUri = uri.ToString();

        if (tempUri.Contains("/FileTypeAssociation?"))
        {
            int fileIDIndex = tempUri.IndexOf("fileToken=") + 10;
            string fileID = tempUri.Substring(fileIDIndex);

            string fileUri = string.Format(@"/SecondPage.xaml?file={0}", fileID);

            return new Uri(fileUri, UriKind.Relative);
        }
        else
            return uri;
    }
}

En esta clase, simplemente examinamos la Uri que nos llega. Si esta no contiene el fragmento “/FileTypeAssociation?”, devolvemos la misma Uri, pues se trata de un lanzamiento normal. Por el contrario, si lo contiene, buscamos el token, fileID, del archivo y se lo pasamos como parámetro a una página llamada SecondPage que se encargará de tratarlo. Ahora tenemos que modificar el método InitializePhoneApplication de la clase App para añadir nuestro FileUriMapper al Frame de la aplicación:

private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;

    RootFrame = new PhoneApplicationFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;
            
    RootFrame.UriMapper = new FileUriMapper();

    RootFrame.NavigationFailed += RootFrame_NavigationFailed;
    RootFrame.Navigated += CheckForResetNavigation;

    phoneApplicationInitialized = true;
}

Simplemente establecemos la propiedad UriMapper de RootFrame a una nueva instancia de nuestro mapeador. Así cuando se inicie la aplicación, la Uri de inicio pasará por nuestra clase, que podrá examinarla y devolver la Uri correcta en cada caso. En el método OnNavigatedTo de la página SecondPage.xaml, vamos a examinar la QueryString en busca del token de archivo.:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    string fileToken = NavigationContext.QueryString["file"];

    var file = await SharedStorageAccessManager.CopySharedFileAsync(ApplicationData.Current.LocalFolder, "File.geeksms", NameCollisionOption.ReplaceExisting, fileToken);
    var stream = await file.OpenReadAsync();

    IBuffer buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
    await stream.ReadAsync(buffer, (uint)stream.Size, InputStreamOptions.None);
    DataReader reader = DataReader.FromBuffer(buffer);
    textContent.Text = reader.ReadString(buffer.Length);
}

Con este token, podemos usar el método CopySharedFileAsync de la clase SharedStorageAccessManager. Este método nos devuelve una instancia de IStorageFile, con la que podremos acceder al stream del archivo, ya copiado en el espacio de nuestra aplicación. Con esto solo nos queda leer su contenido usando el método ReadAsync y un DataReader con el método ReadString para finalmente obtener el contenido en formato de texto.

image

Asociación de protocolos

De una forma muy parecida a como hemos realizado la asociación de archivos, podemos llevar a cabo una asociación de protocolos. ¿A qué nos referimos exactamente con un protocolo? Por regla general, nos referimos a una URL, que podría ser algo como lo siguiente:

geeksms:showarticle?ArticleId=100

Podemos asociar nuestra aplicación al protocolo geeksms: y recibir el resto de parámetros para decidir en que página iniciar la aplicación o que datos cargar. El primer paso para llevar a cabo esto, es registrar el protocolo al que deseamos responder (o protocolos, pueden ser más de uno). Lo realizaremos en el archivo WMAppManifest.xml, al igual que el registro de tipo de archivos:

<Extensions>
  <Protocol Name="geeksms" TaskID="_default" NavUriFragment="encodedLaunchUri=%s"/>
</Extensions>

Creamos una sección Extensions, si no existe ya, y dentro añadimos un nodo Protocol. El nombre debe ser único. Los otros dos parámetros son fijos, la tarea asociada (_default es la que abre nuestra aplicación) y el fragmento de navegación. Este fragmento debe ser siempre el mostrado en este ejemplo, dentro llegará la URL enviada a nuestra aplicación. Para poder saber cuando se está activando la aplicación desde un protocolo, usaremos una clase UriMapper, igual que con la asociación de archivos:

public class ProtocolUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        string tempUri = HttpUtility.UrlDecode(uri.ToString());

        if (tempUri.Contains("geeksms:ShowArticle?ArticleId="))
        {
            string navUri = string.Format(@"/Views/SecondPage.xaml?protocol={0}", HttpUtility.UrlEncode(tempUri));
            return new Uri(navUri, UriKind.Relative);
        }
        return uri;
    }
}

En el método OnNavigatedTo de nuestra página SecondPage, obtendremos el parámetro protocol que enviamos para mostrarlo en pantalla:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    protocolTextBlock.Text = NavigationContext.QueryString["protocol"];
}

¿Como podemos probar que está funcionando? La activación por protocolo solo puede realizarse desde otra aplicación. Para ello haremos uso de otra novedad de Windows Phone 8, la clase Launcher, del namespace Windows.System.

Bonus: LaunchUriAsync y LaunchFileAsync

Otra de las novedades de Windows Phone 8 es la clase Launcher, del namespace Windows.System. Esta clase contiene dos métodos: LaunchUriAsync y LaunchFileAsync. Estos nos permiten lanzar de una forma sencilla una URL o un archivo.

LaunchUriAsync nos permite lanzar una URL y el sistema se encargará de buscar una aplicación asociada. Por ejemplo, podemos usar una dirección de un sitio web, de la siguiente forma:

await Windows.System.Launcher.LaunchUriAsync(new Uri("http://www.geeks.ms"));

Que dará como resultado la apertura de Internet Explorer. Pero también podremos lanzar una URL con un protocolo propio, lo que ejecutará nuestra aplicación:

await Windows.System.Launcher.LaunchUriAsync(new Uri("geeksms:ShowArticle?ArticleId=100"));

LaunchFileAsync nos permite realizar la misma operación, pero con un archivo. Pasando como parámetro la instancia del archivo contenida en un IStorageFolder. Por ejemplo, con un archivo que distribuyamos con la aplicación:

var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("file.geeksms");
await Windows.System.Launcher.LaunchFileAsync(file);

Conclusión

Y esto es todo por hoy. Hemos visto como asociar nuestra aplicación a tipos de archivos y protocolos y como lanzar desde nuestra aplicación, otras aplicaciones usando archivos y URLs. Como siempre, a continuación tenéis tres proyectos con los ejemplos que hemos desarrollado en el post para que juguéis con ellos.

Un saludo y Happy Coding!

2 comentarios sobre “Windows Phone 8: Asociación de archivos y protocolos”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *