Validazione Client-Side su ASP.NET MVC 2 con jQuery

Print Content | More

Ormai ne hanno parlato tutti (me incluso, vedi qui) di come sfruttare le DataAnnotations per effettuare una validazione client side su ASP.NET MVC, ma in tutti gli esempi si fa uso dei files javascript realizzati da Microsoft e presenti nel template di default di ASP.NET MVC.

Purtroppo, o per fortuna, io sono un grandissimo estimatore di jQuery e mi sono subito posto la domanda: “Come faccio a sfruttare le Data Annotations e jQuery per effettuare una validazione ClientSide?”. L’implementazione non dovrebbe essere molto difficile dato che il metodo “Html.EnableClientValidation()” non fa altro che iniettare in pagina il JSon contenente le regole di validazione specificate con le Data Annotations; l’unica cosa che bisogna fare è leggere il Json e, interpretarlo ed infine collegarlo alla form con jQuery.

Per nostra fortuna Microsoft ha già pensato a questa necessità e, se scarichiamo da qui MVC Futures, troviamo al suo interno un file javascript che ci permette di utilizzare jQuery Validation con MVC2 a costo zero.

Il file javascript in questione si chiama “MicrosoftMvcJQueryValidation.js” e per utilizzarlo è sufficiente sostituire quello che avremmo fatto normalmente per utilizzare la validazione client side, ossia questo:

<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script> 
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script> 
<script src="/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script>

con questo

<script src="/Scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcJQueryValidation.js" type="text/javascript"></script>

oppure ancora meglio con questa versione che sfrutta il CDN di Microsoft, con un ovvio vantaggio di performance e risparmio banda:

<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.4.2.min.js" type="text/javascript"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcJQueryValidation.js" type="text/javascript"></script>

enjoy jQuery.

.u


Mvc , JQuery

0 comments

Errore di ViewState su ASP.NET MVC

Print Content | More

Anche se il titolo è un po’ fuorviante, devo dire che il messaggio “Invalid viewstate” è apparso realmente; ma procediamo con ordine.
Molti di voi si saranno accorti che ultimamente, quando si accedeva al dettaglio di un post sul mio blog, si verificava in maniera del tutto casuale un errore e la navigazione dell’utente veniva deviata verso una pagina di cortesia.
Purtroppo l’errore non è dovuto a Dexter - dico purtroppo perchè altrimenti l’avrei corretto da tempo - ma ad una serie di fattori che non sono neanche riuscito a riprodurre sistematicamente; sta di fatto che l’applicazione andava in crash quando invocava l’helper AntiForgeryToken, restituendo il seguente stack tracce:

System.Web.HttpException: Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerWrapper'.

---> System.Web.Mvc.HttpAntiForgeryException: A required anti-forgery token was not supplied or was invalid. --->

System.Web.HttpException: Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that

configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster. --->

System.Web.UI.ViewStateException: Invalid viewstate.
    Client IP: 94.86.54.138
    Port: 44787
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9
    ViewState: eMzLc7Gx/IAC2ALGLNZWrgie4SgDWbcFeooL0JSADQan8USvQo7F0Fgx3u0m4aB4
    Referer:
    Path: /blog/post/welcome-parallel-linq ---> System.Security.Cryptography.CryptographicException: Padding is invalid and cannot

be removed.
   at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte

[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
   at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32

inputCount)
   at System.Security.Cryptography.CryptoStream.FlushFinalBlock()
   at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start,

Int32 length, IVType ivType, Boolean useValidationSymAlgo)
   at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---
   at System.Web.UI.ViewStateException.ThrowError(Exception inner, String persistedState, String errorPageMessage, Boolean

macValidationError)
   at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
   at System.Web.Mvc.AntiForgeryDataSerializer.Deserialize(String serializedToken)
   --- End of inner exception stack trace ---
   at System.Web.Mvc.AntiForgeryDataSerializer.Deserialize(String serializedToken)
   at System.Web.Mvc.HtmlHelper.GetAntiForgeryTokenAndSetCookie(String salt, String domain, String path)
   at System.Web.Mvc.HtmlHelper.AntiForgeryToken(String salt, String domain, String path)
   at System.Web.Mvc.HtmlHelper.AntiForgeryToken()
   at ASP.themes_default_views_blog_post_aspx.__RenderContent1(HtmlTextWriter __w, Control parameterContainer) in c:\domains

\tostring.it\wwwroot\Themes\Default\Views\Blog\Post.aspx:line 113
   at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
   at ASP.themes_default_views_shared_site_master.__Render__control1(HtmlTextWriter __w, Control parameterContainer) in c:\domains

\tostring.it\wwwroot\Themes\Default\Views\Shared\Site.Master:line 113
   at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
   at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
   at System.Web.UI.Page.Render(HtmlTextWriter writer)
   at System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest()
   at System.Web.UI.Page.ProcessRequest(HttpContext context)
   at System.Web.Mvc.ViewPage.ProcessRequest(HttpContext context)
   at ASP.themes_default_views_blog_post_aspx.ProcessRequest(HttpContext context) in c:\windows\Microsoft.NET

\Framework64\v2.0.50727\Temporary ASP.NET Files\root\878362b4\44ad105e\App_Web_qkslf0lt.1.cs:line 0
   at System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.<>c__DisplayClass1.b__0()
   at System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.<>c__DisplayClass4.b__3()
   at System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.Wrap[TResult](Func`1 func)
   at System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.Wrap(Action action)
   at System.Web.HttpServerUtility.ExecuteInternal(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean

setPreviousPage, VirtualPath path, VirtualPath filePath, String physPath, Exception error, String queryStringOverride)
   --- End of inner exception stack trace ---
   at System.Web.HttpServerUtility.ExecuteInternal(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean

setPreviousPage, VirtualPath path, VirtualPath filePath, String physPath, Exception error, String queryStringOverride)
   at System.Web.HttpServerUtility.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm, Boolean setPreviousPage)
   at System.Web.HttpServerUtility.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm)
   at System.Web.HttpServerUtilityWrapper.Execute(IHttpHandler handler, TextWriter writer, Boolean preserveForm)
   at System.Web.Mvc.ViewPage.RenderView(ViewContext viewContext)
   at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
   at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass14.b__11()
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1

continuation)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1

continuation)
   at System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters,

ActionResult actionResult)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
   at Dexter.Web.Mvc.Controllers.DexterActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
   at System.Web.Mvc.Controller.ExecuteCore()
   at System.Web.Mvc.MvcHandler.<>c__DisplayClass8.b__4()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.b__0()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.b__7(IAsyncResult _)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
ASPIMPERSONATING:

Sinceramente non so se sono riuscito a risolvere il problema: di fatto questo post è anche un appello a chi ha avuto questa problematica e l’ha risolta.

In tutte le righe del log veniva riportato come user agent quello di Internet Explorer 8 e, inizialmente, mi sono concentrato su di lui per capire la problematica; poi, guardando un po’ i logs e grazie all’aiuto del buon Giorgio ed Andrea, ci siamo accorti che il problema era su ASP.NET MVC e non su IE8 (il log mostrava sempre IE8 per il semplice motivo che il browser di casa Microsoft è molto più diffuso rispetto alla concorrenza).

Cercando in rete ho provato questa soluzione, sperando che dia i suoi frutti.
Nello specifico consiste nel registrare a mano i valori del MachineKey all’interno del nostro web.config. Ovviamente i valori da registrare sono diversi, ma fortunatamente esiste questo Generator" href="http://aspnetresources.com/tools/keycreator.aspx" rel=nofollow target=_blank>sito che ci autogenera l’apposita sezione del file di configurazione da insaaerire.

Il risultato finale del web.config dovrebbe essere una cosa del genere:

stay tuned!
.u


Configurazione , Exception , Mvc

0 comments

Invocare una action utilizzando il JSonResult in post con JQuery ed ASP.NET MVC 2

Print Content | More

Con la release di ASP.NET MVC 2 è stata introdotta una “breaking change”, direi corretta e necessaria, che va ad influenzare il comportamente di un JsonResult; nello specifico, la nuova release non permette di interrogare una action che restituisce le informazioni in formato JSon tramite il JSonResult se l’invocazione è stata fatta in GET anziché POST.

Prima di allarmarci è necessario dire che è possibile ancora invocare la Action in GET; di fatto la scritta breaking change era tra virgolette, e possiamo in qualsiasi momento ripristinare il comportamento della release precedente in questo modo:

public ActionResult JsonAction()
{
    return Json ( myObject, JsonRequestBehavior.AllowGet );
}

Ovviamente in caso di upgrade alla nuova versione di MVC questo può causare un malfunzionamento della nostra applicazione, specie se questa fa forte uso di javascript per interrogare actions e popolare dinamicamente parti di HTML.
Nonostante l’enorme supporto offerto da Visual Studio 2010, spesso può risultare scomodo, o ancora peggio costoso, modificare il codice javascript per adattare la nostra applicazione al nuovo comportamento richiesto, e siamo portati a lavorare sul codice lato server anziché client.
Prima però di effettuare queste modifiche è necessario domandarsi il perchè di questa “breaking change” e come la motivazione si rispecchia nel nostro scenario.
Provo a spiegarmi un po’ meglio portando come esempio quanto accaduto tempo fa a Google, nello specifico Gmail, che è stato vittima di un attacco proprio in uno scenario che includeva il formato JSon e l’interrogazione dello stesso tramite javascript.

Non voglio star qui a spiegare nel dettaglio il tipo di attacco, ma è sufficiente sapere che è stato possibile recuperare tutto l’addressbook di un utente gmail con un semplice link mandato al suo stesso indirizzo email :).
Direi che questo attacco, e di conseguenza la vulnerabilità riscontrata, ha fatto pendere l’ago della bilancia verso la disabilitazione della possibilità di interrogare in Get il JSonResult da parte del team di ASP.NET e lasciare così allo sviluppatore la responsabilità di esporre i propri dati verso l’esterno.

Ora, la prima domanda che un dev deve porsi nel momento in cui si trova a decidere se modificare tutto il javascript della sua applicazione - che abbiamo già detto richiede un maggior effort - oppure ripristinare il comportamento antecedente all’upgrade (rischiando un' eventuale “grab” delle informazioni), è una domanda di questo tipo:

Sto esponendo tramite JSon informazioni sensibili?

Dovrebbe bastare a farci prendere una decisione: se la risposta è si, siamo “costretti” a dover mettere mano a tutto il nostro javascript e ad invocare le nostre Actions in POST; in caso contrario l’overload con AllowGet mostrato precedentemente può ridurre di parecchio il nostro sforzo.
Fortunatamente, per chi usa jQuery per popolare queste informazioni all’interno delle pagine, può facilmente sostituire il codice potenzialmente vulnerabile con un qualcosa di più “robusto” che invochi le nostre Actions in POST.

Guardando il lato pragmatico della cosa, lo snippet seguente mostra come precedentemente recuperavamo le informazioni da una fonte dati Json:

$.getJSON('/Home/JsonAction', function (dr) {
    $.each(dr, function () {
        //DO SOMETHING
    });
});

Questo sistema non è sbagliato, ma non ci permette di cambiare il “method” della richiesta. Fortunatamente il metodo getJson presente nello snippet è soltanto un overload del metodo ajax(), che ci offre un maggior numero di opzioni, alcune delle quali utili al raggiungimento del nostro scopo.

Di seguito si può vedere come è possibile invocare la action anche in POST:

$.ajax({
    type: "POST",
    url: "/Home/JsonAction,
    dataType: 'json',
    success: function (dr) {
        $.each(dr, function () {
        //DO SOMETHING
        });
    }
});

A questo punto ci siamo tutelati da un eventuale attacco come quello di cui è stata vittima Google, ed abbiamo modificato a costo “piuttosto basso” il nostro codice javascript.

jQuery Rulez!
.u


MVC , JQuery , Security , Json

3 comments

Unit test su HttpModule ed un HttpHandler

Print Content | More

ASP.NET MVC ha aperto un mondo nuovo allo sviluppo di applicazioni web, ossia quello del testing. Di fatto, grazie ad MVC sono stati astratti alcuni concetti che precedentemente impedivano la testabilità delle webforms.

Purtroppo anche con MVC alcune cose rimangono scomode da testare, come gli HttpModule e HttpHandler; anzi, nella normale implementazione non sono proprio testabili. Cercando un po’ in rete ho scovato questo post, che mostra un approccio molto elegante su come effettuare Unit Test anche sui moduli e sugli handler, ma procediamo per gradi.

Con l’uscita del  ServicePack 1 del .NET Framework è stata introdotta una nuova libreria, la “System.Web.Abstraction”, contenente una serie di wrapper che hanno lo scopo di impedire l’utilizzo diretto di alcune classi (come l’HttpContext) e, di conseguenza, permettono di testare del codice precedentemente non testabile (HttpModule e HttpHandler).
Per far ciò è necessario creare delle classi base da cui tutti i Module/Handler andranno ad ereditare e gestire gli eventi a livello di baseclass, permettendo così un’eventuale override della classe concreta nel caso del Module, o un’implementazione nel caso dell’Httphandler. Nell’esempio seguente viene mostrata la base classe per un HttpModule:

/// <summary>
///        The base class for the HttpModules
/// </summary>
public abstract class BaseHttpModule : IHttpModule
{
    #region IHttpModule Members

    /// <summary>
    /// Initializes a module and prepares it to handle requests.
    /// </summary>
    /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>
    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, e) => OnBeginRequest(new HttpContextWrapper(((HttpApplication)sender).Context));
        context.Error += (sender, e) => OnError(new HttpContextWrapper(((HttpApplication)sender).Context));
        context.EndRequest += (sender, e) => OnEndRequest(new HttpContextWrapper(((HttpApplication)sender).Context));
    }

    /// <summary>
    /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
    /// </summary>
    public virtual void Dispose()
    {
    }

    #endregion

    /// <summary>
    /// Method called when a server receive a webrequest before other requests
    /// </summary>
    /// <param name="context">The context.</param>
    public virtual void OnBeginRequest(HttpContextBase context)
    {
    }

    /// <summary>
    /// Method called when an error occurred.
    /// </summary>
    /// <param name="context">The context.</param>
    public virtual void OnError(HttpContextBase context)
    {
    }

    /// <summary>
    /// Method called when a server receive a webrequest and all methods in the request life cycle are completed.
    /// </summary>
    /// <param name="context">The context.</param>
    public virtual void OnEndRequest(HttpContextBase context)
    {
    }
}

Da qui l’implementazione di un Module (nell’esempio il ReferrerModule di dexter semplificato) è piuttosto banale, l’unica differenza è che invece di agganciare un evento va effettuato l’override del metodo virtual presente sulla classe base, come mostrato di seguito:

public class ReferrerModule : BaseHttpModule
{
    private ILogger logger;
    private ITraceService traceService;
    private IUrlBuilderService urlbuilder;

    public ILogger Logger
    {
        get { return logger ?? (logger = IoC.Resolve<ILogger>()); }
    }

    public ITraceService TraceService
    {
        get { return traceService ?? (traceService = IoC.Resolve<ITraceService>()); }
    }

    public IUrlBuilderService Urlbuilder
    {
        get { return urlbuilder ?? (urlbuilder = IoC.Resolve<IUrlBuilderService>()); }
    }

    public ReferrerModule ()
    {
    }

    public ReferrerModule ( ILogger logger , ITraceService traceService , IUrlBuilderService urlbuilder )
    {
        this.logger = logger;
        this.traceService = traceService;
        this.urlbuilder = urlbuilder;
    }

    public override void OnEndRequest ( HttpContextBase context )
    {
        base.OnEndRequest ( context );

        if (context.Request.UrlReferrer != null)
            TraceService.AddReferrer(url.ToString(), referrer.ToString());
    }
}

A questo punto il test è facilmente scrivibile, come mostrato sotto:

[TestMethod]
public void OnEndRequest_WithValidRequestUrl_ShouldInvokeTheServiceMethod()
{
    //Arrage
    var httpContext = MockRepository.GenerateStub<HttpContextBase> ();
    var httpRequest = MockRepository.GenerateStub<HttpRequestBase>();
    var httpResponse = MockRepository.GenerateStub<HttpResponseBase>();

    httpContext.Expect ( x => x.Request ).Return ( httpRequest );
    httpContext.Expect(x => x.Response).Return(httpResponse);
        
    Uri currentUrl = new Uri ( "http://www.tostring.it");
    Uri urlReferrer = new Uri ( "http://www.bing.com/search?q=imperugo");
    
    httpRequest.Expect ( x => x.Url ).Return ( currentUrl ) );
    httpRequest.Expect ( x => x.UrlReferrer ).Return ( urlReferrer ) );

    ITraceService traceService = MockRepository.GenerateMock<ITraceService> ();

    var module = new ReferrerModule (
        MockRepository.GenerateStub<ILogger> () ,
        traceService ,
        MockRepository.GenerateStub<IUrlBuilderService> ()
        );

    //Act
    module.OnBeginRequest(httpContext);

    //TODO:Assert
    traceService.AssertWasNotCalled(x => x.AddReferrer(Arg<Uri>.Is.Equal(currentUrl), Arg<Uri>.Is.Equal(urlReferrer)));
    
}

Come potete vedere, se si ha la necessità di iniettare delle dipendenze potete creare un secondo costruttore che accetti l’instanza della dipendenza e gestire l’eventuale null nella property di get o nel costruttore parameterless (nel mio caso ero obbligato a gestire la dipendenza dalle properties perchè non avevo ancora inizializzato l’IoC Container al momento in cui l’HttpModule viene registrato nell’applicazione, problema che in Dexter si andrà a risolvere nelle prossime release).

Per quanto riguarda un HttpHandler l’approccio è esattamente lo stesso, classe base, metodi virtual ed override.

/// <summary>
///        The base class for the HttpHandlers
/// </summary>
public abstract class HttpHandlerBase : IHttpHandler
{
    #region IHttpHandler Members

    /// <summary>
    /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance.
    /// </summary>
    /// <value></value>
    /// <returns>true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false.
    /// </returns>
    public virtual bool IsReusable
    {
        get { return false; }
    }

    /// <summary>
    /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
    /// </summary>
    /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
    public void ProcessRequest(HttpContext context)
    {
        ProcessRequest(new HttpContextWrapper(context));
    }

    #endregion

    /// <summary>
    /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
    /// </summary>
    /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
    public abstract void ProcessRequest(HttpContextBase context);
}

Buopn Testing
Ciauz

.u


ASP.NET , MVC , Unit Test , Testing , HttpModule , HttpHandler

2 comments

Testmex => tab (snippet)

Print Content | More

In questo periodo sto scrivendo test in continuazione, un po’ perchè sto leggendo il libro di Roy OsheroveThe Art of Unit Test”, ed un po’ perchè sto cercando di colmare un gap su dexter. Chi mi frequenta pensa che ormai sono vittima del testing in quanto non faccio altro che parlare di unit test, di come scrivere test, etc., e devo ammettere che un po’ è anche vero :).

Il tutto è partito da una certa persona (un po’ contabile ed un po’ commercialista :D) che mi ha spronato più e più volte a guardare lo sviluppo anche da una prospettiva differente, ossia da quella del testing...per questo  non posso che ringraziarlo, anche se per assimilare bene i concetti e metterli in pratica ho impiegato un po’ di tempo, ma credo che sia del tutto normale.

Parlando dell’aspetto pragmatico dei test scritti in questi giorni, posso dire che hanno una cosa che li contraddistingue, ossia la presenza di SharpTestEx e RhinoMock; di fatto mi sono creato uno snippet che mi creasse a sua volta un metodo con la struttura secondo la mia nomenclatura preferita e, nel caso mi aspettassi un’eccezione dal test, mi implementasse anche il controllo della stessa.
Per farla breve tutti i miei test devono avere un nome leggibilissimo, che rispecchi il più possibile i tre aspetti base, quindi far capire cosa si sta testando, con quali valori e cosa ci si aspetta:

“MethodUnderTest_Scenario_ExpectedBehavior”

In un esempio pratico in cui si voglia testare un metodo “GetList”, passando un valore negativo al parametro “pageSize” e aspettandosi dal metodo da testare un’eccezione, il nome del test dovrebbe essere una cosa tipo: “GetList_WithNegativePageSize_ShouldThrowArgumentOutOfRangeException” che, tradotto in soldoni, dovrebbe essere implementato più o meno così:

[TestMethod]
public void GetList_WithNegativePageIndex_ShouldThrowNewArgumentOutOfRangeException()
{
    //TODO:Arrage

    //TODO:Act

    //TODO:Assert
    ActionAssert.Throws<ArgumentOutOfRangeException>(() => something).ParamName.Should().Be.EqualTo("pageSize");
}

Purtroppo anche con il copia/incolla può essere scomodo ripetere ogni volta questo codice, così mi sono deciso a scrivere uno snippet che, digitando !testmex + tab!, mi crea automaticamente lo scheletro.

Di seguito lo snippet che possiamo copiare ed incollare direttamente nell’apposita folder

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
            <Title>Test Method With Exception Management</Title>
            <Shortcut>testmex</Shortcut>
            <Description>Code snippet for a test method with Exception </Description>
            <Author>Ugo Lattanzi</Author>
        </Header>
        <Snippet>
            <Imports>
                <Import>
                    <Namespace>SharpTestsEx</Namespace>
                </Import>
                <Import>
                    <Namespace>Rhino.Mocks</Namespace>
                </Import>
            </Imports>
            <References>
                <Reference>
                    <Assembly>SharpTestsEx.MSTest.dll</Assembly>
                    <Assembly>Rhino.Mocks.dll</Assembly>
                </Reference>
            </References>
            <Declarations>
                <Literal>
                    <ID>MethodName</ID>
                    <ToolTip>Replace with the name of the test method</ToolTip>
                    <Default>MethodName</Default>
                </Literal>
                <Literal>
                    <ID>StateUnderTest</ID>
                    <ToolTip>Replace with the state under test name</ToolTip>
                    <Default>StateUnderTest</Default>
                </Literal>
                <Literal>
                    <ID>ExpectedParameterName</ID>
                    <ToolTip>Replace with the expected exception parameter name</ToolTip>
                    <Default>ExpectedParameterName</Default>
                </Literal>
                <Literal>
                    <ID>ExceptionType</ID>
                    <ToolTip>Exception type</ToolTip>
                    <Function>SimpleTypeName(global::System.Exception)</Function>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[[TestMethod]
          public void $MethodName$_$StateUnderTest$_ShouldThrowNew$ExceptionType$()
        {
            //TODO:Arrage
            
            //TODO:Act
            
            //TODO:Assert
            ActionAssert.Throws<$ExceptionType$> ( () => something ).ParamName.Should().Be.EqualTo ( "$ExpectedParameterName$" );
          }]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

enjoy the snippet!

Ciauz


Visual Studio , Snippet , Testing , SharpTestEx , Unit Test

3 comments

Disponibile il mio webcast su ASP.NET MVC 2

Print Content | More

Ho scoperto solo questo weekend la pubblicazione di un mio webcast su MVC intitolato “ASP.NET MVC 2: Powerful data form” sul portale BEIT di Microsoft.
Devo dire di essere particolarmente contento per aver avuto la possibilità di registrare questo webcast (il primo per me) e ringrazio partiolarmente Pietro per avermi dato la fiducia e la possibilità.
Ovviamente se qualcuno ha feedback, domande, etc mi trovate qui.

image


MVC , Webcast

0 comments

Un’odissea chiamata Boot-Camp

Print Content | More

Provo a raccontare un po’ quanto già detto da Paperino qui.

Poco più di una settimana fa mi è arrivato il fatidico MacBook Pro aziendale, ordinato a metà febbraio (core i7, 8gb di Ram, 500gb di disco, etc). Ad un primo impatto devo dire che il prodotto è di ottima fattura, che sia bello è inutile negarlo, il monitor spettacolare, scheda audio ottima, tastiera scomoda (per l’utente windows) ma illuminata e abbastanza sensibile.
Nel complesso devo dire proprio un bel prodotto. Ora, la domanda che normalmente mi fanno quando dico che ho un MacBook Pro è: “ma come mai un Mac quando lavori con Windows?”

Beh la risposta nasce dall’esigenza di mercato, il Mac inizia ad essere “diffuso”, l’esigenza di dover sviluppare un qualcosa per iPhone è una realtà, e poi diciamolo, sul Mac “gira” anche Windows!

Proprio su questo argomento devo dire che la società di Cupertino non è che possa tanto impedire l’installazione di Windows, dato che l’hardware è il medesimo di tantissimi altri pc, e quindi ha ben pensato di creare una dipendenza del sistema operativo di Redmond da loro; di fatto, ad oggi non è possibile utilizzare Windows su un prodotto Mac senza utilizzare i loro drivers.

Il motivo è piuttosto semplice: pur trattandosi dello stesso hardware, gli ID sono differenti da quelli presenti su tutti i pc, e, di fatto, se si prova ad installare il driver della scheda video nVidia su un Mac, si riceve un bel messaggio che comunica all’utente la non presenza della scheda per la quale si sta tentando di installare il driver; se si prova a far cercare a Windows stesso il driver, la musica non cambia perchè c’è sempre quell’HW ID sconosciuto, quindi bisogna arrendersi ed installare i drivers Apple.

Ad un primo impatto ho pensato: “vabbè, sarà solo la scheda video, pazienza!”, invece neanche a pensarci… tutto l’hardware che riporto di seguito necessita dei drivers Boot-Camp:

  • Apple Bluetooth;
  • Apple Keyboard Support;
  • Apple Remote Driver;
  • Apple Trackpad;
  • Atheros 802.11 Wireless;
  • Boot Camp control panel for Microsoft Windows;
  • Boot Camp System Task Notification item (System Tray);
  • Broadcom Wireless;
  • Intel Chipset Software;
  • Intel Integrated Graphics;
  • iSight Camera;
  • Marvel Yukon Ethernet;
  • nVidia Graphics;
  • Cirrus Logic Audio;
  • Realtek Audio;
  • SigmaTel Audio;
  • Startup Disk control panel for Microsoft Windows

Direi un bel po’ di roba che, con i drivers scritti da Apple, funziona malissimo in ambiente Windows; tantissimi errori del Bluetooth (il Magic Mouse impazzisce), che mi hanno portato anche a resettare la PRAM con una valangata di problemi a seguire (periferiche viste due volte, schede non riconosciute, il tool di boot-camp che ignora i settaggi), schermate blu a go go, per non parlare della durata della batteria che sotto Windows è di 3.5 ore, mentre con OSX è di ben 7.5 ore.

Direi un netta differenza di durata, e a quel punto mi son detto : “possibile che Windows consumi così tanto"?”; indagando un po’ ho scoperto, grazie allo spione di “powercfg –energy”, che tutte le periferiche con driver firmato Apple non permettono al sistema operativo di gestire il loro consumo energetico e di conseguenza di disabilitarne alcune allo scopo di preservare la durata della batteria.

Per chi fosse ancora dubbioso sul fatto che un driver non può influire tanto sul funzionamento di una periferica, dico solo che sotto OSX con un disco Firewire il transfer-rate è di 55 Mb/sec, sotto Windows, con la stessa porta e lo stesso Hard Disk, è di 15 Mb/sec (il buon Mauro ne può raccontare molte altre…)!

Ad oggi, dopo 10gg, ho formattato 2 volte la partizione Windows, ed ho tutte le periferiche di rete doppie.

Da qui è nata la domanda: “ma quando un utente prova un computer Apple con dentro Windows e si accorge di quanto possa andare male, cosa pensa?”
Per la risposta leggete qui.

enjoy your Mac!


BootCamp , Windows , Apple

5 comments

Aero, Power Saver e Windows 7

Print Content | More

Se, per qualche strano motivo, quando cambiate la modalità di utilizzo del vostro portatile impostandola in “Power Saver”, Aero rimane attivo invece di disabilitare alcuni effetti, come la trasparenza (causando un ovvio consumo di batteria in più), potete risolvere il problema seguendo passo passo i seguenti step:

  1. Be sure you don't have an unsaved theme in the personalization window. In that case delete it.
  2. Save your theme if you didn't do yet.
  3. Switch to balanced power plan if you are using another plan.
  4. Select Windows 7 and switch back to your theme then. Keep power options and personalization windows open.
  5. Now change power plan to power saver
  6. In personalization window -> window color and and disable trasparency. Click save changes. You can see that an unsaved theme has just been created and activated.
  7. Select balanced power plan.
  8. Activate your theme, delete the unsaved theme and close personalization window.

Sembra una cosa assurda, ma funziona :)

Fonte: http://social.technet.microsoft.com/Forums/en-US/w7itproperf/thread/e11d6d0c-b3ac-4e44-8912-ef64e6484e5b


Windows 7

0 comments

SharpTestEx e Visual Studio 2010

Print Content | More

Già dal post precedente si capiva che ho avuto problemi con SharpTestEx e Visual Studio 2010. Nello specifico il problema era dovuto al fatto che il Framework di testing cercava di caricare un’assembly non presente nel mio computer, in quanto disponibile con Visual Studio 2008 che non ho avuto ancora il tempo di installare (maggiori info qui).

Detto ciò, dopo uno scambio di email con il disponibilissimo Fabio Maulo, siamo giunti a due soluzioni possibili:

  1. Scaricare e ricompilare il progetto;
  2. Effettuare un redirect del binding;

Per ovvi motivi di tempo ho optato per la seconda soluzione, già spiegata nel bug di codeplex, ma che riporto qui di seguito:

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <dependentAssembly>
            <assemblyIdentity name="Microsoft.VisualStudio.QualityTools.UnitTestFramework" 
                    publicKeyToken="b03f5f7f11d50a3a" 
                    culture="neutral" />
             <bindingRedirect oldVersion="9.0.0.0"
                    newVersion="10.0.0.0"/>
        </dependentAssembly>
    </assemblyBinding>
</runtime>

Enjoy SharpTestEx.


Visual Studio 2010 , SharpTestEx , Unit Test

0 comments

Assembly Binding Log Viewer (spettacolo).

Print Content | More

Ametto la mia totale ignoranza a riguardo, ma purtroppo ho scoperto solo ieri l’esistenza di questo tool (fighissimo) che mi ha permesso di risolvere un problema in pochi istanti; ma partiamo con ordine.

La settimana scorsa mi è arrivato il portatile aziendale ed ovviamente c’è stata tutta la trafila di installazione di quei tools fondamentali per il lavoro, quindi Visual Studio, Reflector, Sql, etc.
Forse perchè lo uso un po’ meno, però ho tralasciato l’installazione di Visual Studio 2008 (in azienda siamo su 2010 e per i miei sfoghi tecnologici non uso sicuramente la 2008).

Una volta reso abile ed arruolato il pc per lavorare, ho deciso di mettermi a scrivere un po’ di test: tutti i test che precedentemente andavano hanno deciso di non andare e, dalla test detail view, si poteva notare che il problema era riconducibile ad un’assemlby ben precisa “Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0”, ed il messaggio di errore era il seguente:

Test method Dexter.Core.UI.Framework.Test.Services.PostServiceTest.Get_list_with_valid_data_should_raise_post_retrieved_list_event threw exception:
System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

La prima domanda è stata: come mai c’è qualcosa che sta referenziando la versione 9.0.0.0 dello UnitTestFramework quando tutto il progetto è su .NET Framework 4.0? Ma sopratutto chi lo stava facendo?

Il buon Gian Maria mi ha indirizzato verso questo tool (per avviarlo è sufficiente digitare dal prompt di Visual Studiofuslogvw”), che permette di abilitare e consultare il Binding Log tramite una UserInterface, senza andare di conseguenza ad agire manualmente sulle chiavi di registro. Di fatto, dopo aver impostato il livello di logging nella finestra dei settings e rieseguito i test, la finestra del tool si è popolata come da screenshot:

image

La cosa bella è che facendo doppio click sulla voce interessata all’interno della lista viene aperto un ulteriore dettaglio che permette di individuare chi referenzia l’assembly mancante.

image

 

 

In questo caso SharpTestEx.MSTest.

Ciauz


Tools , Unit Test , Visual Studio

0 comments