Creare la versione Mobile di un sito web in Asp.Net

Come modificare dinamicamente l'interfaccia di un sito internet in base al tipo di dispositivo del client visitatore.

Come ho già descritto in un articolo precedente esistono diversi modi per modificare l'interfaccia di un sito web in base al dispositivo client, che può essere uno smartphone, un tablet o un pc laptop o desktop.

Per Dev-oClock ho scelto di utilizzare la tecnica delle master pages dinamiche.
Il funzionamento è il seguente:

  1. Il client richiede una pagina del sito
  2. La pagina rileva il tipo di dispositivo richiedente
  3. In base al tipo di dispositivo viene scelta al volo la master page opportuna

In questo modo il sito è il medesimo, cambia solo l'interfaccia o meglio la master page.
I paragrafi successivi dell'articolo descrivono passo passo come ho messo in pratica questa tecnica.

Creazione nuova Master Page

Chiaramente questa tecnica prevede la presenza di almeno due Master Pages: una per la visualizzazione standard, ovvero per pc desktop e laptop, e un'altra per dispositivi Mobile.

Per realizzare la versione mobile ho scelto di usare il framework JQuery Mobile, ma ognuno può utilizzare il framework che preferisce, o anche non usarne nessuno.
L'unica accortezza è quella di disattivare il comportamento di default dei link:

$(document).ready(function () {

    /* Conversione Link */
    //disattivo il comportamento predefinito dei link per JQuery Mobile:
    //non desidero che usino Ajax ma voglio che siano link tradizionali
    $('a').each(function () {
        $(this).attr("data-ajax", "false");
    });
    ...

Pagina Base

In Asp.Net Web Forms ogni pagina per default è un oggetto che eredita dalla classe System.Web.UI.Page. Esempio:

Partial Class admin_articles_Articles_AddEdit
    Inherits System.Web.UI.Page
Ho cambiato questo comportamento creando una nuova classe che ho chiamato BasePageFront facendola ereditare dalla System.Web.UI.Page, poi ho fatto in modo che ogni pagina web del front end del sito derivi dalla BasePageFront anziché dalla System.Web.UI.Page. Chiaro no? :)

Le pagine quindi diventano qualcosa del tipo:

Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration

Partial Class articolo
    Inherits BasePageFront

    Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        If Not Page.IsPostBack Then

Questo significa che ogni volta che viene richiesta una pagina, viene prima eseguito il codice Visual Basic contenuto nella classe BasePageFront. E' qui che viene rilevato il tipo di client e vene stabilita l'interfaccia opportuna da utilizzare.

Questa interfaccia è adatta a schermi di piccole dimensioni e ha i classici menu laterali.

Classe BasePageFront

Public Class BasePageFront
    Inherits System.Web.UI.Page

    Dim blnMobile As Boolean

    Private Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreInit

        'selezione automatica interfaccia sito (Desktop/Mobile)
        CheckIsMobile()

    End Sub

    ''' <summary>
    ''' Verifica se il dispositivo che ha richiesto la pagina è mobile
    ''' e seleziona l'opportuna Master Page
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub CheckIsMobile()
        
        Dim myBrowserCaps As System.Web.HttpBrowserCapabilities = Request.Browser
        
        blnMobile = (CType(myBrowserCaps, System.Web.Configuration.HttpCapabilitiesBase)).IsMobileDevice

        If blnMobile Then
            Me.MasterPageFile = "~/master_pages/FrontEnd-Mobile.master"
        Else
            Me.MasterPageFile = "~/master_pages/FrontEnd.master"
        End If
    End Sub
End Class

Quando un client richiede una pagina, prima ancora che vengano generati i controlli viene richiamata la funzione CheckIsMobile(). Questa funzione utilizza una libreria di terze parti chiamata 51Degrees per capire se il client è un dispositivo mobile oppure no, leggendo la proprietà IsMobileDevice.
Il risultato viene salvato nella variabile booleana blnMobile, che viene usata per stabilire la Master Page: FrontEnd.master è usata per la versione desktop, mentre FrontEnd-Mobile.master è usata per la versione Mobile.

Forzare il tipo di visualizzazione

Avrete notato che su Dev-oClock.com a fondo pagina è presente un link per forzare la visualizzazione del sito in versione Mobile. Nella versione Mobile invece nel menu laterale destro è presente il link per forzare la visualizzazione desktop.

Per fare questo, oltre naturalmente ai link stessi ho aggiunto una funzione JavaScript (o meglio JQuery) e un form html nelle rispettive Master Pages. La funzione JS è uguale per entrambi:

function ChangeVersion(version) {
    $('#hidDevice').val(version);
    $('#frmChangeDevice').submit();
}

Questo è il link per la versione desktop, situato nel Footer:

<p>
    Dev-oClock.com&nbsp;<span style="font-size: small;">2013 - 2016</span><br />                    
    <span style="font-size: small;color:#FFFFFF;">
        <a href="javascript:ChangeVersion('s');" rel="nofollow"
        title="passa alla versione mobile">versione mobile</a></span>
 </p>

Questo è il Form per la versione desktop del sito:

<form id="frmChangeDevice" method="post" action="">
    <input type="hidden" name="hidDevice" id="hidDevice" value="s" />
</form>

Questo è il link per la versione mobile:

<a runat="server" id="lnkVersioneDesktop" class="ui-icon-action"
    href="javascript:ChangeVersion('d');">versione desktop</a>

Questo è il Form per la versione mobile, da posizionare in fondo alla pagina html prima di </body>:

<form id="frmChangeDevice" data-ajax="false" method="post" action="">
    <input type="hidden" name="hidDevice" id="hidDevice" value="d" />
</form>

La forzatura naturalmente prevale sulla lettura del dispositivo da parte della funzione CheckIsMobile(). Il valore che indica il tipo di master page viene memorizzato in un cookie in modo da poter essere mantenuto anche quando si visitano altre pagine. Se non ci sono forzature e il cookie è vuoto allora si utilizza la variabile blnMobile come descritto sopra.

Listato Completo

Riporto il listato competo della classe BasePageFront che tiene conto anche della forzatura e utilizza il cookie. Questa è la classe realmente utilizzata per il sito Dev-oClock.

Imports Microsoft.VisualBasic
Imports System.Configuration

Public Class BasePageFront
    Inherits System.Web.UI.Page

    Dim blnMobile As Boolean

    Private Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreInit

        'selezione automatica interfaccia sito (Desktop/Mobile)
        CheckIsMobile()

        '...

    End Sub

    ''' <summary>
    ''' Verifica se il dispositivo che ha richiesto la pagina è mobile
    ''' e seleziona l'opportuna Master Page
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub CheckIsMobile()

        Dim chrForzatura As Char    'n: nessuna / d: desktop / s: smartphone / t: tablet (gestito come desktop)
        Dim myBrowserCaps As System.Web.HttpBrowserCapabilities = Request.Browser
        
        'IsMobileDevice è resa disponibile dalla libreria esterne 51Degrees
        blnMobile = (CType(myBrowserCaps, System.Web.Configuration.HttpCapabilitiesBase)).IsMobileDevice
        
        chrForzatura = "n"
        Dim objCookie As HttpCookie
        objCookie = Request.Cookies("dev-oclock")

        If Not String.IsNullOrEmpty(Request.Form("hidDevice")) Then
            'forzatura da Form: salvo il valore in un cookie
            chrForzatura = Request.Form("hidDevice")
            If objCookie Is Nothing Then
                objCookie = New HttpCookie("dev-oclock")
                objCookie.Expires = Now.AddDays(1)
                Response.Cookies.Add(objCookie)
            End If
            Response.Cookies.Remove("dev.oclock")
            objCookie("device") = chrForzatura
            Response.Cookies.Add(objCookie)
        Else
            'forzatura da Form non presente: se presente leggo il valore dal cookie
            If (objCookie IsNot Nothing) AndAlso Not (String.IsNullOrEmpty(objCookie("device"))) Then
                chrForzatura = objCookie("device").ToString
            Else
                'no POST, no COOKIE; viene usata la variabile blnMobile
            End If
        End If

        Select Case chrForzatura
            Case "d"
                Me.MasterPageFile = "~/master_pages/FrontEnd.master"
            Case "s"
                Me.MasterPageFile = "~/master_pages/FrontEnd-Mobile.master"
            Case Else
                If blnMobile Then
                    Me.MasterPageFile = "~/master_pages/FrontEnd-Mobile.master"
                Else
                    Me.MasterPageFile = "~/master_pages/FrontEnd.master"
                End If
        End Select
    End Sub

End Class

Autore: Sergio Roberto Boarina