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.