|
|
Microsoft .NET Framework, Base Class Library
-
Zajímavá novinka v C# 5 poslouží například k logování. Následující příklad funguje ve Visual Studio 11 Beta:
class Program { static void Main(string[] args) { Log("Message"); }
static void Log(string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { Console.WriteLine(message); Console.WriteLine(callerMemberName); Console.WriteLine(callerFilePath); Console.WriteLine(callerLineNumber); } }Výstupem je např:
Message Main d:\Development\CallerAttributes\CallerAttributes\Program.cs 14
|
-
-
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 }
|
-
Přestože jsem se nakonec nemohl zúčastnit společného promítání PDC v českém Microsoftu, sleduji to alespoň z domova a dělám si zápisky bez dalšího zpracování.
...přednáší Hejlsberg.
...live vysílání bohužel těžce nestíhá, i když běží na Azure ;-)
...PowerPoint z prezentace je přiložen.
...mimochodem všichni účastníci PDC dostanou LG Windows Phone 7 telefon (tento měsíc jsem si koupil iPhone 4, svůj první iPhone, tak jim naštěstí nezávidím)
Asynchronous Programming Improvements
- co největší přiblížení podoby kódu synchronní podobě
- Task<T> jako návratový typ asynchronních metod (obdoba starého AsyncResult), reprezentuje probíhající operaci, vše, co s ní souvisí (stav, výsledek, výjimky, atp.)
- klíčové slovo "async" v signatuře metody, např. async Task<string> DoSomethingAsync(int inputValue)
- klíčové slovo "await" uvnitř async metody volá async operaci
- volání jinak stejně, jako by šlo o synchronní kód, včetně možného zachytávání výjimky obalením try-catch blokem
- volitelný cancelation-token argument umožňuje zrušit probíhající async operace, snadno lze implementovat timeouty
- Composable callback model
- var task2 = task1.ContinueWith(t => ... t.Result ...);
- "await" vlastně není nic jiného než syntactic sugar pro extension metodu ContinueXy(...), která znamená pokračování po dokončení asynchronní operace
- Combinators - WhenAll, WhenAny, Delay, ...
- ukázky kódu jsou v přiloženém PPTX, namátkou:
async Task ProcessFeedAsync(string url) { var text = await DownloadFeedAsync(url); var doc = ParseFeedIntoDoc(text); await SaveDocAsync(doc); ProcessLog.WriteEntry(url); }
async void DoWorkAsync() { var t1 = ProcessFeedAsync("www.acme.com/rss"); var t2 = ProcessFeedAsync("www.xyznews.com/rss"); await Task.WhenAll(t1, t2); DisplayMessage("Done"); }
nebo
async Task<XElement> GetRssAsync(string url) { var client = new WebClient(); var task = client.DownloadStringTaskAsync(url); var text = await task; var xml = XElement.Parse(text); return xml; }
Compiler as a Service
Na konci už si hrál jenom chvilku s CaS, jak je známý už v NET4 a předváděl konvertor ze C# do VB (Copy / Paste as Visual Basic).
|
-
Trochu nedobrovolně jsem byl dnes donucen šťourat se podrobněji v ClickOnce útrobách, přestože to není moje oblast zájmu.
Symptomy problému byly zajímavé - aplikace instalovaná pomocí ClickOnce, která v sobě měla volbu "Automaticky spouštět při startu Windows", se chovala divně. Zprvu se zdálo, že nastavení provedená v aplikaci (.NET Settings mechanizmus), se restartem počítače ztratí. Nakonec jsem diagnostikoval, že ve skutečnosti aplikace používá dvoje nastavení - jedna nastavení používaná po instalaci a při spuštění aplikace automaticky vytvořenou položkou z menu Start, druhá nastavení používaná při automatickém spuštění po startu Windows. Zároveň jsem v C:\Users\... našel v profilu uživatele dva různé user.config soubory, které se používaly pro tyto dva scénáře spuštění aplikace.
Chyba byla ve skutečnosti v nekoretním spouštění aplikace po startu Windows. ClickOnce totiž sám automatické spouštění po startu Windows nepodporuje a při vlastní implementaci nebyl na rozdíl od zástupce v menu Start totiž použit link typu .asppref-ms (link soubor pro spouštění ClickOnce apliace, který zajišťuje i aktualizace a další ClickOnce věci), ale nekorektně aplikace pro spuštění po startu Windows používala přímo odkaz na svůj .exe soubor. To způsobilo nejen použití jiného user.config souboru, ale i nefunkčnost automatických aktualizací a dalších vlastností ClickOnce.
Poučení tedy zní: Pokud chcete korektně spouštět ClickOnce aplikaci, je potřeba volat vytvořený soubor .appref-ms (vytvořený ClickOnce instalátorem do menu Start) a nikoliv se odkazovat na .exe soubor aplikace.
Chybný kód tak může vypadat:
Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true) .SetValue("InformTrayReader", Application.ExecutablePath);Zatímco "správně" (při dalších omezeních) by to mohlo vypadat nějak takto:
Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true) .SetValue("InformTrayReader", GetApplicationStartupLink());
private object GetApplicationStartupLink() { const string productName = "název dle ClickOnce Publish nastavení"; const string publisherName = "nastavení dle ClickOnce Publish nastavení";
string allProgramsPath = Environment.GetFolderPath(Environment.SpecialFolder.Programs); string shortcutPath = Path.Combine(allProgramsPath, publisherName);
return Path.Combine(shortcutPath, productName) + ".appref-ms"; }
Viz též související info:
UPDATE pro Windows Vista
Pokud nedejbože potřebujete, aby to fungovalo pro nějakého nebožáka používajícího alfa-verzi od Windows7 zvanou Windows Vista (dodnes nechápu, jak mohl Microsoft něco takového vydat), potom je nutný ještě jeden workaround.
Visty totiž nepustí .appref-ms přímo, a to ani z registrů, ani kdybyste odkaz dopravili do Start/Startup. Funguje v nich ale spouštění .exe a kupodivu lze .appref-ms zavolat z našeho exe a spustí se. Stačí tedy našemu programu přidat přepínač příkazové řádky -LaunchClickOnce a při jeho zjištění v Main() místo aplikace samotné odstartovat jen .appref-ms a sám se ukončit.
Podrobnější popis s ukázkou konkrétního kódu najdete na stránce http://www.brokenwire.net/bw/Programming/116/run-clickonce-app-on-startup.
|
-
Pokud vytváříte ve webové aplikaci X509Certificate2
myCertificate = new X509Certificate2(rawData, password); //nebo myCertificate = new X509Certificate2(fileName, password);pak můžete být po nasazení své funkční aplikace na produkční server (např. hosting) obdařeni výjimkou
System.Security.Cryptography.CryptographicException: The system cannot find the file specified. at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr) at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx) at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags) at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password)
...přestože soubor s certifikátem buď zaručeně existuje (fileName), nebo se ani nepoužívá (rawData).
Problém souvisí s tím, že v produkčním prostředí Vaše aplikace beží pod uživatelským účtem, který nemá založen profil, který se má certifikát vytvořit (volně přeloženo, omlouvám se za případnou nepřesnost).
Řešením je použití přetížení constructoru, kterým zvolíte cílové uložiště certifikátu:
myCertificate = new X509Certificate2(rawData, password, X509KeyStorageFlags.MachineKeySet); //nebo myCertificate = new X509Certificate2(fileName, password, X509KeyStorageFlags.MachineKeySet);
|
-
Při poslední z implementací GP WebPay (PayMuzo) se nám stalo, že nám generátor klíčů obchodníka zahlásil chybu
exception encrypting data - java.security.InvalidKeyException: Illegal key size
Po chvilce testování (na dvou různých PC) jsme zjistili, že to dělá při Heslu pro keystore delším 8 a více znaků. Pokud jsme heslo zkrátili na 7 a méně znaků, vygenerování klíčů proběhlo úspěšně.
Verzi utility lze popsat jako "(c) 2004/2005 Muzo a.s." v UI a velikost souboru GenCert.jar 33567 bytů, víc mě nad tím bádat nebaví.
Pokud byste někdo bojoval s implementací GP WebPay pro .NET, tak se mi klidně ozvěte, máme to hotový a určitě se nějak dohodneme... ;-)
|
-
Zrovna jsem přišel z přednášky o "Code Contracts" a jsem relativně nadšen tím, co Microsoft chystá. Naštěstí již je to k veřejnému testování přes DevLabs, obecné věci nejsou pod NDA, a proto se s Vámi mohu bez detailů podělit o první dojmy.
Code Contracts je aparát, kterým bude možné v kódu (nezávisle na jazyce) definovat contract, tedy pravidla, které daná metoda/property/třída/... musí splňovat, například: public class MyClass { public int DoSomething(int vstup, int vstup2) { Contract.Requires(vstup != 0); Contract.Requires(vstup2 > vstup1, "argument vstup2 musí být větší než vstup1"); Contract.Ensures(Contract.Result<int>() > 0, "návratová hodnota bude větší než 0");
...implementace metody } }
Co by to mělo umět:
- pre-conditions - Contract.Requires() - podmínky, které musí být splněny pro spuštení metody - na začátku
- post-conditions - Contract.Ensures() - podmínky, které budou splněny na konci metody
- object invariants - [ContractInvariant] - podmínky, které musí být splněny stále (na konci každé operace)
- asserts - Contract.Assert() - podmínky, které je možné vyžadovat průběžně v kódu (ála Debug.Assert())
- ...
Co by to mělo znamenat:
- run-time checking - v podstatě náhrada za if (..) then throw new ArgumentXyException(...)
- static verification - kontrola (třeba jakou součást buildu), že budou podmínky splněny
- generování dokumentace - pravidla contract se stanou součástí dokumentace (např. místo současných ArgumentExceptions)
- náhrada Debug.Assert() a podobných
Jak to funguje:
- třída Contract v podstatě nemá žádnou implementaci a funguje jako metadata, která se díky ní dostanou do kódu
- post-build stepem v msbuildu se tato metadata vyhodnotí a provedou se modifikace zkompilovaných assembly dle požadovaných nastavení (zda dělat testy i v Release, zda vygenerovat assembly s contracty, atp.); procesně to lze přirovnat k Obfuskaci, nebo mechanizmům používaným v AOP (Aspekt Oriented Programmingu)
Odkazy
|
-
Problém, který poprvé zaskočí snad každého .NET programátora. Ani já nebyl kdysi výjimkou a tuto otázku dostávám stále dokola.
Symptom je jednoduchý - při spouštění .NET aplikace z lokálního počítače vše krásně funguje, při spouštění přes síť (např. po deploymentu do sdílené složky) aplikace padá na SecurityException.
Jádrem celého problému je .NETí mechanizmus Code Access Security Policy, který má mj. chránit hostující počítač před neoprávněným spouštěním kódu, popř. před závadným kódem. Stručně řečeno tento mechanizmus používá pro řízení práv k provádění jednotlivých operací sadu faktů zvanou Evidence (důkazy), přičemž jeden z podstatných faktů je zóna, z které je kód spouštěn (jde o klasické zóny Můj počítač, Lokální intranet, Internet, Důveryhodné servery, ...). No a výchozí nastavení na hostujícím počítači po instalací .NET frameworku říká, že kód spouštěný ze zón Intranet/Internet má určitá omezení, např. nesmí přistupovat k souborovému systému, či nesmí pracovat se sítí (SQL serverem), atp.
V podstatě jsou dvě cesty k řešení:
- Rozšířit sadu Evidence aplikace tak, aby ve výchozím nastavení .NET frameworku měla aplikace práva lepší. Základem je např. podepsat aplikaci (strong-name), atp.
- Změnit výchozí nastavení zabezpečení .NET frameworku na hostujícím počítači tak, aby i aplikace z méně bezpečných zón směly provádět zabezpečené operace. Což provedeme:
- Spustíme MMC konzoli pro konfiguraci .NET Frameworku (z Nástrojů pro správu, popř. z Run mmc.exe a pak Přidat modul snap-in...)
- Donavigujeme se ve stromu na My Computer ~ Runtime Security Policty
- dáme Adjust Zone Security
- Make changes to this computer / to the current user only (dle potřeby)
- Vybereme zónu a šoupátkem zvýšíme její "trust". Full Trust znamená "bez omezení".
Pozor, neměňte bezhlavě tato nastavení. Týkají se totiž i kódu spouštěného z internetu, např. i přímo z webových stránek. Můžete tak nechtěně otevřít bránu do svého počítače i pro nežádoucí kód.
Správná cesta k řešení vede přes bod 1), nicméně připouštím, že pro začínajícího programátora a jednoduché síťové utilitky je to cesta složitější.
|
-
Na Microsoft Connect jsou k dispozici "všechny" hotfixy pro .NET Frameworky a Visual Studia ke stažení hezky z jednoho místa. Jedná se o nový " Visual Studio and .NET Framework Hotfix Public Availability Program".
|
-
Pokud se sháníte po jednoduchém mechanizmu, jak ve Vašich aplikacích logovat výjimky, pak zřejmě v .NET pro consolové/WinForm aplikace marně hledáte něco, jako je healthMonitoring pro ASP.NET, kde je připraven jednoduchý, ale mocný nástroj a lze pomocí pouhých několika řádků ve web.config souboru získat přehled o výjimkách ve Vaší aplikaci, např.:
<configuration> <system.web> <healthMonitoring enabled="true"> <rules> <add name="Mail Notifications on All Errors" eventName="All Errors" provider="SimpleMailWebEventProvider" profile="Default"/> </rules> <providers> <add name="SimpleMailWebEventProvider" type="System.Web.Management.SimpleMailWebEventProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" from="errors@havit.cz" to="errors@example.cz" subjectPrefix="MyApplication: " buffer="true" bufferMode="Notification" maxEventLength="4096" maxMessagesPerNotification="2"/> </providers> </healthMonitoring> </system.web> </configuration>...výše uvedená konfigurace Vám zajistí zasílání mailových notifikacích o neobsloužených výjimkách a chyách Vaší aplikace, jednoduché, prosté.
Jak na to u consolových a WinForm aplikací?
Pro consolové či WinForm aplikace nic jako healthMonitoring připraveno není, nicméně s pár řádky kódu lze dosáhnout obdobného efektu a dokonce u toho využít široké možnosti pro tracing v .NET 2.0. Základní myšlenka je prostá:
- .NET 2.0+ v sobě obsahuje široké možnosti pro tracing, kdy můžeme definovat různé zdroje zpráv (TraceSource), zprávy různé rozdělovat (Switch), filtrovat (TraceFilter) a směrovat na různé listenery (výstup do konzole, XML, textového souboru, event-logu, SQL, SMTP, atd. další si můžeme napsat) - to vše bez nutnosti jakýchkoliv doplňků - ať už Microsoftích Enterprise Library / Application Blocků, či produktů třetích stran typu Log4Net. Základní přehled o možnostech si můžeme udělat například z článku A Tracing Primer - Part I [Mike Rousos].
- .NET sám nemá mechanizmus, jak výjimky zapisovat do trace
- napsat pár řádků kódu, které zajistí zápis výjimek do trace je snadné!!!
- do trace můžeme buď výjimky posílat explicitně (níže volání ExceptionTracer.TraceException(myException)),
- nebo se můžeme přihlásit k odběru událostí, které neošetřené výjimky způsobují,
- obecně jde o událost AppDomain.CurrentDomain.UnhandledException
- ve WinForm aplikacích pak událost Application.ThreadException
Interface pro nasazení? ...prostý
V nejjednodušší variantě si můžeme udělat například třídu ExceptionTracer, kterou přihlásíme k odběru příslušných událostí a v obsluze událostí už budeme jen výjimku posílat do trace.
Pro konzolové aplikace:
class Program { static void Main(string[] args) { ExceptionTracer.SubscribeToUnhandledExceptions();
throw new InvalidOperationException("Chybka!");
// ExceptionTracer.Default.TraceException(new ArgumentNullException("param", "Sakrapísek!")); } }Pro WinFormAplikace:
static class Program { [STAThread] static void Main() { ExceptionTracer.SubscribeToWindowsFormsThreadExceptions();
Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } }
App.config pak může vypadat nějak takto:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <source name="Exceptions" switchValue="Error"> <listeners> <add name="LogFileListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="Exceptions.log" /> <add name="XmlListener" initializeData="Exceptions.xml" type="System.Diagnostics.XmlWriterTraceListener" /> </listeners> </source> </sources> </system.diagnostics> </configuration>
ExceptionTracer [Simplified Version]
Jednoduchá, ale plně funkční verze ExceptionTracekru může vypadat třeba takto:
using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Windows.Forms;
namespace Havit.Diagnostics { public class ExceptionTracer { /// <summary> /// Přihlásí ExceptionTracer k odběru všech neobsloužených výjimek (event AppDomain.CurrentDomain.UnhandledException). /// </summary> public static void SubscribeToUnhandledExceptions() { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { if (e.ExceptionObject is Exception) { TraceException((Exception)e.ExceptionObject); } }
/// <summary> /// Přihlásí ExceptionTracer k odběru všech neobsloužených výjimek WinForm (event Application.ThreadException). /// </summary> public static void SubscribeToWindowsFormsThreadExceptions() { Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException); } private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { TraceException(e.Exception);
// původní implementace obsluhy výjimky, WinForm dialog s chybou using (ThreadExceptionDialog excptDlg = new ThreadExceptionDialog(e.Exception)) { DialogResult result = excptDlg.ShowDialog(); if (result == DialogResult.Abort) { Application.Exit(); } } }
/// <summary> /// Pošle do trace zadanou výjimku. /// </summary> /// <param name="exception">výjimka k zaznamenání</param> public static void TraceException(Exception exception) { TraceSource ts = new TraceSource("Exceptions");
ts.TraceEvent(TraceEventType.Critical, 0, exception.ToString());
ts.Flush(); ts.Close(); } } }
Příklad výstupu [XML]
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent"> <System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system"> <EventID>0</EventID> <Type>3</Type> <SubType Name="Critical">0</SubType> <Level>1</Level> <TimeCreated SystemTime="2008-01-31T21:08:35.0625000Z" /> <Source Name="Exceptions" /> <Correlation ActivityID="{00000000-0000-0000-0000-000000000000}" /> <Execution ProcessName="WindowsFormsApplication1" ProcessID="1232" ThreadID="1" /> <Channel /> <Computer>OSKAR</Computer> </System> <ApplicationData>System.ApplicationException: Test !!! at WindowsFormsApplication1.Form1.button1_Click(Object sender, EventArgs e) in D:\Development\ExceptionLogging\WindowsFormsApplication1\Form1.cs:line 20 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)</ApplicationData> </E2ETraceEvent>
|
-
|
Pokud vyvíjíte aplikaci ve Visual Studiu 2008 a aplikace je určena pro runtime .NET Framework 2.0, nic vám nebrání použít nové charakteristiky jazyka C# 3.0. Úžasné je, že pro to nemusíte udělat vůbec nic.
Není možné pochopitelně použít ty rysy jazyka, které vyžadují knihovny instalované .NET Frameworkem 3.5. Můžete využít například eleganci lambda výrazů nebo zkráceného zápisu vlastností, zapomeňte ale na LINQ.
|
-
Jen krátký kousek kódu, který možná někdy ušetří pár minut nad dokumentací:
using System; using System.Collections.Generic; using System.Text; using System.DirectoryServices;
namespace ActiveDirectorySynchronizer { class Program { // doména test.branik.havit.cz, Organizational Unit: My Organizational Unit, pod tím OU: SubUnit public static string rootPath = "LDAP://OU=SubUnit,OU=My Organizational Unit,DC=test,DC=branik,DC=havit,DC=cz"; public static string usernameOptional = @"test.branik.havit.cz\Administrator"; public static string passwordOptional = "P@ssw0rd";
static void Main(string[] args) { // node, od kterého se mají uživatelé hledat DirectoryEntry searchRoot = new DirectoryEntry(rootPath); if (!String.IsNullOrEmpty(usernameOptional)) { searchRoot.Username = usernameOptional; searchRoot.Password = passwordOptional; }
// vyhledání všech uživatelů DirectorySearcher ds = new DirectorySearcher(searchRoot, "(objectClass=user)"); ds.PropertiesToLoad.Add("displayName"); ds.PropertiesToLoad.Add("sAMAccountName"); ds.PropertiesToLoad.Add("objectGUID");
foreach (SearchResult sr in ds.FindAll()) { Guid userGuid = new Guid((byte[])sr.Properties["objectGUID"][0]); string userDisplayName = String.Empty;
Console.Write(userGuid);
if (sr.Properties["displayName"].Count > 0) { userDisplayName = sr.Properties["displayName"][0].ToString(); Console.Write(","); Console.Write(userDisplayName); }
Console.WriteLine(); }
Console.WriteLine("DONE"); } } }
|
-
Jaký je rozdíl v prapagaci výjimky z bloku catch:
try { ... } catch (Exception e) { throw; }
versus
try { ... } catch (Exception e) { throw e; }
Odpověď je tentokrát jednoduchá a jako obvykle ji najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):
>>> Při vyhození výjimkt přes "throw;" se nezmění stack trace - výjimka se stále tváří, že vzešla z původní metody. Při vyhození přes "throw e;" je změněn stack trace, výjimka se tváří, že vzešla z naší metody obsluhující výjimku. <<<
|
-
Jaký je rozdíl v zachytávání výjimek při použití typu výjimky Exception try { ... } catch (Exception e) { ... }a bez použití tohoto typu, tedy try { ... } catch { ... }
Zdůrazňuji, že tento rozdíl existuje jen v .NET Frameworku 1.x, ve verzi 2.0 jsou způsoby funkčně rovnocenné.
Odpověď najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):
>>> Konstrukce catch (Exception e) zachytává jen CLS-compliant výjimky, catch zachytává všechny chyby. Praktický rozdíl je při zachytávání chyb z COM objektů, jejichž chyby nejsou CLS-compliant výjimkami. .NET Framework 2.0 tyto chyby z COM objektů zabalí do RuntimeWrappedException, které jsou CLS-compliant, takže je chyba zachycena i při použití catch (Exception e). <<<
...a jako posledně: teď se přiznejte, kdo jste to znal!
|
|
|
|