asp.net comments edit

Nella prima versione di ASP.NET MVC non è presente la possibilità di gestire i temi nelle applicazioni: ciò significa che, per tutti i siti sviluppati con questo Framework che necessitino di una forte customizzazione dell’interfaccia web, è necessario realizzare un’implementazione custom in grado di far ciò.

Com’è possibile apprendere dalla roadmap della versione 2.0 di ASP.NET MVC qui, che sarà inglobata in ASP.NET, avrà incluso un sistema per la gestione dei temi; in attesa del rilascio, una possibile soluzione per affrontare il problema è quella di sostituire il WebFormViewEngine con uno realizzato ad hoc, in grado di cambiare la View in base al tema scelto.

Sebbene sia possibile realizzare forti customizzazioni dell’interfaccia cambiando semplicemente il foglio di stile della pagina, a volte questo può non bastare in quanto si necessita di un particolare markup di output, tipo HTML Strict, Transitional, ecc;
per rendersi meglio conto della situazione basta pensare ad un dispositivo mobile, la cui visualizzazione è differente da quella di browser su un normale computer, e alle molte interazioni che da una parte avvengono tramite javascript, dall’altra devono avvenire senza.

L’immagine seguente mostra la nuova struttura per le Views e i Contents di un’applicazione che fa uso del Framework ASP.NET MVC:

 

Temi

Per far sì che l’applicazione cambi il percorso da cui leggere la View, è necessario realizzare un nuovo WebFormViewEngine; nell’esempio, come quello mostrato di seguito, si chiamerà WebFormThemeViewEngine:

public class WebFormThemeViewEngine : WebFormViewEngine
{
    private static readonly string[] masterLocationFormats = new[] { "~/Themes/{2}/Views/{1}/{0}.master", "~/Themes/{2}/Views/Shared/{0}.master" };
    private static readonly string[] viewLocationFormats = new[] { "~/Themes/{2}/Views/{1}/{0}.aspx", "~/Themes/{2}/Views/{1}/{0}.ascx", "~/Themes/{2}/Views/Shared/{0}.aspx", "~/Themes/{2}/Views/Shared/{0}.ascx" };

    public WebFormThemeViewEngine()
    {
        MasterLocationFormats = masterLocationFormats;
        ViewLocationFormats = viewLocationFormats;
        PartialViewLocationFormats = viewLocationFormats;
    }

    protected override bool FileExists(ControllerContext controllerContext, string virtualPath)
    {
        try
        {
            return File.Exists(controllerContext.HttpContext.Server.MapPath(virtualPath));
        }
        catch (HttpException exception)
        {
            if (exception.GetHttpCode() != 0x194)
            {
                throw;
            }
            return false;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Finds the view.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="viewName">Name of the view.</param>
    /// <param name="masterName">Name of the master.</param>
    /// <param name="useCache">if set to <c>true</c> [use cache].</param>
    /// <returns>The page view.</returns>
    /// <exception cref="ArgumentNullException"><c>controllerContext</c> is null.</exception>
    /// <exception cref="ArgumentException">viewName must be specified.</exception>
    public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
    {
        string[] strArray;
        string[] strArray2;

        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");
        
        if (string.IsNullOrEmpty(viewName))
            throw new ArgumentException("viewName must be specified.", "viewName");

        string themeName = GetThemeToUse(controllerContext);

        string requiredString = controllerContext.RouteData.GetRequiredString("controller");

        string viewPath = GetPath(controllerContext, ViewLocationFormats, viewName, themeName, requiredString, "View", useCache, out strArray);
        string masterPath = GetPath(controllerContext, MasterLocationFormats, masterName, themeName, requiredString, "Master", useCache, out strArray2);

        if (!string.IsNullOrEmpty(viewPath) && (!string.IsNullOrEmpty(masterPath) || string.IsNullOrEmpty(masterName)))
            return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
            
        return new ViewEngineResult(strArray.Union(strArray2));
    }

    /// <summary>
    /// Finds the partial view.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="partialViewName">Partial name of the view.</param>
    /// <param name="useCache">if set to <c>true</c> [use cache].</param>
    /// <returns>The partial view.</returns>
    /// <exception cref="ArgumentNullException"><c>controllerContext</c> is null.</exception>
    /// <exception cref="ArgumentException">partialViewName must be specified.</exception>
    public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
    {
        string[] strArray;
        if (controllerContext == null)
            throw new ArgumentNullException("controllerContext");
            
        if (string.IsNullOrEmpty(partialViewName))
            throw new ArgumentException("partialViewName must be specified.", "partialViewName");

        string themeName = GetThemeToUse(controllerContext);

        string requiredString = controllerContext.RouteData.GetRequiredString("controller");
        string partialViewPath = GetPath(controllerContext, PartialViewLocationFormats, partialViewName, themeName, requiredString, "Partial", useCache, out strArray);
        if (string.IsNullOrEmpty(partialViewPath))
        {
            return new ViewEngineResult(strArray);
        }
        return new ViewEngineResult(CreatePartialView(controllerContext, partialViewPath), this);
    }

    private static string GetThemeToUse(ControllerContext controllerContext)
    {
        return controllerContext.HttpContext.Items["theme"] as string ?? "Default";
    }

    /// <summary>
    /// Gets the path.
    /// </summary>
    /// <param name="controllerContext">The controller context.</param>
    /// <param name="locations">The locations.</param>
    /// <param name="name">The name.</param>
    /// <param name="themeName">Name of the theme.</param>
    /// <param name="controllerName">Name of the controller.</param>
    /// <param name="cacheKeyPrefix">The cache key prefix.</param>
    /// <param name="useCache">if set to <c>true</c> [use cache].</param>
    /// <param name="searchedLocations">The searched locations.</param>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException">locations must not be null or emtpy.</exception>
    private string GetPath(ControllerContext controllerContext, string[] locations, string name, string themeName, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
    {
        searchedLocations = new string[0];
        if (string.IsNullOrEmpty(name))
            return string.Empty;
            
        if ((locations == null) || (locations.Length == 0))
            throw new InvalidOperationException("locations must not be null or emtpy.");

        bool flag = IsSpecificPath(name);
        string key = CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, themeName);
        
        if (useCache)
        {
            string viewLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
            
            if (viewLocation != null)
                return viewLocation;
        }
        
        if (!flag)
            return GetPathFromGeneralName(controllerContext, locations, name, controllerName, themeName, key, ref searchedLocations);
        
        return GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
    }

    private static bool IsSpecificPath(string name)
    {
        char ch = name[0];
        
        if (ch != '~')
            return (ch == '/');
        
        return true;
    }

    private string CreateCacheKey(string prefix, string name, string controllerName, string themeName)
    {
        return string.Format(CultureInfo.InvariantCulture, ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}", new object[] {GetType().AssemblyQualifiedName, prefix, name, controllerName, themeName});
    }

    private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string themeName, string cacheKey, ref string[] searchedLocations)
    {
        string virtualPath = string.Empty;
        searchedLocations = new string[locations.Length];
        for (int i = 0; i < locations.Length; i++)
        {
            string str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] {name, controllerName, themeName});

            if (FileExists(controllerContext, str2))
            {
                searchedLocations = new string[0];
                virtualPath = str2;
                ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
                return virtualPath;
            }
            searchedLocations[i] = str2;
        }
        return virtualPath;
    }

    private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
    {
        string virtualPath = name;
        if (!FileExists(controllerContext, name))
        {
            virtualPath = string.Empty;
            searchedLocations = new[] {name};
        }
        ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
        return virtualPath;
    }
}

Come si può notare, basta replicare i metodi delle classi WebFormViewEngine e VirtualPathProviderViewEngine, andando a cambiare i percorsi tramite le 3 variabili impostate dal costruttore dell’engine.
È importante notare il metodo che restituisce il tema corrente, GetThemeToUse, che sarà utilizzato per andare a costruire il path da dove leggere le Views e i Contents.
Nell’esempio mostrato è stata utilizzata la collection Items dell’HttpContext in modo da poter cambiare anche solo provvisoriamente il tema da visualizzare (http://localhost:1659/?theme=yellow), come mostrato dallo snippet seguente:

public abstract class ControllerBase : Controller
{
    private string currentTheme;

    protected override void Initialize(RequestContext requestContext)
    {
        //Andrebbe recuperato dal profilo
        string defaultTheme = "Default";

        string previewTheme = requestContext.HttpContext.Request.QueryString["theme"];

        requestContext.HttpContext.Items["theme"] = !string.IsNullOrEmpty(previewTheme) ? previewTheme : defaultTheme;

        currentTheme = (string)requestContext.HttpContext.Items["theme"];

        base.Initialize(requestContext);

        ViewData["ThemeName"] = currentTheme;
    }
}

Questa funzione può risultare particolamente comoda quando si ha la necessità di vedere un’anteprima di una skin con i propri contenuti.

Per concludere ed utilizzare la nuova skin è necessario registrare il nuovo WebFormViewEngine nel file Global.asax, come mostrato di seguito:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);

    // Sostituisco il Default WebFormViewEngine con il nostro WebFormThemeViewEngine
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new WebFormThemeViewEngine());
}

Ciauz

 

Download Esempio qui

 

.net comments edit

Ancora oggi noto come il metodo GetHashCode risulti un mistero per molte persone (nascerebbe la domanda: Ma se non lo conosci perchè lo implementi?).
Proprio ieri mi è capitato di leggere un articolo in rete e trovare al suo interno un'implementazione del metodo GetHashCode, come il seguente:

public class Book
{
    public Book()
    {
        BookID = -1;
        BookTitle = string.Empty;
    }


    public int BookID { get; set; }
    public string BookTitle { get; set; }


    public override string ToString()
    {
        return BookTitle ?? string.Empty;
    }


    public override bool Equals(object obj)
    {
        var book = obj as Book;
        if (book != null)
            return (BookID == book.BookID);
        return false;
    }


    public override int GetHashCode()
    {
        return string.Format("{0}{1}", BookID, BookTitle).GetHashCode();
    }
}

Come dice il buon Jeffrey Richter (padre del CRL) in "CLR via C#", l’HashCode è rappresentativo dell'oggetto e deve essere consistente con la relazione di uguaglianza.

Andando a ridefinire quest’ultima tramite il metodo Equals è necessario fare lo stesso anche per il metodo GetHashCode, e di fatto il compilatore solleva un Warning se ciò non avviene.
Questo serve a garantire che due oggetti definiti uguali abbiano lo stesso HashCode, altrimenti alcuni classi (come l'Hashtable, HashSet<T>, Dictionary<T,K>, ecc) potrebbero non funzionare.

L'implementazione del metodo GetHashCode deve rispettera tre criteri:

- Se due oggetti dello stesso tipo rappresentano lo stesso valore, la funzione hash deve restituire lo stesso valore di costante per entrambi gli oggetti.

- Per ottenere le migliori prestazioni, una funzione hash deve generare una distribuzione casuale per tutti gli input.

- La funzione hash deve restituire esattamente lo stesso valore indipendentemente da qualsiasi modifica apportata all'oggetto.

Partendo da quest'ultimo punto, si può capire che l'implementazione andrebbe fatta in un ValueType o, al massimo, in una classe in cui i field utilizzati per il calcolo dell'Hash siano readonly e quindi immutabili.

Lo snippet seguente mostra come due classi aventi valori differenti risultino uguali al metodo Equals ma differenti al metodo GetHashCode, a causa dell'errata implementazione mostrata sopra:

var b1 = new Book();
b1.BookID = 10;
b1.BookTitle = "Titolo1";


var b2 = new Book();
b2.BookID = 10;
b2.BookTitle = "Titolo2";


int hc1 = b1.GetHashCode();
int hc2 = b2.GetHashCode();


Console.WriteLine(hc1 == hc2); //ritorna false
Console.WriteLine(b1.Equals(b2)); //ritorna true

Anche se molti esempi in rete riportano l'utilizzo dell'operatore XOR (eXclusive OR) per il calcolo dell'HashCode della classe, io consiglio sempre l'implementazione mostrata da Marco qui, in quanto se si hanno due fields dello stesso tipo e si inverte il loro valore, l'HashCode restituito è sempre lo stesso, mentre non dovrebbe esserlo.
Ovviamente si potrebbe aggiungere che nel .NET Framework moltissime classi (Hashtable, Dictionary, ecc) fanno uso massiccio di questo metodo e ne consegue che invocare uno string.format ad ogni richiesta non è ottimale per l'applicaizone.

Per concludere, fate attenzione e soprattutto interrogatevi sulla reale necessità di reimplementare Equals e GetHashCode.


Ciauz

various comments edit

È stata la mia espressione appena arrivati alla location del “Workshop 3.5 e oltre”, ed è stata la stessa espressione del momento in cui Raf ha concluso la sua super sessione.

Sono stati due giorni di grande entusiamo, emozione e divertimento quelli passati insieme a tutte le persone presenti all’evento, dagli speaker con cui ho condiviso questa esperienza, ai partecipanti con cui ho avuto modo di scambiare opinioni ed idee sulle passioni che ci accomunano.

Personalmente è stato un piacere partecipare come speaker all’evento e, data la forte emozione, non sò se sarei riuscito a farlo senza il supporto di Andrea e di tutti gli altri.
Vedere il proprio nome affiancato a quelli di Raf, Corrado, Roberto, Mauro, Lorenzo, Massimiliano e del compagno di battaglia Andrea - che ringrazio in particolar modo per avermi dato la possibilità di tenere la sessione e per aver accettato di affiancarmi - fa un certo effetto.

Purtroppo, chi non ha avuto la possibilità di partecipare all’evento si è perso delle sessioni spettacolari, dalle novità di C# 4.0, VS2010, TFS 2010, Mono, EntityFramework, Win7, fino al SurfRaf.
Per far capire cosa sia SurfRaf posso solo dire che quanto Microsoft ha fatto con Surface e Apple ha fatto con l’IPhone Raf è riuscito a farlo con un cestino dell’IKEA e, in assoluto, ancora una volta ha dimostrato di essere unico!

Ciauz

asp.net comments edit

Microsoft, attraverso Scott Guthrie qui, ha presentato la prima Beta del Search Engine Optimization
Toolkit.
Questo toolkit offre interessantissime soluzioni per la tematica SEO (Search Engine Optimization)
tramite tre moduli che hanno il compito di aiutare lo sviluppatore nella parte di indicizzazione del
sito web nei vari motori di ricerca.

I moduli in questione sono:

  • Site Analysis;
  • Robots Exclusion;
  • Sitemaps e Sitemap Indexes;

Il primo ha il compito di effettuare un'analisi della struttura di siti (locali o remoti), il loro
contenuto e gli url.
Tramite delle regole predefinite è possibile avere un report su ciò che non risulta conforme alle
pratriche SEO, come gli url errati o la mancanza dell'attributo alt sulle immagini.

Robots Exclusion serve per specificare al motore di ricerca quali pagine deve escludere
dall'indicizzazione. Normalmente questa procedura viene gestita tramite un file robot.txt, ma con il
Search Engine Optimization Toolkit è possibile mantenere e creare questo tipo di file da una
comodissima interfaccia grafica.

L'ultimo modulo serve per la creazione e mantenimento della sitemap.xml del sito in modo da rendere più
accurato il lavoro dei motori di ricerca, specificando proprio in questo file cosa dovranno indicizzare.

Sicuramente interessantissima è l'integrazione con IIS 7 che a differenza di tool esterni ha la
possibilità di accedere ad un numero di informazioni nettamente maggiori, e la possibilità di gestire
tutto tramite interfaccia grafica come mostrano gli screenshot seguenti:

Sarebbe bello capire quanto sia estendibile e se ci fosse la possibilità di una "collaborazione" con il
neonato Bing.

Il download lo potete trovare qui.

Ciauz

asp.net comments edit

In un post precedente avevo parlato dell’utilità dello Script Combine introdotto con la Service Pack 1 di ASP.NET, che permette di ridurre le chiamate dal client verso il server riunendo più files Javascript in un unico file.
Purtroppo questa tecnologia non è presente in ASP.NET MVC, ma è facilmente realizzabile andando a creare un HttpHandler che raggruppi in un’unica richesta tutti i files Javascript, ed ottimizzando il risultato tramite quella tecnica chiamata “Minification” (maggiori info le trovate qui.) che consiste nella rimozione degli spazi, newline, ecc dall’output.

L’HttpHandler avrà il compito di leggere il contenuto dei files Javascript dal disco, riunirli in un’unica stringa, ottimizzarla tramite un’apposita classe e mettere il tutto in cache, in modo da non rielaborare tutti i files ad ogni richiesta.

Nel codice seguente potete trovare l’implementazione dell’HttpHandler, che sfrutta una sezione nel file di configurazione per recuperare i files da unire; la parte di “Minification” è basata su questo ottimo post di Rick Strahl.

public class CombineHandler : IHttpHandler
{
    #region IHttpHandler Members

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
        string javascriptCombineName = context.Request.QueryString["jscn"];
        string javascritpCombineVersion = context.Request.QueryString["v"];

        string cacheKey = string.Format("JSCOMBINE_{0}_{1}", javascriptCombineName, javascritpCombineVersion);

        string output = context.Cache.Get(cacheKey) as string;

        if (string.IsNullOrEmpty(output))
        {
            output = GetValue(javascriptCombineName, context);

            context.Cache.Insert(cacheKey, output);
        }

        context.Response.ContentType = @"text/javascript";
        context.Response.Write(output);
        context.Response.End();
    }

    #endregion

    private static string GetValue(string javascriptCombineName, HttpContext context)
    {
        string configurationValue = ConfigurationManager.AppSettings[javascriptCombineName];

        string[] files = configurationValue.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);

        StringBuilder allScripts = new StringBuilder();

        for (int i = 0; i < files.Length; i++)
        {
            string path = context.Server.MapPath(files[i].Trim());

            if (File.Exists(path))
                allScripts.Append(File.ReadAllText(path));
        }

        JavaScriptMinifier jsm = new JavaScriptMinifier();
        return jsm.MinifyString(allScripts.ToString());
    }
}

Download Esempio qui

asp.net comments edit

Con Windows Server 2008 R2 e ASP.NET 4.0 (entrambi in Beta) è possibile far sì che un’applicazione web possa avviarsi automaticamente, anche se non vi è nessuna richiesta web in quel determinato istante.
In tutte le versioni precedenti ad IIS 7.5 una applicazione web inizia il suo ciclo di vita nel momento in cui viene effettuata la prima richiesta da parte di un client; ho specificato precedenti ad IIS 7.5 perché, proprio con quest’ultima versione, è possibile fare in modo che l’applicazione si avvii automaticamente, annullando così la dipendenza dalla prima richiesta web.
Come prima cosa è necessario specificare ad IIS 7.5 quale Application Pool deve essere avviato automaticamente e, dato che un Application Pool può contentere più applicazioni web, quale applicazione deve essere avviata.
Tutto questo va fatto tramite il file applicationHost.config andando ad agire sulla sezione <sites> e, nel caso si decida di avviare un qualcosa di custom, anche nella sezione <preloadProviders>, come mostrato di seguito:

<sites>
  <site name="MySite" id="1">
    <application path="/"
         preloadEnabled="true"
         preloadProvider="PrewarmMyCache" >
    </application>
  </site>
</sites>

<preloadProviders>
     <add name="myPreloadImplementation" type="imperugo.application.myPreloadClass, imperugo.application" />
</preloadProviders>

Come si può vedere il tutto è piuttosto semplice, l’unica nota è per la classe myPreloadClass che deve necessariamente implementare l’interfaccia IProcessHostPreloadClient presente in System.Web.Hosting.

Personalmente ritengo molto utile questa funzionalità in tutte quelle applicazioni che necessitano di un pre-caching di alcune informazioni o fanno uso di timer per eseguire operazioni pianificate, laddove non sia possibile installare servizi sul server.

Ciauz

asp.net comments edit

In un precedente post ho parlato della comodità e potenzialità dell’OutputCache di ASP.NET, e, sempre in un’altro post, ho parlato di alcune novità che caratterizzano la nuova versione di ASP.NET che verrà rilasciata con il Framework 4.0.
Proprio con la futura versione sarà possibile specificare un provider per l’OutputCache che può essere differente tra usercontrol e usercontrol, permettendo così di utilizzare questo potente strumento anche in ambienti distribuiti dove la comune cache di ASP.NET non può arrivare.

Per prima cosa nel file di configurazione è stata aggiunta la possibilità di specificare l’elenco dei provider utilizzabili da questo tipo di cache, come mostrato di seguito:

<caching>
    <outputCache defaultProvider=" SossProvider ">
        <providers>
            <add name="SossProvider" type="imperugo.cache.SossOutputCacheProvider,  imperugo.cache"/>
            <add name="NCacheProvider" type="imperugo.cache.NCacheOutputCacheProvider,  imperugo.cache"/>
            <add name="FakeProvider" type="imperugo.cache.FakeOutputCacheProvider,  imperugo.cache"/>
        </providers>
    </outputCache>
</caching>

Ogni provider che si desidera realizzare deve ereditare dalla classe astratta  OutputCacheProvider presente in System.Web.Caching, che espone quattro metodi (Add, Get, Remove, Set).

A livello di UserControl basta specificare nella direttiva @Page il provider che si intende utilizzare, come mostrato dallo snippet seguente (attenzione che nella Beta 1 di VS 2010 l’intellisense per la proprietà providerName non funziona):

<%@ OutputCache Duration="60" VaryByParam="None"  providerName="SossProvider" %>

Personalemente avrei trovato molto comoda la possibilità di disabilitare l’output cache da file di configurazione (funzione comoda in fase di testing), in modo da evitare di aggiungere nuove righe o sezioni di configurazioni custom per ogni provider.

Ciauz

asp.net comments edit

Nel post precedente ho riportato alcune delle principali novità introdotte con la Beta 1 del .NET Framework 4.0 e Visual Studio 2010; da questo post in poi vorrei iniziare una serie di pubblicazioni che entrano un po’ più in dettaglio su come possono essere utilizzate queste importanti features introdotte da Microsoft.

Le novità di ASP.NET 4.0 sono divise nelle sei categorie seguenti:

  • Core;
  • AJAX Functionality in ASP.NET 4.0;
  • Web Forms;
  • Dynamic Data;
  • Visual Studio 2010 Web Designer Improvements;
  • Web Application Deployment with Visual Studio 2010;

Ovviamente ogni categoria racchiude diverse migliorie rispetto alle versioni precedenti del .NET Framework; per esempio nella categoria Core possiamo trovare il PermantRedirect, Extensible Output Caching, Auto-start, ecc.

In questo post si vedrà nel dettaglio il funzionamento di due nuove features presenti nella sezione Core:

  • Permanent Redirect;
  • Session State Compression;

Permanent Redirect:
Quando un indirizzo web viene cambiato è necessario effettuare un redirect dal vecchio indirizzo verso il nuovo, per far sì che non venga restituito un errore 404.

Nel caso il client richiedente l’indirizzo sia un motore di ricerca è necessario comunicare che il cambiamento è di tipo Permanent (status code 301); questo permetterà al Bot del search engine di sostiturire il vecchio indirizzo con il nuovo.

Questo tipo di approccio è importantissimo per le pratiche SEO (Search Engine Optimization) ed andrebbe sempre utilizzando per questo tipo di redirect.
Dalla versione Beta 1 è disponibile un apposito metodo che ci permette di effettuare questo tipo di Redirect; il codice seguente mostra la differenza tra la nuovissima Beta 1 del .NET Framework 4.0 e le versioni precedenti.

Versione antecedente al Framework 4.0:

Response.Status = "301 Moved Permanently";
Response.AddHeader("Location","http://www.mysite.com/newurl/");

Versione del Framewrok 4.0:

RedirectPermanent("http://www.mysite.com/newurl/");

 

Session State Compression:
Quando si fa uso della Sessione Out Of Process, i dati che vengono inseriti in questo repository devono essere serializzati al momento del salvataggio e deserializzati al momento della lettura, per fare in modo che vengano salvati in un qualcosa che possa essere letto al di fuori del processo corrente.

Se si prova ad immaginare un’applicazione web che risiede su una server farm o, senza andare così lontano, un’applicazione il cui Application Pool ha la WebGarden abilitata, ci si rende subito conto del perchè i dati debbano essere serializzati.

Ovviamente il processo di Serializzazione/Deserializzazione ha un costo che può crescere se lo si aggiunge al trasporto delle informazioni da un server ad un altro; proprio quest’ultima parte può essere migliorata andando a comprimere il dato, garantendo così una riduzione del tempo di trasporto delle informazioni.

Con il .NET Framework 4.0 è stata aggiunta la possibilità di attivare questa compressione per la Session Out Of Process.
L’abilitazione di questa funzione avviene tramite file di configurazione, come mostrato di seguito:

<sessionState    
    mode="SqlServer"    
    sqlConnectionString="data source=mydbserver;Initial Catalog=myDatabase"    
    allowCustomSqlDatabase="true"    
    compressionEnabled="true"/>

La parte della compressione viene eseguita in automatico dalla libreria System.IO.Compression.GZipStream.

Ciauz

.net comments edit

Già dalla presentazione alla PDC di Los Angeles se ne parla molto e da ieri, per i possessori di un abbonamento MSDN, è possibile scaricare la Beta1 del .NET Framework 4.0 e Visual Studio 2010.

Ovviamente le novità sono tantissime, a partire dal tool di sviluppo che include nuove e numerosissime migliorie, fino ad arrivare ad ASP.NET passando per il totalmente nuovo WorkFlow e Windows Communication Foundation che sarà sempre più presente nel mondo dello sviluppatore.

Partendo da Visual Studio la prima cosa che salta all’occhio è sicuramente l’interfaccia migliorata come testimoniano gli screenshot seguenti:

001 002 003 004  006 010 007 008 009 005

Anche il designer di WPF e Workflow è totalmente nuovo e potenziato; inoltre è stato aggiunto il supporto per i diagrammi UML per la versiose Architect e TeamSystem e nello specifico per i seguenti diagrammi:

  • Activity;
  • Use Case;
  • Component;
  • Sequence;
  • Class;

Interessante l’introduzione di un designer (che sfrutta WorkFlow 4.0 con delle custom activity) per la customizzazioni di Build.

Altre novità di Visual Studio 2010 le trovate in questo video.

ASP.NET 4.0:

  • Possibilità di specificare providers custom per la gestione della cache;
  • AutoStart delle applicazione web;
  • Aggiunto il metodo per il Permanent Redirect (301);
  • Aggiunte le Proprietà Keywords e Description all’oggetto Page;
  • Possibilità di abilitare il ViewState per il singolo controllo ignorando la direttiva ViewState della pagina;
  • Introdotto il Routing come in ASP.NET MVC;
  • Possibilità di intervenire sul ClientID dei controlli;
  • Migliorata l’integrazione con la libreria Javascript JQuery;
  • Migliorato il supporto per i CSS e Javascript;
  • Aggiunta la possibilità di inserire snippet anche nel markup;
  • É stato introdotto un nuovo meccanismo di deploy che consente la trasformazione del file di configurazione tramite XDT (XML Document Transform);
  • È stato introdotto il Web Packaging che permette di creare un file di installazione con tutto il necessario per il corretto funzionamento della nostra applicazione web;

Maggiori informazioni sulle novità di ASP.NET 4.0 sono disponibili qui ed un video introduttivo sulle novità qui.

 

ENTITY FRAMEWORK:

  • La tanto richiesta Persistance Ignorance;
  • Il LazyLoad;
  • Possibilità di utilizzare il tool grafico per il mapping partendo da un dominio esistente;
  • Migliorata la stabilità del designer;
  • Aggiunta la possibilità di mappare stored procedure su entità custom;

Per maggiori informazioni sulle novità di EntityFramework consiglio di seguire il blog del team qui ed un video sulle principali novità qui.

 

WINDOWS COMMUNICATION FOUNDATION:

  • Migliorate le performance del DataContractSerializer;
  • Semplificazione della configurazione;
  • Autoconfigurazione dei file svc;
  • Aggiunto il binding HTTP Pooling Duplex, utilizzato da Silverlight;
  • Possibilità di esporre un servizio sul protocollo UDP;
  • Routing (precedentemente incluso su Dublin);
  • REST Caching;
  • Hosting in IIS senza SVC;
  • Discovery dei servizi;
  • Nuovo sistema di Claim basato su Geneva (nuovo framework per l'autenticazione e gestione dell'identità, maggiori info le trovate qui);
  • Aggiunto il routing, utile per dirottare chiamate verso diversi endpoint;

Maggiori informazioni sulle novità di WCF le trovate qui ed un video sulle novità qui.


WORKFLOW:

  • Totalmente riscritto;
  • MIgliorate le performarce;
  • Nuova Activity Library con il supporto per le collection;
  • Nuovo WorkFlow chiamato FlowChart;
  • Nuovo e più potente designer integrato in Visual Studio 2010;
  • Maggior integrazione con WCF;

Maggiori informazioni le trovate qui ed un video introduttivo su WorkFlow 4.0 qui.

Da annotare che nella Beta non è presente il Framework ASP.NET MVC come spiegato da Phil Haack qui.

Ciauz

orm comments edit

Tempo fa, insieme a Marco, mi sono imbattuto in un problema relativo al fetching di collection polimorfiche tramite NHibernate.

In pratica, quando si hanno collection il cui tipo contenuto eredita da un’altra entity persistita tramite Table for Hierarchy (maggiori info qui) , si possono riscontrare dei problemi in fase di fetching quando la collection è contenuta in una entity parent.
Per capire meglio il significato di quanto appena detto si osservi il diagramma di classe seguente:

Diagramma

Per la situazione sopra descritta si avrà un mapping come il seguente:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping
    schema="dbo"
    xmlns="urn:nhibernate-mapping-2.2"
    default-access="property"
    namespace="ConsoleApplication1.ObjectModel"
    assembly="ConsoleApplication1">

    <class name="Post" table="Posts" lazy="true" dynamic-update="true">
        <id name="ID" column="ID" unsaved-value="0">
            <generator class="identity"/>
        </id>
        <property name="Title">
            <column name="Title" sql-type="nvarchar(100)" not-null="true" />
        </property>
    <bag name="PostTags" cascade="all-delete-orphan" generic="true">
            <key column="PostID" />
            <one-to-many class="PostTag" />
        </bag>
    <bag name="ImageTags" cascade="all-delete-orphan" generic="true">
      <key column="PostID" />
      <one-to-many class="ImageTag" />
    </bag>
    </class>
    
</hibernate-mapping>

 

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping
    schema="dbo"
    xmlns="urn:nhibernate-mapping-2.2"
    default-access="property"
    namespace="ConsoleApplication1.ObjectModel"
    assembly="ConsoleApplication1">

    <class name="TagBase" table="Tags" lazy="true" dynamic-update="true">
        <id name="ID" column="ID" unsaved-value="0">
            <generator class="identity"/>
        </id>
    <discriminator force="true">
      <column name="Discriminator" not-null="true"  sql-type="int" />
    </discriminator>
        <property name="Name">
            <column name="Name" sql-type="nvarchar(100)" not-null="true" />
        </property>
    </class>
    
</hibernate-mapping>

 

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping
    schema="dbo"
    xmlns="urn:nhibernate-mapping-2.2"
    default-access="property"
    namespace="ConsoleApplication1.ObjectModel"
    assembly="ConsoleApplication1">

    <subclass name="ImageTag" extends="TagBase" discriminator-value="1" >
    </subclass>
    
</hibernate-mapping>

 

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping
    schema="dbo"
    xmlns="urn:nhibernate-mapping-2.2"
    default-access="property"
    namespace="ConsoleApplication1.ObjectModel"
    assembly="ConsoleApplication1">

    <subclass name="PostTag" extends="TagBase" discriminator-value="0">
    </subclass>
    
</hibernate-mapping>

Se si tenta di recuperare una collection di PostTag non si riscontrano problemi; al contrario, se si tenta di recuperare un’istanza della classe Post e, allo stesso tempo, la collection PostTag ad esso associata, si andrà incontro ad un’eccezione.
Per ovviare al problema basta inserire nel mapping della collection la where con il discriminator, come mostrato di seguito:

<bag name="PostTags" cascade="all-delete-orphan" generic="true" where="Discriminator = 0">
    <key column="PostID" />
    <one-to-many class="PostTag" />
</bag>
<bag name="ImageTags" cascade="all-delete-orphan" generic="true" where="Discriminator = 1">
    <key column="PostID" />
    <one-to-many class="ImageTag" />
</bag>

Questo comportamento, pur essendo documentato dal team di Nhibernate qui, resta comunque strano.
Un’altra soluzione, sicuramente più comoda e mantenibile, consiste nell’impostare l’attributo force del discriminator a true, dicendo così ad Nhibernate di specificare sempre la where del discriminator nelle proprie query, come mostrato dal seguente mapping:

<discriminator force="true">
  <column name="Discriminator" not-null="true"  sql-type="int" />
</discriminator>

In allegato potete trovare un esempio che riproduce il problema.
Maggiori informazioni ed aggiornamenti potete trovarli qui.

Download Esempio qui