Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

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

Pozicování obrázkových bulletů – ul, li

S pozicování obrázkových bulettů použitých jako list-style-image je děsnej opruz, každý prohlížeč to interpretuje jinak a spolehlivě to snad ani nejde.

Je mnohem jednodušší dát list-style-type:none a obrázkové bulettky udělat jako správně pozicované pozadí li:

ul { list-style-type: none; }
li
{
   background-image: url(...);
   background-repeat: no-repeat;
   background-position: 0 5px;
   padding-left: 10px;
}
 

ExternalException: A generic error occurred in GDI+. při Image.Save()

Tuto krásnou výjimku dostaneme, pokud chceme Image ukládat do stejného souboru, z kterého byl načten. GDI+ to prostě nedovoluje.

Dá se to ale obejít například zkopírováním obrázku do nové instance:

Bitmap bitmap;
using (Image image = Image.FromFile(sourceFilename))
 {
   // načteme si obrázek do bitmapy, abychom mohli zavřít soubor
   bitmap = new Bitmap(image);
}
...
bitmap.Save(...); // tady už to nevadí, přetrhli jsme vazbu na soubor

Jak přistupovat na HttpContext.Current v novém threadu

Když si v ASP.NET spustíme nový thread (vlákno), tak tento sice získá některé vlastnosti z původního threadu (např. culture), ale nemá přístup k HttpContext.Current, resp. je v něm null.

V každém případě musíme vědět, co děláme, protože druhé vlákno například nemůže moc manipulovat s Response, neboť není vůbec jisté, do jakého stavu Response uvedl thread první, např. už totiž mohl Response odeslat na klienta.

Společné uložiště threadů – statická property

První možností je připravit hodnoty, které bude nový thread potřebovat, do uložiště, které je přístupné oboum threadům, klasicky třeba statické property nějaké třídy.

public static string RootUrl
{
   get
   {
      if (rootUrl == null)
      {
         if (HttpContext.Current != null)
         {
            rootUrl = "http://" + HttpContext.Current.Request.Url.Host + HttpContext.Current.Request.ApplicationPath;
         }
         else
         {
            rootUrl = "http://nejaky/default/url";
         }
      }
      return rootUrl;
   }
}
private static string rootUrl;

Výše uvedený příklad statické property zahrnuje i určitou logiku. Rozhodující je však uložení výsledku prvního volání do statické privátní proměnné rootUrl. Pokud si tedy před spuštěním nového threadu zaručíme alespoň jedno čtení dané property, ta si inicializuje svojí hodnotu v prvním threadu a v druhém threadu už dá rovnou výsledek připravený v rootUrl (nebude HttpContext.Current potřebovat).

Teoreticky lze do statické property typu HttpContext cachovat celý HttpContext.Current, nepovažuji to však za příliš vhodné.

Předání contextu v instanci třídy zapouzdřující thread

Druhou možností, která je vhodnější, pokud už potřebujeme předat do druhého threadu opravdu celý context, je zapouzdření druhého threadu do samostatné třídy:

public class MyClass 
{ 
   private HttpContext _context; 
   public MyClass(HttpContext context)
   {
      _context = context;
   } 
   public void Start() 
   { 
      ThreadStart start = new ThreadStart(DoWork); 
      Thread.Start(start); 
   } 
   private void DoWork() 
   { 
      // tady můžem něco dělat z contextem,
      // ale pozor, že request už může být odeslán na klienta
   } 
} 

private void Page_Load(object sender, EventArgs e) 
{ 
   MyClass myClass = new MyClass(HttpContext.Current); 
   myClass.Start(); 
}
HttpContext.Current.Cache

Pokud potřebujeme jen HttpContext.Current.Cache, tak je vše výše uvedené nesmysl, protože na cache máme přístupovat přes HttpRuntime.Cache, a to funguje i v novým threadu…

SQL: Stránkování záznamů pomocí ROW_NUMBER()

Pokud potřebujeme stránkovat záznamy na straně SQL, můžeme to udělat pomocí jedné z nových funkcí SQL2005:

USE AdventureWorks
WITH Rows AS
(
   SELECT *, ROW_NUMBER() OVER(ORDER BY ProductID) AS RowNumber FROM Production.Product
)
SELECT * FROM Rows WHERE RowNumber BETWEEN 101 AND 200

Bohužel se mi to nepodařilo přímo bez CTE v prvním WHERE, tyhle nové funkce jdou zřejmě jenom jako sloupce nebo v ORDER BY.
Další funkce ze skupiny ranking jsou RANK(), DENSE_RANK(), ROW_NUMBER() a NTILE().

Komu se nelíbí Common Table Expression (CTE) s WITH, tak to samozřejmě jde i jako vnořený SELECT:

SELECT  Description, Date
   FROM (SELECT  ROW_NUMBER() OVER (ORDER BY Date DESC) AS Row,
            Description, Date FROM LOG) AS LogWithRowNumbers
   WHERE  Row >= 1 AND Row <= 10

Počet znaků ntext položky – DATALENGTH()

Na pole referenčních typů ntext, text, apod. nejde použít řetězcovou funkci LEN(expr), funguje však

 DATALENGTH(expr)

které vrátí velikost v bytech (u Unicode položek je to dvojnásobek počtu znaků).

Mimochodem DATALENGTH() se dá použít i jako takové SQL String.IsNullOrEmpty():

WHERE DATALENGTH(MyColumn) = 0

SQL2005: Nepoužívat už ntext, text, image

Datové typy ntext, text a image budou z budoucích verzích Microsoft SQL Serveru odstraněny. Doporučuje se tedy místo nich používat nové typy nvarchar(MAX), varchar(MAX) avarbinary(MAX).

Rozdíl mezi int.Parse() a Convert.ToInt32()

Mnoho vývojářů tápe, kterou z metod Int32.Parse() a Convert.ToInt32() používat pro parsování textových vstupů na číslo. Rozdíl je malý, a sice v interpretaci vstupu null. Vše je jasné, pokud se podíváme na vnitřnosti Convert.ToInt32():

 

public static int ToInt32(string value)
{
   if (value == null)
   {
      return 0;
   }
   return int.Parse(value, CultureInfo.CurrentCulture);
}

Metoda Convert.ToInt32() tedy na vstup null vrátí hodnotu 0, zatímco int.Parse() vyhodí výjimku ArgumentNullException.

Vyčištění webové Cache (application cache)

Někdy se nám může hodit kompletní vyčištění webové application cache (datové cache přístupné přes HttpRuntime.Cache, Control.Cache, HttpContext.Current.Cache, …), například po změně cachovaných hodnot „read-only“ číselníků.

.NET Framework pro takovou operaci žádnou přímou pomůcku nemá, nebo o ní alespoň nevím. Jde to však obejít poměrně snadno. Třída Cache totíž implementuje rozhraní IEnumerable a metoda GetEnumerator() vrací IDictionaryEnumerator. Stačí tedy po jedné vyházet všechny položky:

 foreach (DictionaryEntry de in Cache)
{
   Cache.Remove(de.Key.ToString());
}
 

Implementováno v Havit.Web.HttpServerUtilityExt.ClearCache().

Viz též

Zobrazení controlu jen v určité položce Repeateru

Je poměrně snadný způsob, jak alespoň částečně obměňovat obsah položek repeateru. Chceme-li například zobrazit control jen v první položce (mezi první a druhou položkou), pak  takto:

<asp:Repeater ID="ZpravickyRepeater" Runat="server">
   <ItemTemplate>
      ...
      <xy:CokolivTrebaPlaceHolder Visible="<%# (Container.ItemIndex == 0) %>" Runat="server"/>
   </ItemTemplate>
</asp:Repeater>

Equals, operátory == a !=

 

Metoda Equals() slouží v .NET Frameworku k ověření rovnosti. Její výchozí implementace Object.Equals() ověřuje referenční rovnost, tedy zda obě reference odkazují na stejnou instanci. U typů, které nesou nějaké smysluplné hodnoty, může být však vhodnější implementovat Equals jako ověření hodnotové rovnosti, byť by se jednalo o dvě různé instance.

.NET má metodu Object.Equals ve dvou podobách

  • public virtual bool Equals(object obj) – k té chceme implementovat override
  • public static bool Equals(object objA, object objB)
public static bool Equals(object objA, object objB)
{
   if (objA == objB)
   {
      return true;
   }
   if ((objA != null) && (objB != null))
   {
      return objA.Equals(objB);
   }
   return false;
}

Statická metoda tedy nejprve ověří rovnost referencí, pokud jde o jednu instanci, vrátí true. Potom ověří, zda-li není jeden z objektů null. Pokud ano, vrátí false, pokud ne zavolá instanční podobu, kterou právě overridujeme.

Kontrakt

Nejprve základní pravidla, které musí Equals splňovat:

  • reflexe – A.Equals(A) = true,
  • symetrie – když A.Equals(B), pak B.Equals(A),
  • tranzitivita – když A.Equals(B) a B.Equals(C), pak i A.Equals(C),
  • konzistence – pokud nedojde ke změně objektů, pak A.Equals(B) musí dávat stále stejnou hodnotu,
  • A.Equals(null) = false,
  • metoda nesmí vyhazovat žádnou výjimku,

Doporučení:

  • potomek by měl volat base.Equals(), pokud je to možné,
  • měly bychom overridovat též GetHashCode(),
  • pokud implementujeme IComparable, měli bychom overridovat Equals(),
  • měly bychom vytvořit i strong-typed overload Equals().
Operátory ==, !=

Zároveň s override metody Equals() je vhodné přetížit i operátory == a !=, jejichž základní implementace ověřuje opět referenční shodu. Implementace je jednoduchá, prostě se odkážeme opět na statické Object.Equals()

Implementace

Ukažme si tedy běžnou implementaci pro business-object, u kterého nám stačí shoda ID.

public virtual bool Equals(BusinessObjectBase obj)
{
   if ((obj == null) || (this.GetType() != obj.GetType()))
   {
      return false;
   }
   if (!Object.Equals(this.ID, obj.ID))
   {
      return false;
   }
   return true;
}
public override bool Equals(object obj)
{
   if (obj is BusinessObjectBase)
   {
      BusinessObjectBase bob = obj as BusinessObjectBase;
      return this.Equals(bob);
   }
   return false;
}
public static bool operator ==(BusinessObjectBase objA, BusinessObjectBase objB)
{
   return Object.Equals(objA, objB);
}
public static bool operator !=(BusinessObjectBase objA, BusinessObjectBase objB)
{
   return !Object.Equals(objA, objB);
}
public override int GetHashCode()
{
   return this.ID;
}

Samotné porovnávání členských hodnot, které determinují shodu, je opět vhodné nechat na statické metodě Object.Equals().

Ještě si ukažme, jak by měla vypadat implementace potomka, který by chtěl mimo ID hlídat ještě shodu na nějakou další hodnotu (doporučené volání base.Equals())

public override Equals(Order order)
{
   return base.Equals((BusinessObjectBase)order)
            && Object.Equals(this.Cena, order.Cena);
}
public override bool Equals(object obj)
{
   if (obj is Order)
   {
      Order order = obj as Order;
      return this.Equals(order);
   }
   return false;
}
Viz též