Dal blog di Giorgio Sardo arriva la notizia che sono stati pubblicati i controlli ASP.NET per IE8. Questi controlli dovrebbero supportare lo sviluppatore ad implementare nei propri siti le feature specifiche di IE8 quali acceleratoes, webslice, etc.

Questi sono i controlli inclusi:

  • ASP.NET Web Slice control
  • ASP.NET Accelerator control
  • ASP.NET Visual Search control
  • ASP.NET Browser Helper control
  • Link: ASP.Net Controls for IE8 released!

     


    In ASP.NET 3.5 è stata introdotta la nuova LinqDataSource che consente di fare delle query direttamente su una sorgente dati LinqToSQL. Durante una prova mi sono reso conto che la conversione del tipo del dato fornito dalla proprietà SelectedValue di una ListView falliva con il seguente errore:

    Operator '==' incompatible with operand types 'Int32' and 'Object'

    Il problema è dato dal fatto che la proprietà SelectedValue è di tipo object mentre MeetingId è di tipo Int32, così come specificato dal ControlParameter. In tale caso il runtime non è in grado di operare la conversione e di conseguenza solleva questa eccezione.

    Per ovviare al problema occorre usare un operatore di conversione Int32() nella clausola Where della LinqDataSource come segue:

    <asp:LinqDataSource ID="lds" runat="server" ContextTypeName="MeetingSummary.XeDotNetDataContext" TableName="Subscriptions" Where="MeetingId == Int32?(@MeetingId)"> <WhereParameters> <asp:ControlParameter ControlID="meetingsListView" Name="MeetingId" PropertyName="SelectedValue" Type="Int32" /> </WhereParameters> </asp:LinqDataSource>

    Dato che MeetingId è una proprietà Nullable è anche necessario specificare l'operatore ? che indica appunto l'uso di parametri nullabili. Ecco quindi che il nostro operatore diventa Int32?().

    Per le conversioni sono disponibili gli operatori per tutti i tipi che lo richiedono: Object, Boolean, Char, String, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Decimal, Single, Double, DateTime, TimeSpan e Guid


    Eilon Lipton: Un post molto lucido e chiaro che spiega molti aspetti innovativi della preview che è stata pubblicata nei giorni scorsi. Si parla di MVC, di Unit Testing, di estensibilità e di tutta una serie di punti che non ho ancora letto da nessun'altra parte.

    Link:

    Marcin Dobosz: Una spiegazione di come associare metadata al proprio data model con l'intento di avvalersi delle capacità di validazione e formattazione dei Dynamic Data Controls.

    Link:

    Scott Guthrie: Basterebbe il nome e non servirebbe aggiungere altro... comunque si tratta di un ennesimo post su MVC framework che spiega come gestire il post di dati e l'edit.

    Link:

    Phil Haack: MVC è nato anche per supportare al meglio scenari di Test Driven Development. In questo post Phil mostra come procedere per testare il proprio progetto.

    Link:


    IMAGE_038.jpgSono alla sessione interattiva di Matt Gibbs (Development Manager di ASP.NET) sul nuovo framework MVC.

    La prima notizia che Matt ha dato, fuori argomento ma interessante, è che le applicazioni potranno ASP.NET 2.0 migrare senza problemi alla 3.5

    Il team di ASP.NET sta attualmente lavorando alla fase di test di questo prodotto che è considerato feature-complete. Lo scopo di MVC è di garantire la massima separazione tra logica e interfaccia e miglorare la navigazione.

    Il sistema è composta da View collegate tra loro da Routes che descrivono la navigazione.

    Una delle caratteristiche importanti e che questo sistema consente di facilitare il testing delle applicazioni anche conl'uso di Mocks e di strumenti di test automatizzati. I questo modo diventa applicabile lo sviluppo TTD

    Il sistema consente di generare anche immagini oltre che semplici pagine HTML.

    Peccato che la versione attuale non sia in grado di supportare ajax e che non disponga di controlli pensati per lavorare con questo paradigma.


    Stephan Schacow sta introducendo una serie di tips, alcuni dei quali già conosciuti, sulla ottimizzazione di AJAX

    1) Usate sempre UpdateMode="Conditional" e ChildrenAsTrigger="False" per minimizzare la quantità di dati trasferiti e il lavoro del server. In caso di necessità usate il metodo Update() degli UpdatePanel da aggiornare in cascata.

    2) L'uso dell'object model client-side consente un miglior controllo del partial rendering.

    3) come ovvio, l'uso di webservices per il partial rendering è preferibile perché evita il transito della viewstate e non richiede che il cliclo di vita della pagina asp.net venga eseguito

    In definitiva nulla di nuovo. Rinfrescare le idee ogni tanto è utile ma stavolta era davvero troppo e me ne sono andato prima della fine della sessione.


    Con l'avvento di ASP.NET AJAX 1.0, l'uso dei webservice asmx per popolare contenuti delle pagine in modo asincrono, almeno nel mio caso sta godendo di un notevole favore. Trovo davvero proficuo l'uso di questo metodo perchè alleggerisce notevolmente il roundtrip verso il server facendo transitare il minimo indispensabile. Credo ormai tutti sappiano come si fa a mettere una referenza e a sfruttare un webservice, perciò non tornerò su questo argomento (a meno che non me lo chiediate...) ma vorrei invece sollevare un caso pratico.

    Poniamo di realizzare uno ScriptControl, utilizzando l'interfaccia IScriptControl. Questo controllo sfrutta un webservice per popolare il controllo (pensate ad esempio ai nodi di un albero). Anzichè mettere la referenza al WebService hardcoded sarebbe opportuno usare una coppia si proprietà alla stregua di quello che fa il Control Toolkit nel controllo AutoComplete.

    /* ... omissis ... */ private string servicePath; private string serviceMethod; public string ServicePath { get { return this.servicePath; } set { this.servicePath = value; } } public string ServiceMethod { get { return this.serviceMethod; } set { this.serviceMethod = value; } } /* ... omissis ... */

    Assodato che ServiceMethod deve per forza implementare una determinata firma ben conosciuta tra client e server, ecco come fare a chiamare questo metodo in modo dinamico senza mettere una referenza nello ScriptManager. il primo passo è esportare le proprietà nella parte client dello ScriptControl:

    /* ... omissis ... */ public IEnumerable<ScriptDescriptor> GetScriptDescriptors() { ScriptControlDescriptor classDescriptor = new ScriptControlDescriptor("MyControl", this.ClientID); if (!string.IsNullOrEmpty(this.ServicePath)) classDescriptor.AddProperty("servicePath", VirtualPathUtility.ToAbsolute(this.ServicePath)); if (!string.IsNullOrEmpty(this.ServiceMethod)) classDescriptor.AddProperty("serviceMethod", this.ServiceMethod); yield return classDescriptor; } /* ... omissis ... */

    in seguito aggiungeremo le proprietà corrispondenti nella parte client, servicePath e serviceMethod che sono valorizzate per mezzo del precedente spezzone di codice:

    /* ... omissis ... */ get_servicePath : function() { return this._servicePath; }, set_servicePath : function(value) { this._servicePath = value; }, get_serviceMethod : function() { return this._serviceMethod; }, set_serviceMethod : function(value) { this._serviceMethod = value; }, /* ... omissis ... */

    Infine ecco come usare la classe Sys.Net.WebServiceProxy per effettuare la chiamata:

    /* ... omissis ... */ Sys.Net.WebServiceProxy.invoke( this.get_servicePath(), this.get_serviceMethod(), false, { mode : this.get_mode()}, Function.createDelegate(this, this._onMethodComplete), Function.createDelegate(this, this._onMethodFailed), null, null); /* ... omissis ... */

    in particolare i primi due parametri sono appunto il path del servizio e il metodo da invocare. In seguito si specificano i parametri del metodo con una sintassi JSON, poi ancora i callback consueti per Complete e Failed. Per la documentazione completa del metodo vedere .

    Technorati Tag: , , ,

    Qualche tempo fa avevo accennato alle problematiche che derivano dall'uso del SqlProfileProvider. Avevo anche introdotto una possibile soluzione che consentiva di indicizzare i contenuti in una tabella appositamente creata. Oggi ho scoperto sul sito www.asp.net un provider creato dal tema di asp.net che consente di superare questo problema salvando i dati di profilazione in un campo a ciascuno.

    Link:


    Stasera ho provato a smontare l'updatepanel per cercare di capirne il funzionamento. Dopo un po' che mettevo il naso qui e li nel codice con FireBug, sono arrivato al cuore ovvero dove vengono effettuate le chiamate. Ne è uscito questo pezzettino di codice, tratto adattando gli internals che utilizza Sys.Net.WebClient per effettuare una chiamata in POST sul server di provenienza della pagina. In realtà il codice è volutamente semplificato per renderlo maggiormente comprensibile a chiunque:

       1: Downloader = function Elite$Downloader()
       2: { }
       3:  
       4: Downloader.prototype =
       5: {
       6:     _asyncPostBackCompleted:function(sender, eventArgs)
       7:     {
       8:         var tm = $get("time");
       9:         tm.innerHTML = sender.get_responseData();
      10:     },
      11:     download:function()
      12:     {
      13:         var request = new Sys.Net.WebRequest();
      14:         request.set_url("time.axd");
      15:         request.get_headers()['X-AndreaBoschinAjax'] = 'Delta=true';
      16:         request.get_headers()['Cache-Control'] = 'no-cache';
      17:         request.set_timeout(10000);
      18:         request.set_httpVerb("GET");
      19:         request.add_completed(Function.createDelegate(this, this._asyncPostBackCompleted));
      20:         request.set_body("");
      21:         request.invoke();
      22:     }      
      23: }

    Nel codice che ho scritto dichiaro una classe "Downloader" creata semplicemente per pura utilità nell'assegnare i delegate. Al click di un pulsante (non incluso nel riquadro) viene invocato il metodo download() che effettua la chiamata al server e restituisce la risposta in modo asincrono.

    Non so dirvi l'utilità pratica di questo codice, ma dicerto ha una sua utilità "accedemica". Chissà poi che qualcuno non trovi un caso reale in cui applicarlo.


    Stamane nel newsgroup di asp.net è apparsa una domanda interessante che mi ha fatto pensare un po' a proposito delle potenzialità di Membership e di ProfileProvider. Ho colto così l'occasione per scrivere due righe di codice per risolvere un problema che effettivamente potrebbe essere limitante nell'uso di questi strumenti. Il problema in questione è che così come le informazioni del profilo vengono persistite nella relativa tabella è praticamente impossibile se non molto difficile accedere ad esse per filtrare gli utenti in base ad esse.

    Le informazioni di profilo vengono memorizzate tutte in due campi nella tabella aspnet_Profile con un formato particolare che consente al runtime di recuperarle velocemente ma che d'altro canto impedisce di accedervi per mezzo di una semplice query. Ecco come:

       1: PropertyNames: Age:S:0:2:Province:S:2:2:Town:S:4:7:
       2: PropertyValues: 38TVTreviso

    In sostanza il campo PropertyNames oltre al nome delle proprietà contiene il tipo, il punto di inizio e la lunghezza della frazione del campo PropertyValues. Nel riquadro è riportato il formato di tre proprieta: Age, Province e Town.

    Ora è del tutto evidente che sperare di scrivere una query SQL che consenta di raggiungere ad esempio tutti gli utenti che abitano in provincia di Treviso è pura utopia. Inoltre se andiamo a vedere i metodi del ProfileProvider in breve ci renderemo conto che estrarre questa informazione è piuttosto complicato e rischia di costringerci a leggere i campi uno per uno decodificandoli.

    Oggi qualcuno naturalmente aveva bisogno di ottenere proprio questo risultato e quindi mi sono dovuto inventare un workaround. Di per se non sarebbe complesso trovare gli utenti di Treviso se i dati fossero spalmati correttamente in una tabella, perciò ho pensato di salvare una copia dei dati di interesse in una tabella di appoggio da usare per questi scopi.

    L'idea è quella di inserirsi al punto giusto ed operare inserimenti e update all'unisono con il provider stesso in modo da tenere allineata questa tabella senza però perdere le funzionalità comode del provider che deve rimanere l'unico gestore in lettura e scrittura. Quindi ho deciso di estendere il provider e fare l'override del metodo SetPropertyValues().

       1: public override void SetPropertyValues(
       2:     SettingsContext sc, 
       3:     SettingsPropertyValueCollection properties)
       4: {
       5:     base.SetPropertyValues(sc, properties);
       6:  
       7:     using (SqlConnection conn = new SqlConnection(this.ConnectionString))
       8:     {
       9:         conn.Open();
      10:  
      11:         using (SqlCommand command = new SqlCommand("spx_UpdateProfileIndex", conn))
      12:         {
      13:             command.CommandType = CommandType.StoredProcedure;
      14:             command.Parameters.AddWithValue("@username", sc["UserName"]);
      15:             command.Parameters.AddWithValue("@province", properties["Province"].PropertyValue);
      16:             command.ExecuteNonQuery();
      17:         }
      18:     }
      19: }

    Nel metodo dapprima si chiama la base che provvede ad inserire i dati al loro posto come consueto. Poi il controllo passa alla stored procedure nella quale dati che vengono salvati nella tabella di appoggio. La tabella in questione ha un campo per ogni proprietà. La stored procedure è abbastanza furba da sapere se deve fare una insert o un update.

    Con questo sistema si riesce a creare quindi una tabellina semplice che ci aiuti a fare delle query per trovare il riferimento agli utenti che hanno un dato valore in una proprietà. L'importante è non abusarne. Non si deve nemmeno per un momento pensare che modificando un valore in essa si modificheranno in accordo anche il valore della proprietà di profile, altrimenti ci troveremmo nella condizione in cui rifare il provider è più conveniente. Ovviamente poi sarà nostro onere tenere il numero di colonne appropriato per proprietà che vengano aggiunte in futuro.

    Tuttavia lasciatemi fare una considerazione: la palese inadeguateza del ProfileProvider in casi come questo mi suggerisce che sia preferibile adottare un sistema custom per la persistenza di proprietà che poi servano per catalogare gli utenti, mentre il ProfileProvider dovrebbe essere usato per dati meno sensibili come ad esempio le personalizzazione dell'interfaccia o comunque dei dati che devono solamente essere persistiti tra diverse sessioni.

    Codice di esempio completo: (6,8 KB)


    Gli UserControl sono indubbiamente una gran bella invenzione in ASP.NET, per qualche verso anche meglio dei WebControl per i progetti low-end, perchè consentono di creare delle componenti riutilizzabili con molta più facilità risparmiando molto tempo. Con l'introduzione delle webparts poi il loro utilizzo si è moltiplicato a dismisura. Tuttavia non è la prima volta che mi trovo ad avere degli UserControl che pur essendo riutilizzabili come auspicabile talvolta soffrono di alcuni fastidiosi vincoli imposti ad esempio dalla navigazione del website.

    Se ad esempio pensiamo ad un controllo che rappresenti la form di creazione/modifica di una entità del nostro sistema, non è sempre detto che alla pressione del tasto salva il comportamento debba essere il medesimo. Poniamo che il controllo sia usato una volta come "inserimento rapido" - posizionato immediatamente sotto ad una gridview - mentre un'altra volta verrà inserito in una pagina autonoma per l'edit dell'entità stessa. Se nel primo caso alla pressione del tasto Salva ci dovremmo aspettare di rimanere nella stessa pagina per consentire l'inserimento di un'altro record, nel secondo potremmo attenderci di tornare alla pagina da cui siamo arrivati. Se inseriamo questo comportamento hard-coded nello UserControl stiamo indubbiamente limitando la riutilizzabilità del controllo stesso. Dovremmo perciò gestire una serie di eventi per fare in modo che la navigazione non abbia impatto sulla riutilizzabilità del controllo.

    Recentemente ho adottato un sistema interessante che mi aiuta a gestire in modo più rapido questa problematica. Il primo passo è quello di creare uno UserControl base per tutti i controlli che dovranno implementare questo tipo di navigazione:

       1: public class CoreUserControl : UserControl
       2: {
       3:     /// <summary>
       4:     /// Gets or sets the navigations.
       5:     /// </summary>
       6:     /// <value>The navigations.</value>
       7:     [PersistenceMode(PersistenceMode.InnerProperty)]
       8:     public NavigationCollection Navigations
       9:     {
      10:        get { return GetViewState<NavigationCollection>("navigations", new NavigationCollection()); }
      11:        set { SetViewState<NavigationCollection>("navigations", value); }
      12:     }
      13:     
      14:     /// <summary>
      15:     /// Navigates to.
      16:     /// </summary>
      17:     /// <param name="navigationName">Name of the navigation.</param>
      18:     protected void NavigateTo(string navigationName, params object [] args)
      19:     {
      20:        string navigation = this.Navigations.GetNavigationUrl(navigationName, args);
      21:     
      22:        if (!string.IsNullOrEmpty(navigation))
      23:            Response.Redirect(navigation);
      24:     }
      25: }

    Come si vede il controllo espone una collection di oggetti di tipo Navigation che rappresentano appunto le possibili transizioni in base agli eventi generati dallo usercontrol. Nel caso del controllo di cui sopra potrebbero essere OnSaved e OnCancel per rappresentare appunto la navigazione successiva al salvataggio dell'entità e quella causata dalla pressione di Cancel. L'oggetto Navigation espone solamente due proprietà: Name e NavigationUrlFormat. Il primo è il nome che daremo all'evento che causa la transizione mentre il secondo è l'url di destinazione comprensivo di eventuali marcatori per parametri che siano inseriti a runtime. Ecco un esempio di configurazione:

       1: <uc:PropertyForm runat="server" id="editProperty" Title="<%$ Resources:common,EditProperty_Title %>">
       2:     <Navigations>
       3:         <uc:Navigation Name="OnSaved" NavigationUrlFormat="~/list.aspx?id={0}" />
       4:         <uc:Navigation Name="OnCancel" NavigationUrlFormat="~/home.aspx" />
       5:     </Navigations>
       6: </uc:PropertyForm>

    Lo usercontrol, perciò al termine di ogni operazioe dovrà comportarsi come segue:

    1) Verifica che sia presente una Transizione con il nome richiesto

    2) Se la transizione esiste attiva la navigazione verso la destinazione

    3) Se la transizione non esiste rimane sulla pagina stessa

    Questa verifica è fatta dal metodo NavigateTo(), esposto dallo UserControl base. Grazie ad esso, all'interno del codice del controllo sarà sufficiente scrivere il seguente codice per gestire la navigazione:

       1: protected void thisForm_ItemInserted(object sender, FormViewInsertedEventArgs e)
       2: {
       3:     if (e.Exception != null)
       4:         e.ExceptionHandled = HandleException(e.Exception);
       5:     else
       6:     {
       7:         NavigateTo("OnSaved", this.CurrentClass);
       8:     }
       9:  
      10:     e.KeepInInsertMode = true;
      11: }

    Naturalmente non è l'unico modo: ad esempio potremmo usare l'url prodotto da GetNavigationUrl() per alimentare un link in una gridview e fare così in modo che si apra la form relativa ad una riga presenti in essa.

    Personalmente ho trovato il sistema da un lato abbastanza flessibile e dall'altro sufficientemente semplice da non richiedere complesse infrastrutture che alla fine complicano le cose molto più di quello che semplificano.

    Technorati tags: , ,