asp.net comments edit

Personalmente faccio parecchio uso dell'OutputCache, e lo ritengo uno dei migliori sistemi di caching (in ambienti non distribuiti) che si possano utilizzare: il vantaggio consiste nel fatto che viene messo in cache il markup già elaborato, tagliando di netto dall'applicazione tutta la parte di elaborazione dati necessaria alla creazione della pagina, o una sua porzione; in pratica è come avere un file statico in ram!

Nella maggior parte dei casi non è possibile utilizzare l'OutputCache sull'intera pagina web in quanto questa può contenere informazioni differenti da utente a utente, come delle porzioni di pagine visibili solo ad alcuni ruoli o la maschera di login nel caso l'utente non sia loggato;
è comunque sicuramente vantaggioso utilizzare l'outputcache su porzioni di pagine (usercontrol).
In ASP.NET MVC ho avuto problemi nell'adottare questa tecnica in quanto, per renderizzare lo usercontrol, utilizzavo il metodo RenderPartial, che ignora totalmente l'attributo OutputCache .
Da quanto invece apprendo dal blog di Phil Haack (maggiori info qui), risulta che è comunque possibile risolvere tale problema sostituendo l'utilizzo del RenderPartial ed andando a registrare il controllo, utilizzandolo così tramite il suo tag, come mostrato dal blocco di codice seguente:

<%@ Register Src="~/Views/Home/Partial.ascx" TagName="Partial" TagPrefix="imperugo" %>

<p>
    Metodo con Registrazione del tag
    <imperugo:Partial runat="server" />
</p>

In allegato trovate un progetto che mostra lo stesso MVC View User Control utilizzato con le due metodologie, e, al refresh della pagina, si potrà notare la differenza di comportamento.

Ciauz

Download Esempio qui

asp.net comments edit

Con il Framework ASP.NET MVC è stato introdotto il TempDataProvider che, come fa ben intuire il suo nome, ha lo scopo di memorizzare delle informazioni temporanee tra una chiamata e la successiva. Infatti, se si prova ad immaginare un ciclo di navigazione di tre pagine (A,B e C), dalla pagina C non si può accedere alle informazioni aggiunte al TempDataProvider della pagina A, ma solo a quelle della pagina B.

Di default queste informazioni vengono memorizzate nella Session e, anche se il TempDataProvider non viene utilizzato, se si prova a disabilitare la Sessione tramite l’apposita riga del file di configurazione, verrà sollevato un’errore dall’applicazione tipo il seguente:

The SessionStateTempDataProvider requires SessionState to be enabled.

Questo avviene perchè, all’interno della classe Controller (System.Web.Mvc.Controller) nel metodo Get, viene restituita (nel caso non fosse stato impostato un TempDataProvider differente) un’istanza della classe SessionStateTempDataProvider, e ne verrà invocato il metodo Load all’interno del metodo ExecuteCore del Controller.

Per capirne meglio il funzionamento basta guardare lo snippet seguente:

public ITempDataProvider TempDataProvider
{
     get
     {
         if (this._tempDataProvider == null)
         {
           this._tempDataProvider = new SessionStateTempDataProvider();
         }

         return this._tempDataProvider;
     }
     set
     {
         this._tempDataProvider = value;
     }
}
protected override void ExecuteCore()
{
  base.TempData.Load(base.ControllerContext, this.TempDataProvider);

  //…
}

Se si ha la fortuna (o sfortuna, in base ai punti di vista) di sviluppare applicazioni che hanno come requisito il funzionamento in una server farm, la sessione non può essere utilizzata (salvo uso di frameowrk particolari), ed il provider deve essere sostituito con qualcosa di più estensibile.

Per poter rispettare il requisito è necessario impostare un TempDataProvider nel metodo Inizialize del Controller con una classe sostitutiva, come mostrato di seguito:

protected override void Initialize(RequestContext requestContext)
{
  TempDataProvider = new CookieTempDataProvider(requestContext.HttpContext);
  base.Initialize(requestContext);
}

Il custom provider dovrà implementare l’interfaccia ITempDataProvider che richiede i seguenti metodi:

  • LoadTempData;
  • SaveTempData;

Il primo verrà invocato nel momento in cui si vogliono recuperare delle informazioni dal repository, il secondo quando si vogliono aggiungere delle informazioni al repository.
Se non si ha la necessità di memorizzare grandi quantità di informazioni nel TempDataProvider, sicuramente può essere utile memorizzarle in un cookie.
Di fatto, su codeplex nelle ASP.NET MVC Futures, si trova un’implementazione di un TempDataProvider che utilizza proprio il cookie come repository (maggiori info qui: http://aspnet.codeplex.com/SourceControl/changeset/view/21528#266402).

asp.net comments edit

Fin dalla Preview 2 del Framework ASP.NET MVC sono stati introdotti gli ActionFilter, che permettono di variare o migliorare il comportamento di un Controller o della singola Action in esso contenuta, consentendo così un forte riutilizzo del codice.
Il loro utilizzo è piuttosto semplice: basta decorare la Action o il Controller con l'attributo e implementare la logica nei metodi esposti dall'ActionFilter base da cui tutti ereditano.
I metodi messi a disposizione sono 4:

  • OnActionExecuted;
  • OnActionExecuting;
  • OnResultExecuted;
  • OnResultExecuting;


Per capire più a fondo le potenzialità e semplicità di utilizzo di questi ActionFilter ci basti osservare lo snippet seguente che abilita l’accesso alla Action soltato agli utenti presenti nel ruolo di Administrator.

[Authorize(Roles = "Administrator")]
public ActionResult Index()
{
    //Nostra Action
}

Ovviamente nel Framework troviamo già parecchi ActionFilter come HandleError, OutputCache, Authorize, ecc, ma spesso non tutti riescono a soddisfare esigenze quali, ad esempio, l'abilitazione della compressione sulle pagine, la rimozione di spazi vuoti dal markup o l'aggiunta di una firma a fondo pagina.
Proprio dall'esigenza di rimuovere gli spazi vuoti è nata l'idea di realizzare un custom ActionFilter, che nel nostro esempio si chiamerà OptimizationFilter, che avrà il compito di ottimizzare la nostra pagina.

I requisiti per un filter di questo genere sono abbastanza semplici:

  • Possibilità di attivare la compressione dei dati;
  • Possibilità di rimuovere gli spazi vuoti dal markup;
  • Possibilità di ignorare l'esecuzione del filtro per tutti gli utenti presenti in un determinato ruolo;

Precedentemente in ASP.NET, per la rimozione degli spazi vuoti dal markup veniva realizzato un HttpModule, che aveva il compito di verificare se la richiesta effettuata era rivolta verso una pagina web, e, nel caso, eseguiva una Regular Expression per la rimozione degli spazi (maggiori info qui: http://madskristensen.net/post/A-whitespace-removal-HTTP-module-for-ASPNET-20.aspx).
Purtroppo, benchè comodissime, le Regular Expression non brillano sicuramente per le performance, quindi consiglio di abbinare l'outputcache all'utilizzo di questo ActionFilter.

Partendo da quanto mostrato in questo post (http://madskristensen.net/post/The-WebOptimizer-class.aspx) di Mads Kristensen, ho implementato il CustomFilter come mostrato di seguito:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class OptimizationFilter : ActionFilterAttribute
{
    private bool? executeUser;
    private string[] rolesException;

    public OptimizationFilter()
    {
        Compress = false;
        RemoveWhiteSpace = false;
    }

    public bool Compress { get; set; }
    public bool RemoveWhiteSpace { get; set; }

    public string RolesException
    {
        get { return string.Join(",", rolesException); }
        set { rolesException = value.Split(","); }
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        bool execute = Execute(filterContext);

        if (!execute)
            return;

        if (!Compress)
            base.OnActionExecuting(filterContext);

        HttpRequestBase request = filterContext.HttpContext.Request;

        string acceptEncoding = request.Headers["Accept-Encoding"];

        if (string.IsNullOrEmpty(acceptEncoding))
            return;

        acceptEncoding = acceptEncoding.ToUpperInvariant();

        HttpResponseBase response = filterContext.HttpContext.Response;

        if (acceptEncoding.Contains("GZIP"))
        {
            response.AppendHeader("Content-encoding", "gzip");
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else if (acceptEncoding.Contains("DEFLATE"))
        {
            response.AppendHeader("Content-encoding", "deflate");
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }

    private bool Execute(ControllerContext filterContext)
    {
        bool execute = true;

        if (rolesException == null)
            executeUser = true;

        if (executeUser != null)
            return executeUser.Value;

        for (int i = 0; i < rolesException.Length; i++)
            if (filterContext.HttpContext.User.IsInRole(rolesException[i]))
            {
                execute = false;
                break;
            }

        executeUser = execute;

        return execute;
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        base.OnResultExecuted(filterContext);

        if (!Execute(filterContext))
            return;

        if (RemoveWhiteSpace)
            filterContext.HttpContext.Response.Filter = new WhitespaceFilter(filterContext.HttpContext.Response.Filter);
    }
}

internal class WhitespaceFilter : Stream
{
    private static readonly Regex RegexBetweenTags = new Regex(@">\s+<", RegexOptions.Compiled);
    private static readonly Regex RegexLineBreaks = new Regex(@"\n\s+", RegexOptions.Compiled);

    private readonly Stream sink;

    public WhitespaceFilter(Stream sink)
    {
        this.sink = sink;
    }

    public override void Flush()
    {
        sink.Flush();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return sink.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        sink.SetLength(value);
    }

    public override void Close()
    {
        sink.Close();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return sink.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        var data = new byte[count];
        Buffer.BlockCopy(buffer, offset, data, 0, count);
        string html = Encoding.Default.GetString(buffer);


        html = RegexBetweenTags.Replace(html, "> <");
        html = RegexLineBreaks.Replace(html, string.Empty);
        html = html.Replace("\r", string.Empty);
        html = html.Replace("//<![CDATA[", string.Empty);
        html = html.Replace("//]]>", string.Empty);
        html = html.Replace("\n", string.Empty); 

        byte[] outdata = Encoding.Default.GetBytes(html.Trim());
        sink.Write(outdata, 0, outdata.GetLength(0));
    }

    public override bool CanRead
    {
        get { return true; }
    }

    public override bool CanSeek
    {
        get { return true; }
    }

    public override bool CanWrite
    {
        get { return true; }
    }

    public override long Length
    {
        get { return 0; }
    }

    public override long Position { get; set; }
}

Come si potrà notare, l’ ActionFilter ha esposte tre proprietà:

  • Compress;
  • RemoveWhiteSpace;
  • RolesException;

La prima serve per impostare la compressione, la seconda a rimuovere gli spazi vuoti dal markup e la terza per disabilitare le prime solo ad alcuni ruoli; proprio quest'ultima si può rivelare molto utile nel caso si debbano effettuare controlli sul markup.
Come si può vedere dallo snippet seguente il suo utilizzo risulta molto semplice.

[OptimizationFilter(Compress = false, RemoveWhiteSpace = true, RolesException = "CSSDesigner,MarkupDesigner")]
public ActionResult Index(string page)
{
    //Nostra Action
}

Ulteriori informazioni riguardo agli Action Filter le potete trovare qui.

Ciauz

various comments edit

Purtroppo per cause di forza maggiore (provider), il blog è stato down per circa 13 ore.
Fortunatamente tutto è stato ripristinato con una nuova versione di Dexter, che include nuove migliorie:

  • Validazione Feed RSS;
  • Validazione Feed Atom;
  • Miglior utilizzo della Cache;
  • Miglior ricerca FullText;
  • Bugfixing;

A breve sarà disponibile online il codice sorgente di questo blog engine per il download.

Ciauz

asp.net comments edit

Con l’uscita del Service Pack 1 per il .NET Framework 3.5 è stata introdotta una nuova ed utilissima funzionalità che permette di ridurre il numero di chiamate verso file Javascript effettuate dal browser verso il server, ottimizzando così il traffico e le performance della pagina.
Ormai si fa sempre più uso di Javascript, che siano queste semplici chiamate AJAX o complesse animazioni, ed il numero di librerie esistenti (JQuery, ShadowBox, Prototype, ecc) ci impone di aggiungere reference continue alle nostre pagine, riducendone così le performance ed aumentado il traffico di rete.
Una soluzione consiste nel riunire tutti i file Javascript in un unico file, riducendo così il numero di chiamate dal browser verso il server.
Si provi ad immaginare la situazione in cui la nostra pagina web abbia le seguenti reference javascript

<script src="Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="Scripts/shadowbox.js" type="text/javascript"></script>
<script src="Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="Scripts/customFunction.js" type="text/javascript"></script>
<script src="Scripts/jquery.cookie.js" type="text/javascript"></script>
<script src="Scripts/jquery.delegate-1.1.min.js" type="text/javascript"></script>
<script src="Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="Scripts/shadowbox-jquery.js" type="text/javascript"></script>

Come si può vedere si hanno ben 8 file Javascript e, se nella stessa pagina è presente anche lo Script Manager di ASP.NET AJAX, il numero sale fino a 11, come mostrato dallo screenshot seguente:

old22-04-2009 21.04

Per ottimizzare le chiamate nello script manager è stata aggiunta una nuova sezione (CompisiteScript) che offre la possibilità di specificare tutti i file Javascript che devono essere accorpati in un unico file, come mostrato dallo snippet seguente:

<asp:ScriptManager ID="sm" runat="server" ScriptMode="Release" CompositeScript-ScriptMode="Release">
    <CompositeScript>
        <Scripts>
            <asp:ScriptReference Path="Scripts/jquery-1.3.2.min.js" />
            <asp:ScriptReference Path="Scripts/shadowbox.js" />
            <asp:ScriptReference Path="Scripts/jquery.validate.min.js" />
            <asp:ScriptReference Path="Scripts/customFunction.js"  />
            <asp:ScriptReference Path="Scripts/jquery.cookie.js" />
            <asp:ScriptReference Path="Scripts/jquery.delegate-1.1.min.js" />
            <asp:ScriptReference Path="Scripts/MicrosoftMvcAjax.js" />
            <asp:ScriptReference Path="Scripts/shadowbox-jquery.js"  />
        </Scripts>
    </CompositeScript>
</asp:ScriptManager>

Questo permette di ridurre pesantemente le chiamate del browser da 11 a 5 (4 dello script manager ed 1 per tutti i file sopra specificati), come mostrato qui di seguito.

 22-04-2009 21.21

Anche se il test è stato effettuato su una macchina di sviluppo e non in una condizione reale, si può gia notare come il tempo di esecuzione della pagina si sia ridotto da 12.77 secondi a 5.44 secondi, come mostrato dall’immagine seguente:

22-04-2009 21.04

Ovviamente il tutto può essere impostato anche lato Object Oriented, eliminando però la possibilità di effettuare cambiamenti a runtime.

sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/jquery-1.3.2.min.js"));
sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/shadowbox.js"));
sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/jquery.validate.min.js"));
sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/customFunction.js"));
sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/jquery.cookie.js"));
sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/jquery.delegate-1.1.min.js"));
sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/MicrosoftMvcAjax.js"));sm.CompositeScript.Scripts.Add(new ScriptReference("Scripts/shadowbox-jquery.js"));

Successivamente si vedrà come implementare lo stesso tipo di ottimizzazione in un’applicativo che fa uso del nuovo framework ASP.NET MVC, andando a migliorare l’output dei file javascript.

various comments edit

Per chi come me fa uso di NHibernate per la parte di persistenza saprà sicuramente che è necessario impostare le proprietà delle nostre entities come virtual. Questo è necessario in quanto NHibernate per poter sfruttare a pieno le sue potenzialità, come LazyLoad, crea un’istanza di un proxy (con DynamicProxy di Castle) ed inietta a runtime la logica da noi richiestra tramite mapping, fetching, ecc.

Ora se il nostro strato di accesso ai dati non restituisce la entity di dominio, bensì un DTO (Data Transfer Object, ne abbiamo parlato qui)  vorrei che il progetto che referenzierà il nostro Data Layer non possa creare un’istanza della entity di dominio; per far questo ci basta specificare che la entity di dominio sia di tipo internal.

Questo va si a risolvere il nostro problema, ma impedisce all’assembly di NHibernate di accedere alle entities di dominio in quanto questo si trova a sua volta in un’assembly esterna, sollevando una bella Castle.DynamicProxy.Generators.GeneratorException, come quella mostrata qui di seguito:

Type is not public, so a proxy cannot be generated. Type: imperugo.example.domain.entitybase

Castle.DynamicProxy.Generators.GeneratorException: Type is not public, so a proxy cannot be generated. Type : imperugo.example.domain.entitybase

Per ovviare a questo problema e permettere solo al proxy di Castle di poter accedere all nostra assembly ci basta modificare il file AssemblyInfo aggiungendo la seguente riga:

[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

A questo punto tutto dovrebbe essere ok, il Data Layer non ha esposto esternamente il dominio ed NHibernate riesce a creare i suoi proxy.
Ciauz

asp.net comments edit

 

Pur essendo io un fan dei DTO (Data Transfer Object) anche in ambienti non service oriented, trovo alquanto noiosa la parte di idratazione dalla entity di DTO mediante “copia” dei dati dalla entity di dominio utilizzata per la persistenza.

Se ci si trova a lavorare con i servizi, le motivazioni per cui una entity di dominio non dovrebbe MAI essere restituita dal servizio possono essere molteplici, alcune delle quali elencate qui di seguito:

  • La entity di dominio potrebbe non essere serializzabile;
  • La entity di dominio potrebbe avere relazioni unidirezionali e bidirezionali verso altre entità;
  • La entity di dominio potrebbe contenere informazioni necessarie alla sua persistenza o informazioni che nascono e muoiono all’interno del servizio;
  • Restituire informazioni non necessarie all’esterno di un servizio va ad influire sulle performance dello stesso, in quanto le informazioni che deve restituire e serializzare sono maggiori di quelle realmente necessarie causando così rallentamenti nella fase di serializzazione/deserializzazione e trasporto dei dati;
  • Un servizio deve vivere di vita propria: non deve quindi necessitare di nessun altro servizio per poter svolgere il suo lavoro e non deve divulgare verso l’esterno informazioni come eccezioni e dominio;

Ora, se si prova ad analizzare il grafo di dominio riportato qui di seguito e si immagina un metodo che restituisca un’istanza di User, ci si renderà subito conto che si entra in conflitto con tutti i punti elencati poc’anzi.

Dominio

 

In un’applicazione perfetta il servizio dovrebbe restituire un’istanza di un oggetto differente da quella di dominio, contenente solo le informazioni necessarie all’esterno, come il suo ID, Nome, Cognome ed Username, come per la entity seguente:

dto

Andando a guardare il lato implementativo si dovrebbe avere un metodo che, in base ai parametri forniti dal client, recupera un’istanza della entity di dominio User, dalla quale verranno poi prese le informazioni necessarie per idratare la entity di dominio UserDTO, che verrà restituita dal servizio in quanto rispetta tutti i punti sopra elencati (è snella, è serializzabile, non contiene informazioni futili esternamente al servizio, ecc).

Sicuramente una soluzione ottimale sarebbe quella di prelevare dal repository (un database per esempio) solo le informazioni necessarie ad idratare la entity di DTO, ma questo spesso può risultare un lavoro dispendioso, in quanto si rischia di avere poco riutilizzo di codice; oppure ci si può trovare nella condizione in cui si dispone di un’intera entity in cache e l’andare a recuperare l’intera entity dalla memoria può risultare meno dispendioso del recupero delle singole informazioni dal repository.
Con l’avvento degli O/RM l’utilizzo dei DTO si è semplificato di parecchio, come mostrato negli snippet seguenti:

Nhibernate:

using(Isession session = SessionHelper.GetSession())
{
    ICriteria myCriteria = session.CreateCriteria(typeof (User));
    myCriteria.Restrictions.Add("Name", name);

    myCriteria.SetProjection(
          Projections.ProjectionList()
            .Add(Projections.Property("Firstname"), "Firstname")
            .Add(Projections.Property("ID"), "ID")
            .Add(Projections.Property("Lastname"), "Lastname")
                .Add(Projections.Property("Username"), "Username"));    
    
    myCriteria.SetResultTransformer(NHibernate.Transform.Transformers.AliasToBean(typeof(UserDTO))); 
    return myCriteria.List();
}

Linq:

var q = from u in Users where u.ID = 1 select new UserDTO { ID = u.ID, Firstname = u.Firstname, Lastname = u.Lastname, Username = u.Username };
q.ToList();

Entity Idratata manualmente:

User usr = //recupero la entity dal database o dalla cache

UserDTO userDto = new UserDTO();
userDto.ID = usr.ID;
userDto.Firstname = usr.Firstname;
userDto.Lastname = usr.Lastname;
userDto.Username = usr.Username;

Come si potrà intuire, finchè si parla di poche proprietà per poche entities la cosa è fattibile, ma quando si tratta di applicazioni complesse l’utilizzo dei DTO può incidere in maniera pesante sulle tempistiche e sui costi di sviluppo, specie se ci si trova con una entity già idratata.

Il problema potrebbe essere facilmente risolto se ci fosse qualcosa che svolge il lavoro di “trasbordo” dei dati per noi, ad esempio la Reflection.
Purtroppo, come è stato detto in numerosi articoli e blog, questa non è propriamente la soluzione migliore, in quanto a compile time non si conoscono né tipi né i membri in azione.
Per poter ovviare a questo problema si può generare codice IL (Intermediate Language) ad hoc per il nostro scopo ed avere le “stesse performance” del codice compilato (per chi fosse interessato all’argomento può avere maggiori info qui).


Con l’aiuto di Ricciolo ho creato degli Extension Methods che generano IL a runtime ed effettuano il “trasbordo” dei dati dalla entity di dominio alla entity di DTO, senza avere un decadimento delle performance e senza violare i principi esposti precedentemente.
Per prima cosa si realizza l’Extension Method che leggerà tutte le proprietà della entity di Dominio, e questa operazione viene fatta tramite la reflection (se pur la reflection sia lenta, quanta questa operazione verrà effettuata solo alla prima richiesta ed il risultato viene “cachato“ in una hashtable statica) .

 

Come si può notare, all’interno del metodo vengono invocati altri due Extension Methods (FastCreateInstance e FastCopyValue), che hanno il compito di generare l’IL necessario al nostro scopo.
Il metodo FastCreateInstance ha lo scopo di creare una nuova istanza: per ottimizzare le performance viene “cachato” il delegate in una hashtable.

private static readonly Hashtable createInstanceInvokers = new Hashtable();
private delegate object CreateInstanceInvoker();

private static object FastCreateInstance(this Type type)
{
    if (type == null)
        throw new ArgumentNullException("type");

    var invoker = (CreateInstanceInvoker)createInstanceInvokers[type];
    if (invoker == null)
    {
        LambdaExpression e = Expression.Lambda(typeof(CreateInstanceInvoker), Expression.New(type), null);
        invoker = (CreateInstanceInvoker)e.Compile();
        createInstanceInvokers[type] = invoker;

    }
    return invoker();
}

Per il metodo FastCopyValues il discorso è più o meno lo stesso, si ha un’hashtable per il caching del delegate ed il codice IL necessario a copiare i valori:

private static readonly Hashtable getAndSetValuesInvokers = new Hashtable();
private delegate void GetSetValuesInvoker(object source, object target);

private static void FastCopyValues(this IEnumerable property, object source, object target)
{
    if (property == null)
        throw new ArgumentNullException("property");

    if (source == null)
        throw new ArgumentNullException("source");

    if (target == null)
        throw new ArgumentNullException("target");

    GetSetValuesInvoker invoker = GetGetAndSetCachedInvoker(property, source.GetType(), target.GetType());
    invoker(source, target);
}

private static GetSetValuesInvoker GetGetAndSetCachedInvoker(IEnumerable properties, Type sourceType, Type targetType)
{
    var invoker = (GetSetValuesInvoker)getAndSetValuesInvokers[properties];
    if (invoker == null)
    {
        var method = new DynamicMethod("test", null, new[] { typeof(object), typeof(object) }, typeof(object), true);
        ILGenerator il = method.GetILGenerator();
        il.DeclareLocal(sourceType);
        il.DeclareLocal(targetType);

        il.Emit(OpCodes.Nop);
        il.Emit(OpCodes.Ldarg_0);
        if (sourceType.IsClass)
            il.Emit(OpCodes.Castclass, sourceType);
        else
            il.Emit(OpCodes.Unbox_Any, sourceType);
        il.Emit(OpCodes.Stloc_0);

        il.Emit(OpCodes.Ldarg_1);
        if (targetType.IsClass)
            il.Emit(OpCodes.Castclass, targetType);
        else
            il.Emit(OpCodes.Unbox_Any, targetType);
        il.Emit(OpCodes.Stloc_1);

        foreach (PropertyInfo property in properties)
        {
            PropertyInfo sourceProperty = sourceType.GetProperty(property.Name, BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetProperty);
            PropertyInfo targetProperty = targetType.GetProperty(property.Name, BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetProperty);

            if (sourceProperty != null && targetProperty != null)
            {
                il.Emit(OpCodes.Ldloc_1);
                il.Emit(OpCodes.Ldloc_0);
                il.EmitCall(OpCodes.Callvirt, sourceProperty.GetGetMethod(true), null);
                il.EmitCall(OpCodes.Callvirt, targetProperty.GetSetMethod(true), null);
                il.Emit(OpCodes.Nop);
            }
        }
        il.Emit(OpCodes.Ret);

        invoker = (GetSetValuesInvoker)method.CreateDelegate(typeof(GetSetValuesInvoker));
        getAndSetValuesInvokers[properties] = invoker;
    }

    return invoker;
}

Come si può vedere dal codice precedente, spesso viene utilizzata un hashtable per il caching dei delegate e delle proprietà da copiare. Seppure ad ogni lettura viene effettuato un cast, l’hashtable risulta molto più adatta rispetto ad un Dictionary tipizzato, in quanto è già thread-safe, evitando così l’inserimento di vari lock nel codice.
Se a prima vista può sembrare complesso, basterà guardare il codice seguente per capire la sua semplicità di utilizzo.

User item = new User
                {
                    ID = 45, 
                    Username = "imperugo", 
                    Firstname = "Ugo", 
                    Lastname = "Lattanzi", 
                    CreationDate = DateTime.Now, 
                    LastLoginDate = DateTime.Now.AddDays(-5)
                };


UserDTO returnItem = item.CopySameValues();

L’unica accortezza che bisogna avere riguarda le proprietà che si vogliono copiare tra le due entità, che devono avere lo stesso nome ed essere dello stesso tipo, per il resto è semplicissimo.

various comments edit

Ebbene sì, i’m Back!

Fin ora non ho dato molta importanza al blog, ed il numero di post e tipologia del mio vecchio blog ne danno conferma, ma ora riparto con la volonta di curarlo maggiormente con contenuti sempre più frequenti.
 
Per chi mi seguiva nel vecchio blog (ed ha intenzione di farlo ancora) può aggiungere il feed principale del nuovo blog al proprio newsaggregator. Proprio per questo spostamento ho deciso di far puntare il feed a FeedBurner in modo da rendere indolore per gli utenti eventuali spostamenti futuri.
 
Purtroppo non sono stato molto attivo nelle community in questo periodo, un po’ per la mole di lavoro, un po’ per lo sviluppo di questo nuovo engine di Blog (codename dexter, ulteriori info qui) totalmente fatto in casa, e proprio per questo motivo a breve usciranno una serie di post su alcune implementazioni di quest’applicazione sviluppata interamente in ASP.NET MVC ed NHibernate per la parte di persistenza.
 
Che dire, a presto news tecniche.
Ciauz

.net comments edit

La riconciliazione notturna ormai è parte integrante delle applicazioni che mi trovo a sviluppare tutti i giorni. 
Spessissimo mi capita di dover eseguire qualcosa (invalidazione della cache, pulizia di record sporchi, ecc) in un determinato momento, che sia questo in un'applicazione web o in un servizio.

Queste procedure tra decine di servizi e applicazioni web sono sempre difficoltose da gestire, spesso alcune devo essere seguite dopo altre o solo in alcuni giorni.

Proprio per questa serie di motivi che ho deciso di crearmi un servizio custom del tutto autonomo in cui, tramite file di configurazione, devo solo specificare cosa, dove e quando devo effettuare una chiamata.

Tradotto in codice viene fuori una roba del genere:

<schedule name="mySchedule"> 
  <service proxyFullyQualifiedName="MyServiceClient, Imperugo.Services.Client"> 
    <method scheduleName="sc1" methodName="Test"> 
      <day weekDay="Sunday" startTime="19:00:00"> 
        <parameter parameterName="param1" parameterType="System.Int32"parameterValue="10" /> 
        <parameter parameterName="param2" parameterType="System.String"parameterValue="prova"></parameter> 
        <parameter parameterName="param3" parameterType="System.Int32"parameterValue="4"></parameter> 
       </day> 
       <day weekDay="Monday" startTime="19:00:00" /> 
       <day weekDay="Tuesday" startTime="19:00:00" /> 
       <day weekDay="Wednesday" startTime="19:00:00" /> 
       <day weekDay="Thursday" startTime="19:00:00" /> 
       <day weekDay="Friday" startTime="19:00:00" /> 
       <day weekDay="Saturday" startTime="19:00:00" /> 
    </method> 
  </service>

   <service proxyFullyQualifiedName="MyServiceClient, Imperugo.Services.Client"> 
    <method scheduleName="sc1" methodName="Test"> 
      <day weekDay="Sunday" startTime="19:00:00"> 
        <parameter parameterName="param1" parameterType="System.Int32"parameterValue="10" /> 
        <parameter parameterName="param2" parameterType="System.String"parameterValue="prova"></parameter> 
        <parameter parameterName="param3" parameterType="System.Int32"parameterValue="4"></parameter> 
       </day> 
       <day weekDay="Monday" startTime="19:00:00" /> 
       <day weekDay="Tuesday" startTime="19:00:00" /> 
       <day weekDay="Wednesday" startTime="19:00:00" /> 
       <day weekDay="Thursday" startTime="19:00:00" /> 
       <day weekDay="Friday" startTime="19:00:00" /> 
       <day weekDay="Saturday" startTime="19:00:00" /> 
    </method> 
  </service> 
</schedule> 

 

La realizzazione è piuttosto semplice, una custom section nel file di configurazione,un Timer, il ThreadPool e un po' di reflection per assegnare i parametri ed invocare i proxy. 
Ovviamente essendo un servizio di Maintenance non mi sono preoccupato tanto delle performance, quindi la reflection andava benissimo ;).

Appena ultimato con alcune aggiunte (tipo poter impostare una chiamata giornaliera senza inseririe tutti i giorni, oppure eseguire un qualcosa ogni x minuti) posterò un po' di codice e perchè no il download.

Ciauuzz