Technology Experience
Hardware

Steam Controller, recensione personale

A Natale ho ricevuto un bel regalo geek da parte della mia ragazza che – conoscendomi ormai bene – sa quanto io ami svagarmi dal lavoro con qualche videogioco acquistato su Steam. Non ho mai avuto una console in vita mia, e per me giocare significa farlo prevalentemente con il PC. Già negli anni scorsi ho parlato di Steam, per cui se siete miei fedeli lettori sapete quanto io adori questa piattaforma di distribuzione di Valve (gli ideatori della serie di Half Life, per capirci).

Beh, insomma, la mia ragazza mi ha regalato lo Steam Controller.
Ed è stata una incredibile sorpresa.

Che cos’è? A cosa serve?
Sullo Store di Steam lo Steam Controller c’era da molto tempo e mi faceva venire l’acquolina in bocca, ma alla fine ho sempre rimandato l’acquisto (per risparmiare, per pigrizia, per un leggero dubbio che comunque imperversava nella mente). Ed ho sempre sbagliato, perchè funziona un gran bene con tantissimi videogiochi. Ma andiamo con calma.

Che cos’è lo Steam Controller? E’ banalmente un controller per PC, che funziona in modalità wired o wireless: il modello è unico, per cui non dovete scegliere uno o l’altro. Semplicemente acquistate lo Steam Controller e poi scegliete come usarlo. Nella scatola trovate il seguente materiale:

  • il controller, ovviamente
  • il dongle USB da inserire nel PC (foto qui sotto)

  • un cavetto USB –> Micro USB che ha due scopi: o usare il controller in modalità wired, oppure per collegare la base su cui poi collegare il dongle di cui sopra. Quest’ultima soluzione serve nel momento in cui non riusciate (o non vogliate) collegare direttamente il dongle al PC, per motivi di spazio o per entrare nel raggio dei 5 metri di portata

  • batterie, due foglietti di istruzioni, etc. etc.

L’utilizzo è molto semplice. Si inserisce il dongle al PC e si avvia Steam. Steam rileva immediatamente il nuovo controller. Non c’è bisogno di installare alcun driver o software.

Qui ci sono due cose da dire importanti: Windows non rileva il controller come un normalissimo controller da gioco; morale: potete utilizzare lo Steam Controller solo all’interno di giochi comprati e/o configurati all’interno di Steam. Poco male. Secondo: il controller funziona solo ed esclusivamente quando si entra nella modalità Big Picture. Può sembrare uno svantaggio, ma dopo un paio di giorni ho cambiato idea. Ne parlo fra poco.

A chi è rivolto? Serve a tutti?
Procediamo con calma. Ho appena detto che lo Steam Controller è utilizzabile solo ed esclusivamente nella modalità Big Picture. Di cosa si tratta? E’ una modalità che trasforma Steam dalla classica applicazione desktop per Windows ad una modalità entertainment da console, stile XBox o Playstation, per capirci. Steam gira in pieno schermo, ed è possibile navigare all’interno di tutte le funzioni Steam usando tastiera, mouse oppure lo Steam Controller. Qualche foto rende meglio l’idea.

Non direste che si tratta di un PC, vero? Andiamo oltre. Personalmente ho sempre snobbato la modalità Big Picture, invece in questi pochi giorni di relax mi sono dovuto ricredere. Al contrario di quanto accade con la tradizionale modalità desktop, la Big Picture è decisamente più accattivante. Con lo Steam Controller è possibile navigare tutte le sezioni di Steam: la libreria, lo store, la community, le recensioni dei giochi, il workshop, etc. E’ possibile entrare nelle impostazioni di Steam, impostare il controller in modo specifico per ciascun gioco installato (mappando tasti, sensibilità delle levette), e via dicendo.

Sotto tantissimi aspetti, ritengo che la modalità Big Picture di Steam sia molto più curata rispetto alla versione desktop che ho sempre utilizzato. E’ difficile spiegare il motivo così a parole. La versione desktop, per esempio, per esempio, presenta quattro sezioni distinte fra loro: Negozio, Libreria, Comunità ed un’ultima sezione dedicata al nostro account. Quando si entra in una sezione, le altre rimangono escluse. Nella modalità Big Picture, invece, si ha tutto a portata di mano. Esempio: per poter giocare, è necessario entrare nella pagina dedicata al gioco che ci interessa (funzione che non esiste nella modalità desktop), e da qui è possibile raggiungere i contenuti associati al gioco stesso: ultime news, recensioni, screenshot e video degli altri utenti. Il tutto a pochi clic di distanza, con lo Steam Controller o con il mouse.

Lo Steam Controller è adatto a tutti? Direi di sì, anche se rende il suo meglio quando si riesce a collegare il PC ad una televisione come faccio io. Perchè? Perchè Big Picture rende l’esperienza d’uso molto più immersiva e più adatto ad un uso da salotto. Non c’è nulla di male ad usare lo Steam Controller con un classico monitor da PC, sia chiaro, ma secondo me dà il suo meglio con una bella TV come sto facendo io.

Ok, parliamo del controller, adesso!
E’ giunto il momento di parlare del controller vero e proprio. Chiunque l’abbia pensato, ha fatto davvero un ottimo lavoro. Giocando con il PC, la combinazione migliore è sicuramente quella composta da tastiera + mouse, praticamente obbligatoria quando si gioca a sparatutto come CoD, Wolfstein, etc. etc. Con il mouse vi guardate attorno (mano destra), mentre con la tastiera camminate (tasti WASD) ed inviate comandi al vostro avatar (mano sinistra).

Lo Steam Controller cerca di portare questa esperienza d’uso all’interno di un controller. Innanzitutto ha tra aree di controllo principali che si comportano in modo diverso:

  1. il classico joystick analogico (quello al centro)
  2. il classico controllo a croce digitale per le quattro direzioni (quello più a sinistra). Bellissimo perchè funziona anche solo sfiorando la superficie del trackpad; in realtà supporta anche il click, ma onestamente non mi è ancora chiaro se si tratta di due azioni diverse. E’ comunque molto comodo perchè si può spostare il proprio personaggio evitando di utilizzare troppo (leggesi: sollecitare) il joystick analogico del punto (1)
  3. un innovativo touchpad, molto simile a quello già esistente sui notebook da parecchi anni (quello più a destra)

Quest’ultimo è leggermente concavo, lo si utilizza con il pollice destro, vibra leggermente al tocco per dare un feedback al giocatore, ed emula il comportamento del mouse. Lo utilizzate esattamente come un touchpad da portatile, solo che mentre quest’ultimo lo utilizzate un po’ come volete, quello dello Steam Controller lo utilizzate con il pollice per direzionare lo sguardo del vostro alter-ego all’interno del gioco. Con la mano sinistra vi muovete ed il gioco è fatto.

Chiaramente lo Steam Controller dispone di numerosi altri pulsanti: dai classici X, Y, A e B fino a quelli posti sul retro (tre per mano).

I pulsanti sul retro sono azionabili con indice e dito medio delle due mani, e possono essere configurati a piacimento, in base al gioco. Sul retro c’è anche l’alloggiamento per le due batterie AA necessarie per il funzionamento, che sono comprese nella confezione.

La versatilità del controller è eccezionale, il peso adeguato, e l’integrazione con la community è molto molto utile. Ciascuno di noi può crearsi una configurazione per lo Steam Controller specifica per un gioco, e salvarla sul cloud di Steam, in forma privata o pubblica. Se una configurazione è pubblica, allora è scaricabile ed utilizzabile da altri giocatori. In pratica non c’è più nemmeno lo sbattimento di doversi settare il controller: qualcuno l’ha già fatto probabilmente prima di noi. Spettacolare.

Lo Steam Controller è dotato poi di un pulsante Steam illuminato che serve per entrare in una modalità di editing del gioco corrente, per personalizzare il setup associato al gioco corrente e così via. Emette un suono quando si accende e si spegne. Dallo Steam Controller è possibile controllare anche il sistema operativo:

E’ possibile uscire da Steam, riavviare o spegnere il PC, eccetera. Davvero comodo.

Io lo utilizzo in modalità wireless; il dongle USB dista 3,5 metri dal divano su cui mi trovo, e non ha il minimo problema di ricezione. Ottimo. Il controller è molto ben supportato: in questi pochi giorni ho già ricevuto due aggiornamenti del firmware; ho il sospetto che abbiano diminuito l’intensità della vibrazione che il controller emette quando si utilizza il touchpad di destra.

E’ difficile da utilizzare? La risposta è dipende, è molto soggettiva. Se siete sempre stati abituati ad un controller da console, vi troverete incredibilmente bene. Se avete sempre giocato con il PC, come me, allora la curva di apprendimento/adozione sarà un po’ più ripida. In questi giorni sto passando il tempo con SOMA, un horror fantascientifico, il cui gameplay è prevalentemente basato su enigmi ed esplorazione di basi sottomarine. Zero combattimenti, insomma. E questo mi aiuta per impratichirmi un po’. Vedo molto difficile che andrò ad utilizzarlo con Call Of Duty o altri giochi FPS in soggettiva. Di sicuro va benissimo per giochi di strategia, RTS, RPG come Skyrim, eccetera.

Costo dello Steam Controller
Un ultimo appunto sul costo. Il controller costa 54,99 euro, al quale vanno aggiunte le spese di spedizione. La mia ragazza ha speso 12,30 euro di spedizione, per un totale quindi di 67,29 euro.

Se siete curiosi e volete vedere altre fotografie, consultate l’album fotografico che ho pubblicato su Flickr : https://www.flickr.com/photos/igordamiani/albums/72157663253065485.

Insomma, promosso a pieni voti!!!!

Send to Kindle
.NET World

UWP, MVVM e ListView con multiselezione

Il problema di oggi è quello di avere un’app UWP (Universal Windows Platform), sviluppata seguendo la tecnica del Modem-View-ViewModel, che mostra una ListView con attivata la multiselezione. Ci sono alcuni passaggi che dobbiamo risolvere.

Innanzitutto, l’oggetto ListView espone la proprietà SelectedItem (singolare) ma non una proprietà SelectedItems. Quindi dobbiamo trovare un modo per bypassare il problema. Io ho risolto scrivendo una classe statica SelectionManager, che espone una proprietà List di tipo IList. Da viewmodel posso fare binding su questa dependency property con una proprietà ObservableCollection. All’interno, questa classe sottoscrivere l’evento SelectionChanged della ListView; ogni volta che l’utente seleziona o deseleziona un ListViewItem la lista viene sincronizzata.

public static class SelectionManager
{
public static IList GetList(DependencyObject obj)
{
return (IList)obj.GetValue(ListProperty);
}

public static void SetList(DependencyObject obj, IList value)
{
obj.SetValue(ListProperty, value);
}

public static readonly DependencyProperty ListProperty =
DependencyProperty.RegisterAttached("List",
typeof(IList),
typeof(SelectionManager),
new PropertyMetadata(null,
new PropertyChangedCallback(SetupList)));

private static void SetupList(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ListViewBase lst = d as ListViewBase;
lst.SelectionChanged += Lst_SelectionChanged;
}

private static void Lst_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
ListViewBase element = sender as ListViewBase;
var arrayList = GetList(element);

foreach (object o in e.AddedItems)
{
arrayList.Add(o);
}

foreach (object o in e.RemovedItems)
{
if (arrayList.Contains(o))
{
arrayList.Remove(o);
}
}
}
}

A livello di ViewModel espongo due ObservableCollection pubblici: una funge come ItemsSource per la ListView stessa (grazie alla quale la ListView mostra l’elenco degli Items), l’altra invece è una ObservableCollection che mantiene costantemente aggiornato l’elenco degli Items selezionati. Ecco uno stralcio di XAML per rendere evidente questa cosa.

<ListView
ItemsSource="{Binding Cars}"
SelectionMode="Extended"
hlp:SelectionManager.List="{Binding SelectedCars}" />

La proprietà Cars è una ObservableCollection<Car> che fa da datasource alla ListView, come si fa di solito. La proprietà SelectedCars è un’altra ObservableCollection<Car> dedicata solamente al mantenimento degli items correntemente selezionati.

Un altro approccio che NON ho preferito

Un altro approccio che avrei potuto seguire è quello di passare al mio SelectionManager la stessa ObservableCollection impostata su ItemsSource (nel mio caso qui sopra, quindi, Cars). Con questa logica la mia SelectionManager avrebbe potuto impostarmi una proprietà booleana IsSelected su ciascuna delle istanze di Car che risultano selezionate.

Non ho preferito questa strada per due ragioni:

  1. Così facendo sarai stato costretto ad aggiungere una proprietà bool IsSelected sul mio viewmodel, cosa che non sempre vorrei/potrei fare. D’altro canto, invece, SelectionManager è in grado di lavorare con qualsiasi tipo di classe. Quando un oggetto risulta selezionato sulla UI esso viene aggiunto alla lista in binding; quando viene deselezionato l’oggetto viene rimosso
  2. Avendo una proprietà dedicata alla multiselezione, posso fare immediatamente binding sulla mia UI. Nel mio esempio ho una proprietà SelectedCars pronta all’uso ed aggiornata, quindi è semplicissimo aggiungere al visual tree una TextBlock la cui proprietà Text fa binding con un {Binding Path=SelectedCars.Count} (per visualizzare il numero di elementi selezionati). Con questo secondo approccio, invece, avrei un’unica proprietà Cars, e per ottenere l’elenco degli Items selezionati devo continuamente giocare con LINQ e fare una query per ottenere le Cars selezionate

Se serve, posso aggiungere un Command ed un CommandParameter

Una volta sviluppata la classe SelectionManager con il codice qui sopra, è un gioco da ragazzi aggiungere due nuove dependency property: una di tipo ICommand ed una di tipo object. Lo scopo in questo caso è quello potenzialmente di andare ad eseguire un Command sul mio viewmodel dopo che la selezione è stata gestita (se serve, mica è obbligatorio).

Ecco il codice completo della classe SelectionManager:

public static class SelectionManager
{
public static IList GetList(DependencyObject obj)
{
return (IList)obj.GetValue(ListProperty);
}

public static void SetList(DependencyObject obj, IList value)
{
obj.SetValue(ListProperty, value);
}

public static readonly DependencyProperty ListProperty =
DependencyProperty.RegisterAttached("List",
typeof(IList),
typeof(SelectionManager),
new PropertyMetadata(null,
new PropertyChangedCallback(SetupList)));

public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}

public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}

public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(SelectionManager),
new PropertyMetadata(null,
new PropertyChangedCallback(SetupList)));

public static object GetCommandParameter(DependencyObject obj)
{
return obj.GetValue(CommandParameterProperty);
}

public static void SetCommandParameter(DependencyObject obj,
object value)
{
obj.SetValue(CommandParameterProperty, value);
}

public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(SelectionManager),
new PropertyMetadata(null));

private static void SetupList(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ListViewBase lst = d as ListViewBase;
lst.SelectionChanged += Lst_SelectionChanged;
}

private static void Lst_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
ListViewBase element = sender as ListViewBase;
var arrayList = GetList(element);

foreach (object o in e.AddedItems)
{
arrayList.Add(o);
}

foreach (object o in e.RemovedItems)
{
if (arrayList.Contains(o))
{
arrayList.Remove(o);
}
}

var command = GetCommand(element);

if (command != null)
{
var parameter = element.GetValue(CommandParameterProperty);

if (command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
}

Dopo che l’evento SelectionChanged è stato scatenato, la proprietà del viewmodel viene sincronizzata con la selezione corrente. Dopodichè, se c’è un Command in binding viene eseguito, passando anche il suo eventuale parametro object.

 
Fine!
Send to Kindle
.NET World

Applicazioni UWP: come gestire correttamente il tasto Back

Le applicazioni Universal Windows Platform sono organizzate attraverso pagine (classe Page), esattamente come accadeva nelle precedenti versioni di Windows 8 ed 8.1. Questo significa che l’app parte mostrando una pagina iniziale, poi l’utente naviga e si sposta secondo un flusso che dipende dall’app stessa. L’utente può ovviamente anche tornare indietro fra le pagine, con diverse modalità dipendentemente dal software e dall’hardware che ha a disposizione. Ad esempio:

  • su uno smartphone l’utente può utilizzare il tasto hardware fisico Back, oppure può toccare il tasto software previsto da Windows (come ad esempio su un Lumia 640 LTE)
  • su un tablet o su un PC Windows 10 il tasto hardware Back non esiste, per cui l’utente può solamente cliccare/toccare il tasto Back software previsto dal sistema operativo. Questo tasto si può trovare in due punti distinti: se la modalità Tablet è spenta, allora il pulsante si trova sulla barra del titolo relativo all’app stessa. Se la modalità Tablet è attiva, allora il pulsante si trova sulla taskbar di Windows
  • Da notare che l’utente può posizionare la taskbar su uno dei quattro lati dello schermo, per cui il tasto Back fisicamente può ritrovarsi ai quattro angoli dello schermo:
    – taskbar inferiore : tasto Back nell’angolo in basso a sinistra
    – taskbar sul lato sinistro : tasto Back nell’angolo in alto a sinistra
    – taskbar superiore : tasto Back nell’angolo in alto a sinistra
    – taskbar sul lato destro : tasto Back nell’angolo in alto a destra
  • L’app può ovviamente anche contenere un pulsante Back previsto dallo sviluppatore ed inserito all’interno della pagina: aspetto, posizionamento e comportamento dipendono ovviamente da chi scrive il codice ed implementa la pagina

Qualche screenshot è d’obbligo per chiarire meglio le cose.

image

Sopra : Pulsante Back sulla barra del titolo in un’app UWP con modalità Tablet spenta.

image

Sopra : Pulsante Back sulla taskbar di Windows agganciata al lato sinistro dello schermo, con modalità Tablet attiva.

image

Sopra : Pulsante Back sulla taskbar di Windows agganciata al lato inferiore dello schermo, con modalità Tablet attiva.

Rendere visibile o invisibile il tasto Back 
Vediamo adesso qualche riga di codice C# per gestire tutti questi scenari.

Innanzitutto, occorre controllare la visibilità del pulsante Back, che di default è e rimane nascosto. Con Windows 10 questo pulsante non fa parte della nostra UI, non è un contenuto inserito nelle nostre pagine tramite XAML, per cui se vogliamo controllarlo dobbiamo passare da classi che consentono l’accesso a questo componente messo a disposizione dal sistema operativo. Ad esempio, se vogliamo rendere visibile il pulsante Back ogni volta che si naviga verso una nuova pagina il codice necessario è il seguente:

SystemNavigationManager.GetForCurrentView()
.AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;

Dove posizionare questo blocco di codice? Ecco il codice completo che suggerisco:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;

if (rootFrame == null)
{
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
rootFrame.Navigated += RootFrame_Navigated;
Window.Current.Content = rootFrame;
}

if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}

Window.Current.Activate();
}

private void RootFrame_Navigated(object sender,
NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView()
.AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
Nel metodo OnLaunched() definito nella classe App sottoscrivo l’evento Navigated della classe Frame, che si scatena ogni qualvolta viene completata la navigazione verso una nuova pagina. Il bottone Back, di conseguenza, viene reso visibile se la proprietà CanGoBack del Frame è true.
Questo, però, completa solo metà del problema. Il bottone è visibile, ma se viene cliccato non accade nulla: per portare l’utente sulla pagina precedente dobbiamo scrivere ancora qualche riga di codice C#.

 

Intercettare la pressione del tasto Back e spostarsi alla pagina precedente

Capire quando l’utente clicca o tocca il pulsante Back e reagire di conseguenza è semplice:

public SecondPage()
{
this.InitializeComponent();

SystemNavigationManager.GetForCurrentView()
.BackRequested += SecondPage_BackRequested;
}

private void SecondPage_BackRequested(object sender,
BackRequestedEventArgs e)
{
if (this.Frame.CanGoBack)
{
this.Frame.GoBack();
}

e.Handled = true;
}

Si utilizza la classe SystemNavigationManager, si ottiene un riferimento alla view corrente e si sottoscrive l’evento BackRequested. Nell’event handler si effettua la navigazione alla pagina precedente: un buon modo per evitare eccezioni o problemi da questo punto di vista è verificare se sia possibile controllando la proprietà CanGoBack esposta da Frame.

 
Ed in un mondo MVVM?
Quando si sviluppa un’app con il pattern MVVM, le cose sono un po’ diverse, ma non molto. Non scriviamo codice nel code-behind della Page, bensì abbiamo a che fare con una classe ViewModel. Nel costruttore di ciascuna classe ViewModel sottoscriviamo l’evento BackRequested:
 
public AboutViewModel()
{
SystemNavigationManager.GetForCurrentView()
.BackRequested += (s, e) =>
{
e.Handled = true;
base.GoBack();
};
}
Si può evitare di sottoscrivere l’evento nella classe ViewModel relativa alla prima pagina che parte con l’app, perchè da questa pagina non si può tornare indietro da alcuna parte. Nel momento in cui l’utente clicca/tocca il tasto Back, viene scatenato l’evento BackRequested. Nel mio esempio qui sopra, vado a chiamare il metodo GoBack() esposto da tutti i miei ViewModel, che fa semplicemente:
 
protected void GoBack()
{
Frame rootFrame =
Windows.UI.Xaml.Window.Current.Content as Frame;

if (rootFrame.CanGoBack)
{
rootFrame.GoBack();
}
}
 
In questo modo si evita di andare a lavorare nel code-behind: qui semplicemente si recupera il Frame relativo alla vista corrente e se possibile si scatena la navigazione all’indietro.
 
Et voilà, il gioco è fatto!
Send to Kindle
My daily work

Installation Guide for Team Foundation Server 2015

Durante il weekend scorso, per prepararmi ad un corso di 3 giorni che avrei dovuto tenere a Trieste su Team Foundation Server ed Application Lifecycle Management, mi sono preparato un pochino e mi sono deciso ad effettuare l’installazione di TFS 2015 all’interno di una macchina virtuale Hyper-V, partendo dal sistema operativo (Windows Server 2012 R2) fino a Microsoft Office 2016, passando da tutte le ultime versioni di software e di strumenti di sviluppo. Non ho molta dimestichezza con le installazioni server, per cui prima di cominciare e tentare ho googlato ed ho trovato questa guida:

Team Foundation Server 2015 (TFS2015) Installation Guide

Include tutto il necessario: Windows Server 2012 R2, SQL Server 2014 Standard Edition, Team Foundation Server 2015, tutte le configurazioni per ogni singolo passo.

E’ una guida davvero ottima, scritta in modo semplice ed illustrata tramite screenshot. Dal mio punto di vista pecca solo di una cosa: non è inclusa l’installazione di Sharepoint Server 2013. Per quest’ultimo setup ho seguito questo link.

Oltre a questo, c’è un ulteriore piccolo problema, che ho provveduto a segnalare anche all’autore del post originario: appena terminata l’installazione di Windows Server 2012 R2, viene detto di attivare Windows Update, che comporta ovviamente il download e l’installazione di tutti gli aggiornamenti di sistema, compresa l’ultima versione del .NET Framework, che al momento è la 4.6. Non ci sarebbe nulla di male, anzi, se non fosse per il fatto che l’installer di Sharepoint Server 2013 non riconosce questa versione di .NET Framework, e quindi non potete più procedere con la sua installazione. Ho provato a disinstallare, a giocare con il registry, ma non ho combinato nulla di buono. Morale: ho dovuto ricominciare daccapo la macchina. Il mio consiglio è quello di lasciare Windows Update spento, fino a quando non avete terminato il setup di tutto quanto, e di attivarlo solo alla fine.

Send to Kindle
.NET World

Spunti di Adaptive Code con UWP

Quando parliamo di Universal Windows Platform parliamo per diretta conseguenza di Windows 10, e quindi di tutti i possibili hardware capaci di far girare Windows 10, dallo smartphone a tutti gli altri possibili ed immaginabili dispositivi. E’ importante applicare quindi correttamente concetti come Adaptive UI (ovvero un’interfaccia utente capace di adattarsi e di scalare in base alla risoluzione ed allo schermo correnti), ma anche di Adaptive Code, ovvero di codice c# che capisca su quali famiglia di dispositivi ci troviamo e di adattarsi di conseguenza.

Per quale motivo? Ce ne sono tanti, a dir la verità.

Facciamo una cosa semplice. Supponiamo di creare ed istanziare una MessageDialog:

var dialog = new MessageDialog(
string.Format("Aprire il link {0} ?", url),
"Apertura sito Web");
dialog.Commands.Add(
new UICommand { Label = "&Sì", Id = 0 });
dialog.Commands.Add(
new UICommand { Label = "&No", Id = 1 });
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 1;
var res = await dialog.ShowAsync();
Notare che ho impostato le label come &Sì e &No, in modo tale che da tastiera sia possibile premere ALT+S e ALT+N per rispettivamente premere Sì oppure no. La tastiera però su smartphone non ce l’abbiamo, quindi questa è una feature che non è supportata. Purtroppo, con la build corrente di Windows 10 for Mobile quelle & commerciali compaiono comunque, con uno sgradevole effetto per l’utente finale. La soluzione è intercettare e capire su quale famiglia di device ci troviamo a run-time, ed adattarci in base allo scenario.
Possiamo capire su quale famiglia di dispositivi Windows 10 ci troviamo con questa linea di codice:
string family = Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily;

Il valore restituito è una semplice stringa. E quindi possiamo scrivere:

string family = Windows.System.Profile
.AnalyticsInfo.VersionInfo.DeviceFamily;
string yesLabel = "Sì";
string noLabel = "No";

if (family == "Windows.Desktop")
{
yesLabel = "&Sì";
noLabel = "&No";
}

Windows.Desktop è il valore che quella DeviceFamily assume quando il codice gira sotto l’ambiente desktop (quindi PC di varia natura). Il valore diventa Windows.Mobile se il codice sta girando sotto smartphone.

Send to Kindle
.NET World

App UWP localizzate solo in italiano: qualche problema con MvvmLightLibs e CommonServiceLocator

Introduzione
Una volta mi impegnavo a scrivere i titoli dei miei post sul blog concisi ed efficaci: ormai ci ho rinunciato. Passiamo alle cose serie.

Ho avuto un grave problema mischiando i seguenti ingredienti:

  1. Blank App (Universal Windows) con Visual Studio 2015, quindi parliamo di UWP
  2. Aggiunta di MvvmLightLibs tramite NuGet (attualmente la versione è la 5.2.0)
  3. Impostazione nel Package.appxmanifest (file di manifest dell’app) della lingua “it-IT” (voglio che la mia app sia solo ed esclusivamente localizzata in italiano)

Compilando l’app così creata otteniamo immediatamente 4 warning:

  1. MakePRI : warning 0xdef00522: Resources found for language(s) ‘en-us’ but no resources found for default language(s): ‘it-IT’. Change the default language or qualify resources with the default language. http://go.microsoft.com/fwlink/?LinkId=231899
  2. MakePRI : warning 0xdef01051: No default or neutral resource given for ‘Microsoft.Practices.ServiceLocation.Properties.Resources/ActivateAllExceptionMessage’. The application may throw an exception for certain user configurations when retrieving the resources.
  3. MakePRI : warning 0xdef01051: No default or neutral resource given for ‘Microsoft.Practices.ServiceLocation.Properties.Resources/ServiceLocationProviderNotSetMessage’. The application may throw an exception for certain user configurations when retrieving the resources.
  4. MakePRI : warning 0xdef01051: No default or neutral resource given for ‘Microsoft.Practices.ServiceLocation.Properties.Resources/ActivationExceptionMessage’. The application may throw an exception for certain user configurations when retrieving the resources.

I warning riguardano la libreria Common Service Locator, dipendenza di MvvmLightLibs, che NON vedete nelle reference del progetto, ma che in realtà c’è eccome. Sostanzialmente ci viene detto che ci sono tre risorse che NON hanno una resource per la nostra lingua italiana. Ho sempre ignorato questi warning, fino al momento in cui ho creato il package della mia app UWP da inviare sullo Windows Store. Una volta terminato l’upload, infatti, lo Store chiede l’inserimento delle informazioni dell’app in due lingue: italiano ed inglese. Questo perchè il package in realtà contiene risorse un po’ in italiano (e fin qua tutto ok), ed un po’ in inglese (falso!!!), e quindi dobbiamo dare supporto ad entrambe le lingue. Grazie anche al mio amico Twitter Fela, che mi ha dato qualche dritta sull’utility “makepri”, che mi ha permesso di indagare più a fondo.

Per risolvere ho provato a rimuovere totalmente la libreria MvvmLightLibs, ed a importare direttamente i suoi file sorgenti, ed effettivamente la situazione si sblocca. Importando uno ad uno i file .cs di MvvmLightLibs (solo lo stretto necessario), la libreria Common Service Locator non viene più inclusa,  e quindi quei 4 warning non compaiono più. Ma la cosa non mi piaceva, per nulla.

Soluzione finale
La tattica risolutiva è la seguente: nel nostro progetto dobbiamo trovare il modo di aggiungere nelle reference la libreria MvvmLightLibs, escludendo la Common Service Locator (fonte del problema). Non possiamo affidarci a NuGet, purtroppo, perchè scaricando MvvmLightLibs da lì viene automaticamente aggiunta la Common Service Locator. La soluzione però è dietro l’angolo. Se abbiamo scaricato almeno una volta MvvmLightLibs significa anche che ce l’abbiamo in locale sul nostro hard-disk, ovvero sul nostro PC. Nel caso specifico, essa si trova nel path:

%userprofile%.nugetpackagesMvvmLightLibs5.2.0lib
et45

Perciò basta aggiungerla direttamente da qui, saltando NuGet e compagnia bella. In questo modo la Common Service Locator è automaticamente esclusa, non avrete più quei warning e una volta uploadato il package sullo Store avrete a che fare con un’app interamente e solamente italiana.

image

 

Vinto!

Send to Kindle
.NET World

App UWP, Cortana ed attivazione vocale

Windows 10 include l’assistente vocale Cortana, comparso per la prima volta in Windows Phone. Attraverso Cortana possiamo comandare il PC a voce, con tutta una serie di comandi predefiniti di sistema. Come sviluppatori Universal Windows Platform, inoltre, possiamo configurare tutta una serie di frasi che il sistema operativo può riconoscere. Quando lo fa, attiva la nostra app, e la nostra app può intercettare questo evento e reagire di conseguenza.

Vediamo passo passo come ottenere questo obiettivo.

Implementazione
La prima cosa da fare è modificare il file App.xaml.cs, e nell’override del metodo OnLaunched aggiungere le righe seguenti:

Task.Run(async () =>
{
var storageFile = await StorageFile.GetFileFromApplicationUriAsync
(new Uri("ms-appx:///VoiceCommands.xml"));
await VoiceCommandDefinitionManager.
InstallCommandDefinitionsFromStorageFileAsync(storageFile);
});

In pratica, viene utilizzato il file VoiceCommands.xml – ovviamente incluso nel progetto come Content e “Copy if newer” – e registrato a livello di sistema operativo. In questo modo, Windows 10 riconoscerà le frasi specificate nel file XML, anche quando la nostra app UWP è chiusa.

Come è composto questo file XML? Ecco un esempio:

<?xml version="1.0" encoding="utf-8" ?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.1">
<CommandSet xml:lang="it-IT" Name="VoiceCommands">
<CommandPrefix> Nome App, </CommandPrefix>
<Example> Chi ha vinto la medaglia di bronzo ? </Example>
<Command Name="medal">
<Example> Chi ha vinto la medaglia di bronzo ? </Example>
<ListenFor> Chi ha vinto la medaglia di {medal} </ListenFor>
<Feedback> Chi ha vinto la medaglia di {medal} </Feedback>
<Navigate/>
</Command>
<PhraseList Label="medal">
<Item> bronzo </Item>
<Item> argento </Item>
<Item> oro </Item>
</PhraseList>
</CommandSet>
</VoiceCommands>

Grazie a questo XML, possiamo chiedere a Windows 10 ed alla nostra app chi ha vinto un certo tipo di medaglia. Il riconoscimento di questa frase – ripeto – avviene a livello di sistema operativo. Una volta eseguite le righe di codice C# indicate qui sopra, possiamo:

  1. Premere Win+C per avviare Cortana in modalità ascolto (oppure attivare Cortana con la voce pronunciando “Hey Cortana”, se questa funzionalità è stata preventivamente configurata ed attivata)
  2. Pronunciare ad esempio “Nome App, Chi ha vinto la medaglia di argento?”

Windows riconosce la frase ed avvia automaticamente l’app. Quando la nostra app viene attivata da un comando vocale come in questo caso, si passa dal metodo OnActivated della classe App (e non da OnLaunched) che si trova in App.xaml.cs.

protected override void OnActivated(IActivatedEventArgs e)
{
if (e.Kind == ActivationKind.VoiceCommand)
{

}
}

Il parametro e espone una proprietà Kind che ci dice il tipo di attivazione a cui siamo soggetti. Quando è di tipo ActivationKind.VoiceCommand, sappiamo che si tratta di un comando vocale. Giunti a questo punto, possono esserci due strade differenti:

  1. la nostra app è già aperta in foreground
  2. la nostra app è chiusa

Anche nel caso (1) l’esecuzione passa sempre e comunque da OnActivated. Nel caso (2), invece, la nostra app è completamente chiusa, perciò sta a noi decidere quale page visualizzare. Questo è il codice che ho utilizzato in una mia app reale:

protected override void OnActivated(IActivatedEventArgs e)
{
if (e.Kind == ActivationKind.VoiceCommand)
{
var commandArgs = e as VoiceCommandActivatedEventArgs;
var speechRecognitionResult = commandArgs.Result;
var textSpoken = speechRecognitionResult.Text;

if (this.rootFrame == null)
{
// App chiusa
rootFrame = new Frame();
Window.Current.Content = rootFrame;
this.rootFrame.Navigate(typeof(MainPage), textSpoken);
Window.Current.Activate();
}

// Invio un messaggio che verrà intercettato dal MainViewModel
// sia ad app già aperta, che appena avviata dal comando vocale
Messenger.Default.Send<TellMeMedalWinnerMessage>
(new TellMeMedalWinnerMessage(textSpoken));
}
}

Notare che ho spostato la dichiarazione dell’oggetto rootFrame a livello di classe, al contrario di quello che fa il template di base, che invece lo dichiara in modo locale al metodo OnLaunched.

Se l’oggetto rootFrame è null, significa che l’app era chiusa, quindi creo il frame, scateno la navigazione, imposto Window.Current.Content, etc. etc. Se invece rootFrame è diverso da null, significa che la mia app è già aperta. Per evitare di scrivere tutta la logica di riconoscimento in App.xaml.cs – davvero brutto – ho adottato questa soluzione: sia in un caso che nell’altro, grazie al Messenger di MvvmLight spedisco un messaggio di tipo TellMeMedalWinnerMessage. Questo è un passaggio molto importante. Il messaggio viene di conseguenza intercettato dal viewmodel che governa la UI, in questo modo:

// Nel costruttore
Messenger.Default.Register<TellMeMedalWinnerMessage>(this, GetMedalWinner);

// Funzione
private async void GetMedalWinner(TellMeMedalWinnerMessage message)
{
// invoco un servizio?
}

Nel costruttore del viewmodel registro quel messaggio, ed imposto il delegate da eseguire quando quel particolare messaggio viene ricevuto. La logica è tutta contenuta nel metodo GetMedalWinner: non fa altro che riceve in input un oggetto di tipo TellMeMedalWinnerMessage, che è una classe anemica implementata in questo modo:

class ScoreboardPositionMessage : BaseMessage
{
public string Phrase { get; set; }

public ScoreboardPositionMessage(string phrase)
{
this.Phrase = phrase;
}
}

Questa classe contiene solamente il testo della frase pronunciata dall’utente della nostra app. Adesso sta a noi decidere cosa farne. Possiamo elaborarla sul client Windows 10, oppure possiamo invocare un servizio remoto che elabora la stringa, consulta un database e restituisce un risultato al client.

Ci sono altre considerazioni da fare (grammatica, come gestire frasi libere oppure comandi più precisi, gestire più lingue): saranno tema di un prossimo post che scriverò nei prossimi giorni.

Alla prossima!!

Send to Kindle
.NET World

Impostazioni di un’app UWP

Ho scritto in questi giorni una semplice classe SettingsHelper che permette di leggere od impostare un valore di un setting relativamente ad una Universal Windows App per Windows 10.

Il codice è il seguente:

static class SettingsHelper
{
public static T GetValue<T>(string name, T defaultValue)
{
return GetValue<T>(ApplicationData.Current.LocalSettings, name, defaultValue);
}

public static T GetValue<T>(ApplicationDataContainer container, string name, T defaultValue)
{
object obj = null;
T result = default(T);

bool canRead = container.Values.TryGetValue(name, out obj);

if (canRead)
{
result = (T)obj;
}

return result;
}

public static void SetValue(string name, object value)
{
ApplicationData.Current.LocalSettings.Values[name] = value;
}

public static void SetValue(ApplicationDataContainer container, string name, object value)
{
container.Values[name] = value;
}
}

L’utilizzo è molto semplice. E’ statica e quindi non va istanziata. Dal punto di vista del consumatore della classe la cosa avviene in modo molto trasparente. Se un certo setting esiste, allora ne viene restituito l’effettivo valore, altrimenti viene restituito un valore di default preventivamente passato in input all’atto della chiamata. Ad esempio:

// Get
var autoUpdate = SettingsHelper.GetValue<bool>("AutoUpdate", false);
int page = SettingsHelper.GetValue<int>("Page", -1);
int hs = SettingsHelper.GetValue<int>(
ApplicationData.Current.RoamingSettings,
"HighScore", 0);
SettingsHelper.GetValue<DateTime>(
ApplicationData.Current.RoamingSettings,
"LastUpdate", DateTime.MinValue);

// Set
SettingsHelper.SetValue("AutoUpdate", true);
SettingsHelper.SetValue("Page", 9);
SettingsHelper.SetValue(ApplicationData.Current.RoamingSettings, "OrderCriteria", "4");
SettingsHelper.SetValue(ApplicationData.Current.RoamingSettings, "AutoSave", true);

L’utilizzo dei generics evita casting sparsi un po’ ovunque. E grazie all’utilizzo di questa SettingsHelper possiamo evitarci l’utilizzo di TryGetValue, potenziali valori null nel caso in cui un certo setting non esiste, if e test.

Ho scritto anche degli overload sia per il SetValue che per il GetValue, perchè di default la classe accede ai LocalSettings dell’app, mentre così è possibile accedere anche ai RoamingSettings.

Chiaccherata sui LocalSettings e RoamingSettings

Colgo l’occasione per ricordare che i LocalSettings sono impostazioni locali dell’app, salvate sul dispositivo Windows che stiamo utilizzando. I RoamingSettings invece sono impostazioni dell’app che vengono automaticamente replicate su tutti gli altri nostri device a cui accediamo con lo stesso Microsoft Account. Diciamo che possiamo definire i LocalSettings come impostazioni per-device, mentre i RoamingSettings sono per-user.

A cosa possono servire? Nelle app che ho pubblicato negli anni scorsi, per Windows Phone e Windows, ho utilizzato i LocalSettings per memorizzare tutti quei settings che possono cambiare da un device all’altro, chiaramente per la stessa app. Ad esempio, impostazioni relative a grafica o performance. Vi faccio un esempio. Supponiamo di avere un’app che permetta di vedere la traccia di un volo aereo; questa traccia può essere più o meno precisa in base alla densità dei punti che si decide di scaricare da Internet e successivamente di renderizzare sullo schermo. Quindi su un device a basse performance potrei decidere di sacrificare precisione ed accuratezza per avere più velocità e consumi minori. Su un desktop potrei decidere invece di vedere la traccia di volo con la massima precisione possibile. I LocalSettings sono proprio lo strumento più adatto per scenari di questo tipo: supponendo che la precisione sia espressa con un setting chiamato “FlightLogTraceAccuracy”, di tipo int, questo andrebbe memorizzato nei LocalSettings, in modo che su smartphone abbia un valore, sul desktop un altro. Oppure, ancora, l’utente potrebbe decidere di attivare le push notification su un device mentre su un altro no, e quindi ancora una volta i LocalSettings sono il posto migliore.

Nei RoamingSettings, d’altro canto, andrei a memorizzare tutti questi settings indipendenti dal device, ma associati all’app. Ad esempio, il mio nome, la mia data di nascita, il peso, il punteggio massimo di un gioco, e così via. Indipendentemente dal device che sto utilizzando, quelle informazioni sono sempre le stesse.

Send to Kindle
Software

TinyTake: cattura schermate e registrazione video

L’altra sera mi sono accorto che per dimostrare l’efficacia della Adaptive UI di Windows 10 era molto meglio registrare un breve video, giusto per mostrare cosa accadesse alla UI quando la finestra veniva ridimensionata. Uno screenshot non rende l’idea. Così ho navigato un po’ alla ricerca di un software freeware per registrare un video del mio schermo, allo scopo di uploadarlo su YouTube e poi di renderlo visibile attraverso il mio blog. Di strumenti di questo tipo ce ne sono tanti, ma l’altro giorno mi sono imbattuto in TinyTake, ottimo freeware scaricabile da qui.

tiny_take

Pro:

  • ha una bella UI, che non guasta mai
  • cattura immagini da: porzioni dello schermo, finestre, full-screen o webcam
  • registra video del vostro deskop o dalla vostra webcam
  • pubblica direttamente e senza troppi sforzi su YouTube, previo login ed autorizzazione

Contro:

  • richiede la creazione di un account TinyTake
  • video di massimo 5 minuti (se c’è bisogno di più tempo dovete invitare amici e fare in modo che questi si logghino davvero)
  • non è possibile registrare un video senza la componente audio (io ho impostato un microfono e nelle opzioni di Windows ho messo a zero tutto il volume)

Direi che fa al caso mio. Promosso!

Send to Kindle
.NET World

La parte server dell’invio di una push notification con una tile ed un’immagine dinamica

Nei giorni scorsi mi sono imbattuto in un problema che mi ha fatto perdere tantissimo, ma davvero tantissimo tempo, problema che non ho visto risolto da nessuna parte. Spero con questo articolo di aiutare tutti coloro che in futuro potranno ricadere nello stesso scenario.

Sto parlando di inviare tramite push notification una nuova tile relativa ad un’app Windows 10, quindi UWP. Ma facciamo un passo indietro.

Il blocco XML più semplice che un server deve inviare al client per poter aggiornare una tile è:

<tile>
<visual version="2">
<binding template="TileWide310x150Image">
<image id="1" src="http://server/nomeimmagine.png"/>
</binding>
</visual>
</tile>

Ci sono tanti template da poter utilizzare, chiaramente. Questo è il template TileWide310x150Image, che invia al client le informazioni minime per aggiornare solo la tile rettangolare 310×150. Il parametro src indicato nell’XML è semplicemente l’url della nuova immagine. Nulla di particolarmente complicato. Invio un’immagine ogni volta diversa, e quindi la tile su ogni client si aggiorna.

Il problema arriva quando invece di puntare ad un’immagine fissa (jpg o png, per esempio), voglio puntare un qualche url che invece è una Action di ASP.NET MVC, Action che restituisce un’immagine sotto forma, ovviamente, di binario.

Immaginate di avere ad esempio un codice server simile al seguente:

public ActionResult RandomBackground()
{
MemoryStream ms = new MemoryStream();
Image image = new Bitmap(320, 240);

using (Graphics drawing = Graphics.FromImage(image))
{
Random rnd = new Random((int)DateTime.Now.Ticks);
int r = rnd.Next(0, 256);
int g = rnd.Next(0, 256);
int b = rnd.Next(0, 256);
Color c = Color.FromArgb(255, r, g, b);
drawing.Clear(c);

image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
}

return File(ms.ToArray(), "image/png");
}

Questa è una Action di ASP.NET MVC, quindi stiamo ragionando sulla parte server dell’invio di una push notification. Questa Action risponde ad un url simile al seguente…

http://server/controller/RandomBackground

e non fa altro che restituire ogni volta un’immagine 320×240 di colore diverso, ad ogni colpo di F5. Supponiamo quindi di inviare una push notification come questa:

<tile>
<visual version="2">
<binding template="TileWide310x150Image">
<image id="1" src="http://server/controller/RandomBackground"/>
</binding>
</visual>
</tile>

Sui dispositivi Windows 8 / 8.1 / 10 questa push notification ha un comportamento ben particolare. La prima volta la notifica arriva, il device la riceve ed aggiorna la tile. Fin qua nulla di strano. Dalla seconda volta in poi la tile non si aggiorna più. Perchè? Deduco, dopo infinite prove & test, perchè l’url della tile è sempre lo stesso, e quindi il device reputa che sia inutile aggiornarla, perchè interpreta che sia sempre la stessa tile. Peccato, per il ragionamento di cui sopra, che in realtà l’url punti ad un contenuto dinamico generato server-side, e quindi DEVE aggiornarla, anche se l’url è lo stesso.

Sono inoltre giunto alla conclusione che il meccanismo di ricezione di push notification sul client ignori tutti i parametri passati in querystring, così url come questi sono esattamente gli stessi:

http://server/controller/RandomBackground?index=534543
http://server/controller/RandomBackground?a=blablabla
http://server/controller/RandomBackground?guid=589353985u39835u49

Per risolvere, bisogna effettivamente puntare ad un url differente. La firma della Action insomma deve diventare qualcosa del genere:

public ActionResult RandomBackground(string id)
{
// codice
}

Il parametro id non ci serve, ma a questo punto noi possiamo invocare l’Action in questo modo:

http://server/controller/RandomBackground/34
http://server/controller/RandomBackground/aaaa
http://server/controller/RandomBackground/fkldglkdfm34
http://server/controller/RandomBackground/;ert;54353

Gli url qui puntano alla Action RandomBackground, passando diversi valori nel parametro id, valori che vengono bellamente ignorati, ma che semplicemente servono a scatenare effettivamente l’aggiornamento della tile. Cosa significa questo? Significa che server-side, in fase di preparazione dell’XML da inviare a ciascun client, l’url deve essere generato ogni volta diverso, quindi giocare con System.Random per fare una cosa del genere:

string tileUri = string.Format("http://server/controller/RandomBackground/{1}",
new Random((int)DateTime.Now.Ticks).Next(1, 999999));

L’url della tile, insomma, viene generato random ogni volta, mandato al client, che lo aggiorna senza farsi troppe domande. Ok, primo step risolto.

Ulteriori complicazioni

Fin qua, volendo, è tutto semplice, perchè la Action RandomBackground che abbiamo ipotizzato negli esempi qui sopra non ha bisogno di veri parametri di input. Supponiamo che la generazione della tile abbia a che fare con il meteo, e che la Action debba restituire l’immagine di una tile contenente le previsioni della provincia passata in input fra i parametri. Ad esempio:

http://server/controller/Meteo/Lodi
http://server/controller/Meteo/Roma
http://server/controller/Meteo/Lecce
http://server/controller/Meteo/Ancona

Ma torniamo ai discorsi di prima. Se inviassi questo url ad un device Windows 10, la prima volta la tile verrebbe aggiornata, dalla seconda volta in poi no. E quindi dobbiamo generare url ogni volta sempre diversi, accodando un secondo parametro fittizio come qui sotto:

http://server/controller/Meteo/Lodi/5437534
http://server/controller/Meteo/Roma/989
http://server/controller/Meteo/Lecce/1123
http://server/controller/Meteo/Ancona/9043

Ovviamente i numeri sono sparati a caso. Per ottenere questo, bisogna modificare le regole di routing di ASP.NET MVC, per indicare che nell’url possiamo indicare un parametro aggiuntivo opzionale. Quindi quello che ho fatto è stato andare nel file RouteConfig.cs e scrivere quanto segue:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}/{key}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional,
key = UrlParameter.Optional }
);
}

Cioè: tutti gli url possono contenere un parametro {key} opzionale. Di conseguenza, la firma della nostra Action Meteo diventa:

public ActionResult Meteo(string id, string key)
{
// Codice
}

All’interno del parametro id avremmo la provincia passata. All’interno del parametro key arriverebbe un secondo parametro, generato ogni volta casualmente, bellamente ignorato, inserito al solo scopo di avere un url ogni volta differente.

Conclusioni

Direi che è tutto. Bel problema, non c’è che dire.

Finalmente risolto, e le mie tile arrivano puntuali e precise.

Send to Kindle