<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>Tutorials</title>
        <link>http://blogs.ugidotnet.org/dsenatore/category/Tutorials.aspx</link>
        <description>Tutorials</description>
        <language>it-IT</language>
        <copyright>Davide Senatore</copyright>
        <generator>Subtext Version 2.6.0.0</generator>
        <item>
            <title>Distributed Dataset in uno scenario Multi-Tier [2]</title>
            <link>http://blogs.ugidotnet.org/dsenatore/archive/2009/01/14/distributed-dataset-in-uno-scenario-multi-tier-2.aspx</link>
            <description>Riprendendo il discorso lasciato in un mio precedente post, vediamo di creare una soluzione che ci faccia capire come i Dataset Distribuiti possano essere utilizzati per creare soluzioni che impieghino questa tecnica per l'accesso ai dati.
Lavoreremo con un Database di nome ECommerce. La struttura è volutamente molto semplice (solo 2 tabelle) perchè non è importante complicare lo schema del database per comprendere come il Dataset Distribuito possa essere utilizzato per i nostri fini. Le tabelle sono Order e OrderDetail, collegate con una relazione uno a molti.
Procediamo creando un progetto di nome CodeSapiens.DataAccess, in cui creeremo il nostro dataset di nome DataContext.xsd, ed una classe DataManager che incapsulerà i metodi utilizzati per accedere ai dati. Dovremo creare contestualmente anche un progetto "Class Library" di nome CodeSapiens.Entities. Questo progetto verrà utilizzato indicando nel Dataset il "DataSet Project", ovvero il progetto in cui il dataset genererà le classi "entità". Come abbiamo già visto nel precedente post, infatti, il dataset distribuito separa nettamente l'accesso ai dati dalla parte di "entità".
Altra proprietà a cui fare attenzione nel Dataset è la "Hierarchical Update". La troveremo già impostata a True, e questo indica che il generatore di codice avrà generato per noi una classe TableAdapterManager ed un metodo UpdateAll che avrà come parametro un'istanza del nostro Dataset. Questo metodo consente di aggiornare tutte le tabelle del Dataset rispettando l'ordine delle relazioni tra le tabelle che compongono il dataset. In precedenza eravamo noi che dovevamo sopperire all'ordinamento degli insert/update/delete effettuati dal dataset. Per maggiori approfondimenti riguardanti questa interessante caratteristica vi invito a consultare questo riferimento.
Nel pezzo di codice seguente possiamo vedere i metodi della classe DataManager, la quale si occuperà di implementare i metodi ad alto livello che nasconderanno le particolari implementazioni a livello TableAdapter, stringa di connessione ed utilizzo del Dataset per quanto riguarda l'accesso ai dati. In pratica si tratterà di scrivere dei metodi che accettino in input tipi base del Framework e/o classi delle Entities derivate dal Dataset DataContext già creato nel nostro progetto.


   1: /// &amp;lt;summary&amp;gt;
   2: /// Salva un ordine
   3: /// &amp;lt;/summary&amp;gt;
   4: /// &amp;lt;param name="order"&amp;gt;Dataset che rappresenta l'ordine&amp;lt;/param&amp;gt;        
   5: public void SaveOrder(DataContext order)
   6: {
   7:     if (order != null)
   8:     {
   9:         CodeSapiens.DataAccess.DataContextTableAdapters.TableAdapterManager _ta = new CodeSapiens.DataAccess.DataContextTableAdapters.TableAdapterManager();
  10:         _ta.OrderTableAdapter = new CodeSapiens.DataAccess.DataContextTableAdapters.OrderTableAdapter();
  11:         _ta.OrderDetailTableAdapter = new CodeSapiens.DataAccess.DataContextTableAdapters.OrderDetailTableAdapter();
  12:         _ta.Connection.ConnectionString = _connectionString;
  13:         _ta.UpdateAll(order);
  14:     }
  15:     else
  16:     {
  17:         throw new Exception("Order can't be null.");
  18:     }
  19: }
  20:  
  21: /// &amp;lt;summary&amp;gt;
  22: /// Carica un ordine
  23: /// &amp;lt;/summary&amp;gt;
  24: /// &amp;lt;param name="orderId"&amp;gt;Id interno (guid) dell'ordine&amp;lt;/param&amp;gt;
  25: /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
  26: public DataContext LoadOrder(Guid orderId) 
  27: {
  28:     DataContext _context = new DataContext();
  29:     DataContextTableAdapters.OrderTableAdapter _orderAdapter = new DataContextTableAdapters.OrderTableAdapter();
  30:     _orderAdapter.Connection.ConnectionString = _connectionString;
  31:     DataContextTableAdapters.OrderDetailTableAdapter _orderDetailAdapter = new DataContextTableAdapters.OrderDetailTableAdapter();
  32:     _orderDetailAdapter.Connection.ConnectionString = _connectionString;
  33:     _orderAdapter.FillByOrderId(_context.Order, orderId);
  34:     _orderDetailAdapter.FillByOrderId(_context.OrderDetail, orderId);
  35:     return _context;
  36: }


Notiamo come tutto il codice sia privo di istruzioni SQL. Queste istruzioni sono memorizzate all'interno dei Table Adapters a livello di Dataset, e sono istruzioni SQL che abbiamo scritto personalmente. Vediamo dei fatti derivanti da questo approccio:

    Posso scrivere il codice SQL ESATTAMENTE come voglio, ottimizzando così le mie query 
    Devo scrivere il codice di tutte le query, mentre con altri approcci questo viene autogenerato (a run time o a design time) 
    Ho un unico punto in cui scrivere tutto il codice SQL 
    Posso usare delle Stored Procedures se voglio implementare un accesso ai dati tramite esse 

In questo post utilizzeremo la classe DataManager in un progetto a due livelli, ovvero Client-Server. Creiamo quindi un progetto WinForm per consumare il nostro dataset. Al progetto aggiungeremo come riferimenti il progetto CodeSapiens.DataAccess e CodeSapiens.Entities e scriviamo una semplice interfaccia composta da due finestre, la principale con la lista degli ordini e la seconda da utilizzare per la modifica/creazione del singolo ordine.
Per la gestione dell'associazione dati-interfaccia impiegheremo il Databinding a design time di Visual Studio. A questo riguardo, dobbiamo fare una precisazione: tutto il databinding viene fatto tramite le entities del progetto Codesapiens.Entities, e non tramite il Dataset DataContext. Questo è molto importante, perchè in pratica crea un legame tra l'interfaccia e queste entità e non tramite l'interfaccia e l'accesso ai dati.


   1: public partial class MainForm : Form
   2: {
   3:     public MainForm()
   4:     {
   5:         InitializeComponent();
   6:     }
   7:  
   8:     private void Form1_Load(object sender, EventArgs e)
   9:     {
  10:         LoadOrders();
  11:     }
  12:  
  13:     private void LoadOrders()
  14:     {
  15:         CodeSapiens.DataAccess.DataManager _dm = new CodeSapiens.DataAccess.DataManager();
  16:         dataContext_OrderDataTableBindingSource.DataSource = _dm.LoadOrders().Order;        
  17:     }
  18:     
  19:     private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e)
  20:     {
  21:         EditOrderDialog _dialog = new EditOrderDialog();
  22:         if (_dialog.CreateNewOrder() == DialogResult.OK)
  23:         {
  24:             LoadOrders();
  25:         }
  26:     }
  27:  
  28:     private void dgvOrders_DoubleClick(object sender, EventArgs e)
  29:     {
  30:         EditOrderDialog _dialog = new EditOrderDialog();
  31:         if (_dialog.EditOrder(((DataContext.OrderRow)((DataRowView)dgvOrders.SelectedRows[0].DataBoundItem).Row).Id) == DialogResult.OK)
  32:         {
  33:             LoadOrders();
  34:         }
  35:     }
  36: }


La form principale è estremamente semplice in quanto deve solamente caricare la lista degli ordini e permettere di andare in modifica o creazione di un nuovo ordine.


   1: public DialogResult CreateNewOrder()
   2: {
   3:   dataContext.OrderDetail.TableNewRow += new DataTableNewRowEventHandler(OrderDetail_TableNewRow);
   4:   DataContext.OrderRow _newOrder = dataContext.Order.NewOrderRow();            
   5:   _newOrder.Id = Guid.NewGuid();
   6:   _newOrder.OrderDate = DateTime.Today;
   7:   _newOrder.ShipmentAddress = string.Empty;
   8:   _newOrder.CustomerAddress = string.Empty;
   9:   dataContext.Order.AddOrderRow(_newOrder);
  10:   return ShowDialog();
  11: }
  12:  
  13: public DialogResult EditOrder(Guid orderId)
  14: {
  15:   dataContext.OrderDetail.TableNewRow += new DataTableNewRowEventHandler(OrderDetail_TableNewRow);
  16:   dataContext.Merge(_dm.LoadOrder(orderId));
  17:   return ShowDialog();
  18: }
  19:  
  20: void OrderDetail_TableNewRow(object sender, DataTableNewRowEventArgs e)
  21: {
  22:   ((DataContext.OrderDetailRow)e.Row).Id = Guid.NewGuid();
  23: }
  24:  
  25: private void btnSaveAndClose_Click(object sender, EventArgs e)
  26: {            
  27:   orderBindingSource.EndEdit();
  28:   orderDetailDataGridView.EndEdit();
  29:   orderDetailBindingSource.EndEdit();
  30:   _dm.SaveOrder((DataContext)dataContext.GetChanges());
  31:   this.DialogResult = DialogResult.OK;
  32: }


La form per la creazione/modifica ordine è anch'essa molto semplice, e l'accesso ai dati è mantenuto al minimo. In questo esempio non sono state gestite problematiche riguardanti concorrenza, possibili errori dovuti a errori sul database e tutto è stato semplificato per far emergere l'utilizzo del dataset distribuito.
A questo punto possiamo fare un riassunto e vedere che cosa abbiamo guadagnato con questo approccio, considerato che stiamo realizzando un Client-Server:

    Se cambiamo qualcosa sul database, è sufficiente aggiornare il Dataset per vedere propagate le modifiche anche a livello di entità 
    Utilizzando il databinding di Visual Studio a Design-Time abbiamo a disposizione un eccezionale strumento che ci assicura un'ottima produttività 
    Possiamo accedere anche a database non-SQL Server (Oracle, Sybase, ODBC generici), contrariamente a LINQ2SQL 
    Non dobbiamo gestire a mano l'entity tracking, in quanto il dataset lo fa per noi 
    Utilizzando i guid abbiamo risolto il problema della gestione dei campi identity sul database. Inoltre vedremo che in ambiente distribuito questa si rivelerà una scelta felice 
    Non ci dobbiamo preoccupare dell'ordine dell'aggiornamento delle tabelle, in quanto il TableAdapterManager con le opportune configurazioni segue l'ordine di Update/Insert/Delete corretto 
    Le classi delle entities, come quelle per l'accesso ai dati (TableAdapters), possono essere estese mediante l'uso delle partial class, permettendo così di implementare funzioni con visibilità limitata al progetto in cui si trovano 

Ovviamente, come già detto nell'articolo precedente, questo approccio è da tenere in considerazione per applicazioni RAD di dimensioni medio/piccole. Non deve mai essere sottovalutato il fatto che comunicando attraverso le entità del dataset stiamo legando il modello (database) alla rappresentazione (interfaccia). Anche se questo può essere ricorrente e accettabile in un numero molto elevato di applicazioni (tipicamente proprio in quelle medio piccole con requisiti di tempistiche molto strette), in applicazioni enterprise di grosso livello può non essere consigliabile. Nel prossimo post relativo a questo argomento vedremo come utilizzare un servizio WCF per esporre le entità e come modificare l'applicazione Client-Server per farle utilizzare i servizi, piuttosto che accedere direttamente al database. Il codice sorgente dell'applicazione è scaricabile a questo indirizzo.&lt;img src="http://blogs.ugidotnet.org/dsenatore/aggbug/95191.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Davide Senatore</dc:creator>
            <guid>http://blogs.ugidotnet.org/dsenatore/archive/2009/01/14/distributed-dataset-in-uno-scenario-multi-tier-2.aspx</guid>
            <pubDate>Wed, 14 Jan 2009 00:51:03 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/dsenatore/archive/2009/01/14/distributed-dataset-in-uno-scenario-multi-tier-2.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/dsenatore/comments/commentRss/95191.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/dsenatore/services/trackbacks/95191.aspx</trackback:ping>
        </item>
        <item>
            <title>Scrivere un control class converter per VB6(2)XAML</title>
            <link>http://blogs.ugidotnet.org/dsenatore/archive/2006/11/29/57910.aspx</link>
            <description>Per tutti coloro i quali sono interessati all'utilizzo ed alla personalizzazione del tools VB6(2)XAML, pubblico un esempio di come possa essere possibile ridefinire il rendering dell'elemento VB.Label con una classe proprietaria. Dopo aver creato un progetto di tipo ClassLibrary, possiamo aggiungere la referenza a VB62XAML.exe nella quale si trova la classe VBControl, dalla quale dovremo ereditare. Implementando un override del metodo RenderToWPF possiamo definire come renderizzare il nostro controllo Label. Nell'esempio che ho riportato sotto, è possibile osservare una tecnica basata sulla creazione "manuale" degli elementi, ed un'altra basata sull'utilizzo di templates che magari possono essere caricati da disco. Imports&amp;nbsp;System.XmlImports&amp;nbsp;CodeSapiens.VB62XAMLPublic&amp;nbsp;Class&amp;nbsp;MyLabel&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Inherits&amp;nbsp;CodeSapiens.VB62XAML.VBControl&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'''&amp;nbsp;&amp;lt;summary&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'''&amp;nbsp;Override&amp;nbsp;base&amp;nbsp;function&amp;nbsp;RenderToWPF&amp;nbsp;and&amp;nbsp;replace&amp;nbsp;with&amp;nbsp;new&amp;nbsp;rendering&amp;nbsp;attributes&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'''&amp;nbsp;&amp;lt;/summary&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'''&amp;nbsp;&amp;lt;param&amp;nbsp;name="document"&amp;gt;The&amp;nbsp;XML&amp;nbsp;document&amp;nbsp;that&amp;nbsp;parser&amp;nbsp;passes&amp;nbsp;to&amp;nbsp;classes&amp;lt;/param&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'''&amp;nbsp;&amp;lt;returns&amp;gt;XMLElement&amp;nbsp;representing&amp;nbsp;the&amp;nbsp;control&amp;nbsp;in&amp;nbsp;XML&amp;nbsp;Format&amp;lt;/returns&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'''&amp;nbsp;&amp;lt;remarks&amp;gt;&amp;lt;/remarks&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Public&amp;nbsp;Overrides&amp;nbsp;Function&amp;nbsp;RenderToWPF(ByVal&amp;nbsp;document&amp;nbsp;As&amp;nbsp;System.Xml.XmlDocument)&amp;nbsp;As&amp;nbsp;System.Xml.XmlElement&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'Create&amp;nbsp;element&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dim&amp;nbsp;_item&amp;nbsp;As&amp;nbsp;XmlElement&amp;nbsp;=&amp;nbsp;document.CreateElement("Label")&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'Set&amp;nbsp;base&amp;nbsp;positioning&amp;nbsp;attributes&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Name",&amp;nbsp;Helpers.BuildName(Me.Name,&amp;nbsp;Me.Properties))&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Canvas.Left",&amp;nbsp;Helpers.TwipsToPixels(Convert.ToInt32(Me.Properties.GetProperty("Left").Value)))&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Canvas.Top",&amp;nbsp;Helpers.TwipsToPixels(Convert.ToInt32(Me.Properties.GetProperty("Top").Value)))&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Width",&amp;nbsp;Helpers.TwipsToPixels(Convert.ToInt32(Me.Properties.GetProperty("Width").Value)))&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Height",&amp;nbsp;Helpers.TwipsToPixels(Convert.ToInt32(Me.Properties.GetProperty("Height").Value)))&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Content",&amp;nbsp;Me.Properties.GetProperty("Caption").Value)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.SetAttribute("Padding",&amp;nbsp;"0,0,0,0")&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'Create&amp;nbsp;a&amp;nbsp;document&amp;nbsp;fragment&amp;nbsp;to&amp;nbsp;show&amp;nbsp;another&amp;nbsp;manner&amp;nbsp;of&amp;nbsp;embedding&amp;nbsp;XML&amp;nbsp;in&amp;nbsp;Control&amp;nbsp;Rendering&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dim&amp;nbsp;_docFrag&amp;nbsp;As&amp;nbsp;XmlDocumentFragment&amp;nbsp;=&amp;nbsp;document.CreateDocumentFragment()&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Dim&amp;nbsp;_gradientTemplate&amp;nbsp;As&amp;nbsp;String&amp;nbsp;=&amp;nbsp;String.Empty&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'&amp;nbsp;This&amp;nbsp;string&amp;nbsp;could&amp;nbsp;be&amp;nbsp;loaded&amp;nbsp;from&amp;nbsp;disk,&amp;nbsp;and&amp;nbsp;maybe&amp;nbsp;values&amp;nbsp;could&amp;nbsp;be&amp;nbsp;placeholders&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'&amp;nbsp;so&amp;nbsp;we&amp;nbsp;can&amp;nbsp;substitute&amp;nbsp;them&amp;nbsp;at&amp;nbsp;run-time&amp;nbsp;using&amp;nbsp;our&amp;nbsp;special&amp;nbsp;values...&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_gradientTemplate&amp;nbsp;=&amp;nbsp;"&amp;lt;Label.Background&amp;gt;"&amp;nbsp;&amp;amp;&amp;nbsp;_&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;LinearGradientBrush&amp;nbsp;StartPoint=""0.5,0""&amp;nbsp;EndPoint=""0.5,1""&amp;gt;"&amp;nbsp;&amp;amp;&amp;nbsp;_&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;GradientStop&amp;nbsp;Offset=""0.0""&amp;nbsp;Color=""LightBlue""/&amp;gt;"&amp;nbsp;&amp;amp;&amp;nbsp;_&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;GradientStop&amp;nbsp;Offset=""1.0""&amp;nbsp;Color=""SlateBlue""/&amp;gt;"&amp;nbsp;&amp;amp;&amp;nbsp;_&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/LinearGradientBrush&amp;gt;"&amp;nbsp;&amp;amp;&amp;nbsp;_&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"&amp;lt;/Label.Background&amp;gt;"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_docFrag.InnerXml&amp;nbsp;=&amp;nbsp;_gradientTemplate&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_item.AppendChild(_docFrag)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'Handle&amp;nbsp;font&amp;nbsp;generation&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;If&amp;nbsp;Me.Properties.ContainsProperty("Font")&amp;nbsp;Then&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Me.RenderFont(Me.Properties.GetProperty("Font"),&amp;nbsp;_item)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;End&amp;nbsp;If&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Return&amp;nbsp;_item&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;End&amp;nbsp;FunctionEnd&amp;nbsp;Class Uno dei punti fondamentali, comunque, è rappresentato dalla "dichiarazione" fatta nel file .config dell'applicazione, del class converter che utilizzerà il controllo Label: &amp;lt;!--&amp;lt;add&amp;nbsp;key="VB.Label"&amp;nbsp;value="VB62XAML,&amp;nbsp;CodeSapiens.VB62XAML.VBLabel"/&amp;gt;--&amp;gt;&amp;lt;add&amp;nbsp;key="VB.Label"&amp;nbsp;value="CustomControls,&amp;nbsp;CustomControls.MyLabel"/&amp;gt;&lt;img src="http://blogs.ugidotnet.org/dsenatore/aggbug/57910.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Davide Senatore</dc:creator>
            <guid>http://blogs.ugidotnet.org/dsenatore/archive/2006/11/29/57910.aspx</guid>
            <pubDate>Wed, 29 Nov 2006 17:05:00 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/dsenatore/archive/2006/11/29/57910.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/dsenatore/comments/commentRss/57910.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/dsenatore/services/trackbacks/57910.aspx</trackback:ping>
        </item>
        <item>
            <title>Articolo su WSE 3.0 e ASP.net Membership API</title>
            <link>http://blogs.ugidotnet.org/dsenatore/archive/2006/10/10/50780.aspx</link>
            <description>Con mio grande piacere, oggi è stato pubblicato il mio ultimo articolo sul sito di XeDotNet. Nell'articolo in questione si parla di autenticazione ed autorizzazione tramite WSE 3.0 e Membership API. Grazie all'approccio trattato si può realizzare un sistema Single Sign On tra Web Services ed applicazioni ASP.net, condividendo l'identità dell'utente tra le varie applicazioni. Lo potete trovare qui.&lt;img src="http://blogs.ugidotnet.org/dsenatore/aggbug/50780.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Davide Senatore</dc:creator>
            <guid>http://blogs.ugidotnet.org/dsenatore/archive/2006/10/10/50780.aspx</guid>
            <pubDate>Tue, 10 Oct 2006 12:00:00 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/dsenatore/archive/2006/10/10/50780.aspx#feedback</comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/dsenatore/comments/commentRss/50780.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/dsenatore/services/trackbacks/50780.aspx</trackback:ping>
        </item>
        <item>
            <title>Single Sign-On con ASP.net 2.0 e Membership API</title>
            <link>http://blogs.ugidotnet.org/dsenatore/archive/2006/06/07/42459.aspx</link>
            <description>Spesso mi è capitato di imbattermi nel problema di avere più applicazioni web che permettano agli stessi utenti di autenticarsi e di lavorare liberamente al loro interno. Ancora più spesso mi sono imbattuto nella difficoltà di realizzare tale obiettivo, soprattutto per via del fatto che ogni applicazione web implementava l'autenticazione a modo suo, con risultati spesso discutibili dal punto di vista della sicurezza.Molte volte ci siamo trovati a valutare come sistema di Single Sign-On l'autenticazione su Active Directory, ma questa è fattibile soprattutto in ambienti intranet e non in un ambiente internet, dove magari i nostri utenti possono addirittura auto-registrarsi. L'eleganza della Membership API permette di sorpassare ogni problema, raggiungendo l'obiettivo di condividere le credenziali dell'utente attraverso più applicazioni web. Ovvero si potrà, ad esempio, creare un'applicazione web dedicata alle vendite e, successivamente, un'applicazione web dedicata alla reportistica. Queste due applicazioni potranno condividere le informazioni relative agli&amp;nbsp;utenti ed ai loro ruoli, in modo da uniformare la gestione della sicurezza.Ora, come implementare una feature così appetibile? Beh, vi assicuro che è più facile farlo che spiegarlo ...Come prima cosa, dobbiamo evitare di usare il "classico" database SQL express che il buon vecchi VS2005 crea automaticamente per noi. Piuttosto, creiamo un database che fungerà da repository condiviso di credenziali (Shared Credential Repository, SCR), mediante il tool aspnet_regsql.exe, che possiamo trovare nella directory %WINDOWS%\Microsoft.NET\Framework\v2.0.50727. Lasciando come &amp;lt;default&amp;gt; la selezione del database, creeremo un DB di nome ASPNETDB nel nostro server SQL che potrà essere utilizzato per i nostri fini. A questo punto, visto che siamo nei paraggi, diamo un'occhiatina al file Machine.config che si trova nella sottodirectory CONFIG. Al suo interno possiamo trovare le seguenti parti "interessanti":&amp;lt;connectionStrings&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;add&amp;nbsp;name="LocalSqlServer"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connectionString="data&amp;nbsp;source=.\SQLEXPRESS;Integrated&amp;nbsp;Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User&amp;nbsp;Instance=true"&amp;nbsp;providerName="System.Data.SqlClient"&amp;nbsp;/&amp;gt;&amp;lt;/connectionStrings&amp;gt;...snip...&amp;lt;membership&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;providers&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;add&amp;nbsp;name="AspNetSqlMembershipProvider"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type="System.Web.Security.SqlMembershipProvider,&amp;nbsp;System.Web,&amp;nbsp;Version=2.0.0.0,&amp;nbsp;Culture=neutral,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PublicKeyToken=b03f5f7f11d50a3a"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;connectionStringName="LocalSqlServer"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enablePasswordRetrieval="false"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enablePasswordReset="true"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requiresQuestionAndAnswer="true"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;applicationName="/"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requiresUniqueEmail="false"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;passwordFormat="Hashed"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;maxInvalidPasswordAttempts="5"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;minRequiredPasswordLength="7"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;minRequiredNonalphanumericCharacters="1"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;passwordAttemptWindow="10"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;passwordStrengthRegularExpression=""&amp;nbsp;/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/providers&amp;gt;&amp;lt;/membership&amp;gt;...snip...Il punto su cui ragionare, in vista delle nostre applicazioni, è la ConnectionString "LocalSqlServer" e come questa viene utilizzata dal provider AspNetSqlMembershipProvider. Noi vogliamo mantenere questo provider senza apportare modifiche in ciascuna applicazione web, in modo che modificando il comportamento della membership all'interno del Machine.config, tutte le applicazioni installate sul nostro server beneficino del comportamento. Del resto è anche una questione di coerenza nelle policies di sicurezza, giusto? Preso atto di questo, creiamo una applicazione web di nome SalesApp, aggiungiamo un paio di pagine Login.aspx e Register.aspx nelle quali aggiungiamo, rispettivamente, un controllo Login ed un controllo User Creation Wizard. Nella pagina&amp;nbsp;Default.aspx aggiungiamo un controllo LoginStatus e un controllo LoginName. Il pezzo forte di tutto questo, comunque è il file Web.config, che deve essere così composto:&amp;lt;?xml&amp;nbsp;version="1.0"?&amp;gt;&amp;lt;configuration&amp;gt;&amp;nbsp;&amp;nbsp;&amp;lt;appSettings/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;lt;connectionStrings&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;clear/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--configure&amp;nbsp;Shared&amp;nbsp;Credential&amp;nbsp;Repository--&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;add&amp;nbsp;name="LocalSqlServer"&amp;nbsp;connectionString="SERVER=MYSERVER;DATABASE=ASPNETDB;UID=sa;PWD=password"&amp;nbsp;providerName="System.Data.SqlClient"&amp;nbsp;/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;lt;/connectionStrings&amp;gt;&amp;nbsp;&amp;nbsp;&amp;lt;system.web&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;compilation&amp;nbsp;debug="false"&amp;nbsp;strict="false"&amp;nbsp;explicit="true"&amp;nbsp;/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;authentication&amp;nbsp;mode="Forms"&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--configure&amp;nbsp;authentication&amp;nbsp;element:&amp;nbsp;name,&amp;nbsp;path,&amp;nbsp;protection--&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;forms&amp;nbsp;name=".SHAREDTOKEN"&amp;nbsp;loginUrl="login.aspx"&amp;nbsp;protection="All"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout="30"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path="/"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;requireSSL="false"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;slidingExpiration="true"&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cookieless="UseCookies"/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/authentication&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--configure&amp;nbsp;machineKey.&amp;nbsp;Must&amp;nbsp;be&amp;nbsp;the&amp;nbsp;same&amp;nbsp;for&amp;nbsp;each&amp;nbsp;application!!--&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;machineKey&amp;nbsp;validationKey="95E209A1882F59B1585FD2691BB0AEF28E8158C1CD26EADA9B2B3CF8821F2D3D39347B487F72D94E438E4EE510A730FE979A3E79D18A5DB453FE87979D25AACC"&amp;nbsp;decryptionKey="B753AE2B2CEAFD2BA6B938744ECD8550ECE5F6BB51506E43"&amp;nbsp;validation="SHA1"&amp;nbsp;/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;!--deny&amp;nbsp;access&amp;nbsp;to&amp;nbsp;unauthenticated&amp;nbsp;users--&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;authorization&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;deny&amp;nbsp;users="?"&amp;nbsp;/&amp;gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/authorization&amp;gt;&amp;nbsp;&amp;nbsp;&amp;lt;/system.web&amp;gt;&amp;lt;/configuration&amp;gt;Esaminiamo, dunque, questo file, fondamentale per il buon funzionamento della nostra "Shared-Membership"; ho modificato la stringa di connessione LocalSqlServer, in modo da farla puntare allo Shared Credential Repository creato in precedenza. Nell'elemento authentication si può vedere l'attibuto mode, impostato a Forms, ed all'interno, l'elemento forms configurato con un name, un path ed una protection ben definite. Dobbiamo fare attenzione a questi valori, perchè dovranno essere uguali in ogni applicazione che vogliamo far entrare nella nostra Shared Membership (nel club, per intenderci...). Oltre a questa impostazione, un altro elemento deve essere identico in tutte le nostre web application, ed è l'elemento machineKey. I tre attributi validationKey, decryptionKey e validation devono essere identici sempre per tutte le web app del club. Per generare valori validi riferitevi a MachineKey Generator. A questo punto, il gioco è fatto! Per creare il primo utente, rimuovete il deny users="?" in modo da poter navigare la pagina register.aspx e create l'utente. Questo verrà creato all'interno del repository condiviso, e sarà a disposizione di tutte le applicazioni che utilizzeranno questo pattern di autenticazione condivisa. Ripristinando il deny users, la cosa simpatica è che se create un'applicazione di nome ReportApp, copiate il file web.config dalla SalesApp&amp;nbsp;alla ReportApp, create un link che dalla SalesApp punti alla ReportApp e vi autenticate nella SalesApp, una volta giunti alla ReportApp, questa vi "riconoscerà" e nella pagina di default comparirà il nome del vostro utente. Stiamo quindi lavorando con un utente in un'applicazione web, essendoci autenticati presso un'altra applicazione. Estremizzando, potremmo creare un'applicazione per l'autenticazione che rinvia ad altre applicazioni concrete che implementino le funzionalità desiderate. A questo indirizzo potete trovare un semplice progetto con due applicazioni web per testare questa feature estremamente potente di ASP.net 2.0.Nel caso in cui vogliate riportare le impostazioni del provider nel Web.Config, fate attenzione al nome dell'applicazione! Se volete condividere Membership Ruoli e Profili tra le applicazioni, dovete impostare l'attributo name allo stesso valore per ogni applicazione!Happy authenticating!&amp;nbsp;
&lt;img src="http://blogs.ugidotnet.org/dsenatore/aggbug/42459.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Davide Senatore</dc:creator>
            <guid>http://blogs.ugidotnet.org/dsenatore/archive/2006/06/07/42459.aspx</guid>
            <pubDate>Wed, 07 Jun 2006 18:15:00 GMT</pubDate>
            <comments>http://blogs.ugidotnet.org/dsenatore/archive/2006/06/07/42459.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://blogs.ugidotnet.org/dsenatore/comments/commentRss/42459.aspx</wfw:commentRss>
            <trackback:ping>http://blogs.ugidotnet.org/dsenatore/services/trackbacks/42459.aspx</trackback:ping>
        </item>
    </channel>
</rss>