Autor:
ViewState – jak pozbyć się tego ze strony
stworzony przez Tomek Dziemidowicz dnia mar.15, 2009, w kategorii: ASP.NET
ViewState to wygodny i często nadużywany mechanizm. Wszystko co zapisujemy do ViewState jest wrzucane na stronę w postaci HiddenField. Widziałem strony gdzie ViewState zajmował kilkadziesiąt KB. Nie każdy zdaje sobie jednak sprawę, że wartość obiektu ViewState jest wysyłana do serwera przy każdym request od przeglądarki. Zdażyło wam się że w momencie kliknięcia przycisku trochę potrwa zanim serwer zwróci wam stronę? Prawdopodobnie opóźnienie jest spowodowane wysłaniem ViewState do serwera a trzeba pamiętać że upload od przeglądarki jest zazwyczaj dużo mniejszy.
Istnieje pewien sposób pozwalający na zapanowanie nad ViewState.
protected override void SavePageStateToPersistenceMedium(object viewState)
{
string Key = Request.Url.ToString() + "__VIEWSTATE";
System.IO.MemoryStream memStream = new System.IO.MemoryStream();
LosFormatter formatter = new LosFormatter();
formatter.Serialize(memStream, viewState);
memStream.Flush();
Session[Key] = memStream;
}
protected override object LoadPageStateFromPersistenceMedium()
{
string Key = Request.Url.ToString() + "__VIEWSTATE";
if ((Session[Key] != null))
{
System.IO.MemoryStream memStream = (System.IO.MemoryStream)Session[Key];
memStream.Seek(0, SeekOrigin.Begin);
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(memStream);
}
else
return null;
}
Dodanie tego kodu do strony, spowoduje, że zniknie wam wartość ViewState ze strony. Ta wartość zostanie zapisana do sesji użytkownika. Rozwiązanie nie zawsze się sprawdza. Osobiście miałem problemy z korzystaniem z tego mechanizmu np przy używaniu kontrolek Telerik.
Innym sposobem radzenia sobie z ViewState jest kompresja wartość ViewSate. Niestety i ta metoda nie jest we wszystkich przypadkach skuteczna.
protected override object LoadPageStateFromPersistenceMedium()
{
string viewState = Request.Form["__VSTATE"];
byte[] bytes = Convert.FromBase64String(viewState);
bytes = Compressor.Decompress(bytes);
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(Convert.ToBase64String(bytes));
}
protected override void SavePageStateToPersistenceMedium(object viewState)
{
LosFormatter formatter = new LosFormatter();
StringWriter writer = new StringWriter();
formatter.Serialize(writer, viewState);
string viewStateString = writer.ToString();
byte[] bytes = Convert.FromBase64String(viewStateString);
bytes = Compressor.Compress(bytes);
ClientScript.RegisterHiddenField("__VSTATE", Convert.ToBase64String(bytes));
}
Klasa kompresująca wykorzystana w powyższych metodach:
using System.IO;
using System.IO.Compression;
public static class Compressor
{
public static byte[] Compress(byte[] data)
{
MemoryStream output = new MemoryStream();
GZipStream gzip = new GZipStream(output,
CompressionMode.Compress, true);
gzip.Write(data, 0, data.Length);
gzip.Close();
return output.ToArray();
}
public static byte[] Decompress(byte[] data)
{
MemoryStream input = new MemoryStream();
input.Write(data, 0, data.Length);
input.Position = 0;
GZipStream gzip = new GZipStream(input,
CompressionMode.Decompress, true);
MemoryStream output = new MemoryStream();
byte[] buff = new byte[64];
int read = -1;
read = gzip.Read(buff, 0, buff.Length);
while (read > 0)
{
output.Write(buff, 0, read);
read = gzip.Read(buff, 0, buff.Length);
}
gzip.Close();
return output.ToArray();
}
}
Cache w aplikacji ASP.NET, dlaczego warto
stworzony przez Tomek Dziemidowicz dnia mar.15, 2009, w kategorii: ASP.NET
Przeglądając materiały w sieci na temat cache w ASP.NET można zauważyć, że większość skupia się na bardzo wąskiej i raczej niewystarczającej deklaracji :
<%@ OutputCache Duration=„60″ VaryByParam=„none” %>
Nie będę rozwijał i opisywał tej metody. Odsyłam do artykułu na CodeGuru.pl – jest to tam dokładnie opisane.
W tym artykule skupię się na wykorzystaniu obiektu System.Web.Caching.Cache do zapamiętywania wyników z bazy danych czyli w efekcie zmniejszenia obciążenia silnika bazy danych.
W moim przykładzie, dostęp do bazy danych został podzielony na dwie powłoki: BLL i DAL. Każda z nich spełnia określoną rolę:
Powłoka BLL (z ang. Bussiness Logic Layer) odpowiada za dostęp powłoki prezentacyjnej (stron aspx) do bazy danych. W dużym skrócie działa to tak: Pobierz dane z bazy z użyciem powłoki DAL i zapisz je do Cache jeżeli jeszcze nie ma ich w pamięci podręcznej. Przykładowa funkcja z tej powłoki pobierająca wszystkie wiersze z bazy danych tabeli Products:
W pierwszej kolejności dodajemy obsługę obiektu cache
public abstract class BaseProducts : BizObject
{
protected static bool EnableCaching
{
get { return true; }
}
/// <summary>
/// Cache the input data, if caching is enabled
/// </summary>
protected static void CacheData(string key, object data)
{
if (EnableCaching && data != null)
{
BizObject.Cache.Insert(key, data, null,
DateTime.Now.AddSeconds(60), TimeSpan.Zero);
}
}
}
Klasa BizObject obudowuje obiekt Cache dla naszych potrzeb :
namespace AuroraSoft.BLL
{
public abstract class BizObject
{
protected static Cache Cache
{
get { return HttpContext.Current.Cache; }
}
/// <summary>
/// Remove from the ASP.NET cache all items whose key starts with the input prefix
/// </summary>
protected static void PurgeCacheItems(string prefix)
{
prefix = prefix.ToLower();
List<string> itemsToRemove = new List<string>();
IDictionaryEnumerator enumerator = BizObject.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Key.ToString().ToLower().StartsWith(prefix))
itemsToRemove.Add(enumerator.Key.ToString());
}
foreach (string itemToRemove in itemsToRemove)
BizObject.Cache.Remove(itemToRemove);
}
}
}
Klasa Products odzwierciedla operację na tabeli Products.
public class Products : BaseProducts
{
/// <summary>
/// Returns a collection with all the Products
/// </summary>
public static List<ProductsEntity> GetProducts()
{
List<ProductsEntity> productsCollection = null;
string key = "Products_";
if (BaseProducts.EnableCaching && BizObject.Cache[key] != null)
{
productsCollection = (List<ProductsEntity>)BizObject.Cache[key];
}
else
{
productsCollection = SiteProvider.Products.GetProducts();
BaseProducts.CacheData(key, productsCollection);
}
return productsCollection;
}
}
Bardzo ważnym elementem tej metody jest string key = „Products_”; , ponieważ mówi dla obiektu Cache pod jakim kluczem zostaje zapamiętany nasz obiekt. Jeżeli w innej funkcji użyjemy tego samego klucza spowoduje to zastapienie obiektu. Metoda GetProducts() sprawdza czy dane znajdują się w pamięci Cache, jeżeli nie to pobiera je z bazy danych (SiteProvider.Products.GetProducts();) i zapisuje do pamięci Cache. Przy następny przeładowaniu strony, dane zostaną pobrane z Cache.
OK, ale co się stanie jeżeli ktoś doda jakiś rekord? Tutaj funkcja dodająca rekord do tabeli Products (z klasy Products)
/// <summary>
/// Creates a new Products
/// </summary>
public static int InsertProduct(string Title)
{
ProductsEntity record = new ProductsEntity(0, Title);
int ret = SiteProvider.Products.InsertProduct(record);
BizObject.PurgeCacheItems("Products_");
return ret;
}
Po dodaniu rekordu do bazy danych zostaje wywołane wyczyszczenie Cache BizObject.PurgeCacheItems(„Products_”); dla wszystkich kluczy które zaczynają się od „Products_”.
Pozostaje pytanie: jak długo obiekt będzie istniał w pamięci cache? Otóż to jest zależne od nas i określamy to w metodzie dodającej obiekt do pamięci cache:
BizObject.Cache.Insert(key, data, null, DateTime.Now.AddSeconds(60), TimeSpan.Zero);
W moim przykładzie obiekt będzie istniał w pamięci cache przez 60 sek.
Powłoka BLL komunikuje się z bazą danych przy pomcy DAL (Data Access Layer). DAL odpowiada za zapisywanie i odczyt informacji z bazy danych.
Wadą tego rozwiązania jest zwiększone zapotrzebowanie aplikacji na pamięć RAM.
Przykładowa strona do pobrania (źródła) z zastosowaniem tej techniki. Znajduje się tam pełna implementacja powłoki BLL i DAL dla jednej tabeli :
Uwaga: Musisz mieć VS2008 i MSSQL (przynajmniej w wersji Express). Do projektu jest dołączony backup bazy danych. Wystarczy odtworzyć to na swoim lokalnym serwerze i odpowiednio zmodyfikować w web.config ConnectionString.
Wydajne aplikacje web – kompresja HTTP
stworzony przez Tomek Dziemidowicz dnia mar.14, 2009, w kategorii: ASP.NET
Wspominałem wcześniej, że warto używać kompresji HTTP i jako przykład podałem darmową bibliotekę Blowery. Przykładowa konfiguracja:
1.Pobieramy skompilowaną bibliotekę Blowery.
2.Kopiujemy bibliotekę do katalogu Bin naszej strony.
3. Modyfikujemy plik web.config.
W zanczniku configSections dodajemy deklarację grupy opcji Blowery:
<configSections> <sectionGroup name="blowery.web"> <section name="httpCompress" type="blowery.Web.HttpCompress.SectionHandler, blowery.Web.HttpCompress"/> </sectionGroup> </configSections>
Dodajemy moduł Blowery do sekcji httpModules:
<system.web> <httpModules> <add name="CompressionModule" type="blowery.Web.HttpCompress.HttpModule, blowery.web.HttpCompress"/> </httpModules> </system.web>
Konfigurujemy moduł kompresji (dodajemy bezpośrednio po <system.web>):
<blowery.web> <httpCompress preferredAlgorithm="gzip" compressionLevel="high"> <excludedMimeTypes> <add type="image/jpeg"/> <add type="image/gif"/> </excludedMimeTypes> <excludedPaths> <add path="WebResource.axd"/> <add path="ScriptResource.axd"/> </excludedPaths> </httpCompress> </blowery.web>
Tutaj sprawdzamy czy kompresja działa. Używając tej metody nie musimy zmienić ani dodwać nawet linijki kodu.
Trochę liczb. Posłuże się przykładem jednego z moich systemów AuroraWeb. Po wdrożeniu kompresji, ilość danych przesyłanych do klienta spadła średnio 5 razy. Co to oznacza?
Dla strony o wielkości 100KB klient dostaje 20 KB. Dla 1000 odsłon to:
bez kompresji: 1000 odsłon * 100KB = 100000KB = 100MB
z kompresją: 1000 odsłon * 20KB = 20000KB = 20MB
W scenriuszu gdzie system jest hostowny w tzw. shared enviroment (hosting virtualny typu home.pl) i mamy ograniczoną przepustować (ilość wysyłanych danych) na 1000 odsłonach zaoszczędzamy 80MB.
Wydajne aplikacje web oparte na technologi ASP.NET…
stworzony przez Tomek Dziemidowicz dnia mar.14, 2009, w kategorii: ASP.NET
…czyli , jak napisać aplikację przyjazną dla serwera i łącza.
Tak, temat bez wątpienia poruszany wiele razy. W czasach kiedy większość z nas używała PHP (ze względu na to że ASP.NET nie było a ASP raczej nie dorastało PHP do pięt) napisanie wydajnej i szybkiej aplikacji było łatwe.
Przeglądając niektóre strony, zastanawiam się, co się stało z zasadą, że pierwsza strona powinna pokazać się maks w przeciągu kilku sek i nie powinna zajmować więcej niż 100KB. Dzisiaj stworzenie aplikacji WWW sprowadza się praktycznie tylko i wyłącznie do zainstalowania odpowiedniego środowiska i opanowaniu technologi Drag’N'Drop.
Kilka podstawowych zasad tworzenia aplikacji:
1. Używamy kompresji HTTP. Bezwarunkowo. Większość programistów uważa, że to zadanie administratora serwera – skonfigurowanie IISa żeby używał kompresji. Błąd. Powinien o to zadbać programista. Polecam bibliotekę blowery. Konfiguracja zajmuje nie więcej jak 15 min i przynosi niezłe efekty. Tutaj możesz sprawdzić, ile zaoszczędzasz na kompresji. Średnio ilość przesłanych danych jest 5-10 razy mniejsza.
2. Używanie cache. Dużo się o tym mówi i pisze, zapominając o jednym – dobrze przygotowana strona trzyma w pamięci cache oprócz strony (wyrenderowaną stronę aspx) również dane pobrane z bazy danych. Dzięki temu mamy nie 1000 zapytań do bazy na sek a tylko 1 zapytanie (oczywiście zależy to od użytej techniki).
3. JavaScript i CSS. Często pisząc fukncje JavaScript i klasy CSS dodajemy komentarze, odstępy, wcięcia – wszystko po to aby ułatwić nawigację i zwiększyć czytelność kodu. Warto zadbać o to, żeby użytkownik dostawał CZYSTY JavaScript i CSS tzn bez tzw. whitespaces i komentarzy.