in

dotNet Umbria

Il primo User Group in Umbria sul mondo .Net

Articoli

Articoli vari degli iscritti a DotNetUmbria
  • Primi passi con il .NET Microframework

    Come accennavo in un mio precedente commento ad un post di Andrea, qualche tempo fa alcuni miei colleghi ed io ci siamo dilettati con alcuni primordiali esperimenti di “programmazione embedded” (le virgolette sono d’obbligo visto il livello davvero elementare dei nostri esperimenti); all’inizio è duro anche solamente scegliere cosa provare: il campo di applicazioni è vasto ma richiede competenze un po’ ad ampio spettro, comunque che vanno al di là delle sole conoscenze relative alla progettazione/realizzazione del software.

    Abbiamo acquistato un kit di sviluppo Tahoe, ovvero una scheda equipaggiata con CPU Meridian, dotata di un display LCD, porta seriale, usb e una ridda di pin con passo un decimo di pollice per accedere facilmente alle porte I/O messe a disposizione dalla CPU stessa.  Ecco la nostra scheda (noterete che è ancora marcata ‘Embedded Fusion’…)

    Il Tahoe Development Kit

    Il passo successivo per noi è stato quello di informarci sui rudimenti dei protocolli di comunicazione I2C e SPI, supportati dal Meridian, e di procurarci i primi integrati da far interagire con la nostra scheda…

    abbiamo pensato ai classici sensori di temperatura e ne abbiamo scelti alcuni che implementano il bus I2C e altri che invece dialogano via SPI e, per farci veramente del male, ce li siamo procurati in versione microscopica (con package SMD tra i più piccoli, al limite della saldabilità a mano!)… per poterli ‘cablare’ in una breadboard di dimensioni umane abbiamo comprato (o in un caso realizzato in casa, con tanto di bromografo e bagni in percloruro ferrico!) piccole Printed Board che ospitano l’integrato e offrono piazzole forate per poter saldare i classici cavetti di collegamento… ed eccovi il risultato nella sua forma migliore!

    La breadboard 

    Cosa stiamo osservando? Alla scheda sono collegati: due sensori di temperatura TMP100 (I2C) e un sensore di temperatura TMP123 (SPI) di Texas Instruments, ma non solo! Abbiamo fatto piccoli esperimenti anche con un RTC DS1678 (I2C) della Maxim e, dulcis in fundo (ma ancora tutto è in stato embrionale), un ADC ADS1000 (I2C) ancora di Texas Instruments.

    Al di là del layout confusionario e dei singoli componenti usati quello che abbiamo messo in opera è una semplice comunicaizone con dispositivi esterni con i due protocolli citati; il risultato? Eccovi un termometro con ben tre sensori (e altre cose di contorno)! La temperatura rilevata dai primi due (identici) è quella delle prime due righe, poi abbiamo il clock; alla quarta riga la temperatura rilevata dal sensore SPI (che ha sensibilità maggiore) e in ultima riga un dato analogico (una tensione in particolare) convertito dall’ADC

    Il display quando tutto è in funzione

    Applicazioni pratiche? Chiaramente un termostato programmabile con rilevazione della temperatura in più punti, eccetera eccetera…

    Ma da un punto di vista prettamente informatico, cosa abbiamo realizzato? Per ogni dispositivo una classe driver che eredita da una opportuna classe fornita dal SDK di Embedded Fusion (una per ogni protocollo che abbiamo usato, intendo), a formare un semplice diagramma di questo tipo:

    ClassDiagram1

    La routine principale del nostro software ‘prototipale’? A intervalli regolari chiediamo al processore di fare un giro di ‘transazioni’ di scrittura/lettura con i dispositivi esterni che abbiamo inizializzato e scriviamo i risultati sul display:

    float temp_1 = Hardware.TMP100_1.Temperature;
    float temp_2 = Hardware.TMP100_2.Temperature;
     
    DateTime timestamp = Hardware.DS1678.TimeStamp;
     
    float temp_spi=Hardware.TMP123.Temperature;
     
    float data = Hardware.ADS1000.Data;
     
    Dispatcher.Invoke(new TimeSpan(1000),
        new TimerCallback(delegate(object state) {
            _txt_1.TextContent = string.Concat("#1 : ",string.Concat(temp_1.ToString(), " °C"));
            _txt_2.TextContent = string.Concat("#2 : ", string.Concat(temp_2.ToString(), " °C"));
            _txt_3.TextContent = timestamp.ToString();
            _txt_4.TextContent = string.Concat("SPI : ", string.Concat(temp_spi.ToString(), " °C"));
            _txt_5.TextContent = string.Concat("Data : ", string.Concat(data.ToString(), ""));
        }),
        null);

    Che ne dite? Approfondiamo? Avete idee o suggerimenti? A presto, allora!

  • Progetto NHooligans - Parte 1 - Concetti, entità e interfacce del sistema di simulazione

    In considerazione della straordinaria risposta al mio post nella sezione "futuri appuntamenti" (intendo straordinariamente scarsa), provo ad addentrarmi un po' più nel concreto del sistema di simulazione che costituirà l'ambiente all'interno del quale interagiranno i vari agenti (denominati "hooligan") comandati dai programmi "contendenti".

    Facendo riferimento alla terminologia utilizzata nel framework NSteer, intorno al quale verrà sviluppato il sistema di simulazione, si individuano i seguenti "attori", ciascuno con le proprie peculiarità (o, in altri termini, ciascuno caratterizzato dall'implementazione di una o più delle interfacce definite dal framework:

    Un oggetto di tipo "agente" (Agent, in NSteer), descrive un'entità che vive all'interno del "mondo" (World, in NSteer). Un agente ha un "corpo" (Body, in NSteer), un sistema di "visione" (Vision, in NSteer) e un "comportamento" (Behavior, in NSteer).

    Il "corpo" di un agente definisce le proprietà cinematiche dell'agente (posizione, velocità e accelerazione).

    Un sistema di "visione" definisce la regione in cui un agente può "vedere" gli altri agenti o gli ostacoli presenti, tipicamente, nelle sue vicinanze.

    Il "comportamento" di un agente determina, ad ogni passo della simulazione, la forza (in senso vettoriale) cui sarà sottoposto il suo "corpo".

    Gli agenti sono confinati all'interno del "mondo".

    Il "simulatore" (Simulator, in NSteer) determina, ad ogni passo della simulazione, la situazione cinematica (posizione, velocità e accelerazione) di tutti gli agenti presenti nel sistema.

    Il sistema di simulazione implementato in NSteer definisce quattro servizi per gestire gli agenti, il mondo, gli ostacoli e la "prossimità" di un agente nei confronti degli altri rispettivamente mediante i servizi AgentService, WorldService, ObstacleService e NeighborhoodService.

    Per quanto concerne la visualizzazione della simulazione NSteer definisce il concetto di Sprite, un oggetto 2D caratterizzato da attributi quali colori, visibilità, forma, ecc. e il concetto di "Scena" (Scene, in NSteer), che astrae gli aspetti legati al rendering della simulazione.

    Prima che vi sloghiate la mandibola per gli sbadigli, vi incollo un'immagine relativa ad uno step della simulazione realizzata all'interno del demo che accompagna il framework NSteer.

    Nello screenshot è possibile individuare una decina di agenti (i punti contornati dal cerchietto rosa, che rappresenta lo spazio occupato dall'agente) ciascuno dei quali ha un proprio "orientamento" ed un proprio "cono visivo" (rappresentato dai 3 segmenti terminati da pallini che escono dal centro dell'agente). I comportamenti di ciascun agente sono descritti dall'etichetta che compare accanto a ciascun cerchietto rosa. I comportamenti definiti all'interno del framework NSteer sono già parecchi, ma è possibile definirne di nuovi o scrivendoli da zero o utilizzando uno dei comportamenti "contenitore" già definiti in NSteer. I 3 cerchi viola rappresentano degli ostacoli circolari. Nell'immagine gli agenti al centro della finestra hanno attivato il comportamento "Seek" (ossia insegui) nei confronti del pallino verde in alto (comandato dal movimento del cursore del mouse nell'applicazione di esempio). Gli altri hanno attivato invece un comportamento composito caratterizzato dalla somma del comportamento "Confine" (ossia confina, argina) e del comportamento "Avoid" (ossia evita), rispettivamente per rimanere all'interno della regione permessa (rappresentata dal riquadro interno) e per evitare l'ostacolo che hanno accanto.

    Anche se nello step di simulazione immortalato da questo screenshot non è evidente (perché in questo screenshot altri comportamenti avevano priorità), gli agenti tendono a rimanere uniti ma a non sovrapporsi grazie ad un altro comportamento: il FlockBehavior (dove flock significa "stormo").Questo comportamento ha una valenza "storica" particolare perché è proprio dalla scomposizione delle regole che guidano il comportamento di uno stormo di uccelli che Reynolds ha tratto lo spunto per il proprio lavoro. Per rendervi conto meglio di quello che Reynolds ha inventato (o forse dovrei dire "scoperto") vi consiglio di dare un'occhiata ai demo allegati, ed in particolare (premento Tab si passa da un demo al successivo) al demo "Boids".

    Nel prossimo post inizieremo a dare un'occhiata a qualche snippet di codice, per poi fare chiarezza sui "lotti" di progetto disponibili per l'assegnazione a chi vorrà partecipare attivamente allo sviluppo di NHooligans.

  • Tutorial WPF & LINQ - Lezione 2 - Accesso al database tramite LINQ

    Eccoci qui con la seconda lezione!
    In questa lezione vedremo come creare in automatico delle classi di accesso al database sfruttando le potenzialità di LINQ to SQL quindi vedremo come creare una listview che prelevi i dati dalla nostra tabellina e li mostri a video. Tutto questo senza scrivere una sola riga di codice !!

    4. Creazione di Classi LINQ to SQL

    In Visual Studio, Microsoft mette a disposizione un tipo di file (dbml) che ci consente di creare le classi in modo visuale senza complicarci troppo la vita. Quindi cliccate con il pulsante destro sul progetto nella finestra di esplorazione progetti e seguite il percorso "Add -> New Item ... " e vi apparirà una finestra come questa:Inserimento di LINQ to SQL
    dove selezionate "LINQ to SQL Classes" quindi date un nome al vostro file considerando che tutti i riferimenti poi sfrutteranno questo nome. Io ho messo "MyLinqToSQL.dbml".

    Questa operazione aggiunge tutte le dipendenze necessarie per lavorare con LINQ to SQL se controllate sulle risorse infatti dovreste avere tra gli Assembly:

    System.Data.LINQ
    System.Xml.LINQ

    Ora trascinate la vostra tabella "TIPI" dal "Server Explorer" all'area di lavoro (dovreste infatti avere il file MyLinqToSQL.dbml aperto in modalità Design). Se avete fatto tutto bene vi apparirà la vostra tabella nell'area di lavoro. Questa tabella è la rappresentazione grafica della classe "Tipi" creata in automatico da VS.
    Ora salvate il file e chiudetelo visto che per il momento non ci serve più. L'area di lavoro torna al design della finestra Window1 del progetto.

    5. Creazione della LISTVIEW

    Per inserire controlli in una form WPF il mio consiglio è di utilizzare Expression Blend. Aprite Expression Blend (senza chiudere il progetto VS) e caricate il progetto TestWPFLinqToSql. Vi comparirà la vostra window1. Cercate il controllo ListView nella toolbar a sinistra (probabilmente dovrete aprire l'Asset Library cliccando sul pulsante ">>" quindi cercate ListView e fateci Click, verrà aggiunto alla toolbar) quindi fate doppio click su di esso e vedrete che vi si posizionerà in alto a sinistra. impostate i margini a 0,0,0,40 così da lasciare lo spazio per i futuri bottoni.

    A questo punto andate nel pannello delle proprietà (sempre di expression studio) e se non vi da problemi di refresh potete scorrerlo fino a trovare "miscellaneous" che è una sezione compattata.
    Cliccate su di essa e, sempre se non avete problemi di refresh, potete scorrerla fino a trovare "View" che è anch'essa una sezione compattata, sempre cliccandoci sopra la aprite e scorrete fino a trovare la voce "columns" che invece è una collezione per cui avrete un pulsantino con "..." scritto sopra.
    Cliccateci e se tutto va bene dovrebbe comparirvi il "Collection Editor" delle colonne.
    Create 1 colonna (l'altra già c'è) poi selezionate la prima e impostate la Proprietà "Header" in "Codice"
    Selezionate la seconda e impostate la proprietà "Header" a "Descrizione".
    Ora chiudete premendo OK.
    Visto che probabilmente avrete problemi di refresh anche qui, vi consiglio di chiudere la finestra di design della Window1 e riaprirla dal pannello "Project".
    otterrete una cosa simile a questa:

    6. Associare i dati alla ListView

    Ora comincia il bello.
    Premete F5 e magicamente il programma si avvierà, correggete errori se ce ne sono ma credo che non ve ne siano.
    Ora chiudete l'applicazione appena eseguita e selezionate il controllo ListView facendo doppioclick su esso dal pannello "Object and TimeLine" in modo tale che vi si evidenzi in giallo.
    A questo punto cliccate con il pulsante destro sul controllo nel form e scegliete la voce "BindItemSourceToData" dal menu contestuale.
    Premete sul pulsante "+ CLR" che trovate nella finestra che si è appena aperta e cliccate su "MyLinqToSQLDataContext" che dovreste avere nella lista (io avevo chiamato il file delle classi linq così: MyLinqToSQL.dmbl per cui il mio datacontext si chiama MyLinqToSQLDataContext)
    Premete su "OK" e il datacontext verrà aggiunto alle connessioni "Data Sources", cliccateci sopra e vi apparirà nella colonna di destra, l'elenco degli oggetti che implementano l'interfaccia IObservableCollection (e qui non sono proprio sicuro, quindi la butto là così come mi viene e prendetela con beneficio di inventario) tra cui la classe "Tipis (Array):(Table Tipi)"
    Selezionatela e premete Finish.
    Sembra che non sia cambiato niente ma in realtà abbiamo appena detto che il compilatore XAML deve creare un'istanza di Tipis perché la ListView la deve usare come DataSource ed abbiamo detto alla ListView di usare quell'istanza come DataSource per i suoi DataItem.
    passate dalla visualizzazione Design a quella XML, il Codice XAML ora è il seguente:

    <Window
        xmlns="
    http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="
    http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="Window1"
        Title="Window1" Height="300" Width="300" xmlns:d="
    http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:TestWPFLinqToSql="clr-namespace:TestWPFLinqToSql">
     <Window.Resources>
      <ObjectDataProvider x:Key="MyLinqToSQLDataContextDS" d:IsDataSource="True" ObjectType="{x:Type TestWPFLinqToSql:MyLinqToSQLDataContext}"/>
     </Window.Resources>
        <Grid>
         <ListView IsSynchronizedWithCurrentItem="True" Margin="0,0,0,40" ItemsSource="{Binding Path=Tipis, Mode=Default, Source={StaticResource MyLinqToSQLDataContextDS}}">
          <ListView.View>
           <GridView>
            <GridViewColumn Header="Codice"/>
            <GridViewColumn Header="Descrizione"/>
           </GridView>
          </ListView.View>
         </ListView>
        </Grid>
    </Window>

    Ora bisogna dire alle colonne quale valore prendere:
    Qui il mio consiglio è di farlo a mano:

            <GridViewColumn Header="Codice" DisplayMemberBinding="{Binding Path=TipoCodice}"/>
            <GridViewColumn Header="Descrizione" DisplayMemberBinding="{Binding Path=TipoDescrizione}"/>

    Purtroppo Expression Blend non ha intellisense e quello di VS si spegne proprio nel Binding per cui dovete sapere come si chiamano le colonne della tabella, le mie si chiamano TipoCodice e TipoDescrizione.
    ora compilate ed eseguite premendo F5. se avete messo dei valori di prova nella tabella dovreste vederli nella listview.
    Tutto questo ancora una volta senza scrivere nessuna riga di codice se non per effettuare il Binding alle colonne. spero che questa limitazione venga al piu presto sorpassata (io sto utilizzando VS2008 SP1 e Blend 2.5 March CTP).

    Con questo abbiamo chiuso la Lezione 2, arrivederci alla prossima lezione dove vedremo come permettere la modifica dei valori direttamente da listview.
    Vi lascio in allegato lo screenshot di quello che è venuto fuori.
  • Tutorial WPF & LINQ - Lezione 1 - Preparazione dell'ambiente di lavoro

     

    Come promesso eccomi qua per creare un mini-tutorial sulla creazione di una piccola applicazione WPF che legga e scriva dati in una tabella di un database SQL sfruttando LINQ.

    1. Creazione del database in SQL EXPRESS 

    Per prima cosa occorre creare un database, io utilizzo SQL Management Studio Express per gestire i database.
    posto la query per la creazione del database così come me la propone SQL Management Studio Express:

    USE [master]
    GO
    /****** Oggetto:  Database [MySampleDB]    Data script: 05/22/2008 13:36:33 ******/
    CREATE DATABASE [MySampleDB] ON  PRIMARY
    ( NAME = N'MySampleDB', FILENAME = N'c:\Programmi\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\MySampleDB.mdf' , SIZE = 2240KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
     LOG ON
    ( NAME = N'MySampleDB_log', FILENAME = N'c:\Programmi\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\MySampleDB_log.LDF' , SIZE = 560KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
    GO
    EXEC dbo.sp_dbcmptlevel @dbname=N'MySampleDB', @new_cmptlevel=90
    GO
    IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
    begin
    EXEC [MySampleDB].[dbo].[sp_fulltext_database] @action = 'enable'
    end
    GO
    ALTER DATABASE [MySampleDB] SET ANSI_NULL_DEFAULT OFF
    GO
    ALTER DATABASE [MySampleDB] SET ANSI_NULLS OFF
    GO
    ALTER DATABASE [MySampleDB] SET ANSI_PADDING OFF
    GO
    ALTER DATABASE [MySampleDB] SET ANSI_WARNINGS OFF
    GO
    ALTER DATABASE [MySampleDB] SET ARITHABORT OFF
    GO
    ALTER DATABASE [MySampleDB] SET AUTO_CLOSE ON
    GO
    ALTER DATABASE [MySampleDB] SET AUTO_CREATE_STATISTICS ON
    GO
    ALTER DATABASE [MySampleDB] SET AUTO_SHRINK OFF
    GO
    ALTER DATABASE [MySampleDB] SET AUTO_UPDATE_STATISTICS ON
    GO
    ALTER DATABASE [MySampleDB] SET CURSOR_CLOSE_ON_COMMIT OFF
    GO
    ALTER DATABASE [MySampleDB] SET CURSOR_DEFAULT  GLOBAL
    GO
    ALTER DATABASE [MySampleDB] SET CONCAT_NULL_YIELDS_NULL OFF
    GO
    ALTER DATABASE [MySampleDB] SET NUMERIC_ROUNDABORT OFF
    GO
    ALTER DATABASE [MySampleDB] SET QUOTED_IDENTIFIER OFF
    GO
    ALTER DATABASE [MySampleDB] SET RECURSIVE_TRIGGERS OFF
    GO
    ALTER DATABASE [MySampleDB] SET  ENABLE_BROKER
    GO
    ALTER DATABASE [MySampleDB] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
    GO
    ALTER DATABASE [MySampleDB] SET DATE_CORRELATION_OPTIMIZATION OFF
    GO
    ALTER DATABASE [MySampleDB] SET TRUSTWORTHY OFF
    GO
    ALTER DATABASE [MySampleDB] SET ALLOW_SNAPSHOT_ISOLATION OFF
    GO
    ALTER DATABASE [MySampleDB] SET PARAMETERIZATION SIMPLE
    GO
    ALTER DATABASE [MySampleDB] SET  READ_WRITE
    GO
    ALTER DATABASE [MySampleDB] SET RECOVERY SIMPLE
    GO
    ALTER DATABASE [MySampleDB] SET  MULTI_USER
    GO
    ALTER DATABASE [MySampleDB] SET PAGE_VERIFY CHECKSUM 
    GO
    ALTER DATABASE [MySampleDB] SET DB_CHAINING OFF

    una volta creato il database è necessario creare la tabella:
    anche qui posto il codice così come me lo propone SQLMSE:

    USE [MySampleDB]
    GO
    /****** Oggetto:  Table [dbo].[Tipi]    Data script: 05/22/2008 13:40:07 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    CREATE TABLE [dbo].[Tipi](
     [TipoCodice] [bigint] NOT NULL,
     [TipoDescrizione] [nvarchar](100) NULL,
     CONSTRAINT [PK_Tipi] PRIMARY KEY CLUSTERED
    (
     [TipoCodice] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY]

    La nostra tabella sarà quindi una tabellina di 2 soli campi: TipoCodice e TipoDescrizione. la tabella si chiama Tipi

    2. Creazione del un progetto con VISUAL STUDIO 2008

    Apriamo ora Visual Studio 2008 e creiamo un nuovo progetto di tipo "WPF Application" e lo chiamiamo "TestWPFLinqToSql"

    3. Creazione di una connessione dati in Visual Studio 2008

    Dalla finestra "Server Explorer" premete il pulsante "Connect To Database" (quello con il cilindretto giallo ed il + verde) vi comparirà la finestra "Add Connection" dove andrete a selezionare il server (es. NOMEPC/SQLEXPRESS) dove avete creato il database e quindi impostate, se necessario, le credenziali e selezionate il database MySampleDB quindi date ok e vi comparirà nella finestra "Server Explorer" il vostro collegamento al database.

    Ora abbiamo preparato tutto il necessario per poter lavorare. Salvate tutto il progetto e proseguite con la Lezione 2

  • Come scrivere un articolo

    Ecco a voi alcune nozioni tecniche, per chi volesse cimentarsi nella scrittura di un articolo per il nostro portale.

    La seziona articoli è un blog e il suo url è http://dotnetumbria.org/blogs/articoli

    Una volta aperta la pagina del blog, e dopo avere fatto login, in alto a destra trovate il link "Control Panel".

    All'interno del Link è presente il comando "Write a Blog Post", che apre l'editor che vi permette la scrittura direttamente dal WEB del vostro nuovo articolo; ovviamente potete inserire allegati all'articolo e Tag di  ricerca.

    Se scrivete molti articoli nello stesso giorno potete decidere di pubblicarli un po' alla volta cambiando la data di pubblicazione nel Tab "Publication Settings".

    Essendo il nostro un portale tecnico, abbiamo inserito un piccolo plugin che ci permette di formattare il codice in modo agevole.

    Tutto il codice che volete formattare va inserito all'interno dei tag [code language=""] [/code], dove language è il linguaggio di programmazione che state utilizzando. Il plugin si occuperà di colorare le keywords e i riferimenti sintattici in modo coerente con il linguaggio scelto. I linguaggi supportati sono i più disparati e vanno dall'XML semplice al VB.Net passando per C# ed altri. Unico consiglio, se state scrivendo XAML utilizzate XML come linguaggio, e in linea generale utilizzate il tag XML per tutto quello che può essere riferito a tale standard.

    Se preferite utlizzare uno strumento Offline, un'ottima scelta secondo me è Windows Live Writer.

    L'url per la configurazione del blog è sempre la stessa e il login è il vostro account.

    Buon lavoro a tutti.

  • Wpf - Animazioni (2D e 3D) con DoubleAnimation

    Sviluppando un progetto, mi sono trovato a dover implementare una animazione. Semplicemente, il mio oggetto 3D alla pressione di una determinata combinazione di tasti deve allontanarsi dal punto di vista dell'utente e quindi scorrere negativamente sull'asse z, ricordiamoci la regola della mano destra:

    righthand

    Per far questo ho creato un oggetto DoubleAnimation che non è altro che un semplice interpolatore. Basta definire pochi valori per avere un'animazione semplice ma funzionale.

    [code language="xml"]

    DoubleAnimation animazione= new DoubleAnimation();

    ...

    animazione.To = -100; //valore di fine interpolazione
    animazione.Duration = new Duration(TimeSpan.Parse("0:0:0.5")); //durata dell'animazione
    Oggetto3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, animazione); //applico l'interpolatore alla proprietà dell'oggetto 3D

    [/code]

    Come vedete, non ho impostato il valore di inizio interpolazione perché, come per l'altro mio post, anche qui l'interpolatore prende in automatico, come punto di partenza, il valore attuale (in questo caso la posizione attuale sull'asse Z).

    Fin qui tutto ok. Quando però successivamente, in un altra parte del programma, ho tentato di cambiare ulteriormente la distanza del mio oggetto, questa volta senza animazione

    [code language="xml"]

    Oggetto3D.OffsetZ = -50;

    [/code]

    questo mi è risultato impossibile. Mettendo un breakpoint sull'assegnazione, vedevo chiaramente che l'operazione avveniva ma che, apparentemente almeno, era totalmente ignorata e che quindi la traslazione sull'asse Z del mio oggetto restava -100.

    Dopo un discreto tempo passato su msdn ho trovato motivo e soluzione al mio problema.

    L'interpolatore, anche se arrivato a fine "ciclo" rimane comunque attivo, continuando a impostare a -100 il valore della mia traslazione.

    Per ovviare al problema, basta "scollegare" l'oggetto DoubleAnimation dalla proprietà OffsetZProperty

    [code language="xml"]

    Oggetto3D.BeginAnimation(TranslateTransform3D.OffsetZProperty, null);
    Oggetto3D.OffsetZ = -50;

    [/code]

    impostando come "null" l'interpolatore addetto alla modifica della property.

    Spero di essere stato d'aiuto.

    Un saluto a tutta la community.

  • Timeline ed interpolazione con WPF

    Come la maggior parte di coloro che per la prima volta si avvicinano a WPF mi sono trovato a gestire le animazioni attraverso le Timeline.

    Un animazione gradevole e didatticamente interessante consiste nel far ingrandire un oggetto qualsiasi (button, image, o qualsiasi altro) al passaggio del mouse.

    Ottenere quest’effetto è molto semplice, basta andare a modificare i valori dei due moltiplicatori X e Y (scale orizzontale e scale verticale). Di default entrambi i valori sono impostati a 1, che è come dire 100%

    Cosa significa questo: che se il nostro button (ad esempio) è alto 10 pixel, con il valore di scale Y a 1, diventa 10 x 1 = 10 pixel quindi dimensione originale. Modificando il moltiplicatore e impostandolo a 2 (200%), è banalmente ovvio che il nostro button diventerà alto 20.

    Torniamo a noi. Volendo creare un animazione, che al passaggio del mouse ingrandisca il nostro oggetto raddoppiandone le dimensioni, ci basta agganciarne l’evento OnMouseEnter e, impostando nella timeline un arco di tempo a piacere (diciamo mezzo secondo) e successivamente cambiare i valori, da 1 a 2, dei campi X e Y nel campo scale del LayoutTransform .

    Volendo fare l’operazione opposta, e cioè agganciando all’oggetto l’evento OnMouseLeave per far si che all’uscita del mouse, il nostro oggetto torni alle dimensioni originali, ho incontrato un problema. Ho ovviamente creato una nuova timeline, impostato lo scale a 2 del LayoutTransform per indicare, ovviamente, i valori di inizio interpolazione, e poi, a mezzo secondo di distanza nella timeline, ho impostato di nuovo i valori dello scale a 1.

    [code language="xml"]

    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="2"/>
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>

    [/code]

    Mandando in esecuzione l’applicazione, dopo pochissime prove, ho notato un insolito e alquanto poco gradito comportamento: facendo uscire il mouse dal mio oggetto prima del termine dell’animazione (in pratica scatenando l’evento OnMouseLeave quando il mio oggetto non era ancora completamente ingrandito), questo “scattava” alla dimensione raddoppiata per poi proseguire correttamente con la seconda animazione e quindi tornando fluidamente alle dimensioni originali.

    Facendo un paio di prove ho scoperto che la cosa è di una semplicità disarmante ma, soprattutto per chi come me ha avuto già a che fare in precedenza con animazioni, timeline e interpolatori vari, non molto ovvia.

    L’errore da me commesso è stato di settare lo scale a 2 per indicare i valori di inizio interpolazione per l’animazione di “restringimento”. Non impostandoli, ma impostando solo i valori di fine interpolazione, WPF fa partire l’interpolazione dai valori attuali dello scale, qualsiasi essi siano.

    [code language="xml"] 

    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="prova" Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
    </DoubleAnimationUsingKeyFrames>

    [/code] 

    Lanciando la nostra applicazione, possiamo vedere che, l’effetto voluto è gradevole e fluido.

    Sperando di avervi risparmiato quei 5 minuti di prove, saluto tutti.

  • Inserisci il tuo articolo !!

    Ogni utente registrato può inserire liberamente i propri articoli nella sezione "Articoli".
    Potete scrivere l'articolo direttamente dal sito oppure utilizzare Windows Live Writer indicando il seguente url come url del blog:

    http://dotnetumbria.org/blogs/articoli/

    Aspettiamo numerosi vostri contenuti.

  • WPF Invalidate Binding

    Il binding di WPF è molto reattivo grazie a tutta la struttura di DependencyObjects e DependencyProperties a cui si appoggia. Per questo motivo lavorando con WPF molto spesso non ci dobbiamo preoccupare di fare un refresh dell'interfaccia utente per visualizzare i dati aggiornati.

    Può capitare tuttavia di dover mettere in binding alcune sorgenti dati che non utilizzano DependencyProperties e che nella loro struttura non implementano l'interfaccia INotifyPropertyChanged . In questi rari casi accade che l'interfaccia utente non è aggiornata con i dati perchè non è in grado di capire che i dati sono stati modificati in qualche modo. Dobbiamo essere quindi noi a forzare il refresh.

    Vediamo quali strumetni abbiamo a disposizione. Viene sicuramente in nostro aiuto la classe statica BindingOperations, la quale ci permette di interrogare la struttura ad oggetti dell'interfaccia utente e capire se le proprietà degli oggetti stessi sono in binding oppure no.

    image

     

    BindingOperations ci restituisce oggetti di tipo BindingExpression che sono di fatto il collegamento tra il target e la sorgente dati.

    image

    Possiamo utilizzare il metodo UpdateTarget per forzare la lettura dei dati ed un refresh dell'interfaccia utente.


    Come facciamo tuttavia da codice a utilizzare questo metodo senza vincolare la grafica a delle strutture fisse di binding ?
    Mi spiego meglio: Se utilizziamo questo metodo, dobbiamo conoscere esattamente quali sono i campi bindati per poter sollevare il binding e quindi vincoliamo lo sviluppo della parte grafica impedendo la modifica di template e di datatemplate.

    Ci viene in aiuto un'altra classe molto importante: VisualTreeHelper.

    image

    Questa classe ci permette di navigare all'interno della VisualTree, ovvero all'interno degli oggetti renderizzati nell'interfaccia utente. Con un po' di reflection , e con l'aiuto di BidingOperations possiamo estrarre dalla VisualTree tutte le BindingExpressions ed invocare il metodo UpdateTarget.

    Ecco il codice :

    [code language="c#"]

     

    private static void InvalidateBinding(DependencyObject objectbase)
    {

      // Per ogni oggetto all'interno del ramo di visualtree
      for (int x = 0; x < VisualTreeHelper.GetChildrenCount(objectbase); x++)
      {

        // Leggo l'oggetto nodo
        DependencyObject obj = VisualTreeHelper.GetChild(ww, x) as DependencyObject;

        // Se è un DependencyObject
        if (obj != null)
        {

          // Cerco tutti i fields di
          foreach (FieldInfo finfo in obj.GetType().GetFields())
          {

            // Se il field trovato è una DependencyProperty allora può essere sottoposta a binding
            if (finfo.FieldType.Equals(typeof(DependencyProperty)))
            {

              // Verifico se è sottoposta a binding
              DependencyProperty dpvalue = (DependencyProperty)finfo.GetValue(obj);
              if (BindingOperations.IsDataBound(obj, dpvalue))
              {

                // Estraggo la BindingExpression
                BindingExpression expr = BindingOperations.GetBindingExpression(obj, dpvalue);
               

                // Sollevo il metodo di aggiornamento del binding.
                expr.UpdateTarget();
              }

              // Se il contenuto della DependencyProperty è un DependencyObject allora devo controllare anche tutte DP dell'oggetto contenuto nella DP corrente
              DependencyObject subdpobject = obj.GetValue(dpvalue) as DependencyObject;
              if (subdpobject != null)
                if (!(subdpobject is Visual))
                {
                  foreach (FieldInfo subfinfo in subdpobject.GetType().GetFields())
                  {
                    DependencyProperty subdpvalue = (DependencyProperty)subfinfo.GetValue(subdpobject);
                    if (BindingOperations.IsDataBound(subdpobject, subdpvalue))
                    {
                      BindingExpression subexpr = BindingOperations.GetBindingExpression(subdpobject, subdpvalue);
                      subexpr.UpdateTarget();
                    }
                  }
                }
            }
          }

          // Rendo la funzione ricorsiva in modo da esplorare tutta la VisualTree
          InvalidateBinding(obj);
        }
      }
    }

     

     

    [/code]

  • ObjectDataProvider e databinding su WPF

    Tramite WPF è possibile istanziare in diversi modi un oggetto da utilizzare come sorgente dati per il databinding.
    Nel precedente post "Ancora databinding su WPF" si è visto come (attraverso le risorse della classe Window - Window.Resources) è possibile  dichiarare un oggetto come risorsa statica, e, grazie al tag x:key, qualsiasi controllo è in grado di referenziarlo come sorgente dati, permettendo all'engine di WPF di istanziare l'oggetto richiamando il suo costruttore base.

    In alcuni scenari, nei quali non si vuole perdere l'approccio dichiarativo messo a disposizione da XAML, ma che richiedono comportamenti particolari, come il richiamo di costruttori specializzati o metodi con parametri dell'oggetto, WPF fornice la classe ObjectDataProvider.Questa Classe non fa altro che incapsulare la risorsa statica e fornire gli strumenti per interoperare con essa.

    Riprendiamo la configurazione precedente della risorsa statica mySource che referenziava la classe mySource scritta in c#

    <Window.Resources>

           <local:mySource x:Key="mySource"/>

    </Window.Resources>

    Per incapsulare la risorsa basta aggiungere

    <Window.Resources>

             <local:mySource x:Key="mySource"/>
             <ObjectDataProvider x:Key="dataProvider"  ObjectType="{x:Type local:mySource}"/>

    </Window.Resources>

    Dal codice precedente si vede subito che anche l'ObjectDataProvaider è stato definito come risorsa statica, e che può essere referenziato grazie alla sua chiave dataProvider, definita attraverso il tag x:Key.
    A questo punto  si può effettuare il binding attraverso l'ObjectDataProvider, che si occuperà di istanziare la risorsa mySource che sta incapsulando.
    L'esempio del precedente post diventa così

    <ListView ItemsSource="{Binding Source={StaticResource dataProvider}}">

    Da notare che l'applicazione continua a funzionare come in precedenza.
    Fino ad ora non abbiamo visto nulla di interessante, infatti per vedere le potenzialità di questo oggetto dobbiamo fare qualche modifica alla nostra classe sorgente dati, aggiungendo un secondo costruttore che accetta un parametro di tipo int e un metodo che accetta anche'esso un parametro di tipo int.

    public class mySource : ObservableCollection<mySourceItem>
    {
        public mySource()
       {
        }

        //Nuovo costruttore che accetta un parametro int, rappresentante il numero di item da construire
        public mySource(int param)
       {
           for (int i = 0; i <= param; i++)
          {
              this.Add(new mySourceItem("Customer"+i.ToString(), "Customer"+i.ToString()+" Description"));
           }   
        }
      
      //Metodo che inizializza una nuova classe mySource con un numero di item definito dal paramtro param
        public mySource GetItems(int param)
        {

           mySource _mySourse = new mySource();
           for (int i = 0; i <= param; i++)
           {

              _mySourse.Add(new mySourceItem("Customer" + i.ToString(),
               "Customer" + i.ToString() + " Description"));
           }
           return _mySourse;
        }

    }

    Benché non ha molto senso creare un metodo che realizzi una nuova istanza della stessa classe, è stato aggiunto solo per facilitare questo esempio.

    Utilizzando la proprietà CostructorParameters dell' ObjectDataProvider  che accetta una collezione di oggetti, possiamo stabilire quale costruttore prendere attraverso la firma dei suo parametri.

    L'esempio diventa:

    <Window.Resources>
            <ObjectDataProvider x:Key="dataProvider" ObjectType="{x:Type local:mySource}">
                <ObjectDataProvider.ConstructorParameters>
                    <sys:Int32>4</sys:Int32>
                </ObjectDataProvider.ConstructorParameters>
            </ObjectDataProvider>
    </Window.Resources>

    Ogni parametro è referenziato con il suo tipo, in questo caso Int32, perciò sul nodo root di Window bisogna aggiungere il namespace dei tipi base del Framework; nota che la chiave sys è puramente arbitraria e può essere cambiata a proprio piacimento.

    xmlns:sys="clr-namespace:System;assembly=mscorlib"

    Se mandiamo in esecuzione il programma si noterà che il nostro ListView è costituito da quattro Item, proprio perché ha utilizzato il costruttore custom, seguendo la sequenza dei parametri, passandogli il numero 4.

    Analogamente l'ObjectDataProvider ci fornisce la possibilità di chiamare un metodo della classe che incapsula al suo interno , utilizzando la proprietà MethodName e passandogli  i propri parametri grazie alla proprietà MethodParameters  che accetta una collezione di oggetti.
    L'esempio precedente diventa

    <Window.Resources>
            <ObjectDataProvider x:Key="dataProvider" ObjectType="{x:Type local:mySource}" MethodName="GetItems">
              <ObjectDataProvider.MethodParameters>
                  <sys:Int32>4</sys:Int32>
               </ObjectDataProvider.MethodParameters>
           </ObjectDataProvider>
    </Window.Resources>

    Potete scaricare la soluzione d'esempio realizzata con Visual Studio 2008 :
    ODPDemo.zip

     

  • Un metodo per generare le chiavi di licenze delle nostre applicazioni.

    Premessina

    Come programmatori dotnet, ci troviamo a volte ad affrontare il problema di proteggere le nostre applicazioni da occhi indiscreti che potrebbero curiosare troppo nel nostro codice.
    Il problema che mi sono trovato ad affrontare più volte è quello di proteggere il codice che gestisce la licenza che concedo per le mie applicazioni. Una soluzione potrebbe essere quella di offuscare il codice, oppure potrei appoggiarmi a codice unmanaged, tuttavia sono soluzioni che non mi hanno mai attirato più di tanto e che di fatto non garantiscono un elevato grado di protezione a sguardi esperti.

    La soluzione che mi è venuta in mente è ben diversa dalle soluzioni che "nascondono" il codice per fare sicurezza, al contrario la mia soluzione prevede assoluta trasparenza. Ed è anche il motivo per cui scrivo questo post. Spero che possiate aiutarmi a trovare le falle e rendere ancora più sicuro il tutto.


    Come fare (la teoria)

    Visto che nascondere il codice o criptare licenza e altro è una strada che ho scartato, la cosa più naturare è scrivere il codice di licenza, con tutte le informazioni che sono ad esso legate (come ad esempio:data di attivazione, data di scadenza, numero di utenti, licenziatario, productkey e altro), all interno di un file xml nel path di installazione. (Evitiamo allo smanettone di turno la fatica di doversi cercare il file di licenza)
    Lo scriviamo anche il chiaro in modo da poterlo consultare tranquillamente senza dover ricorrere a tools particolari.
    Inseriamo all interno del file stesso un tag che identifica in modo univoco la macchina per la quale la licenza è stata rilasciata (vedremo in seguito come ottenere queste informazioni)

    Nella nostra applicazione quindi quello che dobbiamo fare è leggere il file di licenza, fare le verifiche del caso per accertarci che il productkey sia valido e che le informazioni all interno della licenza siano corrette, verificare il codice che lega la licenza alla macchina ed eseguire la nostra applicazione.

    Rimane un problema: evitare che qualcuno modifichi a piacimento il file di licenza.

    La soluzione viene quasi da sola. E sufficiente firmare il file XML con una chiave RSA. Chiunque modificherà il file di licenza, corromperà la firma e il file risulterà non più valido.
    Abbiamo sempre a disposizione una chiave RSA che viene gia distribuita all interno dei nostri assembly: la stessa chiave che utilizzamo per firmare i nostri assembly.
    Viene generata da VisualStudio, e ne viene distribuita solo la parte pubblica insieme alla nostra applicazione, la parte privata rimane di nostra esclusiva proprietà.

    Quindi riassumendo quello che dobbiamo fare è:

    1. Creare un file di licenza in fomato XML
    2. Generare un file snk (che contiene sia chiave pubblica che privata)
    3. Firmare i nostri assembly con il file snk
    4. Firmare il file di licenza con il file snk
    5. Distribuire applicazione e file di licenza
    6. Prima di eseguire l applicazione verificare che la firma del file di licenza sia ancora valida.

     

    Come fare (la pratica)

     

    Per prima cosa ci serve una struttura per scrivere il file di licenza, quindi creiamo una classe serializzabile per contenere tutte quest informazioni.

    [code language="c#"]

    [Serializable]
      internal class BaseLicense
      {
        private string _name;
        private object _value;

        public object Value
        {
          get { return _value; }
          set { _value = value; }
        }

        public virtual string ObjectName
        {
          get { return _name; }
          set { _name = value; }
        }
      }

      [Serializable]
      internal class PCInfo : BaseLicense
      {
        private string _signature;

        public string PCSignature
        {
          get { return _signature; }
          set { _signature = value; }
        }
      }

      [Serializable]
      internal class LicenseArchive
      {
        private Dictionary<string, BaseLicense> _licenses = new Dictionary<string, BaseLicense>();
        private byte[] _signature = new byte[0];

        public byte[] Signature
        {
          get { return _signature; }
          set { _signature = value; }
        }

        public Dictionary<string, BaseLicense> Licenses
        {
          get { return _licenses; }
          set { _licenses = value; }
        }
      }

    [/code]

     

    La classe LicenseArchive è il nostro file di licenza, contiene un dictionary e una proprietà pubblica "Signature" che conterrà la firma del file xml stesso. Il dictionary esposto con la proprietà Licenses conterrà tutte le informazioni della licenza.
    La classe LicenseBase è un contenitore che contiene una coppia di informazioni "Chiave-Valore" della licenza. La classe PCInfo arricchisce queste informazioni con una stringa che contiene l identificativo del PC per il quale è stata rilasciata la licenza.
    Vediamo come leggere le informazioni per identificare in modo univoco il PC.

    Possiamo utilizzare WMI (Windows Management Instrumentation) per ottenere le informazioni che ci servono.

    [code language="c#"]

    internal static class MachineInfo
    {

        /// <summary>
        /// return Volume Serial Number from hard drive
        /// </summary>
        /// <param name="strDriveLetter">[optional] Drive letter</param>
        /// <returns>[string] VolumeSerialNumber</returns>
        public static string GetVolumeSerial(string strDriveLetter)
        {
           if (strDriveLetter == "" || strDriveLetter == null) strDriveLetter = "C";
           ManagementObject disk =
               new ManagementObject("win32_logicaldisk.deviceid=\"" + strDriveLetter + ":\"");
           disk.Get();
           return disk["VolumeSerialNumber"].ToString();
        }

        /// <summary>
        /// Returns MAC Address from first Network Card in Computer
        /// </summary>
        /// <returns>[string] MAC Address</returns>
        public static string GetMACAddress()
        {
           ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
           ManagementObjectCollection moc = mc.GetInstances();
           string MACAddress = String.Empty;
           foreach (ManagementObject mo in moc)
           {
              if (MACAddress == String.Empty)  // only return MAC Address from first card
              {
                 if ((bool)mo["IPEnabled"] == true) MACAddress = mo["MacAddress"].ToString();
              }
              mo.Dispose();
           }
           MACAddress = MACAddress.Replace(":", "");
           return MACAddress;
        }


        /// <summary>
        /// Return processorId from first CPU in machine
        /// </summary>
        /// <returns>[string] ProcessorId</returns>
        public static string GetCPUId()
        {
           string cpuInfo = String.Empty;
           string temp = String.Empty;
           ManagementClass mc = new ManagementClass("Win32_Processor");
           ManagementObjectCollection moc = mc.GetInstances();
           foreach (ManagementObject mo in moc)
           {
              if (cpuInfo == String.Empty)
              {// only return cpuInfo from first CPU
                 cpuInfo = mo.Properties["ProcessorId"].Value.ToString();
              }
           }
           return cpuInfo;
        }
    }

    [/code]

    Ci siamo, abbiamo tutto il necessario per generare il nostro file di licenza.

    Vediamo cosa ci serve per firmarlo.
    Ho trovato questa classettina che ci permette di ottenere le chiavi RSA da un file snk e dalla parte dell snk che si trova all interno dell assembly.

     

    [code language="C#"]

    ///
    /// One static method to get an RSACryptoServiceProvider from a *.snk file.
    /// NOTE: These methods assume 1024 bit keys, the same as exported from sn.exe.
    /// See also: CryptExportKey() Win32 API.
    ///
    public sealed class SnkUtil
    {
      private const int magic_priv_idx = 0x08;
      private const int magic_pub_idx = 0x14;
      private const int magic_size = 4;

      private SnkUtil()
      {
      }

      ///
      /// Returns RSA object from *.snk key file.
      ///
      /// Path to snk file.
      /// RSACryptoServiceProvider
      public static RSACryptoServiceProvider GetRSAFromSnkFile(string path)
      {
        if (path == null)
          throw new ArgumentNullException("path");

        byte[] snkBytes = GetFileBytes(path);
        if (snkBytes == null)
          throw new Exception("Invalid SNK file.");

        RSACryptoServiceProvider rsa = GetRSAFromSnkBytes(snkBytes);
        return rsa;
      }

      private static byte[] GetFileBytes(string path)
      {
        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
        using (BinaryReader br = new BinaryReader(fs))
        {
          byte[] bytes = br.ReadBytes((int)fs.Length);
          return bytes;
        }
      }

      public static RSACryptoServiceProvider GetRSAFromSnkBytes(byte[] snkBytes)
      {
        if (snkBytes == null)
          throw new ArgumentNullException("snkBytes");

        RSAParameters param = FigureParams(snkBytes);

        // Must set KeyNumber to AT_SIGNATURE for strong
        // name keypair to be correctly imported.
        CspParameters cp = new CspParameters();
        cp.KeyNumber = 2; // AT_SIGNATURE

        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024, cp);
        rsa.ImportParameters(param);
        return rsa;
      }

      private static byte[] BlockCopy(byte[] source, int idx, int size)
      {
        if ((source == null) || (source.Length < (idx + size)))
          return null;

        byte[] ret = new byte[size];
        Buffer.BlockCopy(source, idx, ret, 0, size);
        return ret;
      }

      ///
      /// Returns true if buffer length is public key size.
      ///
      ///
      ///
      private static bool SnkBufIsPubLength(byte[] keypair)
      {
        if (keypair == null)
          return false;
        return (keypair.Length == 160);
      }

      ///
      /// Check that RSA1 is in header (public key only).
      ///
      ///
      ///
      private static bool CheckRSA1(byte[] pubkey)
      {
        // Check that RSA1 is in header.
        // R S A 1
        byte[] check = new byte[] { 0x52, 0x53, 0x41, 0x31 };
        return CheckMagic(pubkey, check, magic_pub_idx);
      }

      ///
      /// Check that RSA2 is in header (public and private key).
      ///
      ///
      ///
      private static bool CheckRSA2(byte[] pubkey)
      {
        // Check that RSA2 is in header.
        // R S A 2
        byte[] check = new byte[] { 0x52, 0x53, 0x41, 0x32 };
        return CheckMagic(pubkey, check, magic_priv_idx);
      }

      private static bool CheckMagic(byte[] keypair, byte[] check, int idx)
      {
        byte[] magic = BlockCopy(keypair, idx, magic_size);
        if (magic == null)
          return false;

        for (int i = 0; i < magic_size; i++)
        {
          if (checkIdea != magicIdea)
            return false;
        }

        return true;
      }

      ///
      /// Returns RSAParameters from byte[].
      ///
      ///
      ///
      private static RSAParameters FigureParams(byte[] keypair)
      {
        RSAParameters ret = new RSAParameters();

        if ((keypair == null) || (keypair.Length < 1))
          return ret;

        bool pubonly = SnkBufIsPubLength(keypair);

        if ((pubonly) && (!CheckRSA1(keypair)))
          return ret;

        if ((!pubonly) && (!CheckRSA2(keypair)))
          return ret;

        int magic_idx = pubonly ? magic_pub_idx : magic_priv_idx;

        // Bitlen is stored here, but note this
        // class is only set up for 1024 bit length keys
        int bitlen_idx = magic_idx + magic_size;
        int bitlen_size = 4; // DWORD

        // Exponent
        // In read file, will usually be { 1, 0, 1, 0 } or 65537
        int exp_idx = bitlen_idx + bitlen_size;
        int exp_size = 4;

        //BYTE modulus[rsapubkey.bitlen/8]; == MOD; Size 128
        int mod_idx = exp_idx + exp_size;
        int mod_size = 128;

        //BYTE prime1[rsapubkey.bitlen/16]; == P; Size 64
        int p_idx = mod_idx + mod_size;
        int p_size = 64;

        //BYTE prime2[rsapubkey.bitlen/16]; == Q; Size 64
        int q_idx = p_idx + p_size;
        int q_size = 64;

        //BYTE exponent1[rsapubkey.bitlen/16]; == DP; Size 64
        int dp_idx = q_idx + q_size;
        int dp_size = 64;

        //BYTE exponent2[rsapubkey.bitlen/16]; == DQ; Size 64
        int dq_idx = dp_idx + dp_size;
        int dq_size = 64;

        //BYTE coefficient[rsapubkey.bitlen/16]; == InverseQ; Size 64
        int invq_idx = dq_idx + dq_size;
        int invq_size = 64;

        //BYTE privateExponent[rsapubkey.bitlen/8]; == D; Size 128
        int d_idx = invq_idx + invq_size;
        int d_size = 128;

        // Figure public params
        // Must reverse order (little vs. big endian issue)
        ret.Exponent = BlockCopy(keypair, exp_idx, exp_size);
        Array.Reverse(ret.Exponent);
        ret.Modulus = BlockCopy(keypair, mod_idx, mod_size);
        Array.Reverse(ret.Modulus);

        if (pubonly) return ret;

        // Figure private params
        // Must reverse order (little vs. big endian issue)
        ret.P = BlockCopy(keypair, p_idx, p_size);
        Array.Reverse(ret.P);

        ret.Q = BlockCopy(keypair, q_idx, q_size);
        Array.Reverse(ret.Q);

        ret.DP = BlockCopy(keypair, dp_idx, dp_size);
        Array.Reverse(ret.DP);

        ret.DQ = BlockCopy(keypair, dq_idx, dq_size);
        Array.Reverse(ret.DQ);

        ret.InverseQ = BlockCopy(keypair, invq_idx, invq_size);
        Array.Reverse(ret.InverseQ);

        ret.D = BlockCopy(keypair, d_idx, d_size);
        Array.Reverse(ret.D);

        return ret;
      }
    }

    [/code]

     

    Quando avremo ottenuto le chiavi RSA dovremo semplicemente passarle ad un Crypto service provider per fare la firma.
    Daremo in pasto al metodo SignData di RSACryptoServiceProvider i dati xml da firmare.
    In questo caso utilizzeremo il file snk che abbiamo generato per firmare il nostro assembly.

    [code language="C#"]

       RSACryptoServiceProvider rsa = SnkUtil.GetRSAFromSnkFile(snkfilepath);
       byte[] signature;
       signature = rsa.SignData(datatoSign, new SHA1CryptoServiceProvider());

    [/code]

    Per verificare la firma abbiamo solo bisogno della chiave pubblica e non dell intero snk. possiamo ottenere la chiave pubblica direttamente dall assembly.

    [code language="C#"]

    byte[] publickey = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
          if (publickey == null || publickey.Length == 0)
            throw new LicenseException(null, null, "This assembly cannot be licensed because is not strongly named");

          RSACryptoServiceProvider rsa = SnkUtil.GetRSAFromSnkBytes(publickey);
          return rsa.VerifyData(datatoverify, new SHA1CryptoServiceProvider(), signature);

    [/code]

    Questo è tutto. Vi riporto per intero anche le classi che ho scritto per serializzare  e firmare la licenza ( da utilizzare nel nostro license manager), e la classe LicenseReader che inseriremo all interno del nostro client.

    [code language="c#"]

    internal class LicenseReader
    {
       private LicenseArchive _licenses = new LicenseArchive();

       protected LicenseArchive LicensesArchive
       {
         get { return _licenses; }
       }

       public LicenseReader()
       {
         this.LoadLicenses();
       }

       private bool VerifyObjectSign(byte[] datatoverify, byte[] signature)
       {
         byte[] publickey = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
         if (publickey == null || publickey.Length == 0)
           throw new LicenseException(null, null, "This assembly cannot be licensed because is not strongly named");

         RSACryptoServiceProvider rsa = SnkUtil.GetRSAFromSnkBytes(publickey);
         return rsa.VerifyData(datatoverify, new SHA1CryptoServiceProvider(), signature);
       }

       protected void LoadLicenses()
       {
         BinaryFormatter bf = new BinaryFormatter();

         // Get license file path
         string licensepath = Application.StartupPath + @"\" + "TeamDev.Licenses.lic";

         //Search for License File
         if (!File.Exists(licensepath)) return;
         FileStream fs = File.OpenRead(licensepath);

         // Deserialize the license file
         _licenses = (LicenseArchive)bf.Deserialize(fs);
         fs.Close();

         // serialize in a bytestream the Licenses for validation purposes
         MemoryStream ms = new MemoryStream();
         bf.Serialize(ms, _licenses.Licenses);
         byte[] buffer = new byte[ms.Length];
         ms.Position = 0;
         ms.Read(buffer, 0, (int)ms.Length);

         // Check the PC Signature & LicenseFile Signature.
         try
         {
           string pcinfo = string.Empty;

           try { pcinfo += MachineInfo.GetCPUId(); }
           catch { }
           try { pcinfo += MachineInfo.GetVolumeSerial("C"); }
           catch { }

           // Il PCInfo non può essere vuoto.
           if (pcinfo == string.Empty)
             throw new Exception("Invalid License File");

           if ((((PCInfo)_licenses.Licenses["PCINFO"]).PCSignature != pcinfo) ||
              (!VerifyObjectSign(buffer, _licenses.Signature)))
           {
             _licenses.Licenses.Clear();
             _licenses.Signature = new byte[0];

             throw new Exception("Invalid License File");
           }
         }
         catch (Exception ex)
         {
           throw new Exception("Invalid License File", ex);
         }
       }
    }



     

    internal class LicenseProvider : LicenseReader
      {
        private string _snkFilePath = string.Empty;

        public LicenseArchive Archive
        {
          get { return base.LicensesArchive; }
        }

        public string SnkFilePath
        {
          get { return _snkFilePath; }
          set { _snkFilePath = value; }
        }

        // Override Default Constructor because i don't like to load license
        // at startup.
        public LicenseProvider()
        {
          this.ClearLicenses();
        }

        public void ClearLicenses()
        {
          base.LicensesArchive.Licenses.Clear();
          base.LicensesArchive.Signature = new byte[0];
        }

        public void SaveLicenseFile(string filename)
        {
          BinaryFormatter bf = new BinaryFormatter();

          MemoryStream ms = new MemoryStream();
          bf.Serialize(ms, base.LicensesArchive.Licenses);
          byte[] buffer = new byte[ms.Length];
          ms.Position = 0;
          ms.Read(buffer, 0, (int)ms.Length);

          byte[] signature = CreateSignature(buffer,this.SnkFilePath);
          base.LicensesArchive.Signature = signature;

          FileStream fs = File.Create(filename);
          bf.Serialize(fs, base.LicensesArchive);
          fs.Flush();
          fs.Close();
        }

        private byte[] CreateSignature(byte[] datatoSign, string snkfilepath)
        {
          if(!File.Exists(snkfilepath))
            throw new FileNotFoundException("Key Archive not found");

          RSACryptoServiceProvider rsa = SnkUtil.GetRSAFromSnkFile(snkfilepath);
          byte[] signature;
          signature = rsa.SignData(datatoSign, new SHA1CryptoServiceProvider());
          return signature;
        }

        public RSACryptoServiceProvider CreateNewRSAKey()
        {
          RSACryptoServiceProvider rsaprov = new RSACryptoServiceProvider(1024);
          return rsaprov;
        }

        public RSAParameters GetPublicKey(RSACryptoServiceProvider rsaprovider)
        {
          return rsaprovider.ExportParameters(false);
        }

        public RSAParameters GetPrivateKey(RSACryptoServiceProvider rsaprovider)
        {
          return rsaprovider.ExportParameters(true);
        }
      }


    [/code]

    Conclusioni

    Abbiamo ottenuto un gestore di file di licenza per le nostre applicazioni che non necessita di "nascondere" il codice che gestisce il meccanismo di verifica del file di licenza stesso. E quindi non dobbiamo preoccuparci di offuscare nulla.

    Aspetto numerosi vostri commenti.

  • Paginazione Lato Server Parte 3 - Oggetti che non supportano la paginazione con ricerca

    Il seguente articolo si pone come obbiettivo la riutilizzazione delle logiche implementate nei due articoli precedenti (Parte 1 - Oggetti che supportano la paginazioneParte 2 - Ricerca negli oggetti che supportano la paginazione) per fare in modo che esse siano fruite anche da quegli oggetti che non supportano nativamente la paginazione.

    Un esempio classico di applicazione delle suddette logiche potrebbe essere l'uso del Data List, per presentare liste con un accattivante aspetto grafico in quanto altamente personalizzabile dal punto di vista estetico. Pensiamo ad esempio alla nostra lista di prodotti: quale miglior oggetto se non il Data List per presentare i prodotti al pubblico,