asp.net comments edit

Ormai, al giorno d’oggi, se non si sviluppa un qualcosa che offra un utilizzo Fluent allora vuol dire che il codice non è figo :D. Ovviamene in Dexter non potevamo rimanere indietro, per cui, per seguire questa “moda”, ho deciso di sviluppare un Resource Manager per effettuare il combine ed il minifier dei javascript e dei css.

Proprio osservando la mia skin mi rendo conto di avere un numero piuttosto alto di includes al suo interno (css del sito, css del codehightlither, shadowbox, etc), e la stessa cosa vale per i js (in realtà sono anche di più dei css). Già in passato (vedi qui) avevo parlato dei vantaggi che si hanno nel ridurre al minimo le chiamate del browser verso il client, ma purtroppo l’approccio mostrato in quel post non era utilizzabile in ASP.NET MVC.

Il goal che mi ero proposto era creare un qualcosa di molto semplice che permettesse di accorpare il numero di files js/css in un unico file e di restituire quest’ultimo effettuando un minify (quindi rimuovendo gli spazi inutili), mettendo in cache il riusultato, con dipendenza sui files in uso.

Ovviamente mi sono dovuto munire di alcuni strumenti che mi rendessero più agevoli alcune operazioni come il minify. Su consiglio di Alessandro, ho deciso di appoggiarmi alla libreria offerta da Yahoo, che svolge il suo lavoro egregiamente. Per il resto, con un paio di handlers, alcune interfacce, una classe ed alcuni extension methods ho raggiunto questo risultato:

<%= ResourceManager.Js()
        .Combine()
        .Minify()
        .Add(SkinJs("codeHighlither/shCore.js"))
        .Add(SkinJs("codeHighlither/shBrushCSharp.js"))
        .Add(SkinJs("codeHighlither/shBrushXml.js"))
        .Add(SkinJs("XFNHighlighter.js"))
        .Add(SkinJs("jquery.cookie.js"))
        .Add(SkinJs("jquery.highlight-3.yui.js"))
        .Add(SkinJs("customFunction.js"))
        .Add(SkinJs("xVal.jquery.validate.js"))
        .Add(SkinJs("shadowbox.js"))
        .Render()
%>

<%= ResourceManager.Css()
        .Combine()
        .Minify()
        .Add(SkinCss("Site.css"))
        .Add(SkinCss("XFNHighlighter.css"))
        .Add(SkinCss("shCore.css"))
        .Add(SkinCss("shThemeDefault.css"))
        .Add(SkinCss("shadowbox.css"))
        .Add(SkinCss("CodeSnippets.css"))
        .Render()
%>

A pagina renderizzata si ha una chiamata all’handler in cui i files da comprimere vengono passati in querystring (ho dovuto comprimere il risultato per evitare di eccedere nella lunghezza massima utilizzabile da una querystring), che svolgerà il resto del lavoro.

La cosa che mi è piaciuta di più è che, con questa classe, anche se non si vuole effettuare né il combine né il minify, quindi renderizzare i semplici tag per i css e js come nelle normali pagine, il risultato è più comodo e veloce; di fatto il seguente snippet:

<%= ResourceManager.Js()
        .DoNotCombine()
        .DoNotMinify()
        .Add(SkinJs("codeHighlither/shCore.js"))
        .Add(SkinJs("codeHighlither/shBrushCSharp.js"))
        .Render()
%>

renderizza il seguente markup:

<script src="/codeHighlither/shCore.js" type="text/javascript"></script>
<script src="/codeHighlither/shBrushCSharp.js" type="text/javascript"></script>

Giusto per aggiungere un po’ di numeri al post, nella mia skin sono riuscito ad accorpare 9 files javascript in un un’unico file, riducendo il peso da 79kb a 60kb; inoltre, accorpando 6 files css, ho ottenuto una riduzione quasi del 30% (53kb contro i 38kb del minify).

Un paio di screenshot per mostrare il fantaggio tratto da tale approccio.

notCombined

Combined

A breve il codice sorgente sia dell’interfaccia fluent che degli httphandler per il minify.

Stay tuned.

asp.net comments edit

Ho approfittato del recente rilascio e della possibilità di utilizzare ASP.NET MVC 2.0 su Visual Studio RC per effettuare un po’ di refactoring del repository di Dexter e migrarlo alla versione 2.0 di MVC.

Per chi segue lo sviluppo del codice la vecchia trunk è stata rinomina in “trunk (mvc 1.0)” e la considero come deprecata, non ci saranno sviluppi sopra. Il lavoro si è spostato su una branch (che poi è la nuova trunk) che gira su asp.net mvc 2.
Il porting è stato semplicissimo, basta utilizzare l’apposito tool (lo trovate qui) e, nel caso di dexter, è stato necessario modificare un solo metodo (causa nuovo overload) per il controllerbase.

Una nota da segnalare sono i progetti di Testing, che Visual Studio 2010 converte al Framwework 4.0 e non permette il reverse. Quindi per questi progetti è necessario aprire la solution di VS 2010.

La semplicità di questo porting mostra la buona base che c’è su questo prodotto. Il prossimo step per dexter sarà rimuovere tutti i Frameworks e le implementazioni aggiunte alla versione 1.0 di mvc, per colmare alcune mancanze della vecchia versione (come la validazione client side).

Ah, dimenticavo, www.tostring.it gira su mvc 2.0 :)

Ciauz

asp.net comments edit

Finalmente è stata rilasciata la versione definitiva di ASP.NET MVC 2. Il download è disponibile qui e soltanto per Visual Studio 2008 (di fatto Visual Studio 2010 integrerà già in sé la versione 2 di ASP.NET MVC).
Ovviamente non ci sono novità di rilievo in quanto la RC2 era già features complete. Ciò su cui si è sicuramente lavorato sono le performances, la stabilità ed un po’ di bugfixinig.

Ovviamente è appena stata rilasciata la versione 2 e già si parla della versione 3, infatti a questo indirizzo potete trovare la roadmap della prossima release, che riporto qui di seguito:

 

Now that ASP.NET MVC 2 is released, it's time to start planning for ASP.NET MVC 3. This roadmap will be updated with more information as we get further in our planning process.
Here is a list of some of the high-level areas of focus we're looking at.

  • Productivity - ASP.NET MVC 3 should provide ways to make application developers more productive when building web applications.
    • Tasked based helpers - These helpers focus on tasks that commonly come up when building web applications such as adding a CAPTCHA to a site or providing a sortable, pageable grid of data.
    • Validation Improvements - Support for more validation attributes such as the new ones introduced in ASP.NET 4 as well as possibly a few new ones such as an email validation attribute.
    • View Engine Options - We're experimenting with a new streamlined view engine syntax as well as making it easier to pull in community view engines such as Spark into a project.
    • Command line tools - As we add new tooling for ASP.NET MVC to Visual Studio, we will look at providing command line alternatives for the same functionality
  • Ajax - Modern applications make use of Ajax to provide a rich experience for their users. ASP.NET MVC 3 should make building such applications much easier.
    • More Ajax helpers - New Ajax helpers may leverage libraries such as jQuery UI to provide widgets such as a calendar date picker, autocomplete, etc.
    • Multiple Partial Updates - The existing Ajax helpers, such as Ajax.BeginForm and Ajax.ActionLink, only update one element in the page when receiving a partial view from an action method. Support for multiple partial updates would allow the Ajax helpers to receive multiple partial views and update multiple sections of the page with those views.
    • Client Templates Support - Client templates enable you to format and display a single data item or set of data items by using a fragment of HTML. ASP.NET MVC 3 may provide the ability to connect client templates easily with an action method which returns and receives JSON data.
  • Architecture - ASP.NET MVC 3 should contain architectural improvements which provide benefits such as improved extensibility that enables developers to more easily bend the framework to their will.
    • Dependency Injection at all levels - We're looking at opening up more seams for applying the dependency injection when instantiating components of the framework. This would allow developers to hook into the creation of models during model binding, action filters, etc.
    • MEF Controller Factory - MEF is a new library in .NET for building applications that can be incrementally extended without requiring modification. Enabling MEF by default within the default controller factory would allow for extensibility scenarios out of the box, while still allowing for 3rd party dependency injection frameworks to be used as alternatives to MEF.
    • Application scaffolding - Scaffolding allows for quickly implementing simple Create, Read, Update, Delete pages against a model which provide a nice starting point for quickly getting data into an application during development.
  • Performance - As always, we're looking at ways to make ASP.NET MVC 3 blazingly fast.
    • Improved Caching Support - Enable caching of an child action when called via the RenderAction method. Also looking at donut caching and donut hole caching.
    • More Control Over Session - Support for granular session state as well as turning session state on and off on a per action/controller basis.

 

Buon download.

.net comments edit

Il logging è una parte fondamentale dell’applicazione; se implementato correttamente ed inserito nei posti giusti può salvare da lunghi down applicativi e molto altro ancora.

Pur potendo una persona essere bravissima a scrivere del codice, purtroppo (o per fortuna, dipende dai punti di vista) la stabilità e le problematiche dell’applicazione non dipendono soltanto dallo sviluppatore. Di fatto può cadere la connessione verso  il database, possono esserci problemi di rete verso la SAN, un update può destabilizzare l’applicativo, ma per l’utente finale la colpa sarà sempre dell’applicazione; del resto è un punto di vista comprensibile, in quanto ciò che interessa è usare l’applicazione e non le eventuali problematiche esterne che ne possono causare un crash.

Appreso il fatto che, qualsiasi sia il problema, sta a noi trovarlo, passando poi la palla a chi di dovere (sistemista, tecnico di rete, etc), è comunque utile sapere che proprio in questi casi il logging può aiutare a ridurre al minimo il tempo necessario per poter identificare un bug applicativo e/o un problema di infrastruttura.

Purtroppo io sono abituato ad aggiungere al Dictionary Data delle eccezioni alcune informazioni riguardo al contenuto della chiamata, come dei parametri o delle variabili calcolate (vedi qui); inoltre, per tutte le applicazioni web, mi piace essere a conoscenza dell’esatta richiesta che è stata effettuata, quindi, per entrare un po’ più nel dettaglio, per ogni errore voglio sapere:

  • Url della richiesta;
  • RawURL nel caso esista un sistema di routing o rewriting;
  • UrlReferrer, se esiste;
  • UserAgent, se esiste;
  • UserHostName;
  • Tutte le ServerVariables;
  • Tutti i valori provenienti dalla Form;
  • ServerName, nel caso mi trovi in un sistema di load balancing;
  • ThreadLanguage;

Ovviamente tutte queste informazioni sono prensenti nel HttpContext, ma inserirle a mano ogni volta non è il massimo della comodità, così come non lo è crearsi una classe di helper perchè spesso il Logger può essere instanziato tramite un Framework di Inversion Of Control come Castle (che offre già una serie di facilities a riguardo). Per concludere, il Logger deve aggiungere da solo tutto ciò che è ripetitivo.

Purtroppo il Logger di default non salva queste informazioni (né le informazioni aggiuntive della collection data, né il contesto web), ma è comunque possibile realizzare un layout base che aggiunga all’output del Logger le informazioni necessarie.

Personalmente utilizzo come output un file xml (il discorso rimane lo stesso anche se si opta per un’altra soluzione) perchè è compatibile con il mio log viewer preferito, Log4View, che offre moltissime opzioni, tra cui la possibilità di effettuare logging tramite nettcp su un client remoto.

Chi segue il codice di Dexter può già trovare questa implementazione - che qui riporto solo per le parti relative all’argomento - al suo interno:

protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent)
{
    if (DexterEnvironment.Context.IsSafe)
    {
        try
        {
            loggingEvent.Properties["Url"] = DexterEnvironment.Context.CurrentRequestUri;
            loggingEvent.Properties["UrlReferrer"] = DexterEnvironment.Context.CurrentUrlReferrer;
            loggingEvent.Properties["UserAgent"] = DexterEnvironment.Context.CurrentUserAgent;
            loggingEvent.Properties["UserHostName"] = DexterEnvironment.Context.CurrentUserHostName;
            loggingEvent.Properties["ServerVariables"] = ServerVariables(DexterEnvironment.Context.CurrentServerVariables);
            loggingEvent.Properties["Form"] = ServerVariables(DexterEnvironment.Context.CurrentForm);
            loggingEvent.Properties["RawURL"] = DexterEnvironment.Context.CurrentRawUrl;//ctx.Request.RawUrl;
            loggingEvent.Properties["ServerName"] = DexterEnvironment.Context.CurrentServerMachineName;
            loggingEvent.Properties["ThreadLanguage"] = Thread.CurrentThread.CurrentCulture.DisplayName;
        }
        catch
        {
            //needed because the logging called from the appliction start che throw a new exception
            //more info here:http://mvolo.com/blogs/serverside/archive/2007/11/10/Integrated-mode-Request-is-not-available-in-this-context-in-Application_5F00_Start.aspx
        }
    }

    writer.WriteStartElement(mElmEvent);
    writer.WriteAttributeString("logger", loggingEvent.LoggerName);
    writer.WriteAttributeString("timestamp", XmlConvert.ToString(loggingEvent.TimeStamp, XmlDateTimeSerializationMode.Local));
    writer.WriteAttributeString("level", loggingEvent.Level.DisplayName);
    writer.WriteAttributeString("thread", loggingEvent.ThreadName);
    
    if (!string.IsNullOrEmpty(loggingEvent.Domain))
        writer.WriteAttributeString("domain", loggingEvent.Domain);

    if (!string.IsNullOrEmpty(loggingEvent.Identity))
        writer.WriteAttributeString("identity", loggingEvent.Identity);

    if (!string.IsNullOrEmpty(loggingEvent.UserName))
        writer.WriteAttributeString("username", loggingEvent.UserName);

    writer.WriteStartElement(mElmMessage);

    if (!Base64EncodeMessage)
        Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, InvalidCharReplacement);
    else
    {
        byte[] bytes = Encoding.UTF8.GetBytes(loggingEvent.RenderedMessage);
        string textData = Convert.ToBase64String(bytes, 0, bytes.Length);
        Transform.WriteEscapedXmlString(writer, textData, InvalidCharReplacement);
    }
    writer.WriteEndElement();
    
    PropertiesDictionary properties = loggingEvent.GetProperties();
    if (properties.Count > 0)
    {
        writer.WriteStartElement(mElmProperties);
        foreach (DictionaryEntry entry in properties)
        {
            writer.WriteStartElement(mElmData);
            writer.WriteAttributeString("name", Transform.MaskXmlInvalidCharacters((string) entry.Key, InvalidCharReplacement));
            string str2;
            if (!Base64EncodeProperties)
                str2 = Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value), InvalidCharReplacement);
            else
            {
                byte[] inArray = Encoding.UTF8.GetBytes(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value));
                str2 = Convert.ToBase64String(inArray, 0, inArray.Length);
            }
            writer.WriteAttributeString("value", str2);
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
    }
    
    if (loggingEvent.ExceptionObject != null)
    {
        StringBuilder exceptionString = new StringBuilder();

        exceptionString.Append(loggingEvent.ExceptionObject);
        exceptionString.Append(Environment.NewLine);

        foreach (DictionaryEntry de in loggingEvent.ExceptionObject.Data)
        {
            exceptionString.Append(de.Key.ToString());
            exceptionString.Append(": ");
            exceptionString.Append(de.Value.ToString());
            exceptionString.Append(Environment.NewLine);
        }
        if ((exceptionString.Length > 0))
        {
            writer.WriteStartElement(mElmException);
            Transform.WriteEscapedXmlString(writer, exceptionString.ToString(), InvalidCharReplacement);
            writer.WriteEndElement();
        }
    }
    if (LocationInfo)
    {
        LocationInfo locationInformation = loggingEvent.LocationInformation;
        writer.WriteStartElement(mElmLocation);
        writer.WriteAttributeString("class", locationInformation.ClassName);
        writer.WriteAttributeString("method", locationInformation.MethodName);
        writer.WriteAttributeString("file", locationInformation.FileName);
        writer.WriteAttributeString("line", locationInformation.LineNumber);
        writer.WriteEndElement();
    }
    writer.WriteEndElement();
}

Come potete vedere tutto si svolge nel metodo FormatXml, dove è possibile aggiungere, rimuovere e leggere alcune informazioni. Nella prima parte vado ad inserire le informazioni inerenti il contesto web, quindi la url richiesta, il rawurl, useragent, etc, mentre più avanti renderizzo tutte le informazioni del Dictionary Data dell’eccezione.

Per poter utilizzare questa customizzazione è sufficiente specificare nel file di log4net la classe per il rendering delle informazioni, come mostrato di seguito:

<appender name="NHibernateFileLog" type="log4net.Appender.RollingFileAppender">
    <file value="Logs/nHibernate.txt" />
    <maximumFileSize value="1000KB" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="5" />
    <layout type="Dexter.Shared.Log.Log4Net.XmlLayout">
    </layout>
</appender>

Chi è interessato all’implementazione completa del XmlLayout può guardare qui.

Ciauz

web dev comments edit

Quando il tempo necessario richiesto a fornire una determinata informazione eguaglia il tempo necessario allo sviluppo di un sistema di risposta automatico, forse è il caso di fare qualcosa :).

Da tale situazione è nata l’esigenza di creare un sistema che configurasse in automatico Windows Live Writer.

In quest’ ultimo periodo, insieme agli altri ragazzi, abbiamo rilasciato diverse versioni di Dexter ed abbiamo lavorato in particolar modo con l’integrazione su Windows Live Writer. Questo ha causato diversi reset della configurazione di WLW da parte di tutti gli utilizzatori (in realtà ora sono solo i beta tester) che, giustamente, mi chiedevano quale API Dexter implementasse e quale fosse la sua url.

Da qui è nata la decisione di creare un qualcosa che fornisse automaticamente a WLW le informazioni necessarie per autoconfigurarsi.

Grazie ad una dritta del buon Alessandro ho scoperto che è possibile fare ciò tramite un file RSD (Really Simple Discoverability 1.0), che non fa altro che esporre le informazioni necessarie dei providers implementati tramite una struttura xml.

La struttura seguente mostra il file del mio blog:

<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
    <service>
        <engineName>Dexter Blog Engine</engineName>
        <engineLink>http://dexterblogengine.codeplex.com/</engineLink>
        <homePageLink>http://tostring.it/</homePageLink>
        <apis>
            <api name="MetaWeblog" blogID="1" preferred="false" apiLink="http://tostring.it/metaweblog.axd" />
        </apis>
    </service>
</rsd>

Come potete vedere è tutto piuttosto semplice, l’unica nota (oltre al dexter blog engine :P) è il nodo APIs, dove è possibile specificare le varie API esposte dal blog engine (nel mio caso solo i metaweblog API).

Nella home page del sito basta inserire il seguente tag ed il gioco è fatto:

<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://tostring.it/MetaWeblogRsd.axd" />

Per quanto riguarda WLW, basta configurarlo specificando la Home Page: lui effettuerà il parser del markup in ricerca dell’apposito tag e si autoconfigurerà.

Di Seguito alcuni screenshots sulla procedura di configurazione

wlw_001  wlw_002

 wlw_003 wlw_004

Enjoy your favorite blog engine (dexter)

Ciauz

silverlight comments edit

Silverlight 4 ha introdotto la possibilità di eseguire operazioni in OutOfBrowser anche in FullTrustMode. Inutile dire che questa è una delle features più interessanti della prossima release di SIlverlight, in quanto ci permette di poter accedere a risorse precedentemente non accessibili come Fotocamere, Hard Drives, Office Documents, etc.

Ovviamente però, oltre a specificare che l’applicazione richiede permessi elevati, è necessario che l’utente autorizzi l’applicazione ad eseguire operazioni anche fuori dalla propria sandbox. Dato che non c’è certezza che l’utente approvi o no tali operazioni è sicuaramente utile capire da codice se ciò è avvenuto e, nel caso, cambiare il comportamento dell’applicazione e/o gestirne eventuali errori.

Come prima cosa è necessario specificare che l’applicazione richiede permessi elevati; questo è possibile dalla finestra delle proprietà del progetto, come mostrato dallo screenshot seguente:

Silverlight_OOB_FullTrust1

Per la parte codice ci basta verificare tramite l’apposità proprietà se l’utente ha acconsentito il FullTrust mode, come mostrato nello snippet seguente:

if(!App.Current.HasElevatedPermissions)
    MessageBox.Show("You have to accept the Full Trust Mode!");

Ciauz

silverlight comments edit

Nel post precedente avevo mostrato come verificare se l’applicaizone Silverlight fosse installata o meno sul client. L’idea di questo post è di mostrare come installare l’applicazione tramite un pulsante custom presente all’interno della pagina ed eseguire la procedura di installazione via C#.

Lo snippet seguente mostra l’evento OnClick del button per l’installazione:

private void InstallButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
    if (!App.Current.IsRunningOutOfBrowser && App.Current.InstallState == InstallState.NotInstalled)
    {
        App.Current.Install();
    }
}

Come avrete potuto notare, fin’ora tutte le informazioni necessarie per poter lavorare sono state esposte tramite la classe System.Windows.Application; grazie a quest’ultima, nei prossimi post si vedrà come verificare se l’applicazione è in esecuzione in fulltrust mode e, nel caso ne esista una più recente, come aggiornarla.

Ciauz

eventi comments edit

Avevo annunciato una serie di posts che affrontavano un po’ la vita OutOfBrowser di un’applicazione sviluppata con Silverlight 4 ma poi, proprio quando la cosa si faceva più interessante, sono sparito.

Per vari motivi, e devo dire alcuni anche piacevoli, ho dovuto trascurare il blog. Un po’ perchè da questa settimana non sono più in MTV (dove ho passato più di tre anni bellissimi) e mi trovo a lavorare con quel “matto” di Mauro in Gaia, ed un po’ perchè sono stato all’MVP Summit in Microsoft, a Seattle.

Devo dire che quest’ultima è stata un’esperienza stupenda: girare per il Campus, conoscere Scott Guthrie, parlare via Twitter con Scott Hanselman mentre tiene una sessione…è stato emozionante. In più ho avuto l’occasione di conoscere di persona alcuni italiani che lavorano al campus, nello specifico Vittorio, Enzo (aka paperino) e Giorgio, che mi hanno offerto anche il pranzo e sono stati gentilissimi.

Putroppo non posso raccontare molto del Summit (è tutto sotto NDA), ma posso solo dire che l’esperienza è stata sopra le aspettative e che tutti gli Italians Boys (Raf, Alessandro, Alead, Janky, Davide Mauri, Andrea, Davide Zordan, Giorgio, Leone, Valter ed il mio super roommate Gian Maria) sono stati mitici.

Sotto Alcune foto.

summit_tutti mvpsummitScott

_MG_3768  _MG_3528

Stay tuned!