.net comments edit

Il bello di avere un collega MVP su C#, ma ancor prima devMaskio, è che quando hai un dubbio sul linguaggio lui sa subito risponderti e spesso con una soluzione al tuo problema.
Sorvolando la domanda ed il perchè è nata, l’idea era quella di evitare la noiosa costruzione di apposite classi di “state” per invocare metodi in asincroni.
Osservando la parte pragmatica del problema, prima della versione 4.0 del .NET Framework quello che dovevamo fare per invocare un metodo asincrono era più o meno questo:

internal class Program
{
    private static void Main(string[] args)
    {
        AsyncCallState obj = new AsyncCallState
                                 {
                                     Property1 = "String" ,
                                     Property2 = 10 ,
                                     Property3 = new AsyncCallState2 ()
                                                     {
                                                         P1 = "SubProperty1"
                                                     }
                                 };

        ThreadPool.QueueUserWorkItem(AsyncCall, obj);

        Console.ReadLine();
    }

    private static void AsyncCall(object obj)
    {
        AsyncCallState anonymous = (AsyncCallState)obj;

        string p1 = anonymous.Property1;
        int p2 = anonymous.Property2;

        string sP1 = anonymous.Property3.P1;

    }
}

internal class AsyncCallState
{
    public string Property1 { get; set; }
    public int Property2 { get; set; }
    public AsyncCallState2 Property3 { get; set; }
}

internal class AsyncCallState2
{
    public string P1 { get; set; }
}

Come potete vedere le classi AsyncCallState ed AsyncCallState2 hanno un utilizzo ridottissimo e legato soltanto a questa chiamata, infatti difficilmente ci capiterà di riutilizzare questa classe per altre firme all’interno della nostra applicazione.
Grazie alla keyword dynamic di C# 4.0 è possibile evitare la costruzione di questa classe e risparmiare un bel po’ di tempo:

internal class Program
{
    private static void Main ( string[] args )
    {
        ThreadPool.QueueUserWorkItem ( AsyncCall , new
                                                       {
                                                           Property1 = "String" ,
                                                           Property2 = 10 ,
                                                           Property3 = new
                                                                           {
                                                                               P1 = "SubProperty1"
                                                                           }
                                                       } );

        Console.ReadLine ();
    }

    private static void AsyncCall ( object obj )
    {
        dynamic anonymous = obj;

        string p1 = anonymous.Property1;
        int p2 = anonymous.Property2;
        dynamic p3 = anonymous.Property3;

        string sP1 = p3.P1;

    }
}

Ciauz

asp.net comments edit

Ultimamente sto facendo parecchio uso ASP.NET MVC 2, più precisamente mi sto occupando della parte di markup, jQuery ed input di dati; di fatto in un post precedente avevo spiegato come era possibile sfruttare jQuery e le DataAnnotations per validare dei dati sul client.

Quanto detto precedentemente funziona perfettamente nel caso in cui l’oggetto che effettuerà il submit della <form> sarà un input type, al contrario se si ha la necessità di invocare il submit tramite un’immagine o un link è necessario a ricorrere al javascript.
Purtroppo se si invoca il classico metodo submit() per invocare l’action della form, non viene invocata la validazione (questo a prescindere dal fatto che si usi o no jQuery per validare la form) e di conseguenza il controllo dei dati di input avverrebbe totalmente lato server.

Il problema è facilmente aggirabile e consiste nell’invocare via javascript la validzione associata alla form e, nel caso questa venga superata, si può invocare il submit() sopracitato.
Lo script seguente mostra come fare:

<div id="submitbox" class="left">
    <span class="button submit">
        <a href="javascript:if($('#myForm').validate().form())$('#myForm').submit();" title="Submit form">
            <span>Submit form</span>
        </a>
    </span>
</div>

byez

.u

asp.net comments edit

Tempo fa avevo parlato qui di come realizzare un ViewEngine Custom per ASP.NET MVC che permettere di gestire diversi temi per la stessa applicazione, ossia offre la possibilità di cambiare la folder dove andare a “pescare” le nostre Views, Master, etc. a runtime, ma il tutto su ASP.NET MVC 1.0.

Con la nuova release di MVC è stata aggiunta una comodissima novità, ossia il supporto alle “Aree”, che non sono altro che dei “sottositi” che hanno lo scopo di semplificare la struttura dei Controllers e delle Views presenti nella struttura principale.
L’immagine seguente rende meglio l’idea di cosa siano le Aree e di come si collochino all’interno del nostro progetto:

 

image 

Cambiando un po’ la struttura delle folders ho dovuto modificare mio “vecchio” ViewEngine in modo che “digerisca” questa nuova feature.
Il codice è più o meno lo stesso del ViewEngine precedente, fatta eccezione per alcune classi (come AreaHelpers, già presente nel framework ma di tipo internal e quindi inutilizzabile).

Di seguito riporto il codice:

public partial 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 () != 404 )
                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>
    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 = DexterEnvironment.Instance.Context.CurrentTheme();
        
        string requiredString = controllerContext.RouteData.GetRequiredString ( "controller" );
        
        string viewPath = this.GetPath(controllerContext, this.ViewLocationFormats, this.AreaViewLocationFormats , viewName, requiredString, "View", useCache, out strArray, themeName);
        string masterPath = this.GetPath(controllerContext, this.MasterLocationFormats, this.AreaMasterLocationFormats , masterName, requiredString, "Master", useCache, out strArray2, themeName);
        
        if ( !string.IsNullOrEmpty ( viewPath ) && ( !string.IsNullOrEmpty ( masterPath ) || string.IsNullOrEmpty ( masterName ) ) )
            return new ViewEngineResult ( CreateView ( controllerContext , viewPath , masterPath ) , this );
        
        ViewEngineResult view = new ViewEngineResult ( strArray.Union ( strArray2 ) );
        
        if (view.View == null)
            throw new HttpException(404, "File Not Found");
        
        return view;
    }

    /// <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>
    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 = DexterEnvironment.Instance.Context.CurrentTheme();
        
        string requiredString = controllerContext.RouteData.GetRequiredString ( "controller" );
        
        string partialViewPath = this.GetPath(controllerContext, this.PartialViewLocationFormats, this.AreaPartialViewLocationFormats , partialViewName, requiredString, "Partial", useCache, out strArray, themeName);
        
        if ( string.IsNullOrEmpty ( partialViewPath ) )
            return new ViewEngineResult ( strArray );
        
        return new ViewEngineResult ( CreatePartialView ( controllerContext , partialViewPath ) , this );
    }


    private string GetPath( ControllerContext controllerContext , string[] locations , string[] areaLocations , string name , string controllerName , string cacheKeyPrefix , bool useCache , out string[] searchedLocations , string themeName )
    {
        searchedLocations = new string[0];
        
        if (string.IsNullOrEmpty(name))
            return string.Empty;
        
        string areaName = AreaHelper.GetAreaName(controllerContext.RouteData);
        bool flag = !string.IsNullOrEmpty(areaName);
        
        List<ViewLocation> viewLocations = GetViewLocations(locations, flag ? areaLocations : null);
        
        if (viewLocations.Count == 0)
            throw new InvalidOperationException("locations must not be null or emtpy.");
        
        bool flag2 = IsSpecificPath(name);
        string key = this.CreateCacheKey(cacheKeyPrefix, name, flag2 ? string.Empty : controllerName, areaName,themeName);
        
        if (useCache)
        {
            string viewLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
        
            if (viewLocation != null)
                return viewLocation;
        }
        
        return !flag2
                   ? this.GetPathFromGeneralName ( controllerContext , viewLocations , name , controllerName , areaName , key , ref searchedLocations, themeName )
                   : this.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 areaName,string themeName)
    {
        return string.Format(CultureInfo.InvariantCulture, ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:{5}:", new object[] { GetType().AssemblyQualifiedName, prefix, name, controllerName, areaName ?? "nullArea", themeName });
    }

    private static List<ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats)
    {
        List<ViewLocation> list = new List<ViewLocation>();
        if ( areaViewLocationFormats != null )
            list.AddRange ( areaViewLocationFormats.Select ( str => new AreaAwareViewLocation ( str ) ).Cast<ViewLocation> () );
        
        if ( viewLocationFormats != null )
            list.AddRange ( viewLocationFormats.Select ( str2 => new ViewLocation ( str2 ) ) );
        
        return list;
    }

    private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations, string themeName)
    {
        string virtualPath = string.Empty;
        searchedLocations = new string[locations.Count];
        for (int i = 0; i < locations.Count; i++)
        {
            string str2 = locations[i].Format(name, controllerName, areaName,themeName);
            if (this.FileExists(controllerContext, str2))
            {
                searchedLocations = new string[0];
                virtualPath = str2;
                this.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 (!this.FileExists(controllerContext, name))
        {
            virtualPath = string.Empty;
            searchedLocations = new[] { name };
        }

        this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
        return virtualPath;
    }
}

//Classe introdotta per il supporto alle aree
internal class ViewLocation
{

    protected readonly string VirtualPathFormatString;
    
    public ViewLocation ( string virtualPathFormatString )
    {
        this.VirtualPathFormatString = virtualPathFormatString;
    }
    
    public virtual string Format ( string viewName , string controllerName , string areaName , string themeName )
    {
        return string.Format ( CultureInfo.InvariantCulture , this.VirtualPathFormatString , new object[]
                                                                                                  {
                                                                                                      viewName ,     controllerName,themeName
                                                                                                  } );
    }
}

//Classe introdotta per il supporto alle aree
internal static class AreaHelper
{
    public static string GetAreaName ( RouteData routeData )
    {
        object obj2;
        if ( routeData.DataTokens.TryGetValue ( "area" , out obj2 ) )
        {
            return ( obj2 as string );
        }
        return GetAreaName ( routeData.Route );
    }

    public static string GetAreaName ( RouteBase route )
    {
        IRouteWithArea area = route as IRouteWithArea;
        if ( area != null )
        {
            return area.Area;
        }
        Route route2 = route as Route;
        if ( ( route2 != null ) && ( route2.DataTokens != null ) )
        {
            return ( route2.DataTokens [ "area" ] as string );
        }
        return null;
    }
}

//Classe introdotta per il supporto alle aree
internal class AreaAwareViewLocation : ViewLocation
{
    public AreaAwareViewLocation(string virtualPathFormatString)
        : base(virtualPathFormatString)
    {
    }

    public override string Format( string viewName , string controllerName , string areaName , string themeName )
    {
        return string.Format(CultureInfo.InvariantCulture, VirtualPathFormatString, new object[] { viewName, controllerName, areaName });
    }

}

Ciauz

.u

asp.net comments edit

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

asp.net comments edit

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:

<machinekey decryption="AES" decryptionkey="CB5A09CB8CAF8CD33A97F1099A451D7C80C9CC175F34DDAFA925BA55043570CB" validation="SHA1" validationkey="E7E40ADAC94F3D467AAB86AEA2561E246A6323C69A0B32BF808587694E1CB387265CC6F2C46420F315831B54F683FA82F08A8E95E00B93BFEC3CD91DF65FEE8C"></machinekey>

stay tuned!
.u

asp.net comments edit

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

asp.net comments edit

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

.net comments edit

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

asp.net comments edit

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

various comments edit

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!