BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

ASP.NET MVC klaidų apdorojimas iš JavaScript

Parašė Sergejus | 2010-07-27 22:23

Šiais laikais praktiškai sunku įsivaizduoti Web aplikaciją, kuri nenaudotų Ajax technologijos. Vienas iš tradicinių Ajax panaudojimo pavyzdžių – asinchroninis duomenų pakrovimas (kuris su ASP.NET MVC ir jQuery tapo kaip niekada paprastas). Sakykime, mes turime kontrolerį Home:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult GetList()
    {
        throw new HttpException((int)HttpStatusCode.NotImplemented, "This method is not yet implemented");
    }
}

Ir turime puslapį Index.aspx:

<!DOCTYPE>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>ASP.NET MVC serverinių klaidų apdorojimas iš JavaScript</title>
    <script type="text/javascript" src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.4.1.js"></script>
</head>
<body>
    <div id="list_content"></div>
</body>
<script type="text/javascript">
    $.ajax({
        url: '/Home/GetList',
        cache: false,
        success: function (data) {
            $('#list_content').html(data);
        }
    });
</script>
</html>

Kaip matyti iš pateikto kodo, pasikrauna tuščias puslapis, kuris bando į DIV elementą asinchroniškai pakrauti duomenis kviečiant kontrolerio Home metodą GetList (kuris pas mus išmeta klaidą). Kaip teisingai pagauti ir apdoroti serverio klaidas iš JavaScript?

Pirma reikia papildyti mūsų kontrolerį perrašant metodą OnException (šią logiką aš dažniausiai iškeliu į bazinį kontrolerį):

protected override void OnException(ExceptionContext filterContext)
{
    if (filterContext.HttpContext.Request.IsAjaxRequest())
    {
        var statusCode = filterContext.Exception is HttpException ?
                        (filterContext.Exception as HttpException).GetHttpCode() :
                        (int)HttpStatusCode.InternalServerError;
        filterContext.HttpContext.Response.StatusCode = statusCode;
        filterContext.Result = new JsonResult
        {
            JsonRequestBehavior = JsonRequestBehavior.AllowGet,
            Data = new
            {
                filterContext.Exception.Message,
                filterContext.Exception.StackTrace,
                Url = filterContext.HttpContext.Request.RawUrl
            }
        };
        filterContext.ExceptionHandled = true;
    }
}

Išnagrinėkime kas yra daroma. Pirma patikrinama ar klaida kilo asinchroninio kreipinio metu. Jeigu taip – patikrinama ar tai HTTP klaida ir jeigu ne, suteikiamas HTTP501 (Internal Server Error) klaidos kodas. Galiausiai yra sudaromas JSON atsakymas, kuriame yra talpinama visa su klaida susijusi informacija (galima papildyti pagal poreikį). Tokiu būdu, vykdant asinchroninį kreipinį, kurio metu serveryje įvyksta klaida, mes gauname klaidos JSON objektą. Beliko šį objektą apdoroti HTML pusėje, tam papildysime jQuery ajax metodą:

<script type="text/javascript">
    $.ajax({
        url: '/Home/GetList',
        cache: false,
        success: function (data) {
            $('#list_content').html(data);
        },
        error: function (response) {
            var ex = $.parseJSON(response.responseText);
            alert("HTTP" + response.status + ": " + ex.Message);
        }
    });
</script>

Įvykus klaidai serverio pusėje, mes gauname klaidos JSON objektą (objekto response savybė responseText), kuriuo toliau galime operuoti apdorojant klaidą.

Tiek mano patirties iš ASP.NET MVC klaidų apdorojimo iš JavaScript. O kokius dar “triukus” jus taikote JavaScript klaidų apdorojimui?

Rodyk draugams

Patogesnis darbas su ConfigurationManager klase

Parašė Sergejus | 2010-07-16 23:45

ConfigurationManager yra viena svarbiausių dažniausiai naudojamų .NET klasių, nes būtent ji yra naudojama nustatymų iš web.config ar app.config nuskaitymui. Vienas dalykas kuris paskutiniu metu šioje klasėje man nepatinka – kodo ilgis, reikalingas pasiekti nustatymą iš appSettings skilties ar prisijungimo eilutę iš connectionStrings skilties. Pavyzdžiui, norint nuskaityti nustatymą Name, reikia tokio kodo:

var name = ConfigurationManager.AppSettings["Name"];

PrisijungimoEilutės Connection nuskaitymas reikalauja dar daugiau kodo:

var connection = ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;

Šiandien noriu pasiūlyti jums supaprastiną ConfigurationManager klasę Config. Klasės interfeisus (reikalavimus) aprašiau kaip testus:

[TestClass]
public class ConfigTest
{
    [TestMethod]
    public void NamePropertyReturnsSameValueAsAppSettings()
    {
        var actual = Config.Current.Name;
        var expected = ConfigurationManager.AppSettings["Name"];

        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void ConnectionPropertyReturnsSameValueAsConnectionStringWhenNoAppSettingsWithSameKeyExist()
    {
        var actual = Config.Current.Connection;
        var expected = ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;

        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void DatabasePropertyReturnsSameValueAsAppSettingsEvenWhenConnectionStringWithSameKeyExist()
    {
        var actual = Config.Current.Database;
        var expected = ConfigurationManager.AppSettings["Database"];

        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void NewInstanceOfConfigCanBeCreatedAndUsedToAccessAppSettingsWithKeyName()
    {
        dynamic config = new Config();
        var actual = config.Name;
        var expected = ConfigurationManager.AppSettings["Name"];

        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    [ExpectedException(typeof(RuntimeBinderException))]
    public void NonExistingAppSettingsKeyAndConnectionStringKeyThrowsException()
    {
        var actual = Config.Current.NonExisting;
    }
}

Testams naudojamas toks app.config failas:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="Connection" connectionString="StubConnectionString"/>
    <add name="Database" connectionString="StubDatabaseConnectionString"/>
  </connectionStrings>
  <appSettings>
    <add key="Name" value="Sergejus"/>
    <add key="Database" value="StubDatabaseSetting"/>
    <add key="Age" value="25"/>
  </appSettings>
</configuration>

Kaip matote, pagrindinis noras buvo pasiekti tiek AppSettings, tiek ConnectionStrings nustatymus kaip savybes. Tai ypač lengva realizuoti naudojant C# 4.0 dinaminius objektus:

public class Config : DynamicObject
{
    private static readonly Config config = new Config();

    public static dynamic Current
    {
        get
        {
            return config;
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = ConfigurationManager.AppSettings[binder.Name];

        if (result == null && ConfigurationManager.ConnectionStrings[binder.Name] != null)
        {
            result = ConfigurationManager.ConnectionStrings[binder.Name].ConnectionString;
        }

        return result != null;
    }
}

O ką jus manote apie tokios klasės naudą? Kaip visada laukiu komentarų…

Rodyk draugams

Windows 7 Ultimate nemokamai #4

Parašė Sergejus | 2010-07-16 16:48

IMG_0088

Taisyklės:

Šio straipsnio komentaruose parašykite savo vardą ir pavardę arba slapyvardį. Kiekvieno mėnesio 15-ą bei paskutinę dieną atsitiktine tvarka išrinksiu vieną nugalėtoją, kuriam ir atiteks dovana. Kompaktą galėsiu perduoti tik VILNIUJE ir KAUNE. Jeigu esate iš kito miesto – teks patiems atvažiuoti į Vilnių.

Jeigu akcija patinka, nepamirštame paspausti ‘Patiko’.

Rodyk draugams

Windows 7 Ultimate nemokamai #3 - laimėtojas paaiškėjo

Parašė Sergejus | 2010-07-15 23:22

Prabėjo 15 liepos dienų, o tai reiškia, kad trečią Windows 7 Ultimate diską laimėjo…

image

…skaitytojas vardu Paulius. Prašau susisiekti su manimi ir aš perduosiu prizą. Nenusiminkite kam nepasisekė, nes jau nuo rytojaus turėsite šansą laimėti dar vieną Windows 7 Ultimate DVD diską.

Rodyk draugams

Patogus internal klasių testavimas

Parašė Sergejus | 2010-07-08 20:42

ATNAUJINTA

Jeigu jus turite pilną prieigą ir galimybę papildyti testuojamą rinkinį, gali būti patogiau naudoti ne mano metodą, bet pasiūlytą Sauliaus – atributą InternalsVisibleTo.

 

Šiandien norėjau ištestuoti vieną iš savo klasių, bet pasirodė, jos pasiekiamumo lygis yra internal. Tai reiškia, kad iš kitų rinkinių (angl. assembly) ši klasė tiesiog nėra matoma, kas sukelia pakankamai nemažai nepatogumų norint ją ištestuoti.

Tarkime, mes norime ištestuoti labai paprastą klasę Switch:

internal class Switch
{
    public Switch(bool isOn)
    {
        IsOn = isOn;
    }

    public bool IsOn
    {
        get;
        private set;
    }

    public bool Invert()
    {
        IsOn = !IsOn;
        return IsOn;
    }
}

Vienas iš galimų variantų gali būti tiesiog panaudoti refleksiją (angl. reflection). Tarkime, mes norime ištestuoti ar perdavus į konstruktorių reikšmę true ir vėliau iškvietus savybę IsOn, mes gausime tą pačią reikšmę true. Naudojant paprastą refleksiją testavimo metodas galėtų atrodyti taip:

[TestMethod]
public void SwitchIsOnWhenCreatedWithArgumentSetToTrue()
{
    var assembly = Assembly.GetAssembly(typeof(AnyPublicClass));
    var type = assembly.GetType("Code.Switch");
    var ctor = type.GetConstructor(new[] { typeof(bool) });
    var obj = ctor.Invoke(new object[] { /*is on*/ true });
    var isOn = type.GetProperty("IsOn").GetValue(obj, null);

    Assert.IsTrue(Convert.ToBoolean(isOn));
}

Kaip matote, užrašymas tikrai nėra labai patogus (AnyPublicClass – tai bet kokia public klasė, kuri randasi tame pačiame rinkinyje kaip ir testuojama internal klasė). Kitas galimas variantas, kurį aš atsimyniau iš MVP Summit pristatymų, panaudoti C# 4 dinaminius objektus. Jų pagalba galima „apgaubti“ refleksiją į paprastą metodų ar savybių iškvietimą. Vieną iš tokių objektų realizacijų aš radau David Ebbo tinklaraštyje (klasė PrivateReflectionDynamicObject). Šios klasės pagalba bet kurį objektą (įskaitant ir internal), galima paversti dinaminiu AsDynamic() metodo pagalba. Tam kad būtų dar patogiau, aš aprašiau praplėtimo metodą New(), leidžiantį kurti reikalingo tipo dinaminius objektus:

public static class TestExtensions
{
    private const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Public |
                                       BindingFlags.Static | BindingFlags.Instance;

    public static dynamic New(this Assembly assembly, string typeName, params object[] ctorArgs)
    {
        var type = assembly.GetTypes().First(item => item.Name == typeName);
        var ctorArgTypes = ctorArgs.Select(arg => arg.GetType()).ToArray();
        var ctor = type.GetConstructor(Flags, null, ctorArgTypes, null);

        return ctor != null ? ctor.Invoke(ctorArgs).AsDynamic() : null;
    }
}

Perrašytas testavimo metodas atrodo taip:

[TestMethod]
public void SwitchIsOnWhenCreatedWithArgumentSetToTrue()
{
    var assembly = Assembly.GetAssembly(typeof(AnyPublicClass));
    dynamic obj = assembly.New("Switch", /*is on*/ true);

    Assert.IsTrue(obj.IsOn);
}

Sutinkate, kad metodas atrodo daug paprasčiau? Mes ne tik žymiai paprasčiau galime kurti objektus, bet ir „natūraliai“ kviesti objekto savybę IsOn! Žemiau pateiktas kitas pavyzdys, kuris parodo kaip galima kviesti metodus:

[TestMethod]
public void SwitchStateChangedToOffWhenInvertCalledForOn()
{
    var assembly = Assembly.GetAssembly(typeof(AnyPublicClass));
    dynamic obj = assembly.New("Switch", /*is on*/ true);

    obj.Invert();
    Assert.IsFalse(obj.IsOn);
}

C# 4 dinaminių objektų pagalba kaip niekada paprastai ir patogiai mes galime testuoti internal klases!

P.S.

Be abejo visas pateiktas kodas tinka ir private klasių testavimui, kitas klausimas ar tikrai logiška testuoti private klases..

P.P.S.

Visą kodą galite rasti ir mano SBToolkit projekte.

Rodyk draugams

Bazinė Windows servisų klasė

Parašė Sergejus | 2010-07-07 18:31

Paskutiniu metu darbe teko nemažai rašyti įvairiausius Windows servisus (integracinius, asinchroninių užduočių ir pan.). Kaip ir daugelis jūsų, tam aš turėjau nuosavą bazinę klasę, kuri tebuvo .NET Framework klasės ServiceBase abstrakcija. Prieš kelias savaites nusprendžiau iš esmės perrašyti savo bazinę klasę, kad apart standartinio ServiceBase funkcionalumo, ji spręstų dvi pagrindines Windows servisų problemas:

  • Galimybė paleisti kodą tiek kaip konsolinę aplikaciją (derinimui), tiek kaip Windows servisą
  • Galimybė instaliuoti / išinstaliuoti servisą nesinaudojant pagalbine InstallUtil programa, o tiesiog nurodant parametrus servisas.exe /install ir servisas.exe /uninstall.

Viso sprendimo kodo nekomentuosiu (jis pakankamai aiškus), tik pažymėsiu kelias įdomias vietas:

Pavyzdinis Windows servisas galėtų atrodyti taip:

public class TestService : WindowsServiceBase
{
    public const string Name = "TestService";

    public TestService() : base(Name)
    {
    }

    private static void Main(string[] args)
    {
        Run<TestService>(args);
    }

    protected override void Execute()
    {
        Console.WriteLine("Bus spausdinama kas 60 sek.");
    }
}

Pavyzdinio Windows serviso diegėjas:

[RunInstaller(true)]
public class Installer : WindowsServiceInstaller
{
    public Installer() : base(TestService.Name)
    {
    }
}

Bazinės WindowsServiceBase klasės kodas pateiktas žemiau:

public abstract class WindowsServiceBase : ServiceBase
{
    private readonly Timer timer;

    private int isWorking;

    protected WindowsServiceBase(string name)
    {
        if (String.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException("name");
        }

        timer = new Timer(PollInterval) { AutoReset = true };
        timer.Elapsed += (sender, args) => DoExecute();

        ServiceName = name;
        CanPauseAndContinue = false;
    }

    protected ILogger Logger { get; set; }

    protected int PollInterval
    {
        get
        {
            return Int32.Parse(ConfigurationManager.AppSettings["PollInterval"] ?? "60") * 1000;
        }
    }

    protected abstract void Execute();

    protected override void OnStart(string[] args)
    {
        Logger.Information("Starting service " + ServiceName);

        base.OnStart(args);
        timer.Start();
    }

    protected override void OnStop()
    {
        Logger.Information("Stopping service " + ServiceName);

        base.OnStop();
        timer.Stop();

        CleanUp();
    }

    protected static void Run<TService>(params string[] args) where TService : WindowsServiceBase, new()
    {
        var service = new TService();

        if (args != null && args.Length > 0)
        {
            for (int i = 0; i < args.Length; i++)
            {
                args[i] = args[i].Trim().ToLowerInvariant();
            }

            var exeName = Path.GetFileName(Assembly.GetCallingAssembly().Location);
            if (args.Contains("uninstall"))
            {
                using (var installer = new AssemblyInstaller(exeName, null) { UseNewContext = true })
                {
                    installer.Uninstall(null);
                    return;
                }
            }
            if (args.Contains("install"))
            {
                using (var installer = new AssemblyInstaller(exeName, null) { UseNewContext = true })
                {
                    installer.Install(null);
                    installer.Commit(null);
                    return;
                }
            }
        }

        if (Environment.UserInteractive)
        {
            service.OnStart(args);

            Console.WriteLine("Service is running... Press Ctrl+C to exit.");

            while (true)
            {
                // loop to execute service, sleep for 0.1 second to save CPU ticks
                Thread.Sleep(100)
            }
        }
        else
        {
            Run(service);
        }
    }

    protected virtual void CleanUp()
    {
        timer.Dispose();
    }

    private void DoExecute()
    {
        if (Interlocked.Exchange(ref isWorking, 1) == 0)
        {
            Logger.Information("Execution process started...");
            try
            {
                Execute();
            }
            catch (Exception e)
            {
                Logger.Error(e.Message, e);
            }
            Logger.Information("Execution process completed...");

            Interlocked.Exchange(ref isWorking, 0);
        }
    }
}

Čia naudojamas bendro pobūdžio interfeisas ILogger:

public interface ILogger
{
    void Information(string message);
    void Warning(string message);
    void Error(string message);
    void Error(string message, Exception ex);
}

Bazinė WindowsServiceInstaller klasė atrodo taip:

public class WindowsServiceInstaller : Installer
{
    private readonly ServiceProcessInstaller serviceProcessInstaller;

    private readonly ServiceInstaller serviceInstaller;

    public WindowsServiceInstaller(string name)
    {
        serviceProcessInstaller = new ServiceProcessInstaller
        {
            Account = ServiceAccount.LocalSystem,
            Password = null,
            Username = null,
        };

        serviceInstaller = new ServiceInstaller
        {
            DisplayName = name,
            ServiceName = name,
            StartType = ServiceStartMode.Automatic
        };

        Installers.AddRange(new Installer[]
            {
                serviceProcessInstaller,
                serviceInstaller
            });
    }
}

Tokia nesudėtinga bazinė klasė leido man žymiai efektyviau ir patogiau kurti bei naudoti Windows servisus, tikiuosi pravers ir jums. Kaip visada, laukiu jūsų pastabų / siūlymų / komentarų.

P.S.

WindowsServiceBase ir WindowsServiceInstaller kodą galite rasti ir mano SBToolkit projekte.

Rodyk draugams

Lietuvos .NET vartotojų grupės susitikimo skaidrės

Parašė Sergejus | 2010-07-06 14:28

Vaidas jau patalpino savo pristatymo skaidres iš realaus Scrum naudojimo įmonėse. Papildomai, skaidrės papildytos nuorodų sąrašu, kas padės plačiau susipažinti su Scrum.

Rodyk draugams

Treti metai kaip MVP

Parašė Sergejus | 2010-07-02 08:17

Vakar, Lietuvos .NET vartotojų grupės susitikimo metu Kaune, gavau labai lauktą laišką:

Dear Sergejus Barinovas,
Congratulations! We are pleased to present you with the 2010 Microsoft® MVP Award! This award is given to exceptional technical community leaders who actively share their high quality, real world expertise with others. We appreciate your outstanding contributions in Visual C# technical communities during the past year.

 

Taip pat noriu pasveikinti naują Lietuvos MVP - Tomą Dabašinską, vieną iš mugit.lt įkūrėjų, Microsoft Student Partner bei aktyvų Microsoft Answers forumų dalyvį. Tomo MVP apdovanojimo sritis - Windows Desktop Experience. Nuoširdžiai sveikinu Tomą, welcome to the club!

Rodyk draugams

Windows 7 Ultimate nemokamai #3

Parašė Sergejus | 2010-07-01 09:00

IMG_0088

Taisyklės:

Šio straipsnio komentaruose parašykite savo vardą ir pavardę arba slapyvardį. Kiekvieno mėnesio 15-ą bei paskutinę dieną atsitiktine tvarka išrinksiu vieną nugalėtoją, kuriam ir atiteks dovana. Kompaktą galėsiu perduoti tik VILNIUJE ir KAUNE. Jeigu esate iš kito miesto – teks patiems atvažiuoti į Vilnių.

Jeigu akcija patinka, nepamirštame paspausti ‘Patiko’.

Rodyk draugams