Silverlight 4 e la possibilità di navigare su Page definite in un assembly separato

Introduzione
Sappiamo tutti che Silverlight 4 ha un meccanismo di navigation interno. A cosa serve? Il tutto si basa sul controllo Frame, che può contenere oggetti Page. Questi oggetti Page possono contenere a loro volta contenere tutto ciò che vi può passare per la testa, quindi testo, immagini, altri controlli, animazioni, etc. etc.

Perchè si chiama navigation? Perchè dietro le quinte Silverlight fa un pochino di cose interessanti, prima fra tutte la history delle Page che avete visitato. Attraverso l’oggetto NavigationService, quindi, potete chiamare metodo come GoBack(), GoForward(), accedendo quindi a tutte le Page già visitate dall’utente.

Maggiori informazioni sull’argomento a questo indirizzo.

Concetti base
Ok, supponiamo di aver creato una semplice SilverlightApplication1, hostata da un progetto SilverlightApplication1.Web. Sulla MainPage.xaml creiamo un controllo Frame:

<UserControl x:Class="SilverlightApplication1.MainPage"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 xmlns:nav="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
 mc:Ignorable="d"
 d:DesignHeight="300" d:DesignWidth="400">
 <nav:Frame Background="LightCyan" Source="/Page1.xaml">
 </nav:Frame>
</UserControl>

 

Il controllo Frame è definito nell’assembly System.Windows.Controls.Navigation, quindi ho dovuto dichiarare il namespace ‘nav’. Nulla di anormale fin qua.

Notare la proprietà Source del controllo, impostata su “/Page1.xaml”. Questo significa che nella stessa directory dove abbiamo la MainPage.xaml dobbiamo aggiungere una Silverlight Page, chiamarla Page1.xaml, e definirne il contenuto. Supponiamo di aver creato una pagina così:

<navigation:Page x:Class="SilverlightApplication1.Page1" 
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 mc:Ignorable="d"
 xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
 d:DesignWidth="640" d:DesignHeight="480"
 Title="Page1 Page">
 <TextBlock Text="Page 1" FontSize="56" />
</navigation:Page>

 

La pagina contiene una semplice TextBlock con scritto “Page 1”. In questo momento, quindi, se avviate il progetto la prima cosa che vedete è:

NavigationPage1

Impostazione della proprietà UriMapper del Frame
Ritorniamo sul valore della proprietà Source del Frame. In questo momento, esso rappresenta a tutti gli effetti il path fisico del file .xaml che vogliamo caricare e renderizzare. E’ possibile però "giocare” con la proprietà UriMapper del Frame, in modo tale da “convertire” un url logico in uno fisico. Supponiamo di impostarla come segue:

<nav:Frame Background="LightCyan" Source="/Page1">
 <nav:Frame.UriMapper>
 <uriMapper:UriMapper>
 <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/{pageName}.xaml" />
 </uriMapper:UriMapper>
 </nav:Frame.UriMapper>
</nav:Frame>

 

Notare l’import del namespace uriMapper, definito come segue:

xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"

 

Cosa succede adesso? Succede che Source adesso vale “/Page1”, e ci pensa l’UriMapper a convertirlo nel vero Uri che punta al file .xaml. Tutte le volte che chiediamo una pagina nella forma “/PaginaElencoOrdini”, l’uri effettivo diventerà “/PaginaElencoOrdini.xaml”. Ovviamente possiamo sfruttare questa caratteristica in modo più intelligente, per esempio mettendo tutti i files di Page in una cartella e lasciare che l’UriMapper li recuperi per noi. Maggiori informazioni a questo indirizzo.

Ed ora la parte più “difficile”: navigare su pagine definite in un assembly esterno
Lo so, sono blog-orroico: questo è il tema principale di questo post, e ci sono arrivato solo alla fine. Supponiamo di voler aggiungere un nuovo progetto – SilverlightClassLibrary1.Pages – contenente solo oggetti di tipo Page. Vogliamo navigare su queste pagine, e raggiungerle attraverso il controllo Frame.

La vera domanda è questa: quale url devo utilizzare? Qual’è l’url corretto che punta ad un file Page1.xaml definito nell’assembly SilverlightClassLibrary1.Pages?

La risposta, una volta trovata la soluzione, è abbastanza semplice. E’ bastato trovare la pagina Pack URIs in WPF. Definiamo il controllo Frame come segue:

<nav:Frame Background="LightCyan" Source="/Page1">
 <nav:Frame.UriMapper>
 <uriMapper:UriMapper>
 <uriMapper:UriMapping Uri="/External/{pageName}"
 MappedUri="/SilverlightClassLibrary1.Pages;component/{pageName}.xaml" />
 <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/{pageName}.xaml" />
 </uriMapper:UriMapper>
 </nav:Frame.UriMapper>
</nav:Frame>

 

Abbiamo aggiunto un UriMapping nuovo. Se la pagina viene richiesta nella forma “/External/{pageName}”, il Mapped Uri viene risolto così:

/SilverlightClassLibrary1.Pages;component/{pageName}.xaml

 

Quello che dobbiamo fare poi è aggiungere il riferimento a SilverlightClassLibrary1.Pages nel progetto SilverlightApplication1. Fatto questo, abbiamo terminato.

In pratica, la pagina viene cercata nell’assembly SilverlightClassLibrary1.Pages. Adesso basta reimpostare il Source su “/External/Page1” per caricare la pagina dall’altro assembly. Ovviamente prima di eseguire dobbiamo andare nell’altro assembly e definire anche lì una Page1.xaml – magari con un contenuto diverso rispetto a quella precedente – per ottenere quanto segue:

NavigationPage2

Missione compiuta!

Il codice sorgente, se proprio volete vederlo – è scaricabile da qui (86 Kb).

Send to Kindle

Ancora su Silverlight 4 ed il caricamento del ViewModel dinamico con MEF

Rispetto al mio post precedente, le cose si sono fatte più serie. Nel tentativo di tranquillizzare Roberto :-), e nel tentativo di dare una risposta a Corrado, mi sono immaginato uno scenario più “complesso”, che riassumo così:

  1. L’applicazione Silverlight parte: la UI è minimale, ed il corrispondente ViewModel sottostante espone un sottoinsieme limitato di funzionalità
  2. Appena dopo l’avvio, MEF comincia a fare il suo sporco lavoro, risolve le dipendenze ed inietta nell’applicazione il ViewModel reale

Adesso vediamo di rendere più chiare le cose. Supponiamo di avere un’app Silverlight che nel browser si presenta in questo modo:

DynamicViewModel01

Nulla di particolarmente evoluto, no? Quella che vedete qui sopra è contenuto in MainPage.xaml. Ho aggiunto nelle Resources di questo UserControl un’istanza di InitialViewModel:

 <UserControl.Resources>
 <ivm:InitialMainViewModel x:Key="InitialViewModel" />
 </UserControl.Resources>

La classe InitialViewModel ha le seguenti caratteristiche:

  1. Eredita da Galasoft.MvvmLight.ViewModelBase
  2. Implementa l’interfaccia IMainViewModel, che è definita così:
public interface IMainViewModel
{
 string Message { get; set; }
 RelayCommand NewCommand { get; set; }
 RelayCommand ListCommand { get; set; }
 RelayCommand MailCommand { get; set; }
 RelayCommand LoginCommand { get; set; }
}

La stringa Message è quella che vedete sulla UI – “Attendere prego…inizializzazione in corso!”. I quattro command sono bindati ai quattro pulsanti che vedete sulla UI.

Piccola precisazione: l’idea è quella secondo la quale l’applicazione parte con un ViewModel minimale, così l’utente non vede una pagina bianca, ma può comunque interagire in qualche modo. Per questo motivo, all’inizio se l’utente clicca su uno qualsiasi dei pulsanti, appare il seguente messaggio:

DynamicViewModel02

L’unica differenza è la pressione del pulsante Login. Questo pulsante comincia a far lavorare MEF. L’execute del LoginCommand è definito come segue:

void loginCommandExecute()
{
 ViewModelLocator.Current.ResolveDependencies();
}

In pratica, quindi, la pressione di New/List/Mail dice all’utente di attendere, mentre la pressione di Login fa qualcosa di diverso. E qui entra in ballo ancora il ViewModelLocator, che oggi ho reso singleton, e ne invoco il metodo ResolveDependencies, che fa quanto segue:

public void ResolveDependencies()
{
 Uri uri = new Uri(this.Parameters["ViewModelAssembly"]);
 
 WebClient client = new WebClient();
 client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
 client.OpenReadAsync(uri);
}

Leggo un Uri dal web.config (secondo la tecnica descritta qui), e comincio il download asincrono. Quando il download è completato…

void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
 if (e.Error != null) return;
 
 AssemblyPart assemblyPart = new AssemblyPart();
 Assembly pluginAssembly = assemblyPart.Load(e.Result);
 
 AssemblyCatalog ac = new AssemblyCatalog(pluginAssembly);
 CompositionContainer container = new CompositionContainer(ac);
 container.SatisfyImportsOnce(this);
}

 

L’Uri deve puntare ad un assembly .NET, che deve contenere una classe che implementa IMainViewModel. Quello che accade qui è che l’assembly scaricato viene caricato in un’istanza di Assembly, poi viene creato un AssemblyCatalog. Alla fine viene invocato il metodo SatisfyImportsOnce del CompositionContainer, che non fa nient’altro che fare il match tra [Export] ed [Import] di MEF. Viene quindi risolta la dipendenza della proprietà Main del ViewModelLocator:

[Import]
public IMainViewModel Main
{
 get { return _mainViewModel; }
 set
 {
 _mainViewModel = value;
 UserControl control = Application.Current.RootVisual as UserControl;
 injectViewModel(control, _mainViewModel);
 }
}

 

Quindi, una volta che il download dell’assembly è terminato, non faccio altro che prende lo UserControl principale dell’applicazione, e vado a reimpostarne il DataContext, attraverso la chiamata a injectViewModel(UserControl, object).

Morale: il messaggio ed i pulsanti sulla UI non dicono più di attendere, ma grazie al nuovo ViewModel iniettato da MEF dinamicamente, cominciano a “funzionare” veramente:

DynamicViewModel03

Punti di forza di questo approccio:

  1. Supponiamo che l’assembly scaricato sia http://www.mywebsite/viewmodel/viewmodel_10.dll – ebbene, nessuno degli assembly che compone l’applicazione principale ha un riferimento a questa dll – e ci mancherebbe!
  2. Modificando il web.config posso determinare quale assembly viene scaricato, e quindi di fatto assegnare diversi ViewModel , e quindi comportamenti diversi. Mi spiego meglio: nell’esempio qui sopra, il NewCommand del nuovo ViewModel non fa altro che mostrare una MessageBox con “New!”. Poco utile! 🙂 Ma basta scrivere una nuova classe che implementi IMainViewModel, deployarla da qualche parte, modificare il web.config e di fatto si deploya una nuova applicazione – UI sempre uguale, ma comportamento differente.
  3. L’idea finale non è tanto che l’utente debba premere Login per risolvere le dipendenze – in realtà, tutto questo meccanismo potrebbe partire anche all’avvio. Quindi di fatto l’utente raggiungere l’url, aspetta qualche secondo per il download della dll e vede la UI refresharsi adeguandosi al nuovo ViewModel.
  4. Per rispondere esplicitamente a Corrado, cosa mi impedisce di mettere nel ViewModelLocator una bella new() che ritorna l’istanza di IMainViewModel? Diverse cose: una new() richiede una reference esplicita all’assembly, annullando di fatto tutti i vantaggi descritti prima.
  5. Supporto a design-time? Il ViewModelBase di Galasoft.MvvmLight espone una proprietà IsInDesignTime, che posso utilizzare per capire se sono in IDE oppure no. E poi c’è un altro vantaggio: l’applicazione a design-time vede InitialViewModel, e quindi di fatto a design-time gira questo viewmodel. Tant’è che se apro la MainPage.xaml in Visual Studio 2010, vedo quanto segue:

DynamicViewModel04

Quindi anche a design-time ho un ViewModel attivo a tutti gli effetti (InitialViewModel), che è quello che verrà sostituito a run-time da MEF.

Che ne dite?

Il codice è liberamente scaricabile da qui (406Kb).

Send to Kindle

Leggere i settings contenuti nel web.config da un’applicazione Silverlight 4

E’ l’esigenza più vecchia del mondo, cioè inserire da qualche parte le impostazioni della nostra applicazione, ed ovviamente andare poi a leggerli per modificare il comportamento dell’applicazione stessa. Ho trovato questo articolo su Codeproject che spiega come risolvere questa esigenza: l’articolo si basa su Silverlight 3 – io con il 4 ho trovato qualche differenza minimale, che comunque non mi ha bloccato più di tanto. Ecco i passi da fare.

1) Aggiungere nel web.config uno o più parametri
Dietro ogni progetto Silverlight che si rispetti, c’è sempre un progetto Web che lo deploya! 🙂 In questo progetto Web avete già bello & pronto il file web.config, per cui è sufficiente aprirlo ed inserire tutte le impostazioni che vi servono. Ad esempio:

<?xml version="1.0"?>
<configuration>
 <appSettings>
 <add key="Language" value="it-IT" />
 </appSettings>
 <system.web>
 <compilation debug="true" targetFramework="4.0" />
 </system.web>
</configuration>

 

La cosa è molto semplice: ho aggiunto un setting con key=”Language” e value=”it-IT”.

2) Modificare la pagina aspx che carica e renderizza il plugin Silverlight
Qui la cosa è un po’ diversa da come viene raccontata nell’articolo linkato prima. All’interno del progetto Web avete sicuramente una pagina aspx, che poi è quella che viene caricata nel browser, ed è quella che contiene il plug-in Silverlight, etc. etc. E’ necessario modificare questa pagina, ed il suo relativo code-behind. Ma c’è un problema: la pagina aspx che viene creata di default quando create un progetto Silverlight non ha code-behind. Io ho risolto facendo così:

2a) Aggiunta di una nuova pagina Startup.aspx
Nulla di particolare, qui. Da VS2010, ho fatto semplicemente Add New Item –> Web –> WebForm, e ho dato il nome Startup.aspx.

2b) Ho copiato & incollato il contenuto della pagina .aspx originale a questa nuova Startup.aspx
Dunque, supponiamo che il progetto Silverlight su cui state lavorando si chiami Pippo. La pagina aspx dovrebbe presentarsi nella forma PippoTestPage.aspx. Ho copiato tutto il codice aspx da questa pagina alla pagina aspx nuova, ovvero Startup.aspx.

2c) Modifica di Startup.aspx (aggiunta del controllo Literal)
All’interno del tag <object></object> di Silverlight ho aggiunto questo controllo:

<asp:Literal ID="ParamInitParams" runat="server"></asp:Literal>

 

2d) Modifica di Startup.aspx (modifica della direttiva @Page)
Questo è il motivo per cui ho dovuto creare una nuova pagina Startup.aspx: perchè qui abbiamo il code-behind, in quella originale no. Ma c’è ancora qualcosa da sistemare: è vero che fisicamente il file Startup.aspx.cs, ma è anche vero che i files Startup.aspx <—> Startup.aspx.cs non sono legati. Quindi, ho modificato la direttiva @Page di Startup.aspx, aggiungendo gli attributi CodeBehind e Inherits. Ecco come si presenta il tutto:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Startup.aspx.cs" Inherits="ViewModelWithMEF.Web.Startup" %>

 

Questo serve, altrimenti dal code-behind ovviamente non riusciamo ad accedere al controllo Literal definito allo step precedente.

2e) Modifica del code-behind Startup.aspx.cs
Fate così: semplicemente sostituite il code-behind con il seguente:

 public partial class Startup : System.Web.UI.Page
 {
 private void SaveSilverlightDeploymentSettings(Literal litSettings)
 {
 NameValueCollection appSettings = ConfigurationManager.AppSettings;
 
 StringBuilder SB = new StringBuilder();
 SB.Append("<param name=\"InitParams\" value=\"");
 
 int SettingCount = appSettings.Count;
 for (int Idex = 0; Idex < SettingCount; Idex++)
 {
 SB.Append(appSettings.GetKey(Idex));
 SB.Append("=");
 SB.Append(appSettings[Idex]);
 SB.Append(",");
 }
 SB.Remove(SB.Length - 1, 1);
 SB.Append("\" />");
 
 litSettings.Text = SB.ToString();
 }
 
 
 protected void Page_Load(object sender, EventArgs e)
 {
 Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
 SaveSilverlightDeploymentSettings(this.ParamInitParams);
 }

 

Non scendo nel dettaglio per mancanza di competenza! 🙂 Nell’evento Load della Page viene eseguito il metodo privato SaveSilverlightDeploymentSettings, al quale viene passato il controllo ParamInitParams, che è il controllo Literal definito allo step 2c.

3) Accesso del web.config dall’applicazione Silverlight
Tranquilli, siamo arrivati alla fine. Dopo tutto questo giro, possiamo tranquillamente andare a gestire l’evento Startup dell’applicazione Silverlight, all’interno del file App.xaml.cs. Esso può essere una cosa simile a questa:

private void Application_Startup(object sender, StartupEventArgs e)
{
 IDictionary<string, string> webConfigSettings = e.InitParams;
 string language = webConfigSettings["Language"];
 this.RootVisual = new MainPage();
}

 

La classe StartupEventArgs espone la proprietà InitParams, che non facciamo altro che andare a leggere per impostare un Dictionary di sole stringhe. Ovviamente il Dictionary come l’ho definito qui sopra ha poco senso, perchè esce immediatemente dallo scope: è sufficiente avere una classe di impostazioni singleton, statica, o qualsiasi altra soluzione, in modo tale che l’applicazione possa accedere ai settings definiti nel web.config ovunque ci sia bisogno.

Essendo un Dictionary, è sufficiente scrivere webConfigSettings[“Language”] per accedere al suo valore, così come è stato definito allo step (1).

Direi che è un giro un po’ lungo da mettere in piedi la prima volta, ma una volta preparato il tutto, alla fine viene tutto gratis. Va da sè infatti che se vado successivamente nel web.config ad aggiungere altri parametri, questi “arrivano” all’applicazione Silverlight senza toccare più nulla.

Send to Kindle

Silverlight4: iniettare il ViewModel in una vista con l’utilizzo di MEF

Qualche giorno fa un certo Alessandro Scardova ha postato un link ad un articolo di un certo Thomas Martinsen, che mostrava come caricare l’istanza del ViewModel attraverso il Managed Extensibility Framework (per gli amici MEF). La cosa è sicuramente interessante, perchè non andiamo più a “cablare” il fatto che sulla MainPage.xaml deve essere utilizzata la classe MainViewModel. Questa dipendenza viene infatti iniettata, attraverso l’utilizzo degli attributi [Import] da una parte, ed [Export] dall’altra. Questi due attributi sono contenuti nell’assembly System.ComponentModel.Composition.dll, installato automaticamente – ricordiamolo – con il .NET Framework 4.

La soluzione, ripeto, è interessante, ma non mi piace più di tanto, perchè (cito dall’articolo originale di Thomas) “In the code-behind of the MainPage.xaml file hosting the view, I have created a property called ViewModel.” Insomma, è necessario aggiungere una proprietà pubblica della classe MainPage, agendo quindi sul file MainPage.xaml.cs, ovvero il code-behind, e quindi vanificando un po’ lo sforzo di adottare M-V-VM. Non voglio fare il fanatico, se c’è da toccare il code-behind facciamolo pure, ma consci del fatto che potrebbero correre dei rischi se il file viene toccato da altre persone, che magari per un motivo o per l’altro cancellano il nostro codice.

Ma la soluzione ovviamente c’è, e sono lieto di proporvela.

L’idea che ho avuto è quello di creare una classe ViewModelLocator, definita come segue:

public class ViewModelLocator
{
 private MainViewModel _mainViewModel;
 
 public ViewModelLocator()
 {
 CompositionInitializer.SatisfyImports(this);
 }
 
 [Import]
 public MainViewModel Main
 {
 get { return _mainViewModel; }
 set { _mainViewModel = value; }
 }
 
 public void ClearMain()
 {
 _mainViewModel.Cleanup();
 _mainViewModel = null;
 }
 
 public void Cleanup()
 {
 ClearMain();
 }
}

 

Non è tutta farina del mio sacco. Ho trascorso questa domenica pomeriggio a studiare il MVVM Light Tookit, di un certo Laurent Bugnion. Questa classe ViewModelLocator è la classe che vi trovate quando create un nuovo progetto usando questo tookit. Io l’ho solo ripulita un po’ apposta per questo esempio. Lo scopo di questa classe è quello di esporre – tramite public properties – tutti i ViewModel necessari al nostro progetto. Data la semplicità, in questo momento il ViewModel è uno solo, MainViewModel, ed è marcato con l’attributo [Import]. Questo significa che l’engine di MEF dovrà cercare ed iniettare la dipendenza, e lo fa con la chiamata a CompositionInitializer.SatisfyImports(this) che vedete nel costruttore della classe stessa.

A questo, basta andare nell’App.xaml ed aggiungere nelle Resources un’istanza di ViewModelLocator, come ho fatto qui sotto:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
 x:Class="MvvmLight1.App"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 xmlns:vm="clr-namespace:MvvmLight1.Services;assembly=MvvmLight1.Services"
 mc:Ignorable="d">
 <Application.Resources>
 <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
 </Application.Resources>
</Application>

 

Ho dichiarato il prefix “vm” ed ho istanziato ViewModelLocator.

A questo punto rimane solo un’ultima cosa: creare il MainViewModel vero e proprio, che decoreremo con l’attributo [Export] per informare l’engine di MEF. Supponiamo di scrivere quanto segue:

[Export(typeof(MainViewModel))]
public class MainViewModel : ViewModelBase
{
 public string Welcome
 {
 get
 {
 return "Welcome to MVVM Light";
 }
 }
 
 public MainViewModel()
 {
 if (IsInDesignMode)
 {
 // Code runs in Blend --> create design time data.
 }
 else
 {
 // Code runs "for real"
 }
 }
}

 

Questa è una normalissima classe ViewModel, che eredita da GalaSoft.MvvmLight.ViewModelBase. Notate – appunto – l’aggiunta dell’attributo [Export]. A questo punto, cosa accade? Accade che questo [Export] e l’[Import] messo sul ViewModelLocator si accoppiano fra loro, e MEF è quindi in grado di risolvere la dipendenza. Notate la presenza della public property Welcome sul ViewModel: è di tipo stringa, quindi possiamo bindarla ad una normale TextBlock sulla UI.

Consiglio dell’ultimo minuto :  provate a mettere un breakpoint sul costruttore del ViewModelLocator, dove viene chiamato CompositionInitializer.SatisfyImports(), cioè la risoluzione delle composizioni da parte di MEF. Vedrete che prima della chiamata this.Main è null, mentre dopo la chiamata this.Main vale l’istanza del ViewModel marcata con Export.

Ed il tutto senza toccare il code-behind dello UserControl o della Page!!!
Questo sì che è vero MVVM!!!!

Send to Kindle

[Mercurial.7] Pubblicare un nuovo progetto su un repository Mercurial disponibile via http

Nel post di questa mattina abbiamo spiegato come ricevere via http il contenuto di un repository Mercurial pubblicato sul Web, modificarlo e successivamente aggiornare lo stesso repository.

Adesso invece facciamo esattamente il contrario, cioè:

  1. Creiamo un nuovo progetto con Visual Studio 2010, di qualsiasi tipo esso sia
  2. Su questo nuovo progetto creiamo un repository Mercurial
  3. Pubblichiamo il nostro repository, che fino ad adesso era solamente sul nostro PC, su un server Web adibito alla pubblicazione di repository Mercurial

Sui punti (1) e (2) ho poco da aggiungere. Il punto (1) in particolare prevede che siate programmatori .NET con più o meno esperienza :-), mentre per il punto (2) è sufficiente leggere i miei post precedenti su Mercurial, o qualsiasi altro tutorial disponibile su Internet.

Quindi, diciamo, ad un certo punto avrete una situazione simile a questa:

Ovvero, una cartella contenente un repository Mercurial, perfettamente sincronizzato. Ovviamente si spera che abbiate qualcosa di più interessante di un progetto chiamato “ConsoleApplicationMercurial”. Supponiamo che questo fantastico progetto si chiami TeamsEditor. Cosa occorre fare per pubblicare questo repository sul Web? E’ ora di rispondere a questa domanda.

Server Mercurial
Innanzitutto, sappiate che il server Mercurial espone via http tutti i suoi repository. E’ possibile configurare IIS per farlo, o qualsiasi altro tipo di web server. Ma non sono qui per parlarvi di questo. Il modo più semplice per avere un server Web adibito alla pubblicazione di repository Mercurial è quello di avere qualcun’altro che lo fa per voi: banalmente, andate su http://www.bitbucket.org/, registratevi e potete creare tutti i progetti che volete.

Ad esempio, io ho attivato questi due progetti:

Precisazione: ovviamente, quando andate su BitBucket e create un progetto, esso apparirà completamente vuoto, senza alcun file. Noi non dobbiamo fare alto che prendere il nostro repository locale e fare un Push verso il server. Per farlo, è sufficiente eseguire i seguenti passi:

  • Dall’interno di Visual Studio 2010, cliccate col destro ed andate su HG History (oppure da Windows Explorer cliccate col destro sulla cartella ed andate su HG Respository Explorer).
  • In entrambi i casi, sullo schermo apparirà il tool Repository Explorer

mercurial_repo_explorer

  • Vi ho evidenziato con un rettangolo fucsia l’unico campo che va impostato, ovvero l’url del respository remoto su cui vogliamo pubblicare

Giusto per curiosità, l’elenco di tutti i commit che avete fatto appare in questa schermata, con il commento che avete inserito, con data & ora, e con il nome di chi l’ha fatto. Selezionando un changeset, potete avere maggiori dettagli: quali files sono stati aggiunti/modificati/cancellati, ed in che modo.

Una volta impostato l’url del repository remoto, è sufficiente andare sul menù Synchronize –> Push. Il tool contatterà il server, e vi chiederà le credenziali, che in questo caso corrispondono al nome-utente e password con cui vi siete registrati a BitBucket.

Al termine, abbiamo proprio finito! Cosa abbiamo ottenuto?

Conclusioni
Abbiamo ottenuto una cosa molto semplice. E’ sufficiente andare a questo url per rendersene conto: il nostro progetto è online: possiamo vedere tutti i nostri commit, i changeset, tutti i commenti inseriti, per ogni changeset viene indicato quanti files sono stati aggiunti/rimossi/modificati, chi ha lavorato, etc. etc. Cliccando su un changeset BitBucket fornisce una grande quantità di informazioni: per ogni file, mette in evidenza le righe modificate, con il nome di chi ha fatto quel commit, e così via.

Dal momento che adesso è online, possiamo dire a chiunque è interessato: clonate il progetto sul vostro PC, unitevi al team di sviluppo, etc. etc.

Send to Kindle

[Mercurial.6] Clonare e lavorare su un progetto Mercurial già esistente

Riprendiamo la serie di mini-post dedicati a Mercurial. Prima di cominciare, voglio farvi un riassunto delle puntate precedenti, così potete balzare da un punto all’altro e vedere gli argomenti già trattati.

[Mercurial.1] Utilizzo di Mercurial per versionare i propri sorgenti

[Mercurial.2] Mercurial: come creare il proprio repository

[Mercurial.3] Creazione di un repository Mercurial per gestire una solution Visual Studio 2010

[Mercurial.4] Mercurial: icone dei files, cancellazione repository e plug-in per Visual Studio 2005/2008/2010

[Mercurial.5] Primi passi di Mercurial con un progetto Silverlight

Oggi vediamo questi argomenti:

  1. come utilizzare un server Mercurial pre-esistente
  2. come copiare in locale i sorgenti di un progetto già esistente
  3. come effettuare modifiche al sorgente e…
  4. …come rispedirle al server Mercurial del punto (1)

Come utilizzare un server Mercurial
Se vi ricordate bene i primi argomenti tirati in ballo quando si parla di Mercurial, noterete che si dice che per poter funzionare Mercurial non ha bisogno di un vero server, come invece accade con TFS. Ognuno dei PC che possiede il repository in locale è allo stesso tempo server & client. Quindi, la prima domanda è: perchè si vorrebbe avere un server centralizzato di Mercurial?

Io personalmente qualche risposta potrei anche darvela, ma sicuramente non rappresenta la giusta e sacrosanta verità. Io penso che un server centralizzato di Mercurial possa essere comunque utile per i seguenti motivi:

  1. più facile gestirne i backup, perchè posso schedularlo sul server aziendale (se aspettiamo che ogni developer del team faccia il backup del proprio PC, “campa cavallo che l’erba cresce”, come dice un proverbio)
  2. disponibilità sempre, dovunque e comunque dei sorgenti. Non è detto che siamo all’ultima versione disponibile, dal momento che ogni utente può averli modificati in locale, ma almeno i sorgenti ce li ho
  3. ed altri motivi che adesso non mi vengono in mente 🙂

Torniamo a noi. Esiste il sito http://bitbucket.org/, che permette di pubblicare repository Mercurial in modo del tutto gratuito, esattamente come accade con CodePlex. Ora: supponiamo che un qualcuno abbia già creato un progetto disponibile su bitbucket: noi vogliamo semplicemente prendere i sorgenti, guardarli e lavorarci un po’: poi, se siamo autorizzati, vogliamo rispedire al server le nostre modifiche, per metterle a disposizione a tutti.

Con Mercurial le cose sono estremamente semplici.

Come copiare in locale i sorgenti di un progetto pre-esistente
Dunque, date un’occhiata a questa pagina Web: http://bitbucket.org/igordamiani/consoletest. Un mesetto fa circa ho creato un progetto “ConsoleTest” per studiare e fare i primi esperimenti con Mercurial. Adesso ciascuno di voi lo clonerà sul proprio PC. Come fare? Seguite i seguenti passi.

  1. Andare in una directory a vostro piacimento (io utilizzerò X:\ perchè mi fa comodo così)
  2. Create una cartella (ad esempio: “ConsoleTest”)
  3. Cliccate con il pulsante destro del mouse, andate su TortoiseHG –> Clone…
  4. Apparirà il tool per clonare un progetto

mercurial_clone

E’ sufficiente compilare il Source Path ed il Destination Path, poi premete Clone ed il gioco è fatto. Piccole precisazioni: il source path è l’URL del repository remoto, per cui potete semplicemente fare copia & incolla dal browser. Il destination path viene impostato automaticamente in base alla cartella che avete selezionato per dare il comando.

Al termine dell’esecuzione del comando Clone, la cartella X:\ConsoleTest conterrà il progetto Visual Studio 2010, che potete aprire, guardare, compilare, eseguire e modificare.

Come effettuare modifiche al codice sorgente
Dunque, supponiamo che abbiate aperto ed eseguito il codice. Nel momento in cui vi scrivo, il Main() non fa altro che visualizzare sulla console l’elenco di tutti i processi installati e disponibile sul vostro sistema. Non è scopo di questo post spiegare come fare: basta leggere il codice! 🙂

Adesso, da bravi developer C#, cancellate il contenuto del Main() e scrivetene uno voi. Non importa cosa, l’importante è avere un Program.cs diverso dal mio. Basta avere un po’ di fantasia. Supponiamo di aver digitato questo:

 class Program
 {
 static void Main(string[] args)
 {
 DateTime today = DateTime.Now;
 
 Console.WriteLine("Oggi è il {0}.", today.ToShortDateString());
 
 if (today.Day == 2 && today.Month == 6)
 {
 Console.WriteLine("Buona Festa della Repubblica a tutti!");
 }
 
 Console.ReadKey();
 }
 }

Nulla di particolarmente sconvolgente. Adesso potete committare il codice. Ricordiamoci per l’ennesima volta che il commit avviene sul vostro repository locale, per cui potete fare & disfare il codice tutte le volte che volete, senza paura di intaccare il lavoro degli altri vostri colleghi. Per maggiori dettagli sull’operazione di commit, vi consiglio di dare un’occhiata ai miei precedenti post, soprattutto i [Mercurial.3] e [Mercurial.4], dove descrivo il plug-in per Visual Studio 2010.

Una volta che siete sicuri del vostro codice, potete finalmente buttare sul server la vostra modifica.

Come rispedire le modifiche al server Mercurial
Se avete fatto tutto come indicato, se adesso – stando in Visual Studio 2010 – andate sul tool HG Synchronize, vi appare la seguente schermata.

mercurial_synchronize

La schermata è piuttosto scarna. L’informazione più importante qua è il path del repository remoto, che non avete più bisogno di inserire, perchè TortoiseHG se la ricorda! 🙂 A questo punto cliccate sul pulsante Push e – previo inserimento di username & password – non fate altro che sincronizzare il repository remoto con le modifiche appena fatte. Il risultato è una cosa simile a questa:

mercurial_synchronize_completed

Tutto qua. Adesso all’url http://bitbucket.org/igordamiani/consoletest potete vedere tutta la history dei changeset committati, vostri e degli altri utenti.

Conclusioni
Solo una cosa ancora da dire. Una delle cose più interessanti di Mercurial, a mio avviso, è che quando clonate un repository non avete solo l’ultima versione dei sorgenti, ma avete a tutti gli effetti l’intero repository in locale sul vostro PC. Cosa significa? Significa che anche in assenza di connessione ad Internet, potete vedere tutti i commit, potete fare il revert ad una certa versione dei sorgenti, potete leggere tutti i commenti inseriti nei commit. Quindi, se lavorate offline, avete tutta una serie di informazioni che magari con TFS non potete avere.

Send to Kindle

[Mercurial.5] Primi passi di Mercurial con un progetto Silverlight

I concetti base esposti nei post precedenti rimangono ovviamente validi, ma li voglio riassumere qui velocemente:

  1. Apriamo Visual Studio 2010 e creiamo un’applicazione Silverlight 4
  2. Visto che ci siete, compilate l’applicazione
  3. Andiamo in Esplora Risorse e creiamo il repository cliccando col destro sulla cartella contenente la solution, usando il menù contestuale di TortoiseHG
  4. Aggiungiamo i files usando il menù TortoiseHG –> Add Files

Se avete seguito questi passaggi, vi siete appena sentiti male, perchè l’elenco che TortoiseHG vi propone di aggiungere al repository sono un po’ troppi. Questo, secondo me, è un pregio ed un difetto di Mercurial. Siccome potete usare questo CVS per qualsiasi cosa (e non per forza su progetti .NET), non sa a priori quali estensioni e quali cartelle filtrare. Quindi, TortoiseHG vi mette in elenco files eseguibili, dll, xap, pdb, tutto ciò che è contenuto in /bin/Debug o in /obj/Debug, etc. etc. Se ci fossero documenti Word o fogli Excel, ci sarebbero stati anche loro.

Ovviamente è possibile rimediare a tutto ciò. Basta andare nel menù TortoiseHG –> Edit Ignore Filter ed inserire uno o più filtri per eliminare la “sporcizia”. Ecco come appare il tool per l’editing di questi filtri, e come l’ho configurato io:

mercurial_ignore_filters

Ho inserito le seguenti voci:

*/Bin/Debug/*
*.xap
*.dll
*/obj/Debug/*
*/bin/*

Man mano che inserite questi filtri, l’elenco sulla destra si aggiorna, mostrandovi il contenuto effettivo. Se avete fatto tutto bene, TortoiseHG vi propone l’Add Files e successivamente il Commit dei files *.cs, *.xaml, *.xml, *.csproj, etc. etc.: tutto il contenuto sorgente della vostra solution.

A questo punto, due piccole note:

  1. Questi filtri vengono salvati nel file .hgignore, dentro la cartella repository
  2. Questo file secondo me va committato a sua volta nel repository: in questo modo, un altro componente del team che “clona” il vostro repository (operazione che non abbiamo ancora preso in considerazione) automaticamente eredita questi filtri, e non dovrà reimpostarseli manualmente
  3. Non è detto che dobbiate escludere le dll: se utilizzate assembly di terze parti (controlli e/o librerie) queste vanno aggiunte al repository, esattamente per gli stessi ragionamenti espressi al punto (2)

Ovviamente non tutto è scolpito nella pietra. Io stesso utilizzo Mercurial da relativamente poco tempo, per cui tutto questo è opinabile e assolutamente aperto a critiche: non c’è nemmeno bisogno di dirlo.

Ho volutamente preso un progetto Silverlight perchè, come è facile intuire, la compilazione genera diversi files eseguibili, e la cosa si prestava bene per spiegare i filtri.

Conclusione
Detto questo, possiamo aggiungere e committare i files senza paura di versare nel repository strani files che non vogliamo nemmeno vedere. Quando committeremo da Visual Studio, i tools saranno esattamente gli stessi di quelli avviati da Esplora Risorse, per cui quello che abbiamo fatto è tutto utile.

Send to Kindle

Uptime del mio blog e del mio (inutile) sito

Il mio sito Web è praticamente inutile perchè è composto da una sola pagina, in ASP.Net, scritta da me, ed è l’unica testimonianza reale che anche io so farne una, perciò la lascio lì dov’è.

A parte scherzi, questa mattina il buon Paolo Ongari mi ha consigliato su Facebook l’utilizzo di http://www.uptimerobot.com, un servizio Web che monitora uno o più URL per dirvi il periodo di uptime degli stessi. L’ho attivato questa mattina verso le 9:00. Ecco le statistiche catturate pochi minuti fa:

uptime_vivendobyte

Ovviamente il tutto è ahimè ancora sotto Webhost4life.com. Ieri, parlando con il loro customer support, ho saputo che c’è qualcosa che scatena un IIS reset ogni tanto: mah! Considerando che ogni tanto entrambi vanno giù, ho idea che abbiamo messo un qualcosa che in automatico riaccende e riavvia il servizio IIS. Mah!

Send to Kindle

[Mercurial.4] Mercurial: icone dei files, cancellazione repository e plug-in per Visual Studio 2005/2008/2010

A cosa serve TortoiseHG Overlay Icon Server ?
Lavorando con Mercurial da un po’ di tempo, mi sono accorto di una cosa strana.

Riprendiamo un’immagine usata nel post precedente:

La cartella ConsoleApplicationMercurial è contrassegnata da un’icona di un tick verde, che indica il fatto che il repository è sincronizzato e non contiene files da committare e cose del genere. Se apro la solution con Visual Studio e faccio una semplice modifica al Program.cs, e magari faccio scrivere qualcosa nella Console e salva tutto, Mercurial ovviamente capisce che quel file è cambiato rispetto alla versione contenuta nel repository. Questo “capisce che quel file è cambiato” è evidente dall’icona che contraddistingue il file stesso:

mercurial_file_modified

Adesso c’è un punto esclamativo. Questa icona non cambia solo sul file, ma anche su tutte le cartelle superiori del repository, fino a raggiungere quella root del repository stesso, che nel nostro caso è X:\DocumentiIgor\Visual Studio 2010\Projects\ConsoleApplicationMercurial.

Queste belle icone possono sparire!
Allora, se deciderete di usare Mercurial attivamente, probabilmente gestirete il tutto da Visual Studio, e quindi potrebbe non capitarvi di andare in Esplora Risorse e vedere lo stato di ogni singolo file. Ma se lo farete, sappiate che c’è un piccolo trabocchetto dietro l’angolo: queste belle icone possono sparire. Non ditemi perchè, ma TortoiseHG (e non tanto Mercurial) ha bisogno della presenza nella tray-bar di un piccolo applicativo, TortoiseHG Overlay Icon Server, che evidentemente si occupa di monitorare il sistema e di mettere le icone giuste sui files giusti. Se questo tool non è avviato, vedrete sempre un punto interrogativo.

Ora sia chiaro: quando installa TortoiseHG viene installato anche questo tool, e viene messo per giunta da qualche parte all’avvio del sistema. Se però siete pignoli come me e andate a toglierlo, potreste cadere in questo problema. Se vi interessa saperlo, l’exe è questo:

C:\Program Files (x86)\TortoiseHg\TortoiseHgOverlayServer.exe

Tutto qua.

Cancellazione di un repository
Nei primi giorni in cui studiavo e sperimentavo Mercurial, mi capitava spesso di creare e di voler cancellare i repository. Si può fare? Assolutamente sì.

Supponiamo di aver creato un repository in X:\DocumentiIgor\Visual Studio 2010\Projects\ConsoleApplicationMercurial, così come indicato in questa piccola mini-serie di post. Se vogliamo cancellarlo, è sufficiente:

  1. Entrare nella cartella ConsoleApplicationMercurial
  2. Cancellare la directory .hg
  3. Cancellare il file .hgignore

Ed il gioco è fatto. Ovviamente cancellare un repository non cancella i vostri sorgenti: cancella tutti i commit fatti, l’intero storico di tutte le modifiche fatte, etc. etc. Ma tutto il resto rimane: solution di Visual Studio, tutti i .cs: ma questo non c’è nemmeno bisogno di dirlo.

Installazione del plug-in per Visual Studio
Il plug-in è compatibile con Visual Studio 2005/2008/2010 ed è scaricabile dal progetto su Codeplex. L’indirizzo è il seguente: http://visualhg.codeplex.com/.

Non richiede particolari configurazioni. Dopo l’installazione basta aprire Visual Studio, andare su Tools –> Options –> SourceControl e dal plug-in selection selezionare la voce VisualHG.

Da questo momento in poi, quando aprite una solution gestita sotto Mercurial, i files verranno identificati dalle icone, come mostrato di seguito.

visualhg_solution_explorer

ed avrete un menù contestuale per le operazioni indispensabili…

visualhg_menu

ovverro Commit, Status, History, Synchronize ed Update.

Vedremo queste operazioni nei prossimi post.

Send to Kindle

[Mercurial.3] Creazione di un repository Mercurial per gestire una solution Visual Studio 2010

Nel post precedente abbiamo dato solo una leggera infarinatura. Oggi facciamo un po’ più sul serio. Vediamo come creare un progetto Visual Studio 2010 e vediamo come versionare i sorgenti con Mercurial.

Al principio non dobbiamo fare nulla di particolare: apriamo VS2010 e creiamo una Console Application: lo facciamo solo per semplicità, ovviamente il tutto continua a valere con progetti WPF, Silverlight e ASP.Net. Creiamolo come al solito nella directory predefinita di Visual Studio, ottenendo un path ad esempio simile a questo:

X:\DocumentiIgor\Visual Studio 2010\Projects\ConsoleApplicationMercurial

La cartella apparirà come tutte le altre normalissime cartelle.

A questo possiamo creare il repository, seguendo gli stessi passaggi indicati nel post precedente. Clicchiamo col destro sulla cartella ConsoleApplicationMercurial, andiamo su TortoiseHG e poi clicchiamo su Create Repository here.

Apparirà la seguente finestra:

Non toccate nulla e premete Create. Prima di farlo, però, date un’occhiata alla casella Add special files (.hgignore): questo è un file particolare, perchè può contenere un elenco di “filtri” per evitare di versionare determinati files, basandosi su estensioni/cartelle, etc. etc. E’ utile, come è facile intuire, per evitare di versionare exe, dll, files binari e così via.

Per adesso, ripeto, clicchiamo su Create e proseguiamo. A questo la cartella appare con un tick verde:

Questo perchè il repository in questo momento non contiene files modificati che richiedono il commit. E’ ovvio: il repository è ancora vuoto, per cui il passaggio seguente è quello di aggiungere i files. Clicchiamo col destro sulla cartella, andiamo su TortoiseHG e poi su Add files. Notare che siccome in questo momento stiamo lavorando su una cartella che è un repository Mercurial, il numero di voci disponibili è cresciuto a dismisura: possiamo esplorare il repository, possiamo aggiungere/rimuovere files, possiamo inviare ad un webserver il contenuto del repository, etc. etc.

Rimaniamo sul semplice, aggiungendo i files. La finestra di dialogo per questo task è la seguente:

Notare che ho spento le checkbox degli ultimi tre files, che sono files eseguibili o comunque files che non mi interessa versionare. Clicchiamo su Add ed il gioco è fatto.

A questo punto possiamo effettuare il commit vero e proprio. Clicchiamo col destro sulla cartella ed andiamo su HG Commit. La finestra di dialogo è la seguente:

Questo tool è più complesso di quello che sembra: c’è una casella di testo per inserire una descrizione relativa al nostro commit (“Primo commit”); sul lato sinistro c’è l’elenco dei files, mentre sul lato destro appare il contenuto del file correntemente selezionato sul lato sinistro. Se il file ha una history di qualche tipo (lo vedremo in seguito), sul lato destro vediamo le righe aggiunte/cancellate/modificate nel changeset corrente.

Inseriamo una descrizione e poi clicchiamo sul pulsante Commit nella toolbar.

A questo punto, possiamo tranquillamente lavorare con Visual Studio: aggiungiamo classi, scriviamo codice, aggiungiamo progetti e riferimenti. Ogni file che verrà aggiunto all’interno della cartella X:\DocumentiIgor\Visual Studio 2010\Projects\ConsoleApplicationMercurial viene automaticamente preso in considerazione per l’inserimento nel repository. Ogni volta che fate il commit, l’elenco dei files sulla sinistra si aggiornerà: la colonna status (in breve ‘st’) vi dirà ‘A’ per quelli ‘added’ o ‘M’ per i files ‘modified’.

Questo se non vogliamo lavorare con il plug-in di Visual Studio, cosa che invece faremo dal prossimo post. La cosa è molto semplice: dentro il Solution Explorer ogni file verrà indicato con un’icona per indicare se è sincronizzato rispetto a quello nel repository, oppure se è stato modificato e così via. Possiamo committare, possiamo vedere lo stato corrente del repository, e un po’ di altre cose interessanti.

Alla prossima!

Send to Kindle