mailto: Příprava mailů pro poštovního klienta včetně cc, bcc, subject a body

I při vývoji webových aplikací se občas setkáme s výstředním požadavkem, kdy je potřeba, aby aplikace „generovala maily pro Outlook“ (či jakéhokoliv poštovního klienta). Obchodníci prostě aplikacím nevěří, chtějí si mail před odesláním upravit, připojit vlastní podpis a mít ho ve vlastní Odeslané poště.

Obvykle chtějí „kliknu tady na to tlačítko a objeví se mi nový předvyplněný mail v Outlooku“… :-))
(Nebudu tu teď hodnotit, do jaké míry je to vhodné, spolehlivé, …)

Jednou z možných cest, jak výsledku dosáhnout, je použít HTML element

<a href="mailto:jmeno@prijmeni.cz">Tlačítko</a>

…asi nikoho nepřekvapuje, že takovýto odkaz vytvoří na klientu nový mail pro odeslání na zadanou adresu.

Ne každý však ví, že takovýto odkaz lze odekorovat tak, že mimo adresy příjemce předáme poštovnímu klientovi i Cc, Bcc, Subject, Body, či dokonce libovolnou hodnotu do hlavičky mailu. Není to kupodivu nic proprietarního, nýbrž to vyplývá z RFC 2368. Jednotlivé hlavičky najdete v RFC 822.

Můžeme tak například vytvořit bohatší odkaz:

<a href="mailto:infobot@example.com?subject=current-issue&amp;body=current-issue-body&amp;cc=jmeno@prijmeni.cz">Tlačítko</a>

Ještě jeden drobný zádrhel – jak nakódovat mezery, konce řádek, či dokonce češtinu. V zásadě bychom měli encodovat URL podle RFC 1738, nicméně v praxi to tak jednoduché není, protože např. v ASP.NET si se samotnými HttpUtility.UrlEncode(), ani HttpUtility.UrlPathEncode() nevystačíme:

  • mezerníky musíme převést na %20, nikoliv na + (to sice dělá UrlPathEncode(), ale ten zase neřeší konce řádek) ,
  • konce řádek musíme převést na %0D%0A, z praxe stačí i %0A (UrlEncode() prozměnu kóduje mezery na +),
  • …atp. i další speciální znaky.

Takže nakonec to může odkaz vypadat nějak takto:

<a href="mailto:?Subject=Nab%eddka%20%e8.%20&amp;Body=Pos%edl%e1m%20nab%eddku%0a%0aCENA%20%3d%201%a0114%20(111%2c40%2fks)">Tlačítko</a>

přičemž v ASP.NET lze odkaz emitovat třeba takto:

TlacitkoHL.NavigateUrl = String.Format(
    "mailto:{0}?Body={1}",
    email,
    HttpUtility.UrlEncode("tělo\nzprávy", Encoding.GetEncoding(1250)).Replace("+", "%20")
);

Ještě zajímavý postřeh – problémy s kódováním. Nezbývá než testovat, např. přestože samotná HTML stránka je třeba v kódování utf-8, je potřeba mailto URL odkaz encodovat do win-1250. Zřejmě je to o tom, že browser předá URL v nezměněné podobě poštovnímu klientovi (např. Outlooku) a informace o encodingu tam nikde mezi nimi neproběhne. Moc pěkné to není. Pokud byste k tomu někdo měl přesnější informace, uvítám je v komentáři. Mě to takhle funguje všude, kde to potřebuji, takže nemám teď motivaci ani čas nad tím dál bádat… ;-)

Refresh stránky po postbacku do počátečního stavu

Při ladění webové aplikace se často dostanete do situace, kdy vyzkoušítě nějaké postback funkce stránky, a pak se chcete vrátit v prohlížeči rychle zpět do původního výchozího stavu (IsPostBack = false), abyste vyzkoušeli něco jiného.

S prohlížečem může být s tímto docela boj, protože Back nás většinou hodí buď na předchozí adresu nebo vytáhne stránku z cache a GET neprovede, Refresh naopak znovu posílá POST data a zopakuje tak poslední postback. Dělá to prostě všechno možný, jen ne nový GET potřebné stránky.

Je na to jednoduchý trik, pokud chceme znovu načíst počáteční stav stránky, stačí změnit něco bezvýznamného v adrese stránky (adresním řádku prohlížeče). Oblíbeným indiferentním trikem je změna malého písmena na velké (pro prohlížeč je to jiná adresa, udělá požadovaný GET počátečního stavu, naopak IIS je case-insensitive, takže mu to nevadí).

http://www.northwind.com/my-page.aspX

…a je to.

Manage auditing and security logs (SeSecurityPrivilege) chybí/mizí

Pokud skupině Exchange Enterprise Servers chybí právo „Manage auditing and security logs“ (SeSecurityPrivilege), což lze snadno ověřit utilitou \SUPPORT\EXDEPLOY\policytest.exe z Exchange CD, pak nám nastávají závažné potíže. Obyčejně nám začne failovat spouštění Exchange služeb, především Information Store.

Do logu můžem dostávat:

Event ID 7024, Service Control Manager:
   „The Microsoft Exchange Information Store service terminated with service-specific error 0 (0x0).“

Event ID 1121, MSExchangeIS:
   „Error 0x80004005 connecting to the Microsoft Active Directory.“

Event ID 5000, MSExchangeIS:
   „Unable to initialize the Microsoft Exchange Information Store service. – Error 0x80004005.“

Event ID 2103, MSExchangeDSAccess:
   „Process MAD.EXE (PID=2356). All Global Catalog Servers in use are not responding:“

…a podobné

Můžeme to snadno napravit setup.exe /domainprep, nicméně někdy ani to trvale nepomůže a dost krušné chvíle nám mohou nastat, pokud toto právo po čase mizí (policytest.exe nejdřív OK, později failuje).

Naštěstí už je člověk naučený, že pokud nějaké právo samovolně mizí, jsou prvním podezřelýmzásady zabezpečení (lokální, doménové, …). Je tedy potřeba ohlídat, aby nám zásady zabezpečení právo „Manage auditing and security logs“ pro doménovou skupinu „Exchange Enterprise Servers“ nelikvidovaly (je to v User Rights Assignments).

GUI pro Sandcastle – Microsoftí generátor dokumentace ála nDoc

Microsoftí projekt Sandcastle se začíná pomalu usazovat, nicméně jeho klíčovým nedostatkem zatím byla absence GUI, grafického uživatelského rozhraní. Naštěstí to někteří vzali do svých rukou, a tak už dnes existuje několik GUI pomůcek pro ovládání Sandcastle a pohodlné generování MSDN-style .NET 2.0 dokumentace.

Vše zajímavé kolem Sandcastle se momentálně děje na webu http://www.sandcastledocs.com/.

Visual Studio 2005 SDK je ke stažení z Microsoft Downloads, je v něm HtmlHelp 2.0.

IIS: Vytvoření žádosti o certifikát na website s existujícím certifikátem

Pokud je potřeba vytvořit žádost, která obsahuje stejná data jako aktuální certifikát, není problém – na IIS se proces prokliká.

Ovšem pokud je v žádosti třeba něco modifikovat (např. se změnil vlastník domény nebo jeho název), je potřeba vygenerovat žádost s novými údaji a to IIS neumí – nabízí pouze certifikát odebrat a to nechceme. Řešení spočívá ve vytvoření dočasné website, ke které žádost vytvoříme, návod je uveden na serveru Thawte pod kódem vs32621.

Zde je kopie textu:

To work around this problem without having to ‚remove‘ the existing certificate from your web site, do the following:

1. In IIS right click the ‚Default Web Site‘ and click on ‚New–>Site‘

2. Create a new site. You can give it a temporary name.

3. Right click on this new site and go to ‚Properties–>Directory Security–>Server certificate‘

4. Select ‚Create a new certificate‘ and follow the wizard to create a new CSR, please refer to the following solution: vs27731

5. Backup the Private Key file – very important, if no backup is made and the Private Key is lost, the certificate issued will not work. The Private Key backup instructions can be found in the following solution vs22515

6. When you receive the certificate back, right click on this temporary site and go to ‚Properties–>Directory
Security–>Server certificate‘ and follow the wizard to ‚process the pending request‘

7. Once the certificate has been installed, right click on the site where the certificate should go and click ‚Properties–>Directory Security–>Server certificate‘

8. Select the option, ‚Replace the current certificate‘

9. You will then be able to select the certificate that you have just installed

10. Once installed we strongly advise you to make a backup of your certificate with its corresponding private key. View Solution vs22528

For more information please see Microsoft’s article at the following link: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q295281

System.Transactions – dobrý sluha, ale špatný pán

.NET Framework 2.0 zavádí nový namespace System.Transactions, který umožňuje velmi programátorsky pohodlnou práci s transakcemi, a to jako transakcemi ADO.NET/SQL Serveru, tak i MSMQ (Message Queues) a MSDTC (Distributed Transaction Coordinator).

Můžeme tak například celkem transparentně obalit kus kódu transakcí, aniž bychom museli do kódu zasahovat a transakce explicitně nastavovat.

using (TransactionScope scope = new TransactionScope())
{
   using (SqlConnection connection = new SqlConnection(connectionString))
   {
      SqlCommand command = connection.CreateCommand();
      command.CommandText = "Insert....";
      command.Connection = connection;

      SqlCommand command2 = connection.CreateCommand();
      command2.CommandText = "Update....";
      command2.Connection = connection;

      connection.Open();
      command.ExecuteNonQuery();
      command2.ExecuteNonQuery();
      connection.Close();
   }
   scope.Complete();
}

…vše vypadá krásně a opravdu to může i krásně fungovat, můžeme si ale i pěkně naběhnout.

V první řadě, pokud výše uvedený kód běží vůči SQL2000 serveru, pak se namísto běžné SQL-transakce vytvoří distribuovaná transakce spravovaná MSDTC, Distributed Transaction Coordinatorem – což bude mít velmi nepříjemný dopad na výkon naší aplikace. Při použití s SQL2000 totiž nejsou podporovány tzv. „promotable transactions“.

Pokud používáme SQL2005 server, tento problém odpadá, transakce bude realizována prostřednictvím SqlTransaction.

Dalším problémem však je, že explicitně neurčujeme, co vše je součástí transakce, takže veškeré transakční zdroje (resources), které v rámci transaction-scope používáme, se automaticky zaregistrují jako součást transakce a snadno tak opět skončíme na distribuované transakci spravované MSDTC.

Závěr

Osobně raději pro transakční zpracování SQL používám klasickou SqlTransaction, navíc pokud si vytvoříme malou pomůcku, pak můžeme i SqlTransaction řešit obdobně pohodlným způsobem:

int myID = 5;
object result;

SqlDataAccess.ExecuteTransaction(
   delegate(SqlTransaction transaction)
   {
      // uvnitř lze používat i lokální proměnné (samozřejmě i parametry, statické fieldy atp.)

      SqlCommand cmd1 = new SqlCommand("command string");
      cmd1.Transaction = transaction;
      cmd1.Connection = transaction.Connection;
      cmd1.Parameters.AddWithValue("@MyID", myID);
      cmd1.ExecuteNonQuery();

      SqlCommand cmd2 = new SqlCommand("another command");
      cmd2.Transaction = transaction;
      cmd2.Connection = transaction.Connection;
      result = cmd2.ExecuteScalar();
   });
Související články

GoogleToolbarNotifier.exe – součást Google Toolbar 4

Zaktualizoval se mi Google Toolbar, nu což, dobrá. Do programů spouštěných při startu Windows mi však přibyl GoogleToolbarNotifier.exe. A nejenom, že tam přibyl, i si chtěl hned komunikovat ven kamsi na port 80 (HTTP, možná CLR). O to se Tě Google opravdu nikdo neprosí!!!

Údajně to má být utilitka, která si hlídá, aby v IE nedošlo ke změně výchozího vyhledávače na něco jiného.

Každopádně konvenčně lze spouštění tohoto procesu vypnout v Options Google Toolbaru, na záložce More vypnout „Notify me on settings change“ a „Set and keep Seach settings to Google“. Tím dojde k odstranění spouštěcího klíče z registru (HKCU\Software\Microsoft\Windows\CurrentVersion\Run).

NTBACKUP: Scheduled job failuje – médium není prázdné

Pokud vytvoříme pomocí wizzardu full backup job zálohování na médium – schedulovanej na každý den – a předpokládáme, že budeme prostě jen cyklicky měnit média – tak nám po prvním cyklu zálohy přestanou probíhat, po chvilce pátrání se dostaneme k chybové hlášce „médium není prázdné“.

V základním nastavení ntbackup totiž média nemaže, což lze považovat za rozumné výchozí nastavení, nicméně pro náš případ nevhodné. Pokud bychom to neměli jako scheduled task, aplikace by se nás na smazání zeptala, nicméně takto job sfailuje.

Řešením je přidání parametru /UM do příkazové řádky, kterou příslušný scheduled task provádí. Tím se vynutí smazání média před spuštěním zálohování.

Jak se zbavit namespaces (xmlns) v rootovém elementu XML při serializaci

Při běžné serializaci objektu do XML nám XmlSerializer vytvoří root-element, který má nastavené namespaces, např.

<rootElement xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

To odpovídá XML normě. Může však nastat situace, např. při generování RSS Feedu, kdy namespace definovat nechceme.

Fígl, jak se zbavit namespace definice, spočívá v předhození XmlSerializeru kolekce XmlSerializerNamespaces s jednou „prázdnou“ položkou:

XmlSerializer serializer = new XmlSerializer(typeof (RssFeed));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", null);
StringWriter writer = new StringWriter(CultureInfo.CurrentCulture);
serializer.Serialize(writer, this, ns);

…a je to, výsledné XML bude mít kořenový element jen

<rootElement>
  ...
</rootElement>

SQL2005: Odmazávání starších záloh

Na SQL 2005 Serveru mě velice překvapilo, že pomocí základního maintenance-planu již nelze nastavit automatické odmazávání starších záloh, starších .BAK souborů.

Řešení je naštěstí snadné, i když ho bohužel pomocí wizzardu nedosáhneme. Stačí však ručně modifikovat (pravým tlačítkem – Modify, nebo jen double-click) wizzardem vygenerovaný maintenance-plan a přidat do něj novou úlohu – Maintenance Cleanup Task, v jehož vlastnostech pouze nastavíme v jaké složce máme zálohy, jestli se mají procházet i podsložky a jak staré zálohy se mají zlikvidovat.

Přidání je opravdu snadné, je to snad na tři kliknutí (přetáhnout z toolboxu, nastavit vlastnosti a navázat do workflow protažením příslušné šipky). Na zvážení administrátorů nechávám, jakou závislost čištění udělat na zálohování (poklikáním na vazbu můžeme volit Success, Error, nebo jen Completion). Teoreticky tedy můžeme čištění podmínit úspěšným zálohováním, aby nám po čase nezmizely staré soubory a nové nevznikaly.