Golyóálló Ajax hívások

Korábban írtam már arról, hogy nagyon egyszerűen tudunk ASP.NET-es page method-okat hívni jQuery segítségével. No, de mi van akkor, ha valamilyen nem várt hiba történik közben?

Nézzük lépésről lépésre, kezdjük először azzal, hogy hogyan is megy ez. Először is legyen egy link, ami elindítja az Ajax hívást, és egy span, ami majd megjeleníti az eredményt:

  <a href="#" id="simpleLink">Egyszerű Ajax hívás</a>
  <span id="result"></span>

Legyen egy WebMethod az oldalon belül, amit ajaxosan meg akarunk hívni a linkre kattintáskor:

  [WebMethod]
  public static string SayHello( string name )
  {
    return "Hello " + name;
  }

Az Ajax híváshoz persze jQuery-t használunk, szerencsére így nagyon rövid a JavaScript kód:

  $("#simpleLink").click(function (e) {
    e.preventDefault();

    $.ajax({
      type: "POST",
      url: "Default.aspx/SayHello",
      data: "{ 'name':'World' }",
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      success: function (data) {
        $("#result").html(data.d);
      }
    });
  });

Hol lehet ezzel probléma? Néhány tipikus eset:

  • Bármiféle alacsonyszintű (hálózati) hiba keletkezik.
  • A szerver oldali kódból kivétel röppen a kliensre.
  • Lejár a felhasználó login sessionje, ezért az Ajax hívás authentikációs hibával száll el, mert nem jut el a szerver oldali végpontig.
  • Lejár a felhasználó munkamenete, ezért a szerver oldali kód érvénytelen Session értékekkel dolgozik.

Ezek között van könnyebb és nehezebb eset. Az alacsonyszintű hibákat lehet kezelni egy error callbackben kliens oldalon, szerencsére a jQuery támogatja.

A szerver oldalon keletkező kivétel már rosszabb, mert ennyire barátságos válaszból kell dolgoznia a kliensnek, ami egy HTTP 500 Internal Server Error társaságában jön vissza:

  {
    "Message":"Baj van!",
    "StackTrace":"   at _Default.SayHello(String name) 
       in w:\\System\\Desktop\\AjaxWrapperSample\\Default.aspx.cs:line 14",
    "ExceptionType":"System.InvalidOperationException"
  }

A példában itt egy “nem várt” eset szerepel (hiszen az InvalidOperationException általában erre utal), de mihez kezdünk akkor, ha az üzleti logikánk is kivétellel jelzi, ha valamit nem sikerült végrehajtani? Például mert a felhasználónak nincs hozzá jogosultsága, vagy megváltozott az üzleti objektum állapota, karbantartás alatt van az alkalmazásszerver stb.

A lejárt login session kifejezetten kellemetlen, mert klasszikus HTTP 200 OK tud visszajönni, csak éppen a tartalomban nem a kért adatokat küldi vissza a szerver, hanem a bejelentkező oldalunk HTML markupját. Talán erre számít legkevésbé az ember.

Ezeket természetesen mind lehet kezelni szerver és kliens oldalon is, a nehézséget az jelenti, hogy ezt minden egyes Ajax hívásnál meg kell tennünk kliens és szerver oldalon is. Célszerű lenne olyan megoldást találnunk, ami nem igényli az összes WebMethodunk és az összes kliens oldali hívásunk szétbarmolását, hanem központilag tudjuk valahogy kezelni a hibákat. És itt jön be a képbe az a szemérmetlenül kellemetlen tény, hogy Ajax hívások esetén az ASP.NET klasszikus központi eseménykezelői (pl. Application_Error) nem hívódnak meg…

Az alábbi megoldást az egyik projektünkben használjuk és eddig bevált. A módszer lényege, hogy az eredeti kódot becsomagoljuk és a csomagolás feladata a részletek elrejtése mind kliens, mind szerver oldalon.

Első lépésként definiáltuk az alábbi osztályt:

  public class AjaxResult<TResult>
  {
    public bool Success { get; set; }
    public TResult Value { get; set; }
    public string Error { get; set; }
  }

Ennek az osztálynak a feladata, hogy burkolja az eredeti választ a Value paraméterben és további információkat csapjon hozzá, amit a kliens felhasználhat az egyedi hibakezeléshez. Itt a példában csak két paramétert vettem fel, a Success jelzi, ha a hívás szerver oldalon nem okozott kivételt, az Error pedig az esetleges hibaüzenetet (ezeket persze lehetne egyben is, de a bool és a string külön nekem szimpatikusabb).

A következő feladat a szerver oldali csomagolás elkészítése olyan módon, hogy az eredeti kódra a legkisebb hatással legyen. Erre az alábbi megoldás született:

  public static class AjaxWrapper
  {
    public static AjaxResult<TResult> Execute<TResult>( Func<TResult> body )
    {
      try
      {
        // Inicializálás, szerver konfigurálás itt...

        return new AjaxResult<TResult>
        {
          Success = true, 
          Value = body()
        };
      }
      catch( Exception ex )
      {
        return new AjaxResult<TResult>
        {
          Success = false,
          Error = ex.GetType().Name
        };
      }
    }
  } 

Oké, a Func miatt elsőre talán nem egyértelmű, hogy mi történik, ezért gyorsan megmutatom, hogyan kell használni és máris világos lesz a jelentése. A fenti SayHello metódusból ez lett:

  [WebMethod]
  public static AjaxResult<string> SayHelloSafe( string name )
  {
    return AjaxWrapper.Execute( () =>
    {
      return "Hello " + name;
    });
  }

A kód lényegi része tehát megmaradt, mindössze egy AjaxWrapper.Execute hívást kell köré csapni. Siker esetén a lényegi rész eredménye bekerül a Value tulajdonságba, hiba esetén pedig a kivétel típusa kerül az Error tulajdonságba. Ezzel elértük azt, hogy a kódunkban keletkező összes szerver oldali hibát elkapjuk és barátságos formában juttatjuk a kliensre.

Már csak ki kell csomagolnunk a választ kliens oldalon, amivel egyúttal a klasszikus d tulajdonságtól is megszabadulhatunk. Ehhez jó lesz az alábbi JavaScript függvény:

  function myAjax( url, params, successCallback, errorCallback ) {
    $.ajax({
      type: "POST",
      url: url,
      data: params,
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      success: function (response, status, xhr) {
        if (response.d.Success) {
          successCallback(response.d.Value, status, xhr);
        }
        else {
          errorCallback(response.d.Error, status, xhr);
        }
      },
      error: function (xhr, status, error) {
        if (xhr.status === 401) {
          alert("Lejárt a munkameneted, lépj be újra!");
          window.location.href = "Login.aspx";
          return;
        }

        errorCallback(xhr, status, error);
      }
    });            
  }

Itt egyúttal megoldhatjuk a lejárt login session esetét is. Ha szerver oldalon az AjaxWrapper.Execute metódusban kitalálunk egy módszert a lejárt Session kezelésére, akkor ezt az esetet is kezelhetjük itt központilag.

Végezetül csak arra kell figyelnünk, hogy mostantól a $ajax helyett mindenhol ezt a myAjax függvényt használjuk:

  myAjax(
    "Default.aspx/SayHelloSafe",
    "{ 'name':'World' }",
    function (response) {
        $("#result").html(response);
    },
    function (response) {
        $("#result").html('Gáz van: ' + response);
    }
  );

Kicsit egyszerűsítettem a kódon, élesben egy kicsit bonyolultabb, de ez a lényeg. A teljes forráskód letölthető innen.

Ti hogyan kezelitek ezeket a kivételesen kellemetlen eseteket?

 

Technorati-címkék: ,,,,

zip AjaxWrapperSample.zip (77 kB)


Balássy György (MS RD, ASP.NET MVP, MCTS)

Balássy György (MS RD, ASP.NET MVP, MCTS) Villamosmérnök, a BME Automatizálási és Alkalmazott Informatikai Tanszékén webportálok fejlesztését oktatja. 2000 óta foglalkozik a Microsoft .NET platformjával, melynek meghonosításában jelentős szerepet vállalt előadóként, konzulensként és A .NET Framework és programozása című könyv társszerzőjeként. Az MSDN Kompetencia Központon belül a Portál Technológiák Csoport vezetője, szakterülete web alapú rendszerek fejlesztése és üzemeltetése. 2004-ben Magyarországon elsőként kapta meg a Most Valuable Professional címet, majd 2005 óta a Microsoft magyarországi regionális igazgatója. Publikációi a Technet Magazinban, az MSDN Kompetencia Központ honlapján és szakmai blogjában olvashatóak.

2011.09.04. 13:04:17 | Permalink | Hozzászólások: 0 | Tárgyszavak: , , ,


  • PHP-MySQL Alapok Windows Serveren

    Dávid Zoltán A PHP-alapú webes alkalmazások egyik leggyakoribb partnere a MySQL adatbáziskezelő. Ezt használja például a sorozatunk negyedik részében feltelepített Drupal is – és ezt fogjuk használni mi is. A rész célja, hogy bemutassa a MySQL és a PHP-MySQL páros használatát és használhatóságát Windows rendszereken, IIS7 webszerver és FastCGI modul felhasználásával. Tovább »
  • ASP.NET Calendar Postback nélkül

    Dávid Zoltán A Calendar egy klasszikus vezérlő az ASP.NET hőskorából: csak egy kattintás és van egy naptárunk, ami viszont ronda HTML-t generál, és csak Postbackelni tud. Emiatt persze a saját kiválasztott dátumának a tárolásához is ViewSate kell. Én egy olyan Calendarra vágytam, ami az URL-be dobja fel a kiválasztott napott. A megoldást az eredeti kiválasztási mód (SelectionMode) kikapcsolásában és naponként egy új link elhelyezésében találtam meg. Tovább »


Írja meg Ön is véleményét!


Hozzászólásokat csak regisztrált, bejelentkezett felhasználóktól tudunk elfogadni!

Hozzászólások