Tag Archives: Errors and Exceptions

Coded UI Tests: The playback failed to find the control with the given search properties.

Ve VS 2013 pomocí Coded UI Test Builderu nahrávám skript pro proklikání stránek.

Test se podaří vyrobit, ale při přehrávání dostávám výjimku:

Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotFoundException: The playback failed to find the control with the given search properties. Additional Details: 
TechnologyName:  'Web'
ControlType:  'Hyperlink'
TagName:  'A'
Id:  ''
Name:  ''
Target:  ''
InnerText:  'Kanda Jiří'
 Failed to find any control that matched the search condition Id='' && Name='' && ControlType='Hyperlink' && Target='' && InnerText='Kanda Jiří' ---> System.Runtime.InteropServices.COMException: Volání součásti COM vrátilo chybu HRESULT E_FAIL.

Hraji si s vyrobeným kódem a zjišťuji, že problém se vyskytuje pouze tehdy, pokud se hledá pomocí podmínky na InnerText.

This.Xyz.SearchProperties[HtmlHyperlink.PropertyNames.InnerText] = "xyz";

Pokud je tato podmínka odstraněna, test začne fungovat.

Řešení, jak tento problém obejít, je neuvěřitelné a pro mě těžko pochopitelné. V PlaybackSettings je třeba nastavit vlastnost AlwaysSearchControls (což je nutné i pro další problém s Coded UI Testy) a navíc značně prodloužit DelayBetweenActions z výchozí hodnoty 100 ms.

Playback.PlaybackSettings.AlwaysSearchControls = true;
Playback.PlaybackSettings.DelayBetweenActions = 500;

Coded UI Tests: Another control is blocking the control. Please make the blocked control visible and retry the action…

Ve VS 2013 pomocí Coded UI Test Builderu nahrávám skript pro proklikání stránek na www.havit.cz. Test se podaří vyrobit, ale při přehrávání dostávám výjimku:

Microsoft.VisualStudio.TestTools.UITest.Extension.FailedToPerformActionOnBlockedControlException: Another control is blocking the control. Please make the blocked control visible and retry the action. Additional Details:
TechnologyName:  'Web'
ControlType:  'Hyperlink'
TagName:  'A'
Id:  ''
Name:  ''
Target:  ''
InnerText:  '…'
 ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0xF004F003

Na internetu se tomuto problému věnují obsáhlé diskuse, většinou okolo VS 2012 a IE9/10. Většina doporučených řešení spočívá v odinstalaci IE10, odinstalaci patche KB2870699 nebo instalaci VS 2012 Update 4. Některé řešení většině zabírá.

My jsme však o verzi dále – VS 2013 a IE 11, kde žádné z předchozích řešení nefunguje. Chyba se shodně projevuje na šesti ze sedmi počítačů, nepůjde tedy o náhodný problém s jedním počítačem. Shodou náhod reinstaluji jeden starý stroj, zkouším tedy čistou instalaci (Windows 7, Internet Explorer 11, Visual Studio 2013, kompletní aktualizace Windows Update) a ověřuji, že problém se objevuje i zde.

Výjimka říká, že se nepodařilo na control kliknout, že je z nějakého důvodu blokovaný. Pokud zkusím zobrazit místo, kde se control nachází (metodou DrawHightlight() ), je místo označeno správně, takže se domnívám, že v hledání controlu by neměl být problém a mělo by jít skutečně o problém přehrávání. K mému překvapení zabírá řešení, které čistě náhodně zkouším – nastavení AlwaysSearchControls na true.

Playback.PlaybackSettings.AlwaysSearchControls = true;

Health Monitoring a sledování chyb v ASP.NET webových službách (.asmx)

Jak jsem již popisoval v předchozím články, nevýhodou jinak šikovného mechanizmu Health Monitoringu je, že nesbírá chyby z aynchronních requestů a z webových služeb ASP.NET. Jak se vypořádat s prvním problémem u AJAX requestů bylo již naznačeno, podívejme se teď na problematiku webových služeb ASP.NET (.asmx). V zásadě jde opět o zachycení problémové výjimky a předání WebRequestErrorEvent.

SoapExceptionHealthMonitoringHandler (SoapExtension)

Elegantní metodou jak se dostat k odběru výjimek během zpracování webových služeb a jak je předávat mechanizmu Health Monitoringu je požití SoapExtension. Uvádím opět kód kolegy Jiřího Kandy (pomocná třída WebRequestErrorEventExt byla již definována v předchozím článku):

/// <summary>
/// V případě chyby ve zpracování web metody (webové služby) zajistí oznámení chyby health monitoringem.
/// Pozor, toto nefunguje (a chyby healthmonitoringu tak nejsou oznamovány),
/// pokud se webové služby testují v browseru!!! Pro testování nutno použít skutečného klienta webové služby (třeba service reference v konzolovce).
/// </summary>
public class SoapExceptionHealthMonitoringHandler : System.Web.Services.Protocols.SoapExtension
{
    #region ProcessMessage
    public override void ProcessMessage(System.Web.Services.Protocols.SoapMessage message)
    {
        try
        {
            if ((message != null) && (message.Stage == SoapMessageStage.AfterSerialize))
            {
                if (message.Exception != null)
                {
                    Exception exception = message.Exception;
                    if ((exception is SoapException) && (exception.InnerException != null))
                    {
                        exception = exception.InnerException;
                    }
                    if ((exception is HttpUnhandledException) && (exception.InnerException != null))
                    {
                        exception = exception.InnerException;
                    }
                    new WebRequestErrorEventExt(exception.Message, message, exception).Raise();
                }
            }
        }
        catch // pokud by zde nedejbože došlo k nějaké další výjimce, tak ji zamaskujeme
        {
            // NOOP
        }
    }
    #endregion

    #region GetInitializer
    public override object GetInitializer(Type serviceType)
    {
        return null;
    }

    public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
    {
        return null;
    }
    #endregion

    #region Initialize
    public override void Initialize(object initializer)
    {
    }
    #endregion
}

…a zapojení ve web.configu potom v elementu configuration/system.web/webServices:

<webServices>
    <soapExtensionTypes>
        <add type="MyNamespace.SoapExceptionHealthMonitoringHandler, MyAssembly" priority="0" group="0" />
    </soapExtensionTypes>
</webServices>

A JE TO.

Health Monitoring a sledování chyb asynchronních postbacků (AJAX)

Health Monitoring je šikovný vestavěný mechanizmus ASP.NET pro sledování a hlášení problémových situací. Typicky je používán pro zápis výjimek aplikace do event-logu nebo jejich posílání mailem.

Slabinou Health Monitoringu je, že se neumí vypořádat s chybami vzniklými během asynchronních postbacků (AJAX) ani s chybami vzniklými v rámci webových služeb ASP.NET. Přesněji řečeno ASP.NET na tyto chyby nějak nepamatuje a neoznamuje je do web-events mechanizmu, na němž je Health Monitoring závislý.

Podívejme se nyní na cestu, jak doplnit do Health Monitoringu sledování chyb AJAXu, sledování chyb webových služeb ponechávám do samostatného článku. Jak již bylo naznačeno, jde o to, že potřebujeme doplnit chybějící oznamování chyb do web.events mechanizmu. Chyba vzniklá během asynchronního postbacku způsobí vyvolání události ScriptManager.AsyncPostBackError. Potřebujeme se tedy napojit na tuto událost s vlastní obsluhou a v ní chybu předat jako WebRequestErrorEvent.

AjaxHealthMonitoring control

Jedním z možných opakovaně použitelných elegantních řešení je vytvoření vlastního controlu, který se bude umisťovat do stránek stejně jako samotný ScriptManager, např. tedy v MasterPage. Uvádím zde kód kolegy Jirky Kandy, který se s tím vypořádal takto:

public class AjaxHealthMonitoring: Control
{
    #region OnInit
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);

        ScriptManager scriptManager = ScriptManager.GetCurrent(Page);
        if (scriptManager == null)
        {
            throw new InvalidOperationException("Ve stránce nebyl nalezen ScriptManager, který je controlem AjaxHealthMonitoring vyžadován.");
        }
        scriptManager.AsyncPostBackError += new EventHandler<AsyncPostBackErrorEventArgs>(ScriptManager_AsyncPostBackError);
    }
    #endregion

    #region ScriptManager_AsyncPostBackError
    /// <summary>
    /// Obsluha události AsyncPostBackError ScriptManageru. Zajistí vyvolání události health monitoringu.
    /// </summary>
    private void ScriptManager_AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
    {
        if (e.Exception != null)
        {
            new WebRequestErrorEventExt(e.Exception.Message, this, e.Exception).Raise();
        }
    }
    #endregion
}

Pro úplnost uvádím odvozenou podobou třídy WebRequestErrorEventExt:

public class WebRequestErrorEventExt : WebRequestErrorEvent
{
    public WebRequestErrorEventExt(string message, object eventSource, Exception exception)
        : base(message, eventSource, WebEventCodes.WebExtendedBase + 999, exception)
    {
    }
}

Control se pak používá ve stránce stejně jako ScriptManager (do výstupního HTML kódu nic nerenderuje):

<asp:ScriptManager ScriptMode="Release" AllowCustomErrorsRedirect="true" runat="server" />
<havit:AjaxHealthMonitoring runat="server" />

SmtpTraceListener – mailování výstupů trace

V návaznosti na používání ExceptionTraceru, který pomocí standardní .NET mechanizmů Trace/TraceSource loguje neobsloužené výjimky, jsem dopsal jednoduchý TraceListener, který lze používání pro mailování těchto výjimek na určený mail. Cílovým scénářem je tedy automatické mailování výjimek z consolových/WinForm aplikací, či spíše utilit. Není to zatím příliš vyladěno, spíše takový náznak, jakou cestou se vydat.

Do činnosti se to zapojuje zhruba takto (app.config):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <sources>
            <source name="Exceptions" switchValue="Error">
                	<listeners>
<!--                    <add name="XmlListener" initializeData="Exceptions.xml" type="System.Diagnostics.XmlWriterTraceListener"/>
                    <add name="TextWriterListener" initializeData="Exceptions.log" type="System.Diagnostics.TextWriterTraceListener"/>
-->
                    <add name="SmtpListener" initializeData="Subject=Chyby z mojí utility;To=errors@firma.cz" type="Havit.Diagnostics.SmtpTraceListener, Havit"/>
                </listeners>
            </source>
        </sources>
    </system.diagnostics>
    <system.net>
        <mailSettings>
            <smtp deliveryMethod="Network" from="neco@nekde.cz">
                <network host="mail.server.cz"/>
            </smtp>
        </mailSettings>
    </system.net>
</configuration>

Základem je implementovat třídu dědící z předka TraceListener:

  /// <summary>
    /// TraceListener, který výstup posílá mailem.
    /// </summary>
    /// <remarks>
    /// Inspirováno implementaci System.Diagnostics.XmlWriterListener.
    /// </remarks>
    public class SmtpTraceListener : TraceListener
    {
        #region MailTo
        /// <summary>
        /// E-mailová adresa, na kterou se posílají zprávy.
        /// </summary>
        public string MailTo
        {
            get
            {
                if (_mailTo == null)
                {
                    return "devmail@havit.cz";
                }
                return _mailTo;
            }
            set
            {
                _mailTo = value;
            }
        }
        private string _mailTo;
        #endregion

        #region Subject
        /// <summary>
        /// Subject zprávy.
        /// </summary>
        public string Subject
        {
            get
            {
                if (_subject == null)
                {
                    return "SmtpTraceListener";
                }
                return _subject;
            }
            set
            {
                _subject = value;
            }
        }
        private string _subject;
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor, který je volán při použití TraceListerneru z app.configu a předává se do něj hodnota atributu initializeData.
        /// </summary>
        /// <param name="initializeData">hodnota atributu initializeData z app.config</param>
        public SmtpTraceListener(string initializeData)
        {
            if (initializeData == null)
            {
                return; // použijí se defaulty                
            }

            foreach (string arg in initializeData.Split(';'))
            {
                string[] paramValue = arg.Split('=');
                if (paramValue.Length >= 2)
                {
                    switch (paramValue[0].Trim().ToUpper())
                    {
                        case "TO":
                            MailTo = paramValue[1].Trim();
                            break;
                        case "SUBJECT":
                            Subject = paramValue[1].Trim();
                            break;
                        default:
                            throw new InvalidOperationException("Neznámý parametr konfigurace SmtpTraceListeneru v initializeData.");
                    }
                }
            }
        }
        #endregion

        #region SendMessage
        /// <summary>
        /// Interní implementace odesílání mailu.
        /// </summary>
        /// <param name="message">zpráva z trace</param>
        private void SendMessage(string message)
        {
            if (String.IsNullOrEmpty(this.MailTo))
            {
                return;
            }

            try
            {
                MailMessage mailMessage = new MailMessage();
                mailMessage.To.Add(this.MailTo);
                mailMessage.Subject = this.Subject;
                mailMessage.Body = message;
                SmtpClient smtpClient = new SmtpClient();
                smtpClient.Send(mailMessage);
            }
            catch
            {
                // NOOP - nechceme, aby nám nefunkční trace-mailing zabil server
#if DEBUG
                // při debugování nás to ale zajímá
                throw;
#endif
            }

            // http://www.codeproject.com/KB/trace/smtptracelistenerarticle.aspx
            // In the SMTPTraceListener Write method - I call the Flush method. This forces the e-mail output to happen right then, and makes the component more stable.
            // With the Flush taken out of the Write method, I was experiencing some inconsistent behavior - i.e. exceptions thrown sometimes but not always...
            // goofy problem perhaps someone knows why?
            this.Flush();
        }
        #endregion

        #region SendTrace
        /// <summary>
        /// Hlavní interní implementace sestavení mailu.
        /// </summary>
        /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param>
        /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param>
        /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param>
        /// <param name="id">A numeric identifier for the event.</param>
        /// <param name="data">An array of objects to emit as data. Pokud je string, obsahuje přímo text zprávy.</param>
        private void SendTrace(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data)
        {
            StringBuilder message = new StringBuilder();
            foreach (object item in data)
            {
                if (item != null)
                {
                    message.AppendLine(item.ToString());
                }
            }
            message.AppendLine();

            message.Append("CommandLine: ");
            message.AppendLine(Environment.CommandLine);

            message.Append("CurrentDirectory: ");
            message.AppendLine(Environment.CurrentDirectory);

            message.Append("MachineName: ");
            message.AppendLine(Environment.MachineName);

            message.Append("UserDomainName: ");
            message.AppendLine(Environment.UserDomainName);

            message.Append(".NET Framework: ");
            message.AppendLine(Environment.Version.ToString());

            if (eventCache != null)
            {
                message.AppendLine();
                message.AppendLine("Call stack:");
                message.AppendLine(eventCache.Callstack);
                message.AppendLine();

                message.AppendLine("Logical operation stack:");
                foreach (object item in eventCache.LogicalOperationStack)
                {
                    if (item != null)
                    {
                        message.AppendLine(item.ToString());
                    }
                }
                message.AppendLine();

                message.Append("DateTime: ");
                message.AppendLine(eventCache.DateTime.ToString());

                message.Append("Timestamp: ");
                message.AppendLine(eventCache.Timestamp.ToString());

                message.Append("ProcessId: ");
                message.AppendLine(eventCache.ProcessId.ToString());

                message.Append("ThreadId: ");
                message.AppendLine(eventCache.ThreadId);
            }

            if (!String.IsNullOrEmpty(source))
            {
                message.Append("Source: ");
                message.AppendLine(source);
            }

            message.Append("EventType: ");
            message.AppendLine(eventType.ToString("g"));

            message.Append("EventId: ");
            message.AppendLine(id.ToString("g"));

            SendMessage(message.ToString());
        }
        #endregion

        #region TraceData (override)
        /// <summary>
        /// Writes trace information, an array of data objects and event information to the listener specific output.
        /// </summary>
        /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param>
        /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param>
        /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param>
        /// <param name="id">A numeric identifier for the event.</param>
        /// <param name="data">An array of objects to emit as data.</param>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/>
        /// </PermissionSet>
        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data)
        {
            if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, null, null, null, data))
            {
                SendTrace(eventCache, source, eventType, id, data);
            }
        }

        /// <summary>
        /// Writes trace information, an array of data objects and event information to the listener specific output.
        /// </summary>
        /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param>
        /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param>
        /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param>
        /// <param name="id">A numeric identifier for the event.</param>
        /// <param name="data">An array of objects to emit as data.</param>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/>
        /// </PermissionSet>
        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
        {
            if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, null, null, data, null))
            {
                SendTrace(eventCache, source, eventType, id, data);
            }
        }
        #endregion

        #region TraceEvent (override)
        /// <summary>
        /// Writes trace information, a message, and event information to the listener specific output.
        /// </summary>
        /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param>
        /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param>
        /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param>
        /// <param name="id">A numeric identifier for the event.</param>
        /// <param name="message">A message to write.</param>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/>
        /// </PermissionSet>
        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
        {
            if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null))
            {
                SendTrace(eventCache, source, eventType, id, message);
            }
        }

        /// <summary>
        /// Writes trace information, a formatted array of objects and event information to the listener specific output.
        /// </summary>
        /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param>
        /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param>
        /// <param name="eventType">One of the <see cref="T:System.Diagnostics.TraceEventType"/> values specifying the type of event that has caused the trace.</param>
        /// <param name="id">A numeric identifier for the event.</param>
        /// <param name="format">A format string that contains zero or more format items, which correspond to objects in the <paramref name="args"/> array.</param>
        /// <param name="args">An object array containing zero or more objects to format.</param>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/>
        /// </PermissionSet>
        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
        {
            if ((Filter == null) || Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null))
            {
                SendTrace(eventCache, source, eventType, id, String.Format(CultureInfo.InvariantCulture, format, args));
            }
        }
        #endregion

        #region TraceTransfer (override)
        /// <summary>
        /// Writes trace information, a message, a related activity identity and event information to the listener specific output.
        /// </summary>
        /// <param name="eventCache">A <see cref="T:System.Diagnostics.TraceEventCache"/> object that contains the current process ID, thread ID, and stack trace information.</param>
        /// <param name="source">A name used to identify the output, typically the name of the application that generated the trace event.</param>
        /// <param name="id">A numeric identifier for the event.</param>
        /// <param name="message">A message to write.</param>
        /// <param name="relatedActivityId">A <see cref="T:System.Guid"/> object identifying a related activity.</param>
        public override void TraceTransfer(TraceEventCache eventCache, string source, int id, string message, Guid relatedActivityId)
        {
            SendTrace(eventCache, source, TraceEventType.Transfer, id, String.Format("{0} : {1}", message, relatedActivityId));
        }
        #endregion

        #region Write, WriteLine (override)
        /// <summary>
        /// When overridden in a derived class, writes the specified message to the listener you create in the derived class.
        /// </summary>
        /// <param name="message">A message to write.</param>
        public override void Write(string message)
        {
            TraceEvent(null, "Write", TraceEventType.Information, 0, message);
        }

        /// <summary>
        /// When overridden in a derived class, writes a message to the listener you create in the derived class, followed by a line terminator.
        /// </summary>
        /// <param name="message">A message to write.</param>
        public override void WriteLine(string message)
        {
            this.Write(message);
        }
        #endregion
    }

WebResource.axd: This is an invalid webresource request. Špatné datum na serveru.

Symptom

Všechny requesty na WebResource.axd (včetně základních na .NET skripty) končí chybou 404 – „This is an invalid webresource request.“, resp. „Toto je neplatný požadavek webového prostředku.“

Cause

Problém byl v tom, že na serveru byl nastaven čas o několik měsíců nazpět.

Jestli tomu vadí, že aplikace byla kompilována později, než je datum/čas na serveru, nebo co konkrétně je problém, to jsem nehledal. Každopádně navrácení korektního data okamžitě problém vyřešilo.

Bezpečnostní chyba v ASP.NET a workaround s customErrors k jejímu vyřešení

V ASP.NET byla objevena (a zveřejněna!) bezpečnostní chyba, která sofistikovanějšímu útočníkovi umožní získat obsah zdrojových souborů uložených na webovém serveru, včetně web.configu. Podrobnosti popisuje ve svém blog-postu Scott Guthrie. Workaround spočívá v zapnutí customErrors bez rozlišování typu chyby.

Kdo by to chtěl vidět na vlastní oči, tak na YouTube je pěkné video.