Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Orchard CMS

Estendibilità di Orchard

Bertrand Le Le

Scaricare il codice di esempio

La maggior parte delle applicazioni Web hanno molto in comune, ma allo stesso tempo presentano un sacco di differenze. Tutte hanno pagine statiche ("termini d'utilizzo," "Chi siamo" e così via). Essi presentano contenuti all'interno di un layout comune. Essi hanno il menu di navigazione. Avrebbero potuto ricerca, commenti, valutazioni e integrazione di rete sociale. Ma alcuni sono blog, alcuni vendere libri, alcuni tenersi amici in contatto e alcuni contengono centinaia di migliaia di articoli di riferimento sulle vostre tecnologie preferiti.

Un Content Management System (CMS) mira a fornire i pezzi comuni pur non imponendo vincoli sul tipo di sito in costruzione. È un delicato esercizio di estensibilità.

I creatori del CMS Orchard (orchardproject.net) — che include me — hanno optato per un approccio che si basa massicciamente sulla composizione e convenzione. In questo articolo, ti presento alcuni esempi di semplici estensioni per il sistema che dovrebbe essere un buon punto di partenza per i proprio moduli.

Un sistema di tipo dinamico

Non importa quello che si utilizza per costruire il tuo sito CMS, ci sarà un'entità di contenuto centrale che va dai nomi differenti. In Drupal, si chiama un nodo, e nel frutteto, è un elemento di contenuto. Gli elementi di contenuto sono atomi di contenuto come i post del blog, pagine, prodotti, widget o gli aggiornamenti dello stato. Alcuni di essi corrispondono a un URL, e alcuni non. Loro schemi variano notevolmente, ma ciò che hanno in comune è che sono le più piccole unità di contenuto sul sito. O sono?

Suddivisione dell'atomo

Gli sviluppatori, la nostra prima reazione è per identificare gli elementi contenuti come istanze di classi (post, pagina, prodotto o widget), che è corretto in misura. Allo stesso modo che le classi sono composte di membri (campi, proprietà e metodi), tipi di contenuto (le "classi" di elementi di contenuto) sono essi stessi oggetti compositi. Invece di essere composto di proprietà semplici con tipi che sono esse stesse classi, sono composti da parti di contenuto, che sono gli atomi del comportamento dei tuoi contenuti. Questa è una distinzione importante, che io sarò illustrare con un esempio.

Un post sul blog, in genere, è composto di un URL, titolo, data, corpo del testo RTF, un elenco di tag e una lista di commenti degli utenti. Nessuna delle parti è specifica per un post del blog. Che cosa rende un blog post è la specifica composizione delle parti, non appena uno qualsiasi delle parti.

La maggior parte dei post di blog hanno commenti, ma commenti potrebbero essere utilizzati anche in un sito di commercio per implementare recensioni. Allo stesso modo, i tag sono potenzialmente utili come un modo di classificare qualsiasi elemento di contenuto, non solo i post del blog. Il corpo del testo ricco di un post non è diverso dal corpo di una pagina. La lista continua. Dovrebbe essere chiaro a questo punto che l'unità di comportamento su un sito Web è inferiore all'unità di contenuto.

Un altro fatto su CMS è che i tipi di contenuto non sono fissati in anticipo. I post del blog usato per essere testo semplice, ma divennero rapidamente molto di più. Ora regolarmente contengono video, podcast o image galleries. Se blog sui vostri viaggi intorno al mondo, magari vorrete aggiungere geolocation ai tuoi post.

Ancora una volta, parti di contenuto vengono in soccorso. Avete bisogno di una longitudine e latitudine? Estendere il tipo di post di blog con l'aggiunta di una parte di mappatura (abbiamo diversi disponibili nella nostra galleria di modulo: Gallery.orchardproject. net). Diventa subito evidente quando si pensa che questa operazione di aggiunta di una parte di un tipo esistente verrà eseguita più spesso dal proprietario del sito, non da uno sviluppatore. Quindi, non dovrebbe essere possibile solo con l'aggiunta di una proprietà complessa a un Microsoft.Tipo di NET Framework. Deve essere basato su metadati e accadere in fase di esecuzione, in modo che possiamo costruire un admin UI per farlo (vedere Figura 1).

The Orchard Content Type Editor
Figura 1 l'Editor di tipi di contenuto Orchard

Questo è il primo modo per estendere il frutteto: È possibile creare ed estendere il contenuto tipi a saranno da admin UI. Naturalmente, tutto ciò che si può fare da interfaccia utente admin, potete fare dal codice come questo:

item.Weld(part);

Questo codice saldature una parte su un elemento di contenuto in modo dinamico. È un'interessante possibilità, come ti permette di istanze di tipi può essere prorogato dinamicamente in fase di esecuzione. In linguaggi dinamici, questo è chiamato un mix-in, ma è un concetto che è quasi inascoltato in linguaggi tipizzati staticamente, ad esempio c#. Questo apre nuove possibilità, ma non è esattamente la stessa cosa come quello che stavamo facendo da admin UI. Anche noi vogliamo essere in grado di aggiungere la parte del tipo una volta per tutte, invece di aggiunta a ogni istanza, come illustrato di seguito:

ContentDefinitionManager.AlterTypeDefinition(
  "BlogPost", ctb => ctb.WithPart("MapPart")
);

Questo è in realtà esattamente come il tipo di contenuto del post di blog è definito in primo luogo:

ContentDefinitionManager.AlterTypeDefinition("BlogPost",
  cfg => cfg
    .WithPart("BlogPostPart")
    .WithPart("CommonPart", p => p
      .WithSetting("CommonTypePartSettings.ShowCreatedUtcEditor", "true"))
      .WithPart("PublishLaterPart")
      .WithPart("RoutePart")
      .WithPart("BodyPart")
  );

Si può notare in questo frammento di codice che tag e commenti sembrano essere mancante dal post del blog. Questo è un altro esempio di attenta separazione dei problemi. Il modulo blog in realtà sa nulla di tag e commenti, oltre il tag e commenti moduli fanno a proposito di blog. Una terza parte è responsabile di metterli insieme.

Ricette

All'installazione, viene eseguita una ricetta che è responsabile di questo tipo di attività. È una descrizione XML della configurazione iniziale del sito. Frutteto è dotato di tre ricette predefinito: blog, predefinito e core. Il codice seguente mostra la parte della ricetta blog che aggiunge i tag e i commenti ai post di blog:

<BlogPost ContentTypeSettings.Draftable="True" TypeIndexing.Included="true">
  <CommentsPart />
  <TagsPart />
  <LocalizationPart />
</BlogPost>

Vi ho mostrato finora i vari modi in cui le parti di contenuto possono essere composti in elementi di contenuto. Il mio prossimo passo sarà quello di spiegare come si possono costruire i proprio parti.

Costruzione di una parte

Per illustrare il processo di costruzione di una nuova parte, sto andando a fare affidamento sull'esempio della caratteristica Meta dal mio modulo Vandelay Industries (Scaricalo da bit.ly/u92283). La funzionalità di Meta aggiunge parole chiave e proprietà di descrizione per scopi di Search Engine Optimization (SEO) (vedere Figura 2).

The SEO Meta Data Editor
Nella figura 2 la SEO Meta dati Editor

Queste proprietà verranno eseguito il rendering nella sezione head della pagina come capire metatags standard che i motori di ricerca:

    <meta content="Orchard is an open source Web CMS built on ASP.NET MVC."
      name="description" />
    <meta content="Orchard, CMS, Open source" name="keywords" />

Il Record

Il primo pezzo del puzzle sarà una descrizione del modo in cui i dati sta per essere mantenuta nel database. Strettamente parlando, non tutte le parti devono un record, perché non tutte le parti memorizzano i propri dati nel database di frutteto, ma la maggior parte fanno. Un record è solo un normale oggetto:

public class MetaRecord : ContentPartRecord {
  public virtual string Keywords { get; set; }
  public virtual string Description { get; set; }
}

La classe MetaRecord deriva da ContentPartRecord. Questo non è assolutamente necessario, ma è sicuramente conveniente come ottiene alcuni l'impianto idraulico fuori del modo. La classe ha due proprietà di stringa, per parole chiave e descrizione. Queste proprietà devono essere contrassegnate virtuale così il quadro può "mix-in" una propria logica per costruire la classe concreta che verrà utilizzata in fase di esecuzione.

Esclusiva responsabilità del record è la persistenza di database, con l'aiuto di una dichiarazione per il meccanismo di archiviazione che può essere trovato nella MetaHandler:

public class MetaHandler : ContentHandler {
  public MetaHandler(
    IRepository<MetaRecord> repository) {
    Filters.Add(
      StorageFilter.For(repository));
  }
}

Anche l'archiviazione deve essere inizializzato. Prime versioni del frutteto erano la deduzione lo schema del database da classi di record, ma indovinando può solo prendere finora, e questo da allora è stato sostituito con un sistema di migrazione più accurato cui modifiche allo schema vengono esplicitamente definite, come mostrato nella Figura 3.

Figura 3 definito in modo esplicito le modifiche allo Schema

public class MetaMigrations : DataMigrationImpl {
  public int Create() {
    SchemaBuilder.CreateTable("MetaRecord",
      table => table
        .ContentPartRecord()
        .Column("Keywords", DbType.String)
        .Column("Description", DbType.String)
    );
    ContentDefinitionManager.AlterPartDefinition(
      "MetaPart", cfg => cfg.Attachable());
    return 1;
  }
}

La tabella MetaRecord viene creata con un nome che il sistema sarà in grado di mappa dalla convenzione alla classe MetaRecord. Ha le colonne di sistema per un record di parte del contenuto aggiunto dalla chiamata al metodo di ContentPartRecord, più le parole chiave e descrizione colonne di stringa che verranno automaticamente eseguito il mapping dalla convenzione le proprietà corrispondenti della classe di record.

La seconda parte del metodo di migrazione dice che la nuova parte sarà associabile da admin UI a qualsiasi tipo di contenuto esistente.

Il metodo Create rappresenta sempre la migrazione iniziale e di solito restituisce 1, ovvero il numero di migrazione. La convenzione è che in futuro versioni del modulo, lo sviluppatore può aggiungere metodi di UpdateFromX, dove x è sostituito dal numero di migrazione da cui il metodo funziona. Il metodo deve restituire un nuovo numero di migrazione che corrisponde al nuovo numero di migrazione dello schema. Questo sistema consente aggiornamenti lisci, indipendenti e flessibili di tutti i componenti del sistema.

Per rappresentare la parte reale, una classe separata viene utilizzata, che esaminerò ora.

Classe Part

La rappresentazione della parte stessa è un'altra classe che deriva da ContentPart <TRecord>:

public class MetaPart : ContentPart<MetaRecord> {
  public string Keywords {
    get { return Record.Keywords; }
    set { Record.Keywords = value; }
  }
  public string Description {
    get { return Record.Description; }
    set { Record.Description = value; }
  }
}

La parte funge da proxy per parole chiave e la descrizione del record proprietà comodità, ma se non lo è, il record e le sue proprietà sarebbe ancora disponibili tramite la proprietà pubblica di Record della classe base ContentPart.

Qualsiasi codice che ha un riferimento a un elemento di contenuto che ha la parte MetaPart sarà in grado di ottenere l'accesso fortemente tipizzata per le parole chiave e proprietà di descrizione chiamando il metodo As, che è l'analogo del sistema di tipo frutteto di una CLR cast dell'operazione:

var metaKeywords = item.As<MetaPart>().Keywords;

La classe parte è anche dove si attuerebbe qualsiasi comportamento specifico di dati della parte. Ad esempio, un prodotto composto potrebbe esporre metodi o proprietà accedere ai suoi sottoprodotti o calcolare un prezzo complessivo.

Comportamento che pertiene all'interazione dell'utente (il codice di orchestrazione che sarebbe in un regolare ASP.Applicazione MVC NET essere trovato nel controller) è un'altra questione. Questo è dove entrano i driver.

Il Driver

Ogni parte in un elemento di contenuto deve ottenere l'opportunità di partecipare al ciclo di vita di richiesta e fare in modo efficace il lavoro di un'applicazione ASP.Controller di NET MVC, ma si deve farlo presso la scala della parte invece di farlo alla scala della richiesta completa. Un driver di parte del contenuto svolge il ruolo di un controller di ridotta. Non ha la ricchezza di un controller in quanto non non c'è alcun mapping ai suoi metodi dalle rotte. Invece, è fatto di metodi di gestione degli eventi ben definiti, come ad esempio Display o Editor. Un driver è solo una classe che deriva da ContentPartDriver.

Il metodo di visualizzazione è ciò che viene chiamato quando Orchard deve eseguire il rendering della parte in forma di sola lettura (vedere Figura 4).

Figura 4 metodo di visualizzazione del conducente prepara il Rendering della parte

protected override DriverResult Display(
  MetaPart part, string displayType, dynamic shapeHelper) {
  var resourceManager = _wca.GetContext().Resolve<IResourceManager>();
  if (!String.IsNullOrWhiteSpace(part.Description)) {
    resourceManager.SetMeta(new MetaEntry {
      Name = "description",
      Content = part.Description
    });
  }
  if (!String.IsNullOrWhiteSpace(part.Keywords)) {
    resourceManager.SetMeta(new MetaEntry {
      Name = "keywords",
      Content = part.Keywords
    });
  }
  return null;
}

Questo driver è in realtà un po' atipico, poiché la maggior parte dei driver semplice sul posto di rendering (più su che in un attimo), mentre la parte di Meta ha bisogno eseguire il rendering relativo metatags in testa. La sezione head di un documento HTML è una risorsa condivisa, quindi sono necessarie particolari precauzioni. Frutteto fornisce le API per accedere a tali risorse condivise, che è quello che stai vedendo qui: Sto attraversando il gestore di risorse per impostare il metatag. Il gestore delle risorse si prenderà cura del rendering dei tag effettivo.

Il metodo restituisce null, perché non c'è nulla per eseguire il rendering sul posto in questo scenario specifico. La maggior parte dei driver metodi restituiranno invece un oggetto dinamico chiamato una forma che è analoga a un modello di visualizzazione in ASP.NET MVC. Tornerò a forme in un attimo quando arriva il momento di eseguire il rendering in HTML, ma per il momento, basti dire che sono oggetti molto flessibili, dove si può attaccare tutto ciò che sta per essere rilevante per il modello che eseguirà il rendering, senza dover creare una classe speciale, come illustrato di seguito:

protected override DriverResult Editor(MetaPart part, dynamic shapeHelper) {
  return ContentShape("Parts_Meta_Edit",
    () => shapeHelper.EditorTemplate(
      TemplateName: "Parts/Meta",
      Model: part,
      Prefix: Prefix));
}

Il metodo Editor è incaricato di preparare il rendering dell'editor dell'interfaccia utente per la parte. In genere restituisce un tipo speciale di forma che è appropriato per la costruzione di interfacce utente edizione composito.

L'ultima cosa sul driver è il metodo che gestirà i messaggi dall'editor:

protected override DriverResult Editor(MetaPart part,
  IUpdateModel updater, dynamic shapeHelper) {
  updater.TryUpdateModel(part, Prefix, null, null);
  return Editor(part, shapeHelper);
}

Il codice in questo metodo chiama TryUpdateModel per aggiornare automaticamente la parte con i dati che è stati inviati indietro. Una volta che è fatto con questo compito, lo chiama al primo metodo di Editor per restituire la stessa forma di editor che stava producendo.

Rendering di forme

Chiamando a tutti i driver per tutte le parti, è in grado di costruire una struttura di forme Orchard — un modello di grandi dimensioni vista composito e dinamica per la richiesta di tutto. Il suo prossimo compito è quello di capire come risolvere ogni forma in modelli che saranno in grado di renderli. Lo fa così da guardando il nome di ciascuna forma (Parts_Meta_Edit nel caso del metodo Editor) e tentando di mappa che ai file in luoghi ben definiti del sistema, come il tema corrente e il modulo visualizzazioni delle cartelle. Questo è un punto di estensibilità importante perché consente di eseguire l'override del rendering predefinito di nulla nel sistema trascinando semplicemente un file con il nome giusto nel vostro tema locale.

Nella cartella Views\EditorTemplates\Parts del mio modulo, ho lasciato cadere un file chiamato Meta.cshtml (vedere Figura 5).

Nella figura 5, il modello di Editor per la MetaPart

    @using Vandelay.Industries.Models
    @model MetaPart
    <fieldset>
      <legend>SEO Meta Data</legend>
      <div class="editor-label">
        @Html.LabelFor(model => model.Keywords)
      </div>
      <div class="editor-field">
        @Html.TextBoxFor(model => model.Keywords, new { @class = "large text" })
        @Html.ValidationMessageFor(model => model.Keywords)
      </div>
      <div class="editor-label">
        @Html.LabelFor(model => model.Description)
      </div>
      <div class="editor-field">
        @Html.TextAreaFor(model => model.Description)
        @Html.ValidationMessageFor(model => model.Description)
      </div>
    </fieldset>

Tutto è contenuto

Prima che mi muovo su ad altri argomenti di estensibilità, vorrei ricordare che, una volta capito il sistema di tipi di elemento di contenuto, sotto­stare il concetto più importante nel frutteto. Molte entità importante del sistema sono definiti come gli elementi di contenuto. Ad esempio, un utente è un elemento di contenuto, che consente il profilo moduli aggiungere proprietà arbitrarie a loro. Abbiamo anche gli elementi contenuti widget, è possono ottenere il rendering in zone che definisce un tema. Questo è come le forme di ricerca, blog archives, tag clouds e altri sidebar UI ottenere creato nel frutteto. Ma l'uso più sorprendente di elementi di contenuto potrebbe essere il sito stesso. Impostazioni sito sono elementi efficacemente contenuti nel frutteto, che fa un sacco di senso, una volta capito come multi-tenancy è riuscito nel frutteto. Se si desidera aggiungere impostazioni sito, tutto quello che dovete fare è aggiungere una parte per il tipo di contenuto del sito, e si può costruire un'edizione di admin UI per essa ha seguito la stessa procedura esatta che delineato in precedenza. Un sistema del tipo di contenuto extensible unificato è un concetto estremamente ricco.

Estensioni di imballaggio

Vi ho mostrato come costruire i proprio parti per frutteto e ho accennato i concetti di moduli e temi senza definire questi termini. In poche parole, sono le unità di distribuzione nel sistema. Le estensioni sono distribuite come moduli, e l'aspetto visivo è distribuito come temi.

Un tema è in genere un mazzo di immagini, fogli di stile e modello di sostituzioni, confezionati in una directory sotto la directory di temi. Ha anche un theme.txt file alla sua radice per definire i metadati come l'autore del tema manifesto.

Analogamente, un modulo è una directory sotto la directory di moduli. È anche una pagina ASP.Zona di NET MVC, con pochi colpi di scena. Ad esempio, ha bisogno di un file manifesto module.txt aggiuntive che dichiara alcuni metadati per il modulo, come il suo autore, sito Web, i nomi di funzionalità, dipendenze o numero di versione.

Essendo solo una zona di un sito più grande, un modulo deve giocare bello con poche risorse condivise. Ad esempio, le rotte che utilizza devono essere definite da una classe che implementa IRouteProvider. Frutteto costruirà la tabella completa rotta da che cosa è un contributo di tutti i moduli. Analogamente, moduli possono contribuire a costruire il menu admin implementando l'interfaccia INavigationProvider.

Curiosamente, il codice per i moduli non è solito consegnato come file binari compilati (anche se questo è tecnicamente possibile). Questa era una decisione consapevole che abbiamo fatto per incoraggiare il modulo hacking, dove si inizia da un modulo che puoi scaricare dalla Galleria e modificarlo per risolvere le vostre esigenze specifiche. Essere in grado di ottimizzare il codice per niente è uno dei punti di forza di un PHP CMS come Drupal o WordPress, e abbiamo voluto fornire questo stesso tipo di flessibilità nel frutteto. Quando si scarica un modulo, è scaricare il codice sorgente e che il codice viene compilato in modo dinamico. Se si apporta una modifica in un file di origine, la modifica viene raccolto e il modulo viene ricompilato.

Inserimento delle dipendenze

Finora, ho ho concentrato su un tipo di estensibilità, il sistema di tipi, perché che rappresenta la maggior parte dei moduli di frutteto, ma ci sono molti altri punti di estendibilità — troppi, infatti, per me, per elencare qui. Ho ancora voglia di aggiungere alcune cose circa i principi generali che sono all'opera in tutto il quadro.

Una cosa fondamentale per ottenere ragione in un sistema altamente modulare è accoppiamento lasco. Nel frutteto, quasi tutto sopra a basso livello dell'impianto idraulico è un modulo. Anche moduli sono gestite da un modulo! Se si desidera che questi moduli di funzionare come indipendentemente uno da altro come possibile — se si desidera un'implementazione di una caratteristica di essere swap con un altro — non si possono avere dipendenze dure.

Una soluzione chiave per raggiungere questo obiettivo consiste nell'utilizzare dependency injection. Quando avete bisogno di utilizzare i servizi da un'altra classe, è non solo istanziare, come che stabilirebbe una dipendenza difficile da tale classe. Invece, si inietta un'interfaccia che implementa questa classe, come un parametro del costruttore (vedere Figura 6).

Figura 6 iniettando dipendenze attraverso parametri del costruttore

private readonly IRepository<ContentTagRecord> _contentTagRepository;
private readonly IContentManager _contentManager;
private readonly ICacheManager _cacheManager;
private readonly ISignals _signals;
public TagCloudService(
  IRepository<ContentTagRecord> contentTagRepository,
  IContentManager contentManager,
  ICacheManager cacheManager,
  ISignals signals)
  _contentTagRepository = contentTagRepository;
  _contentManager = contentManager;
  _cacheManager = cacheManager;
  _signals = signals;
}

In questo modo la che vostra dipendenza è l'interfaccia, non la classe e l'attuazione può essere scambiato senza dover modificare il codice. L'implementazione specifica dell'interfaccia che ottiene iniettato non è più la decisione del consumatore dell'interfaccia. Controllo è inverso qui, ed è il quadro che rende tale decisione.

Naturalmente, questo non è limitato alle interfacce che definisce frutteto. Ogni modulo può fornire i propri punti di estendibilità dichiarando semplicemente un'interfaccia che deriva da IDependency. È davvero così semplice.

Alcuni altri punti di estendibilità

Ho solo scalfito la superficie di estensibilità qui. Ci sono molte interfacce che possono essere utilizzate nel frutteto di estendere il sistema in modo creativo. Si potrebbe anche dire che Orchard essenzialmente è nient'altro che un motore di estensibilità. Tutti i pezzi del sistema sono swap ed estensibile.

Prima di concludere questo articolo, accennerò alcuni delle interfacce più utili al sistema che si potrebbe desiderare di check-out. Non ho quasi abbastanza spazio qui per entrare in questi in profondità, ma posso darti puntatori, e si può andare nel codice e seguire l'utilizzo delle interfacce. Questo è un modo veramente grande per imparare, tra l'altro.

  • IWorkContextAccessor consente al vostro codice accedere al contesto di lavoro per la richiesta corrente. Il contesto di lavoro, a sua volta, consente di accedere a HttpContext, attuale Layout, configurazione del sito, user, tema e cultura. Fornisce inoltre servizi per ottenere un'implementazione di un'interfaccia, da quei luoghi dove non si può fare dependency injection o dove è necessario ritardare fino a dopo la costruzione.
  • IContentManager fornisce tutto il che necessario per eseguire query e gestire gli elementi di contenuto.
  • IRepository <T> dà accesso ai metodi di accesso dati di livello inferiore, per quei tempi, quando IContentManager non è sufficiente.
  • IShapeTableProvider consente una barcata di scenari di manipolazione forma on-the-fly. In sostanza, si collegare agli eventi su forme, e da loro è possibile creare forme alternative per essere utilizzato in determinate situazioni, trasformare forme, aggiungere membri a loro, li intorno a muoversi nel layout e così via.
  • IBackgroundTask, IScheduledTask e IScheduled­TaskHandler sono le interfacce da usare se avete bisogno di ritardo o ripetute attività deve essere eseguito in background.
  • IPermissionsProvider consente i moduli per esporre le proprie autorizzazioni.

Ulteriori informazioni

Orchard è una potenza di estendibilità e presentare il tutto che ha da offrire in questo spazio limitato è una sfida. La mia speranza è che ti ho dato la voglia di saperne di più su di esso e tuffarsi profondo. Abbiamo una comunità vivace e cordiale su orchard.codeplex.com/discussions che sarà lieto di guidarvi e rispondere alle vostre domande. Con così tanto da implementare e tante idee per esplorare, qui è una grande occasione per fornire un contributo significativo.

Bertrand Le Roy iniziò la sua carriera di sviluppatore professionista nel 1982, quando pubblicò il suo primo videogioco. Ha pubblicato nel 2002, ciò che è stato probabilmente il primo CMS per eseguire in ASP.NET. Un anno dopo, è stato assunto da Microsoft ASP.NET team e si trasferì negli Stati Uniti. Ha lavorato su ASP.NETTE versioni 2.0-4 e ASP.NET AJAX; contribuito a rendere jQuery parte ufficiale della.NET del petto strumento dello sviluppatore; e rappresenta Microsoft nel comitato direttivo OpenAjax Alliance.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Sebastien Ros