A qualche giorno dall'uscita ufficiale degli strumenti di sviluppo per la piattaforma, in questo post riprendo tutti i nostri mini-articoli, post nei vari blog, e link agli esempi e simulatori per fare il punto della situazione.
Questi primi quattro post sono impressioni personali mie e del team con cui stiamo lavorando alla realizzazione di 5 applicazioni per Windows Phone 7.
Windows Phone 7 Primo contatto
Windows Phone 7 Feeling
Windows Phone 7 Capability
Windows Phone 7 Marketplace
Push Notification: cambio formato
E’ cambiato il formato per l’invio delle notifiche Toast e Tile. E’ rimasto invariato il formato per le notifiche Raw. Non occorre modificare il codice lato telefono, ma solo il codice lato receiver. Toast Il codice seguente, dove notificationMessage...
Windows Phone 7 Beta SDK Bug e soluzione
Dopo vari (e vani) tentativi di aggiungere una service reference da un progetto Windows Phone 7 (Silverligth) da Visual Studio 2010 e senza ottenere nessun errore durante la “add” ho scoperto che il classico file Reference.cs era vuoto e non erano stati...
Windows Phone 7 Beta upgrade 5
Con questo post abbiamo quasi finito il porting delle nostre applicazioni e librerie, nonchè le demo e i test alla versione Beta degli SDK. In questo post ci dedichiamo alle modifiche alle classi del servizio Push Notification Per la parte teorica fate...
Windows Phone 7 SDK Beta upgrade 4
Stiamo aggiornando una demo che sfrutta il Location Service e colgo l’occasione per indicare le modifiche rispetto alla versione April CTP come nei precedenti post. La prima modifica è l’assembly da referenziare: adesso è System.Device.dll e prende il...
Phone application inizialization (Beta news)
Un’altra modifica alle librerie introdotta con l’SDK uscito lunedì sera riguarda l’inizializzazione delle applicazioni. Come si nota dallo screenshot l’inizializzazione viene fatta da un metodo, presente nella classe App derivata da Application, e richiamato...
Windows Phone 7 SDK Beta upgrade 2
Rispetto al post precedente , occorre prestare attenzione al macro find/replace. Se abbiamo usato la ApplicationBar , presente nel namespace Shell, occorre prestare attenzione al namespace utilizzato. Questo un esempio corretto con il nuovo SDK Beta Attenzione...
Windows Phone 7 SDK Beta upgrade 1
Oggi giornata di upgrade dei vari progetti e demo creati con la versione di Aprile degli strumenti di sviluppo. Il primo progetto, già pubblicato nella sezione media di ThinkMobile.it e oggetto di alcuni articoli usciti sul sito ThinkMobile.it, IoProgrammo...
Windows Phone 7 Beta SDK Tools
I primi due strumenti che saltano all’occhio dopo l’installazione dei nuovi strumenti di sviluppo sono sicuramente Developer Phone Registration e XAP Deployment. In entrambi i casi, alcune preview erano disponibili come prodotti separati su CodePlex o...
New Beta Dev Tools for Windows Phone 7
Sono stati appena rilasciati i nuovi strumenti di sviluppo per Windows Phone 7. Il download è pubblico a partire da questo link: http://www.microsoft.com/express/Downloads/#2010-Visual-Phone . Per adesso e probabilmente per il resto del periodo beta sono...
Accelerometer by mouse
Ho pubblicato nella sezione media una nostra libreria interna ( http://we.thinkahead.it ), ancora in fase di sviluppo, per simulare l'acceletrometro via mouse durante l'utilizzo dell'emulatore. Il funzionamento è semplice: premere il tasto...
WP7: Formati Audio/Video supportati
Audio WAV WAV Audio MP3 MP3 Audio WMA Standard v9 ASF (WMA) Audio AAC-LC (Low Complexity) 3GP, 3G2, MP4, M4A Audio HE-AAC v1 (AAC+) 3GP, 3G2, MP4, M4A Audio HE-AAC v2 (eAAC+) 3GP, 3G2, MP4, M4A Audio Adaptive Multi-Rate Narrow Band (AMR-NB) 3GP, 3G2,...
Articolo Windows Phone 7
E’ uscito un nostro articolo introduttivo sui passi da seguire per scrivere il primo “hello world” con Windows Phone 7, su HTML.it http://aspnet.html.it/articoli/leggi/3419/sviluppare-per-windows-phone-7-la-prima-applicazione/ Domani mattina, durante...
Visual Studio 2010 for Windows Phone Menù
Visual Studio 2010 Express for Windows Phone ha serie di menù che non vengono mostrati appena istallato il prodotto. Ad esempio il menù View si presenta così Anche il menù Debug si presenta molto “triste” :-) Nei Settings è però possibile impostare Expert...
Windows Phone 7 Trial Application
Fra i vari servizi esposti dal Markeplace (certification, advertising, cross-selling, etc.) esiste il “servizio” di prova di una applicazione. L’idea è molto semplice e nel contempo, molto potente. 1) Come sviluppatori indicheremo al Marketplace se vogliamo...
Windows Phone 7 SMS
Chiudiamo questa mini-serie sull’interazione fra codice e API applicative del device/emulatore con un esempio semplicissimo sull’emulatore. Direi che dopo i post precedenti non c’è bisogno di ulteriori spiegazioni sul flusso che stiamo creando: Il tasto...
Windows Phone 7 Camera…from Code
Come abbiamo avuto modo di segnalare in altri blog e articoli precedenti, l’emulatore attuale non espone attraverso il menù start tutte le applicazioni che è in grado di supportare. La fotocamera è una di queste applicazioni “nascoste”. Da codice possiamo...
Windows Phone 7 Email from code
Come abbiamo visto nell’ultimo post sulla fotocamera, l’emulatore attuale non espone attraverso il menù start tutte le applicazioni che è in grado di supportare. In questo mini-articolo vediamo come attivare la parte di “email” che comprende la composizione...
Windows Phone 7 Input Scope
Windows Mobile, iPhone e Android ci hanno abituato a semplificare la vita all’utente durante la digitazione di informazioni. Da sempre, su un device mobile occorre facilitare l’input all’utente agendo su vari fronti. Su Windows Phone 7 è possibile agire...
Windows Phone 7 Location Service
Il servizio “Location Service”, come indica il nome stesso, consente di notificare alle applicazioni che lo utilizzano la posizione corrente del device. Il servizio ottiene i dati da fonti diverse in base alle impostazioni (effettuabili da codice) che...
Esempi Windows Phone 7
Nella nuova sezione Windows Phone 7 di ThinkMobile.it ho postato gli esempi relativi ai mini-articoli pubblicati su questo blog. http://thinkmobile.it/media/g/sviluppo/default.aspx
Windows Phone 7 Isolated Storage
Come per le applicazioni Silverlight tradizionali, anche sui nuovi Windows Phone 7 sarà possibile utilizzare l’Isoltated Storage per memorizzare informazioni applicative. Vista la natura a Page dell’applicazione è quasi indispensabile salvare lo stato...
WPF 7: DevLeap mini-browser applications
Riprendendo una parte di workshop direttamente da MSDN ho ricreato questo piccolo esempio di applicazione client che sfrutta il controllo web browser presente su Windows Phone 7 Si faccia riferimento Windows Phone 7 Intro per una introduzione alla creazione...
Il primo progetto…si comincia sempre così.
Aprire Visual Studio Express for Windows Phone 7
La schermata iniziale ci ricorda le operazioni possibili puntando a documentazione e video tutorial sul'l’ambiente, sulla modalità di crazione delle applicazioni oppure sul Marketplace.
Il primo passo del wizard di creazione è abbastanza famialiare e si presenta come segue:
XNA Game Studio 4.0 a parte, per creare una applicazione per Windows Phone si sceglie Silverlight (è la 4 con qualche rivitazione) e la tipologia di applicazione.
Windows Phone Class Library è una dll per Windows Phone, mentre le prime due tipologie di applicazione sono il classico “eseguibile” sul device o sull’emulatore. La differenza fra le due si comprende semplicemente selezionando il template e vedendo l’anteprima che vi ripropongo sotto:
La schermata iniziale si prensenta con Toolbox dei controlli Silverlight a sinistra come sempre. Al centro il doppio editor visuale + XAML (Luca direbbe che è uno solo :-)). Nel solution explorer troviamo i due .png usati dal progetto per lo sfondo e l’icona.
Il file MainPage.xaml si presenta con alcune namespace propri dell’ambiente Windows Phone 7:
<phoneNavigation:PhoneApplicationPage
x:Class="WindowsPhoneApplication2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitleGrid is the name of the application and page title-->
<Grid x:Name="TitleGrid" Grid.Row="0">
<TextBlock Text="MY APPLICATION" x:Name="textBlockPageTitle" Style="{StaticResource PhoneTextPageTitle1Style}"/>
<TextBlock Text="page title" x:Name="textBlockListTitle" Style="{StaticResource PhoneTextPageTitle2Style}"/>
</Grid>
<!--ContentGrid is empty. Place new content here-->
<Grid x:Name="ContentGrid" Grid.Row="1">
</Grid>
</Grid>
</phoneNavigation:PhoneApplicationPage>
Altro componente importate, il file app.xaml che contiene, come da “manuale”, le risorse applicative utilizzabile dalle varie componenti:
<Application
x:Class="WindowsPhoneApplication2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:mpc="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:phoneNavigation="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation">
<!--RootFrame points to and loads the first page of your application-->
<Application.RootVisual>
<phoneNavigation:PhoneApplicationFrame x:Name="RootFrame" Source="/MainPage.xaml"/>
</Application.RootVisual>
<!-- Resources for following the Windows Phone design guidelines -->
<Application.Resources>
<!--************ THEME RESOURCES ************-->
<!-- Color Resources -->
<Color x:Key="PhoneBackgroundColor">#FF1F1F1F</Color>
<Color x:Key="PhoneContrastForegroundColor">Black</Color>
<Color x:Key="PhoneForegroundColor">White</Color>
<Color x:Key="PhoneInactiveColor">#FF666666</Color>
<Color x:Key="PhoneDisabledColor">#FF808080</Color>
<Color x:Key="PhoneSubtleColor">#FF999999</Color>
<Color x:Key="PhoneContrastBackgroundColor">#FFFFFFFF</Color>
<Color x:Key="PhoneTextBoxColor">#FFBFBFBF</Color>
<Color x:Key="PhoneBorderColor">#FFCCCCCC</Color>
<Color x:Key="PhoneTextSelectionColor">Black</Color>
<Color x:Key="PhoneAccentColor">#FF1BA1E2</Color>
<!-- Brush Resources -->
<SolidColorBrush x:Key="PhoneAccentBrush" Color="{StaticResource PhoneAccentColor}"/>
<SolidColorBrush x:Key="PhoneBackgroundBrush" Color="{StaticResource PhoneBackgroundColor}"/>
<SolidColorBrush x:Key="PhoneContrastForegroundBrush" Color="{StaticResource PhoneContrastForegroundColor}"/>
<SolidColorBrush x:Key="PhoneForegroundBrush" Color="{StaticResource PhoneForegroundColor}"/>
<SolidColorBrush x:Key="PhoneInactiveBrush" Color="{StaticResource PhoneInactiveColor}"/>
<SolidColorBrush x:Key="PhoneDisabledBrush" Color="{StaticResource PhoneDisabledColor}"/>
<SolidColorBrush x:Key="PhoneSubtleBrush" Color="{StaticResource PhoneSubtleColor}"/>
<SolidColorBrush x:Key="PhoneContrastBackgroundBrush" Color="{StaticResource PhoneContrastBackgroundColor}"/>
<SolidColorBrush x:Key="PhoneTextBoxBrush" Color="{StaticResource PhoneTextBoxColor}"/>
<SolidColorBrush x:Key="PhoneBorderBrush" Color="{StaticResource PhoneBorderColor}"/>
<SolidColorBrush x:Key="PhoneTextSelectionBrush" Color="{StaticResource PhoneTextSelectionColor}"/>
<SolidColorBrush x:Key="TransparentBrush" Color="Transparent"/>
<!-- Touch Target Area -->
<Thickness x:Key="PhoneTouchTargetOverhang">12</Thickness>
<!-- Default Border Size-->
<Thickness x:Key="PhoneDefaultBorderThickness">3</Thickness>
<!-- Font Names -->
<FontFamily x:Key="PhoneFontFamilyNormal">Segoe WP</FontFamily>
<FontFamily x:Key="PhoneFontFamilyLight">Segoe WP Light</FontFamily>
<FontFamily x:Key="PhoneFontFamilySemiLight">Segoe WP Semilight</FontFamily>
<FontFamily x:Key="PhoneFontFamilySemiBold">Segoe WP Semibold</FontFamily>
<!-- Font sizes -->
<!--14pt-->
<system:Double x:Key="PhoneFontSizeSmall">18.667</system:Double>
<!--15pt-->
<system:Double x:Key="PhoneFontSizeNormal">20</system:Double>
<!--17pt-->
<system:Double x:Key="PhoneFontSizeMedium">22.667</system:Double>
<!--19pt-->
<system:Double x:Key="PhoneFontSizeMediumLarge">25.333</system:Double>
<!--24pt-->
<system:Double x:Key="PhoneFontSizeLarge">32</system:Double>
<!--32pt-->
<system:Double x:Key="PhoneFontSizeExtraLarge">42.667</system:Double>
<!--54pt-->
<system:Double x:Key="PhoneFontSizeExtraExtraLarge">72</system:Double>
<!-- TextBox styles -->
<Style x:Key="PhoneTextBoxStyle" TargetType="TextBox">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeSmall}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneTextBoxBrush}"/>
</Style>
<!-- TextBlock styles -->
<Style x:Key="PhoneTextNormalStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeNormal}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextPageTitle1Style" TargetType="TextBlock" BasedOn="{StaticResource PhoneTextNormalStyle}">
<Setter Property="Margin" Value="20,20,0,0" />
</Style>
<Style x:Key="PhoneTextSubtleStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeNormal}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneSubtleBrush}"/>
</Style>
<Style x:Key="PhoneTextTitle1Style" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeExtraExtraLarge}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextPageTitle2Style" TargetType="TextBlock" BasedOn="{StaticResource PhoneTextTitle1Style}">
<Setter Property="Margin" Value="20,43,0,0" />
</Style>
<Style x:Key="PhoneTextTitle2Style" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeLarge}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextTitle3Style" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextExtraLargeStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeExtraLarge}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextGroupHeaderStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeLarge}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneSubtleBrush}"/>
</Style>
<Style x:Key="PhoneTextLargeStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiLight}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeLarge}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextSmallStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeSmall}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneSubtleBrush}"/>
</Style>
<Style x:Key="PhoneTextContrastStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeNormal}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneContrastForegroundBrush}"/>
</Style>
<Style x:Key="PhoneTextAccentStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeNormal}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneAccentBrush}"/>
</Style>
<Style x:Key="PhoneTextBodyTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="LineHeight" Value="32"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="20,20,20,0" />
</Style>
<!--************ THEME RESOURCES ************-->
<!--***** LISTBOX/LISTBOXITEM TEMPLATES *****-->
<Style x:Key="PhoneListBox" TargetType="ListBox">
<Setter Property="Padding" Value="1"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Grid>
<ScrollViewer Foreground="{TemplateBinding Foreground}" x:Name="ScrollViewer" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Margin="7,1,1,1">
<ItemsPresenter/>
</ScrollViewer>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Background" Value="{StaticResource TransparentBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMedium}"/>
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilyNormal}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="Background" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="Transparent"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="Opacity" Duration="0" To=".55" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselected"/>
<VisualState x:Name="Selected"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Margin="{StaticResource PhoneTouchTargetOverhang}">
<ContentPresenter x:Name="contentPresenter" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PhoneListBoxItemLayout" TargetType="mpc:ListViewItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="ImageStretch" Value="UniformToFill" />
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Height" Value="82"/>
<!-- The intended height of the control is 95 but the ItemsControl pads each element with 12 -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="mpc:ListViewItem">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}" Height="{TemplateBinding Height}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="ItemText" Text="{TemplateBinding Text}" Style="{StaticResource PhoneTextExtraLargeStyle}" VerticalAlignment="Center" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" Margin="-3,-4,0,0"/>
<ContentPresenter Grid.Column="1" x:Name="SecondaryContent" Content="{TemplateBinding SecondaryContent}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="TextWithIconTemplate">
<Setter.Value>
<ControlTemplate TargetType="mpc:ListViewItem">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="43"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{TemplateBinding ImageSource}" Stretch="{TemplateBinding ImageStretch}" Width="43" Height="43"/>
<TextBlock Grid.Column="1" Margin="9,-4,0,0" x:Name="ItemText" Text="{TemplateBinding Text}" Style="{StaticResource PhoneTextExtraLargeStyle}" VerticalAlignment="Center" />
<ContentPresenter Grid.Column="2" x:Name="SecondaryContent" Content="{TemplateBinding SecondaryContent}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="TextAndDetailsTemplate">
<Setter.Value>
<ControlTemplate TargetType="mpc:ListViewItem">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center" >
<TextBlock x:Name="ItemText" Text="{TemplateBinding Text}" Style="{StaticResource PhoneTextExtraLargeStyle}" HorizontalAlignment="Left" Margin="-3,-13,0,0" />
<TextBlock x:Name="DetailsText" Text="{TemplateBinding Details}" Style="{StaticResource PhoneTextSubtleStyle}" HorizontalAlignment="Left" Margin="-1,-6,0,0" />
</StackPanel>
<ContentPresenter Grid.Column="1" x:Name="SecondaryContent" Content="{TemplateBinding SecondaryContent}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="TextAndDetailsWithIconTemplate">
<Setter.Value>
<ControlTemplate TargetType="mpc:ListViewItem">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="43"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{TemplateBinding ImageSource}" Stretch="{TemplateBinding ImageStretch}" Width="43" Height="43" VerticalAlignment="Top"/>
<StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Center">
<TextBlock x:Name="ItemText" Text="{TemplateBinding Text}" Style="{StaticResource PhoneTextExtraLargeStyle}" HorizontalAlignment="Left" Margin="-3,-13,0,0"/>
<TextBlock x:Name="DetailsText" Text="{TemplateBinding Details}" Style="{StaticResource PhoneTextSubtleStyle}" HorizontalAlignment="Left" Margin="-1,-6,0,0" />
</StackPanel>
<ContentPresenter Grid.Column="2" x:Name="SecondaryContent" Content="{TemplateBinding SecondaryContent}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="CustomTemplate">
<Setter.Value>
<ControlTemplate TargetType="mpc:ListViewItem">
<Grid x:Name="LayoutRoot" Background="{TemplateBinding Background}" Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="12,0,0,0" x:Name="ItemText" Text="{TemplateBinding Text}" Style="{StaticResource PhoneTextExtraLargeStyle}" VerticalAlignment="Center" />
<ContentPresenter Grid.Column="1" x:Name="SecondaryContent" Content="{TemplateBinding SecondaryContent}" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--***** LISTBOX/LISTBOXITEM TEMPLATES *****-->
<!--************ BUTTON TEMPLATE ************-->
<Style x:Key="PhoneButtonBase" TargetType="ButtonBase">
<Setter Property="Background" Value="{StaticResource TransparentBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="Foreground" Value="{StaticResource PhoneForegroundBrush}"/>
<Setter Property="MinHeight" Value="72" />
<Setter Property="BorderThickness" Value="{StaticResource PhoneDefaultBorderThickness}"/>
<Setter Property="FontFamily" Value="{StaticResource PhoneFontFamilySemiBold}"/>
<Setter Property="FontSize" Value="{StaticResource PhoneFontSizeMediumLarge}"/>
<Setter Property="Padding" Value="10,0,10,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ButtonBase">
<Grid Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="foregroundContainer" Storyboard.TargetProperty="Control.Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneBackgroundBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ButtonBackground" Storyboard.TargetProperty="Border.Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneForegroundColor}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ButtonBackground" Storyboard.TargetProperty="Border.BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneForegroundColor}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="foregroundContainer" Storyboard.TargetProperty="Control.Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ButtonBackground" Storyboard.TargetProperty="Control.BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PhoneDisabledBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="ButtonBackground" Storyboard.TargetProperty="Control.Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource TransparentBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="ButtonBackground" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="0" Background="{TemplateBinding Background}" Margin="{StaticResource PhoneTouchTargetOverhang}">
<ContentControl x:Name="foregroundContainer" FontFamily="{TemplateBinding FontFamily}" Foreground="{TemplateBinding Foreground}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" FontSize="{TemplateBinding FontSize}" Padding="{TemplateBinding Padding}" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--************ BUTTON TEMPLATE ************-->
<!--********* PHONE SLIDER TEMPLATE *********-->
<Style x:Key="PhoneSlider" TargetType="Slider">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Maximum" Value="10"/>
<Setter Property="Minimum" Value="0"/>
<Setter Property="MinHeight" Value="84"/>
<Setter Property="MinWidth" Value="60"/>
<Setter Property="Value" Value="0"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PhoneAccentBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid x:Name="Root" Background="{TemplateBinding Background}">
<Grid.Resources>
<ControlTemplate x:Key="PhoneSimpleRepeatButton" TargetType="RepeatButton">
<Rectangle />
</ControlTemplate>
<ControlTemplate x:Key="PhoneSimpleThumb" TargetType="Thumb">
<Rectangle />
</ControlTemplate>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="HorizontalTrack" Storyboard.TargetProperty="Opacity" To="0.55" />
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="HorizontalThumbDisabledOverlay" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Duration="0" Storyboard.TargetName="VerticalTrack" Storyboard.TargetProperty="Opacity" To="0.55" />
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="VerticalThumbDisabledOverlay" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="HorizontalTemplate">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="0"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="HorizontalTrack" Fill="{StaticResource PhoneForegroundBrush}" Opacity="0.2" StrokeThickness="0" Grid.ColumnSpan="3" Margin="0,24,0,0" VerticalAlignment="Top" Height="12"/>
<Rectangle Fill="{TemplateBinding Foreground}" StrokeThickness="0" Grid.Column="0" Margin="0,24,0,0" VerticalAlignment="Top" Height="12"/>
<RepeatButton x:Name="HorizontalTrackLargeChangeDecreaseRepeatButton" IsTabStop="False" Template="{StaticResource PhoneSimpleRepeatButton}" Background="Transparent"/>
<RepeatButton x:Name="HorizontalTrackLargeChangeIncreaseRepeatButton" IsTabStop="False" Template="{StaticResource PhoneSimpleRepeatButton}" Grid.Column="2" Background="Transparent"/>
<Thumb x:Name="HorizontalThumb" Width="1" Margin="-1,0,0,0" Grid.Column="1" BorderThickness="0" Template="{StaticResource PhoneSimpleThumb}" Background="Transparent" RenderTransformOrigin="0.5,0.5">
<Thumb.RenderTransform>
<ScaleTransform ScaleX="32" ScaleY="1"/>
</Thumb.RenderTransform>
</Thumb>
<Rectangle x:Name="HorizontalThumbDisabledOverlay" IsHitTestVisible="False" Opacity="0.55" Visibility="Collapsed" Fill="{StaticResource PhoneDisabledBrush}" Grid.Column="1" Height="12" Margin="0,24,0,0" VerticalAlignment="Top"/>
</Grid>
<Grid x:Name="VerticalTemplate">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="0"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="VerticalTrack" Fill="{StaticResource PhoneForegroundBrush}" Opacity="0.2" StrokeThickness="0" HorizontalAlignment="Left" Margin="24,0,0,0" Width="12" Grid.RowSpan="3"/>
<Rectangle Fill="{TemplateBinding Foreground}" StrokeThickness="0" Grid.Row="2" HorizontalAlignment="Left" Margin="24,0,0,0" Width="12"/>
<RepeatButton x:Name="VerticalTrackLargeChangeDecreaseRepeatButton" IsTabStop="False" Template="{StaticResource PhoneSimpleRepeatButton}" Background="Transparent" Grid.Row="2"/>
<RepeatButton x:Name="VerticalTrackLargeChangeIncreaseRepeatButton" IsTabStop="False" Template="{StaticResource PhoneSimpleRepeatButton}" Grid.Row="2" Background="Transparent"/>
<Thumb x:Name="VerticalThumb" Width="{TemplateBinding Width}" Height="1" Margin="0,-1,0,0" IsTabStop="True" Grid.Row="1" BorderThickness="0" Template="{StaticResource PhoneSimpleThumb}" Background="Transparent" RenderTransformOrigin="0.5,0.5">
<Thumb.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="32"/>
</Thumb.RenderTransform>
</Thumb>
<Rectangle x:Name="VerticalThumbDisabledOverlay" HorizontalAlignment="Left" Margin="24,0,0,0" Width="12" IsHitTestVisible="False" Opacity="0.55" Visibility="Collapsed" Fill="{StaticResource PhoneDisabledBrush}" Grid.Row="1"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--********* PHONE SLIDER TEMPLATE *********-->
<!--****** PHONE TOGGLESWITCH TEMPLATE ******-->
<Style x:Key="PhoneToggleSwitch" TargetType="ToggleButton">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource PhoneBorderBrush}"/>
<Setter Property="BorderBrush" Value="{StaticResource PhoneBorderBrush}"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="MinWidth" Value="136"/>
<Setter Property="MaxWidth" Value="136"/>
<Setter Property="MinHeight" Value="96"/>
<Setter Property="MaxHeight" Value="96"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="borderDisabled" Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked" >
<Storyboard>
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="switchButton" Storyboard.TargetProperty="(Canvas.Left)" To="80" />
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="rectangleSwitchOn" Storyboard.TargetProperty="Width" To="78" />
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="switchButtonDisabled" Storyboard.TargetProperty="(Canvas.Left)" To="80" />
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="rectangleSwitchOnDisabled" Storyboard.TargetProperty="Width" To="78" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="switchButton" Storyboard.TargetProperty="(Canvas.Left)" To="0" />
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="rectangleSwitchOn" Storyboard.TargetProperty="Width" To="0" />
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="switchButtonDisabled" Storyboard.TargetProperty="(Canvas.Left)" To="0" />
<DoubleAnimation Duration="00:00:00.3" Storyboard.TargetName="rectangleSwitchOnDisabled" Storyboard.TargetProperty="Width" To="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="border" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" Width="112" Height="32" HorizontalAlignment="Right" Margin="0,32,0,32" >
<Canvas x:Name="switchContainer" Margin="2">
<Rectangle x:Name="rectangleSwitchOn" Width="0" Height="24" Fill="{TemplateBinding Foreground}" />
<Grid x:Name="switchButton" Width="24" Height="24" Canvas.Left="0" Background="{StaticResource PhoneForegroundBrush}" >
<Rectangle Fill="{StaticResource PhoneBackgroundBrush}" Width="2" HorizontalAlignment="Left" StrokeThickness="0" Margin="5,4,0,4" />
<Rectangle Fill="{StaticResource PhoneBackgroundBrush}" Width="2" HorizontalAlignment="Center" StrokeThickness="0" Margin="0,4,0,4" />
<Rectangle Fill="{StaticResource PhoneBackgroundBrush}" Width="2" HorizontalAlignment="Right" StrokeThickness="0" Margin="0,4,5,4" />
</Grid>
</Canvas>
</Border>
<Border x:Name="borderDisabled" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" BorderBrush="{StaticResource PhoneInactiveBrush}" Width="112" Height="32" HorizontalAlignment="Right" Margin="0,32,0,32" Visibility="Collapsed" >
<Canvas x:Name="switchContainerDisabled" Margin="2">
<Rectangle x:Name="rectangleSwitchOnDisabled" Width="0" Height="24" Fill="{StaticResource PhoneInactiveBrush}" />
<Grid x:Name="switchButtonDisabled" Width="24" Height="24" Canvas.Left="0" Background="{StaticResource PhoneInactiveBrush}" />
</Canvas>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--****** PHONE TOGGLESWITCH TEMPLATE ******-->
</Application.Resources>
</Application>
Mandiamo in esecuzione il tutto nel nuovo emulatore dopo aver cambiato qualche testo:
L’emulatore si può ruotare a piacimento come gli attuali.
Alla prossima
Stamani sto effettuando due porting su VSTS 2010.
Il primo è una applicazione nata direttamente in .NET 4.0 e attualmente basata sulla RC. L’applicazione è venuta su subito senza necessità di conversioni particolari e si presenta pronta nel Solution Explorer:
Si tratta di una applicazione in corso di sviluppo per Dompè (grazie Sebastiano) composta da un Data Access Layer nato in Linq To SQL quando non sapevamo ancora cosa ci sarebbe realmente stato un Entity Framework 4.0. All’uscita della versione RC di VS 2010 abbiamo cambiato il Dal realizzando il modello con EF 4. L’applicazione, come si nota è scritta in WPF e sfrutta alcune caratteristche di VSTS 2010 per il test della user interface (uno spettacolo: si veda il mio articolo basato sulla beta). L’applicazione contiene anche un progetto SketchFlow di base che abbiamo creato direttamente con Sebastiano, il responsabile del progetto in Dompè.
La secoda operazione che sto facendo il parallelo è il porting dell’applicazione Estates Management su .NET 4.0 e VSTS 2010.
Prima della partenza della migrazione l’applicazione che molti dei nostri clienti conoscono si presentava in versione 3.7:
Sfruttando i branch di Team Foundation Server, dalla versione 2.0 abbiamo un workspace locale diverso per ogni versione. La versione 2.0 nacque con .NET 2.0 alla DevCon 2005 e fornisce la base del Business Layer e Data Access Layer (semplificati rispetto al nostro normale modello di sviluppo) su cui sono state costruire le estensioni WPF, WCF e WF all’uscita di .NET 3.0. Nella versione 3.5 abbbiamo inserto Linq To SQL come DAL pluggabile senza sporcare il modello delle entità e l’architettura, entrambi rimasti intatti dalla versione 2.0.
La versione 3.6 presentata a DevCon 2009 ha visto l’introduzione di Entity Framework 3.5 e dei Data Services nonchè altre piccole varianti mobile con SQLCE 3.5 e Linq to Object lato mobile.
La versione 3.7 che abbiamo analizzato con vari clienti è la directory creata stamani che accoglie l’ultima versione prima del passaggio (sul workspace current) alla versione .NET 4.0 e VSTS 2010: la versione 3.7, come indica il workspace contiene il Dal pluggabile per lavorare con la Table del Cloud Storage di Windows Azure e sfrutta il Service Bus di AppFabric in the cloud per la comunicazione fra i client e la parte server: abbiamo sempre utilizzato un service agent pluggabile senza toccare niente dal punto di vista del domain model e dell’architettura.
La solution, su VSTS 2008, prima dell’upgrade si presentava quindi con 90 progetti Visual Studio + una serie di Web Site (vedi più avanti):
Visto che sto lavorando offline (come si nota dall’immagine sopra) rispetto al controllo sorgenti, il primo passo di upgrade mi chiede se voglio riportare il progetto “online” prima di effettuare l’upgrade.
Ci sono rimasto un po’ male vedendo che il wizard sempre quello di Visual Studio 2005: mi immaginavo una maschera fotonica con animazioni 3D fatte in WPF…sto troppo vicino a Luca in questo periodo :-)
Il passo successivo è il riepilogo dei progetti da convertire che vi riporto per dare un’idea della complessità della soluzione demo che usiamo nelle conferenze e nei corsi di architettura.
Solution to be converted: EstatesManagement
Projects:
DevLeap.EstatesManagement
DevLeap.EstatesManagement.BIZ
DevLeap.EstatesManagement.DAL
DevLeap.EstatesManagement.DAL.Sql2005DataReader
DevLeap.EstatesManagement.Entities
DevLeap.Library.Dal.Sql2005
DevLeap.Library.Dal.Sql2005.Test
DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test
DevLeap.EstatesManagement.BIZ.Test
DevLeap.EstatesManagement.DB.SQL2005.Test
DevLeap.EstatesManagement.MSMQ
DevLeap.EstatesManagement.MSMQ.MSMQ30
DevLeap.Library.MSMQ.MSMQ30
DevLeap.Library.MSMQ.MSMQ30.Test
DevLeap.EstatesManagement.MSMQ.MSMQ30.Test
DevLeap.EstatesManagement.UI.WinForm.MSMQAccepter
DevLeap.EstatesManagement.UI.Web.Test
DevLeap.EstatesManagement.WindowsServices.EstatesMSMQAccepter
DevLeap.Library.Web.UI.AjaxControls
DevLeap.EstatesManagement.Web.Security
DevLeap.EstatesManagement.UI.Web
DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional
DevLeap.EstatesManagement.Mobile.UI.WinForm.Common
DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50
DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60
DevLeap.Library.Mobile.Dal.SqlCE31
DevLeap.Library.Mobile.Utility
DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader
DevLeap.EstatesManagement.Mobile.Dal
DevLeap.EstatesManagement.Mobile.BIZ
DevLeap.EstatesManagement.Mobile.Entities
DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader
DevLeap.Library.Mobile.Dal.Sql2005
DevLeap.EstatesManagement.Mobile
DevLeap.Library.Mobile.MSMQ30
DevLeap.EstatesManagement.Mobile.MSMQ
DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30
DevLeap.Library.Dal.SqlCe31
DevLeap.EstatesManagement.Mobile.ServiceAgent
DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20
DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Pro.Desktop
DevLeap.EstatesManagement.UI.WorkflowTracking
DevLeap.EstatesManagement.Workflow
DevLeap.EstatesManagement.Workflow.Activities
DevLeap.EstatesManagement.Workflow.Workflows
DevLeap.EstatesManagement.BIZ.Workflow
DevLeap.EstatesManagement.ServiceAgent
DevLeap.EstatesManagement.ServiceAgent.WCF30
DevLeap.EstatesManagement.WCF.Contracts
DevLeap.EstatesManagement.WCF.EntityMapper
DevLeap.EstatesManagement.WCF.Services
DevLeap.EstatesManagement.WCF.TcpHost
DevLeap.EstatesManagement.WCF.Security
DevLeap.EstatesManagement.UI.WinForm.SmartClient
DevLeap.EstatesManagement.UI.BIZ
DevLeap.EstatesManagement.UI.Entities
DevLeap.EstatesManagement.UI.WPF.SmartClient
DevLeap.EstatesManagement.Workflow.Activities.WCF
DevLeap.EstatesManagement.UI.Web.Mobile
DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSVGA
DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30
DevLeap.EstatesManagement.DAL.LinqToSql
DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService
DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader
DevLeap.Library.Mobile.Dal.SqlCE35
DevLeap.Library.Dal.SqlCe35
LancioVS2008Libreria
LancioVS2008Libreria.Test
ADONETDSHttpHost
DevLeap.EstatesManagement.ServiceAgent.ADONETDataService
DevLeap.EstatesManagement.DAL.LinqToEntities
ASPNET35MVC
ASPNET35MVC.Tests
DevLeap.EstatesManagement.BIZ.Rules
DevLeap.EstatesManagement.Security
DevLeap.EstatesManagement.BIZ.CustomHandlers
DevLeap.EstatesManagement.Dal.LinToSql.Test
DevLeap.EstatesManagement.Dal.LinqToEntities.Test
DevLeap.EstatesManagement.LINQtoEM
DevLeap.EstatesManagement.LINQtoEM.Consumer
DevLeap.EstatesManagement.DAL.AzureStorage
Azure
WebRole
DevLeap.EstatesManagement.ServiceBus.TcpHost
DevLeap.EstatesManagement.ServiceAgent.ServiceBus
Durante l’upgrade di alcuni progetti “Web Site” rimasti dalla versione 2.0 viene richiesto l’upgrade alla versione 4.0:
N.B. Avevamo scelto Web Site per semplificare ai clienti l’installazione della solution al tempo di Visual Studio 2005, prima della famosa “patch” per tornare ai Web Application Project.
La parte server di VSTS ovverto TFS è ospitato sul web e quindi per adesso è ancora in versione 2008.
Durante l’upgrade:
Un altro warning che si è presentato è il seguente sul progetto ASP.NET MVC (creato con MVC 1 per ASP.NET 3.5 lo scorso anno):
Durante l’upgrade del progetto ASP.NET MVC sono stati inseriti i file JavaScript relativi a JQuery all’interno del progetto:
Molti progetti riportano Converted: 2 – Not Converted: –1
Dpo il primo “oh…caz…” il dettaglio ci spiega il motivo; ad esempio il progetto BIZ creato con Workflow riporta quanto segue:
Il file di progetto non necessita di upgrade, mentre il progetto si.
I progetti contenenti Unit Test o Load Test vengono convertiti così:
Le cose strane che mi sono accadute (non hanno creato problemi) e che dovrò indagare prima di altri upgrade sono
1) Ho perso tutte le solution folder: i progetti sono tutti allineati sulla root della solution mentre nella solution di partenza erano divisi logicamente in Solution Folder: non erano divisi fisicamente su disco in raggruppamenti di directory, ma dalla file .sln. Poco male, visto che per fortuna abbiamo usato una naming convention molto rigida e quindi diventa facile riorganizzare le directory
2) Alcuni progetti sono stati marcati come “unvailable”, anche se, facendo Reload si ricaricano immediatamente. Ho provato a chiudere la solution e riaprirla e tutti i progetti si sono ricaricati automaticamente: quindi non occorre ricaricarli a mano uno per uno.
I progetti mobile, come sappiamo, non sono stati convertiti in quanto non supportati dall’installazione.
Come accadde per Visual Studio 2008, visto che anche 2010 supporta il multi-targeting, occorre poi portare i singoli progetti alla versione 4.0 o 4.0 Client Profile.
Adesso la prova più importante: Build…un solo errore…ed è colpa mia.
Quando sviluppo progetti mobile creo sempre un progetto desktop con i sorgenti linkati in modo da testare e debuggare l’applicazione sul desktop invece di testarla dall’emulatore che richiede tempi decisamente superiori in queste fasi: il progetto desktop è stato migrato correttamente ma una reference utilizzava SQLCE 3.5.1 e mi era rimasto il progetto in reference con la 3.5.0.
L’unico altro problema che ho avuto deriva da una reference: il progetto ASPNET20MobileSite è un sito che utilizza i Mobile Controls di ASP.NET: lo strato di user interface è centralizzato nell’assembly risultante dalla compilazione del progetto DevLeap.EM.UI.Web.Mobile. Mentre la conversione aveva allineato le reference alla versione 4.0 per il progetto web, non avevo ancora aggiornato il progetto dll alla versione corrispondente.
Lascio il log completo per i più curiosi.
Time of Conversion: giovedì 15 aprile 2010 11:57
Solution: EstatesManagement
Filename
Status
Errors
Warnings
EstatesManagement.sln
Converted
0
0
Conversion Report - EstatesManagement.sln:
Solution converted successfully
1 file
Converted: 1
Not converted: 0
0
0
Project: ADONETDSHttpHost
Filename
Status
Errors
Warnings
ADONETDSHttpHost\ADONETDSHttpHost.csproj
Converted
0
0
Conversion Report - ADONETDSHttpHost\ADONETDSHttpHost.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Filename
Status
Errors
Warnings
ASPNET35MVC\ASPNET35MVC.csproj
Converted
0
0
Conversion Report - ASPNET35MVC\ASPNET35MVC.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: ASPNET35MVC.Tests
Filename
Status
Errors
Warnings
ASPNET35MVC.Tests\ASPNET35MVC.Tests.csproj
Converted
0
0
Conversion Report - ASPNET35MVC.Tests\ASPNET35MVC.Tests.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: D:\Applications\EstatesManagement\Current\Azure\Azure.ccproj
Filename
Status
Errors
Warnings
Azure\Azure.ccproj
Converted
0
0
Conversion Report - Azure\Azure.ccproj:
Project converted successfully
The project file does not require conversion
1 file
Converted: 1
Not converted: 0
0
0
Project: D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.DB.SQL2005\DevLeap.EstatesManagement.DB.SQL2005.dbproj
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DB.SQL2005\DevLeap.EstatesManagement.DB.SQL2005.dbproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DB.SQL2005\DevLeap.EstatesManagement.DB.SQL2005.dbproj:
Beginning Upgrade
D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.DB.SQL2005\DevLeap.EstatesManagement.DB.SQL2005_2010-04-15T11_58_07.dbproj.old saved.
This project will be upgraded through these steps:
Setting the new project file version
Upgrade project file to include a new targets file.
Upgrade the ToolsVersion property.
Creating database schema provider entry
Upgrading sqlcmdvars, sqldeployment and sqlsettings files
Upgrading project references. ".dbmeta" file references will be converted to ".dbschema" references.
Upgrading refactorlog files
Upgrading the suppression file for database code analysis
Prepares the project to add new files to SCC.
Beginning upgrade...
Upgrading the project version
ProjectVersion was upgraded from 3.5 to 4.0.
Finished upgrading project version.
Upgrading to a new targets file
Finished upgrading to new targets file.
Upgrading to a new tools version
Finished upgrading to new tools version.
Upgrading project to include the correct database schema provider
Upgrading namespace for the Database Schema Provider
Upgraded DSP property to Microsoft.Data.Schema.Sql.Sql90DatabaseSchemaProvider.
Finished upgrading project to include the correct database schema provider.
Upgrading version and namespace of sqlcmdvars, sqldeployment and sqlsettings files
Finished upgrading sqlcmdvars, sqldeployment, and sqlsettings files
Upgrading references
Removing reference to 'Microsoft.SqlTypes.dbschema'. The project property 'LoadSqlClrTypes' now controls the inclusion of Sql Clr Types.
Finished upgrading references.
Upgrading operation names in refactorlog files
Finished upgrading refactorlog files
Upgrading the namespace and categories of the suppression file for database code analysis
Finished upgrading the namespace and categories of the suppression file for database code analysis
Saving upgraded files
D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.DB.SQL2005\DevLeap.EstatesManagement.DB.SQL2005.dbproj saved.
1 file
Converted: 1
Not converted: 0
0
0
Project: DevLeap.EstatesManagement
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement\DevLeap.EstatesManagement.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement\DevLeap.EstatesManagement.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.BIZ
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.BIZ\DevLeap.EstatesManagement.BIZ.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.BIZ\DevLeap.EstatesManagement.BIZ.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.BIZ.CustomHandlers
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.BIZ.CustomHandlers\DevLeap.EstatesManagement.BIZ.CustomHandlers.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.BIZ.CustomHandlers\DevLeap.EstatesManagement.BIZ.CustomHandlers.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.BIZ.Rules
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.BIZ.Rules\DevLeap.EstatesManagement.BIZ.Rules.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.BIZ.Rules\DevLeap.EstatesManagement.BIZ.Rules.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.BIZ.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.BIZ.Test\DevLeap.EstatesManagement.BIZ.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.BIZ.Test\DevLeap.EstatesManagement.BIZ.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.BIZ.Workflow
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.BIZ.Workflow\DevLeap.EstatesManagement.BIZ.Workflow.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.BIZ.Workflow\DevLeap.EstatesManagement.BIZ.Workflow.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.DAL
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DAL\DevLeap.EstatesManagement.DAL.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DAL\DevLeap.EstatesManagement.DAL.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.DAL.AzureStorage
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DAL.AzureStorage\DevLeap.EstatesManagement.DAL.AzureStorage.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DAL.AzureStorage\DevLeap.EstatesManagement.DAL.AzureStorage.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.DAL.LinqToEntities
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DAL.LinqToEntities\DevLeap.EstatesManagement.DAL.LinqToEntities.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DAL.LinqToEntities\DevLeap.EstatesManagement.DAL.LinqToEntities.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Dal.LinqToEntities.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Dal.LinqToEntities.Test\DevLeap.EstatesManagement.Dal.LinqToEntities.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Dal.LinqToEntities.Test\DevLeap.EstatesManagement.Dal.LinqToEntities.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
DevLeap.EstatesManagement.Dal.LinqToEntities.Test\SalesmanUpdate.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Dal.LinqToEntities.Test\SalesmanUpdate.loadtest:
The file was converted to the current format.
2 files
Converted: 3
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.DAL.LinqToSql
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DAL.LinqToSql\DevLeap.EstatesManagement.DAL.LinqToSql.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DAL.LinqToSql\DevLeap.EstatesManagement.DAL.LinqToSql.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Dal.LinToSql.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Dal.LinToSql.Test\DevLeap.EstatesManagement.Dal.LinToSql.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Dal.LinToSql.Test\DevLeap.EstatesManagement.Dal.LinToSql.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
DevLeap.EstatesManagement.Dal.LinToSql.Test\SalesmanUpdate.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Dal.LinToSql.Test\SalesmanUpdate.loadtest:
The file was converted to the current format.
2 files
Converted: 3
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.DAL.Sql2005DataReader
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DAL.Sql2005DataReader\DevLeap.EstatesManagement.DAL.Sql2005DataReader.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DAL.Sql2005DataReader\DevLeap.EstatesManagement.DAL.Sql2005DataReader.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test\DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test\DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test\SalesmanUpdate.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Dal.Sql2005DataReader.Test\SalesmanUpdate.loadtest:
The file was converted to the current format.
2 files
Converted: 3
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.DB.SQL2005.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.DB.SQL2005.Test\DevLeap.EstatesManagement.DB.SQL2005.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.DB.SQL2005.Test\DevLeap.EstatesManagement.DB.SQL2005.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Entities
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Entities\DevLeap.EstatesManagement.Entities.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Entities\DevLeap.EstatesManagement.Entities.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.LINQtoEM
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.LINQtoEM\DevLeap.EstatesManagement.LINQtoEM.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.LINQtoEM\DevLeap.EstatesManagement.LINQtoEM.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.LINQtoEM.Consumer
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.LINQtoEM.Consumer\DevLeap.EstatesManagement.LINQtoEM.Consumer.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.LINQtoEM.Consumer\DevLeap.EstatesManagement.LINQtoEM.Consumer.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Mobile
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile\DevLeap.EstatesManagement.Mobile.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile\DevLeap.EstatesManagement.Mobile.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile\DevLeap.EstatesManagement.Mobile.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.BIZ
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.BIZ\DevLeap.EstatesManagement.Mobile.BIZ.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.BIZ\DevLeap.EstatesManagement.Mobile.BIZ.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.BIZ\DevLeap.EstatesManagement.Mobile.BIZ.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.Dal
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.Dal\DevLeap.EstatesManagement.Mobile.Dal.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.Dal\DevLeap.EstatesManagement.Mobile.Dal.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.Dal\DevLeap.EstatesManagement.Mobile.Dal.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader\DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader\DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader\DevLeap.EstatesManagement.Mobile.Dal.Sql2005DataReader.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader\DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader\DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader\DevLeap.EstatesManagement.Mobile.Dal.SqlCe31DataReader.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReader.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService\DevLeap.EstatesManagement.Mobile.Dal.SqlCe35DataReaderSyncService.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.Entities
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.Entities\DevLeap.EstatesManagement.Mobile.Entities.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.Entities\DevLeap.EstatesManagement.Mobile.Entities.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.Entities\DevLeap.EstatesManagement.Mobile.Entities.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.MSMQ
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.MSMQ\DevLeap.EstatesManagement.Mobile.MSMQ.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.MSMQ\DevLeap.EstatesManagement.Mobile.MSMQ.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.MSMQ\DevLeap.EstatesManagement.Mobile.MSMQ.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30\DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30\DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30\DevLeap.EstatesManagement.Mobile.MSMQ.MSMQ30.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.ServiceAgent
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.ServiceAgent\DevLeap.EstatesManagement.Mobile.ServiceAgent.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.ServiceAgent\DevLeap.EstatesManagement.Mobile.ServiceAgent.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.ServiceAgent\DevLeap.EstatesManagement.Mobile.ServiceAgent.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30\DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30\DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30\DevLeap.EstatesManagement.Mobile.ServiceAgent.WCF30.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20\DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20\DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20\DevLeap.EstatesManagement.Mobile.ServiceAgent.WS20.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.UI.WinForm.Common
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.UI.WinForm.Common\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.UI.WinForm.Common\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM50.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60\DevLeap.EstatesManagement.Mobile.UI.WinForm.Common.WM60.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Pro.Desktop
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Pro.Desktop\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Pro.Desktop.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Pro.Desktop\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Pro.Desktop.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
References to Microsoft.Synchronization.Data were updated to Microsoft.Synchronization.Data 2.0.
References to Microsoft.Synchronization.Data.SqlServerCe were updated to Microsoft.Synchronization.Data.SqlServerCe 2.0.
References to Microsoft.Synchronization.Data.Server were updated to Microsoft.Synchronization.Data.Server 2.0.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60Professional.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSVGA
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSQVA\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSVGA.csproj
Converted
1
0
Conversion Report - DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSQVA\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSVGA.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSQVA\DevLeap.EstatesManagement.Mobile.UI.WinForm.WM60ProfessionalSVGA.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.EstatesManagement.MSMQ
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.MSMQ\DevLeap.EstatesManagement.MSMQ.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.MSMQ\DevLeap.EstatesManagement.MSMQ.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.MSMQ.MSMQ30
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.MSMQ.MSMQ30\DevLeap.EstatesManagement.MSMQ.MSMQ30.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.MSMQ.MSMQ30\DevLeap.EstatesManagement.MSMQ.MSMQ30.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.MSMQ.MSMQ30.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.MSMQ.MSMQ30.Test\DevLeap.EstatesManagement.MSMQ.MSMQ30.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.MSMQ.MSMQ30.Test\DevLeap.EstatesManagement.MSMQ.MSMQ30.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Security
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Security\DevLeap.EstatesManagement.Security.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Security\DevLeap.EstatesManagement.Security.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.ServiceAgent
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.ServiceAgent\DevLeap.EstatesManagement.ServiceAgent.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.ServiceAgent\DevLeap.EstatesManagement.ServiceAgent.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.ServiceAgent.ADONETDataService
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.ServiceAgent.ADONETDataService\DevLeap.EstatesManagement.ServiceAgent.ADONETDataService.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.ServiceAgent.ADONETDataService\DevLeap.EstatesManagement.ServiceAgent.ADONETDataService.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.ServiceAgent.ServiceBus
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.ServiceAgent.ServiceBus\DevLeap.EstatesManagement.ServiceAgent.ServiceBus.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.ServiceAgent.ServiceBus\DevLeap.EstatesManagement.ServiceAgent.ServiceBus.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.ServiceAgent.WCF30
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.ServiceAgent.WCF30\DevLeap.EstatesManagement.ServiceAgent.WCF30.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.ServiceAgent.WCF30\DevLeap.EstatesManagement.ServiceAgent.WCF30.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.ServiceBus.TcpHost
Filename
Status
Errors
Warnings
DevLeap.EstatesManagemenet.ServiceBus.TcpHost\DevLeap.EstatesManagement.ServiceBus.TcpHost.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagemenet.ServiceBus.TcpHost\DevLeap.EstatesManagement.ServiceBus.TcpHost.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.BIZ
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.BIZ\DevLeap.EstatesManagement.UI.BIZ.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.BIZ\DevLeap.EstatesManagement.UI.BIZ.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.Entities
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.Entities\DevLeap.EstatesManagement.UI.Entities.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Entities\DevLeap.EstatesManagement.UI.Entities.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.Web
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.Web\DevLeap.EstatesManagement.UI.Web.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web\DevLeap.EstatesManagement.UI.Web.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.Web.Mobile
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.Web.Mobile\DevLeap.EstatesManagement.UI.Web.Mobile.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Mobile\DevLeap.EstatesManagement.UI.Web.Mobile.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.Web.Test
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesListPaged.webtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesListPaged.webtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesListPagedLoad.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesListPagedLoad.loadtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesSalesmanList.webtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesSalesmanList.webtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesSalesmanListLoad.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\ASPNET20SiteEstatesSalesmanListLoad.loadtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\DevLeap.EstatesManagement.UI.Web.Test.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\DevLeap.EstatesManagement.UI.Web.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
DevLeap.EstatesManagement.UI.Web.Test\SalesmenList.webtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\SalesmenList.webtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\SalesmenList25Users.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\SalesmenList25Users.loadtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\SalesmenListSync.webtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\SalesmenListSync.webtest:
The file was converted to the current format.
DevLeap.EstatesManagement.UI.Web.Test\SalesmenListSync25Users.loadtest
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.Web.Test\SalesmenListSync25Users.loadtest:
The file was converted to the current format.
9 files
Converted: 10
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.WinForm.MSMQAccepter
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.WinForm.MSMQAccepter\DevLeap.EstatesManagement.UI.WinForm.MSMQAccepter.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.WinForm.MSMQAccepter\DevLeap.EstatesManagement.UI.WinForm.MSMQAccepter.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.WinForm.SmartClient
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.WinForm.SmartClient\DevLeap.EstatesManagement.UI.WinForm.SmartClient.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.WinForm.SmartClient\DevLeap.EstatesManagement.UI.WinForm.SmartClient.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.WorkflowTracking
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.WorkflowTracking\DevLeap.EstatesManagement.UI.WorkflowTracking.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.WorkflowTracking\DevLeap.EstatesManagement.UI.WorkflowTracking.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.UI.WPF.SmartClient
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.UI.WPF.SmartClient\DevLeap.EstatesManagement.UI.WPF.SmartClient.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.UI.WPF.SmartClient\DevLeap.EstatesManagement.UI.WPF.SmartClient.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.WCF.Contracts
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.WCF.Contracts\DevLeap.EstatesManagement.WCF.Contracts.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.WCF.Contracts\DevLeap.EstatesManagement.WCF.Contracts.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.WCF.EntityMapper
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.WCF.EntityMapper\DevLeap.EstatesManagement.WCF.EntityMapper.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.WCF.EntityMapper\DevLeap.EstatesManagement.WCF.EntityMapper.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.WCF.Security
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.WCF.Security\DevLeap.EstatesManagement.WCF.Security.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.WCF.Security\DevLeap.EstatesManagement.WCF.Security.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.WCF.Services
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.WCF.Services\DevLeap.EstatesManagement.WCF.Services.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.WCF.Services\DevLeap.EstatesManagement.WCF.Services.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.WCF.TcpHost
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.WCF.TcpHost\DevLeap.EstatesManagement.WCF.TcpHost.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.WCF.TcpHost\DevLeap.EstatesManagement.WCF.TcpHost.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Web.Security
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Web.Security\DevLeap.EstatesManagement.Web.Security.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Web.Security\DevLeap.EstatesManagement.Web.Security.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.WindowsServices.EstatesMSMQAccepter
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.WindowsServices.EstatesMSMQAccepter\DevLeap.EstatesManagement.WindowsServices.EstatesMSMQAccepter.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.WindowsServices.EstatesMSMQAccepter\DevLeap.EstatesManagement.WindowsServices.EstatesMSMQAccepter.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Workflow
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Workflow\DevLeap.EstatesManagement.Workflow.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Workflow\DevLeap.EstatesManagement.Workflow.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Workflow.Activities
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Workflow.Activities\DevLeap.EstatesManagement.Workflow.Activities.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Workflow.Activities\DevLeap.EstatesManagement.Workflow.Activities.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Workflow.Activities.WCF
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Workflow.Activities.WCF\DevLeap.EstatesManagement.Workflow.Activities.WCF.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Workflow.Activities.WCF\DevLeap.EstatesManagement.Workflow.Activities.WCF.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.EstatesManagement.Workflow.Workflows
Filename
Status
Errors
Warnings
DevLeap.EstatesManagement.Workflow.Workflows\DevLeap.EstatesManagement.Workflow.Workflows.csproj
Converted
0
0
Conversion Report - DevLeap.EstatesManagement.Workflow.Workflows\DevLeap.EstatesManagement.Workflow.Workflows.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.Dal.Sql2005
Filename
Status
Errors
Warnings
DevLeap.Library.Dal.Sql2005\DevLeap.Library.Dal.Sql2005.csproj
Converted
0
0
Conversion Report - DevLeap.Library.Dal.Sql2005\DevLeap.Library.Dal.Sql2005.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
Created rule set file "D:\Applications\EstatesManagement\Current\DevLeap.Library.Dal.Sql2005\Migrated rules for DevLeap.Library.Dal.Sql2005.ruleset" for the "Debug (Any CPU)" configuration.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.Dal.Sql2005.Test
Filename
Status
Errors
Warnings
DevLeap.Library.Dal.Sql2005.Test\DevLeap.Library.Dal.Sql2005.Test.csproj
Converted
0
0
Conversion Report - DevLeap.Library.Dal.Sql2005.Test\DevLeap.Library.Dal.Sql2005.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.Dal.SqlCe31
Filename
Status
Errors
Warnings
DevLeap.Library.Dal.SqlCe31\DevLeap.Library.Dal.SqlCe31.csproj
Converted
0
0
Conversion Report - DevLeap.Library.Dal.SqlCe31\DevLeap.Library.Dal.SqlCe31.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.Dal.SqlCe35
Filename
Status
Errors
Warnings
DevLeap.Library.Dal.SqlCe35\DevLeap.Library.Dal.SqlCe35.csproj
Converted
0
0
Conversion Report - DevLeap.Library.Dal.SqlCe35\DevLeap.Library.Dal.SqlCe35.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.Mobile.Dal.Sql2005
Filename
Status
Errors
Warnings
DevLeap.Library.Mobile.Dal.Sql2005\DevLeap.Library.Mobile.Dal.Sql2005.csproj
Converted
1
0
Conversion Report - DevLeap.Library.Mobile.Dal.Sql2005\DevLeap.Library.Mobile.Dal.Sql2005.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.Library.Mobile.Dal.Sql2005\DevLeap.Library.Mobile.Dal.Sql2005.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.Library.Mobile.Dal.SqlCE31
Filename
Status
Errors
Warnings
DevLeap.Library.Mobile.Dal.SqlCE31\DevLeap.Library.Mobile.Dal.SqlCE31.csproj
Converted
1
0
Conversion Report - DevLeap.Library.Mobile.Dal.SqlCE31\DevLeap.Library.Mobile.Dal.SqlCE31.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.Library.Mobile.Dal.SqlCE31\DevLeap.Library.Mobile.Dal.SqlCE31.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.Library.Mobile.Dal.SqlCE35
Filename
Status
Errors
Warnings
DevLeap.Library.Mobile.Dal.SqlCE35\DevLeap.Library.Mobile.Dal.SqlCE35.csproj
Converted
1
0
Conversion Report - DevLeap.Library.Mobile.Dal.SqlCE35\DevLeap.Library.Mobile.Dal.SqlCE35.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.Library.Mobile.Dal.SqlCE35\DevLeap.Library.Mobile.Dal.SqlCE35.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.Library.Mobile.MSMQ30
Filename
Status
Errors
Warnings
DevLeap.Library.Mobile.MSMQ30\DevLeap.Library.Mobile.MSMQ30.csproj
Converted
1
0
Conversion Report - DevLeap.Library.Mobile.MSMQ30\DevLeap.Library.Mobile.MSMQ30.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.Library.Mobile.MSMQ30\DevLeap.Library.Mobile.MSMQ30.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.Library.Mobile.Utility
Filename
Status
Errors
Warnings
DevLeap.Library.Mobile.Utility\DevLeap.Library.Mobile.Utility.csproj
Converted
1
0
Conversion Report - DevLeap.Library.Mobile.Utility\DevLeap.Library.Mobile.Utility.csproj:
Project converted successfully
Project converted successfully
The project file 'D:\Applications\EstatesManagement\Current\DevLeap.Library.Mobile.Utility\DevLeap.Library.Mobile.Utility.csproj' cannot be opened. The project type is not supported by this installation.
1 file
Converted: 2
Not converted: -1
1
0
Project: DevLeap.Library.MSMQ.MSMQ30
Filename
Status
Errors
Warnings
DevLeap.Library.MSMQ.MSMQ30\DevLeap.Library.MSMQ.MSMQ30.csproj
Converted
0
0
Conversion Report - DevLeap.Library.MSMQ.MSMQ30\DevLeap.Library.MSMQ.MSMQ30.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
Created rule set file "D:\Applications\EstatesManagement\Current\DevLeap.Library.MSMQ.MSMQ30\Migrated rules for DevLeap.Library.MSMQ.MSMQ30.ruleset" for the "Debug (Any CPU)" configuration.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.MSMQ.MSMQ30.Test
Filename
Status
Errors
Warnings
DevLeap.Library.MSMQ.MSMQ30.Test\DevLeap.Library.MSMQ.MSMQ30.Test.csproj
Converted
0
0
Conversion Report - DevLeap.Library.MSMQ.MSMQ30.Test\DevLeap.Library.MSMQ.MSMQ30.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: DevLeap.Library.Web.UI.AjaxControls
Filename
Status
Errors
Warnings
DevLeap.Library.Web.UI.AjaxControls\DevLeap.Library.Web.UI.AjaxControls.csproj
Converted
0
0
Conversion Report - DevLeap.Library.Web.UI.AjaxControls\DevLeap.Library.Web.UI.AjaxControls.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
Created rule set file "D:\Applications\EstatesManagement\Current\DevLeap.Library.Web.UI.AjaxControls\Migrated rules for DevLeap.Library.Web.UI.AjaxControls.ruleset" for the "Debug (Any CPU)" configuration.
1 file
Converted: 2
Not converted: -1
0
0
Project: LancioVS2008Libreria
Filename
Status
Errors
Warnings
LancioVS2008Libreria\LancioVS2008Libreria.csproj
Converted
0
0
Conversion Report - LancioVS2008Libreria\LancioVS2008Libreria.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Project: LancioVS2008Libreria.Test
Filename
Status
Errors
Warnings
LancioVS2008Libreria.Test\LancioVS2008Libreria.Test.csproj
Converted
0
0
Conversion Report - LancioVS2008Libreria.Test\LancioVS2008Libreria.Test.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Filename
Status
Errors
Warnings
WebRole\WebRole.csproj
Converted
0
0
Conversion Report - WebRole\WebRole.csproj:
Project converted successfully
Project converted successfully
Scan complete: Upgrade not required for project files.
1 file
Converted: 2
Not converted: -1
0
0
Conversion Settings
Solution File: D:\Applications\EstatesManagement\Current\EstatesManagement.sln
Log Number: 7
Solution File: D:\Applications\EstatesManagement\Current\EstatesManagement.sln
Log Number: 7
Autore: Roberto Brunetti - DevLeap
Per questo articolo facciamo qualcosa di più concreto come abbiamo fatto nel precedente: Ho preso il nostro ormai famoso progetto EstatesManagement (una parte dei 75 progetti, ho preso solo il BIZ+DAL+UI via Web) presentato alla DevCon 2005, 2007 e 2008 nelle sue varie evoluzioni e che vedrà altre evoluzioni verso .NET 4.0 e VSTS 2010 nella nostra prossima conferenza: DevCon 2009. [Update: Link]
Una delle idee dietro la nuova versione di VSTS 2010 è fornire strumenti per analizzare e comprendere il codice esistente. Partiamo proprio dall’analisi dell’applicazione EstatesManagement.
Understanding Existing Assets (Bottom-up Approach)
Nota: ho portato i vari progetti alla 4.0 durante la conversione (e lo fa per tutti: anche dll).
Architecture Explorer
Si parte dalla definizione delle classi: Class View

Cliccando sulla classe
![clip_image002[1]](http://devlab.devleap.it/robertob/immagini/posts/VisualStudio2010primocontattoparte4_D5FC/clip_image0021_thumb.jpg)
Si espandono i “contenuti”
Fra un “tab” e l’altro si può vedere

Che consente di filtrare per Classi – Comunicazione Outbound, inbound, cose non implementate
Ed eseeguire comandi come Generate WorkItem, Opem, Inser Into ActiveDiagram
Dalle classi invece il filtro contiene Field, Method, altri classi, inherits from, called by, container by. Cose pubbliche, chi sono i derivati di questa classe etc
Scendendo sui metodi si vedono le chiamate
Grafici standard
Generate New Graph a partire dalla selezione corrente
si aggiunge poi quello che voglio analizzare (con la doppia freccia verde)

Aggiungendo Salesman e BaseEntity

Si può arrivare a livello di field

Doppio click su un pezzo si va nel codice reale evidenziato rispetto al resto (Es su SalesmanDescritpion)

Dipendenze fra Assemblies o Namespace o Classi
Es relazione fra Namespace

Le linee sono più fini o più spesse a seconda del livello di dipendenza: semplicemente le linee molto fini hanno 1 sola chiamata (quindi dipendenza), mentre le linee più spesse hanno molte chiamate.
Si può espandere ogni nodo per ottenere le classi contenuti

Esempio di dipendenze fra classi (e all’interno i vari membri)

Se si seleziona una classe appaiono i link dettagliati verso gli altri namespace (o classi se il namespace di arrivo è aperto) e con il “cazzillo” si può andare da una parte all’altra del link:
Ogni diagramma è in XML con estensione .dgml. Si possono salvare per analisi future
Ci sono varie viste: ad esempio a Stack, TreeView, Nested Stack,
Sequence diagram
Sempre continuando con la parte di “Understanding existing code” è possibile generare il Sequence Diagram UML a partire da qualunque metodo esistente e tenere sincronizzato il tutto.
Da qualunque metodo si può fare tasto destro:

Questo il diagramma del nostro metodo List filtrando per Current Project (quindi si vedono le chiamate fatte solo alle “cose” di questo progetto

Filtrando per entire solution il nostro metodo che chiama la nostra libreria SqlHelper (simile a DAAB), crea una classe SalesmanList (che sta nel progetto .Entities) e nel ciclo costruisce la classe Salesmna (anch’essa nel progetto .Entities) per ogni venditore del DB, si ottiene questo. N.B. ho fatto due screenshot perchè oggi la mia macchina virtuale non ne vuole sapere del copia/incolla fuori verso l’host.


Il diagramma creato prende l’estensione .sequence e viene inserito nel progetto da cui è stato “reserverd engineered”
Su ogni elemento si può fare tasto destro per vedere il tutto nell’architecture explorer, per scendere nel codice o creare (nel caso di aggiunta di “pezzi”) classi o interfarfacce.
Layer Diagram
Sta in un progetto di tipo Modeling (dove ci stanno i diagrammi UML)
Ad esempio nel nostro caso anche il Biz Layer effettivo ha chiamate verso il progetto Entitiy che sta sotto Entitities. Ecco gli errori

I Layer possono essere anche composti da SottoLayer per dividere ulteriormente i layer: vedere i due articoli precedenti a partire da http://blogs.devleap.com/articolidevleap/archive/2008/10/26/visual-studio-team-system-2010-primo-contatto-parte-2.aspx.
Links agli articoli precedenti:
Intro Parte 1 : http://blogs.devleap.com/articolidevleap/archive/2008/10/17/visual-studio-team-system-2010-primo-contatto.aspx
Intro Parte 2: http://blogs.devleap.com/articolidevleap/archive/2008/10/26/visual-studio-team-system-2010-primo-contatto-parte-2.aspx
Parte 3 Testing & Dubugging: http://blogs.devleap.com/articolidevleap/archive/2008/11/30/visual-studio-2010-primo-contatto-parte-3.aspx
Link a .NET 4.0
Workflow Foundation 4.0: http://blogs.devleap.com/articolidevleap/archive/2008/11/17/workflow-foundation-4-0-introduzione-alle-ctp-ottobre-2008.aspx
Dublin: http://blogs.devleap.com/articolidevleap/archive/2008/11/14/dublin-windows-application-server.aspx
Autore: Roberto Brunetti - DevLeap
In questa puntata ci concentriamo su Testing & Dubugging.
In VSTS2010 è stato aggiunto il supporto per la validazione della user interface. Si parte da un progetto di test con Add New Guided UI Test.

Si sceglie “Launch recorder to generate code”
L’idea è registrare cosa stiamo facendo e generare il codice per testare user interface WinForm e ASP.NET (e forse WPF già nella release 2010). Per Silverlight probabilmente occorrera aspettare ancora un pò.
Ad esempio eseguendo qualche click su questo form si ottiene questa registrazione

Poi si genera il codice tramite il pulsante evidenziato “Generate Code” (stare molto attenti: si siamo in debug posizionare prima il cursore nel metodo di test prima di generare il codice); meglio lanciare prima l’applicazione e poi registrare, al posto di fare F5 diretto da Visual Studio: almeno in questa prima CTP di ottobre 2008.
Per fare la validazione della UI si prende il UI Control Validator (tasto destro sul metodo o sul recorder a registrazione ferma premere “Add Validation”)

Per attivare il controllo si prende il “cazzillo” bianco in alto a destra del panel UI Control Finder e si fa un drag sul controllo da testare: con le freccie ci si può spostare sui controlli da testare.
Per ogni controllo si fa Add e al termine (done) si vedono le proprietà di ogni controllo e si impostano i valori di test.

In questo caso dichiaro che il checkbox deve essere disabilitato. Come si nota si possono controllare più o meno tutte le proprietà (colori, visibilità, focus, etc)
Al lanio del test creato viene fatta girare di nuovo l’applicazione (compleso l’F5 da VS se è stato registrato o l’apertura del browser, oppure l’applicazione deve essere aperta):

Sono andato anche a fare un giro sul sistema operativo per capire come si muove il registratore, e direi che ci segue molto bene.

Si vede la registrazione del Task Manager.
Per far partire un test si può fare tasto destro sul test e Run oppure, come sempre dalla finestra Test View.
Gli Unit Test aiutano, ma in VSTS 2010 si va oltre con Impact Analysis e Historical Debugger, affrontati in modo introduttivo nella prossima sezione.
Vediamo prima qualche dettaglio sul codice generato dallo strumento “Coded UI Test”.
Oltre al file xxx.cs, visto in precedenza, per il coded test viene creato
1) un file UIMap.uitest
2) un file RecordedMethods.cs
3) UserControl.cs
UIMap.uitest è in xml e contiene per ogni elemento (ad esempio la lstElementi)

Il file .cs allegato contiene le classi che consentono a questo framework di recuperare i controlli, definire i criteri di ricerca dei controlli andando per derivazione di alcune classi base: ad esempio LstElementWindow del file xml precedente è un classe definità così:

Si deriva da WinWindow restituendo nel Get di LstElementList l’elemento di tipo WinList (ricordiamoci che si tratta di una finestra Windows Form).
Il codice dello unit test usa metodi del file RecordedMethods.cs che rappresentano i metodi da lanciare per eseguire il test: questi metodo sono stati creati durante la registrazione:

Questi metodi rappresentano appunto lo unit test da eseguire. Ecco il codice:

Come si nota viene creata la struttura per cercare, nel nostro esempio, il pulsante denominato Controlla (dove “Controlla” è il Text del pulsante) e su questo viene eseguito un click nel punto 39,13.
Il file UserControls.cs (che immagino verrà poi inserito nelle librerie Microsoft e non all’interno di ogni progetto come adesso) contiene la definizione dei vari controlli utilizzati (WinButton, WinCheckBox) e ne definisce le proprietà visibili (e quindi controllabili dalla mascherina di validazione del Coded UI Test).
Software Diagnostics and QoS
Bug Prevention: trovarli il prima possibile è la nostra missione.
Oggi in VSTS 2008: Code Analysis per trovare qualche defect strutturale o codice scritto male, ancora prima di lanciare il codice. Il Profiler inoltre ci da una mano fin dalle prime fasi. Poi ci sono gli Unit Test che possono intercettare i bug, poi in produzione si può fare tracing. VSTS 2010 prosegue su questa linea aggiungendo due strumenti interni e uno strumento esterno.
VSTS2010 prima feature: Impact Analysis

Quando cambio del codice vedo quali test dovrebbero essere lanciati perchè la modifica impatta il codice testato da questo test. Nella finestra selezionare il team project e la relativa build.
Serve una Build perchè il mapping fra test e metodi sta dentro TFS insieme ai risultati dei test. Il file di mapping server-side viene caricato in VS per controllare questo mapping.
Selezionata la Build e modificando qualche riga di codice, al momento del salvataggio viene controllato l’impatto:

In questo caso la modifica che ho fatto (che ha toccato il metodo Somma) non impatta su nessun test. Questo strumento consente di verificare anche il famoso problema “One Code Line Change”: non preoccupiamoci, sto modificando solo una riga di codice, non posso fare casino J. In questo modo sappiamo a colpo d’occhio quali test dover rilanciare. Esiste poi (almeno l’ho trovata nel prodotto) una check in policy che consente di bloccare un check-in se il codice modificato ha impattato su alcuni test che non sono stati lanciati:

Inoltre la finestra “Test Impact Analysis” indica anche tutti i metodi modificati dall’ultimo check-out: è quindi comodissimo anche per capire cosa ha fatto; sembra una cosa stupida, ma durante lo sviuppo di una parte di codice spesso (almeno a me accade) saltiamo da un metodo all’altro, e spesso siamo interrotti da una mail (la prima regola dello sviluppatore è: tenere posta, messenger, skype, telefonino...SPENTI, ma spesso questa non è la situazione classica in molti uffici). Ritornando sul codice occorre capire dove eravamo, cosa avevamo fatto: la finestrina aiuta in questo !
Secondo strumento: Hystorical Debugger
Serve per tornare al punto esatto per capire cosa abbiamo fatto durante il debugging.
Si può vedere cosa è successo prima del nostro breakpoint o dove abbiamo riscontrato un errore: in pratica si può risalire la call stack per capire cosa è successo precedentemente (cosa è accaduto nel metodo che ha chiamato questo metodo).
Facendo un esempio stupido, ma spero calzante: al click di un pulsante eseguo questo codice:

Sperando che si legga nel post (ingrandire l’immagine cliccandoci sopra), si nota che chiamo la funzione Somma, dal click del mio pulsante, una prima volta con due numeri random (potrebbero essere presi ovviamente da un record nel DB o da un file di testo). Al risultato delle mia funzione devo aggiungere un altro valore (sempre random nel mio semplice caso, ma ancora una volta può arrivare da dove vogliamo).
Al primo breakpoint si vede che a e b sono 1402002823. Ecco lo screenshot, notare la finestra “Locals”

Facendo F5 chiaramente mi rifermo al breakpoint di Somma e i due valori saranno diversi, come si nota sempre dalla finestra “Locals”:

Se mi accorgo di un problema sul valore che mi aspetto, ad oggi in VSTS 2008 è difficile capire quali valori erano stati usati nella prima somma (magari i dati nel DB sono cambiati, o il file di testo ha numeri diversi perchè nel processo è stato aggiornato: controllare quindi i dati di origine diventa complicato se non imposssibile) è ed un casino risalire alla natura del problema e capire perchè sono finito in questa situazione.
L’hystorical debugger consente di risalire lo stack per verificare cosa è successo: nella finestra a destra si vede infatti che il Thread 5936 ha eseguito il metodo Main che ha chiamato il form su cui si è eseguito il metodo cmd_HistoricalDebugger (è il nome del mio pulsante) il quale adesso sta eseguendo il metodo Somma. Fin quì niente di particolare: è una call stack.
Se però faccio doppio click sul metodo cmd_HystoricalDebugger vedo che ho eseguito due volte il metodo somma:

E facendo doppio click sul primo ritorno indietro nella storia e posso analizzare nuovamente il contenuto delle variabili sia nella classiche mascherine Auto/Local/Immediate/Watch o nella finestra in basso a destra:

Come si nota si vedono i valori 1402002823 per a e b del metodo somma che erano i valori passati alla prima chiamata a Somma.
Pensato al caso in cui stiamo debuggando il Business Layer su dati arrivati dal DataAccess Layer: ci accorgiamo nell’esecuzione del codice che qualche dato non è come ci aspettavamo; possiamo tornare su cosa ha fatto il DataAccess Layer per capire come mai ci sono arrivati dati strani, possiamo capire se i metodi che abbiamo richiamato hanno fatto il loro lavoro.
Per abilitare o configurare il tutto dal Tools/Options, nella sezione debug.

Terzo Strumento: Test Activity Center “Camano”
Non occorre VS, è una interfaccia stand-alone con accesso diretto a TFS, che presenta i vari test case per ogni progetto.
L’idea è risolvere il problema “It works on my machine” e il Test RePro ovvero il problema di riprodurre un bug che durante la fase di test si è verificato, ma che non “esce fuori” sulla macchina di sviluppo. Migliora anche la collaborazione fra Tester e Developer: sono presenti vari collector (configurabili) per prendere informazioni sul sistema che viene testato (Action Log, Event Log, System Information, Debug Information, Video Recorder anche !). Quindi quando si lancia un test case vengono registrate tutte queste informazioni.
Il test case è composto da varie attività da eseguire manualmente per fare le verifiche. Ogni attività ha poi il flag per indicare se è andato a buon fine o no.
Questa una idea rispetto al form che abbiamo già visto all’inizio di questo articolo. Lavoriamo senza collegare Requirements o Build per semplicità e per rimanere sul punto senza divagare:

Una volta creato il Test Case, dal Test Suite Manager si organizzano i test case in Test Suite. Ad esempio, ho creato una Test Suite denominata Prove UI Manuali che comprende il test case “Visualizzazione Listbox” creato in precedenza:

Per organizzare meglio il lavoro si organizzano i piani di itest in Test Plan: ecco la creazione del Test Plan “Prove su User Interface Manuali” che comprende la test suite appena creata. Per ogni Plan si indicano i classici Test Run, a cui però in VSTS2010 si associa un nome, una suite e una configurazione.

E’ tutto, possiamo tornare sulla home page del menù testing per lanciare il nostro test e raccogliere i dati. Notare nello screenshot seguente la gerarchia appena creata: un Test Run contiene varie Test Suite che a loro volta contengono i test case:

Quindi da “Prove su User Interface Manuali” che rappresenta il nostro piano di esecuzione dei test, si sceglie la suite e al suo interno il test case.
Prima di fare Run assicuriamoci che la nostra Test Settings contenga le informazioni che desideriamo registrare. Ho abilitato quasi tutto J

A questo punto si lancia il test case e con Automation, si registrano tutte le fasi del manual test. Alla fine del processo si può fare il replay delle cose fatte a mano durante la registrazione. Ci sono molti hook nel CLR dove questi strumenti si agganciano quindi non occorre prevedere degli hook nel nostro codice per fare le prime analisi.
Sto ripercorrendo i primi due step che risultano ok e quindi ho marcato come “Pass”:

Adesso clicco sul pulsante Controlla e marco lo step come Pass, ma visto che ripremendo il pulsante non accade quello che ho segnato come controllo sullo step da effettuare marco l’ultimo step come fallito:

Da questo punto posso poi rivedere la registrazione, ripercorrere gli step e controllare eventuali commenti inseriti (ci sono molte opzioni, come ad esempio l’inserimento di checkpoint, ma in questo articolo introduttivo direi di saltarli per non complicarci la vita).
I risultato vengono pubblicati e sono disponibili i report su quanto abbiamo fatto. Nel nostro semplice caso abbiamo un solo test case, in una sola suite, con una sola configurazione, con un solo Test Plan, quindi il risultato è alquanto banale:

Facendo doppio click sul test fallito si apre la maschera di dettaglio:

E ovviamente cliccando sul video (in basso TC280.wmw) si ripercorre cosa abbiamo fatto.

Nel log (TC280.txt) invece sono presenti queste voci:

Se troviamo un bug, alla creazione del bug su TFS vengono aggiunte tutte le informazioni raccolte.
Ovviamente molte di queste operazioni si possono fare da Visual Studio, o meglio dal Team Explorer, da cui poi si possono rivedere i dati. Entrambi gli strumenti lavorano sullo stesso store:

Direi che anche per questo terzo articolo della serie Visual Studio 2010 ci fermiamo.
Alla prossima.
Links a precedenti articoli:
Intro Parte 1 : http://blogs.devleap.com/articolidevleap/archive/2008/10/17/visual-studio-team-system-2010-primo-contatto.aspx
Intro Parte 2: http://blogs.devleap.com/articolidevleap/archive/2008/10/26/visual-studio-team-system-2010-primo-contatto-parte-2.aspx
Link a .NET 4.0
Workflow Foundation 4.0: http://blogs.devleap.com/articolidevleap/archive/2008/11/17/workflow-foundation-4-0-introduzione-alle-ctp-ottobre-2008.aspx
Dublin: http://blogs.devleap.com/articolidevleap/archive/2008/11/14/dublin-windows-application-server.aspx
Autore: Roberto Brunetti - DevLeap
La prima novità che salta all’occhio è il nuovo designer, fatto in WPF e sicuramente per adesso non definitivo nella forma e colori.

La toolbox presenta molte activity aggiuntive, alcune delle quali tratteremo in questa breve introduzione.
Partiamo con la prima novità che riguarda le custom activity: in questo semplice progetto, come si nota, ho creato un progetto separato “CustomActivities” con due semplicissime activity: ReadLine e WriteLine; la prima ha il compito di leggere gli input dell’utente nel prompt dei comandi e la seconda di eseguire una Console.WriteLine.
Partiamo dalla seconda: per eseguire una WriteLine su Console l’activity ha bisogno del testo da visualizzare; in WF 4.0 è possibile definire i parametri di input e di output delle activity senza la necessità dover creare proprietà pubbliche o dependency property come avviene per la versione.
Questo il codice dell’activity:

E’ stato definito in parametro di input (InArgument), che, sfruttando i generics dichiara che il tipo di parametro è string. L’altra novità rispetto alla versione 3.0/3.5 è la derivazione della nostra activity da WorkflowElement e non da Activity.
L’override del metodo Execute sin dalla versione 3.0 consente di indicare il codice da eseguire durante l’esecuzione dell’attività e riceve come sempre l’AEC (Activity Execution Context); per recuperare il valore del parametro di input si esegure this.Text.Get passando sempre il Context. Visto che il tipo di parametro è string non occorrono altre operazioni per estrarre eventuali proprietà di una classe più complessa che potremmo ricevere.
Stesso ragionamento per il parametro di output dell’activity (anzi del WorkflowElement) ReadLine; ecco il codice:

In questo caso si usa il Set su outArguments passando sempre l’AEC ricevuto.
E’ possibile definire le activity interamente in XAML: ecco un esempio di activity che definisce tre parametri

Costruite le activity (in xaml o in code) siamo quindi pronti per il nostro primo worfklow 4.0 che può utilizzare in sequenza WriteLine e Readline per chiaccherare con l’utente tramite la console.
Ecco il flusso completo
![clip_image002[1]](http://devlab.devleap.it/robertob/immagini/posts/Workflow.0IntroduzionealleCTPOttobre2008_114BC/clip_image0021_thumb.jpg)
La prima WriteLine deve scrivere a video un messaggio che l’autore del workflow può impostare liberamente. Nelle proprietà dell’activity (come nella versione 3.x) è possibile indicare il valore del parametro direttamente nel designer:

La successiva ReadLine, come abbiamo visto nel codice, espone un parametro di output denominato outArgument che dovrà essere passato come parametro di input alla successiva WriteLine che semplicemente lo visualizzerà nella console.
In WF 3.x questo passaggio di informazioni fra una activity e l’altra era prossibile tramite il binding diretto fra la proprietà di una activity e la proprietà dell’altra activity: il meccanismo è sicuramente semplice ma crea un problema di “scope” ovvero: quando muoiono questa variabili esposte dalle activity ? Visto che teoricamente è possibile fare il binding fra activity presenti nell’intero workflow sequenzale, il runtime del workflow è costretto a tenere in vità questa variabili fino a che non termina l’instanza del workflow, cosa che causa anche un tempo di serializzazione/deserializzazione durante la persistenza del workflow.
In WF 4.0 è possibile creare variabile all’interno di uno scope: ad esempio è possibile definire una variabile a livello di sequence che quindi “nascerà” all’inizio della sequenza e “morirà” alla fine della stessa. Queste variabili sono poi bindable verso i parametri di input e output delle varie activity presenti nello stesso scope, ovvero nella sequence per proseguire con il nostro esempio.

La finestrina si apre dall’apposito pulsante nella toolbar in basso e consente di definire appunto le variabili visibli all’interno dello scope. La finestra mostra anche le variabili disponibili che arrivano da variabili definite nel contenitore dell’activity corrente: ad esempio inserendo una sequnce all’interno del nostro flusso principale (che a sua volta è una sequence) avremmo la visibilità della variabili della sequence padre anche sul figlio. Ecco la figurina esemplificativa:

Come si nota le variabili Destinazione (e Prezzo) sono disponibili anche nella sequence figlia e vengono separate come visualizzazione nella parte alta Variable Available.
Le variabili a livello di scope possono essere bindate ai parametri di input e output della varie activity: nel nostro caso quindi ospitiamo la destinazione del nostro volo nella variabile Destinazione durante la ReadLine e la WriteLine successiva. La Readline avrà il parametro di ouput legato alla Destinazione e la WriteLine avrà il parametro di input legato sempre a Destinazione.
Ecco la Readline:

Per adesso l’editor, in questa primissima CTP di Ottobre 2008, non fornisce nessun aiuto (tipo intellisense e enumeration) delle variabili disponibili.
La WriteLine nel nostro flusso farà esattamente la stessa cosa...o quasi. E’ possibile legare ad un parametro di input (o anche ad una variabile) una espressione: il binding quindi consente non solo di legare un parametro ad un altro, ma anche di eseguire una espressione durante il binding. Ad esempio la nostra WriteLine può contenere questo:

In questo semplice esempio abbiamo lavorato con delle stringhe, ma ovviamente il discorso è valido per altri tipi di dato: questo consente all’autore di workflow di eseguire espressioni (che vengono valutate dal runtime) per assegnare (o anche leggere) valori ai parametri o alle variabili.
Per avviare un workflow (o meglio una istanza di workflow) nel WF 3.0/3.5 occorre avviare il workflow runtime, configurarlo adeguatamente e poi chiedere al workflow runtime di creare e avviare una istanza. Questo il codice di una applicazione console per rimanere in tema:
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
#endregion
namespace _5_CodeCondition
{
class Program
{
static void Main(string[] args)
{
using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
{
AutoResetEvent waitHandle = new AutoResetEvent(false);
workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
{waitHandle.Set();};
workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs
e)
{
Console.WriteLine(e.Exception.Message);
waitHandle.Set();
};
WorkflowInstance instance =
workflowRuntime.CreateWorkflow(typeof(_5_CodeCondition.Workflow1));
instance.Start();
waitHandle.WaitOne();
}
}
}
}
Ecco il codice 4.0, direi molto più semplice per avviare la sequnce del nostro esempio.
namespace TravelSearchApplication
{
using System;
using System.Linq;
using System.Threading;
using System.WorkflowModel;
using System.WorkflowModel.Activities;
class Program
{
static void Main(string[] args)
{
Sequence s;
AutoResetEvent syncEvent = new AutoResetEvent(false);
WorkflowInstance myInstance = WorkflowInstance.Create(new Sequence1());
myInstance.Completed += delegate(object sender, WorkflowCompletedEventArgs e) { syncEvent.Set(); };
myInstance.Resume();
syncEvent.WaitOne();
}
}
}
Complicando leggermente il nostro esempio supponiamo di voler chiamare un servizio WCF che espone le informazioni sui voli in base alla destinazione scelta. A prescindere da come viene creato il servizio server-side (in WCF 4.0 è possibile definire un servizio interamente in xaml), tramite la ClientOperation possiamo indicare il binding, il contratto e la operation da eseguire:

Nel nostro caso usiamo BasicHttpBinding sull’endpoint localhost:8080/service1.xamlx (come accennavo il servizio è scritto interamente in xaml) utilizzando il contratto Contract1 sulla operazione GetData.
Anche in questo caso è possibile fare binding fra i parametri di ingresso e uscita dell’operation rispetto alla variabili del nostro scope:

Il servizio accetta in input il parametro String denominato Destination a cui è verrà legata la nostra ormai famosa variabile Destinazione e risponderà con un valore Double denominato Prezzo che viene legato alla nostra variabile Prezzo (che verrà poi usata per visualizzare il risultato).
L’ultima activity del nostro workflow non farà altro che passare la variabile Prezzo alla WriteLine con il metodo che ormai conosciamo:

Personalmente non mi è chiara una cosa: abbiamo dovuto usare VB.NET per eseguire l’espressione di conversione del Prezzo (Double) in String. Sulla documentazione attuale e alla Professional Developers Conference 2008 di Losa Angeles è stato indicato che le espressioni si scrivono appunto solo in VB.NET. Non ho capito se l’idea sarà questa anche per la versione finale del prodotto o semplicemente per adesso. My Fault.
Anche se l’articolo è su WF 4.0, vale la pena di dare un occhio a come ho realizzato il servizio server-side: no code ! La descrizione del contratto, delle operation e relativi parametri, nonchè le operazioni da eseguire a fronte di una chiamata sono definite in xaml. Ecco la parte server:

L’activity utilizzata descrivere il contratto è ServiceOperation ed è ospitata nel file service1.xamlx (la x finale indica al motore di esecuzione che si tratta di un servizio attivabile tramite WAS di IIS7).
Per descrivere il contratto (non è la maschera definitiva) si usa questa mascherina:

E scendendo in Edit sull’operazione GetData si vedono i parametri Destination e Prezzo che sonon stati usati nella client operation che abbiamo descritto prima:

E’ possibile indicare il flusso Two-way o One-way e il supporto alle transazioni, oltre ai criteri ovvi di security.
Torniamo a WF 4.0: per calcolare il prezzo, il nostro servizio deve eseguire alcune regole in base alla destinazione. Per questo è stato usato (sempre dentro la definizione xamlx del servizio che rappresenta i passi del workflow da eseguire all’arrivo di una richiesta di GetData) un nuovo componente di WF 4.0 denominato Flowchart.
Espandendo il Flowchart si ottiene questo:

Sul rombo con la X si indica la variabile su cui effettuare il test (nel nostro caso Destination) e ogni braccio del flowchart indica il valore che attiva il braccio stesso: nel nostro caso vediamo che sul primo braccio è indicato Brescia nella finestra delle proprietà:

Altra novità della versione 4.0 è l’activity di Assign che valorizza la variabile Price con un valore (o una espressione). Nel nostro caso quindi il volo su Brescia costra 1.000, mentre il volo su Firenze costa 500; negli altri casi si scatena una eccezione che poi verrà convertita in un SOAP Fault.
Una volta calcolato il prezzo base (1000 o 500 nel nostro caso) il flowchart riunisce il flusso per entrare in un RuleSet: chi conosce i RuleSet di Worklfow 3.x si troverà davanti un editor completamente diverso (e molto più intuitivo) che consente di validare una serie di regole (con sequence, chaining, full chaining e reevalutation).
Il nuovo editor si presenta così, e appunto anche in questa prima CTP è molto più intuitivo dell’attuale:

Le regole possono avere nomi descrittivi, il che aiuta molto nell’individuazione delle regole.
Una volta esplosa (nel senso buono J) una regola ottengo la visualizzazione delle espressioni di valutazione:

Se DiscountLevel = 1 Then DiscountPoint = 0.95. L’assegnazione si effettua sempre tramite l’activity di assign.
Per mettere in produzione il nostro servizio che comprende il workflow ci si può affidare alla feature di Dublin di cui trovate un articolo introduttivo sempre nella sezione articolidevleap del sito blogs.devleap.com.
Ecco l’url completo: http://blogs.devleap.com/articolidevleap/archive/2008/11/14/dublin-windows-application-server.aspx
Per un primo contatto su Visual Studio 2010 trovate i seguenti due articoli:
http://blogs.devleap.com/articolidevleap/archive/2008/10/17/visual-studio-team-system-2010-primo-contatto.aspx
http://blogs.devleap.com/articolidevleap/archive/2008/10/26/visual-studio-team-system-2010-primo-contatto-parte-2.aspx.
Roberto Brunetti
di Roberto Brunetti
Questo articolo vuole essere una introduzione sulla prima CTP di Dublin ovvero Windows Application Server, almeno per adesso: il nome in codice Dublin fa riferimento anche ad altri meandri della tecnologia. In questa introduzione vediamo all'opera la parte del Windows Application Server.
E' costruito sopra il .NET Framework 4.0 e integra IIS e SQL Server per fornire una piattaforma per la gestione, la configurazione, lo sviluppo e il deploymnet di soluzioni WF/WCF (4.0 appunto).
Si compone di estensioni per integrare Visual Studio (2010) nel processo di deployment di una soluzione WF/WCF verso l'application server e una serie di strumenti per gestire, localmente o remotamente, la configurazione dell'applicazione ospitata sull'application server.
Attualmente è necessario creare il proprio ambiente di host, sia esso un servizio windows, un Windows Activated Service (WAS) o una applicazione ASP.NET, a mano, ricorrendo a file di configurazione .config, installazione di componenti, configurazione dei binding rispetto alle porte IIS e così via. Il Windows Application Server consente di gestire tutte queste componenti tramite una interfaccia di amministrazione che consente di esportare e importare applicazioni, vedere le istanze in esecuzione, tracciare i dati di tracking di un workflow, analizzare e terminare le istanze fallite.
Iniziamo a capire meglio il tutto partendo da uno screenshot (cliccare sulle immagine per visualizzarle a dimensione originale):
Siamo sul Default Web Site di IIS Manager (su un Windows Server 2008). Come si nota nella parte in basso delle Features View, l'installazione del Windows Application Server ha aggiunto dei moduli per
1) Esportazione e importazione di applicazioni: come vedremo più avanti è possibile esportare una applicazione, ad esempio dall'ambiente di test e importarla, completa di configurazione, nell'ambiente di produzione
2) Database Configuration: consente di gestire le connessioni verso i database di persistenza, tracking e monitoraggio.
3) Diagnostics: consente di configurare il livello di tracing degli errori e message logging rispetto al tipo di errore nell'applicazione e ai messaggi in ingresso
4) Persisted Instances: visualizza le istanza di workflow persistite nei vari database configurati
5) Tracking Configuration e Profile: consentono di gestire profili di tracking per i worflow in esecuzione (come i Tracking Profile di Workflow 3.0) e associarli al database che dovra accogliere queste informazioni.
Procediamo per gradi, questa la maschera di configurazione dei DB:

Come si nota, la mia configurazione prevede un database ProcessServerDB come store di default per le istanze persistite di workflow e un database ProductionStore (che in realtà si appoggia sempre allo stesso database fisico) dove appoggiare le istanze dei workflow persistiti. Sempre nello stesso database fisico vengono appoggiati i dati di monitoring (per il tracking delle istanze di workflow) denominato DefaultMonitoringStore. Nel database fisico troveremo quindi le tabelle adibite al monitoring, tabelle con il prefisso defaultStore per tenere le istanze persistite.
Come è facile intuire i servizi in esecuzione si baseranno sul nome indicato nella colonna Persistence e i dati ad essi associati verranno memorizzati nei (nel caso indicato nell'unico) database fisici configurati.
Accedendo al modulo Services, si accede alla seguente maschera che consente di visualizzare i servizi installati, di cui vengono riepilogati il nome, il path rispetto alla Virtual directory IIS, il nome del site che ospita la virtual directory, e l'application pool in cui gira il servizio.
Nel mio caso, ho aperto la maschera dei services, sulla virtual directory ProductionPizzaOrderService e quindi ottengo la lista dei servizi presenti in questa virtual directory.
Aprendo il maschera dal default web site ottengo ovviamente la visualizzazione di tutti i servizi ospitati sul site di default:
Su ogni servizio è possibile visualizzare gli endpoint disponibili. Ecco un esempio della maschera che riepiloga i binding di tutti i servizi.
E' possibile visualizzare le istanze dei workflow in esecuzione, dei workflow andati in errore, dei workflow bloccati, dei workflow sospesi, dei workflow Ready-to-run: nella versione attuale di workflow foundation è possibile accedere ai valori numerici solo dal performance monitor, che non fornisce però i dettagli rispetto ai counter. Con il Windows Application Server, oltre ai counter riepilogativi che si vedono nella seguente immagine

è possibile cliccare sui vari contatori per capire a cosa si riferiscono i numeri. Ad esempio cliccando su Instances Running (2 sono le istanze riportate anche come suspended) si accede al dettaglio di ogni istanza:
Come si nota appare il dettaglio che indica che ci sono due istanze del PizzaOrderService, ospitate dal Default Web Site, sulla virtual directory Production... che stanno girando.
Su ogni istanza è possibile, tramite il tasto destro, sospendere l'instanza, terminarla, abortirla o visualizzare i dati di tracking, ovvero l'elenco dei passi (in base al profilo di tracking scelto) che sono stati eseguiti.
Ad esempio, se sul servizio PizzaOrderService (nella maschera che abbiamo visto prima riguardante i Services) si sceglie Configure Tracking, è possibile abilitare la configurazione di tracking più appropriata: ogni configurazione comprende il riferimento allo store di monitoring (indicando il nome logico come abbiamo visto nello screenshot relativo) e il profilo di tracking (esiste anche nella versione 3.0 il concetto di Tracking Profile) da utilizzare. Il profilo indica quali attività e quali eventi tracciare su ognuna di esse e può essere completamente personalizzato come vedremo più avanti.
Una volta impostata la configurazione di tracking su un certo servizio è possibile, dalla mascherina precedente visualizzare i dati di tracking per ogni istanza: tramite il filtro in alto è possibile scegliere lo stato fra Running, Suspended, Blocked, Ready To Run.
Ecco il risultato:
Come si può notare per l'instanza selezionata si vedono gli eventi che indicano lo stato (AEC) di ogni activity e il "momento" in cui si sono scatenati.
Attualmente esiste nell'SDK di Workflow 3.x un editor di Tracking Profile, prodotto efficace ma abbastanza rudimentale. Nella prossima versione il Tracking Profile Editor si presenta così (almeno in questa primissima CTP di ottobre 2008):
Il TPE consente di vedere il workflow e indicare quali sono gli eventi da tracciare (la mia freccia rossa indica da dove eseguire questa impostazione) e volendo le variabili (è una novità di workflow 4.0 poter definire variabili a livello di composite activity) da inserire nei dati di tracking.
Come dicevano all'inizio è possibile esportare e importare una applicazione da un server all'altro portandosi dietro la configurazione.
Questa la maschera di esportazione:
In pratica si sceglie il servizio da esportare e si indica il nome di un file .zip da creare. Il contenuto dello zip è il seguente:

Il file applicationManifest.xml contiene le informazioni sull'applicazione, sull'application pool (e relativa configurazione) che la ospita, la virtual directory e il path fisico, oltre ad una serie di informazioni sulle modifiche, il tipo di accesso, i protocolli abilitati e la descrizione di tutti i file (dll, proj, etc) che compongono l'applicazione. Ecco il contenuto facilmente intuibile anche se lunghetto :-). Ho evidenziato i punti salienti.
- <Application xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Id>85c8dc43-1161-4188-8324-de5a831f1431</Id>
<LpsVersion>3.0.1341.0</LpsVersion>
- <Modules>
- <Module>
- <ApplicationPool>
<CpuAffinityEnabled>false</CpuAffinityEnabled>
<CpuAffinityMask>4294967295</CpuAffinityMask>
<CpuLimit>0</CpuLimit>
<CpuLimitAction>NoAction</CpuLimitAction>
<CpuLimitInterval>PT5M</CpuLimitInterval>
<FrameworkVersion>v4.0</FrameworkVersion>
<IdentityType>SpecificUser</IdentityType>
<IdleTimeout>PT20M</IdleTimeout>
<ManagedPipelineMode>Integrated</ManagedPipelineMode>
<MaxWorkerProcesses>1</MaxWorkerProcesses>
<Name>User Services Application Pool</Name>
<OverlappedRecycleDisabled>false</OverlappedRecycleDisabled>
<PingEnabled>true</PingEnabled>
<PingMaxResponseTime>PT1M30S</PingMaxResponseTime>
<PingPeriod>PT30S</PingPeriod>
<ProcessOrphaningEnabled>false</ProcessOrphaningEnabled>
<ProcessOrphaningExecutable />
<ProcessOrphaningExecutableParameters />
<QueueLength>1000</QueueLength>
<RapidFailProtectionEnabled>true</RapidFailProtectionEnabled>
<RapidFailProtectionFailureInterval>PT5M</RapidFailProtectionFailureInterval>
<RapidFailProtectionMaxFailures>5</RapidFailProtectionMaxFailures>
<RapidFailProtectionResponseType>HttpLevel</RapidFailProtectionResponseType>
<RapidFailProtectionShutdownExecutable />
<RapidFailProtectionShutdownExecutableParameters />
<RecyclingEventLogs>Time, Memory, PrivateMemory</RecyclingEventLogs>
<RecyclingForConfigurationChangesDisabled>false</RecyclingForConfigurationChangesDisabled>
<RecyclingPrivateMemoryLimit>0</RecyclingPrivateMemoryLimit>
<RecyclingRegularTimeInterval>P1DT5H</RecyclingRegularTimeInterval>
<RecyclingRequestLimit>0</RecyclingRequestLimit>
<RecyclingSpecificTimes xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
<RecyclingVirtualMemoryLimit>0</RecyclingVirtualMemoryLimit>
<ShutdownTimeLimit>PT1M30S</ShutdownTimeLimit>
<StartAutomatically>true</StartAutomatically>
<StartupTimeLimit>PT1M30S</StartupTimeLimit>
<UserName>Administrator</UserName>
</ApplicationPool>
<Id>6ca9913a-a35a-49a8-8420-2d6d069541be</Id>
<IisVersion>7.0</IisVersion>
<MachineName>ROBDUBLIN</MachineName>
<Name>/PizzaOrderService</Name>
<Technology>WAS</Technology>
<TimeStamp>2008-11-04T13:26:38.8691696-08:00</TimeStamp>
- <VirtualApplication>
<AccessPolicy>513</AccessPolicy>
<AnonymousAccess>true</AnonymousAccess>
<ApplicationPoolName>User Services Application Pool</ApplicationPoolName>
<DirectoryBrowsing>true</DirectoryBrowsing>
<EnabledProtocols>http</EnabledProtocols>
<LogVisits>true</LogVisits>
- <VirtualDirectories>
- <VirtualDirectory>
<Attributes>Normal</Attributes>
- <Directories>
- <Directory>
<Attributes>Normal</Attributes>
<Directories />
<Files />
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService\App_Data</FullPath>
<Name>App_Data</Name>
</Directory>
- <Directory>
<Attributes>Normal</Attributes>
<Directories />
- <Files>
- <File>
<Attributes>Archive</Attributes>
<CreationTime>2008-09-23T12:56:08-07:00</CreationTime>
<LastAccessTime>2008-09-23T12:56:08-07:00</LastAccessTime>
<LastWriteTime>2008-09-23T12:56:08-07:00</LastWriteTime>
<Name>PizzaOrderingService.dll</Name>
<PackageKey>PizzaOrderService\bin\PizzaOrderingService.dll</PackageKey>
</File>
- <File>
<Attributes>Archive</Attributes>
<CreationTime>2008-09-23T12:56:08-07:00</CreationTime>
<LastAccessTime>2008-09-23T12:56:08-07:00</LastAccessTime>
<LastWriteTime>2008-09-23T12:56:08-07:00</LastWriteTime>
<Name>PizzaOrderingService.pdb</Name>
<PackageKey>PizzaOrderService\bin\PizzaOrderingService.pdb</PackageKey>
</File>
</Files>
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService\bin</FullPath>
<Name>bin</Name>
</Directory>
- <Directory>
<Attributes>Normal</Attributes>
- <Directories>
- <Directory>
<Attributes>Normal</Attributes>
- <Directories>
- <Directory>
<Attributes>Normal</Attributes>
<Directories />
<Files />
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService\obj\Debug\TempPE</FullPath>
<Name>TempPE</Name>
</Directory>
</Directories>
- <Files>
- <File>
<Attributes>Archive</Attributes>
<CreationTime>2008-09-23T12:56:10-07:00</CreationTime>
<LastAccessTime>2008-09-23T12:56:10-07:00</LastAccessTime>
<LastWriteTime>2008-09-23T12:56:10-07:00</LastWriteTime>
<Name>PizzaOrderingService.dll</Name>
<PackageKey>PizzaOrderService\obj\Debug\PizzaOrderingService.dll</PackageKey>
</File>
- <File>
<Attributes>Archive</Attributes>
<CreationTime>2008-09-23T12:56:10-07:00</CreationTime>
<LastAccessTime>2008-09-23T12:56:10-07:00</LastAccessTime>
<LastWriteTime>2008-09-23T12:56:10-07:00</LastWriteTime>
<Name>PizzaOrderingService.pdb</Name>
<PackageKey>PizzaOrderService\obj\Debug\PizzaOrderingService.pdb</PackageKey>
</File>
- <File>
<Attributes>Archive</Attributes>
<CreationTime>2008-09-23T12:56:10-07:00</CreationTime>
<LastAccessTime>2008-09-23T12:56:10-07:00</LastAccessTime>
<LastWriteTime>2008-09-23T12:56:10-07:00</LastWriteTime>
<Name>PizzaOrderService.csproj.FileListAbsolute.txt</Name>
<PackageKey>PizzaOrderService\obj\Debug\PizzaOrderService.csproj.FileListAbsolute.txt</PackageKey>
</File>
</Files>
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService\obj\Debug</FullPath>
<Name>Debug</Name>
</Directory>
</Directories>
<Files />
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService\obj</FullPath>
<Name>obj</Name>
</Directory>
- <Directory>
<Attributes>Normal</Attributes>
<Directories />
- <Files>
- <File>
<Attributes>Normal</Attributes>
<CreationTime>2008-09-02T19:50:26-07:00</CreationTime>
<LastAccessTime>2008-09-02T19:50:26-07:00</LastAccessTime>
<LastWriteTime>2008-09-02T19:50:26-07:00</LastWriteTime>
<Name>AssemblyInfo.cs</Name>
<PackageKey>PizzaOrderService\Properties\AssemblyInfo.cs</PackageKey>
</File>
</Files>
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService\Properties</FullPath>
<Name>Properties</Name>
</Directory>
</Directories>
- <Files>
- <File>
<Attributes>Normal</Attributes>
<CreationTime>2008-09-02T19:50:26-07:00</CreationTime>
<LastAccessTime>2008-09-02T19:50:26-07:00</LastAccessTime>
<LastWriteTime>2008-09-02T19:50:26-07:00</LastWriteTime>
<Name>DataContracts.cs</Name>
<PackageKey>PizzaOrderService\DataContracts.cs</PackageKey>
</File>
- <File>
<Attributes>Normal</Attributes>
<CreationTime>2008-09-09T15:33:48-07:00</CreationTime>
<LastAccessTime>2008-09-09T15:33:48-07:00</LastAccessTime>
<LastWriteTime>2008-09-09T15:33:48-07:00</LastWriteTime>
<Name>PizzaOrderService.csproj</Name>
<PackageKey>PizzaOrderService\PizzaOrderService.csproj</PackageKey>
</File>
- <File>
<Attributes>Normal</Attributes>
<CreationTime>2008-09-16T19:17:18-07:00</CreationTime>
<LastAccessTime>2008-09-16T19:17:18-07:00</LastAccessTime>
<LastWriteTime>2008-09-16T19:17:18-07:00</LastWriteTime>
<Name>PizzaOrderService.csproj.user</Name>
<PackageKey>PizzaOrderService\PizzaOrderService.csproj.user</PackageKey>
</File>
- <File>
<Attributes>Normal</Attributes>
<CreationTime>2008-09-16T19:17:18-07:00</CreationTime>
<LastAccessTime>2008-09-16T19:17:18-07:00</LastAccessTime>
<LastWriteTime>2008-09-16T19:17:18-07:00</LastWriteTime>
<Name>PizzaOrderService.suo</Name>
<PackageKey>PizzaOrderService\PizzaOrderService.suo</PackageKey>
</File>
- <File>
<Attributes>Normal</Attributes>
<CreationTime>2008-09-16T19:17:18-07:00</CreationTime>
<LastAccessTime>2008-09-16T19:17:18-07:00</LastAccessTime>
<LastWriteTime>2008-09-16T19:17:18-07:00</LastWriteTime>
<Name>PizzaOrderService.xamlx</Name>
<PackageKey>PizzaOrderService\PizzaOrderService.xamlx</PackageKey>
</File>
- <File>
<Attributes>Archive</Attributes>
<CreationTime>2008-09-23T12:43:42-07:00</CreationTime>
<LastAccessTime>2008-11-04T13:21:57.6702032-08:00</LastAccessTime>
<LastWriteTime>2008-11-04T13:21:57.674216-08:00</LastWriteTime>
<Name>Web.config</Name>
<PackageKey>PizzaOrderService\Web.config</PackageKey>
</File>
</Files>
<FullPath>C:\HandsOnLabs\Lab6\PizzaOrderingApplication\PizzaOrderService</FullPath>
<Name>PizzaOrderService</Name>
<AuthenticationMethod>ClearText</AuthenticationMethod>
<Username />
<VirtualPath>/</VirtualPath>
</VirtualDirectory>
</VirtualDirectories>
<VirtualPath>/PizzaOrderService</VirtualPath>
<WindowsAuthentication>false</WindowsAuthentication>
</VirtualApplication>
- <Website>
<Name>Default Web Site</Name>
</Website>
</Module>
</Modules>
<Name>Default</Name>
</Application>
Oltre questo file viene preparata anche la sottodirectory archiveDir che contiene gli effettivi file che costituiscono l'applicazione.
E' possibile poi importare nuovamente l'applicazione (modificandone alcuni dati per adattarli al server su cui si effettua l'import)
Visto che il server di import sarà probabilmente diverso dal server da cui si è esportata l'applicazione è possibile modificare il nome dell'applicazione, l'application pool che la ospita e il path fisico in cui scompattare il file zip e su cui punterò la virtual directory.
In un prossimo articolo vedremo un altro strumento di configurazione, a livello di macchina e i servizi che l'application server espone: XAML Activation, Durable Timer Service, Forwarding Service e andremo più nel dettaglio delle configurazioni.
Roberto Brunetti
Autore: Roberto Brunetti - DevLeap
Dopo la prima parte ecco un altra serie di informazioni su VSTS 2010.
In Visual Studio 2005 e 2008 il processo di compilazione è demandato a MSBUILD; in prima battuta , avendo un compilatore esterno, è possibile eseguire operazioni di compilazione esterne a Visual Studio e schedularne l’esecuzione. Con l’arrivo della parte server (Team Foundation Server 2005 è uscito 6 mesi dopo rispetto alla Team Suite di VS 2005) il motore di MSBUILD è stato integrato nel processo di Team Build: una build è una operazione di compilazione server-side. MSBuild, da sempre è estendibile, nel senso che possono essere create task custom per effettuare operazioni aggiuntive rispetto alla semplice complilazione. MSBuild quindi, oltre a prendere in pasto i file .proj (VB, C#, DB) e produrre il rispettivo output, ha consentito al motore del Build Server di effettuare operazioni come il recupero dei sorgenti dal repository, l’assegnazione di Label per indicare la fase di compilazione sui sorgenti, il lancio di unit test e la pubblicazione del compilato nella directory outputdrop. In pratica tutto il processo viene eseguito tramite task di MSBuild.
I file .targets di MSBuild che contengono le attività devono essere modificati a mano per inserire attività aggiuntive: questo costringe spesso alla chiusura del progetto (unload da visual studio) e un editing manuale del file .proj per agganciare attività esterne, come ad esempio puntare ad un file .targets custom per lanciare i comandi su stsadm.exe nel caso di sviluppo di una soluzione SharePoint.
In Visual Studio 2010 si cerca di andare oltre: tutto il processo di build sfrutta Workflow Foundation (in versione 4.0) e fornisce quindi un designer molto semplice e intuitivo per compilare gli step di compilazione effettivi. Il flusso può contare su una serie di activity custom sviluppate appositamente per effettuare le operazioni che oggi MSBuild consente di fare, oltre alla normale serie di activiy come ad esempio Sequence, CallExternalMethod per gestire il flusso di lavoro.
Questa una immagine del designer
La sequenza delle attività viene impostate nel designer custom direttamente nella Build Definition. Nella Toolbox l’elenco delle activity out-of-the-box, che ovviamente può essere esteso con activity custom.
Si possono quindi usare le activity classiche (quelle che hanno scelto sono quelle sensate rispetto a cosa si fa nella build) ed esiste una activity per richiamare i “vecchi” task di MSBUILD consentendo di mantenere task custom create per l’attuale versione di Visual Studio.
Inoltre alcune activity possono essere chiamate in causa in modo condizionale rispetto al tipo di build che si fa: quindi il processo disegnato può essere lo stesso con alcune activity enabled solo se si fa un certo tipo di build (se siamo in continous integration oppure in build schedulata o ancora manuale possiamo definire nello stesso flusso cosa è attivo in ogni configurazione).
E’ veramente semplice gestire il tutto rispetto ai file .xml a mano.
Esistono poi due nuove tipologie di Build: Gated Check-in e ShelveSet Check-in.
Gated Check-in: ci si assicura di tutto prima di fare il check-in reale. Si stoppa il tutto se non va. E’ una pratica consolidata che non è però supportata nell’attuale versione e che parte dall’idea di invertire l’operazione di check-in e successiva compilazione. Prima si compila, si verificano le regole del Code Analysis, si lanciano i test e solo se tutto va a buon fine si esegue effettivamente il check-in. L’obiettivo è chiaro: evitare di mettere sotto SCC sorgenti che possono introdurre problemi. Ad oggi è possibile solo impostare alcune regole per fare check-in, ma costringono lo sviluppatore ad effettuare una operazione di CodeAnalysis e successivo lancio dei test prima di poter fare check-in.
Shelvset Build è l’altra nuiova opzione di build. Simile al gated come obiettivo, ma l’idea è questa: Si mettono le modifiche attuale in uno ShelveSet (gli shelveset sono presenti anche nella versione attuale di TFS e anche nella versione 2005) e tutto il processo di build lavora con i sorgenti dello shelveset. Al termine si fa il check-in se il codice non “rompe” la build.
Questa la finestra di gestione della build con le nuove modalità
Nel caso di Gated Check-in o ShelveSet Check-in, a fronte di una operazione di check-in appare questa maschera con cui si può decidere se effettuare la compilazione e la validazione prima dell’effettivo check-in.
Un’altra novità molto interessante è l’integrazione con Debugging History : il tool (si veda la sezione apposita) consente di registrare una operazione di debug in modo da poter rieseguire tutti i passi effettuati e tornare al punto dove sono stati trovati i problemi) senza dover rieseguire tutto il percorso a mano. Lo strumento memorizza anche tutte le informazioni che riguardano lo stato dell’applicazione durante la sessione di debug in modo da ripresentare la situazione completa e reale per poter investigare il tipo di problema risconstrato.
Build Controller: oggi è possibile configurare un solo build agent, il che costringe l’esecuzione del processo di build ad una sola macchina. In 2010 sono stati introdotti gli Agent Pool, gestiti da un Build Controller. In pratica si associa il Controller alla Build e il tutto viene fatto girare dai vari Agent configurati nel Pool.
Il Build Explorer presenta una nuova interfaccia che consente di effettuare le operazioni più comuni in modo semplice: ad esempio è possibile vedere il log completo direttamente da questa interfaccia senza aprire il famoso file di log .txt. Dal log è possibile accedere al codice che ha creato ad esempio un problema di compilazione. E’ possibile vedere le precedenti build dello stesso build type per analizzare la storia di una particolare build.
Ecco alcuni screenshot che si commentano da soli
Log espnso con indicazione di eventuali problemi
Come accennato, se ci sono problemi di compilazione, per ogni riga in errore è presente un link nel log per saltare direttamente al codice che causa il problema. Ad oggi occorre vedere gli errori e poi cercare il problema manualmente. Inoltre, il puntatore al codice, ci porta alla versione dei sorgenti che è stata compilata, non a quella presente nel nostro workspace che nel frattempo potrebbe essere cambiata n volte.
Si può effettuare un Delete di una Build e decidere cosa fare rispetto a tutti i dati della Build:
Anche nella maschera di gestione dei retention period si può decidere cosa fare non solo a livello di build come nella versione 2008, ma anche rispetto alle singole categorie di informazioni:
In ogni caso, giustamente, anche se si cancella una build restano comunque alcune informazioni nel database marcate come deleted; questo per evitare di perdere le info legate ai work item. Si possono cancellare ma a riga di comando: deve essere una richiesta esplicita.
Si possono impostare permessi molto granulari su chi può vedere i dati a livello di Build.
Web Access
Con il SP1 del 2008 è uscito il prodotto ufficiale (dopo l’acquisizione di TeamPlain).
La versione 2010 fa un salto in avanti sulla user interface sfruttando AJAX: l’interfaccia risulta più user friendly e veloce da utilizzare.
Fra le novità, la visualizzazione della gerarchia di workitem che rispetta la gerarchia di TFS, la possibilità di fare bulk edit di una proprietà da applicare ad una query di work item o a una multi-selezione di work-item. Quest’ultimo punto è molto utile quando occorre ad esempio riassegnare una serie di task ad un altro membro del team. Questa interfaccia risulta più funzionale anche rispetto ad Excel che oggi è lo strumento principe per fare bulk load di informazioni. Ecco la maschera di gestione del Bulk Edit in cui si assegna l’Area Northwind\UI e lo Stato di Resolved ad una serie di work item eselezionati
Se ci sono errori nel bulk edit si vede può controllare il progresso e eventuali problemi sui singoli work item (ad esempio la modifica non può essere applicata perchè lo stato non consente quel tipo di modifica)
In Excel, seppur comodissimo, occorre invece eseguire la modifica item per item (o con un find/replace...sempre molto pericoloso).
Molte delle componenti del Web Access sono costituite da Web Part: questo consente di riutilizzare parti della user interface in applicazioni custom basate su SharePoint o ASP.NET. A proposito di SharePoint, quando si crea un nuovo team project si può fare anche provisioning su SharePoint di alcune feature per creare la dashboard da customizzare.
TFS Migration & Sync
Migration:l’obiettivo è portare un altro sistema in TFS. Gli strumenti effettuano una importazione dei dati da altri sistemi. Esistono già strumenti di questo tipo per TFS 2008, primo fra tutti, lo strumento di migrazione da Source Safe.
Nella nuova versione però, nascono nuovi strumenti nativi che consentono di sincronizzare il contenuto di TFS con altri prodotti (ad esempio ClearCase) per consentire l’utilizzo di entrambi gli strumenti in contemporanea. Si pensi ad esempio alla migrazione di cui sopra: sto utilizzando un sistema di bug tracking, decido di passare a TFS, ma per un periodo di tempo dovrò ancora usare il “vecchio” sistema e tenere allineate le informazioni.
Su CodePlex trovate alcuni toolkit per applicare alcune di queste funzionalità anche a TFS 2008.
Molte informazioni che trovate in questo mio articolo e alcuni screenshot arrivano direttamente dai video che sono stati pubblicati nella Team System Week Videos di fine settembre 2008: http://channel9.msdn.com/posts/VisualStudio/Visual-Studio-Team-System-2010-Week-on-Channel-9/.
Sono appena arrivato a Los Angeles per Microsoft PDC 2008: se ci saranno altre informazioni in questi giorni su VSTS 2010 le includo nel prossimo articolo già previsto per la prossima settimana.
Allego tutti i miei post sulla versione 2005 e 2008 di VSTS/TFS come puntatori che spero siano utili
Architect.
• http://blogs.devleap.com/rob/archive/2005/12/29/6428.aspx
• http://blogs.devleap.com/rob/archive/2007/10/15/visual-studio-team-system-2008-architect-power-tools.aspx
• http://www.devleap.com/document.aspx?id=3837
• http://blogs.devleap.com/rob/archive/2006/09/24/14243.aspx
• http://blogs.devleap.com/rob/archive/2006/01/27/6584.aspx
• http://blogs.devleap.com/rob/archive/2005/12/29/6428.aspx
• http://blogs.devleap.com/rob/archive/2005/09/12/5706.aspx
Developer e Tester
• http://blogs.devleap.com/rob/archive/2008/09/10/vsts-2008-sp1-web-test.aspx
• http://blogs.devleap.com/rob/archive/2007/08/21/visual-studio-2008-for-mobile-dev-e-non-solo.aspx
• http://blogs.devleap.com/rob/archive/2007/07/16/workflow-custom-activity-e-vsts-unit-test.aspx
• http://blogs.devleap.com/rob/archive/2005/12/30/6434.aspx
• http://blogs.devleap.com/rob/archive/2005/09/12/5705.aspx
• http://blogs.devleap.com/rob/archive/2006/01/30/6608.aspx
Database Edition
• http://blogs.devleap.com/rob/archive/2007/08/20/vsts-for-db-pro-power-tools.aspx
• http://blogs.devleap.com/rob/archive/2007/07/28/vsts-for-db-pro-service-release-1.aspx
• http://blogs.devleap.com/rob/archive/2007/02/02/vsts-for-db-pro-aggiunta-e-successivo-refactor-di-un-campo.aspx
• http://blogs.devleap.com/rob/archive/2007/02/02/vsts-for-db-pro-schema-update-per-aggiunta-campo.aspx
• http://blogs.devleap.com/rob/archive/2007/02/01/vsts-for-db-pro-schema-update-con-merge-replication.aspx
• http://blogs.devleap.com/rob/archive/2006/12/25/15649.aspx
• http://blogs.devleap.com/rob/archive/2008/09/17/microsoft-174-visual-studio-team-system-2008-database-edition-gdr-august-ctp.aspx
TFS
• http://blogs.devleap.com/rob/archive/2008/09/09/tfs-2008-sp1-problem.aspx
• http://blogs.devleap.com/rob/archive/2008/09/09/tfs-2008-sp1-problem.aspx
• http://blogs.devleap.com/rob/archive/2008/09/06/visual-studio-team-system-web-access-2008-sp1-power-tool.aspx
• http://blogs.devleap.com/rob/archive/2007/11/20/team-foundation-server-2008-upgrade.aspx
• http://blogs.devleap.com/rob/archive/2005/09/03/5645.aspx
• http://blogs.devleap.com/rob/archive/2005/12/05/6330.aspx
http://blogs.devleap.com/rob/archive/2006/03/30/7085.aspx
Roberto Brunetti
Autore: Roberto Brunetti - DevLeap
Questo articolo ripercorre alcuni miei appunti e le prime impressioni di utilizzo di Visual Studio Team System 2010. Sto usando un progetto di esempio per analizzare il prodotto prima di portare la nostra famosa applicazione Estates Management sotto VSTS 2010.
Visual Studio Team System venti-dieci (come viene chiamato affettuosamente) porta con se molte novità sia per la versione Architect che per la versione Tester. Non meno numerose sono le nuove funzionalità della parte Team Foundation Server.
Da quello che si evince dalle conferenze stampa la parte più importante delle novità riguarda l’edizione Architect, sia per quanto riguarda le modalità di lavoro del team ovvero una maggiore singergia fra i componenti, sia per quanto rigurda l’introduzione di strumenti di modellazione UML che si aggiungono agli attuali DSL.
Democratize: everybody has a chance to play
Quello che accade spesso oggi, anche nei progetti che ho seguito personalmente, è la quasi totale divisione dei compiti fra architetti e gli altri membri del team: solitamente all’inizio della progettazione gli architetti si chiudono in una stanza e producono una serie di diagrammi architetturali. E’ vero che già oggi, se questi diagrammi sono prodotti con la Architect Edition, è possibile implementare le componenti applicative partendo dal diagramma e grazie alle estensioni dei power tools è possibile creare progetti di tipo ASP.NET, Windows e class library arrivando alla solution completa dei progetti e delle reference fra di essi. Per esempio, partendo dal diagramma seguente è possibile implementare con un solo colpo di mouse l’applicazione ASP.NET, l’applicazione windows client e le due dll rappresentate in questo estratto del diagramma completo.
Si veda il post completo sui Power Tools.
L’interazione fra il diagramma applicativo e il codice reale è presente anche nella configurazione delle applicazioni: sul client windows e sull’applicazione web, per proseguire con l’estratto del nostro esempio, la configurazione effettuata dagli architetti sui Settings & Constraint si riflette sulle effettive configurazioni presenti all’interno dei rispettivi file .config.
In Visual Studio Team System 2010 la sincronizzazione e quindi la collaborazione fra il “disegno” e il codice reale (e quindi fra Architect e Developer) viene effettuata a più livelli. Ad esempio il nuovo Layer Diagram consente di visualizzare le varie componenti applicative nei layer definiti per l’applicazione. L’architetto e/o il developer possono così avere una rappresentazione più concreta di quali elementi sono compresi in quali layer.
Ecco un esempio di Layer Diagram per una applicazione di esempio:
Nella toolbox ci sono solo Layer e Dependency che consentono di definire i layer applicativi e i flussi delle chiamate fra layer. Si può partire da zero oppure da una applicazione esistente (ad esempio da diagramma dei namaspace, più avanti) trascinando un namespace e portandolo sul layer effettivo. Si può fare poi Show Dependency per vedere le dependency fra le classi dei namespace rappresentati nel diagramma senza dover ricreare i percorsi. Con questo diagramma quindi possiamo rappresentare velocemente i vari componenti applicativi suddividendoli nei layer logici e verificare su una applicazione esistente quali sono le interazioni fra le classi.
Il flusso delle chiamate definisce anche le regole di chiamata fra layer: ad esempio, se il Presentation Layer ha un flusso possibile solo con il Business Layer, non dovremmo permettere allo sviluppatore di fare una reference verso il Data Access Layer e utilizzare direttamente le classi di accesso ai dati. Tramite la funzione Validate All è possibile eseguire questi controlli senza dover analizzare il codice. In pratica si controlla che le classi reali non facciano chiamate che non sono permesse dalle dependency: trovo questo strumento importantissimo per effettuare verifiche sulle applicazioni esistenti e per rappresentare graficamente una applicazione esistente ricavandone le idee architetturali che spesso non sono state documentate (forse ho avuto sfiga, ma nei progetti esistenti su cui ho messo le mani a posteriori, la documentazione è sempre stato un problema..non so se capita anche a voi J). Si possono anche impostare regole di compilazione per MSBuild per evitare una compilazione che non rispetti le corrette chiamate fra layer. E’ possibile fare queste verifiche anche sul Build Server (quindi anche senza Visual Studio) inserendo un tag <validate Include=”diagramma.Layer”> che punta al diagramma da controllare. E’ un FxCop per Architect J.
I diagrammi si possono Copy&Paste ovunque oppure esportare (stanno decidendo dove: sembra Visio di sicuro) per creare documentazione elettronica senza sforzo.
Il layer diagram è accessibile anche dal codice e vice versa, ovvero è possibile conoscere la posizione del codice che sto vedendo rispetto ai layer oppure scendere nel dettaglio di una classe (nel codice della classe) partendo dalla rappresentazione nel layer diagram.
UML
Un’altra novità di rilievo della versione Architect è il supporto a UML che in molti hanno lamentano nella versione 2005 e 2008 di Visual Studio Team System Architect Edition. L’idea è poter approcciare la definizione dell’architettura Top-Down oppure Bottom-Up.
Se si guarda la soluzione dall’altro, sono disponibili 5 designer UML (almeno secondo le previsioni attuali) per rappresentare il livello logico di una applicazione; se invece si guarda la soluzione dal basso, restano più che validi i DSL che, essendo più concreti e più specializzati, aderiscono meglio alla rappresentazione reale. L’idea è quindi non abbandonare DSL, ma creare la formula magica: UML + DSL == Better Code. La cosa interessante e che i diagrammi sono “agganciabili” ovvero si può partire dal diagramma logico in UML (ad esempio il sequence diagram) per scendere nel codice reale con un semplice click del mouse. Ad una modifica del codice reale, supponiamo per effettuare una chiamata ad una nuova classe, il sequence diagram si aggiorna per rappresentare questa interazione.
Per quanto riguarda UML (2.0 e 1.1) è importante comprendere la connessione rispetto ai DSL che stanno sotto. Addirittura, i designer UML sono stati creati con il DSK Toolkit. Quindi i DSL tool esistono ancora e anzi ne viene incoraggiato l’utilizzo per modellare il proprio dominio creando i DSL custom.
Hanno deciso di “tornare” a UML perchè è quello che la gente usa. La prima versione di VSTS puntava invece a rappresentare in modo aderente alla realtà del progetto l’applicazione, i logical datacenter e il deploy relativo. Probabilmente non saranno 100% standard perchè nessuno lo è: cercheranno di coprire i livelli (come sapete il supporto allo “standard” UML viene indicato come compliance ad un determinato livello)
Nella prima versione (2010) ci saranno questi 5 diagrammi UML:
1. Use Case
2. Component
3. Activity
4. Class
5. Sequence
Tutti questi strumenti hanno una migliore integrazione con il codice: ad esempio sto scrivendo o analizzando questo pezzo di codice e posso sapere in che punto mi trovo rispetto ai diagrammi.
Resta l’idea che non viene consigliata una modalità (fra top-down o bottom-up): ognuno si può muovere come preferisce rispetto al suo modo di lavorare.
Rapporto con Visio: Visio è il prodotto più usato al mondo per far modeling UML quindi sarà possibile importare i diagrammi.
Top Down Approach (parto con UML)
Il nuovo Model Explorer Windows consente di vedere e gestire i vari modelli. I diagrammi UML si aggiungono alla solution: ognuno ha una estensione diversa gestita anche da TFS per centralizzare i diagrammi e gestirne il versioning.
Ecco qualche screen-shot dei designer UML.
1) Use Case
a. Actor e i vari Use Case
b. Si connettono (association) gli actor ai vari use case
c. Si possono fare le include di un uc in un altro uc
2) Activity Diagram
a. Descrrive il flusso dei dati (i processi): si mettono gli step e si connettono in sequenza
b. Nel Model Explorer si vedono le componenti degli altri diagrammi per poter creare legami fra di essi.
3) Sequence Diagram
a. Si può fare reverse engineer dal codice
b. Si disegnano le seguenze
c. Il diagramma resta sincronizzato con il codice (come vedremo più avanti), quindi a fronte di una nuova chiamata scritta nel metodo, il diagramma presenta un nuovo passo all’interno della sequenza
4) Component Diagram
a. I Component non rappresentano elementi fisici (potranno poi essere DLL o EXE o composizioni di questi, ma a questo livello non ci interessa)
b. Si mettono poi le interfacce e si connettono i vari componenti sempre utilizzando le informazioni del Model Explorer.
5) Logical Class Diagram
a. Lavora con il Class Diagram .NET che rappresenta il fisico
b. Si trascinano gli elementi definiti negli altri diagrammi
c. Si definiscono le operazioni e gli attributi
d. Si torna nel .sequence e si legano le “cose reali”
e. Si possono fare “cose ereditate” (e il designer mostra il tutto)
In questo ultimo digramma si vede l’interfaccia di pagamento derivata nel sistema di pagamento specifico per l’applicazione.
Botton Up (parto dal codice)
L’idea è che quasi sempre ci sono cose già fatte: anche quando si parte con un progetto nuovo magari ci sono esempi fatti o pezzi già pronti realizzati per prova o altri componenti esistenti da utilizzare.
Il diagramma con estensione.dgml consente di iniziare a comprendere una applicazione esistente. Per creare il diagramma in modo semplice si utilizza l’Architecture Explorer che mostra tutte le classi nella solution divisi per componente e namespace. Ad esempio, partendo da questa situazione esistente nella solution
E trascindando le classi di un namespace (volendo anche tutte le classi della solution) si ottiene la rappresentazione delle classi e l’interazione fra di esse all’interno di questo
E si ottiene questo
A parte una inutile ragnatela, pensiero che colpisce subito appena il diagramma viene rappresentato, basta prendere una classe, cliccarci sopra e iniziare ad esplorare le interazioni fra le classi in modo semplice e intuitivo: pensate sempre a cosa vorrebbe dire entrare nel codcie di ogni classe e di ogni metodo per vedere quali sono le chiamate effettuate, e ancoara più difficile, sapere da quali componenti viene chiamata la classe in oggetto: da sempre l’object explorer ci può dare una mano, ma vedere graficamente “la ragnatela” è decisamente più semplice e intuitivo.
Da questo diagramma si possono far partire diversi Analyzer che analizzano appunto il diagramma stesso per individuare, ad esempio, i riferimenti circolari, piuttosto che le dipendenze fra le chiamate; quest’ultimo individua le classi (volendo raggruppatoe per namespace) e le chiamate effettuate. Ecco un esempio:
Diventa più semplice capire l’applicazione esistente, vederne i dettagli (da ogni classe si può andare a visualizzare il codice, vedi prossima immagine), analizzare le dipendenze e le eventuali anomalie sulle chiamate senza dover per forza vedere il codice (che magari è stato scritto in sanscrito J).
Il dettaglio di ogni classe dentro il namespace e il codice è visibile facendo un semplice doppio click.
Quest’ultima immagine non ha molto senso; era assolutamente chiaro cosa volesse dire andare a vedere il codice con un doppio click, ma averla inserita ci consente di capire che facendo ancora una volta una semplice operazione con il mouse possiamo generare il Sequence Diagram (a partire da qualunque metodo).
Questo il Seguence Diagram del metodo createNewOrderButton_Click
Se nell’approccio Top-Down il Sequence Diagram era la base di partenza per poi scrivere il codice, avendo una applicazione esistente è facile percorrere il percorso inverso.
E’ ottima questa sincronizzazione (e forma di reverse engineering) anche per imparare a creare Sequence Diagram: ad esempio metto una chiamata ad un’altra classe nel codice e vedo come avrei dovuto fare il Sequence Diagram. E’ ovvio che sarebbe bene fare il contrario, il sequence diagram nasce proprio per rappresentare i flussi in modo astratto dal codcie, ma avere entrambe le possibilità è sicuramente un plus.
Ovviamente dal sequence diagram si può tornare codice del metodo rappresentato.
Dal Sequence Diagram si può anche produrre il Class Diagram e sincronizzarlo oppure effettuare una Create Method per aggiungere un metodo reale alla classe rappresentata.
Per questa prima parte direi che è tutto. Ci sentiamo presto per vedere le novità sulla parte tester, build, manual testing e le novità della parte di gestione del progetto come work-item gerarchici.
Molte informazioni che trovate in questo mio articolo e alcuni screenshot arrivano direttamente dai video che sono stati pubblicati nella Team System Week Videos di fine settembre 2008: http://channel9.msdn.com/posts/VisualStudio/Visual-Studio-Team-System-2010-Week-on-Channel-9/.
Allego anche tutti i miei post sulla versione 2005 e 2008 di VSTS/TFS come puntatori che spero siano utili
Architect.
• http://blogs.devleap.com/rob/archive/2005/12/29/6428.aspx
• http://blogs.devleap.com/rob/archive/2007/10/15/visual-studio-team-system-2008-architect-power-tools.aspx
• http://www.devleap.com/document.aspx?id=3837
• http://blogs.devleap.com/rob/archive/2006/09/24/14243.aspx
• http://blogs.devleap.com/rob/archive/2006/01/27/6584.aspx
• http://blogs.devleap.com/rob/archive/2005/12/29/6428.aspx
• http://blogs.devleap.com/rob/archive/2005/09/12/5706.aspx
Developer e Tester
• http://blogs.devleap.com/rob/archive/2008/09/10/vsts-2008-sp1-web-test.aspx
• http://blogs.devleap.com/rob/archive/2007/08/21/visual-studio-2008-for-mobile-dev-e-non-solo.aspx
• http://blogs.devleap.com/rob/archive/2007/07/16/workflow-custom-activity-e-vsts-unit-test.aspx
• http://blogs.devleap.com/rob/archive/2005/12/30/6434.aspx
• http://blogs.devleap.com/rob/archive/2005/09/12/5705.aspx
• http://blogs.devleap.com/rob/archive/2006/01/30/6608.aspx
Database Edition
• http://blogs.devleap.com/rob/archive/2007/08/20/vsts-for-db-pro-power-tools.aspx
• http://blogs.devleap.com/rob/archive/2007/07/28/vsts-for-db-pro-service-release-1.aspx
• http://blogs.devleap.com/rob/archive/2007/02/02/vsts-for-db-pro-aggiunta-e-successivo-refactor-di-un-campo.aspx
• http://blogs.devleap.com/rob/archive/2007/02/02/vsts-for-db-pro-schema-update-per-aggiunta-campo.aspx
• http://blogs.devleap.com/rob/archive/2007/02/01/vsts-for-db-pro-schema-update-con-merge-replication.aspx
• http://blogs.devleap.com/rob/archive/2006/12/25/15649.aspx
• http://blogs.devleap.com/rob/archive/2008/09/17/microsoft-174-visual-studio-team-system-2008-database-edition-gdr-august-ctp.aspx
TFS
• http://blogs.devleap.com/rob/archive/2008/09/09/tfs-2008-sp1-problem.aspx
• http://blogs.devleap.com/rob/archive/2008/09/09/tfs-2008-sp1-problem.aspx
• http://blogs.devleap.com/rob/archive/2008/09/06/visual-studio-team-system-web-access-2008-sp1-power-tool.aspx
• http://blogs.devleap.com/rob/archive/2007/11/20/team-foundation-server-2008-upgrade.aspx
• http://blogs.devleap.com/rob/archive/2005/09/03/5645.aspx
• http://blogs.devleap.com/rob/archive/2005/12/05/6330.aspx
• http://blogs.devleap.com/rob/archive/2006/03/30/7085.aspx
Roberto Brunetti
Articolo Intro to ADO.NET Sync Services pubblicato su Computer Programming di Marzo 2008.
Autore: Roberto Brunetti
Con l’uscita del Framework .NET 3.5 Microsoft propone il primo tassello di un nuovo framework di sincronizzazione informazioni. Questo primo assaggio, denominato ADO.NET Synchronization Services, semplifica la scrittura di codice per replicare/sincronizzare strutture e dati di un database SQL Server verso client remoti che necessitano di lavorare offline.
L’esigenza di replicare il database o porzioni di esso nasce con l’avvento dei portatili e dei palmari a metà degli anni 90: si pensi al classico scenario di raccolta ordini sul campo in cui il venditore scarica sul suo pc/palmare il listino prodotti (tabelle in solo download), inserisce gli ordini su un database locale (in tabelle di solo upload) e magari può modificare alcune informazioni della tabella clienti preventivamente scaricata e successivamente sincronizzata con il database centrale.
Le prime implementazioni su palmare si sono appoggiate ai meccanismi di replica nativi di SQL CE che, sin dalla sua prima versione, propone due tecniche di replica dei dati: Remote Data Access (RDA) e Merge Replication. La prima tecnica consente allo sviluppatore di indicare la query da eseguire server-side e la tabella in locale in cui accogliere i dati tramite il metodo Pull. Una volta scaricati i dati SQL CE tiene monitorate le modifiche effettuate ai dai in locale e consente con un semplice metodo (Push) di inviare al server solo tali modifiche. Tutte le operazioni vengono avviate da codice e non è necessario ne modificare la struttura ne configurare in modo particolare il database server. Diametralmente opposta come approccio troviamo la seconda tecnica nativa di sincronizzazione dei dati presente sia in SQL CE 2.0 che in SQL Server 2000: la Merge Replication richiede una configurazione del database server che comprende la pubblicazione degli oggetti database da replicare con i client, le modalità di replica, l’assegnazione di range per i campi identity, la definizione dei profili di replica (ad esempio il tipo di compressione o il numero di modifiche da inviare in singoli chunk transazionali al client).
Occupandomi di sviluppo mobile dal 1997 ho lavorato molto sia con RDA, scelta preferita nelle casistiche più semplici, sia con la Merge Replication per scenari più “corposi”. Le innumerevoli possibilità di configurazione della Merge Replication la rendono adatta a tutti gli scenari possibili a patto di avere SQL Server (dalla 2000 in poi) come database server e un server IIS che faccia da tramite http per le richieste che arrivano dal client.
In pratica la Merge Replication
· E’ una tecnologia focalizzata sulla configurazione del server
· E’ una tecnologia che fornisce soluzioni end-to-end
· Non è developer-oriented: il codice lato client indica solamente i parametri da usare per quel particolare tipo di client e connessione
· E’ orientata allo scambio di dati e non all’utilizzo di servizi
Per questi motivi il framework .NET in versione 3.5 propone ADO.NET Sync Services, meccanismo più leggero e più gestibile da codice rispetto alla Merge Replication che meglio si adatta a scenari semplici in cui lo sviluppatore, con un impatto minimo sulla struttura delle tabelle, può controllare la replica dei dati. In pratica questa tecnologia consente di avere una cache locale dei dati sul client e i metodi per poter sincronizzare lo store locale con il database server.
I punti fondamentali da chiarire subito sono i seguenti
· La tecnologia si basa su ADO.NET e quindi può lavorare con varie tipologie di database server side
· Lato client viene usato SQL CE 3.5 come store delle informazioni
· Visual Studio 2008 espone wizard e designer per semplificare lo sviluppo
· Il deployment può essere effettuato con ClickOnce
· L’impatto sul server è minimo
· Nativamente lavora single-tier in client-server oppure multi-tier in client-server
· Non è incluso in questa prima versione il supporto nel .NET Compact Framework 3.5
· Il tutto è estendibile per lavorare via servizi ASMX o WCF
Iniziamo con lo schema architetturale delle varie componenti.
Lato client troviamo le classi denominate Client Synchroniztion Provider che si appoggiano al Synchronization Agent per la sincronizzazione delle informazioni. Il Sync Agent controlla la definizione e la modalità di sincronizzazione delle singole tabelle (Sync Table). Al lancio della sincronizzazione il Sync Agent contatta la parte server (per default in-process nella classica topologia client-server) la quale lancia le query sul database per recuperare i dati modificati dall’ultima sincronizzazione e modificare i dati inviati dal client.
Come si può notare dalla slide del nostro corso vengono utilizzati 3 assembly .NET in forma di dll
· Microsoft.Synchronization.Data.SqlServerCe.dll: è la dll utilizzata lato client per iniziare la fase di sincronizzazione. Le classi che si utilizzano derivano dalle classi base definite in questa dll. La versione di tale dll è 3.5, allineata appunto con SQL CE 3.5 che, come abbiamo accennato, viene utilizzato per lo store lato client.
· Microsoft.Synchronization.Data.dll: è in versione 1.0 e rappresenta la parte client del meccanismo di sincronizzazione dei dati. Ogni tabella da sincronizzare espone, sempre tramite derivazione da una classe base definita in questa dll, il tipo di replica da effettuare (ad esempio la tabella è in solo download piuttosto che in upload/download incrementale).
· Microsoft.Synchronization.Data.Server.dll: sempre in versione 1.0 rappresenta la parte server che viene invocata dalla parte client durante ogni processo di sincronizzazione dei dati: anche in questo caso lo sviluppatore deve creare classi derivate da classi base esposte da questa dll per definire i comandi da lanciare sul database server per recuperare i dati o aggiornare le modifiche effettuate dal client.
In pratica, lo sviluppatore deve quindi aderire a questo modello architetturale, creando semplicemente delle classi derivate che rappresentano le tabelle da replicare e i comandi da lanciare sul database. Tutte le dll fanno parte del Framework .NET versione 3.5 e quindi non richiedono installazioni separate. Questa tecnologia non è utilizzabile con il .NET Compact Framework e quindi esclude di fatto l’utilizzo su device basati su Windows CE (e ovviamente Windows Mobile visto che il sistema operativo è sempre Windows CE).
Per facilitare questo compito allo sviluppatore Visual Studio 2008 espone un nuovo item da inserire in progetti desktop per automatizzare la crezione di queste classi: il nuovo item prende il nome di Local Database Cache ed è utilizzabile in qualunque progetto desktop.
Procediamo con il wizard di Visual Studio 2008 per capire le tipologie di replica possibili, gli impatti sul server e il codice generato lato client.
Aggiungiamo un nuovo item al progetto:
Una volta aggiunto il file .sync è possibile aprirlo con un nuovo editor che facilita la configurazione e la creazione delle classi derivate che abbiamo accennato. La prima parte del designer consente di specificare la stringa di connessione verso il database server e verso il database client SQLCE.
Una volta scelte le connessioni viene creato, se non esistente, il file .SDF di SQLCE all’interno del progetto. Nella parte advanced è possibile scegliere in quali progetti della solution corrente dovranno essere create le classi per la sincronizzazione e se tutte le tabella devono essere sincronizzate in un unica transazione. Queste impostazioni si riflettono sulle proprietà delle classi che Visual Studio crea nei progetti indicati: sono quindi impostazioni modificabili in seguito riaprendo il file .sync oppure modificandoli da codice.
Per ogni tabella (nel nostro caso abbiamo solo la tabella tabFeedback) occorre indicareil tipo di sincronizzazione e se presenti i campi che consentono di individuare le modifiche da scaricare sul client ad ogni replica. Il tutto si effettua semplicemente scegliendo la tabella dal designer:
La prima operazione da eseguire è scegliere la modalità di replica: nella sezione client configuration si puà scegliere se creare una nuova tabella o utilizzare una tabella esistente nel database SQLCE per accogliere i dati che arrivano dal server. Nella prima combo-box invece si indica il tipo di replica da effettuare. Le opzioni sono le seguenti
· Snapshot: ogni operazioni di sincronizzazione sulla tabella scarica l’intero set dei dati
· Download-only: la tabella viene solamente sincronizzata dal server verso il client; vengono scaricate solo le modifiche effettuate dall’utlima sincronizzazione
· Upload-only: la tabella viene solamente sincronizzata dal client verso il server; vengono inviate solo le modifiche effettuate dall’utlima sincronizzazione
· Bidirectional: viene effettuato prima l’upload incrementale e poi il download delle modifiche server.
Nella sezione Server Configuration è fondamentale indicare i campi della tabella sul database server da utilizzare per individuare gli insert e le modifiche: le query generate infatti si basano su una clausola WHERE per individuare solo gli aggiornamenti effettuati dopo l’ultima sincronizzazione da parte del client: le ancore agli ultimi valori sono supportati nativamente da SQL CE fin dalla versione 1.0 come vedremo più avanti.
Sempre nella sezione Server Configuration è importante indicare la tabella in cui verranno accolte le cancellazioni effettuate server side e il campo da utilizzare per individuare queste eliminazioni di record.
Dopo aver premuto OK il database server si presenta così:
Nella tabella tabFeedbacks sono stati aggiunti due campi LastEditDate e CreationDate (indicati nel designer nella sezione Server Configuration) per memorizzare la data di modifica e di creazione dei recordo: tali campi vengono valorizzati dai due trigger omonimi come si può vedere dalla figura sopra. Viene creata, sempre secondo i valori indicati nel designer, la tabella _Tombstone, alimentata anch’essa dal trigger DeletionTrigger a fronte di ogni cancellazione effettuata.
Sui campi citati vengono montati degli indici per rendere efficiente la ricerca delle modifiche effettuate quando viene avviata la fase di sincronizzazione.
Il database lato client, creato e sincronizzato subito al termine del wizard, si presenta così:
Oltre ai campi sincronizzati troviamo alcuni campi di sistema (iniziano con un doppio “_”) che consentono al motore di SQLCE di tenere traccia delle modifiche lato client. Da sempre l’engine di SQLCE utilizza campi di sistema per tenere traccia delle modifiche nativamente, visto che su SQL CE non è possibile creare dei trigger: non occorre in ogni caso modificare da codice tali valori.
Su l database SQLCE vengono create anche alcune tabelle:
· __sysOCSDeletedRows
· __sysOCSTrackedObjects
· __sysSyncArticles
· __sysTxCommitSequence
La prima tabella tiene le righe cancellate lato client, la seconda contiene l’elenco degli oggetti da tenere sotto controllo durante le normali operazioni sul DB, la terza tabella, ben conosciuta a chi ha usato la merge replication, tiene l’elenco delle tabella sotto replica, mentre l’ultima tiene traccia della sequenza delle operazioni transazionali andate a buon fine: sono tutte tabelle di sistema non visibili dall’interfaccia di amministrazione di SQL CE (per elencare le tabelle di sistema si può utilizzare select * from information_schema.tables, mentre per vederne il contenuto è sufficiente una semplice select).
Per effettuare una sincronizzazione è sufficiente utilizzare il codice seguente:
LocalDataCacheDevCon2006_FeedbackSyncAgent syncAgent = new LocalDataCacheDevCon2006_FeedbackSyncAgent();
Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();
La prima riga di codice crea l’oggetto syncAgent (la classe è stata generata in automatico dal designer di Visual Studio) e la seconda riga invoca il metodo Synchronize. Tale metodo restituisce un oggetto di tipo SyncStatistics da cui è possibile capire il numero delle modifiche inviate, il numero delle modifiche ricevute per ogni tabella sincronizzata.
E’ possibile modificare, come accennato all’inizio dell’articolo, il comportamento di replica agendo sulle proprietà della classe derivata da SyncAgent. Ad esempio prima di lanciare la replica è possibile impostare la direzione della replica anche da codice:
LocalDataCacheDevCon2006_FeedbackSyncAgent syncAgent = new LocalDataCacheDevCon2006_FeedbackSyncAgent();
// Decido la direzione
syncAgent.tabFeedbacks.SyncDirection =
Microsoft.Synchronization.Data.SyncDirection.Snapshot;
Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();
In questo caso, a prescindere dai parametri indicati nel designer di Visual Studio, stiamo forzando una sincronizzazione di tipo SnatpShot ovvero la ricrezione della tabella lato client e lo scarico di tutti i dati della tabella server.
Come abbiamo indicato precedentemente, Visual Studio 2008, crea anche le classi SyncAdapter server side; il compito di tali classi è specificare i comandi da lanciare sul DB per gestire gli inserimenti, le modifiche e il recupero (incrementale o meno) delle righe modificate. Ecco l’estratto della classe SyncAdapter relativo alla nostra tabella tabFeedbacks:
public partial class tabFeedbacksSyncAdapter : Microsoft.Synchronization.Data.Server.SyncAdapter {
private void InitializeCommands()
{
// tabFeedbacksSyncTableInsertCommand command.
this.InsertCommand = new System.Data.SqlClient.SqlCommand();
this.InsertCommand.CommandText = @" SET IDENTITY_INSERT
dbo.tabFeedbacks ON INSERT INTO dbo.tabFeedbacks ([idFeedback], [FeedbackGUID], [FeedbackTechnology], [FeedbackLevel], [FeedbackSlot], [FeedbackComment], [LastEditDate], [CreationDate]) VALUES (@idFeedback, @FeedbackGUID, @FeedbackTechnology, @FeedbackLevel, @FeedbackSlot, @FeedbackComment, @LastEditDate, @CreationDate” ;
// tabFeedbacksSyncTableDeleteCommand command.
this.DeleteCommand = new System.Data.SqlClient.SqlCommand();
this.DeleteCommand.CommandText = "DELETE FROM dbo.tabFeedbacks WHERE ([idFeedback] = @idFeedback) SET @sync_row_cou" +
"nt = @@rowcount";
// tabFeedbacksSyncTableUpdateCommand command.
this.UpdateCommand = new System.Data.SqlClient.SqlCommand();
this.UpdateCommand.CommandText = @"UPDATE dbo.tabFeedbacks SET [FeedbackGUID] = @FeedbackGUID, [FeedbackTechnology] = @FeedbackTechnology, [FeedbackLevel] = @FeedbackLevel, [FeedbackSlot] = @FeedbackSlot, [FeedbackComment] = @FeedbackComment, [LastEditDate] = @LastEditDate, [CreationDate] = @CreationDate WHERE ([idFeedback] = @idFeedback) SET @sync_row_count = @@rowcount";
// selectIncrementalInsertsCommand command.
this.SelectIncrementalInsertsCommand = new System.Data.SqlClient.SqlCommand();
this.SelectIncrementalInsertsCommand.CommandText = "SELECT * FROM [tabFeedbacks]";
this.SelectIncrementalInsertsCommand.CommandType = System.Data.CommandType.Text;
// tabFeedbacksSyncTableSelectConflictDeletedRowsCommand command.
this.SelectConflictDeletedRowsCommand = new System.Data.SqlClient.SqlCommand();
this.SelectConflictDeletedRowsCommand.CommandText = "SELECT [idFeedback], [DeletionDate] FROM [tabFeedbacks_Tombstone] WHERE ([idFeedback] = @idFeedback)";
// tabFeedbacksSyncTableSelectConflictUpdatedRowsCommand command.
this.SelectConflictUpdatedRowsCommand = new System.Data.SqlClient.SqlCommand();
this.SelectConflictUpdatedRowsCommand.CommandText = "SELECT [idFeedback], [FeedbackGUID], [FeedbackTechnology], [FeedbackLevel], [FeedbackSlot], [FeedbackComment], [LastEditDate], [CreationDate] FROM dbo.tabFeedbac" +
"ks WHERE ([idFeedback] = @idFeedback)";
// tabFeedbacksSyncTableSelectIncrementalInsertsCommand command.
this.SelectIncrementalInsertsCommand = new System.Data.SqlClient.SqlCommand();
this.SelectIncrementalInsertsCommand.CommandText = @"SELECT [idFeedback], [FeedbackGUID], [FeedbackTechnology], [FeedbackLevel], [FeedbackSlot], [FeedbackComment], [LastEditDate], [CreationDate] FROM dbo.tabFeedbacks WHERE ([CreationDate] > @sync_last_received_anchor AND [CreationDate] <= @sync_new_received_anchor)";
// tabFeedbacksSyncTableSelectIncrementalDeletesCommand command.
this.SelectIncrementalDeletesCommand = new System.Data.SqlClient.SqlCommand();
this.SelectIncrementalDeletesCommand.CommandText = "SELECT [idFeedback], [DeletionDate] FROM [tabFeedbacks_Tombstone] WHERE (@sync_initialized = 1 AND [DeletionDate] > @sync_last_received_anchor AND [DeletionDate]" +
" <= @sync_new_received_anchor)";
// tabFeedbacksSyncTableSelectIncrementalUpdatesCommand command.
this.SelectIncrementalUpdatesCommand = new System.Data.SqlClient.SqlCommand();
this.SelectIncrementalUpdatesCommand.CommandText = @"SELECT [idFeedback], [FeedbackGUID], [FeedbackTechnology], [FeedbackLevel], [FeedbackSlot], [FeedbackComment], [LastEditDate], [CreationDate] FROM dbo.tabFeedbacks WHERE ([LastEditDate] > @sync_last_received_anchor AND [LastEditDate] <= @sync_new_received_anchor AND [CreationDate] <= @sync_last_received_anchor)";
}
}
Formattazione complicata per un articolo a parte, i nomi dei comandi sono chiari e non necessita di spiegazione: vengono creati i comandi per esegure le INSERT, gli UPDATE, i DELETE rispetto alle modifiche inviate dai client e i tre comandi per rintracciare, a fronte di una operazioni di sincronizzazione, le modifiche effettuate sul server da inviare al client in modo incrementale.
In base al tipo di sincronizzazione scelto nel designer di Visual Studio vengono generati solo i comandi necessari: ad esempio, se la sincronizzazione è di tipo upload-only non vengono generati i comandi per rintracciare le modifiche effettuate al server, così come se la sincronizzazione è di tipo download-only non vengono generati i comandi per applicare le modifiche al server.
Vista la natura dei comandi generati (non sono stored procedure, le modifiche vengono rintracciate per data di modifica, i dati non vengono partizionati a priori) ADO.NET Sync Service non si adatta a scenari con centinaia di migliaia di record oppure a scenari in cui le modifiche vengono partizionate per zone o per client (territorialità dei dati) che vengono invece brillantemente risolte da tecnologie più corpose e complete come la Merge Replication. Il messaggio che mi preme far passare è che ADO.NET Sync Service è il primo passo di una lunga strada e quindi si adatta benissimo a scenari semplici in cui lo sviluppatore vuole avere il controllo completo e dove non è possibile utilizzare la Merge Replication. Per scenari più “corposi” in cui occorre partizionare molte tabelle con molti record in base al client occorrono tecniche più efficienti di utilizzo dei dati (non si può lanciare una query su 100.000 record per estrarre solo le modifiche destinate ad un agente di vendita ma occorre partizionarle a priori, gestendo anche l’eventuale, ma frequente cambio di territorialità dei dati), tecniche che la Merge Replication espone in modo completo.
Per questo primo articolo introduttivo è tutto, ci risentiamo presto con un articolo di approfondimento sulle classe generate e sulle tecniche per raggruppare i dati, gestire dati per singolo client e passare parametri al motore di replica.
Roberto Brunetti
MCP, MCSD.NET, MCSE + I, MCT
roberto@devleap.it
http://blogs.devleap.com/rob
www.thinkmobile.it
Articoli pubblicati su week.it che ho riuniuto in un unico post.
Uscita la Beta 2 di .NET 3.5 e Visual Studio 2008 (ormai siamo alla RTM, ma era bello lasciare questa frase di inizio agosto che ci ricorda il caldo e le ferie J è giunto il momento di iniziare una serie di articoli settimanali per analizzarne le novità indicando, ove possibile, i puntatori verso informazioni più corpose.
Per prima cosa occorre separare nettamente cosa offre la nuova versione del Framework .NET e quali strumenti mette a disposizione Visual Studio 2008 a supporto delle nuove feature, senza dimenticare i nuovi strumenti indipendenti dalla versione del Framework. Con questa frase voglio sottolineare, se ce ne fosse ancora bisogno, che la piattorma .NET offre, da sempre, librerie e componenti utilizzabili senza l’ausilio di Visual Studio. Dall’altra parte, Visual Studio, in base all’edizione scelta, fornisce non solo strumenti che automatizzano e facilitano l’utilizzano di alcune di queste librerie e componenti, ma fornisce strumenti di supporto per la gestione del ciclo di vita di un software, per l’analisi del codice sorgente e del codice in esecuzione, per la definizione visuale di classi, diagrammi applicativi e di deployment e non ultimo strumenti per la gestione di test sul codice. Quindi se da una parte potremmo scrivere applicazioni, anche complesse, senza utilizzare Visual Studio, dall’altra parte, lo strumento è diventato un alleato importante e indispensabile per la gestione dei progetti software.
Nei vari articoli che seguono, nelle varie settimane, analizzeremo separatamente, le novità che riguardano la piattaforma, come l’integrazione di AJAX nel motore ASP.NET, i nuovi costrutti C# e VB, l’introduzione di LINQ per citarne alcuni, e le novità che riguardano l’ambiente di sviluppo, non tanto rispetto alle novità del Framework, quanto rispetto agli strumenti di supporto della soluzione software, come Unit Testing, nuove funzionalità di refactoring, nuovi designer che appunto prescindono dalla versione del Frameowrk utilizzato: ad esempio, le nuove funzionalità di refactoring possono lavorare anche su progetti .NET FW 2.0/3.0.
Chiudiamo questo primo articolo della serie con un chiarimento sulle versioni con cui lavoreremo: ad oggi, come saprete, i compilatori .NET sono in versione 2.0, versione indicata anche per i linguaggi (C# è in versione 2.0, VB.NET è in versione 8, ovvero la seconda versione di VB.NET dopo la 6.0 che allineata al vecchio mondo COM); il .NET Compact Framework è in versione 2.0 (SQLCE è in versione 3.1), mentre il Framework completo è in versione 3.0: la versione 3.0 ha portato novità con l’aggiunta delle librerie WCF, WPF, WF che si utilizzano ancora con C# 2.0 (e VB.NET 8) e, appunto, i compilatori in versione 2.0.
La versione 3.5 “complica ancora un po’” lo scenario: i compilatori fanno un salto di versione passando alla 3 (quindi avremo C# 3.0 e VB.NET 9), il runtime passa alla versione 3.5 inglobando anche molte librerie ad oggi disponibili come download separato. Il Framework è quindi in versione 3.5 così come il Compact Framework passa alla versione 3.5 e si porta dietro SQL Compact Edition versione 3.5. Visual Studio è invece in versione 2008 e consente la creazione di progetti che si basano sul runtime del Framework 2.0, 3.0 (in pratica versione 2.0 più WPF, WCF, WF) e versione 3.5. Sviluppando un progetto versione 3.5 si ottengono le reference (o si devono fare a mano le reference J) verso le librerie 2.0 per quanto riguarda le funzionalitá base (ad esempio la System.Transaction), verso le librerie 3.0 per quanto riguarda le classi aggiunte nella versione 3.0 (WPF, WCF, WF, WCS) e/o verso le librerie versione 3.5 per le novitá introdotte: queste ultime sono nuove librerie, come il caso di LINQ, rivitazioni di librerie esistenti, come nel caso della System.Web.Extension, oppure aggiunte a funzionalitá della 3.0, come ad esempio i servizi di hosting di workflow in WCF (WorkflowServiceHost).
Mobile Development
La nuova versione di Visual Studio viene equipaggiata con template e SDK per le piattaforme Pocket PC 2003, Windows CE, Windows Mobile 5.0 Pocket PC e Windows Mobile 5.0 SmartPhone e supporta la creazione/gestione di progetti basati sul .NET Compact Framework 2.0 e 3.5. E’ anche possibile convertire un progetto dalla versione .NET CF 2.0 alla versione 3.5. In pratica scegliendo la versione vengono proposte le reference corrette agli assembly .NET nelle varie versioni e agganciato il compilatore allineato alla versione del linguaggio utilizzato. Come abbiamo accennato nel primo articolo della serie, verrà utilizzato il compilatore C# versione 3.0 per progetti .NET CF 3.5 e l’attuale compilatore C# 2.0 per progetti in versione 2.0.
Gli emulatori utilizzano il Device Emulator 2.0 (disponibile oggi al download e installabile anche con Visual Studio 2005), mentre il Device Emulator 3.0 è installabile, almeno per adesso, come download separato. La nuova piattaforma di emulazione fornisce il Device Configuration Manager che elimina la necessità attuale di comporre manualmente file xml per modificare la configurazione degli emulatori. Risulta quindi molto semplice modificare le impostazioni di security per aderire a quelle dei device in commercio: con semplici selezioni da una interfaccia grafica si può lavorare One-Tier, Two-Tier, Locked o Prompt.
Una delle novità più interessanti è sicuramente la possibilità (offerta anche dalla versione Professional) di creare Unit Test su codice .NET CF e eseguirli su device o emulatore. La funzionalità è molto simile a quanto oggi troviamo in Visual Studio Team System for Tester: in pratica sono state aggiunte le librerie mobile per eseguire i test e collezionarne i risultati da riportare nell’IDE di Visual Studio. I risultati possono poi essere pubblicati, associandoli ad una Build, in Team Foundation Server.
Upgrade
Quando si apre un progetto/solution VS 2005 in VS 2008 vengono aggiornati i file di progetto e della solution al nuovo formato.
Occorre tenere presente che:
- Un progetto .NET CF 1.0 viene aggiornato (come reference) alla versione 2.0 del .NET CF
- Un progetto .NET CF 2.0 rimane invariato, non viene quindi portato alla versione 3.5 del runtime
- I riferimenti a SQLCE 3.0 (SQL 2005 Mobile Edition) e SQLCE 3.1 vengono aggiornati a SQL CE 3.5
- I progetti SmartPhone 2003 vengono aggiornati a Windows Mobile 5.0 for SmartPhone
Dopo la conversione, su ogni progetto, con tasto dx, è possibile aggiornare le librerie al .NET CF 3.5.
E' poi possibile, come in VS 2005, modificare la "Target Platform" per portare i progetti da una piattaforma all'altra.
Per quanto riguarda SQL CE, oltre all'ormai famoso upgrade.exe che consente sul device di aggiornare il database .sdf alle nuove versioni (era presente anche in SQLCE 3.0 e SQLCE 3.1), è possibile convertire i database direttamente da VS 2008. E' sufficiente aprire il file sdf dal progetto (o da dove volete ovviamente) per poter effettuare l'upgrade al volo.
Piú avanti vedremo le novità del .NET CF 3.5 come LINQ e WCF e gli strumenti del .NET CF 3.5 SDK per eseguire profiling e analisi delle performance di una applicazione. Come abbiamo avuto modo di chiarire nel precedente articolo, questi strumenti sono indipendenti da Visual Studio.
Visual Studio 2008 for Mobile Dev
Il .NET CF 3.5 adesso installa le stringhe di risorse sul device/emulatore tramite un nuovo cab denominato NETCFv35.Messages.EN.CAB che si trova sotto x:\program files\Microsoft.NET\SDK\CompactFramework\v3.5\windowsce\Diagnostics.
La creazione di un nuovo progetto presenta una nuova maschera, simile a quanto avveniva in VS 2003: non si sceglie il progetto dall'alberino delle varie solution disponibili, ma è sufficiente scegliere Smart Device: una volta scelto il tipo di progetto in una seconda maschera si sceglie la tipologia di piattaforma e poi la versione del .NET CF da utilizzare per le reference e per la distribuzione del runtime sul device. Si può optare per .NET CF 2.0 o per .NET CF 3.5.
N.B. le immagini incluse sono ridotte come dimensione: accanto ad ognuna il link verso la dimensione reale.
Questa la prima maschera:
Come si nota le piattaforme per cui è installato nativamente l'SDK sono Pocket PC 2003, Windows CE, Windows Mobile 5.0 for Pocket PC e SmartPhone.
Una volta scelta la piattaforma si sceglie la versione del .NET CF da utilizzare:
Dopo la scelta della versione del .NET CF, come sempre scegliere Device Application, Class Library e così via per creare il tipo di progetto.
Il designer delle Windows Form (visto che WPF non è disponibile sul .NET CF 3.5) si presenta molto simile al precedente: si può scegliere il form factor da assegnare ad ogni form e ruotare lo schermo del designer come nella versione 2005 di VS.
Anche la toolbox si presenta più o meno identica. Questo l'elenco dei controlli:
La configurazione dell'emulatore da Visual Studio prevede quanto conosciamo con l'attuale Device Emulator 2.0 e consente la gestione del livello della batteria: questa funzione risulta molto comoda per testare un'applicativo power-aware (come dovrebbero essere tutti): è inutile lanciare una operazione lunga di analisi sui dati, ad esempio, se resta il 2% di batteria.
Molto interessante il nuovo Device Configuration Manager, primo fra gli strumenti per facilitare la configurazione di device e emulatori: ad oggi è necessario comporre i file .xml di provisioning e "installarli" tramite rapiconfig.exe. Con questo nuovo strumento risulta molto semplice leggere la configurazione, modificarla e vedere le differenze fra la configurazione desiderata e quella attuale. Si possono importare anche i nostri file di provisioning esistenti per una facile migrazione al nuovo strumento.
Risulta molto semplice così testare l'applicazione firmata o non firmata sulle varie configurazioni dei device One-Tier, Two-Tier, Locked, Prompt e così via.
Tramite questo strumento è possibile visualizzare i certificati digitali installati su device e emulatori, installarne di nuovi e verificarne i dettagli. Una nota, i nomi dei certificate store sono cambiati: non abbiamo più il privileged e unprivileged, ma Privileged Store e Standard Store, dove il secondo rappresenta lo store per i certificati con cui le applicazioni possono girare in normal-mode su device two-tier.
Il .NET CF Remote Performance Monitor consente, nella nuova versione di fare un "attach to process" rendendo più semplice l'analisi di una porzioni di applicazione: la versione attuale deve lanciare l'applicazione e quindi ci costringe a visualizzare i dati complessivi di tutta l'applicazione fino a raggiungere il punto in cui si desidera effettuare le verifiche. La parte device-side viene installata in automatico dallo strumento senza bisogno di copiare manualmente i file nella directory \Windows: qualche problemino sulla mia Beta non consente di eseguire questa operazione in automatico e quindi ho dovuto comunque copiare i file a mano. I nuovi file device-side hanno nomi diversi dai precedenti e più precisamente sono: rtf3_5.dll e rtfhost3_5.exe. Tali file si trovano sempre nella directory dell'SDK del .NET CF 3.5 x:\program files\Microsoft.NET\SDK\CompactFramework\v3.5: ricordatevi di scegliere poi la directory corretta in base al processore del device; per gli emulatori si utilizza sempre la versione ARM.
Nella prossima immagine la directory con gli strumenti lato PC di sviluppo. Oltre al .NET CF RPM, troviamo LogViewer, già presente nella versione 2.0 SP1/SP2 e due nuovi strumenti:
Il primo NETCFCLRProfiler è una versione ridotta (e per la verità anche diversa) del Profiler del Framework completo. Consente di analizzare nel dettaglio un'applicazione in esecuzione: anche in questo caso ci si può "attaccare" ad un processo già avviato e l'installazione delle dll lato device è automatica (come sempre sono sfigato e ho dovuto copiare a mano clrpro3_5.dll sul device). Lo strumento visualizza anche sotto forma di flusso grafico le chiamate fra i vari metodi consentendo una semplice (si fa per dire quando si parla di profiling in generale) individuazione dei punti della call stack che possono dare problemi.
Come abbiamo accennato, il compact frameowrk 3.5 arriva con qualche dll in più per supportare LINQ e WCF (sempre in versione ridotta: entrambi sono c.a. 250KB come obiettivo nella versione finale) e i nomi dei cab sono stati leggermente rivisti.
Nella System.ServiceModel si trovano le "porzioni" del motore di WCF e il binding basicHttpBinding, mentre in Microroft.ServiceModel.Channels.Mail.* il nuovo canale (non disponibile in WCF su desktop) WindowsMobileMailBinding che consente lo scambio di messaggi SOA sfruttando il canale della posta elettronica fra device (Pocket Outlook) e Exchange 2007; ricordo che Exchange 2007 supporta il push dei messaggi verso il device.
Come sempre le sottodirectory wce400 e wce500 contengono i cab per l'installazione reale su device o emulatore in base alla versione del sistema operativo. Ricordo che Windows Mobile 6.0 si basa sempre su Windows CE 5, per precisione sulla 5.2.
System.Data.Entities contiene invece le parti applicative dell'entitiy framework, mentre il provider è contenuto sotto System.Data.SqlServerCe.Entitiy.dll che arriva con l'installazione di Sql Server Compact Edition 3.5.
SQL CE 3.5 arriva con qualche piccola novità per i device e con grandi novità su utilizzato su desktop, Tablet o UMPC.
La directory di installazione sul pc di sviluppo è x:\program files\Microsoft SQL server Compact Edition\v3.5 e si presenta così:
Come accennato, entity framework viene reso disponibile tramite System.Data.SqlServerCe.Entity, ma solo se utilizzato su PC, Tablet o UMPC: non è quindi disponibile per Windows Mobile.
L'installazione prevede diversi componenti:
1) SSCEVSTools-ENU.msi: installa i componenti design-time in Orcas sotto la classica directory x:\program files\microsoft visual studio 9\Common7\IDE. Questi componenti non devono essere distribuiti
2) SSCERuntime-ENU.msi: installa i componenti di runtime per desktop, tablet e UMPC. Sono indispensabili sia a runtime che a design time. Questo MSI installa anche i nuovi componenti per la versione desktop di SQCE 3.5: Microsoft Synchronization Services for ADO.NET (OCS) e la già citata System.Data.SqlServerCe.Entity.dll.
3) SSCEDeviceRuntime.msi: installa i componenti destinati ai device e emulatori sulla macchina. Questi componenti sono necessari per l'integrazione con VS Orcas anche sulla macchina di sviluppo.
Le novità che abbiamo bollato come minori (rispetto appunto a EntityFramework e OCS) riguardano soprattutto il query processor :
- data type di tipo timestamp
- nested query
- CROSS apply e OUTER APPLY
- CAST
- TOP (finalmente !!!!!) diventa così più semplice paginare i dati per la loro visualizzazione nei minuscoli schermi
Oltre a OCS e Entity Framework, SQL CE 3.5, se utilizzato sul desktop, fornisce la possibilità di rientrare in una transazione creata con la System.Transaction 2.0: in due parole una operazione su SQL CE 3.5 può essere inserita all'interno del TransactionScope.
I Book On Line non sono ancora disponibili.
Abbiamo accennato nei post precedenti come in Visual Studio Orcas sia possibile effettura Unit Testing su device (o emulatore): credo che questa funzionalità, affiancata dal nuovo .NET CF RPM e dal profiler, siano le novità più importanti e utili della nuova versione.
Tra l'latro in Orcas gli strumenti di Unit Testing non sono più all'interno di Team System, ma disponibili nella versione Professional, almeno a quanto ci è dato sapere oggi.
Ecco le maschere di crezione di uno unit test, praticamente identiche (tranne un settaggio) a quanto oggi disponibile in VSTS:
Nella seconda immagine, come si può notare, la configurazione degli host prevede il tipo Smart Device con relativa piattaforma e scelta fra device e emulatore.
Se si crea una performance session su uno Unit Test il codice viene fatto girare sul desktop.
Anche senza l'automatismo in VS Orcas, oggi è possibile eseguire unit test su codice mobile, creando un progetto desktop con i sorgenti mobile linkati al suo interno e oppure compilazioni condizionali nel caso in cui il codice faccia uso di dispositivi, tipo lettore di codice a barre ad esempio, non disponibili sul desktop. Fra qualche giorno posso postare un mio articolo, scritto per Infomedia, proprio su questo argomento. La demo allegata all'articolo è già disponibile sul mio sito Think Mobile all'indirizzo http://thinkmobile.it/files/folders/mmdcii/entry5896.aspx.
Il nuovo Device Emulator 3.0 si presenta così:
A parte la user interface pressochè identica, è possibile salvare la configurazione degli emulatori con Save As e riconfigurare l'emulatore. Il file di configurazione (.defcfg) è anche molto semplice da modificare a mano per cambiare al volo qualcosa senza ricorre alla user interface e soprattutto per poter scambiare la configurazione degli emulatori fra i membri di un team di sviluppo senza doverla ricreare su ogni PC di sviluppo.
.NET Compact Framework 3.5
Il nostro filone su Visual Studio 2008 e .NET 3.5 prosegue con questo articolo dedicato alle novità del .NET Compact Framework versione 3.5. Le novità vedono l’introduzione di LINQ To SQLCE, Windows Communication Foundation, alcune API per lavorare con certificati digitali e qualche aggiunta alle classi Windows Forms. Oltre a questo troveremo un rivisto Remote Performance Manager e un nuovo CLR Profiler.
La versione 3.5 utilizza i compilatori in versione 3 (quindi avremo C# 3.0 e VB.NET 9), quindi molte novitá riguardano il linguaggio stesso e i compilatori. Anche SQLCE fa un salto in avanti presentandosi in versione 3.5. Avremo modo di parlare delle novitá del mini/db in un articolo separato.
l primo nuovo componente è Windows Communication Foundation: si tratta di un subset ridotto dell'attuale versione per il framework completo pensata per comunicazioni device-to-server, server-to-device e device-to-device (Peer to Peer).
Il subset di funzionalità prevede il trasporto via Http (BasicHttpBinding), un subset delle funzionalità di WS-Security e WS-Addressing, con la possibilità di estensioni custom, l'encoding basato su Text o algoritmi custom. Quanto non citato non è presente: manca quindi il supporto a Service e Contract Model, manca per adesso anche l'utility svcutil.exe che uscirà sotto forma di Power Toy successivamente.
E' stato creato un trasporto apposito per sfruttare il canale email per l'invio di messaggi WCF: il tutto è accessibile tramite WindowsMobileMailBinding (non presente nella versione completa sul .NET Framework) e sfrutta i servizi esposti da Exchange 2007 per lo store-and-forward dei messaggi. Tramite Exchange 2007 si sfrutta anche la possibilità di usare Push-Technology per inviare messaggi dal server ai device.
L’elenco completo delle features disponibili con WCF “mobile” rispetto alla versione Full è il seguente:
|
Feature |
Desktop WCF |
Compact WCF |
| Binding: |
|
|
| · BasicHttpBinding |
Si |
Si |
| · CustomBinding |
Si |
Si |
| · WindowsMobileMailBinding |
N/A |
Si |
| · ExchangeWebServiceMailBinding |
Si, se si installa NetCF |
Si |
| Formatter: |
|
|
| · SoapFormatter |
Si |
Si |
| · BinaryFormatter |
Si |
No |
| Encoder: |
|
|
| · TextMessageEncoder |
Si |
Si |
| · BinaryMessageEncodingBindingElement |
Si |
No |
| · MTOMEncoder |
Si |
No |
| · GzipEncoder |
No |
No |
| Transport: |
|
|
| · HttpTransportBindingElement |
Si |
Si |
| · HttpsTransportBindingElement |
Si |
Si |
| · MailTransportBindingElement |
Si, se si installa NetCF |
Si |
| · MsmqTransportBindingElement |
Si |
No |
| · TcpTransportBindingElement |
Si |
No |
| · (other transports) |
Si |
No |
| XmlDictionaryReader/Writer |
Si |
Si; stub sopra XmlTextReader/Writer |
| DataContractSerializer |
Si |
No; compatibile però con DCS via XmlSerializer |
| Service proxy generation |
SvcUtil.exe |
NetCFSvcUtil.exe (si trova nei Power Toys, non in VS2008 |
| · Non-HTTP transports |
Si |
No |
| · Custom headers |
Si |
No |
| WS-Addressing |
Si |
Si |
| WS-Security message level security |
|
|
| · X.509 |
Si |
Si |
| · Username/password |
Si |
No |
| · SecurityAlgorithmSuite.Basic256Rsa15 |
Si |
Si |
| · SecurityAlgorithmSuite.Basic256 |
Si |
No |
| WS-ReliableMessaging |
Si |
No |
| Patterns |
|
|
| · Service model |
Si |
No |
| · Message layer programming |
Si |
Si |
| · Buffered messages |
Si |
Si |
| · Streaming messages |
Si |
No |
| · Endpoint descriptions in .config files |
Si |
No |
| Channel extensibility |
Si |
Si |
| Security channel extensibility |
Si |
No |
Il secondo componente importante è LINQ, anch’esso subset del fratello maggiore: sono presenti LINQ to Object, LINQ to XML, LINQ to Dataset; nella Beta2 è apparso il supporto per LINQ to Entities e LINQ to SQL per adesso senza designer associato in Visual Studio 2008: occorre lavorare con Sql Metal per definire il modello. Per una introduzione su LINQ consiglio il libro Introducing LINQ di Paolo Pialorsi e Marco Russo e la relativa community www.introducinglinq.com.
La nuova versione si porta dietro nuovi strumenti di diagnostica: uno strumento centralizzato per abilitare/disabilitare le funzionalità di logging, dal finalizer log, al diagnostic log passando per Interop e statistiche.
Molto interessante il CLR Profiler, versione ridotta della versione del framework completo, che fornisce metodi per tracciare il comportamento delle applicazioni.
Il .NET Compact Framework Remote Performance Monitor 3.5 consente la visualizzazione della FReachable Queue e analisi più dettagliate sugli eventuali memory leak che, contrariamente a quanto pensano in molti, possono comunque verificarsi in ambiente .NET.
L’elenco completo degli strumenti della versione 3.5 del .NET Compact Framework é
.NET CF RPM: migliorato
CLR Profiler: nuovo
Logging Options: nuovo
Finalizer Log: nuovo
Network log: esistente
Native Interop
Loader Log
Gli strumenti non sono inclusi in Visual Studio 2008, ma arrivano come Power Tools, per adesso scaricabili da http://www.microsoft.com/downloads/details.aspx?familyid=c8174c14-a27d-4148-bf01-86c2e0953eab&displaylang=en&tm e in versione September CTP.
Una volta installato il tutto si puó utilizzare Logging Configuration per abilitare e disabilitare, su device e emulatori, le varie opzioni di logging:
- Loader
- Native Interop
- Network
- Error
- Finalizer
Ogni logger scrive le informazioni in un file di log nella stessa directory dove gira l'applicazione.
Per effettuare analisi sul traffico di rete, i pacchetti spediti, i byte inviati e ricevuti é possibile usare il Network Logger Viewer che visualizza, in forma "grafica" il file di log relativo.
Ad esempio questo i log su una chiamata ad un servizio WCF in basicHttpBinding effettuata dall'emulatore.
Nella prima griglia si vedono, in base ai timestamp, i vari record di invio/ricezione pacchetti e abilitando (come da figura) le colonne Bytes Sent e Bytes Received si puó facilmente analizzare quante informazioni sono state inviate.
Per ogni record, nella finestra sotto troviamo il dettaglio dei byte scambiati e lo stream relativo, mentre nella finestrina a destra si vede la rappresentazione testuale.
In questo esempio, la chiamata riguarda appunto un servizio WCF che accetta un valore intero e restituisce, come da figura sotto la scadenza di una polizza.
Questo e gli altri strumenti sono utilizzabili anche con applicazioni .NET CF 2.0, senza bisogno di nessuna ricompilazione
L'unica cosa che occorre fare è eseguire la "vecchia" (si fa per dire) applicazione 2.0 con il runtime versione 3.5. Per farlo occorre creare un file di configurazione (app.exe.config) che indica appunto di far girare l'applicazione con il 3.5 come segue:
<configuration>
<startup>
<supportedRuntime version="v3.5.xxxx"/>
</startup>
</configuration>
dove xxxx sta per la build che state usando (Beta1, Beta2 o l'imminente RC). La Beta 2 attuale è 3.5.71210.
Un modo più semplice per farlo è usare NetCFCfg.exe direttamente dal device: questo softwarino consente di
- Listare le versioni del .NET CF installate sul device
- Visualizzare il contenuto della GAC
- Editare la configurazione di default del device in base alle varie versioni installate
- Editare appunto i file di configurazione delle applicazioni
L'exe può essere copiato a mano nella directory Windows: come sempre occorre prendere quello corretto in base a piattaforma e processore a partire dalla directory C:\Program Files\Microsoft.NET\SDK\CompactFramework\v3.5\WindowsCE (wce500 o wce400 e sottodirectory relativa al processore) o addirittura ottenere una copia automatica quando si attacca il device o l'emulatore ad ActiveSync e si sincronizza il tutto.
VSTS non solo Mobile
Alcuni screen shot che riguardano lo sviluppo con VSTS 2008 in generale.
Per prima cosa la parte server (Team Foundation Server) si presenta così
Si basa su SharePoint v3 (di cui avremo modo di parlare alla SharePoint Conference). Il default web site espone le due classiche directory di reporting server, mentre il sito Team Foundation Server espone i servizi per Build, Services, Version Control, OLAP e Work Item Tracking.
Le varie applicazioni sono divise in 3 Application Pool che ospitano appunto i servizi TFS, la parte di reportistica e l'amministrazione di SharePoint.
Dal menù di un progetto è possibile generare le "metriche del codice" (Code Metrics suona molto meglio) di cui abbiamo già accennato qualcosa nell'altro mio post sulla CTP di Marzo.
Per capire meglio questa nuova funzionalità prendiamo ad esempio il seguente codice
Una volta generato il Code Metrics si ottiene questo risultato.
"Code Metrics" consente di capire se il codice di un progetto/solution è complesso da manutenere, il livello di coupling, la profondità gerarchica delle classi e l'ormai famoso indice di Cyclomatic Complexity (già presente nel Code Analysis della versione attuale in forma testuale).
Altra opzione "carina" è la gestione delle using di un sorgente C#. Dal menù contestuale si possono infatti organizzare le using:
Ultimo screenshot: la versione VSTS for DB Pro 2008 presenta nativamente l'analisi del codice T-SQL tramite FXCop (che in realtà già dalla versione 2005 si chiama Code Analysis): queste le regole disponibili, immagino già note a molti:
Power Tools VSTS 2008 Architect
Il nostro filone su Visual Studio 2008 e .NET 3.5 prosegue con questo articolo dedicato ad un add-in giá disponibile per Visual Studio 2008 Team System Architect Edition che prendeil nome di Power Tools.
Il componente nasce da una esigenza fondamentale nella progettazione di applicazioni distribuite basate su vari layer: nella versione 2005 di Visual Studio Team System Architect Edition non era possibile rappresentare nel diagramma AD (Application Diagram) progetti di tipo class library che, come da documentazione, dovevano essere considerati parte del progetto principale: in pratica un progetto Windows Form, oppure ASP.NET, oppure Web Service ospitava nel digramma tutte le dll referenziate.
Se da una parte, questa rappresentazione logica é corretta, dall’altra non consente di rappresentare tutti gli elementi di una soluzione Visual Studio dal punto di vista fisico. Noi, in DevLeap, avevamo optato per un workaround in modo da rappresentare in AD e DD (Deployment Diagram) i progetti di tipo DLL. Tutte le nostre implemtazioni fanno uso di UI Layer, composto da uno o piú progetti di tipo class library, di un Biz Layer che si appoggia a dei factory per l'accesso ai dati o la chiamata a servizi (Web Service o WCF). Utilizzavamo quindi le application di tipo “Generic Application” per ogni class library presente nella soluzione.
A tal proposito si vedano i post
http://blogs.devleap.com/rob/archive/2005/09/06/5670.aspx
http://blogs.devleap.com/rob/archive/2007/04/11/solution-che-accompagner-molte-demo-della-devcon-2007.aspx
In pratica il diagramma relativo alla parte BIZ/DAL evidenzia l'uso di Generic Application per rappresentare i progetti di tipo Class Library.
In Visual Studio 2008 Architect Edition, a parte altre interessanti novitá, la situazione é rimasta praticamente invariata, a meno di non installare i Power Tools per la versione Architect. Per adesso sono allineati alla Beta 2 di VSTS 2008.
Con questo add-in per la versione 2008 é finalmente possibile rappresentare e soprattutto descrivere i progetti di tipo class library, legarli al diagramma SDM e come sempre, sincronizzarne le impostazioni con il progetto reale. Ogni connessione fra applicazioni e class library gestita dall’Application Diagram verrá poi gestita con una reference fra i progetti Visual Studio, cosí come ogni reference effettuata dai progetti si riflette sul diagramma tramite la visualizzazione della connessione. Il supporto two-way aderisce a quanto giá presente negli strumenti di design fino dalla versione 2005. La descrizione delle impostazioni relative ai progetti di tipo class library viene poi memorizzata in file .SDM direttamente nel progetto Visual Studio che rappresenta la dll stessa.
Ad esempio, partendo da un semplice esempio, é possibile rappresentare i progetti di tipo Class Library che appaiono nella toolbox dopo l'installazione dei Power Tools.
Ogni connessione fra applicazioni e class library verrá poi gestita con una reference fra i progetti Visual Studio, cosí come ogni reference effettuata dai progetti si riflette sul diagramma tramite la visualizzazione della connessione.
Tempo fa ho eseguito l'upgrade della nostra soluzione (presentata a DevCon 2005 e DevCon 2007) composta ormai da una settantina di progetti Visual Studio in unica solution: si veda il post http://blogs.devleap.com/rob/archive/2007/05/10/upgrade-to-orcas-beta-1.aspx.
Dopo aver montato i Power Tool ho deciso di ridisegnare AD e DD facendo uso dei progetti di tipo class library.
Giustamente il risultato che mi si é presentato riaprendo il diagramma dopo l'installazione é il seguente
In pratica l'AD si é accorto delle varie reference a progetti class library dai progetti reali e ha rappresentato le "figurine" e le relative connection sul diagramma. I nomi dei progetti sono gli stessi dei nomi reali dei progetti .csproj a cui sono stati tolti in automatico, come da prassi, i ".".
Per eliminare le Generic Application che avevamo usato come placeholder nella versione 2005 occorre procedere eliminando prima tutti i file .SDM (delle generic application) dalla solution di Visual Studio e poi eliminare dal diagramma i relaitivi elementi grafici.
Gli SDM relativi alle applicazioni di tipo Class Library vengono memorizzati direttamente nel relativo progetto Visual Studio, come accade da sempre per i tipi di applicazioni gestiti dall'AD.
Altra operazione che avevamo fatto come workaround era stata quella di legare le class library dello strato DAL al database, operazione non piú necessaria in quanto il legame fra applicazione e DB sta giustamente nell'applicazione principale (Windows Form, WPF, ASP.NET che sia) ove infatti, da sempre, sono memorizzate le connection string nei rispettivi file .config.
Alla prossima
di Roberto Brunetti
Roberto è un libero professionista del gruppo DevLeap (www.devleap.com) sul cui sito si trovano articoli e blog tecnici sulle tecnologie legate allo sviluppo software in .NET. E’ specializzato in ASP.NET, Sviluppo mobile, Architetture distribuite e Visual Studio Team System. E’ l’autore del libro ASP.NET Full Contact edito da Mondadori Informatica e numerose pubblicazioni su riviste del settore. Ha partecipato come speaker a numerose conferenze del settore ed è MCT da 1997. È il fondatore della community www.ThinkMobile.it dedicata allo sviluppo mobile.
Dopo vari articoli su SQLCE 3.0 (che diventa installabile anche su Desktop con SQL Compact Edition 3.1) è arrivato il momento di dare uno sguardo alla scrittura di una propria libreria di classi per facilitare lo sviluppo di applicazioni. Questo articolo vuole essere una panoramica delle potenzialità spesso ignorate di Visual Studio 2005, non tanto per la produzione automatica di righe di codice (tecnica tra l’altro pericolosa in caso di grandi volumi di dati), quanto per la versatilità nella scrittura della libreria.
Sembra un articolo introdottivo, e in parte lo è, ma i concetti che vorrei passare, soprattutto dopo la prima parte, spero servano a sviluppare più velocemente testando l’applicazione sul desktop e con strumenti evoluti piuttosto che fare F5 e testare l’applicazione su device o emulatori.
In sequenza affronteremo la scrittura da zero di una classe molto semplice per leggere file di configurazione, feature assente nel .NET Compact Framework 2.0. Questo semplicissimo scheletro ci farà da guida per affrontare le modalità con cui noi di DevLeap (www.devleap.com) sviluppiamo applicazioni mobile. Cercheremo di scrivere e testare la libreria direttamente sul desktop per poi farne una analisi con gli strumenti di Visual Studio Team System esattamente come se fosse una libreria desktop.
Non è nel mio stile fare un articolo passo passo in quanto spesso preferisco cercare di andare in profondità sugli argomenti lasciando poi al lettore la fantasia di implementazione. Questa volta farò uno strappo alla regola e partiremo da zero in modo da mettere insieme i vari pezzi. Questo articolo non vuole essere però una guida per neofiti, quanto mettere in evidenza le potenzialità di uno strumento facendo capire passo passo gli step di sviluppo che adottiamo.
“Mi suona strano iniziare così”: aprite Visual Studio e create un nuovo progetto (utilizzeremo C#, ma il tutto è facilmente adattabile a VB.NET)
Creiamo un progetto C# - Smart Device di tipo libreria: scegliere la piattaforma (Pocket PC 2003, SmartPhone 2003, Windows CE 5.0, o Windows Mobile 5.0 xxx) è un dettaglio tralasciabile; come sapete questa selezione serve solo a utilizzare un template di Visual Studio che prepara lo scheletro applicativo con le reference corrette e qualche vincolo per evitare di usare librerie che potrebbero non girare sulla piattaforma target; per esempio, se scegliete Pocket PC 2003 non si vedranno le librerie Windows Mobile 5.0 nell’elenco delle librerie sotto Add Reference. In ogni caso, il codice che viene prodotto dalla compilazione è codice IL (o CIL) e come tale può girare ovunque giri il runtime del .NET Compact Framework 2.0. Nel caso di progetti “Device Application” la scelta, che ancora una volta non è vincolante, è più utile in quanto il designer di Visual Studio visualizza i form all’interno di device aderenti, come look & feel, alla piattaforma scelta. Ripeto: la scelta non è vincolante, ma è solo una facilitazione per trovare Reference e designer “adatti” alla piattaforma. Con Visual Studio 2005 è ancora possibile modificare la piattaforma scelta per un progetto dopo la creazione: è sufficiente premere tasto destro sul progetto e scegliere – Change Target Platform; volendo si può agire anche manualmente nei file di progetto in formato XML per allargare la scelta di possibili piattaforme.
Scelgo Windows Mobile 5.0, posiziono il tutto sotto c:\temp, nomino la Solution DevLeap.Sample, e il progetto DevLeap.Library.Mobile. Solution e progetto hanno nomi diversi semplicemente perché successivamente aggiungeremo altri progetti alla solution in modo da avere un client di test e progetti desktop per testare la nostra libreria.
Naming convention a parte questa la maschera di creazione progetto:
Appena creato il progetto cancelliamo la mitica Class1.cs e creiamo un nuovo sorgente C# denonimato Utility.cs.
Il progetto, a questo punto, ha una classe Utility in cui aggiungeremo i metodi appunto utili J nelle nostre applicazioni mobile. Il namespace per default è giustamente DevLeap.Library.Mobile.
Rendiamo pubblica la classe in modo da poterla richiamare dall’esterno e, se volete, sealed in modo da non poterla derivare ulteriormente e da non doverci preoccupare nella sua definizione di eventuali politiche di inheritance.
Questo il nostro progetto:
Fino a qui niente di nuovo immagino. Aggiungiamo un metodo per leggere il classico file di configurazione. Solitamente utilizzo questa tecnica: espongo un metodo GetAppConfigString che riceve in input il nome del file config e la key da leggere. Non calcolo in automatico il nome del config, come fa il .NET Framework completo per evitare “casini” di concatenazione sul nome del file da trovare; questo evita problemi di naming sul file .config che hanno, ad esempio, creato non pochi problemi nelle vecchie versioni di OpenNETCF. Preferisco passare un parametro in più piuttosto che lasciare alla libreria l’onere di trovare il nome corretto. Se preferite calcolare il nome in automatico, i metodi GetExecutingAssembly e GetCallingAssembly della classe Assembly possono darvi una mano.
Per evitare la lettura del config da disco (pardon, da scheda SD J) ad ogni richiesta, metto in cache il documento XML alla prima lettura. Ci servono quindi una variabile statica _xmlDoc (per rispettare la naming convention) e una variabile di tipo Object su cui effettuare la sincronizzazione in caso di applicazione multithread.
Ecco la prima parte del codice che inserisce in cache un XmlDocument nel caso in cui sia null:
if (Utility._xmlDoc == null)
{
lock (_xmlDocSyncLock)
{
if (Utility._xmlDoc == null)
{
XmlDocument xmlDoc = new XmlDocument();
string xmlDocPath = Utility.GetAppPath() + "\\"
+ configName;
XmlTextReader tr = new XmlTextReader(xmlDocPath);
xmlDoc.Load(tr);
tr.Close();
_xmlDoc = xmlDoc;
}
}
}
Prima di inserire il codice nel metodo corretto due informazione: stiamo usando un pattern .NET che, in questo caso, ci serve a caricare il documento XML nella variabile statica _xmlDoc. L’utilizzo del lock e della doppia if su _xmlDoc serve a evitare che più thread chiamanti vadano in conflitto durante l’assegnazione della variabile _xmlDoc.
Per leggere il file .config in formato XML (il classico file .config di .NET) ho utilizzato un XmlDocument che viene alimentato da un XmlTextReader. Per rintracciare la path del file config si utilizza un’altra funzione da aggiungere alla nostra libreria (Utility.GetAppPath) che vedremo tra un attimo.
Una volta assegnato il documento XML alla nostra variabile statica si procede alla sua lettura; ecco il metodo completo:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
namespace DevLeap.Library.Mobile
{
public sealed class Utility
{
private static XmlDocument _xmlDoc;
private static Object _xmlDocSyncLock = new Object();
public static string GetAppConfigString(string configName,
string key)
{
if (Utility._xmlDoc == null)
{
lock (_xmlDocSyncLock)
{
if (Utility._xmlDoc == null)
{
XmlDocument xmlDoc = new XmlDocument();
string xmlDocPath = Utility.GetAppPath() + "\\"
+ configName;
XmlTextReader tr = new XmlTextReader(xmlDocPath);
xmlDoc.Load(tr);
tr.Close();
_xmlDoc = xmlDoc;
}
}
}
String value = "";
XmlNode node = _xmlDoc.SelectSingleNode
("//configuration//appSettings//add[@key='" +
key + "']/@value");
if (node != null)
{
value = node.Value.ToString();
}
return value;
}
}
}
Per leggere la “key” ricevuta come argomento all’interno dell’albero dei nodi ho utilizzato una espressione XPath tramite il metodo SelectSingleNode. Nel caso in cui non trovi l’elemento di configurazione ho deciso di restituire String.Empty.
Inseriamo a questo punto la funzione GetAppPath:
public static string GetAppPath()
{
#if All
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
#else
return Path.GetDirectoryName(Assembly.GetCallingAssembly().GetName().CodeBase);
#endif
}
Vedremo più avanti l’utilizzo della #if (compilazione condizionale); in pratica, visto che su Windows Mobile caricare tante DLL può essere un problema, solitamente scrivo l’applicazione su diversi layer (UI, BIZ, DAL, Factory, Entity e così via) e poi li confeziono in un unico exe sui device che possono avere il problema di caricamento. Vedremo come fare più avanti. Per adesso la #if All serve a chiamare il metodo Assembly.GetExecutingAssembly nel caso in cui il progetto sia confezionato con un unico exe oppure Assembly.GetCallingAssembly per ottenere l’assembly chiamante di questa libreria. In entrambi i casi prelevo poi il CodeBase che rappresenta la directory da cui è stato letto l’assembly in questione.
Per poter usare la classe Path e la classe Assembly occorrono due using (le reference sono già corrette come le ha impostate il template di Visual Studio): System.IO per Path e System.Reflection per Assembly. Ovviamente qualunque altra tecnica (compreso passare il path completo al file .config sono ugualmente valide).
A questo punto creiamo un progetto exe che utilizza la libreria: per uniformità creiamo un progetto Device Application per Windows CE 5.0 che chiameremo Applicazione. All’interno del progetto metteremo anche il file .config che useremo dalla libreria. Ho aggiunto il progetto dalla solution con tasto destro Add New Project e poi inserito un file xml chiamato Applicazione.Config.
Ecco il risultato
Il nome del file config, per uniformità, è Applicazione.config. Se preferite mantenere la naming convention delle applicazioni desktop usate app.config: occorre però stare attenti in quanto successivamente cercheremo di utilizzare questa libreria sul desktop e così facendo ritorneremo nel problema accennato in precedenza: il calcolo del nome del file config automatico.
A questo punto si può inserire un pulsante di test sul form per provare a leggere il file di configurazione; prima di aggiungere il codice occorre ovviamente una reference dal progetto Applicazione al progetto DevLeap.Library.Mobile. Il form conterrà poi il codice per leggere il .config che, com’è intuibile, è il seguente:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using DevLeap.Library.Mobile;
namespace Applicazione
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void cmdReadConfig_Click(object sender, EventArgs e)
{
MessageBox.Show(Utility.GetAppConfigString
("Applicazione.config", "Test"));
}
}
}
Prima di eseguire il progetto su un emultatore o su un device occorre marcare il file Applicazione.config come Build Action = Content e come Copy To Output Directory = Always oppure Copy if Newer; queste impostazioni si scelgono dalla mascherina delle proprietà del file all’interno del progetto di Visual Studio e consentono appunto di eseguire il deploy come contenuto (non come compilazione ovviamente) del file di configurazione.
Eseguito il deploy e premuto il pulsante di test sul form (che ho chiamato cmdReadConfig) otteniamo ovviamente la stringa memorizzata nel file .config.
Proviamo quindi ad andare oltre: una volta creata la propria libreria, così come gli altri strati dell’applicazione occorre testare il tutto; testare il codice sul device è una procedura abbastanza lunga: per ogni modifica occorre rifare il deploy dell’applicazione, riavviare il debug (non c’è Edit & Continue sul device !). Debuggando il tutto sull’emulatore si va un po’ più veloci (soprattutto se collegate l’emulatore via DMA e usate la tastiera del pc per l’input), ma abbiamo sempre il problema di deploy per ogni modifica, l’impossibilità di usare Edit & Continue e soprattutto utilizziamo un “coso mobile” senza un file system comodo da visualizzare e editare.
Visual Studio Team System offre molti strumenti che facilitano il compito durante il test funzionale e il test di performance...ma…purtroppo questi strumenti non girano in ambiente Windows CE. Lo sviluppatore “accanito” però non si fa spaventare da questa limitazione della prima versione di Team System e si organizza J. Come ? Così !
Il Compact Framework è una versione ridotta del .NET Framework quindi, a parte qualche funzionalità specifica del .NET CF che non esiste su .NET FW completo (vedi IrDA ad esempio), tutto quello che funziona sul .NET CF funziona anche sul .NET FW completo. È disponibile un runtime del .NET CF 2.0 (anche della 1.0 in realtà) per processore X86 che ci consente di far girare il codice IL (o CIL) su Desktop.
Anche per SQLCE versione 3.0 è valido questo ragionamento. Si possono aprire e gestire file SDF sia da Visual Studio che da Sql Server 2005 Management Studio. Le librerie di SQLCE esistono anche per processore X86. Con SQL Compact Edition 3.0 (la 3.1 che al momento di scrittura di questo articolo – Dic 2006 – sta per uscire gira nativamente su desktop) è possibile, con poco lavoro, fare una reference da un progetto desktop alle librerie versione X86 per eseguire il codice di accesso ai dati su desktop; a tal proposito si veda il mio post di dicembre 2005; il post è su Team System, leggete l’ultima parte: http://blogs.devleap.com/rob/archive/2005/12/29/6428.aspx
Bene, abbiamo affermato che il codice si può testare sul desktop (vedremo come evitare l’utilzzo di barcode reader o librerie specifiche per l’ambiente mobile…tutto non si può avere J), vediamo come fare sul nostro semplicissimo progetto mobile composto da un exe e da una dll. Questa logica si può applicare, ed è quello che facciamo sui nostri progetti mobile reali, ad architetture più complesse seguendo sempre questi passi.
La sfiga (termine tecnico che sta a indicare generalmente una forma acuta di sfortuna) vuole che non si possa prendere il codice compilato per farlo girare sul desktop: questo non perché i compilatori siano diversi (vi garantisco che sono identici, anzi è proprio lo stesso csc.exe o vbc.exe a compilare i progetti mobile) ma perché le reference verso le librerie del framework sono diverse. Occorre quindi ricompilare il codice con le reference corrette per l’ambiente desktop.
Pronti? Iniziamo.
Per prima cosa creiamo un progetto di tipo Class Library normale (per il desktop) che per convenzione si chiamerà DevLeap.Library.Mobile.Desktop, così manteniamo i nomi allineati. Dopo la creazione togliamo tutti sorgenti all’interno e facciamo le stesse reference del progetto mobile: le reference vanno appunto fatte alle librerie del FW completo. Quindi se trovo una reference verso System.Xml nel progetto mobile farò una reference verso System.Xml del FW completo, se trovo un riferimento verso System.Windows.Form mobile farò una reference verso System.Windows.Form del FW completo.
Poi aggiungiamo i sorgenti del progetto mobile (Utility.cs nel nostro semplice caso). Nessuno di noi ha voglia di avere lo stesso codice in due posti diversi quindi non aggiungete il file normalmente, ma come LINK: questo consente di mantenere il sorgente “vero” nel progetto mobile, ma poterlo compilare, usare e modificare anche dal progetto desktop.
Dalla mascherina di Add Existing Item andate nella directory del progetto mobile (DevLeap.Library.Mobile nel nostro caso), selezionate tutti i sorgenti e poi premere il pulsantino nascosto accanto a Add: questa la maschera
Fate un Add as Link e non Add altrimenti il sorgente selezionato viene copiato nel progetto, cosa da evitare appunto per non raddoppiare i sorgenti.
Questo vale anche per i file reference.cs, per i file di configurazione (sempre che debbano essere identici), per i file che rappresentano i form (basta linkare il Formxxx.cs, non occorre prendere anche .designer.cs e .resx) e per i controlli custom (anche in questo caso prendere solo il Controxxx.cs; VS si preoccupa di linkare anche i file designer e resx).
Date una compilata al progetto Desktop per vedere se abbiamo tutte le reference corrette.
Proseguiamo poi con la creazione del progetto exe allineato all’eseguibile mobile: creare un Windows Application Project e referenziare DevLeap.Library.Mobile.Desktop (occorre prendere il progetto desktop ovviamente). Cancellare Form1.cs e Program.cs e poi aggiungere con la stessa tecnica dell’Add As Link Form1.cs, Applicazione.config e Program.cs dal progetto Applicazione. Per uniformità chiamerei il progetto desktop Applicazione.Desktop.
Questa la situazione attuale:
Come si nota i progetti desktop hanno i sorgenti dei rispettivi progetti mobile come Link (c’è un simbolino accanto al file) e le rispettive Reference. Ultima cosa: sul file Applicazione.config nel progetto desktop occorre impostare (come avevamo fatto per il progetto mobile) Build Action = Content e Copy to Output Directory = Copy Always o Copy If Newer.
Curiosi ? Avviate il progetto Applicazione.Desktop (fate tasto destro sul progetto, Debug, Start new instance) e come per magia ecco un form su XP (o Vista) con il form Mobile !!!
Questa tecnica funziona per qualunque controllo mobile supportato anche sul desktop. Ad esempio aggiungiamo un menù sul form mobile per capire cosa succede. Un consiglio: anche se si possono aprire i sorgenti linkati dal progetto .Desktop vi consiglio di editare i form sempre partendo dal progetto mobile; questo perché altrimenti la Toolbox di Visual Studio 2005 vi presenta tutti i controlli Windows.Form dell’ambiente desktop (giustamente) molti dei quali non esistono sul .NET CF. Aprendo invece i form dal progetto mobile avrete la toolbox corretta. Non preoccupatevi se ogni tanto riceverete il messaggio di errore “This document is opened by another project”: indica semplicemente che avete aperto un sorgente da un progetto e poi avete provato ad aprirlo da un altro progetto; è solo un warning che giustamente vi avverte che lavorate su un sorgente già aperto.
Per esempio: ho inserito sul form, dal progetto mobile, un po’ di controlli e un menù mobile. Questo il risultato sul desktop
Ovviamente il menù esce stile XP e non stile mobile e non ci sono le due soft key per rappresentare il menù.
Con il progetto desktop si può, solo per citare alcuni punti:
1) Lavorare con l’interfaccia
2) Testare il codice
3) Fare Edit & Continue durante il debug
4) Se l’applicazione lavora con il file system si può controllare il lavoro direttamente nella directory bin sul file system della macchina di sviluppo
5) Se l’applicazione scrive e legge file si possono editare dal file system del PC senza dover usare ActiveSync per copiare i file da e verso il device.
Ci sono una serie di cose che non si possono fare. Ad esempio
1) Testare librerie specifiche mobile come ad esempio BarCode Reader
2) Testare IrDA
3) Testare librerie Windows Mobile per Pocket Outlook
In questi casi uso la sequente tecnica: inserisco una direttiva di compilazione sul progetto Desktop oppure sfrutto le direttive #Pocket PC già inserite da Visual Studio sui progetti mobile per eliminare le righe che non funzionano sul desktop.
Ad esempio, in un applicativo che stiamo scrivendo adesso e che fa uso di librerie per lettori di codice a barre, questo è il codice per eliminare le parti non ammesse sul desktop. Siamo nel load di un form nello strato di User Interface che è divisa dall’eseguibile per essere sfruttata da più risoluzioni video.
private void MainForm_Load(object sender, EventArgs e)
{
UI.DummyCalls();
#if PocketPc
UI.CreateBrowserForm(new BrowserForm());
#endif
UI.ChangeState(ComeurOnline.Maui.UI.Common.BaseUI.FlowState.Login, txtRequest, lblError, lblRequest, treeLog, SendActionStepOutputMessageResponseStatus.Information, null, null, lblUserId, txtUserId, lblUserPassword, txtUserPassword, cmdLogin, cmdLogout);
#if PocketPc
// If we can initialize the Reader
if (BarCodeReader.InitReader())
{
// Create a new delegate to handle scan notifications
this._barCodeEventHandler = new
EventHandler(BarCodeReader_ReadNotify);
// Set the event handler of the Scanning class to our delegate
BarCodeReader.BarCodeEventHandler = this._barCodeEventHandler;
// Attach to activate and deactivate events
// this.Activated += new EventHandler(ReaderForm_Activated);
// Start a read on the reader
BarCodeReader.StartRead();
}
else
{
// Error
UI.ChangeState(ComeurOnline.Maui.UI.Common.BaseUI.FlowState.CommunicationError, txtRequest, lblError, lblRequest, treeLog, SendActionStepOutputMessageResponseStatus.Error, null, "BarCode Reader Error", lblUserId, txtUserId, lblUserPassword, txtUserPassword, cmdLogin, cmdLogout);
return;
}
#endif
}
Come si nota, Visual Studio “dipinge” di grigio il codice sotto compilazione condizionale. In questo caso, semplicemente “attiviamo” la nostra libreria per la lettura del codice a barre nel caso Pocket Pc: questa direttiva di compilazione è già inserita da VS 2005 in tutti i progetti mobile.
Anche se non è argomento di questo articolo, avere un progetto desktop per ogni progetto mobile della solution (un lavoro che costa circa 1 oretta di lavoro su un progetto reale: cronometrati 1 ore e 15 minuti sul progetto che stiamo seguendo adesso composto da 9 layer e quindi 9 progetti in cascata sul client mobile) consente poi di usare gli strumenti di Visual Studio Team System come Unit Testing, Code Coverage e Performance Session.
Update: Visual Studio 2008 consente la creazione e eseguzione di Unit Test direttamente dalla versione Professional. Gli unit test sono eseguibili anche su device e emulatore. In ogni caso mancano gli strumenti integrati di code coverage e performance session.
Per completezza riporto gli screenshot in cui dovrebbe essere facilmente leggibile anche il codice di Unit Test sul metodo GetAppConfigString, il risultato del Code Coverage del test e la maschera di solo sommario della Performance Session basata sull Unit Test.
Unit Test su GetAppConfigString (UtilityGetAppConfigStringTest):
Code Coverage su Unit Test UtilityGettAppConfigStringTest

Performance Session su Unit Test UtilityGettAppConfigStringTest
Per avere un’idea del carico sotto stress si può simulare l’accesso allo unit test da parte di uno o più utenti (simulazione valida anche per più thread) per un tempo stabilito. In VSTS questa tecnica implica l’utilizzo di un Load Test basato sullo Unit Test; sono 3 click di mouse per creare la versione base:
In un solo minuto abbiamo fatto 36.023 chiamate alla nostra funzione e direi che dai risultati (che non posso incollare in questo articolo per motivi di spazio, ma che potete analizzare voi stessi scaricando gli esempio) il tutto gira molto bene...e ci mancherebbe visto che abbiamo scritto una sola funzione J Applicate il ragionamento ad un progetto reale e tutto il tempo che risparmiate nello sviluppo su desktop potete investirlo in test più accurati.
N.B. Nel Load Test ovviamente otteniamo dei warning (39 threshold violation per l’esattezza) sul processore in quanto spesso il processore della macchina va oltre il 95%: la situazione è più che normale visto che “stressante e stressato” (Test e applicazione J) sono sulla stessa macchina.
Con pochi passaggi, avendo un progetto desktop, siamo in grado di testare il codice, di vedere cosa implica l’esecuzione del codice e possiamo mettere sotto stress il tutto per capire cosa succede nell’utilizzo reale dell’applicazione. Ovviamente i dati di performance riguardano l’ambiente desktop, ma facendo gli opportuni “scaling” si può come girerà nell’ambiente mobile.
Il codice completo, compreso il semplice Unit Test, il Load Test per testare il carico, una Performance Session per capire l’allocazione di memoria e i tempi di chiamate, e l’analisi dinamica del codice con Code Coverage, sono disponibili su www.thinkmobile.it/files: il file da scricare, previa registrazione al sito, è Articolo Infomedia Lib Mobile.zip.
Sperando di avervi semplificato la vita durante lo sviluppo delle parti core di una applicazione mobile, dandovi la possiblità con un semplice “lavoretto” di testare il 90% della soluzione e lasciando sul device o emulatore (l’emulatore comunque non avrebbe BarCode Reader ad esempio) solo la parte finale dei test vi rimando alla nostra conferenza http://devcon2007.devleap.com dove avremmo modo di analizzare in varie sessioni anche lo sviluppo mobile oltre a .NET 3.0 per Desktop e Server.
Reference:
OpenNETCF: Libreria Shared Source scaricabile da www.opennetcf.org
Articolo su Progetti desktop/mobile in VSTS : http://blogs.devleap.com/rob/archive/2005/12/29/6428.aspx
Esempi di codice completi che ripercorrono l’articolo
www.thinkmobile.it/files
Prime impressioni sulla Beta1 di Orcas.
Ho installato il tutto su una macchina VMWare dedicando 1.4 GB alla Virtual Machine memore della quantità di memoria che "ciucciava" la CTP di Marzo.
Dalle prime prove mi sembra molto più snello e non supero quasi mai il primo GB di memoria: non c'è paragone neanche sulla velocità.
Nella mia installazione non appariva la toolbox e neanche la tollbat Standard: basta ovviamente attivarle e appaiono.
Nella virtual machine VPC scaricabile (ho preso la versione con VSTS e TFS) non c'è ActiveSync, quindi ho montato la 4.5. Manca anche il Windows Mobile 6.0 SDK come segnalato nei post precedenti: a MEDC hanno confermato che non ci sarà neanche nella versione finale e va installato a parte.
Il .NET CF 3.5 adesso installa le stringhe di risorse sul device/emulatore tramite un nuovo cab denominato NETCFv35.Messages.EN.CAB che si trova sotto x:\program files\Microsoft.NET\SDK\CompactFramework\v3.5\windowsce\Diagnostics.
La creazione di un nuovo progetto presenta una nuova maschera, simile a quanto avveniva in VS 2003: non si sceglie il progetto dall'alberino delle varie solution disponibili, ma è sufficiente scegliere Smart Device: una volta scelto il tipo di progetto in una seconda maschera si sceglie la tipologia di piattaforma e poi la versione del .NET CF da utilizzare per le reference e per la distribuzione del runtime sul device. Si può optare per .NET CF 2.0 o per .NET CF 3.5.
N.B. le immagini incluse sono ridotte come dimensione: accanto ad ognuna il link verso la dimensione reale.
Questa la prima maschera:

Come si nota le piattaforme per cui è installato nativamente l'SDK sono Pocket PC 2003, Windows CE, Windows Mobile 5.0 for Pocket PC e SmartPhone.
Una volta scelta la piattaforma si sceglie la versione del .NET CF da utilizzare:

Dopo la scelta della versione del .NET CF, come sempre scegliere Device Application, Class Library e così via per creare il tipo di progetto.
Il designer delle Windows Form (visto che WPF non è disponibile sul .NET CF 3.5) si presenta molto simile al precedente: si può scegliere il form factor da assegnare ad ogni form e ruotare lo schermo del designer come nella versione 2005 di VS.

Anche la toolbox si presenta più o meno identica. Questo l'elenco dei controlli:

La configurazione dell'emulatore da Visual Studio prevede quanto conosciamo con l'attuale Device Emulator 2.0 e consente la gestione del livello della batteria: questa funzione risulta molto comoda per testare un'applicativo power-aware (come dovrebbero essere tutti): è inutile lanciare una operazione lunga di analisi sui dati, ad esempio, se resta il 2% di batteria.

Molto interessante il nuovo Device Configuration Manager, primo fra gli strumenti per facilitare la configurazione di device e emulatori: ad oggi è necessario comporre i file .xml di provisioning e "installarli" tramite rapiconfig.exe. Con questo nuovo strumento risulta molto semplice leggere la configurazione, modificarla e vedere le differenze fra la configurazione desiderata e quella attuale. Si possono importare anche i nostri file di provisioning esistenti per una facile migrazione al nuovo strumento.

Risulta molto semplice così testare l'applicazione firmata o non firmata sulle varie configurazioni dei device One-Tier, Two-Tier, Locked, Prompt e così via.
Tramite questo strumento è possibile visualizzare i certificati digitali installati su device e emulatori, installarne di nuovi e verificarne i dettagli. Una nota, i nomi dei certificate store sono cambiati: non abbiamo più il privileged e unprivileged, ma Privileged Store e Standard Store, dove il secondo rappresenta lo store per i certificati con cui le applicazioni possono girare in normal-mode su device two-tier.

Il .NET CF Remote Performance Monitor consente, nella nuova versione di fare un "attach to process" rendendo più semplice l'analisi di una porzioni di applicazione: la versione attuale deve lanciare l'applicazione e quindi ci costringe a visualizzare i dati complessivi di tutta l'applicazione fino a raggiungere il punto in cui si desidera effettuare le verifiche. La parte device-side viene installata in automatico dallo strumento senza bisogno di copiare manualmente i file nella directory \Windows: qualche problemino sulla mia Beta non consente di eseguire questa operazione in automatico e quindi ho dovuto comunque copiare i file a mano. I nuovi file device-side hanno nomi diversi dai precedenti e più precisamente sono: rtf3_5.dll e rtfhost3_5.exe. Tali file si trovano sempre nella directory dell'SDK del .NET CF 3.5 x:\program files\Microsoft.NET\SDK\CompactFramework\v3.5: ricordatevi di scegliere poi la directory corretta in base al processore del device; per gli emulatori si utilizza sempre la versione ARM.
Nella prossima immagine la directory con gli strumenti lato PC di sviluppo. Oltre al .NET CF RPM, troviamo LogViewer, già presente nella versione 2.0 SP1/SP2 e due nuovi strumenti:

Il primo NETCFCLRProfiler è una versione ridotta (e per la verità anche diversa) del Profiler del Framework completo. Consente di analizzare nel dettaglio un'applicazione in esecuzione: anche in questo caso ci si può "attaccare" ad un processo già avviato e l'installazione delle dll lato device è automatica (come sempre sono sfigato e ho dovuto copiare a mano clrpro3_5.dll sul device). Lo strumento visualizza anche sotto forma di flusso grafico le chiamate fra i vari metodi consentendo una semplice (si fa per dire quando si parla di profiling in generale) individuazione dei punti della call stack che possono dare problemi.
Il compact frameowrk 3.5 arriva con qualche dll in più per supportare LINQ e WCF (sempre in versione ridotta: entrambi sono c.a. 250KB come obiettivo nella versione finale) e i nomi dei cab sono stati leggermente rivisti.

Nella System.ServiceModel si trovano le "porzioni" del motore di WCF e il binding basicHttpBinding, mentre in Microroft.ServiceModel.Channels.Mail.* il nuovo canale (non disponibile in WCF su desktop) WindowsMobileMailBinding che consente lo scambio di messaggi SOA sfruttando il canale della posta elettronica fra device (Pocket Outlook) e Exchange 2007; ricordo che Exchange 2007 supporta il push dei messaggi verso il device.
Come sempre le sottodirectory wce400 e wce500 contengono i cab per l'installazione reale su device o emulatore in base alla versione del sistema operativo. Ricordo che Windows Mobile 6.0 si basa sempre su Windows CE 5, per precisione sulla 5.2.
System.Data.Entities contiene invece le parti applicative dell'entitiy framework, mentre il provider è contenuto sotto System.Data.SqlServerCe.Entitiy.dll che arriva con l'installazione di Sql Server Compact Edition 3.5.
SQL CE 3.5 arriva con qualche piccola novità per i device e con grandi novità su utilizzato su desktop, Tablet o UMPC.
La directory di installazione sul pc di sviluppo è x:\program files\Microsoft SQL server Compact Edition\v3.5 e si presenta così:

Come accennato, entity framework viene reso disponibile tramite System.Data.SqlServerCe.Entity, ma solo se utilizzato su PC, Tablet o UMPC: non è quindi disponibile per Windows Mobile.
L'installazione prevede diversi componenti:
1) SSCEVSTools-ENU.msi: installa i componenti design-time in Orcas sotto la classica directory x:\program files\microsoft visual studio 9\Common7\IDE. Questi componenti non devono essere distribuiti
2) SSCERuntime-ENU.msi: installa i componenti di runtime per desktop, tablet e UMPC. Sono indispensabili sia a runtime che a design time. Questo MSI installa anche i nuovi componenti per la versione desktop di SQCE 3.5: Microsoft Synchronization Services for ADO.NET (OCS) e la già citata System.Data.SqlServerCe.Entity.dll.
3) SSCEDeviceRuntime.msi: installa i componenti destinati ai device e emulatori sulla macchina. Questi componenti sono necessari per l'integrazione con VS Orcas anche sulla macchina di sviluppo.
Le novità che abbiamo bollato come minori (rispetto appunto a EntityFramework e OCS) riguardano soprattutto il query processor :
- data type di tipo timestamp
- nested query
- CROSS apply e OUTER APPLY
- CAST
- TOP (finalmente !!!!!) diventa così più semplice paginare i dati per la loro visualizzazione nei minuscoli schermi
Oltre a OCS e Entity Framework, SQL CE 3.5, se utilizzato sul desktop, fornisce la possibilità di rientrare in una transazione creata con la System.Transaction 2.0: in due parole una operazione su SQL CE 3.5 può essere inserita all'interno del TransactionScope.
I Book On Line non sono ancora disponibili.
Abbiamo accennato nei post precedenti come in Visual Studio Orcas sia possibile effettura Unit Testing su device (o emulatore): credo che questa funzionalità, affiancata dal nuovo .NET CF RPM e dal profiler, siano le novità più importanti e utili della nuova versione.
Tra l'latro in Orcas gli strumenti di Unit Testing non sono più all'interno di Team System, ma disponibili nella versione Professional, almeno a quanto ci è dato sapere oggi.
Ecco le maschere di crezione di uno unit test, praticamente identiche (tranne un settaggio) a quanto oggi disponibile in VSTS:


Nella seconda immagine, come si può notare, la configurazione degli host prevede il tipo Smart Device con relativa piattaforma e scelta fra device e emulatore.
Se si crea una performance session su uno Unit Test il codice viene fatto girare sul desktop.
Anche senza l'automatismo in VS Orcas, oggi è possibile eseguire unit test su codice mobile, creando un progetto desktop con i sorgenti mobile linkati al suo interno e oppure compilazioni condizionali nel caso in cui il codice faccia uso di dispositivi, tipo lettore di codice a barre ad esempio, non disponibili sul desktop. Fra qualche giorno posso postare un mio articolo, scritto per Infomedia, proprio su questo argomento. La demo allegata all'articolo è già disponibile sul mio sito Think Mobile all'indirizzo http://thinkmobile.it/files/folders/mmdcii/entry5896.aspx.
Il nuovo Device Emulator 3.0 si presenta così:

A parte la user interface pressochè identica, è possibile salvare la configurazione degli emulatori con Save As e riconfigurare l'emulatore. Il file di configurazione (.defcfg) è anche molto semplice da modificare a mano per cambiare al volo qualcosa senza ricorre alla user interface e soprattutto per poter scambiare la configurazione degli emulatori fra i membri di un team di sviluppo senza doverla ricreare su ogni PC di sviluppo.

Dopo la nostra DevCon 2007 proseguiamo con SQL CE 3.5 e soprattutto con LINQ e WCF su mobile.
Alla prossima.
Articolo pubblicato su Computer Programming nel 2006.
Questo articolo, diviso in due parti unite in questo testo, introduce le problematiche di programmazione asincrona (e di conseguenza multithread) in ASP.NET, ripercorrendo passo per passo le modalità disponibili in ASP.NET 1.x per arrivare alle novità della versione 2.0. Il flusso, anche se con meno dettagli per questioni di spazio, ripercorre gli argomenti dei corsi www.devleap.it su ASP.NET 1.x e 2.0 Core. Per ogni demo, l'articolo contiene il codice relativo che potete copiare direttamente; all'indirizzo www.thinkmobile.it:9000/Async ho pubblicato tutti gli esempi funzionanti live in modo che possiate vedere e verificare il tutto su un server Windows 2003 già configurato: a sinistra trovate un menù che porta alle varie pagine riportate in questo articolo.
Quando si parla di programmazione asincrona facciamo riferimento all'esecuzione di operazioni su thread differenti, parallelizzando diverse richieste a risorse remote in modo da annullare il più possibile i tempi di attesa su ciascuna di esse: iniziare un articolo con un frase criptica solitamente incuriosisce il lettore :-)
Proviamo a spiegarci con alcuni esempi partendo da un client Windows (o mobile) tradizionale per poi spostarci su ASP.NET. Il mio obiettivo è chiarire e semplificare, il più possibile, concetti che esistono da sempre nello sviluppo di applicazioni e calarli piano piano nella realtà web che, come sempre, differisce dallo sviluppo di applicazioni tradizionali. Forniamo esempi molto semplici e facilmente riproducibili. In questo primo articolo ci dilungheremo un po' anche su IIS, Thread Pool e Asynchronous Thread Queue (ATQ) per inquadrare bene la problematica e dare al lettore elementi concreti di valutazione, piuttosto che segnalare solamente le carattestistiche di ASP.NET senza inquadrarle nello scenario complessivo.
Supponiamo di avere un client Windows che deve effettuare due chiamate a web service remoti per ottenere alcune informazioni. Utilizzeremo per praticità un solo web service il cui codice .NET 2.0 è il seguente.
---- SalesmanManagerWS.asmx ---
<%@ WebService Language="C#" CodeBehind="~/App_Code/SalesmanManagerWS.cs" Class="SalesmanManagerWS" %>
--- SalesmanManagerWS.asmx.cs ---
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Threading;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class SalesmanManagerWS : System.Web.Services.WebService
{
public SalesmanManagerWS () {
//Uncomment the following line if using designed components
//InitializeComponent();
}
[WebMethod]
public bool SalvaAgente(string idAgente, string descrizione)
{
Thread.Sleep(2000);
try
{
SalesmanManager sm = new SalesmanManager();
sm.AggiornaAgente(idAgente, descrizione);
return true;
}
catch
{
return false;
}
}
}
Come si nota, il web service chiama una classe del Business Layer per salvare i dati di un agente nel DB. Per effettuare prove in locale simuliamo un lavoro di due secondi con una Thread.Sleep(2000). In pratica, visto che lavorando in locale si azzerano i tempi di connessione, imponiamo due secondi come tempo di esecuzione del metodo remoto. Nel ricreare l'esempio potete togliere la chiamata al mio Business Layer e adattare il Thread.Sleep ad un valore vicino al vostro scenario.
Costruendo il nostro client .NET, per semplicità, assegneremo al click su un bottone, la chiamata sincrona al metodo SalvaAgente:
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
bool ris = ws.SalvaAgente("robertob", "Roberto Brunetti");
Sicuramente la risposta non arriverà prima di due secondi visto che il web service internamente spende due secondi a vuoto. Durante questo tempo il thread che gestisce l'interfaccia utente (la Windows Form) resta bloccato in attesa di una risposta: non è possibile interagire con altri elementi del form e tantomeno con il form stesso (ad esempio minimizzare la finestra oppure spostarla).
Eseguire una chiama asincrona risolve il problema del blocco del thread principale consentendo all'utente di interagire con il form durante la chiamata. Dalla versione 1.0, il framework .NET mette a disposizione, sulle chiamate ai web service, un pattern che utilizza BeginMetodo e EndMetodo per iniziare una chiamata asincrona senza doversi preoccupare di creare e gestire thread da codice. In pratica la chiamata a BeginSalvaAgente crea, dietro le quinte, un secondo thread per l'esecuzione della richiesta remota. A tale metodo può essere passato un evento che verrà richiamato in automatico al termina della richiesta remota (Callback). Da questo evento è possibile ottenere il risultato della chiamata tramite il metodo EndSalvaAgente. In pratica nel metodo button_click si esegue la chiamata asincrona:
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "Roberto Brunetti", AgenteSalvato, ws);
definendo poi un event handler per recuperare il risultato al termine della chiamata:
private void AgenteSalvato(IAsyncResult ar)
{
SalesmanManagerWS.SalesmanManagerWS ws = (SalesmanManagerWS.SalesmanManagerWS)ar.AsyncState;
bool ris = ws.EndSalvaAgente(ar);
}
La chiamata impiega sempre un tempo non inferiore ai due secondi, ma il thread principale non viene bloccato durante questa attesa. Non mi dilungo su questi metodi visto che sono presenti nel .NET Framework (e .NET Compact Framework) sino dal febbraio 2002, mese di uscita ufficiale del framework. Abbiamo quindi risolto, senza impazzire con il codice, il primo problema.
Complichiamo, si fa per dire, l'esempio richiamando due web service (nel nostro caso dimostrativo chiameremo sempre lo stesso metodo). Eseguendo due chiamate sincrone è ovvio che il tempo di risposta totale non possa essere inferiore a 4 secondi. Il thread resta bloccato per almeno due secondi per effettuare la prima chiamata e poi per almeno altri due secondi per aspettare la seconda risposta. In questo caso una chiamata (anzi due) asincrona al web service, non solo fa sì che il thread non resti bloccato, ma consente di parallelizzare le due richieste ottenendo un tempo di risposta notevolmente inferiore. Test sulla mia macchina evidenziano che la chiamata sincrona a un web service (a regime) impiega circa 2,016 secondi, la chiamata sincrona a due web service impiegano circa 4,016 secondi. Effettuando chiamate asicrone si ottiene un tempo di riposta di 2,016 per una sola richiesta e di 2,031 secondi per entrambe le richieste.
Facciamo il punto per poi spostarci nel mondo ASP.NET: nel caso di una sola richiesta, con una chiamata asincrona non possiamo migliorare il tempo di risposta in quanto il nostro web service impiega comunque almeno 2 secondi per essere eseguito; con una chiamata asincrona miglioriamo solamente l'interattività dell'utente con il form in quanto la chiamata viene spostata su un thread diverso da quello che gestisce la user interface (e non è poco). Nel caso di due chiamate (o più di due ovviamente) con le chiamate asincrone riusciamo a parallelizzare le richieste da più thread concorrenti (senza dover creare e gestire a mano i thread) abbassando il tempo di risposta totale (sempre che la linea lo permetta). Ad esempio se dobbiamo effettuare 3 richieste che normalmente impiegano rispettivamente 3 secondi, 4 secondi e 6 secondi, nel caso di chiamate sincrone il miglior risultato che possiamo ottenere è superiore ai 13 secondi, con chiamate asincrone possiamo arrivare fino a 6 secondi. Con chiamate sincrone avremo un solo thread in esecuzione, nel caso di chiamate asincrone avremo fino a 3 thread in contemporanea.
E' bene sottolineare da subito che l'utlizzo di più thread, di per se, peggiora i tempi di esecuzione in quanto carichiamo il sistema con la gestione di più thread che implica operazioni di thread switch; abbiamo però dimostrato come l'utlizzo di queste tecniche sia particolarmente efficace durante l'utilizzo di risorse remote in quanto si possono parallelizzare le richieste e diminuire i tempi morti di attesa nelle risposte.
Il pattern BeginMetodo/EndMetodo è disponibile nel .NET Framework 1.x per le chiamate ai web service (la classe proxy autogenerata da Visual Studio o con WSDL.exe contiene questa coppia di metodi per ogni metodo esposto dal web service), per i metodi esposti da molte classi del namespace System.IO e del namespace System.Net e nella versione 2.0 anche per accedere a SQL Server tramite i metodi della SqlClient. In pratica ovunque si debba lavorare con risorse remote come il file system, le risorse in rete e perchè no anche SQL Server.
Il .NET Framework 2.0 espone anche un nuovo e semplificato pattern per le chiamate asincrone, alternativo a Begin/End, che non richiede la definizione di IAsyncResult/AyncCallback. In pratica la chiamata si riassume nel codice seguente:
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
// Nuova riga
ws.SalvaAgenteCompleted += this.OnSalvaAgenteCompleted;
ws.SalvaAgenteAsync("Robertob", "Roberto Brunetti");
// Non occre definire il delegate (fa lui)
//IAsyncResult ar = ws.BeginSalvaAgente("robertob", "Roberto Brunetti", AgenteSalvato, ws);
e l'evento di callback diventa:
private void OnSalvaAgenteCompleted(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
// N.B. Typed Result (SalvaAgenteCompletedEventArgs)
// Via ar.AsyncState
// SalesmanManagerWS.SalesmanManagerWS ws = (SalesmanManagerWS.SalesmanManagerWS)ar.AsyncState;
// bool ris = ws.EndSalvaAgente(ar);
bool ris = e.Result;
}
Da notare come il risultato sia tipizzato, non occorrano cast rispetto alla classe proxy e quanto il codice sia più semplice da leggere rispetto all'esempio iniziale (le righe commentate rappresentano il codice Begin/End); sfortunatamente questo nuovo pattern non è disponibile per tutte le classi che implementano metodi asincroni: ha quindi senso capire e utilizzare anche la sintassi Begin/End in quanto per molte classi (fra cui i vari oggetti SqlClient) occorre ancora lavorare con questo pattern. Creare thread secondari consente anche di eseguire in asincrono operazioni di inizializzazione e start-up di form e applicazioni senza "rubare" tempo prezioso al thread principale durante le attese di risorse remote.
Inquadrate problematiche e soluzioni per un client Windows Form passiamo a ASP.NET che presenta una serie di problematiche diverse: non siamo più lato-client con una interfaccia "mono-user", ma operiamo server-side con diverse richieste in contemporanea. Ad ogni richiesta che arriva via Http viene assegnato un thread che recupera la risorsa da disco e la invia come risposta: questo compito viene assolto da Internet Information Service su macchine Windows. Nel caso in cui la richiesta sia per una risorsa gestita da ASP.NET, IIS tramite la ISAPI Application (o Extension che dir si voglia) ASPNET_ISAPI.DLL, ridirige la richiesta verso il processo ASPNET_WP in IIS 5 o W3WP in IIS 6. Quando il motore di ASP.NET ha completato il suo lavoro, IIS invia la risposta al client che ha richiesto la risorsa.
IIS Thread Pool e Asynchronous Thread Queue
Immaginate cosa succederebbe se per ogni richiesta venisse creato un thread diverso: se sul sito arrivano 1.000 richieste concorrenti avremmo 1.000 thread concorrenti nel processo di Internet Information Service, "tutti il lotta fra di loro sullo stesso sistema" per recuperare le risorse e inviarle al client: come abbiamo accennato nella prima parte dell'articolo avere tanti thread significare caricare il processore per gestire il time slice di ogni thread, l'assegnazione delle risorse della macchina al thread in esecuzione e il thread switch per passare da un thread all'altro. Senza dilungarci, in pratica, il costo di gestione dei thread influirebbe in modo pesante sul tempo di esecuzione delle singole richieste. Dall'altra parte non sarebbe possibile processare le richieste con un solo thread in quanto il centisimo utente aspetterebbe il completamento delle 99 richieste precedenti prima di vedere arrivare una risposta. "Scalando" (applicando su scala server J) il ragionamento che abbiamo appena fatto per un client sarebbe molto intelligente sfruttare i tempi morti di ogni richiesta per iniziare a processare una seconda richiesta: ad esempio quando viene recuperata una pagina html o una immagine da disco, durante il tempo di attesa necessario per spostare le testine sul disco e rintracciare la risorsa potremmo dare un po' di tempo CPU ad un'altra richiesta: così facendo, come abbiamo esasperato nel primo esempio del web service che impiega due secondi (e sperando che il nostro disco non ci metta due secondi per recuperare una risorsa :-)) una seconda richiesta può essere portata avanti mentre la prima attende una risorsa. E' importante però che il thread che aspetta l'IO del disco venga recuperato e assegnato ad altre operazioni per evitare appunto la creazione (e il costo di gestione) di troppi thread sul sistema. Inoltre, anche la creazione di un thread ha un costo, così come la sua terminazione: sarebbe bene non creare e uccidere un thread per ogni richiesta altrimenti si richia che il costo di creazione/distruzione appesantisca il sistema.
Da questa idea, sin dalle prime versioni di IIS, così come per molti application server, si è pensato alla creazione di un thread pool e a una coda di richieste: mi spiego meglio. Un thread pool è un insieme finito di thread che viene creato in un processo. Partiamo da un caso con pochissime richieste per inquadrare il tutto: nel momento in cui arriva la prima richiesta viene creato un thread T1, nel momento in cui arriva la seconda richiesta viene creato un secondo thread T2; nel momento in cui arriva la terza richiesta è probabile che la prima abbia già terminato l suo lavoro: il thread T1, al termine del lavoro sulla prima richiesta, anzichè essere "ucciso" viene inserito nel pool di thread e reso quindi disponibile alla richiesta numero 3. Arriva la richiesta numero 4: se ci sono thread disponibili nel thread pool la richiesta viene assegnata ad un thread esistente, altrimenti viene creato un nuovo thread T3. Tutto questo fino al limite dei thread configurabile per il thread pool.
Pensate che in IIS 4.0 il numero dei thread del thread pool era impostato per default a 10 !!! In pratica IIS 4 non può processare che 10 richieste contemporanee: prima di spaventarsi ripercorriamo un attimo il ragionamento: occorre sfruttare al massimo i tempi morti durante l'esecuzione di una richiesta/risposta http, come ad esempio l'accesso al disco o a una risorsa in rete (non stiamo ancora parlando di applicazioni ASP.NET) per evitare che il numero di thread cresca a dismisura e causi problemi di performance. Visto che le richieste in IIS prevedono nella maggioranza dei casi l'accesso a risorse su disco (pagine html, immagini e così via) e questo accesso sia solitamente velocissimo (anche per la cache dei file più acceduti) avere un numero di thread così basso consente di mantenere "leggero" il sistema pur fornendo un ottimo throughput. Il rapporto, sempre parlando in media, è circa 100:10 (notare che non è 10:1); questo rapporto indica che per 100 richieste che arrivano via Http sono necessari una decina di thread per soddisfare comunque tutte e cento le richieste per risorse normali; per normali intendo pagine di contenuti, immagini, css; se il server deve fare strema video o scaricare immagini da 100 MB cadauna ovviamente il ragionamento 100:10 dovrebbe essere rivisto. Il punto chiave di questi esempi non sono i numeri di per se, ma la relatività della teoria: voglio dire che il ragionamento non cambia se su un sito con risorse pesanti il rapporto debba essere 100:20.
Supponiamo di avere i 10 thread del thread pool impegnati da richieste esistenti: cosa succede alla undicesima richiesta ? Con la teoria esposta fino a adesso dovremmo rispondere: al client arriverà una risposta "Server Too Busy". Ovviamente non è così altrimenti potremmo affermare che IIS non è un gran che come web server J Le richieste, prima di essere processate, vengono inserite in una coda e prelevate dai vari thread disponibili nel thread pool per essere eseguite. Solo al riempimento di tale coda il server risponderà "Server Too Busy". Si utilizza quindi una tecnica (che ha senso utilizzare nelle nostre applicazioni per scenari simili tramite servizi di accodamento custom o tramite MSMQ) molto intelligente: si accodano le richieste, fino ad un certo limite, e si processano in parallelo solo un numero finito di richieste (con i thread del thread pool) per cercare di ottimizzare il più possibile il bilanciamento fra richieste processabili dal sistema e affaticamento del sistema stesso. Ovviamente questi limiti (thread pool e numero di richieste accodabili, così come altri parametri relativi) possono essere adattati al proprio scenario. Non abbiamo lo spazio per una trattazione esaustiva di questi parametri anche perchè dobbiamo arrivare al cuore dell'articolo ovvero ASP.NET e le pagine asincrone: ricordatevi però sempre che aumentare in modo indiscriminato questi parametri, a prima vista molto bassi, non è una buona idea. Ad esempio superare il valore 60-80 per i thread del thread pool non è quasi mai una buona idea. Vi rimando al Resource Kit di IIS 6.0 per una trattazione approfondita di questi parametri e le tecniche di stress test per verificare il valore ottimale.
ASP.NET Thread Pool
Se la richiesta che ha raggiunto IIS è destinata ad una risorsa ASP.NET, questa viene inviata al motore di ASP.NET da ASPNET_ISAPI.dll e, a questo punto senza sorpresa, vi comunico che anche ASP.NET ha una coda per gestire le richieste prima di essere assegnate ai thread di un thread pool interno al processo di ASP.NET (o i processi di ASP.NET nel caso di IIS 6).
Anche ASP.NET ragiona quindi con la teoria che abbiamo appena esposto: appena arriva una richiesta, questa viene accodata; viene poi recuperato un thread del thread pool per processare la richiesta. Anche in questo caso il riempimento della coda fa sì che venga rifiutata la richiesta. Per default ASP.NET ha un thread pool di 25 thread (alcuni devono però essere usati dal motore per operazioni di routine come controllo della cache, ricompilazione, gestione delle session...anche se sapete da sempre che non andrebbero usate !!!).
Non dovrebbe sembrare strano, a questo punto, che il valore di default del thread pool di IIS sia più basso rispetto a quello di ASP.NET: la richiesta arriva a IIS e viene accodata, appena si rende disponibile un thread si inizia a processare la richiesta che viene passata a ASP.NET. Il thread in IIS può essere rimesso immediatamente nel pool e reso disponibile per altre richieste (HTML, immagini e così via) mentre ASP.NET processa la richiesta, sicuramente più complessa, rispetto a prelevare un semplice file da disco. Se arriva una seconda richiesta per ASP.NET, magari è lo stesso thread che ha inviato la prima richiesta ad inviare anche la seconda richiesta. Quando le risposte da parte di ASP.NET sono pronte viene recuperato un thread libero nel thread pool di IIS per mandare la risposta al client. Ottimo no ?
ASP.NET Sync/Async
A questo punto diventa chiara la seguente affermazione: occorre occupare per meno tempo possibile i thread del thread pool durante l'esecuzione di una richiesta in modo da renderli disponibili alle richieste entranti. Come fare ? Se una elaborazione occupa tempo CPU (ad esempio per fare dei calcoli) difficilmente possiamo fare a meno di occupare un thread, ma se la richiesta deve accedere a risorse (disco, rete, web service, database) possiamo liberare il prima possibile il thread (effettuando una chiamata asincrona) in modo da farlo lavorare su un'altra richiesta. Predisponiamo il terreno anche in ASP.NET per effttuare i vari test che proseguiremo in un prossimo articolo dove andremo in dettaglio su ASP.NET 1.x e ASP.NET 2.0 per capire le differenze e le nuove facilitazioni.
Per testare il tutto ho preparato la seguente Master Page che visualizza (menu per le varie demo a parte) un trace delle operazioni e il sorgente della pagina richiesta. In pratica la master page espone un metodo, richiamabile dalle varie pagine delle demo, AddTraceMessage che visualizza il tempo dall'inizio della richiesta, il numero di thread occupati e liberi del thread pool e la stringa di trace. Questa la mia master page:
--- Master.Master ---
<%@ Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Threading" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">
void Page_Init()
{
AddTraceMessage("Page_Init");
Page.PreRenderComplete += new EventHandler(this.PagePreRenderComplete);
}
void PagePreRenderComplete(Object source, EventArgs e)
{
AddTraceMessage("PreRenderComplete");
using (TextReader r = new StreamReader(Request.PhysicalPath))
{
SourceLabel.Text = HttpUtility.HtmlEncode(r.ReadToEnd());
}
}
StringBuilder _trace = new StringBuilder();
DateTime _pageStartTime = DateTime.Now;
public void AddTraceMessage(string message)
{
double t = (DateTime.Now - _pageStartTime).TotalSeconds;
Int32 i;
Int32 totalWorkerThreads;
// Numero massimo di thread del thread pool
ThreadPool.GetMaxThreads(out totalWorkerThreads, out i);
Int32 availableWorkerThreads;
ThreadPool.GetAvailableThreads(out availableWorkerThreads, out i);
// Thread di tutto il processo
Int32 totalThreadCounts = System.Diagnostics.Process.GetCurrentProcess().Threads.Count;
lock (_trace)
{
_trace.AppendFormat("[{0:000}] {1:00.000} - {5} - [{3}/{4}/{6}] -- {2}\r\n",
Thread.CurrentThread.GetHashCode(), t, "Thread " + AppDomain.GetCurrentThreadId() + " " + message,
totalWorkerThreads - availableWorkerThreads, totalWorkerThreads,
Thread.CurrentThread.IsThreadPoolThread ? "TP" : "!TP",
totalThreadCounts);
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Sample Page</title>
</head>
<body>
<form id="form" runat="server">
<table>
<tr>
<td valign="top">
<p>
<asp:SiteMapPath ID="SiteMapPathNode" runat="server" Font-Names="Verdana" PathSeparator=" : "
EnableViewState="False">
<PathSeparatorStyle Font-Bold="True" ForeColor="#990000" />
<CurrentNodeStyle ForeColor="#333333" />
<NodeStyle Font-Bold="True" ForeColor="#FF8000" />
<RootNodeStyle Font-Bold="True" ForeColor="#990000" />
</asp:SiteMapPath>
<asp:SiteMapDataSource ID="SiteMapDS" runat="server" />
</p>
<p>
<asp:TreeView ID="SiteTreeView" runat="server" DataSourceID="SiteMapDS" ImageSet="Simple"
Font-Names="Verdana" Font-Size="0.9em" EnableViewState="False" NodeWrap="True">
<SelectedNodeStyle ForeColor="#990000" Font-Bold="True" />
<NodeStyle VerticalPadding="2px" ForeColor="Black" />
<HoverNodeStyle Font-Underline="True" ForeColor="#5555DD" />
<ParentNodeStyle />
</asp:TreeView>
</p>
</td>
<td valign="top">
<asp:Label ID="Label1" runat="server" Text="Page Trace:" Font-Bold="True" Font-Names="Verdana"
Font-Size="0.8em" ForeColor="Maroon" />
<pre><%= _trace.ToString() %></pre>
<hr />
<asp:Label ID="Label2" runat="server" Text="Page Source:" Font-Bold="True" Font-Names="Verdana"
Font-Size="0.8em" ForeColor="Maroon" />
<pre><asp:Label ID="SourceLabel" runat="server" /></pre>
</td>
</tr>
</table>
</form>
</body>
</html>
Come si può notare, viene intercettato il Page_Init per inserire un messaggio iniziale nel trace e abbonarsi all'evento PreRenderComplete nel cui event handler viene letto il sorgente della pagina e valorizzata la label che lo visualizza. Il PreRenderComplete è un punto importante nell'esecuzione di pagine asincrone e lo analizzeremo in dettaglio nel prossimo articolo.
A questo punto possiamo costruire la prima pagina (referenziando la master page) che esegue una chiamata sincrona al nostro famoso (e lento) web service che impiega 2 secondi per essere eseguito.
--- Sync Page Chiama 1 Web Service ---
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("SalvaAgente 1.0 Sync");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
bool ris = ws.SalvaAgente("robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 1.0 Finito");
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Il codice non è diverso da quanto facevamo nel click del bottone della Windows Form all'inizio dell'articolo. Viene istanziato il web service e invocato il metodo SalvaAgente. Il resto del codice ci consente di inserire messaggi nel trace, che come abbiamo visto nella master page, visualizza informazioni importanti sui thread. Ecco il risultato della esecuzione (depurato del sorgente e del menu per questioni di spazio):
[007] 00,000 - TP - [1/100/25] -- Thread 2264 Page_Init
[007] 00,000 - TP - [1/100/25] -- Thread 2264 SalvaAgente 1.0 Sync
[007] 02,016 - TP - [1/100/27] -- Thread 2264 SalvaAgente 1.0 Finito
[007] 02,016 - TP - [1/100/27] -- Thread 2264 Risultato True
[007] 02,016 - TP - [1/100/27] -- Thread 2264 PreRenderComplete
Il trace ci mostra, in prima battuta, che la pagina ha richiesto 2,016 secondi per essere eseguita: ricordatevi che sotto i due secondi non possono scendere in quanto il metodo del nostro web service impiega almeno due secondi per essere eseguito. Per semplicità ho pubblicato tutti gli esempi all’indirizzo http://thinkmobile.it:9000/async: visto che la master page visualizza il sorgente non avete bisogno di riscriverli…copiateli direttamente.
Il trace mostra 5 messaggi di trace, che grazie al metodo esposto dalla master page visualizza, per ogni chiamata, l'hash code del thread, il tempo di esecuzione dall'inizio della richiesta, TP nel caso in cui si stia utilizzando un thread del thread pool, l'id del thread e il messaggio passato come parametro al metodo. Page_Init e PreRenderComplete vengono aggiunti dal codice della master page. I valori fra parentesi quadre [X/XXX/XX] rappresentano rispettivamente il numero dei thread impegnati nel thread pool, il numero massimo di thread ospitabili e il numero di thread liberi a disposizione.
Come si può notare per eseguire questa pagina che effettua la chiamata sincrona al metodo del web service viene impiegato un solo thread (il 2264) e il tempo fra la chiamata (SalvaAgente 1.0 Sync) e la risposta (SalvaAgente 1.0 Finito) è di poco superiore ai due secondi: questo indica, come abbiamo avuto modo di dire più volte, che il thread aspetta l'intero ciclo della chiamata al metodo. In pratica con una chiamata sincrona stiamo impegnando un solo thread del thread pool per tutta la durata della richiesta; se il thread pool dispone di 20 thread significa che potremmo effettuare fino a 20 richieste in contemporanea e che la ventunesima richiesta aspetterebbe in coda circa due secondi per essere iniziata a processare. Se la coda può ospitare al massimo 100 richieste significa che prima di arrivare a processare la 81 richiesta occorre aspettare 4 trance da 2 secondi (8 secondi almeno). Inoltre la coda si libera di 20 posti dopo almeno due secondi: in pratica a regime possiamo accettare massimo 20 richieste ogni 2 secondi (supponendo sempre che le richieste arrivino nello stesso istante).
Proviamo a chiamare due web service in modo sincrono:
--- Sync Page Chiama 2 Web Service ---
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("SalvaAgente 1.0 Sync");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
bool ris = ws.SalvaAgente("robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 1.0 Finito");
Master.AddTraceMessage("Risultato 1 " + ris.ToString());
SalesmanManagerWS.SalesmanManagerWS ws2 = new SalesmanManagerWS.SalesmanManagerWS();
ris = ws2.SalvaAgente("paolopi", "PaoloPialorsi");
Master.AddTraceMessage("SalvaAgente 1.0 Finito");
Master.AddTraceMessage("Risultato 2 " + ris.ToString());
}
</script>
Ed ecco il risultato:
[001] 00,000 - TP - [1/100/22] -- Thread 3292 Page_Init
[001] 00,000 - TP - [1/100/22] -- Thread 3292 SalvaAgente 1.0 Sync
[001] 02,547 - TP - [1/100/24] -- Thread 3292 SalvaAgente 1.0 Finito
[001] 02,563 - TP - [1/100/24] -- Thread 3292 Risultato 1 True
[001] 04,578 - TP - [1/100/24] -- Thread 3292 SalvaAgente 1.0 Finito
[001] 04,578 - TP - [1/100/24] -- Thread 3292 Risultato 2 True
[001] 04,578 - TP - [1/100/24] -- Thread 3292 PreRenderComplete
Anche in questo caso stiamo impegnando un solo thread (il 3292) per l'intera esecuzione della pagina che giustamente dura non meno di 4 secondi (4,578 in questo caso) perchè le due richieste ai due web service sono sequenziali e sincrone e non consentono di parallelizzare le due richieste. Ovviamente abbiamo peggiorato l'esecuzione in quanto adesso ogni richiesta impiega non meno di 4 secondi quindi possiamo accettare un massimo di 20 richieste ogni 4,5 secondi circa.
Stress (ma non troppo) Test
Mettendo il sito sotto stress rispetto ad un tempo di risposta di 2,2 secondi circa per la chiamata singola, otteniamo un tempo medio di risposta di 16,3 secondi e varie risposte Server Too Busy per il problema accennato: il dramma però sta nel fatto che il processore della macchina è al 24% di utilizzo; in pratica i tempi di risposta diventano inaccettabili anche con pochi client su una macchina che non è carica: il processore passa la maggior parte del tempo ad aspettare le risposte remote e non viene sfruttato come potrebbe. Mettere un processore più potente per cercare di velocizzare l'applicazione sarebbe l'errore più grande che potremmo fare, in quanto il collo di bottiglia non è il processore, ma il tempo di attesa sincrono della nostra banalissima applicazione. Se portate questo esempio nel mondo reale dove una pagina di un portale fa una decina di chiamate remote a web service otteniamo una situazione catastrofica anche con 20 utenti collegati e un hardware da decine di migliaia di euro.
Dobbiamo migliorare i tempi di attesa e quindi sfruttare meglio la CPU. La soluzione però non è effettuare chiamate asincrone di per se. Vediamo il perchè con un esempio che purtroppo viene raccomandato da molti come soluzione al problema appena citato. Prima di implementarlo aspettate di vedere i risultati e leggere il prossimo articolo dove cercherò di spiegare il motivo per cui modificare solamente le chiamate sincrone in asincrone sia più distruttivo. Il motivo che scopriremo è che ASP.NET 1.x esegue le pagine in modo sincrono e questo comportamento è il default anche per ASP.NET 2.0. Andiamo per gradi.
Proviamo quindi a iniziare con il pattern BeginMetodo a eseguire la chiamata in asincrono. Ecco il codice:
--- Sync Page Chiama 1 Web Service BeginXXX ---
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("SalvaAgente 1.0 Begin");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", AgenteSalvato, ws);
Master.AddTraceMessage("SalvaAgente 1.0 Fine Chiamata");
// Occorre aspettare la fine
ar.AsyncWaitHandle.WaitOne();
}
private void AgenteSalvato(IAsyncResult ar)
{
Master.AddTraceMessage("SalvaAgente 1.0 End");
SalesmanManagerWS.SalesmanManagerWS ws = (SalesmanManagerWS.SalesmanManagerWS)ar.AsyncState;
bool ris = ws.EndSalvaAgente(ar);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Prima di vedere i risultati e fare le dovute considerazioni spendiamo un attimo sulla chiamata a ar.AsyncWaitHandle.WaitOne() che non era presente nel primo esempio asincrono su una Windows Form: questa riga serve per evitare che l'esecuzione della pagina finisca (ed è molto probabile per non dire certo in questo semplice esempio) prima che la chiamata asincrona termini. In pratica si imposta un WaitHandle per segnalare l'attesa sulla chiusura della chiamata asincrona: senza questa istruzione la chiamata asincrona verrebbe comunque completata ma non potremmo visualizzare nessun risultato al client in quanto la risposta sarebbe già stata chiusa da ASP.NET.
Chiarito il concetto vediamo i risultati:
[007] 00,000 - TP - [1/100/27] -- Thread 2264 Page_Init
[007] 00,000 - TP - [1/100/27] -- Thread 2264 SalvaAgente 1.0 Begin
[007] 00,000 - TP - [1/100/27] -- Thread 2264 SalvaAgente 1.0 Finito
[005] 02,016 - TP - [1/100/33] -- Thread 2692 SalvaAgente 1.0 End
[005] 02,016 - TP - [1/100/33] -- Thread 2692 Risultato True
[007] 02,016 - TP - [1/100/33] -- Thread 2264 PreRenderComplete
Non ci discostiamo molto come tempi di esecuzione dall'esempio sincrono ed è normale visto che la chiamata comunque impiega due secondi ma come si nota dal trace entra in gioco un secondo thread (il 2692 rispetto al 2264) per la visualizzazione del risultato. Proviamo con due chiamate asincrone:
--- Sync Page Chiama 2 Web Service BeginXXX ---
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("SalvaAgente 1.0 Begin");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", AgenteSalvato, ws);
Master.AddTraceMessage("SalvaAgente 1.0 Fine Chiamata");
Master.AddTraceMessage("SalvaAgente 1.0 Begin");
SalesmanManagerWS.SalesmanManagerWS ws2 = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar2 = ws2.BeginSalvaAgente("robertob", "RobertoBrunetti", AgenteSalvato, ws);
Master.AddTraceMessage("SalvaAgente 1.0 Fine Chiamata");
// Occorre in qualche modo aspettare gli Endxxx prima di abbandonare la pagina
// Tip: Se possibile fare prima la Wait sul comando più veloce (più veloce in Begin e in End)
// Così intanto il secondo va avanti
ar.AsyncWaitHandle.WaitOne();
ar2.AsyncWaitHandle.WaitOne();
}
private void AgenteSalvato(IAsyncResult ar)
{
Master.AddTraceMessage("SalvaAgente 1.0 End");
SalesmanManagerWS.SalesmanManagerWS ws = (SalesmanManagerWS.SalesmanManagerWS)ar.AsyncState;
bool ris = ws.EndSalvaAgente(ar);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Ecco il risultato:
[007] 00,000 - TP - [1/100/27] -- Thread 2264 Page_Init
[007] 00,016 - TP - [1/100/27] -- Thread 2264 SalvaAgente 1.0 Begin
[007] 00,016 - TP - [1/100/29] -- Thread 2264 SalvaAgente 1.0 Finito
[007] 00,016 - TP - [1/100/29] -- Thread 2264 SalvaAgente 1.0 Begin
[007] 00,016 - TP - [1/100/29] -- Thread 2264 SalvaAgente 1.0 Finito
[017] 02,203 - TP - [2/100/30] -- Thread 2792 SalvaAgente 1.0 End
[017] 02,203 - TP - [2/100/30] -- Thread 2792 Risultato True
[017] 02,703 - TP - [1/100/30] -- Thread 2792 SalvaAgente 1.0 End
[017] 02,703 - TP - [1/100/30] -- Thread 2792 Risultato True
[007] 02,703 - TP - [1/100/30] -- Thread 2264 PreRenderComplete
Ecco in gioco un altro thread (notare che i numeri, anche se casualmente, sono gli stessi proprio perchè i thread vengono recuperati dal thread pool) per eseguire la seconda richiesta asincrona e il conseguente risultato.
Sembra tutto perfetto: siamo risciti a parallelizzare le richieste abbassando il tempo totale di risposta....ma le insidie sono ben nascoste dietro questo esempio.
In questo esempio, infatti, stiamo provando la pagina con un solo browser e analizzando i migliorati tempi di risposta...ma cosa succede se effettuiamo più richieste in contemporanea ?
Due dati per capire il problema con un carico di 20 client:
La pagina con la chiamata a un web service che in sincrono impiegava 16,3 secondi passa con una chiamata asincrona a 21.6 secondi !!! e il processore passa dal 24% di carico al 29%; ottimo lavoro J abbiamo peggiorato e parecchio i tempi di risposta e nel contempo caricato di più la macchina.
Purtroppo questo esempio viene citato da molti come la soluzione al problema di parallelizzare le richieste "Se una pagina chiama un web service occorre farlo in asincrono in modo da sfruttare i tempi morti": questo è quello che viene sbandierato da tanti.
Abbiamo invece dimostrato che questa soluzione peggiora i tempi di esecuzione totali della applicazione ! Il problema di fondo è che la chiamata a BeginMetodo impegna un altro thread del thread pool senza liberare il thread corrente, quindi a fronte di una richiesta per la pagina che effettua una chiamata al web service avremo due thread del thread pool impegnati e nella richiesta per la pagina che effettua due chiamate 3 thread del thread pool impegnati per tutta la durata della richiesta: questo il motivo per cui i tempi di esecuzione totali con richieste concorrenti va peggiorare le performance invece di migliorarle; pensate a 6 richieste concorrenti che fanno lavorare (6*3) 18 thread…esaurendo quasi i 20 thread a disposizione.
La soluzione la vedremo nel prossimo articolo (più avanti nel testo) e consta nel rendere asincrona anche l'esecuzione della pagina da parte del motore di ASP.NET. Questa soluzione si può ottenere anche in ASP.NET 1.x implementando l'interfaccia IHttpAsyncHandler, ma perdendo molti degli automatismi di ASP.NET nell'uso dei controlli e nella gestione dello stato. In ASP.NET 2.0 ci hanno facilitato la vita tramite un attributo della pagina che consente in automatico di generare dietro le quinte l'implementazione di IHttpAsyncHandler.
Nel prossimo articolo vedremo le tecniche all'opera sia per ASP.NET 1.x che per ASP.NET 2.0 analizzando anche i metodi più automatizzati come AddOnPreRenderCompleteAysnc e Page Task.
Vi ricordo che all'indirizzo http://thinkmobile.it:9000/async ho pubblicato tutte le demo (anche quelle del prossimo articolo) così potete provare da remoto le varie demo senza riscrivere il codice: il menù di sinistra vi porta ai vari esempi analizzati.
Vi chiedo un favore: visto che il sito thinkmobile.it è una comunità per tutti gli sviluppatori mobile (fra cui gli sviluppatori web mobile in ASP.NET 1.x e 2.0) vi pregherei di non effettuare stress test direttamente sull'indirizzo pubblico ma semplicemente visualizzare live gli esempi e copiare il codice per effettuare stress test privati sui vostri server.
Nel precedente articolo abbiamo introdotto le problematiche di programmazione asincrona (e di conseguenza multithread) in ASP.NET, ripercorrendo passo per passo, con esempi semplici, i motivi per cui, effettuare chiamate asincrone, di per sè, peggiora le performance globali della nostra applicazione. Riporto il punto finale dell’articolo per proseguire con l’analisi della soluzione al problema. Ci siamo lasciati con una pagina sincrona (il default) che chiama due web service con il pattern asincrono Begin/EndMetodo. Questi erano i dati di performance con un carico di soli 20 client.
La pagina con la chiamata ad un web service che in sincrono impiegava 16,3 secondi passa con una chiamata asincrona a 21.6 secondi !!! e il processore passa dal 24% di carico al 29%; ottimo lavoro J abbiamo peggiorato e parecchio i tempi di risposta e nel contempo caricato di più la macchina.
Purtroppo questo esempio viene citato da molti come la soluzione al problema di parallelizzare le richieste "Se una pagina chiama un web service deve farlo in asincrono in modo da sfruttare i tempi morti": questo è quello che viene sbandierato da tanti.
Abbiamo invece dimostrato che questa soluzione peggiora i tempi di esecuzione totali della applicazione ! Il problema di fondo è che la chiamata a BeginMetodo impegna un altro thread del thread pool senza liberare il thread corrente, quindi, a fronte di una richiesta per la pagina che effettua una chiamata al web service, avremo 2 thread del thread pool impegnati e nella richiesta per la pagina che effettua due chiamate 3 thread del thread pool impegnati per tutta la durata della richiesta: questo è il motivo per cui i tempi di esecuzione totali con richieste concorrenti peggiorano le performance invece di migliorarle; pensate a 6 richieste concorrenti che fanno lavorare (6*3) 18 thread…esaurendo quasi i 20 thread a disposizione.
La soluzione al problema consiste nel rendere asincrona anche l'esecuzione della pagina da parte del motore di ASP.NET. Questa soluzione si può ottenere anche in ASP.NET 1.x implementando l'interfaccia IHttpAsyncHandler, ma perdendo molti degli automatismi di ASP.NET nell'uso dei controlli e nella gestione dello stato. In ASP.NET 2.0 ci hanno facilitato la vita tramite un attributo della pagina che consente in automatico di generare dietro le quinte l'implementazione di IHttpAsyncHandler.
Vi ricordo che all'indirizzo http://thinkmobile.it:9000/async ho pubblicato tutte le demo (anche quelle del precedente articolo) così potete provare da remoto le varie demo senza riscrivere il codice: il menù di sinistra vi porta ai vari esempi analizzati.
Partiamo da ASP.NET 1.x. Per rendere asincrona l’esecuzione di una pagina occorre implementare IAsyncHttpHandler. Ecco il codice:
---- 05AsyncHandler.ashx ---
<%@ WebHandler Language="C#" Class="SyncHandler10" %>
using System;
using System.Threading;
using System.Web;
public class SyncHandler10 : IHttpAsyncHandler
{
SalesmanManagerWS.SalesmanManagerWS ws;
HttpContext ctx;
IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object state)
{
ctx = context;
ws = new SalesmanManagerWS.SalesmanManagerWS();
ctx.Response.Output.Write("<p>Thread " + AppDomain.GetCurrentThreadId());
return ws.BeginSalvaAgente("robertob", "Roberto Brunetti", cb, ws);
}
void IHttpAsyncHandler.EndProcessRequest(IAsyncResult asyncResult)
{
ctx.Response.Output.Write("<p>Thread " + AppDomain.GetCurrentThreadId());
ctx.Response.Output.Write(((SalesmanManagerWS.SalesmanManagerWS)asyncResult.AsyncState).EndSalvaAgente(asyncResult).ToString());
}
void IHttpHandler.ProcessRequest(HttpContext context) {
throw new InvalidOperationException();
}
bool IHttpHandler.IsReusable {
get { return false; }
}
}
Questa pagina, in realtà questo codice, sfrutta l’estensione .ashx che ci evita, in questo semplice esempio, di dover creare un Http Handler esterno e registrarlo nel web.config (tecnica consigliata nella maggior parte dei casi in produzione).
Come si nota dall’esempio, la nostra classe, implementa IHttpAsyncHandler e consente, tramite i due metodi BeginProcessRequest e EndProcessRequest di effettuare chiamate asincrone ai web service. In pratica, il motore di ASP.NET rende asincrona l’esecuzione dell’handler, liberando il thread corrente (rimettendolo nel Thread Pool) dopo la chiamata a BeginProcessRequest. Questo significa, appunto, non occupare un thread del thread pool durante la richiesta asincrona al nostro, ormai famoso, web service.
Anche se i tempi di esecuzione sotto stress migliorano notevolmente rispetto a quanto abbiamo visto nell’ultima parte dell’articolo precedente, ci perdiamo il bello di ASP.NET, cioè la possibilità di lavorare con i controlli e gli eventi a livello di pagina: occorre fare tutto da codice.
Un workaround che uso spesso è quello di cercare di mantenere asincrona l’esecuzione dell’handler cercando di evitare la codifica della risposta a manina.
Una prima soluzione è quella di implementare la chiamata asincrona dentro il Global.asax, inserire il risultato in una variabile del contesto http (HttpContext.Items[“Risultato”] ad esempio) per poi recuperare questo elemento da una normale pagina sincrona. Il problema di questo approccio è dover riempire il Global.asax di If e Switch per invocare i web service corretti solo per le richieste a pagine che utilizzano web service.
Un secondo approccio più elegante, di cui vediamo il codice, è invece creare un handler .ashx per ogni pagina che necessita di effettuare chiamate asincrone.
Ad esempio, la pagina 06AsyncContext.aspx avrà un corrispondente 06AsyncHanderContext.ashx. Nell’handler asincrono chiameremo (sempre in asincrono, ovviamente) il web service. Alla ricezione della risposta (EndMethod) mettiamo il risultato in una variabile del contesto http e eseguiamo una Redirect verso la pagina reale, che, a questo punto, può usare Web Control e eventi nel modo tradizionale e recuperare il risultato dalla variabile del contesto.
Ecco l’esempio:
<%@ WebHandler Language="C#" Class="SyncHandler10" %>
using System;
using System.Threading;
using System.Web;
public class SyncHandler10 : IHttpAsyncHandler
{
SalesmanManagerWS.SalesmanManagerWS ws;
HttpContext ctx;
IAsyncResult IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, object state)
{
ctx = context;
ws = new SalesmanManagerWS.SalesmanManagerWS();
return ws.BeginSalvaAgente("robertob", "Roberto Brunetti", cb, ws);
}
void IHttpAsyncHandler.EndProcessRequest(IAsyncResult asyncResult)
{
ctx.Items["RisultatoWS"] = ((SalesmanManagerWS.SalesmanManagerWS)asyncResult.AsyncState).EndSalvaAgente(asyncResult).ToString();
ctx.Server.Execute("~/06AsyncContext10.aspx");
}
void IHttpHandler.ProcessRequest(HttpContext context) {
throw new InvalidOperationException();
}
bool IHttpHandler.IsReusable {
get { return true; }
}
}
L’handler esegue la chiamata a BeginSalvaAgente e dopo aver ricevuto il risultato in EndProcessRequest, lo inserisce in HttpContext con il nome di RisultatoWS. L’esecuzione viene poi rediretta sulla pagina con una Server.Execute. La pagina, a questo punto, può utilizzare Web Control e eventi e recuperare il risultato dal Context. Ecco il codice della pagina:
<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("Salva Agente Async da Global.asax");
Master.AddTraceMessage("Risultato " + Context.Items["RisultatoWS"].ToString());
}
</script>
Ottimo ! Abbiamo reso asincrona l’esecuzione della pagina senza incasinarci più di tanto. Ricordatevi che i link dalle altre pagine non devono puntare a pagina.aspx ma verso pagina.ashx.
In ASP.NET 2.0 ci hanno facilitato notevolmente il compito rendendo trasparente l’implementazione di IHttpAsyncHandler. È sufficiente decorare una pagina con l’attributo Async=”true” per avviare questo processo. Un ulteriore “miglioramento” della versione 2.0 di .NET è un nuovo pattern per le chiamate asincrone. Vediamo entrambi questi aspetti nel codice seguente.
--- 09ChiamaUnWebService.aspx ---
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
// Nuova riga
ws.SalvaAgenteCompleted += thisOnSalvaAgenteCompleted; // Non occre definire il delegate (fa lui)
// Non servono più
//IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", AgenteSalvato, ws);
//ar.AsyncWaitHandle.WaitOne();
ws.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
}
//Definizione più conforme alla normale gestione eventi
// private void AgenteSalvato(IAsyncResult ar)
private void OnSalvaAgenteCompleted(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
// N.B. Typed Result (SalvaAgenteCompletedEventArgs)
// N.B. Il thread che esegue questo codice non è detto che sia lo stesso del Page_Load
Master.AddTraceMessage("SalvaAgente 2.0 Completed");
// Via sto casino
// SalesmanManagerWS.SalesmanManagerWS ws = (SalesmanManagerWS.SalesmanManagerWS)ar.AsyncState;
// bool ris = ws.EndSalvaAgente(ar);
bool ris = e.Result;
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Nell’esempio è stato usato il nuovo attributo Async=”true”. Nel codice trovate commentate le righe utilizzate in precedenza con il pattern Begin/End Metodo. Come si può notare il codice si è semplificato notevolmente. La chiamata al Web Service si effettua tramite il MetodoAsync (nel nostro caso SalvaAgenteAsync; esiste un EventHandler OnSalvaAgenteCompleted che semplifica il recupero dei risultati. In questo nuovo pattern, infatti, il risultato è tipizzato (SalvaAgenteCompletedEventArgs viene definito in automatico alla creazione del proxy) e sicuramente il codice è più semplice da leggere rispetto all’utilizzo di IAsyncResult e AsyncState.
Sul sito trovate anche un esempio di chiamata a due web service, sia in parallelo che in sequenza (nel caso in cui la chiamata al secondo abbia bisogno di un parametro che deriva dall’esecuzione del primo) che ometto per brevità.
Tutte le chiamate asincrone devono essere effettuate prima dell’Async Point: questo nuovo termine indica il punto in cui il motore di ASP.NET sospende l’esecuzione del thread corrente e reinserisce il thread nel thread pool. Quando terminano le operazioni asincrone viene recuperato un thread del thread pool per inviare la risposta al client. In pratica l’esecuzione della pagina viene sospesa sull’async point se ci sono richieste asincrone in corso e ripresa al termine della loro esecuzione. Se sull’async point le operazioni asincrone sono già terminate (nel nostro caso il web service ha già restituito una risposta) il thread non viene sospeso ma prosegue la sua esecuzione. Questa è una tecnica veramente molto efficiente.
Dov’è l’Async Point ? Questo “punto” di esecuzione della pipeline di una pagina si trova in corrispondenza dell’evento PreRenderComplete: questo significa che le chiamate asincrone possono essere effettuate in tutti gli eventi della pagina che vengono scatenati prima di PreRenderComplete. In pratica è consentito effettuare una chiamata asincrona negli eventi Page_PreInit, Page_Init, Page_InitComplete, Page_PreLoad, Page_LoadComplete, Page_PreRender. Non è possibile effettuare chiamate asincrone (o meglio è possibile ma non possono beneficiare di una reale chiamata asincrona) dagli eventi Page_PreRenderComplete, Page_SaveState, Page_SaveStateComplete, Page_Render, Page_Unload.
Nell’esempio precedente abbiamo usato il nuovo pattern MetodoAsync al posto del Begin/EndMetodo. Ci sono però alcune limitazioni nel nuovo pattern: non tutti i componenti .NET implementano questo pattern: ad esempio non è possibile utilizzarlo con chiamate asincrone verso i componenti di ADO.NET 2.0 in quanto non implementano questo pattern. Di conseguenza, conviene prendere la mano con il pattern Begin/EndMetodo, anche se più complicato da leggere/scrivere in quanto disponibile per tutti i componenti .NET che consentono chiamate asincrone. Il metodo Begin/End, però ha una limitazione (anche nella versione 1.x), cioè non fa confluire nella parte End della chiamata (la callback) Impersonation, Culture e HttpContext.Current che vanno reimpostati. Il nuovo pattern MetodoAysnc, al contrario, passa Impersonation, Culture e HttpContext al metodo di callback.
Riassumendo MetodoAysnc è più semplice da usare, ha il risultato tipizzato e lavora sul metodo di callback con lo stesso criterio di Impersonation, Culture e sullo stesso HttpContext (HttpContext.Current), ma non è implementato in molti componenti del .NET Framework. Il metodo Begin/End, anche se è utilizzabile con tutti i componenti asincroni, complica un po’ il codice e non esegue il metodo di callback con gli stessi criteri di Impersonation e Culture e nello stesso contesto http. A voi la scelta.
Sul sito trovate le demo 09, 10, 11 e 12 sul pattern MetodoAsync/Completed e le demo 13 e 14 con il pattern Begin/End.
Riporto per completezza gli esempi in sequenza:
--- 10 Chiamata a due Web Service in sequenza ---
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load() {
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
ws.SalvaAgenteCompleted += thisOnSalvaAgenteCompleted;
ws.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
}
private void OnSalvaAgenteCompleted(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
Master.AddTraceMessage("SalvaAgente 2.0 Completed");
bool ris = e.Result;
Master.AddTraceMessage("Risultato " + ris.ToString());
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
ws.SalvaAgenteCompleted += new SalesmanManagerWS.SalvaAgenteCompletedEventHandler(thisOnSalvaAgenteCompleted2);
ws.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
}
private void OnSalvaAgenteCompleted2(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
Master.AddTraceMessage("SalvaAgente 2.0 Completed");
bool ris = e.Result;
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
--- 11 Chiamata a due Web Service in parallelo---
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load()
{
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
ws.SalvaAgenteCompleted += new SalesmanManagerWS.SalvaAgenteCompletedEventHandler(thisOnSalvaAgenteCompleted);
ws.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws2 = new SalesmanManagerWS.SalesmanManagerWS();
ws2.SalvaAgenteCompleted += new SalesmanManagerWS.SalvaAgenteCompletedEventHandler(this.OnSalvaAgenteCompleted);
ws2.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
}
// N.B. I Completed vengono sincronizzati: non c'è bisogno di lock
private void OnSalvaAgenteCompleted(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
Master.AddTraceMessage("SalvaAgente 2.0 Completed");
bool ris = e.Result;
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
--- 12 Chiamata a due web service in parallelo utilizzando Anonymous Method 2.0 ---
<%@ Page Language="C#" Async="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
void Page_Load()
{
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws = new SalesmanManagerWS.SalesmanManagerWS();
ws.SalvaAgenteCompleted += delegate(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
Master.AddTraceMessage("SalvaAgente 2.0 Completed");
bool ris = e.Result;
Master.AddTraceMessage("Risultato " + ris.ToString());
};
ws.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
Master.AddTraceMessage("SalvaAgente 2.0 Async");
SalesmanManagerWS.SalesmanManagerWS ws2 = new SalesmanManagerWS.SalesmanManagerWS();
ws2.SalvaAgenteCompleted += delegate(object sender, SalesmanManagerWS.SalvaAgenteCompletedEventArgs e)
{
Master.AddTraceMessage("SalvaAgente 2.0 Completed");
bool ris = e.Result;
Master.AddTraceMessage("Risultato " + ris.ToString());
};
ws2.SalvaAgenteAsync("Robertob", "RobertoBrunetti");
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
}
</script>
Priva di vedere il codice che segue il classico pattern Begin/End occorre segnalare che la pagina necessita di una nuova chiamata in cui si registrano i metodi che verranno eseguiti in asincrono. Il modo più semplice per informare ASP.NET 2.0 che deve invocare i nostri metodi asincroni è quello di chiamare il metodo AddOnPreRenderCompleteAsync passando come parametri il nostro metodo Begin e il nostro metodo End. Come si può vedere nell’esempio seguente il codice è molto semplice: nel Page_Load (o comunque prima del Page_PreRenderComplete) si chiama il metodo AddOnPreRenderCompleteAsync indicando i nostri classici BeginSalvaAgente e EndSalvaAgente.
--- 13 Chiamata Begin/End a un web service ---
<%@ Page Language="C#" Async="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
void Page_Load() {
AddOnPreRenderCompleteAsync(new BeginEventHandler(this.BeginSalvaAgente),
new EndEventHandler(this.EndSalvaAgente));
}
IAsyncResult BeginSalvaAgente(Object sender, EventArgs e, AsyncCallback cb, object state) {
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente(IAsyncResult asyncResult) {
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Ecco una chiamata a due web service in sequenza registrando entrambi i Begin/EndMetodo dal metodo AddOnPreRenderCompleteAsync.
--- 14 Chiamata a Due Web Service –
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
SalesmanManagerWS.SalesmanManagerWS ws2;
void Page_Load() {
AddOnPreRenderCompleteAsync(new BeginEventHandler(thisBeginSalvaAgente),
new EndEventHandler(thisEndSalvaAgente));
AddOnPreRenderCompleteAsync(new BeginEventHandler(thisBeginSalvaAgente2),
new EndEventHandler(thisEndSalvaAgente2));
}
IAsyncResult BeginSalvaAgente(Object sender, EventArgs e, AsyncCallback cb, object state)
{
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente(IAsyncResult asyncResult) {
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
IAsyncResult BeginSalvaAgente2(Object sender, EventArgs e, AsyncCallback cb, object state)
{
CompositeAsyncResult compositeAsyncResult = new CompositeAsyncResult(2, cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws2 = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws2.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente2(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws2.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
In questo ultimo esempio le due chiamate sono sequenziali in quanto abbiamo IAsyncResult diversi. Nel caso in cui vogliate effettuare le due richieste in parallelo occorre qualche riga di codice in più per implementare un CompositeAsyncResult. Ecco il codice per crearlo e utilizzarlo nell’esempio 15. Questo codice, per semplicità, potete inserirlo nella directory App_Code:
using System;
using System.Threading;
public class CompositeAsyncResult : IAsyncResult {
int _numberOfOperationsRemaining;
volatile bool _allCompleted;
volatile bool _completedSynchronously;
AsyncCallback _callback;
AsyncCallback _originalCallback;
object _originalState;
public CompositeAsyncResult(int numberOfOperations, AsyncCallback cb, object state) {
_numberOfOperationsRemaining = numberOfOperations;
_allCompleted = (numberOfOperations == 0);
_callback = new AsyncCallback(this.OnOperationCompleted);
_originalCallback = cb;
_originalState = state;
if (_allCompleted) {
_completedSynchronously = true;
_originalCallback(this);
}
}
void OnOperationCompleted(IAsyncResult asyncResult) {
if (Interlocked.Decrement(ref _numberOfOperationsRemaining) == 0) {
_allCompleted = true;
_completedSynchronously = asyncResult.CompletedSynchronously;
_originalCallback(this);
}
}
public AsyncCallback Callback {
get { return _callback; }
}
bool IAsyncResult.IsCompleted {
get { return _allCompleted; }
}
bool IAsyncResult.CompletedSynchronously {
get { return _allCompleted && _completedSynchronously; }
}
object IAsyncResult.AsyncState {
get { return _originalState; }
}
WaitHandle IAsyncResult.AsyncWaitHandle {
get { return null; }
}
}
Ed ecco l’esempio 15 che fa uso del CompositeAsyncResult:
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
SalesmanManagerWS.SalesmanManagerWS ws2;
IAsyncResult ar;
IAsyncResult ar2;
void Page_Load() {
AddOnPreRenderCompleteAsync(new BeginEventHandler(this.BeginWork),
new EndEventHandler(this.EndWork));
}
IAsyncResult BeginWork(Object sender, EventArgs e, AsyncCallback cb, object state)
{
CompositeAsyncResult compositeAsyncResult = new CompositeAsyncResult(2, cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", compositeAsyncResult.Callback, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws2 = new SalesmanManagerWS.SalesmanManagerWS();
ar2 = ws2.BeginSalvaAgente("robertob", "RobertoBrunetti", compositeAsyncResult.Callback, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return compositeAsyncResult;
}
void EndWork(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(ar);
Master.AddTraceMessage("Risultato " + ris.ToString());
Master.AddTraceMessage("SalvaAgente 2.0 End");
ris = ws.EndSalvaAgente(ar2);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Come si può notare si crea un CompositeAsyncResult che viene utilizzato per entrambe le chiamate, che, grazie a questo meccanismo, possono viaggiare in parallelo.
In tutti i casi mostrati, pensate, dando uno sguardo alla parte iniziale di questo articolo, a quanto sia più semplice il codice della versione 2.0 di ASP.NET rispetto all’implementazione di un Http Handler asincrono, necessario nella versione 1.x per rendere più scalabili le soluzioni.
L’ultimo punto che andiamo a esaminare con questo articolo riguarda le Page Task. L’idea nasce dal fatto di semplificare pagine complesse che accedono a servizi esterni: si pensi ad un portale che visualizza previsioni del tempo, quotazioni di borsa, traffico autostradale, e così via. Se una di queste informazioni non è disponibile al momento della richiesta http, probabilmente ha più senso inviare comunque la pagina al cliente, senza ad esempio le previsioni del tempo, piuttosto che indicare all’utente che la pagina non è disponibile. Non ci sono problemi a cablare nel codice queste logiche, anche se richiedono uno sforzo non indifferente per cercare di sincronizzare le cose ed evitare lunghe attese. Con le Page Task diventa tutto più semplice seguendo questo “prontuario”:
1) Una pagina deve impiegare al massimo un tempo X per essere inviata al cliente
2) Tutte le richieste asincrone effettuate verso servizi esterni devono completarsi entro il tempo X
3) Le richieste andate a buon fine serviranno per renderizzare parti della pagina
4) Le richieste non andate a buon fine verranno contrassegnate sulla pagina con una indicazione di “mancato servizio”
Ecco un esempio: si registrano le varie attività asincrone da effettuare e si imposta il tempo massimo sulla pagina. Ogni attività (task) ha un BeginTask e un EndTask che consentono rispettivamente di iniziare la chiamata asincrona e di recuperare il risultato (come sempre) e un TimeOut che consente di indicare all’utente la mancata esecuzione di un servizio.
Ecco il codice autodescrittivo:
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
void Page_Load()
{
PageAsyncTask task = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente), // Evento Inizio
new EndEventHandler(thisEndSalvaAgente), // Evento Fine
new EndEventHandler(thisTimeoutSalvaAgente), // Evento Timeout
null, // State
true); // ExecuteInParallel
RegisterAsyncTask(task);
}
IAsyncResult BeginSalvaAgente(Object sender, EventArgs e, AsyncCallback cb, object state) {
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente(IAsyncResult asyncResult) {
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
void TimeoutSalvaAgente(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 Timeout");
}
</script>
Come prima cosa nel Page_Load andiamo a definire e registrare ogni singola task. Definiamo poi gli eventi per iniziare la chiamata (BeginSalvaAgente), per ricevere il risultato (EndSalvaAgente) e per gestire l’errore durante la chiamata (TimeoutSalvaAgente). Ovviamente potete scegliere i nomi che più vi piacciono
Durante la creazione di una PageAsyncTask si passano i tre eventi già descritti, l’eventuale State (nel nostro caso null) e true per effettuare richieste in parallelo, oppure false per effettuare richieste sequenziali. Ecco un esempio di due attività sequenziali:
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
SalesmanManagerWS.SalesmanManagerWS ws2;
void Page_Load() {
PageAsyncTask task = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente), // Evento Inizio
new EndEventHandler(this.EndSalvaAgente), // Evento Fine
new EndEventHandler(this.TimeoutSalvaAgente), // Evento Timeout
null, // State
false); // ExecuteInParallel
PageAsyncTask task2 = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente2), // Evento Inizio
new EndEventHandler(this.EndSalvaAgente2), // Evento Fine
new EndEventHandler(this.TimeoutSalvaAgente), // Evento Timeout
null, // State
false); // ExecuteInParallel
RegisterAsyncTask(task);
RegisterAsyncTask(task2);
}
IAsyncResult BeginSalvaAgente(Object sender, EventArgs e, AsyncCallback cb, object state)
{
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente(IAsyncResult asyncResult) {
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
void TimeoutSalvaAgente(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 Timeout");
}
IAsyncResult BeginSalvaAgente2(Object sender, EventArgs e, AsyncCallback cb, object state)
{
CompositeAsyncResult compositeAsyncResult = new CompositeAsyncResult(2, cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws2 = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws2.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente2(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws2.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Ecco un esempio di due attività in parallelo:
<%@ Page Language="C#" Async ="true" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
SalesmanManagerWS.SalesmanManagerWS ws2;
void Page_Load() {
PageAsyncTask task = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente), // Evento Inizio
new EndEventHandler(this.EndSalvaAgente), // Evento Fine
new EndEventHandler(this.TimeoutSalvaAgente), // Evento Timeout
null, // State
true); // ExecuteInParallel
PageAsyncTask task2 = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente2), // Evento Inizio
new EndEventHandler(this.EndSalvaAgente2), // Evento Fine
new EndEventHandler(this.TimeoutSalvaAgente), // Evento Timeout
null, // State
true); // ExecuteInParallel
RegisterAsyncTask(task);
RegisterAsyncTask(task2);
}
IAsyncResult BeginSalvaAgente(Object sender, EventArgs e, AsyncCallback cb, object state)
{
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente(IAsyncResult asyncResult) {
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
void TimeoutSalvaAgente(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 Timeout");
}
IAsyncResult BeginSalvaAgente2(Object sender, EventArgs e, AsyncCallback cb, object state)
{
CompositeAsyncResult compositeAsyncResult = new CompositeAsyncResult(2, cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws2 = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws2.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente2(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws2.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Come si nota l’unica differenza rispetto all’esempio precedente è nel valore true passato come ultimo parametro al costruttore della PageAsyncTask.
Un ultimo esempio di task con timeout:
<%@ Page Language="C#" Async ="true" AsyncTimeout="2" MasterPageFile="~/MasterPage.master" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<script runat="server">
SalesmanManagerWS.SalesmanManagerWS ws;
SalesmanManagerWS.SalesmanManagerWS ws2;
void Page_Load() {
PageAsyncTask task = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente), // Evento Inizio
new EndEventHandler(this.EndSalvaAgente), // Evento Fine
new EndEventHandler(this.TimeoutSalvaAgente), // Evento Timeout
null, // State
true); // ExecuteInParallel
PageAsyncTask task2 = new PageAsyncTask(
new BeginEventHandler(this.BeginSalvaAgente2), // Evento Inizio
new EndEventHandler(this.EndSalvaAgente2), // Evento Fine
new EndEventHandler(this.TimeoutSalvaAgente), // Evento Timeout
null, // State
true); // ExecuteInParallel
RegisterAsyncTask(task);
RegisterAsyncTask(task2);
}
IAsyncResult BeginSalvaAgente(Object sender, EventArgs e, AsyncCallback cb, object state)
{
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
return ar;
}
void EndSalvaAgente(IAsyncResult asyncResult) {
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
void TimeoutSalvaAgente(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 Timeout");
}
IAsyncResult BeginSalvaAgente2(Object sender, EventArgs e, AsyncCallback cb, object state)
{
CompositeAsyncResult compositeAsyncResult = new CompositeAsyncResult(2, cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Begin");
ws2 = new SalesmanManagerWS.SalesmanManagerWS();
IAsyncResult ar = ws2.BeginSalvaAgente("robertob", "RobertoBrunetti", cb, state);
Master.AddTraceMessage("SalvaAgente 2.0 Fine Chiamata");
System.Threading.Thread.Sleep(2000);
return ar;
}
void EndSalvaAgente2(IAsyncResult asyncResult)
{
Master.AddTraceMessage("SalvaAgente 2.0 End");
bool ris = ws2.EndSalvaAgente(asyncResult);
Master.AddTraceMessage("Risultato " + ris.ToString());
}
</script>
Vi ricordo, contrariamente a quanto indicato nell’help di ASP.NET 2.0, che il timeout si imposta come attributo della pagina e non può essere discriminato in base alla richiesta: in pratica il timeout indica, in secondi, “il tempo massimo per aspettare tutte le risposte” di tutte le task asincrone.
Una caratteristica interessante delle Page Task è la possibilità di lavorare sia su pagine asincrone (marcate con Async=”true”) sia su pagine sincrone (il default) senza modificare il codice. Ovviamente, se facciamo lavorare le Page Task su una pagina sincrona, l’esecuzione della pagina stessa sarà sincrona impiegando solo thread del thread pool.
Queste tecniche, come abbiamo avuto modo di dire più volte nel corso delle due parti in cui è stato diviso questo articolo, non si applicano solo alle richieste verso web service, ma anche agli altri componenti del framework che implementano uno dei due pattern (Begin-End o Async-Completed).
Roberto Brunetti
Roberto@DevLeap.it
Articolo da me pubblicato su Computer Programming n. 159. Riporto as was :-)
Il 6 Aprile Microsoft ha annunciato l’uscita di un nuovo prodotto denominato SQL Everywhere: come dice il termine stesso il prodotto gira su qualunque piattaforma Microsoft. In realtà SQL Everywhere non è nient’altro che l’attuale SQL 2005 Mobile senza la limitazione che ad oggi ne impone l’utilizzo solo su piattaforma Windows CE e Windows XP Tablet Edition (ricordo che i nuovi Ultra-Mobile PC sono comunque basati su Windows XP Tablet Edition).
In pratica Microsoft ha deciso di togliere la limitazione (di cui abbiamo parlato più volte sui forum di ThinkMobile.it) sulla licenza che ad oggi consente l’utilizzo in produzione di SQL 2005 Mobile su Windows CE e Windows XP Tablet Edition.
Non è quindi un nuovo prodotto, ma bensì un nuovo nome per un prodotto esistente.
Come abbiamo avuto modo di introdurre nell’articolo precedente sui meccanismi di replica di SQL 2005 Mobile, SQL Everywhere (che per gli amici resterà sempre SQLCE, il primo nome di tale prodotto uscito nel 1998) è la versione “ridotta” (striminzita sarebbe il termine più corretto) di SQL Server nato per la piattaforma Windows CE. L’accesso ai dati dal .NET Framework o .NET Compact Framework è pressoché identico nella forma all’utilizzo di Sql Server: il provider nativo System.Data.SqlServerCe implementa, infatti, le stesse interfacce del fratello maggiore System.Data.SqlClient.
La libreria System.Data.SqlServerCe offre però due meccanismi di accesso al database non presenti nel fratello maggiore: SqlCeResultSet e Base Table Cursor. Obiettivo dell’articolo è capire il ruolo di questi due signori e soprattutto le caratteristiche legate alle performance e all’occupazione di memoria.
Il primo meccanismo è spesso pubblicizzato in conferenze ed eventi come una delle novità della versione 2005 di SQLCE, mentre il secondo è pressoché ignorato pur essendo il metodo di accesso più veloce ad una tabella sia per recuperare l’intero set di record, che per effettuare filtri sui dati in base ad un indice, sia per recuperare il singolo record.
Una prefazione
Da sempre nel mondo .NET esiste la diatriba fra DataReader e DataSet e purtroppo in molti forum, riviste, ma soprattutto negli esempi, si predilige l’utilizzo di quest’ultimo. Potremmo scusare chi abusa del DataSet affermando che un DataSet (o una DataTable) può essere “bindata” direttamente ai controlli dell’interfaccia utente Windows (diverso è il ragionamento su applicazioni ASP.NET), mentre un DataReader non lo è e quindi occorre valorizzare i valori dei controlli da codice, così come dobbiamo preoccuparci di rileggere i valori dei controlli per passargli agli statement di update.
Un esempio di codice è meglio di mille parole: supponiamo di voler estrarre tutti i record della tabella tabArticoli e legare IdArticolo e Descrizione ad una ListBox. Questo il codice che utilizza un DataSet da una funzione del layer User Interface. Il codice calcola i tempi di esecuzione con Environment.TickCount, un meccanismo non accuratissimo che restituisce il numero di millisecondi da quando il sistema è stato avviato.
public static void AllSelectStarDS(ComboBox lstArticoli, Label lblTime)
{
int start = Environment.TickCount;
lstArticoli.DataSource = null;
lstArticoli.Items.Clear();
SqlCeConnection conn = new SqlCeConnection();
conn.ConnectionString = “…”;
SqlCeCommand cmd = new SqlCeCommand("SELECT * FROM tabArticoli",
conn);
SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
DataSet ds = new DataSet();
try
{
try
{
da.Fill(ds, "Articoli");
lstArticoli.DisplayMember = "ArticoloDex";
lstArticoli.ValueMember = "IdArticolo";
lstArticoli.DataSource = ds.Tables["Articoli"].DefaultView;
}
finally
{
conn.Dispose();
cmd.Dispose();
da.Dispose();
}
}
catch (SqlCeException ex)
{
MessageBox.Show("Errore Lettore");
}
int end = Environment.TickCount;
lblTime.Text = (end - start).ToString();
}
Questo codice, nell’esempio che stiamo facendo e che è disponibile come zip sotto http:// www.thinkmobile.it/files scegliendo SQL 2005 Mobile Performance, è stato brutalmente scritto nella user interface. Sappiamo da sempre che sarebbe bene separarlo dall’interfaccia utente e gestirlo dal layer di accesso ai dati. Per non complicare troppo il listato e arrivare al punto che stiamo trattando mi sono concesso questo “lusso”. Nella demo indicata trovate anche il codice posizionato correttamente in uno strato di accesso ai dati.
Una nota: Enrironment.TickCount restituisce il tempo in millisecondi da quando il device è stato acceso quindi non è adatto per calcolare prestazioni di codice che impiega meno di un millisecondo per essere eseguito e soprattutto occorre riavviare il sistema ogni tanto per evitare che tale numero, quando cresce, diventi negativo e di conseguenza sballi le statistiche.
Provando a capire i tempi di accesso medi (calcolati sulla media di una serie di dieci test), i risultato che otteniamo realmente sul mio device (i-mate JasJar) sono i seguenti:
|
100 record |
1,061 secondi |
|
1000 record |
42 secondi |
|
10000 record |
151 secondi |
È alquanto improbabile, credo concordiate con me, prelevare 10.000 record dal db per inserirli nella combo box a meno di non voler far impazzire i nostri utenti. Pensate però anche al caso in cui si debbano leggere i 10.000 record per effettuare un calcolo.
Proseguiamo l’esempio di estrazione dei dati da una sola tabella cercando di migliorarlo passo passo: il primo errore che abbiamo commesso è utilizzare SELECT * in quanto il database deve prima cercare i campi appartenenti alla tabella per poi estrarre il contenuto dei record e ancor più importante è assolutamente inutile portarsi nel dataset tutti i campi della strtuttura quando in realtà lavoriamo solo con IdArticolo e Descrizione. Pensate che scegliendo solo questi due campi arriviamo al tempo di accesso di:
|
100 record |
1,012 secondi |
|
1000 record |
41 secondi |
|
10000 record |
149 secondi |
Sembra ininfluente dal punto di vista delle performance, ma in realtà abbiamo allocato in memoria (rispetto alla tabella di esempio che contiene altri 4 campi) il 37% in meno di spazio. Ogni processo su Windows CE ha a disposizione solo 32 MB di RAM, a prescindere dalla RAM fisica del device. Risparmiare memoria è la prima regola da seguire.
Il secondo, o terzo ormai, errore che abbiamo fatto riguarda sempre la memoria: perché allocare la struttura di un DataSet quando noi in realtà lavoriamo su una sola tabella che viene legata alla ListBox oppure su cui dobbiamo effettuare dei calcoli ? Usiamo quindi la struttura più snella della DataTable che comunque consente anche il binding. Ecco il listato:
public static void AllSelectFieldsDT(ComboBox lstArticoli, Label lblTime)
{
int start = Environment.TickCount;
lstArticoli.DataSource = null;
lstArticoli.Items.Clear();
SqlCeConnection conn = new SqlCeConnection();
conn.ConnectionString = “…”;
SqlCeCommand cmd = new SqlCeCommand("SELECT IdArticolo, ArticoloDex FROM tabArticoli", conn);
SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
// N.B. Serve anche System.Xml
DataTable dt = new DataTable();
try
{
try
{
da.Fill(dt);
lstArticoli.DisplayMember = "ArticoloDex";
lstArticoli.ValueMember = "IdArticolo";
lstArticoli.DataSource = dt;
}
finally
{
conn.Dispose();
cmd.Dispose();
da.Dispose();
}
}
catch (SqlCeException ex)
{
MessageBox.Show("Errore Lettore");
}
int end = Environment.TickCount;
lblTime.Text = (end - start).ToString();
}
In questo caso, ottenendo gli stessi risultati, abbiamo abbassato di un altro 10% l’utilizzo di memoria necessaria all’operazione.
Se però, la prima regola è risparmiare memoria, visto che stiamo leggendo i dati per farci un calcolo o eseguire il binding ad un controllo perché non usiamo un DataReader che non alloca oggetti per record e campi, ma semplicemente ci consente di estrarre i dati portandoli nel controllo ? Così facendo evitiamo il cosiddetto double-buffering cioè prelevare i dati da DB per metterli nella DataTable (o DataSet) per poi rileggerli dalla DataTable per metterli nel controllo: stiamo anche facendo due giri sui dati per portarli prima nella struttura di memoria e poi portarli nel controllo che li visualizza.
Testimoniamo il tutto riportando prima il codice che utilizza il DataReader per poi vedere se effettivamente i tempi di esecuzione si abbassano:
public static void AllSelectFieldsDR(ComboBox lstArticoli, Label lblTime)
{
int start = Environment.TickCount;
lstArticoli.BeginUpdate();
lstArticoli.DataSource = null;
lstArticoli.Items.Clear();
SqlCeConnection conn = new SqlCeConnection();
conn.ConnectionString = “…”;
SqlCeCommand cmd = new SqlCeCommand("SELECT IdArticolo, ArticoloDex FROM tabArticoli", conn);
SqlCeDataReader dr = null;
try
{
try
{
conn.Open();
dr = cmd.ExecuteReader();
lstArticoli.DisplayMember = "ArticoloDex";
lstArticoli.ValueMember = "IdArticolo";
int indexArticoloDex = dr.GetOrdinal("ArticoloDex");
while(dr.Read())
{
lstArticoli.Items.Add(dr.GetString(indexArticoloDex));
}
}
finally
{
conn.Dispose();
cmd.Dispose();
dr.Dispose();
lstArticoli.EndUpdate();
}
}
catch (SqlCeException ex)
{
MessageBox.Show("Errore Lettore");
}
int end = Environment.TickCount;
lblTime.Text = (end - start).ToString();
}
Abbiamo sicuramente il “problema” di eseguire a mano il ciclo sui dati visto che il DataReader non può essere “bindato” direttamente su un controllo, ma con la seguente tabella dovremmo aver chiaro il perché lo dovremmo fare sempre:
|
100 record |
0,58 secondi |
|
1000 record |
19 secondi |
|
10000 record |
80 secondi |
Abbiamo raddoppiato le performance in lettura senza contare che i 10000 record non sono stati portati in memoria prima di essere legati al controllo: questo significa poca RAM utilizzata e pochi interventi del Garbage Collector che nel caso precedente doveva deallocare tonnellate di oggetti orfani.
Se utilizziamo un comando prepared, utile per i comandi usati più spesso, arriviamo ai seguenti dati di performance:
|
100 record |
0,39 secondi |
|
1000 record |
12 secondi |
|
10000 record |
66 secondi |
Abbiamo migliorato ancora e non di poco: siamo partiti da 1,06 secondi per arrivare a 0,39 per 100 record e da 151 secondi a 66 per 10.000 record, ma possiamo fare ancora meglio. Prima di vedere il metodo più veloce di accesso ai dati diamo uno sguardo al tanto sbandierato SqlCeResultSet che a detta di molti ha performance quasi uguali al DataReader, ma la possibilità di essere “bindato” all’interfaccia utente. Il codice lo trovate nella demo completa scaricabile: lo ometto per brevità. Avremo poi modo nel prossimo articolo di descrivere le sue funzionalità.
|
100 record |
0,717 secondi |
|
1000 record |
26 secondi |
|
10000 record |
102 secondi |
Direi che questa è la testimonianza che non siamo poi tanto vicini al DataReader perché viaggiamo a circa la metà…che non è esattamente vicino J
Mentre i metodi precedenti possono lavorare anche con tabelle in JOIN nello statement di SELECT, l’ultimo metodo, che come promesso è il più veloce, consente di lavorare su una sola tabella. Si chiama Base Table Cursor e come dice la parola indica l’utilizzo di un cursore su una tabella: prima di spaventarsi alla parola cursore che come sappiamo da anni è il nemico delle performance e scalabilità per SQL Server, pensiamo al fatto che SQLCE gira in-process (su tutte le piattaforme) rispetto all’applicazione che lo utilizza, quindi non è un peccato mortale, anzi, utilizzare un cursore diretto su una tabella.
Ecco i dati e poi il codice seguito da una spiegazione.
|
100 record |
0,35 secondi |
|
1000 record |
6,7 secondi |
|
10000 record |
24 secondi |
La parola più corretta da usare dopo il test non si può scrivere in un articolo, ma è impressionante il guadagno al crescere dei dati: 6,7 secondi contro 12 del DataReader con comando prepared (ricordo che i comandi prepared non è detto che restino nella cache di SQLCE per sempre!) e quindi dovremmo prendere circa 15 secondi come valore di riferimento medio per il DataReader; e un 24 secondi contro 66 mi sembra un ottimo guadagno.
Ecco il codice che utilizza un comando Prepared (come per il DataReader) con un Base Table Cursor
private static SqlCeCommand _cmdAllBaseTableCursor;
public static void AllBaseTableCursor(ComboBox lstArticoli, Label lblTime)
{
int start = Environment.TickCount;
lstArticoli.BeginUpdate();
lstArticoli.DataSource = null;
lstArticoli.Items.Clear();
try
{
try
{
if (_cmdAllBaseTableCursor == null)
{
SqlCeConnection conn = new SqlCeConnection();
conn.ConnectionString = “…”;
SqlCeCommand cmd = new SqlCeCommand("tabArticoli", conn);
cmd.Connection.Open();
cmd.CommandType = CommandType.TableDirect;
_cmdAllBaseTableCursor = cmd;
}
SqlCeDataReader dr = _cmdAllBaseTableCursor.ExecuteReader();
lstArticoli.DisplayMember = "ArticoloDex";
lstArticoli.ValueMember = "IdArticolo";
int indexArticoloDex = dr.GetOrdinal("ArticoloDex");
while (dr.Read())
{
lstArticoli.Items.Add(dr.GetString(indexArticoloDex));
}
}
finally
{
// Non distruggo command e connection
lstArticoli.EndUpdate();
}
}
catch (SqlCeException ex)
{
MessageBox.Show("Errore Lettore");
}
int end = Environment.TickCount;
lblTime.Text = (end - start).ToString();
}
L’unica differenza con il codice del DataReader, in effetti il Base Table Cursor è solamente il cursore che poi va letto con un metodo di accesso ai dati, è nella costruzione del comando che fa riferimento alla tabella tabArticoli e non a uno statement di SELECT e all’indicazione del CommandType = CommandType.TableDirect. Volendo estrarre un subset di record con un filtro o recuperare un singolo record il codice è leggermente diverso rispetto a un DataReader e avremo il prossimo articolo per far luce su questo punto. Nel prossimo articolo vedremo anche come eseguire bulk insert di dati cercando ancora una volta di ottimizzare il processo.
Se la vostra applicazione fa uso di classi custom e collection custom, alimentate dal codice di accesso ai dati, ancora una volta, per leggere i dati da una sola tabella, il metodo migliore è usare un Base Table Cursor.
Sperando di aver testimoniato quello che cerchiamo di dire da anni ad ogni nostra conferenza o corso mobile e cioè: si può far andar forte, anzi, molto forte, un’applicazione mobile che usa SQLCE, vi rimando al prossimo articolo dove analizzeremo la selezione di una serie di righe (filtri sui record) e recupero del singolo record. Vi saluto dandovi una anticipazione: il tempo di accesso ad un singolo record utilizzando un DataSet, un SqlCeResultSet e un Base Table Cursor su 10.000 record:
|
1 record DataSet |
0,354 secondi |
|
1 record SqlCeResultSet |
0,301 secondi |
|
1 record Base Table Cursor |
0,011 secondi |
Parola che non si può scrivere in un articolo ma che comincia con M e finisce per A J
Roberto Brunetti
http://blogs.devleap.com/rob
http://thinkmobile.it
More Posts
Next page »