Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

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

Scheduled task lze spustit pouze ručně, status „Could not start“, 0x80070569: Logon failure

Naplánovaná úloha (Scheduled taks, např. zálohování) lze spustit pouze ručně (pravým tlačítkem Run), ale namísto naplánovaného spuštění je při detailním zobrazení označena stavem „Could not start“ („Nelze spustit“) a chybovým kódem 0x0.

Pokud se podíváme do logu scheduleru (Advanced ~ View log, Upřesnit ~ Zobrazit protokol), pak tam najdeme něco jako:

Backup.job" (NTBACKUP.EXE) 10/12/2006 3:00:00 AM ** ERROR **
    The attempt to log on to the account associated with the task failed, therefore, the task did not run.
    The specific error is:
    0x80070569: Logon failure: the user has not been granted the requested logon type at this computer.
    Verify that the task's Run-as name and password are valid and try again.

resp.

Zálohování.job (NTBACKUP.EXE) 18.11.2004 3:00:00 ** CHYBA **
    Pokus o přihlášení k účtu přidruženému k úloze se nezdařil. Úloha nebyla spuštěna..
    Příslušná chyba:
    0x80070569: Přihlašovací chyba: Uživateli nebyl v tomto počítači udělen požadovaný typ přihlášení.
    Ověřte, zda jsou údaje Spustit jako a heslo platné, a akci opakujte..
Příčina a řešení

Příčina je v zásadách zabezpečení. Účet, pod kterým se má úloha spouštět, nemá nastaveno právo„Logon as batch job“ („Přihlásit jako dávkovou úlohu“). Je tedy potřeba prohlédnout lokální a doménové zásadný zabezpečení a právo účtu přidělit.

web.sitemap – lokalizace a autorizace

Po chvilce bojů a objevení několika bugů se mi nakonec podařilo rozchodit lokalizaci a autorizaci v site-mapách ASP.NET, konkrétně při použití defaultního XmlSiteMapProvider a web.sitemap souborů (částečně to lze ovšem aplikovat i na jiné providery).

Lokalizace website.map

Defaultní XmlSiteMapProvider přímo podporuje lokalizaci web.sitemap souborů, stačí rootovému elementu nastavit atribut enableLocalization=“true“ a dále se pak na resources můžeme explicitně odkazovat obdobně jako v .aspx souborech, přes výraz $resources:ClassName,KeyName,DefaultValue

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" enableLocalization="true">
    <siteMapNode title="$resources: Navigation, UvodniStranka" url="~/default.aspx">
    ...
</siteMap>

Výše uvedený příklad by se opíral o soubor ~/App_GlobalResources/Navigation.resx. Jedná se o explicitní způsob lokalizace, existuje ještě implicitní metodika s využitím atributu resourceKey, nicméně implicitní lokalizace jako taková mě neoslovuje, takže ani tentokrát nebudu podorbněji rozvádět.

Pozor také, že mezi dolarem ($) a klíčovým slovem resources nesmí být mezerník. Lokalizovat lze výše uvedeným způsobem atributy title a description, bohužel ne url. Atribut url takto lokalizovat bohužel nelze, ač je to v některých dokumentacích uvedeno.

Pokud bychom chtěli lokalizovat i URL, můžeme to zařídit například tak, že nakonfigurujeme přes web.config dva nezávislé SiteMapProvidery, kde každý bude ukazovat na jiný .sitemap soubor:

<siteMap defaultProvider="CzXmlSiteMapProvider" enabled="true">
  <providers>
    <add name="CzXmlSiteMapProvider"
      description="Provider pro cestinu."
      type="System.Web.XmlSiteMapProvider"
      siteMapFile="WebCz.sitemap"
      securityTrimmingEnabled="true" />
    <add name="EnXmlSiteMapProvider"
      description="Provider pro cestinu."
      type="System.Web.XmlSiteMapProvider"
      siteMapFile="WebEn.sitemap"
      securityTrimmingEnabled="true" />
  </providers>
</siteMap>

…následně bychom podle aktuálního jazyka museli controlu SiteMapDataSource explicitně nastavovat příslušný SiteMapProvider (nastavit property SiteMapDataSource.SiteMapProvider, typ string). Deklaratorně například fíglem:

<asp:SiteMapDataSource
    ID="SiteMapDS"
    SiteMapProvider="<%$ Resources: Navigation, SiteMapProvider %>"
    runat="server" 
/>

Viz též obecná lokalizace webových aplikací.

Autorizace – skrývání uživateli nepřístupných položek

XmlSiteMapProvider, přesněji řečeno už bázová třída SiteMapProvider podporuje i authorizaci, resp. skrývání uživateli nedostupných položek. Tato funkčnost je řízena přes property SiteMapProvider.SecurityTrimmingEnabled, která se však nenastavuje programově (má jen get), nýbrž přes konfiguraci provideru z web.config souboru, atributem securityTrimmingEnabled:

<siteMap defaultProvider="MyXmlSiteMapProvider" enabled="true">
    <providers>
        <clear/>
        <add name="MyXmlSiteMapProvider"
         type="System.Web.XmlSiteMapProvider"
         siteMapFile="~/Web.sitemap"
         securityTrimmingEnabled="true" />
    </providers>
</siteMap>

Pokud nastavíme tento atribut, pravděpodobně nám začně funkčnost ihned fungovat. SiteMapProvider si sám hlídá přístupová práva uživatele nastavená přes sekci<authorization /> web.configu a uživateli nepřístupné položky skrývá.

Ovšem pozor!!! Implementace tohoto hlídání není úplně korektní a má problém s authorization pravidly web.config souborů, která se přes <location /> odkazují na podsložky. Chceme-li tedy tuto funkčnost SiteMapProvideru využít, můžeme ve web.config souborech definovat <location /> pravidla pouze pro jednotlivé soubory v příslušné složce a přístupová práva složek jako takových musíme řídit přes samostatné web.config soubory v těchto složkách!!!

Podle dokumentace a schématu .sitemap souboru má existovat u elementu <siteMapNode /> i atribut securityTrimmingEnabled, jakékoliv použití tohoto atributu však vyhazuje výjimku

ConfigurationErrorsException: Unrecognized attribute ‚securityTrimmingEnabled‘. Note that attribute names are case-sensitive.

Když jsem bádal v kódu XmlSiteMapProvideru, tak tam je tento atribut přímo zakázaný a zřejmě se jedná o nějaký přežitek z beta-verzí.

Element <siteMapNode /> má ještě atribut roles=“…“, kam lze explicitně nastavit, jakým uživatelským rolím se má node zobrazovat. Pokud však máme dobře nastavené samotné <authorization />, pak to mnohdy nepotřebujeme. Pokud ho přesto nastavíme, pak se vyhodnocuje MÍSTO standardních přístupových práv, nikoliv jako průnik.

Atribut roles=“*“ (nebo podobně) se nám bude hodit u externích odkazů, jejichž autorizaci nelze jinak vyhodnotit a bez explicitní volby roles se nám odkaz vůbec nezobrazí.

Dále je potřeba si uvědomit, že SiteMapProvider vyhodnocuje přístupová práva od rootu sitemap dolů a pokud nevyhoví nadřazená položka, ustřelí se celý podstrom. Například je tedy problematické bez dalšího použít prázdný atribut url=““, protože jeho přístupová práva není jak vyhodnotit a příslušný podstrom se tak nezobrazí – naopak je však možné do url dávat cestu k neexistujícím stránkám, vyhodnocení se provede, jako by existovali. Pokud potřebujeme udělat siteNode bez odkazu, pak musíme použít atribut roles=“…“, aby bylo přístupová práva jak vyhodnotit.

Související články

form defaultfocus=“..“ defaultbutton=“…“ runat=“server“ při použití MasterPage

Občas jsme donuceni umístit control <form runat=“server“> už do MasterPage, například proto, že máme v MasterPage navigační TreeView či jiný control vyžadující <form>. Pak nastává otázka, jak ve vlastní content-page nastavit formuláři vlastnosti DefaultButton a DefaultFocus na tam umístěné controly.

<%@ Master ... %>
...
<body>
    <form id="MainForm" runat="server">
        ...
        <asp:ContentPlaceHolder ID="BodyCPH" runat="server" />
        ...
    </form>
</body>
...

Content-page s controly:

<%@ Page MasterPageFile="..." ... %>
<asp:Content ContentPlaceHolderID="BodyCPH" runat="server">
    <asp:TextBox ID="UsernameTB" runat="server" />
    <asp:LinkButton ID="LoginLB" runat="server" />
</asp:Content>

Mnohé správně napadne, že Page má property Form, přes kterou to jistě půjde nastavit. Ano, nicméně properties DefaultFocus a DefaultButton jsou typu string a pokud zkusíme například

Page.Form.DefaultButton = "LoginLB";  // špatně !!!
Page.Form.DefaultButton = LoginLB.ID;  // špatně !!!

…pak budeme odměněni výjimkou InvalidOperationException: The DefaultButton of ‚MainForm‘ must be the ID of a control of type IButtonControl.

Správně je v případě DefaultButton potřeba předat UniqueID a v případě DefaultFocus ClientIDdaného controlu, protože je potřeba ho identifikovat včetně NamingContaineru (form a button jsou každý v jiném naming containeru)

Page.Form.DefaultButton = LoginLB.UniqueID;
Page.Form.DefaultFocus = UsernameTB.ClientID;

Stejným způsobem bychom postupovali nejenom v případě MasterPage, ale například i v případě, kdy bychom chtěli vlastnosti DefaultButton či DefaultFocus ovlivnit z nějakého controlu (což bych spíše nedoporučoval).

@@IDENTITY vs. SCOPE_IDENTITY()

Velmi často se setkávám se zaměňováním „funkcí“ @@IDENTITY a SCOPE_IDENTITY(). Obě dvě dvě představují hodnotu identity-sloupce posledního INSERTu, nicméně každý trochu jinak definovanou a v určitých případech se mohou jejich výsledky lišit.

Definice

@@IDENTITY představuje poslední vloženou identity hodnotu v kontextu celé session, veškeré její aktivity.

SCOPE_IDENTITY() představuje poslední vloženou identity hodnotu v kontextu aktuálního scope, v nejužším slova smyslu.

Příklad

Mějme tabulku Table1 s identity-column a k ní insert trigger, který vkládá hodnotu do druhé tabulky Table2 opět s identity-column. A tady je rozdíl, pokud zavoláme dávku

INSERT INTO Table1 DEFAULT VALUES

SELECT @@IDENTITY         -- vrátí identity hodnotu Table2 z triggeru
SELECT SCOPE_IDENTITY()   -- vrátí identity hodnotu Table1

…s použitím @@IDENTITY si můžeme pěkně naběhnout.

IDENT_CURRENT(‚table‘)

Funkce IDENT_CURRENT(‚tablename‘) vrací poslední hodnotu identity-column tabulky, nezávisle na session či scope.

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.

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).