Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

Software Architect, Founder at HAVIT, Microsoft MVP - ASP.NET/IIS

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
    }

Tipy a triky T-SQL z praxe – Slides a dema [TechEd Praha 2010]

Slides a dema z přednášky na konferenci TechEd Praha 2010:

Záznam z přednášky nebyl pořizován.

Povinné a volitelné parametry v C# 4.0 – Dema [MS Days 2010]

Dema z mého příspěvku na konferenci MS Days 2010 – Demománii:

Ze vstupu nebyl pořizován záznam.

Optimalizace výkonu webových aplikací – Slides, dema a záznam [MS Fest 2010]

Slides a dema z přednášky na konferenci MS Fest 2010 (tentokrát se zaměřením na client-side a přenos):

Z přednášky byl pořizován obrazový záznam (HD 720p), který najdete na našem YouTube Channelu:

Code Contracts – Slides a dema [TechEd Praha 2010]

Slides a dema z přednášky na konferenci TechEd Praha 2010:

Záznam z přednášky nebyl pořizován.

Ladění výkonu webových aplikací – Slides a dema [TechEd Praha 2010]

Slides a dema z přednášky na konferenci TechEd Praha 2010:

Záznam z přednášky nebyl pořizován, rozšířená podoba byla však prezentována pro WUG Praha a k té záznam existuje.

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.

.NET 4 MCP Certification Exams 70-519 (MCPD: Web) & 70-516 (TS: Accessing Data) – První dojmy z beta exams

Měl jsem příležitost zúčastnit se beta programu nových certifikačních zkoušek Microsoftu pro platformu .NET 4, nedá mi to tedy, abych se s Vámi nepodělil o první dojmy (výstup je naštěstí již značně cenzurován, za ty dva dny už jsem trochu vychladl a nepublikovatelný proud mého hodnocení dopadl jen na prvních pár lidí, které jsem potkal).

Exam 70-516: TS: Accessing Data with Microsoft .NET Framework 4

První zkouška, kterou jsem testoval, byla zkouška na datové přístupy z kategorie Technical Specialist. Dle očekávání a zaměření se jednalo o „kodérskou“ zkoušku, kde nešlo o nějaké koncepční myšlení, ale otázky směřovaly většinou na konkrétní code-snippety, nebo jiné coding techniky pro přístup k datům. V podstatě se vše točilo kolem následujících témat:

  • Entity Framework – odhadem 40%
  • LINQ, LINQ to SQL – odhadem 20%
  • pure DB Access (DbConnection, DbCommand, …) – odhadem 10%
  • „old fashion“ (DataSet, DataTable, TableAdapter, …) – odhadem 10%
  • ostatní všechno možné (XML, ADO.NET Data Services, Sync Services, konfigurace, …) – odhadem 20%

V zásadě se jedná o standardní zkoušku tohoto „kodérského“ typu, kde se opět objevilo pro mě nepochopitelné množství  otázek směřujících čistě na názvy/signatury jednotlivých metod. Něco, co v reálné praxi zcela řeší IntelliSense. Nesnáším otázky typu „Abyste udělali XY, použijete metodu: ExecuteQuery(), ExecuteQuery(true), ExecuteQuery(false) nebo ExecuteQuery<Order>()?“.
Naopak tam úplně chyběla sebemenší otázka na SqlTransactions, T-SQL, SQL-injection, apod., kteréžto znalosti bych od člověka certifikovaného na Data Access očekával (narozdíl o detailů o předávání Table Parameters nebo Spatial Data z kódu a podobných nuancí, které si každý snadno najde, když je potřebuje).
Celkové zkoušku hodnotím jako standardního následovníka dosavadních MCP kodérských zkoušek, kde Microsoft/Prometric nedokázali překonat svůj stín a místo, aby se soustředili na skutečné ověření dovedností vývojáře potřebných v každodenní praxi, tak šlo opět o koncentraci na novinky a okrajová témata, které jsou zrovna „in“. Člověk, který takovou zkoušku úspěšně absolvuje, není vůbec ověřen na základní koncepty přístupu k datům, spíše se dokázal připravit na požadavky zkoušky způsobem, který mu umožnil ji absolvovat (ať už pomocí zkušebních testů, uniklých otázek, nebo tvrdým biflováním faktů), nicméně stejně nelze úspěšně předpokládat, že by si běžný vývojář detaily, které jsou předmětem zkoušky zapamatoval déle než potřebný půlden.

Exam 70-519: Pro: Designing and Developing Web Applications Using Microsoft .NET Framework 4

Druhá zkouška, kterou jsem testoval, byla z kategorie Professional Developer pro Web Development. Zkouška by se měla zaměřovat na koncepty a znalost technologie, prakticky bez kódování. V hodnocení této zkoušky jsem bohužel mnohem radikálnější, točila se totiž celá kolem následujících témat:

  • MVC – odhadem 50%
  • ASP.NET Core Infrastructure – odhadem 25%
  • WebForms – odhadem 10%
  • Ostatní (Data Access, WCF, Silverlight, jQuery, …) – odhadem 15%

Totální zahlcení problematikou MVC mě velmi nemile překvapilo. Nikdy jsem neměl nic proti lidem, kteří se rozhodli věnovat této technologické masturbaci (dovoluji si vypůjčit trefné označení kolegy Michal Altaira Valáška), ale kdo v Microsoftu/Prometric došel k závěru, že pro testování dovedností samostatného webového vývojáře (dovedností relevantních pro běžnou praxi!), je nutné tak silně zdůraznit nové MVC a prakticky zcela opustit WebForms přístup, to mě hlava nebere.
Toto mě znechutilo natolik (a tento feedback, že by se měly oddělit zkoušky na MVC a Webforms, jsem dával do Microsoftu už dávno v dobách, kdy ještě bylo co měnit), že odsuzuji tuto zkoušku jako absolutně irelevantní vzhledem k hodnocení reálného webového vývojáře. Pokud bych měl posuzovat jeho dispozice a dovednosti pro reálnou praxi vývoje webových aplikací, tato zkouška by mi toho mnoho neřekla. Bohužel i těch několik otázek, které by tématicky patřily do podobné zkoušky, bylo položených tak, že často vůbec nesměřovaly k jádru věci, ale jednalo se např. o vyloučení zcestných odpovědí, aby zůstaly ty méně špatné. Když se Vás někdo zeptá, které dva druhy ovoce byste doporučili někomu, kdo má rád sladkosti, tak Vám z nabídky „okurky“, „igelit“, „brambory“ a „mramor“, nezbývá než zaškrtnout… (ne, bohužel tlačítko WTF testovací aplikace nenabízí a co považují autoři za správnou odpověď se můžeme jenom dohadovat).

IIS: Jaký w3wp.exe patří k jakému AppPoolu?

Na IIS6 existoval ve složce System32\ jednoduchý skript k vypsání seznamu worker-procesů a jejich příslušnosti k jednotlivým application poolům IIS:

iisapp.vbs

Na IIS7 už tento skript není, ekvivaltentní příkaz z System32\inetsrv\ však je

appcmd list wp

image

Code Contracts Editor Extensions – konečně contracty v Intellisense tooltipech

Samotné Code Contracts se díky pomalému rewritingu (a ještě pomalejší statické analýze, ale u té je to logické) v praxi stávaly problémem, kdy nejeden vývojář pochyboval, jestli je ochoten obětovat své pohodlí rychlosti buildů na oltář Code Contractů.

Microsoft konečně přichází s dalším logickým krokem, na který všichni tak dlouho čekají, a který by mohl pomyslné váhy opět lehce převážit ve prospěch Code Contracts – po instalaci Code Contracts Editor Extensions (doplňku do Visual Studia 2010) se Vám contracty budou zobrazovat v tooltipových nápovědách IntelliSense.