Confessions of a Dangerous Mind

Brain.FlushBuffer()
posts - 176, comments - 234, trackbacks - 93

Silverlight 2 hosted in DNN 4.x: integrating modules and services

Non appena è stato rilasciato, Silverlight ha fatto subito capire di che cosa sarebbe stato capace. Con la versione 2.0 e l'introduzione della possibilità di utilizzare C# per programmare la logica delle applicazioni piuttosto che Javascript, è maturata finalmente la possibilità di realizzare anche applicazioni complesse fruibili sul web.

L'obiettivo di questo articolo è quello di capire se e come sia possibile sfruttare una applicazione CMS molto diffusa, DotNetNuke, per fungere da collante tra applicazioni Silverlight 2.0, in modo da creare una mashup application che si avvalga della robustezza della piattaforma DNN e, nel contempo, possa sfruttare Silverlight 2.0 per la realizzazione di moduli. Per ultimo, ma non per questo meno importante, andremo a capire come utilizzare l'autenticazione di DNN nei servizi consumati dalle nostre applicazioni Silverlight, in modo che le chiamate ai nostri servizi WCF esposti dal nostro portale impieghino il contesto di sicurezza dell'utente autenticato. Al lavoro, quindi!

Architettura e obiettivi

Nella figura seguente possiamo vedere le relazioni tra i vari componenti della soluzione che andremo a realizzare.

Come esempio, svilupperemo un semplicissimo modulo per DotNetNuke, capace di ospitare al suo interno un'applicazione Silverlight. Questo modulo si avvarrà di una tabella aggiuntiva, creata mediante script, all'interno del database di DNN. La tabella in questione conterrà le impostazioni di ogni modulo che verrà inserito all'interno di una pagina del nostro portale.

Oltre al modulo di DNN, che sarà riutilizzabile con più applicazioni Silverlight, dovremo sviluppare altri due progetti: il primo è un progetto Silverlight che andrà a realizzare in concreto l'applicazione silverlight con l'interfaccia grafica e la logica desiderata; il secondo è un progetto di servizi WCF che verranno referenziati e consumati dalla nostra applicazione Silverlight.

Riassumendo, il modulo di DNN verrà registrato all'interno del portale DNN e potrà ospitare diverse applicazioni Silverlight. Le applicazioni Silverlight che verranno inglobate in questi moduli potranno consumare dei servizi WCF residenti all'interno del portale DNN, utilizzando il contesto di sicurezza dell'utente correntemente autenticato.

Il modulo di DNN: SilverlightModule

Cominciamo dalla parte più semplice: la creazione del modulo di DNN che fungerà da host per le applicazioni Silverlight. La parte HTML del modulo è banale: usiamo il controllo asp.net Silverlight Host senza specificare nessuna proprietà, in quanto queste verranno lette dalla tabella SilverlightModules che avremo avuto l'accortezza di creare all'interno del database di DNN. Vediamo il codice del modulo:

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SilverlightModule.ascx.cs" 
   2:     Inherits="CodeSapiens.DotNetNuke.SilverlightModules.SilverlightModule" %>
   3: <%@ Register Assembly="System.Web.Silverlight" Namespace="System.Web.UI.SilverlightControls" TagPrefix="asp" %>
   4: <asp:Silverlight ID="SH" runat="server"/>
   5: <asp:Label ID="lblMessage" runat="server" ForeColor="Red"/>

Il code behind del modulo fa uso di una classe, SilverlightModuleController, che implementa le varie operazioni di creazione, modifica e lettura delle informazioni di un Silverlight Module dal database di DotNetNuke.

   1: public class SilverlightModuleController
   2: {
   3:     DataProvider _dataProvider;
   4:  
   5:     public SilverlightModuleController()
   6:     {
   7:         _dataProvider = new DataProvider();
   8:     }
   9:     
  10:     public void AddSilverlightModuleInfo(SilverlightModuleInfo moduleInfo)
  11:     {
  12:         _dataProvider.AddSilverlightModule(moduleInfo.ModuleId, moduleInfo.Source, moduleInfo.Width, moduleInfo.Height);
  13:     }
  14:  
  15:     public void UpdateSilverlightModuleInfo(SilverlightModuleInfo moduleInfo)
  16:     {
  17:         _dataProvider.UpdateSilverlightModule(moduleInfo.ModuleId, moduleInfo.Source, moduleInfo.Width, moduleInfo.Height);
  18:     }
  19:  
  20:     public SilverlightModuleInfo GetSilverlightModuleInfo(int moduleId)
  21:     {
  22:         return CBO.FillObject<SilverlightModuleInfo>(_dataProvider.GetSilverlightModule(moduleId));           
  23:     }
  24: }

I dati dei moduli silverlight vengono memorizzati all'interno di una tabella di SQL e sono:

  • ModuleId: identificativo del modulo di DNN
  • Source: nome dell'applicazione xap
  • Width: larghezza del modulo
  • Height: altezza del modulo

Ovviamente le proprietà potrebbero (dovrebbero) essere molte di più, ad esempio includendo gli InitParameters, ma per questo esempio possono bastare. Oltre al modulo di "view" che ho riportato sopra, bisogna realizzare anche il modulo di edit di queste proprietà. Non riporto il codice, perchè piuttosto banale: vi basterà sapere che questo modulo consente di impostare o modificare le proprietà del nostro modulo Silverlight. Al momento, quindi, possiamo vantare di avere un modulo che rende molto semplice linserimento di un'applicazione Silverlight all'interno del portale DotNetNuke, con relativo modulo di editing delle proprietà base dell'applicazione Silverlight. Vedremo ora come creare ed ospitare un semplice servizio WCF all'interno di DNN e fare in modo di poterlo consumare dalla nostra Silverlight Application.

Il servizio WCF: ShippingService

Non appena si parla di servizi, personalmente non riesco a non pensare all'autenticazione e all'autorizzazione del servizio stesso. Credo fermamente che un'infrastruttura che non riesca a controllare chi ha richiesto che cosa e se ha il permesso o meno di utilizzare la risorsa che desidera, non sia un'infrastruttura valida per creare applicazioni per il mondo reale. Devo dire che lo sforzo che è stato fatto con Silverlight è ammirevole e permette di realizzare un sistema completamente integrato per l'autenticazione e l'autorizzazione delle chiamate dei metodi dei servizi WCF basata su memberhip e roles di ASP.net.

Il risultato che vogliamo ottenere è il seguente:

Una volta autenticato correttamente all'interno del portale DNN, vorremmo far sì che ogni chiamata ad un metodo di un web service, effettuata da applicazioni Silverlight ospitate all'interno del nostro portale, venga portata a termine rispettando il contesto di sicurezza (identity+roles) dell'utente correntemente autenticato all'interno del portale. In questo modo potremmo fornire risposte elaborate tenendo conto delle autorizzazioni fornite all'utente.

L'obiettivo è piuttosto interessante ed utile, in quanto se si riesce ad ottenere una infrastruttura di questo tipo, si riesce indirettamente ad ottenere un framework applicativo basato su Silverlight e WCF services che si appoggia ad una delle applicazioni CMS più diffuse.

Come prima  cosa, creiamo un'applicazione web WCF Service. Essa conterrà un semplicissimo servizio che aderirà al contratto IShippingService. L'operazione esposta dal contratto è un calcolo del costo di spedizione di un'ipotetica merce.

   1: namespace CodeSapiens.DNN.Services
   2: {    
   3:     [ServiceContract]
   4:     public interface IShippingService
   5:     {
   6:         [OperationContract]
   7:         string CalculateShipping(string postalCode);
   8:     }
   9: }

L'implementazione del contratto tramite il servizio ShippingService è forse un pò più interessante. Innanzitutto dobbiamo pensare che questo servizio vivrà all'interno dell'infrastruttura di DNN, e quindi, dopo averlo copiato all'interno di una directory Services creata "ad hoc" nella nostra installazione di DNN, sarà raggiungibile all'URI: http://<serveraddress>/Services/ShippingService.svc. Nel servizio potremo far riferimento a tutte le features esposte da DNN, per cui invio e-mail, gestione delle eccezioni centralizzata, gestione della sicurezza e molte altre.

La chiave di volta di tutto il nostro lavoro sta nell'osservare che il contesto della chiamata HttpRequest fatta al servizio ShippingService dall'applicazione Silverlight ospitata all'interno del portale DNN è autenticata con l'utente loggato in DNN in quel momento, ovvero l'oggetto HttpContext.Current.User.Identity ha le proprietà Name e Authenticated impostate rispettivamente al nome dell'utente e a true (ovviamente solo se l'utente è autenticato). Vi ricordo ancora una volta che siamo all'interno di DNN, per cui non possiamo attivare la gestione dei ruoli standard di ASP.net e quindi non abbiamo già di default la possibilità di utilizzare il metodo IsInRole offerto dall'oggetto Principal. Dobbiamo quindi creare un oggetto Principal "Custom" che, una volta creato, verrà assegnato come principal del thread che esegue il servizio, offrendo così la possibilità di impiegare addirittura la sicurezza dichiarativa sui metodi del servizio stesso. Commentiamo insieme il codice del servizio:

   1: namespace CodeSapiens.DNN.Services
   2: {
   3:     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
   4:     public class ShippingService : IShippingService
   5:     {
   6:         const int PORTAL_ID = 2;
   7:  
   8:         public ShippingService()
   9:         {            
  10:             //Necessario solo se si vuole usare la sicurezza dichiarativa [PrincipalPermission]
  11:             DotNetNuke.Entities.Users.UserInfo _ui = DotNetNuke.Entities.Users.UserController.GetUserByName(PORTAL_ID, HttpContext.Current.User.Identity.Name);
  12:             Thread.CurrentPrincipal = new System.Security.Principal.GenericPrincipal(HttpContext.Current.User.Identity, _ui.Roles);
  13:         }
  14:  
  15:         #region IShippingService Members
  16:  
  17:         [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
  18:         public string CalculateShipping(string postalCode)
  19:         {
  20:             if (postalCode == "31050")
  21:                 return "FREE";
  22:             else
  23:                 return "100 Euro";
  24:         }
  25:  
  26:         #endregion
  27:     }
  28: }

Come già anticipato, nel costruttore del servizio sfruttiamo le API di DotNetNuke per costruire di fatto un oggetto GenericPrincipal che viene assegnato al thread corrente. La lettura delle informazioni dell'utente (GetUserByName) ci fornisce anche le informazioni riguardanti i ruoli assegnati all'utente, che vengono infatti utilizzati per completare la creazione dell'oggetto Principal. Sulla chiamata CalculateShipping, a questo punto, abbiamo impostato una sicurezza dichiarativa basata sul ruolo administrators, in modo che la chiamata possa essere effettuata solo da utenti appartenenti a tale ruolo. Se lo avessimo desiderato, avremmo potuto effettuare lo stesso controllo mediante la sicurezza imperativa, basandoci sul metodo IsInRole(...) dell'oggetto Principal. In questo modo potrebbe essere molto facile creare delle ACL per i metodi del nostro servizio esposto attraverso l'infrastruttura di DNN.

Grazie a questa osservazione, possiamo creare un set di primitive sicure, integrate all'interno del nostro portale DNN ed esposte a tutte le applicazioni Silverlight ospitate nelle pagine del nostro portale.

L'applicazione Silverlight: TestModule

Finalmente ci siamo! Dopo tanto Server Programming, ecco la nostra piccola applicazione Silverlight. Si tratta di una banalissima applicazione con un pulsante ed una TextBox. Vogliamo che una volta inserito un CAP nella TextBox e cliccato il pulsante, sempre nella textbox appaia il costo della spedizione. Nell'implementazione dell'esempio, qualunque numero venga inserito, a meno che non sia 31050, fornirà 100 Euro mentre per 31050 il risultato è FREE. Qui sotto l'interfaccia dell'applicazione, volutamente scarna.

L'applicazione Silverlight include il proxy generato aggiungendo la Service Reference al nostro ShippingService. Grazie alla perfetta integrazione di Silverlight con l'infrastruttura di ASP.net, nel gestore dell'evento non ci rimarrà che invocare il metodo asincrono CalculateShippingAsync. Il server controllerà se ci è consentito effettuare la chiamata e a seconda del CAP specificato nella textbox ci fornirà un valore. Ecco il codice della chiamata.

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:    //Non usiamo il file ClientConfig, impostiamo binding e address a mano
   4:    BasicHttpBinding _binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
   5:    EndpointAddress _address = new EndpointAddress("http://localhost/silverlight/services/shippingservice.svc");
   6:    ShippingService.ShippingServiceClient _proxy = new ShippingService.ShippingServiceClient(_binding, _address);
   7:    _proxy.CalculateShippingCompleted += new EventHandler<ShippingService.CalculateShippingCompletedEventArgs>(proxy_CalculateShippingCompleted);
   8:    _proxy.CalculateShippingAsync(txtCAP.Text);
   9: }
  10:  
  11: void proxy_CalculateShippingCompleted(object sender, ShippingService.CalculateShippingCompletedEventArgs e)
  12: {
  13:    if (e.Error != null)
  14:        txtCAP.Text = e.Error.Message;
  15:    else
  16:        txtCAP.Text = e.Result;
  17: }

Per inciso, non utilizziamo il file .ClientConfig per il semplice motivo che in una applicazione "reale", l'uri del servizio dovrebbe essere letto dagli InitParameters (ad esempio), e quindi è impossibile utilizzare il file ClientConfig con i valori statici al suo interno.

Per quanto riguarda la sicurezza, se utilizzassimo il modulo per effettuare la chiamata a Calculate Shipping senza essere autenticati e appartenenti al ruolo Administrators ci verrebbe restituito un errore. Ovviamente utilizzando la sicurezza di DNN è possibile inserire e visualizzare il modulo nella pagina a seconda delle credenziali dell'utente, in modo da evitare di incorrere in problemi di questo tipo.

Conclusioni

In questo (lungo!) articolo abbiamo visto come integrare un'applicazione Silverlight 2.0 all'interno del CMS DotNetNuke. Abbiamo inoltre potuto apprezzare l'integrazione tra Silverlight ed ASP.net per quanto riguarda il contesto di sicurezza basato su Forms che ci permette di utilizzare lo stesso contesto di sicurezza dell'utente autenticato nel portale DNN durante le chiamate ai servizi WCF esposti nel portale stesso. Come già esposto nel mio precedente articolo, i post-build events giocano un ruolo fondamentale per quanto riguarda la producttività durante lo sviluppo, in quanto si occupano di copiare i file necessari al "posto giusto", senza richiedere un intervento manuale. Con una infrastruttura del genere, penso che molte funzionalità che prima richiedevano un lungo lavoro di creazione di interfaccia web, potranno essere realizzate con estrema piacevolezza con questa nuova meravigliosa tecnologia che è Silverlight.

Print | posted on lunedì 2 marzo 2009 00:04 |

Feedback

Gravatar

# re: Silverlight 2 hosted in DNN 4.x: integrating modules and services

Molto interessante, potrà tornarmi utile in futuro.
Intanto grazie e complimenti.
03/03/2009 12:22 | Federico
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET