Optymalizacja aplikacji ASP.NET

82
Optymalizacja aplikacji ASP.NET

description

Prezentacja opisuje różne techniki optymalizacji aplikacji ASP.NET. Omawiane są role poszczególnych warstw wpływających na wydajność - od optymalizacji kodu po stronie klienta (techniki stosowane na poziomie kodu HTML i JavaScript) przez różne poziomy stosowania cache, wybrane ustawienia konfiguracyjne IIS aż po same techniki optymalizacji na poziomie kodu ASP.NET.

Transcript of Optymalizacja aplikacji ASP.NET

Page 1: Optymalizacja aplikacji ASP.NET

Optymalizacja aplikacji ASP.NET

Page 2: Optymalizacja aplikacji ASP.NET

Skalowalność – ilu użytkowników może pracować jednocześnie? Aplikacja może obsługiwać tysiące użytkowników, ale być wolna Zmiany w sprzęcie, infrastrukturze sieciowej, itp. (istotna architektura!)

Wydajność – jak szybko ładują się strony? Zasada 8 sekund Liczy się odczuwalna wydajność (np. reklamy dopiero na końcu)

To także: Szybkość wprowadzania poprawek i zmian Łatwość wdrażania aplikacji Proces wytwórczy …

Architektura Bing vs strony domowej (można przesadzić)

Skalowalność i wydajność

Page 3: Optymalizacja aplikacji ASP.NET

Lokalny cache przeglądarki DNS – cache / zapytanie Proxy – visible / transparent Cache serwera

System - http.sys IIS ASP.NET (dane w ram / z dysku)

Cache SQL Server Dyski – SSD / zwykłe / cache…

Komponenty wpływające na wydajność

Page 4: Optymalizacja aplikacji ASP.NET

Odczuwalna wydajność Kolejność ładowania, AJAX

Mniej żądań do serwera Walidacja po stronie klienta, bundling, itp.

Cache na wszystkich warstwach Minimalizacja blokujących wywołań

Synchroniczne operacje zapisu do bazy, zewnętrzne usługi, itp.

Optymalizacja I/O (dyski, itp.) Także partycjonowanie / sharding, itp.

Najważniejsze zasady przy optymalizacji

Page 5: Optymalizacja aplikacji ASP.NET

Wydajność po stronie klienta

Page 6: Optymalizacja aplikacji ASP.NET

Maximum Transfer Unit (MTU) - rozmiar okna odpowiedzi Od 500 do 1500 bajtów Dużo komunikatów SYN-ACK

TCP Slow Start (RFC 5681) Bardzo kosztowne nawiązywanie połączenia Długi okres między pierwszym pakietem a kolejnym Zapobiega przeciążeniu sieci

Jak najmniej nowych połączeń (np. żądania plików) HTTP Keep-Alives – domyślnie w IIS 120 sekund

Np. przy długich formularzach można zwiększyć (ostrożnie)

Protokół TCP

Page 7: Optymalizacja aplikacji ASP.NET

TCP Slow Start - przykład

IE Developer tools (F12) Żółte – slow start + pierwszy pakiet odpowiedzi Niebieski – reszta odpowiedzi

Jeśli <img> było w pierwszym pakiecie

Page 8: Optymalizacja aplikacji ASP.NET

<head> Przeglądarka nie wyświetli nic przed pobraniem całego

nagłówka „Lookahead” – ograniczenia

Jak najwięcej w <body> Nawet <link> i <style> (niezgodne ze specyfikacją, ale

działa) Kolejność

Np. duży baner reklamowy na górze strony – w kodzie lepiej niżej

Struktura strony

Page 9: Optymalizacja aplikacji ASP.NET

Ustawiać width i height dla <img>!

Późne ładowanie - placeholder

       <img id="myimg" width="50" height="50" />     <!-- Klasycznie -->     

    <script type="text/javascript">        document.getElementById("myimg").src = "myimage.jpg";    </script>

    <img id="Img1" width="50" height="50" />

    <!-- w jQuery -->

    <script type="text/javascript">        $("#myimg").attr("src", "myimage.jpg");    </script>

Page 10: Optymalizacja aplikacji ASP.NET

Późne ładowanie – preloading

    <!-- Preloading - na górze strony lub w onload (jeśli może być później) -->    <script type="text/javascript">        var myimg = new Image();        myimg.src = "myimage.jpg";    </script>        <!-- Niżej na stronie, będzie załadowane z cache (dla rolloverów) -->    <img src="myimage.jpg" width="50" height="50" />

Page 11: Optymalizacja aplikacji ASP.NET

Wielkość liter W Unix – system plików case sensitive Niektóre serwery cache’ują oddzielnie

Może spowodować wysłanie dwóch żądań

Można dołączyć moduł http, który to poprawia Referencje do tej samej domeny

Przekierowanie z domena.com/gfx.jpg na www.domena.com/gfx.jpg

Cache i URL

    <img src="myimage.jpg" width="50" height="50" />    <img src="myimage.JPG" width="50" height="50" />

Page 12: Optymalizacja aplikacji ASP.NET

Przeglądarki mają do 6 równoległych połączeń dla domeny Przed IE8 – tylko 2!

Można podzielić pliki na kilka domen img.mojadomena.com, css.mojadomena.com, itp. Aliasy lub inne usługi cookieless (np. Azure Blob)

Przetwarzanie żądań

<img src="q1.gif" height="16" width="16" /><img src="q2.gif" height="16" width="16" /><img src="q3.gif" height="16" width="16" /><img src="q4.gif" height="16" width="16" /><img src="q5.gif" height="16" width="16" /><img src="q6.gif" height="16" width="16" /><img src="q7.gif" height="16" width="16" /><img src="q8.gif" height="16" width="16" /><img src="q9.gif" height="16" width="16" /><img src="q10.gif" height="16" width="16" />

Page 13: Optymalizacja aplikacji ASP.NET

ok. 30% zwiększenie szybkości wczytywania

Podział plików na kilka domen

<img src="q1.gif" height="16" width="16" /><img src="q2.gif" height="16" width="16" /><img src="q3.gif" height="16" width="16" /><img src="q4.gif" height="16" width="16" /><img src="q5.gif" height="16" width="16" /><img src="http://mojadomena.net/samples/ch02/q6.gif" height="16" width="16" /><img src="http://mojadomena.net/samples/ch02/q7.gif" height="16" width="16" /><img src="http://mojadomena.net/samples/ch02/q8.gif" height="16" width="16" /><img src="http://mojadomena.net/samples/ch02/q9.gif" height="16" width="16" /><img src="http://mojadomena.net/samples/ch02/q10.gif" height="16" width="16" />

Page 14: Optymalizacja aplikacji ASP.NET

Jeśli kilka aliasów – mechanizm generowania powtarzalnych url Plik grafika.jpg zawsze z s1.domena.com, grafika3.jpg z

s2.domena.com, itp. Wewnątrz własnej kontrolki Control adapter dla Image

(dalej)

ASP.NET i podział na kilka domen

    private string _src;    private static string[] subdomains = {         "http://s1.12titans.net",        "http://s2.12titans.net",        "http://s3.12titans.net"    };

    public string src    {        get        {            HttpContext ctx = HttpContext.Current;            if (ctx.Request.Url.Host != "localhost")            {                if (!String.IsNullOrEmpty(this._src) && !this._src.StartsWith("http") &&                    !this._src.StartsWith("data:"))                {                    int n = Math.Abs(this._src.GetHashCode()) % subdomains.Length;                    return subdomains[n] + this._src;                }            }            return this._src;        }        set        {            this._src = ResolveUrl(value).ToLowerInvariant();        }    }

Page 15: Optymalizacja aplikacji ASP.NET

Skrypty inline mogą opóźniać renderowanie strony Renderowanie dopiero po zakończeniu działania skryptów

OnLoad / DOMReady – wtedy po wyrenderowaniu Umieszczać na końcu pliku Jeśli skrypty zmieniają HTML

Zamiast document.write() – innerHTML (możliwe wywołania później) Ukryty div z document.write() + odkrywanie go później

<script defer> i <script async> (HTML 5) Nie wstrzymuje parsera, pobiera skrypt i wykonuje kod (np. podpina do onload) Async – nie gwarantuje kolejności (wywołuje po pobraniu) Defer – gwarantuje kolejność wywołań

Dołączać z CDN (m.in. ASP.NET AJAX, jQuery) - http://www.asp.net/ajaxlibrary/cdn.ashx

Skrypty

Page 16: Optymalizacja aplikacji ASP.NET

Tylko lower case w miarę możliwości – kompresja <img> zamiast <IMG>, itp.

Image sprites dla wielu mniejszych grafik background-position: 0 -120px

Grafika - rozważyć data URI scheme (IE 8+) Narzędzia lub online – np. dataurl.net Do niewielkich grafik (base64, więc 40% większe) Zwłaszcza w CSS, kiedy może być dodatkowo cache’owane

Zmniejszanie liczby żądań

#hdr{border:1px solid #000;height:40px;background:url(data:image/gif;base64,R0lGODlhAQAoANUAAAAAAP///wFUzgNV0ANVzwVX0QZY0gdY0gha0wpb1Qxd1g1e1w9g2BFh2RJj2xVk3BZm3Rho3hpp4Bxr4h5t4x9u5CFw5SRx5yVz6Cd16Sl26it47C157S987jF97zOA8jJ+8TWB8zaD9DiE9TqF9zqG9zyH+D2I+D6J+T+K+v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACoALAAAAAABACgAAAYlwBTqZCqRRqLQB+TpcDaaDOZiqVAmkgjk4WgwFooE4mAoDAiCIAA7)

Page 17: Optymalizacja aplikacji ASP.NET

Javascript Walidacja po stronie klienta Wyłączanie submit po kliknięciu Generowanie długich list (np. select i wiele elementów <option>) Unikać obiekt.inny.jeszczeinny.zmienna (pomocnicza zmienna) Wielokrotny document.write zamiast sklejania stringów textContent szybszy od innerHTML (jeśli element zawiera tekst)

ASP.NET 4.5 – unobtrusive validation

Zmniejszenie liczby żądań – c.d.

<code><add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms"/></code>

ValidationSettings.UnobtrusiveValidationMode = UnobtrusiveValidationMode.WebForms;

Page 18: Optymalizacja aplikacji ASP.NET

Favicon.ico – lepiej żeby był (cache vs 404 za każdym razem) CSS – inline przy pierwszym wyświetleniu

Mniej żądań przy pierwszym wejściu Skrypt ładowany inline – jeśli nie ustawione ciasteczko W Onload – pobierany dynamicznie (cache dla kolejnych żądań) Nie zamiast – plik potrzebny (cache)

Zmniejszenie liczby żądań – c.d.

<script type="text/javascript">    function getcss() {        var h = document.getElementsByTagName('head');        var l = document.createElement('link');        l.type = 'text/css';        l.rel = 'stylesheet';        l.href = 'css/file19.css';        h[0].appendChild(l);    }</script>

<system.webServer>  <httpProtocol>    <customHeaders>      <add name="Set-Cookie"      value="C=A;expires=Sat, 01-Jan-2050 00:00:00 GMT;path=/demo/css/" />    </customHeaders>  </httpProtocol></system.webServer>

Page 19: Optymalizacja aplikacji ASP.NET

Określić <!DOCTYPE> Parser lookahead nie musi restartować

Width i Height przy grafikach Rozmiary kolumn – tabele Charset (dla statycznych stron)

Szybkość renderowania strony

Page 20: Optymalizacja aplikacji ASP.NET

Kiedy wiemy jaka będzie kolejna strona (np. „wizard”) W pageLoad (nie document ready)

Precaching - grafiki

<script type="text/javascript" src="jquery-1.7.1.min.js"></script><script type="text/javascript">    $(window).load(function() {        var pre = new Image(0,0);        pre.src = "http://s1.domena.net/static/next1.jpg";        var prx = new Image(0, 0);        prx.src = "http://s2.domena.net/static/next2.jpg";    });</script>

Page 21: Optymalizacja aplikacji ASP.NET

<img>- dla innych formatów nie zadziała (MIME type) AJAX – można, ale tylko ta sama domena Dynamicznie element <script>

Nie musi być dodawany do DOM Uwaga – parsowane i wywoływane

Dynamicznie <link> Musi być w DOM Wczytywany (może być konflikt selektorów)

Precaching – CSS, JS

<script type="text/javascript">    function preload() {        var req = getreq();        if (req != null) {            req.open("GET", "/static/next.js", true);            req.send(null);        }        var rex = getreq();        if (rex != null) {            rex.open("GET", "/static/next.css", true);            rex.send(null);        }    }</script>

<script type="text/javascript">    function preload() {        var scr = document.createElement("script");        scr.src = "http://s1.domena.net/ch02/next.js";    }</script>

<script type="text/javascript">    function preload() {        var lnk = document.createElement("link");        lnk.rel = "stylesheet";        lnk.type = "text/css";        lnk.href = "http://s1.domena.net/next.css";        document.getElementsByTagName('head')[0].appendChild(lnk);    }</script>

Page 22: Optymalizacja aplikacji ASP.NET

Caching

Page 23: Optymalizacja aplikacji ASP.NET

Każdy ma nieco inną rolę (np. ASP.NET vs http.sys vs SQL)

Wiele rodzajów cache

Page 24: Optymalizacja aplikacji ASP.NET

Cache-Control: max-age lub Expires (dawniej) Jeśli nie ustawione oblicza Aktualny czas +10% różnicy między Last-Modified a aktualnym

Po tym czasie – nadal na dysku, ale nie używane Conditional Get (If-Modified-Since) HTTP 304 – not modified

Statyczne pliki

GET /check.png HTTP/1.1Accept: */*Accept-Language: en-usAccept-Encoding: gzip, deflateIf-Modified-Since: Sat, 10 Jan 2012 10:52:45 GMTIf-None-Match: "80fc52fa8bb2c81:0"User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)Host: www.domena.netConnection: Keep-Alive

<system.webServer>  . . .  <staticContent>    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />  </staticContent></system.webServer>

Page 25: Optymalizacja aplikacji ASP.NET

Cache-Control: no-cache Przeglądarka musi sprawdzić, ale może korzystać z cache

(back/fwd) Cache-Control: no-store

Całkowite wyłączenie (także back/forward)

Cache statyczny - wyłączanie

<location path="image.jpg">  <system.webServer>    <staticContent>      <clientCache cacheControlMode="DisableCache" />    </staticContent>  </system.webServer>

Page 26: Optymalizacja aplikacji ASP.NET

Output cache VaryByParam, VaryByHeader – np. Accept-Language (dla różnych

języków) VaryByControl (np. „src”, jeśli taką właściwość ma nasza User

Control) Dla User Controls – ustawiać to samo ID na różnych stronach

i Shared=true Jeśli shared=false to do porównywania ID brana także nazwa Page

Dynamiczna zawartość

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="dyn-client.aspx.cs" Inherits="dyn_client" %><%@ OutputCache Duration="86400" Location="Client" VaryByParam="None" %>

<system.web>  <caching>    <outputCacheSettings>      <outputCacheProfiles>        <add name="Cache1Day" duration="86400"        location="Client" varyByParam="none" />      </outputCacheProfiles>    </outputCacheSettings>  </caching></system.web>

<%@ OutputCache CacheProfile="Cache1Day" %>

Page 27: Optymalizacja aplikacji ASP.NET

Najprościej – VaryByCustom = „browser” Po swojemu - global.asax

Jeśli chcemy np. jedną wersję dla wszystkich IE i jedną dla Webkit

Cache dla różnych przeglądarek

        public override string GetVaryByCustomString(HttpContext context, string custom)        {            switch (custom.ToLower())            {                case "iemozilla":                    switch (context.Request.Browser.Browser.ToLower())                    {                        case "ie":                        case "blazer 3.0":                            return "ie";                        case "mozilla":                        case "firebird":                        case "firefox":                        case "applemac-safari":                            return "mozilla";                        default:                            return "default";                    }                default:                    return base.GetVaryByCustomString(context, custom);            }        }

Page 28: Optymalizacja aplikacji ASP.NET

Dynamiczna zawartość - wyłączanie

        this.Response.Cache.SetCacheability(HttpCacheability.NoCache);        this.Response.Cache.SetAllowResponseInBrowserHistory(true); // Aby nie było Expires (niepotrzebne)

this.Response.AppendHeader("Cache-Control", "no-store"); // Dla no-store

<%@ OutputCache Location=„None" %>

Cache-Control: no-cachePragma: no-cacheExpires: -1

Czasem chcemy mieć pewność, że dane aktualne Aspx, kod lub profil w web.config

Page 29: Optymalizacja aplikacji ASP.NET

Domyślnie ASP.NET ustawia Cache-Control: private Nie będzie cache’owane przez proxy Możliwe nadpisanie (Cache-Control: public)

<%@ OutputCache Location=„Any” %> Location Downstream – tylko w przeglądarkach

Dynamiczna zawartość – c.d.

        this.Response.Cache.SetMaxAge(age);        this.Response.Cache.SetExpires(DateTime.UtcNow + age);        this.Response.Cache.SetLastModified(DateTime.UtcNow);        this.Response.Cache.SetCacheability(HttpCacheability.Public);        this.Response.Cache.SetNoServerCaching();

Page 30: Optymalizacja aplikacji ASP.NET

Rozszerzalność

<caching> <outputCache defaultProvider="AspNetInternalProvider"> <providers> <add name="DiskCache" type="Test.OutputCacheEx.DiskOutputCacheProvider, DiskCacheProvider"/> </providers> </outputCache></caching>

ASP.NET 4.0+ – możliwe np. zapisywanie na dysku

Możliwa dynamiczna podmiana Np. top 10 w pamięci, reszta na dysku

Page 31: Optymalizacja aplikacji ASP.NET

Stan tymczasowy strony, stan kontrolek serwerowych Często lepsze od sesji (baza przy farmie)

LosFormatter – szybki dla typów string, hashtable, arraylist, pair, triple, int, boolean Dla pozostałych – BinaryFormatter (wolny), ale można napisać własny TypeConverter Lepiej – kolekcja prostych typów niż własny obiekt (bez TypeConvertera)

Obecność elementu wykorzystywana m.in. do rozpoznania Page.IsPostback

ViewStateMac – zabezpieczenie przed modyfikacjami Zabezpieczenie przez CSRF

W Page_init – ViewState[userkey] = this.User.Identy.Name;

ViewState

Page 32: Optymalizacja aplikacji ASP.NET

Ma tendencje do rozrastania się Może być prościej wysłać zapytanie niż uploadować dużo większy formularz

Możliwość wyłączenia Od ASP.NET 4 – nie tylko dla całej strony (EnableViewState), ale pojedynczych

kontrolek ViewStateMode – enabled, disabled, inherit (domyślnie) Warto domyślnie wyłączyć (w web.config) i włączać tam, gdzie potrzeba

ControlState – część, której nie można wyłączyć

ViewState - problemy

Page 33: Optymalizacja aplikacji ASP.NET

Tag Transform, aby wyłączyć Control State Jeśli nie korzystamy z zaawansowanych funkcji kontrolki

ViewState – wyłączanie

    public class ListViewNoCS : ListView    {        protected override object SaveControlState()        {            return null;        }    }

<pages>  . . .  <tagMapping>    <add tagType="System.Web.UI.WebControls.ListView"    mappedTagType="Samples.ListViewNoCS" />  </tagMapping></pages>

Page 34: Optymalizacja aplikacji ASP.NET

Dla wolnych połączeń (np. klienci mobile) można przechowywać po stronie serwera

Można uzależnić to od typu połączenia lub rozmiaru ViewState

ViewState – wolne połączenia

    protected override void SavePageStateToPersistenceMedium(object state)    {        string key = Guid.NewGuid().ToString();        this.ClientScript.RegisterHiddenField(ViewKeyName, key);        this.Cache[key] = state;    }

    protected override object LoadPageStateFromPersistenceMedium()    {        string key = this.Request[ViewKeyName];        if (key == null)            throw new InvalidOperationException("Invalid ViewState Key");        object state = this.Cache[key];        if (state == null)            throw new InvalidOperationException("ViewState too old");        return state;    }

Page 35: Optymalizacja aplikacji ASP.NET

Dla domeny - max 50, 10 KB / cookie Session lub persistent Domyślna wartość path - „/”

Dołączane także do zawartości statycznej wszystkich plików! Ustawiać zawsze cookie.Path = „/katalog/”

Bez ustawienia Domain IE – także wszystkie subdomeny Inne przeglądarki – tylko aktualna domena Lepiej ustawić cookie.Domain (unikanie błędów)

Cookie.HttpOnly = true Cookie niewidoczne dla JS (mniej możliwości ataku) Powinno się ustawiać domyślnie dla wszystkich

Cookie.Secure = true – wysyłane tylko przez SSL Jeśli nie możemy SSL – konwersja do Base64 (Convert.ToBase64String) i szyfrowanie

symetryczne

Cookies

Page 36: Optymalizacja aplikacji ASP.NET

IE8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+ 10 MB – IE, 5 MB - pozostałe

2 typy Per-session (do zamkniecia okna) Per-domain

Tylko string, ale można serializować do JSON Tylko klient – nie przesyłane do serwera

Można dodać np. jako parametr wywołania usługi WCF

HTML 5 Web Storage

<script type="text/javascript">

    sessionStorage.setItem('mykey', 'this is my value');    var item = sessionStorage.getItem('mykey')

</script>

Page 37: Optymalizacja aplikacji ASP.NET

Kernel-mode driver - http.sys Znacznie szybszy (bez context switching, itd.) Domyślnie włączony dla zawartości statycznej

Dla dynamicznej – OutputCache, ustawienie IIS lub applicationHost.config

Kiedy nie działa (najczęstsze) Zawartość z querystring Domyślny dokument (d.com zamiast d.com/default.htm) Włączona kompresja

Domyślnie sliding, 120 sekund HKLM\System\CurrentControlSet\Services\Http\Parameters\UriScavengerPeriod Warto zmienić w razie potrzeby (np. kilkanaście godzin) - ostrożnie

Windows Kernel Cache

Page 38: Optymalizacja aplikacji ASP.NET

Domyślnie włączony – jeśli ustawione OutputCache Także dla żądań z querystring

Dla własnych HttpHandlerów – włączyć User Mode Caching

http://d.com/katalog/ zamiast http://d.com/katalog W przeciwnym wypadku HTTP 302 Zapobiega cache’owaniu 3 różnych wersji

IIS Cache

Page 39: Optymalizacja aplikacji ASP.NET

Podobnie jak IIS, ale VaryByParam może być custom Np. kontekst użytkownika

Fragment Cache – OutputCache w User Control Substitution Cache

Cała strona cache’owana, oprócz <asp:Substitution>

Cache ASP.NET

<body>Cached time: <%= DateTime.Now.ToString() %><br />Page time:<asp:Substitution ID="sub" runat="server" MethodName="SubTime" /></body>

    public static string SubTime(HttpContext context)    {        return DateTime.Now.ToString();    }

Page 40: Optymalizacja aplikacji ASP.NET

Plik Baza danych

Konieczne włączenie Service Broker

Cache Dependency

        CacheDependency depend = new CacheDependency(this.MapPath("~/depend.txt"));        this.CachePolicy.Dependency = depend;

        using (SqlConnection conn = new SqlConnection(cs))        {            string sql = "dbo.GetInfo";            using (SqlCommand cmd = new SqlCommand(sql, conn))            {                cmd.CommandType = CommandType.StoredProcedure;                conn.Open();                SqlCacheDependency dep = new SqlCacheDependency(cmd);                mygrid.DataSource = cmd.ExecuteReader();                mygrid.DataBind();                this.Response.AddCacheDependency(dep);            }        }

// SqlDependency.Start() w web.config

Page 41: Optymalizacja aplikacji ASP.NET

Np. AppFabric

Cache Provider

    public class MemoryCacheProvider : OutputCacheProvider    {        public override object Add(string key, object entry, DateTime utcExpiry)        {            object result = HttpRuntime.Cache[key];            if (result == null)            {                this.Set(key, entry, utcExpiry);                result = entry;            }            return result;        }

        public override object Get(string key)        {            return HttpRuntime.Cache[key];        }

        public override void Remove(string key)        {            HttpRuntime.Cache.Remove(key);        }

        public override void Set(string key, object entry, DateTime utcExpiry)        {            HttpRuntime.Cache.Insert(key, entry, null, utcExpiry,                Cache.NoSlidingExpiration, CacheItemPriority.High, null);        }    }

  <system.web>    <caching>      <outputCache>        <providers>          <add name="MemoryCacheProvider" type="Samples.MemoryCacheProvider" />        </providers>      </outputCache>    </caching>  </system.web>

Page 42: Optymalizacja aplikacji ASP.NET

Kiedy chcemy jak najwięcej, jak najdłużej (na ile pamięć pozwoli)

Omijamy logikę Cache Policy - szybkość Nie dla web farm WeakReference i GC

Statyczne pola – uproszczony cache

public static class Weak{    public static WeakReference MyItem { get; set; }    public static readonly Object lockObject = new Object();

    public static DataSet WeakData()    {        DataSet ds = null;        lock (lockObject)        {            if (MyItem != null)                ds = MyItem.Target as DataSet;            if (ds == null)            {                ds = new DataSet();                MyItem = new WeakReference(ds);            }        }        return ds;    }}

Page 43: Optymalizacja aplikacji ASP.NET

IIS

Page 44: Optymalizacja aplikacji ASP.NET

Domyślnie – jeden proces w3wp.exe AppPool - kilka procesów dla kolekcji aplikacji

(Web Garden) Maximum Worker Processes

AppPool Recycling Tracimy cache, inproc session, static, … Domyślnie 29h Warto zmienić na określoną godzinę lub

liczbę obsłużonych requestów (NLB)

App Pool

Page 45: Optymalizacja aplikacji ASP.NET

Wiele AppPools Dzielenie aplikacji na kilka puli Błąd w jednej puli nie ma wpływu na działanie drugiej Np. obsługa magazynu i aplikacja dla konsumentów

Web Garden Ochrona przed błędami procesu w3wp – kilka procesów Uwaga - context-switching między wątkami szybszy niż między

procesami! Konieczna duplikacja w pamięci danych takich jak cache czy pola

static Zalecane max 1-2 worker procesy / CPU core Tylko, kiedy najistotniejsza ciągłość pracy

Kiedy najważniejsze jest działanie

Page 46: Optymalizacja aplikacji ASP.NET

ASP.NET – ISAPI Extension Duplikacja funkcjonalności

2 odrębne pipeline’y Np. Logowanie, auth -> IIS, później ASP.NET Trudniejsza konfiguracja

IIS6 + ASP.NET (Classic mode)

Authentication

Basic NTLM Anon

...

DetermineHandler

...

SendResponse

HTTP Request

HTTP Response

CGI

Static File

ISAPI

Compression

Log

aspnet_isapi.dll

Authentication

MapHandler

Forms Windows

...ASPX

Trace

...

...

Page 47: Optymalizacja aplikacji ASP.NET

Wyodrębnione komponenty z w3core.dll Niezależne dodawanie / usuwanie

funkcjonalności Np. auth – możemy dać własne zamiast

basic / LDAP, itp.. Lekki core

Architektura IIS 7

Authentication

...

ExecuteHandler

...

SendResponse

HTTP Request

HTTP Response

Authorization

UpdateCache

ResolveCache

Authentication

...

DetermineHandler

...

SendResponse

HTTP Request

HTTP Response

BasicNTLM Anon

CGI

Static File

ISAPI

Log Compression

UrlAuthz

OutputCache

Forwarder

Basic40+

Page 48: Optymalizacja aplikacji ASP.NET

Handler HTTP Mapowany dla rozszerzeń (np. aspx) Migracja z html do dynamic ->

można dodać mapowanie htm naASP.NET

Moduły HTTP ASP.NET bezpośrednio w IIS Pipeline Np. sesja, uwierzytelnianie

dla zawartości statycznej, php, itp. Rozszerzalność przez .NET,

a nie ISAPI (C++) Usuwać niepotrzebne moduły Kolejność typów dokumentów w IIS

IIS 7 + ASP.NET - Integrated Pipeline

ISAPI

Authentication

...

ExecuteHandler

...

SendResponse

Authorization

UpdateCache

ResolveCache

HTTP Request

HTTP Response

Anon

aspnet_isapi.dll

Authentication

MapHandler

...

...

Forms Windows

ASPX

Trace

...

Basic

Compression

Log

Static File

Page 49: Optymalizacja aplikacji ASP.NET

Nowe w Windows Server 2008 Rezerwacja minimalnej ilości pamięci lub CPU dla

procesów

Windows System Resource Manager

Page 50: Optymalizacja aplikacji ASP.NET

Bezpieczeństwo i mniej pobieranych danych X-Powered-By – IIS Nagłówek Server – tylko z kodu Etag – przy web farm można wyłączyć (często jest różny dla node’ów) X-Aspnet-Version - <httpRuntime enableVersionHeader="false"/>

Usuwanie nagłówków

    public class HttpHeaderCleanup : IHttpModule    {        public void Init(HttpApplication context)        {            context.PreSendRequestHeaders += OnPreSendRequestHeaders;        }

        void OnPreSendRequestHeaders(object sender, EventArgs e)        {            HttpResponse response = HttpContext.Current.Response;            response.Headers.Remove("Server");            response.Headers.Remove("ETag");        }

        public void Dispose()        {        }    }

Page 51: Optymalizacja aplikacji ASP.NET

3-5% więcej CPU, ale generalnie warto Gzip (domyślny) lub deflate (mniejszy nagłówek)

Kompresja

<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files" dynamicCompressionDisableCpuUsage="100">  <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" dynamicCompressionLevel="5" />  <scheme name="deflate" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" dynamicCompressionLevel="5" />  <staticTypes>    <add mimeType="text/*" enabled="true" />    <add mimeType="message/*" enabled="true" />    <add mimeType="application/x-javascript" enabled="true" />    <add mimeType="*/*" enabled="false" />  </staticTypes>  <dynamicTypes>    <add mimeType="text/*" enabled="true" />    <add mimeType="message/*" enabled="true" />    <add mimeType="application/x-javascript" enabled="true" />    <add mimeType="*/*" enabled="false" />  </dynamicTypes></httpCompression>

Page 52: Optymalizacja aplikacji ASP.NET

Lepsze dla SEO http.sys nawet przy querystringach Krótsze URLe w odpowiedziach Ukrywa technologię aplikacyjną IIS (URL Rewrite) lub ASP.NET

URL Rewriting

Page 53: Optymalizacja aplikacji ASP.NET

Pamiętać o botach (robots.txt, sitemap.xml)

Bandwidth Throttling Moduł BitRateThrottling do IIS Głównie dla mediów, ale również

można do innych typów danych Np. wolniej dla botów lub kawałek

dużego zdjęcia szybko

Szybkość transferu public class Throttle : IHttpModule    {        public void Init(HttpApplication context)        {            context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;        }

        void OnPostRequestHandlerExecute(object source, EventArgs e)        {            HttpApplication application = (HttpApplication)source;            HttpContext context = application.Context;            HttpResponse response = context.Response;            if (response.ContentType == "application/x-zip-compressed")            {                HttpRequest request = context.Request;                if (!String.IsNullOrEmpty(request.ServerVariables["SERVER_SOFTWARE"]))                {                    request.ServerVariables["ResponseThrottler-InitialSendSize"] = "20";                    request.ServerVariables["ResponseThrottler-Rate"] = "10";                }            }        }

        public void Dispose()        {        }    }

Page 54: Optymalizacja aplikacji ASP.NET

ASP.NET

Page 55: Optymalizacja aplikacji ASP.NET

Tradycyjne przetwarzanie żądań“thread-per-request” a.k.a. “post office”

Thread pool

Żądania

BusyBusy Busy Busy

Przy load testach – zużycie CPU niewielkie, ale długi czas odpowiedzi

Page 56: Optymalizacja aplikacji ASP.NET

Przetwarzanie asynchronicznea.k.a. “restaurant”

Żądania

Thread pool

Page 57: Optymalizacja aplikacji ASP.NET

Domyślnie 12 / CPU Czasem zwiększenie liczby pomaga

Wiąże się z tym koszt (start, pamięć, context switch) Ważniejsza optymalizacja istniejących

Blokujące wywołania – baza, zewnętrzne usługi, I/O Przy obliczeniach (CPU) nie pomoże!

Worker Threads

Page 58: Optymalizacja aplikacji ASP.NET

Domyślnie przetwarzanie synchroniczne (1 wątek całe lifecycle)

Async point

Synchronicznie i asynchronicznie

Page 59: Optymalizacja aplikacji ASP.NET

Asynchronicznie – web forms (APM)<%@ Page Async="true„ AsyncTimeout=„30” Language="C#" AutoEventWireup="true" CodeFile="sql-async.aspx.cs" Inherits="sql_async" %>

    public const string ConnString = "Data Source=.;Integrated Security=True;Async=True";

    protected void Page_Load(object sender, EventArgs e)    {        PageAsyncTask pat = new PageAsyncTask(BeginAsync, EndAsync, null, null, true);        this.RegisterAsyncTask(pat);    }

    private IAsyncResult BeginAsync(object sender, EventArgs e, AsyncCallback cb, object state)    {        SqlConnection conn = new SqlConnection(ConnString);        conn.Open();        SqlCommand cmd = new SqlCommand("WAITFOR DELAY '00:00:01'", conn);        IAsyncResult ar = cmd.BeginExecuteNonQuery(cb, cmd);        return ar;    }

    private void EndAsync(IAsyncResult ar)    {        using (SqlCommand cmd = (SqlCommand)ar.AsyncState)        {            using (cmd.Connection)            {                int rows = cmd.EndExecuteNonQuery(ar);            }        }    }

Page 60: Optymalizacja aplikacji ASP.NET

Task – zaczyna od razu APM – kolejkuje do async point

Asynchronicznie – web forms (Task)

    public const string ConnString = "Data Source=.;Integrated Security=True;Async=True";

    protected async void Page_PreRender(object sender, EventArgs e)    {        using (SqlConnection conn = new SqlConnection(ConnString))        {            conn.Open();            using (SqlCommand cmd = new SqlCommand("WAITFOR DELAY '00:00:01'", conn))            {                await Task.Factory.FromAsync<int>(cmd.BeginExecuteNonQuery,                                                  cmd.EndExecuteNonQuery, null);            }        }    }

Page 61: Optymalizacja aplikacji ASP.NET

Najlepiej agregować (np. kilka zapytań w procedurze skł.)

Wywoływane równolegle lub jedno po drugim

Asynchroniczne strony – wiele zadań

    protected void Page_Load(object sender, EventArgs e)    { // Dwa pierwsze – równolegle. Następnie BeginAsync3 (ostatni parametr PageAsyncTask)        PageAsyncTask pat = new PageAsyncTask(BeginAsync1, EndAsync1, null, null, true);        this.RegisterAsyncTask(pat);        pat = new PageAsyncTask(BeginAsync2, EndAsync2, null, null, true);        this.RegisterAsyncTask(pat);        pat = new PageAsyncTask(BeginAsync3, EndAsync3, null, null, false);        this.RegisterAsyncTask(pat);    }

Page 62: Optymalizacja aplikacji ASP.NET

Czasem potrzeba zarejestrowania zadania po zakończeniu poprzedniego Np. drugie wywołanie usługi po zakończeniu poprzedniego Konieczne wywołanie ExecuteRegeisteredAsyncTasks() Można także wywołać, aby wymusić wywołanie przed async point

Dla Task – zwyczajnie jeden po drugim z await

Późniejsza rejestracja

    private void EndAsync(IAsyncResult ar)    {        using (SqlCommand cmd = (SqlCommand)ar.AsyncState)        {            using (cmd.Connection)            {                int rows = cmd.EndExecuteNonQuery(ar);            }        }        PageAsyncTask pat = new PageAsyncTask(BeginAsync2, EndAsync2, null, null, true);        this.RegisterAsyncTask(pat);        this.ExecuteRegisteredAsyncTasks();    }

Page 63: Optymalizacja aplikacji ASP.NET

Usługi Pliki

Inne operacje

    protected async void Page_Load(object sender, EventArgs e)    {        var terra = new TerraServiceSoapClient();        Place place = new Place()        {            City = "Seattle",            State = "WA",            Country = "US"        };        var result = await terra.GetPlaceFactsAsync(place);        PlaceFacts facts = result.Body.GetPlaceFactsResult;        this.LA.Text = String.Format("Latitude: {0:0.##}", facts.Center.Lat);        this.LO.Text = String.Format("Longitude: {0:0.##}", facts.Center.Lon);    }

// Dla web requestów: // var r = WebRequest.Create(„http://…”); // var res = await r.GetResponseAsync();

    private IAsyncResult BeginAsync(object s, EventArgs e, AsyncCallback cb, object s)    {        FileStream fs = new FileStream(this.Server.MapPath("csg.png"),            FileMode.Open, FileAccess.Read, FileShare.Read, 4096,            FileOptions.Asynchronous | FileOptions.SequentialScan);        this.Data = new byte[64 * 1024];        IAsyncResult ar = fs.BeginRead(this.Data, 0, this.Data.Length, cb, fs);        return ar;

// dla podejścia Task: // int size = await fs.ReadAsync(this.Data, 0, this.Data.Length);

    }

Page 64: Optymalizacja aplikacji ASP.NET

Async controller – przed MVC 4    public class WeatherAsyncController : AsyncController    {        private string _forecastUrl = "http://weather.yahooapis.com/forecastjson?w=523920&u=c";

        public void IndexAsync()        {            var webClient = new WebClient();

            AsyncManager.OutstandingOperations.Increment();

            webClient.DownloadStringCompleted += (sender, evt) =>            {                // capture result when web service completes                AsyncManager.Parameters["json"] = evt.Result;                AsyncManager.OutstandingOperations.Decrement();

                // Exception handling - challenge...            };

            // async call            webClient.DownloadStringAsync(new Uri(_forecastUrl));        }

        public ActionResult IndexCompleted(string json)        {            WeatherData weather = new JavaScriptSerializer().Deserialize<WeatherData>(json);            return View("Weather", weather);        }    }

Page 65: Optymalizacja aplikacji ASP.NET

Async controller – MVC 4

    public class WeatherTaskAsyncController : AsyncController    {        private string _forecastUrl = "http://weather.yahooapis.com/forecastjson?w=523920&u=c";

        public async Task<ActionResult> Index()        {            string json = await new WebClient().DownloadStringTaskAsync(_forecastUrl);            WeatherData weather = new JavaScriptSerializer().Deserialize<WeatherData>(json);            return View("Weather", weather);        }    }

Page 66: Optymalizacja aplikacji ASP.NET

Możliwość wywołania kodu równolegle Po wyrenderowaniu strony lub w trakcie

ThreadPool.QueueUserWorkItem() Zużywa wątki z AppPool

1 wątek w tle + kolejka Typowy consumer - producer Np. logowanie (nie wymagane 100% działanie) Bardziej krytyczne zadania – np. Service Broker lub Azure Worker + kolejka

Przy wielu wątkach – warto ReaderWriterLockSlim EnterReadLock / EnterWriteLock (mniejsze ryzyko zakleszczenia)

Przykład

Background Worker Thread

Page 67: Optymalizacja aplikacji ASP.NET

InProc Najszybsza, ale load balancer tylko sticky Nie odporna na awarie sprzętu

StateServer Web farm – ok, ale single point of failure

SQL (+SQL Agent do usuwania) Możliwe przyspieszenie przez <sessionState compressionEnabled=„true” /> <%@ Page EnableSessionState=„false” @> - ale i tak update (timeout) <%@ Page EnablesessionState=„ReadOnly” @> - mniej locków; ta sama SP, która odczytuje

Przyspieszyć zapis do logu (podział na dyski, SSD, itp.) PartitionResolver (wybór conn str na podst id sesji) i SessionIDManager (id sesji z

nr maszyny) Mimo wszystko niezbyt skalowalne – patrz: AppFabric session provider

Sesja

Page 68: Optymalizacja aplikacji ASP.NET

Control ID

Możliwość kontroli identyfikatorów (klienckich) kontrolek serwerowych

Control.ClientIdMode Legacy Static Predictable Inherit (domyślne dla kontrolek)

Kolekcje – ClientIDRowSuffix Dla całej strony lub kontrolki Ostrożnie – duplikacja, długość

Page 69: Optymalizacja aplikacji ASP.NET

Możliwość zmiany markupu kontrolki serwerowej Np. GridView nie z tabelkami, ale na floatach

Control Adapter

    // Zamiana URL w Image na małe litery

public class ImageControlAdapter : WebControlAdapter    {        public ImageControlAdapter()        {        }

        protected override void BeginRender(System.Web.UI.HtmlTextWriter writer)        {            Image image = Control as Image;            if ((image != null) && !String.IsNullOrEmpty(image.ImageUrl))            {                if (!image.ImageUrl.StartsWith("http") && !image.ImageUrl.StartsWith("data:"))                {                    image.ImageUrl = this.Page.ResolveUrl(image.ImageUrl).ToLower();                }            }            base.BeginRender(writer);        }    }

// adapter.browser

<browsers>  <browser refID="Default">    <controlAdapters>      <adapter controlType="System.Web.UI.WebControls.Image"               adapterType="Samples.ImageControlAdapter" />      <adapter controlType="System.Web.UI.WebControls.Panel"               adapterType="Samples.NoIdControlAdapter" />      <adapter controlType="System.Web.UI.WebControls.Label"               adapterType="Samples.NoIdControlAdapter" />      <adapter controlType="System.Web.UI.WebControls.HyperLink"               adapterType="Samples.NoIdControlAdapter" />    </controlAdapters>  </browser></browsers>

Page 70: Optymalizacja aplikacji ASP.NET

Np. wyłączenie domyślnego generowania ID

Control Adapter c.d. – usuwanie ID

public class NoIdControlAdapter : WebControlAdapter    {        protected override void Render(HtmlTextWriter writer)        {            PageBase page = this.Page as PageBase;            if ((page != null) && page.RemoveIds &&                 (this.Control.ClientIDMode != ClientIDMode.Static))            {                HtmlTextWriter noIdwriter = new NoIdHtmlWriter(writer);                base.RenderBeginTag(noIdwriter);                base.RenderContents(writer);                base.RenderEndTag(noIdwriter);            }            else            {                base.Render(writer);            }        }    }

    public class NoIdHtmlWriter : HtmlTextWriter    {        public NoIdHtmlWriter(TextWriter writer)            : base(writer)        {        }

        public override void AddAttribute(HtmlTextWriterAttribute key, string value)        {            if (key != HtmlTextWriterAttribute.Id)                base.AddAttribute(key, value);        }    }

public class PageBase : Page{    protected override void OnInit(EventArgs e)    {        base.OnInit(e);        this.RemoveIds = true;    }

    public bool RemoveIds { get; set; }}

Page 71: Optymalizacja aplikacji ASP.NET

Np. do zwracania zapisanego w bazie HTML Bez cyklu życia ASP.NET – szybciej Asynchronicznie – uczestniczy w każdym żądaniu! IsReusable – czy jedna instancja może być dla wielu żądań

HTTP Handler w .NET

<%@ WebHandler Language="C#" Class="Handler" %>

using System;using System.Data;using System.Web;using System.Data.SqlClient;

public class Handler : IHttpAsyncHandler {    public const string ConnString =         "Data Source=.;Initial Catalog=Sample;Integrated Security=True;Async=True";    HttpContext Context { get; set; }

    public void ProcessRequest(HttpContext context)    {    }

    public IAsyncResult BeginProcessRequest(HttpContext context,        AsyncCallback cb, object extraData)    {        this.Context = context;        int fileid = 0;        string id = context.Request.QueryString["id"];        if (!String.IsNullOrEmpty(id))            fileid = Convert.ToInt32(id);        SqlConnection conn = new SqlConnection(ConnString);        conn.Open();        SqlCommand cmd = new SqlCommand("GetHtml", conn);        cmd.CommandType = CommandType.StoredProcedure;        cmd.Parameters.Add("fileId", SqlDbType.Int).Value = fileid;        IAsyncResult ar = cmd.BeginExecuteReader(cb, cmd);        return ar;    }

    public void EndProcessRequest(IAsyncResult ar)    {        using (SqlCommand cmd = (SqlCommand)ar.AsyncState)        {            using (cmd.Connection)            {                SqlDataReader reader = cmd.EndExecuteReader(ar);                while (reader.Read())                {                    Context.Response.Write(reader["Html"]);                }            }        }        Context.Response.ContentType = "text/html";    }        public bool IsReusable    {        get        {            return false;        }    }}

Page 72: Optymalizacja aplikacji ASP.NET

Krótsze adresy w kodzie http.sys caching Łączenie np. z ASP.NET MVC (MapRoute /

MapPageRoute)

Routing

        protected void Application_Start(object sender, EventArgs e)        {            RouteTable.Routes.Add("Category", new Route("{category}", new PageRouteHandler("~/1.3 Routing.aspx")));            RouteTable.Routes.Add("CategoryAndPage", new Route("{category}/{page}", new PageRouteHandler("~/1.3 Routing.aspx")));        }

        protected void Page_Load(object sender, EventArgs e)        {            lblCategory.Text = RouteData.Values["category"] as string;            lblPage.Text = RouteData.Values["page"] as string;        }  

Page 73: Optymalizacja aplikacji ASP.NET

Klasyczne this.Response.Redirect("~/pages/error.aspx", true);

True – czy zakończyć request this.Response.RedirectPermanent this.Response.RedirectToRoute

Server.Transfer(„~/pages/error.aspx”, false) False – czy przekazać parametry z obecnej strony True – dla ViewState wyrzuci exception; tylko dla

querystring Cross-page postback

Przekierowania

<asp:Button runat="server" PostBackUrl="~/pages/otherpage.aspx" Text="Submit" />

Page 74: Optymalizacja aplikacji ASP.NET

Zawartość z Render() buforowana i zwracana na koniec Dla długotrwałych zadań może wymagać czasu

Najlepiej – z Ajax Response.Flush() – wysłanie odpowiedzi z bufora

Przed Render() nic w nim nie ma!

Wczesny flush odpowiedzi

Page 75: Optymalizacja aplikacji ASP.NET

W OnPreRender Ręczny zapis odpowiedzi Wyrenderowanie wybranych kontrolek serwerowych Usunięcie wyrenderowanych kontrolek (aby nie było duplikacji po

Render) Długotrwałe zadanie w OnPreRender (async) lub

synchronicznie po PreRender (po async point)

Wczesny flush – c.d.

<html><head id="Head1" runat="server">    <title></title>    <script type="text/javascript" src="test.js"></script></head><body>    <form id="form1" runat="server">    <div>    <asp:Label runat="server" ID="test" Text="testing" />    </div>    </form></body></html>

    protected override void OnPreRender(EventArgs e)    {        base.OnPreRender(e);        this.Response.Write("<!DOCTYPE html PUBLIC " +            "\"-//W3C//DTD XHTML 1.0 Transitional//EN\" " +            "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n");        this.Response.Write("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n");        HtmlTextWriter writer = this.CreateHtmlTextWriter(this.Response.Output);        this.Header.RenderControl(writer);        writer.Flush();        this.Response.Flush();        this.Controls.Remove(this.Header);        Thread.Sleep(2000);    }

Page 76: Optymalizacja aplikacji ASP.NET

Wczesny flush - odpowiedźHTTP/1.1 200 OKCache-Control: privateTransfer-Encoding: chunkedContent-Type: text/html; charset=utf-8Server: Microsoft-IIS/7.5X-AspNet-Version: 4.0.30319X-Powered-By: ASP.NETDate: Fri, 03 Feb 2012 12:16:11 GMT161<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head id="Head1"><link href="App_Themes/mkt/common.css" type="text/css" rel="stylesheet" /><title>Testing</title><script type="text/javascript" src="test.js"></script></head>146<body><form name="form1" method="post" action="flush1.aspx" id="form1"><div><input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"value="/wEPDwULLTE0NDMxNDM0MTlkZA0loE+taD1AMKhxDNDZZLZADwxZqGnPPbIF8Mylq4PV" /></div><div><span id="test">testing</span></div></form></body></html>0

Page 77: Optymalizacja aplikacji ASP.NET

Minification dla dynamicznych    public class MinifyStream : Stream    {        private StreamWriter Writer { get; set; }        private Decoder Utf8Decoder { get; set; }            public MinifyStream(Stream stream)        {            this.Writer = new StreamWriter(stream, Encoding.UTF8);            this.Utf8Decoder = Encoding.UTF8.GetDecoder();        }

        public override void Write(byte[] buffer, int offset, int count)        {            int characterCount = this.Utf8Decoder.GetCharCount(buffer, offset, count);            char[] result = new char[characterCount];            int decodedCount = this.Utf8Decoder.GetChars(buffer, offset, count, result, 0);            if (decodedCount <= 0)                return;            // ... wyfiltrowanie zbędnych znaków i zapis do this.Writer        }

        public override void Close()        {            this.Writer.Flush();            this.Writer.Close();            base.Close();        }

        public override void Flush()        {            this.Writer.Flush();        }

        // ...    }

    // W module HttpModule

private void Sample_PostRequestHandlerExecute(Object source, EventArgs e)    {        HttpApplication application = (HttpApplication)source;        HttpResponse response = application.Context.Response;        if(response.ContentType == "text/html")        response.Filter = new MinifyStream(response.Filter);    }

Page 78: Optymalizacja aplikacji ASP.NET

Page.IsPostBack Rozpoznawanie odświeżenia strony

Unikanie niepotrzebnej pracy

        protected virtual bool IsRefresh        {            get            {                return this.Request.Headers["Pragma"] == "no-cache" ||                this.Request.Headers["Cache-Control"] == "max-age=0";            }        }

Page 79: Optymalizacja aplikacji ASP.NET

Response.IsClientConnected Czy przeglądarka jeszcze czeka na odpowiedź, czy została

zamknięta? Warto sprawdzić przed długotrwałą operacją na bazie

<compilation batch=true /> True – niewiele pakietów False – każda strona w osobnym assembly (wolniej, ale łatwiej

zmiany) Wyłączyć debug mode!

Można wymusić w machine.config

Inne

<system.web>  <deployment retail="true" /></system.web>

Page 80: Optymalizacja aplikacji ASP.NET

Auto-start aplikacji

//applicationHost.config

<serviceAutoStartProviders> <add name="PrewarmMyCache" type="MyNamespace.CustomInitialization, MyLibrary" /></serviceAutoStartProviders>

public class CustomInitialization : System.Web.Hosting.IProcessHostPreloadClient{ public void Preload(string[] parameters) { // Nasza inicjalizacja. }}

Dawniej – Global.asax i Application_Load IIS 7.5 i Windows Server 2008 R2

Page 82: Optymalizacja aplikacji ASP.NET

© 2012 Microsoft Corporation. Wszelkie prawa zastrzeżone. Microsoft, Windows oraz inne nazwy produktów są lub mogą być znakami towarowymi lub zastrzeżonymi znakami towarowymi firmy Microsoft w Stanach Zjednoczonych i innych krajach. Zamieszczone informacje mają charakter wyłącznie informacyjny. FIRMA MICROSOFT NIE UDZIELA ŻADNYCH GWARANCJI (WYRAŻONYCH WPROST LUB DOMYŚLNIE), W TYM TAKŻE USTAWOWEJ RĘKOJMI ZA WADY FIZYCZNE I PRAWNE, CO DO INFORMACJI ZAWARTYCH W TEJ PREZENTACJI.