Webfejlesztés kategória bejegyzései

C# kód futtatása Node.js környezetben Roslyn segítségével

Amikor a Microsoft Managed Languages csapata 2013. decemberében bejelentette, hogy a korábbi C# és Visual Basic fordítót lecserélték, és már a Visual Studio következő verziójának napi buildjei is egy új fordítóval készülnek, hirtelen mindenki számára egyértelművé vált, hogy valami nagyon komoly dolog van készülőben. Az új, “Roslyn” kódnevű eszköz messze túlmutat a korábbi csc.exe és vbc.exe képességein és lehetőségein, nem véletlenül kapta végül a .NET Compiler Platform elnevezést.

Itt ugyanis nem egyszerűen arról van szó, hogy szeretnénk a forráskódunkat futtatható formára alakítani, hiszen arra már sok éve volt kiváló eszközünk. A Roslyn célja, hogy a fordító tudását API-kon keresztül megnyissa a fejlesztők és a fejlesztőkörnyezetek (például a Visual Studio) előtt, azaz egy fordító-mint-szolgáltatás (compiler-as-a-service) megoldásról beszélhetünk.

Miért van erre szükség? Leginkább azért, mert a fordítók nagyon összetett szerkezetek, és futásuk közben igen komoly mennyiségű információval rendelkeznek a forráskódunkról:

compiler-architecture

Ezt a tudást vétek bezárni egyetlen eszközbe, sokkal jobban járunk, ha a fordító által felhalmozott információkból más eszközök is mazsolázhatnak. A fordító az egyetlen eszközünk, amely valóban érti a forráskódunkat, hiszen a kód minden egyes betűjéről tudja, hogy az utasítás, adat, komment stb. Ezt felhasználva sokkal jobb fejlesztőeszközöket készíthetünk, például a Visual Studio 2015 kódszerkesztőjében számos funkció a Roslyn nélkül valószínűleg soha nem valósulhatott volna meg.

Az alábbi ábra a Roslyn platform felépítését mutatja (katt a teljes képért):

roslyn-architecture

Aki szeretne minden egyes dobozt megérteni, bátran látogasson el a projekt honlapjára, én itt most csak annyit emelnék ki, hogy a forráskód olvasásától az értelmezésén keresztül egészen a futtatható kód generálásáig megtalálható itt minden, és mivel platformról van szó, természetesen mindenhez van API.

A kulcs elem a sárgával bekeretezett Syntax Tree, ami a Parser által létrehozott belső reprezentációja a teljes forráskódunknak. Az ehhez tartozó API-t hívják Roslyn Syntax API-nak, aminek segítségével nem csak forráskódból értelmezhetjük a forráskódunkat, hanem módosíthatjuk is azt. Példaként vegyük az alábbi utasítást:

Regex.Match("my text", @"\pXXX");

Ebből a Roslyn az alábbi szintakszisfát építi fel:

Turner_Figure4_hires

(A példát az MSDN Magazine Use Roslyn to Write a Live Code Analyzer for Your API című cikkéből kölcsönöztem, amiben egy Visual Studio bővítményt készítenek erre alapozva.)

Hogy mi ezt a fát pusztán csak nézegetjük, elemezzük, vagy módosítjuk is, az már kizárólag csak rajtunk múlik.

Felismerte ezt a TypeScript csapat is, és az 1.3 verziótól kezdve már a Roslyn motor szolgáltatja Visual Studioban a kódszerkesztő funkciókhoz a szükséges adatokat. Ennek köszönhetően a TypeScript fordító architektúrája nem csak hogy jelentősen letisztult, hanem egyúttal könnyebben érthetővé is vált:

typescript-architecture

Történetünk szempontjából az egyik fontos komponens az alsó dobozban szereplő Parser, a másik pedig az Emitter. A Parser feladata, hogy felépítse az absztrakt szintakszisfát – jelen esetben a TypeScript forráskódból –, az Emitter feladata pedig, hogy a szintakszisfa alapján elkészítse a fordító kimenetét – jelen esetben JavaScript (.js), definíció (.d.ts) vagy source map (.js.map) formában.

Érdemes észrevenni, hogy a Roslyn és a TypeScript architektúrája teljesen hasonló: a forráskódból előbb fát építünk, majd a fából más nyelvű kódot generálunk. A Roslyn esetén ez C# –> fa –> IL, a TypeScript esetén egy TypeScript –> fa –> JavaScript.

Mivel a két fa azonos, összekapcsolhatjuk a kettőt és így máris eljutunk az alábbi megoldáshoz:

flow

Azaz a Roslyn és a TypeScript felhasználásával képesek lehetünk C# forráskódot JavaScriptre fordítani!

A módszer előnye, hogy mivel közvetlenül a C# forráskódból indulunk ki (ellentétben például a JSIL projekttel, ahol Common Intermediate Language kódot fordítanak JavaScriptre), minden olyan információ rendelkezésünkre áll, ami a forráskódban még elérhető (például láthatóság, öröklés stb.), de egyébként a C# fordító kimenetén már nem jelenik meg. Mindez meglepően hatékony optimalizálási lehetőségeket nyújt!

Példaként vegyük a JSIL projekt oldalán elérhető Raytracer demót. A teljes C# forráskód 429 sor, a JSIL által készített JavaScript kód 793 sor. Mondhatnánk,  hogy örülhetünk, hogy egyáltalán fut, csakhogy a memória kezelése korántsem optimális:

raytracer-memory

Ugyanezt a forráskódot a Roslyn Parser + TypeScript Emitter kombináción átfuttatva az alábbi eredményt kapjuk:

raytracer-memory-2

Az Internet Explorer Developer Toolsban (F12) lévő UI Responsiveness eszköz segítségével megmértük a CPU utilization és a Visual throughput (FPS) értékeket is, és mindkét mutató jelentősen jobb értékeket mutatott.

Persze a kedvezőbb memória gazdálkodás és a jobb teljesítmény nem jön ingyen, a generált kód nagyobb lett, összesen 1844 sor. A jelentős méretnövekedést az okozza, hogy az IL kóddal ellentétben a szintakszisfa tartalmaz információt az osztályokról és azokon belül a tagok láthatóságáról, amit JavaScriptben csak körülményesen lehet leképezni, de ha megtesszük (és a TypeScript Emitter meg tudja tenni), akkor több kód letöltése árán összességében jobb teljesítményt tudunk elérni. Más alkalmazások vizsgálata során is ugyanerre a megállapításra jutottunk.

A módszer természetesen nem csak böngésző alapú alkalmazásoknál, hanem natív JavaScript alkalmazásoknál, például Node.js környezetben is működik. A fejlesztés lépései a következők:

  1. Telepítsd a Node.js Tools for Visual Studio bővítményt, ami lehetővé teszi Node.js appok készítését Visual Studioval.
  2. Töltsd le a Node.js with C# bővítményt a Visual Studio Gallery oldaláról (hamarosan elérhetővé tesszük).
  3. Telepítés után meg fog jelenni Visual Studioban a C# projektek között egy “Node.js application” nevű projekt sablon, annak segítségével kell létrehozni a projektet.
  4. Írd meg az alkalmazásod forráskódját C#-ban, ehhez természetesen használhatod a Visual Studiot.
  5. A szokásos módon működik a debuggolás (F5) és a futtatás is. A projekt sablon tartalmazza a szükséges MSBuild targeteket, ami a Roslyn és a TypeScript segítségével előállítja a JavaScript kódot, amit azután a Node.js Tools for Visual Studio fog átadni a Node.js futtatókörnyezetnek.

Tesztelőket keresünk! A Visual Studio plugin publikálása előtt szeretnénk egy szélesebb körű tesztet végezni. Ha szívesen vállalkoznál erre, kérlek itt olvasd el az útmutatót, majd hagyj egy kommentet alább és felveszem veled a kapcsolatot!

 

Frissítés:

reddit-node

http://www.reddit.com/r/node/comments/31r872/write_your_nodejs_app_in_c_with_roslyn/

 

Öröklésnél elveszik az Error message tulajdonsága

Ilyen kódot már mindenki látott:

try {
  throw new Error('Oh, nooooo!');
} catch (e) {
  console.log(e.message);            // Oh, nooooo!
  console.log(e instanceof Error);   // true
}

Ha nagyon sok hasonló keletkezik, akkor előbb-utóbb az ember rájön, hogy szükség lenne saját hiba osztályokra, még JavaScriptben is. Jó ötletnek tűnhet valahogy így létrehozni egyet:

function OhNoooError() {
  Error.call(this, "Oh, nooooo!");
  this.name = "OhNoooError";
}

Az eredmény azonban meglepő lehet:

try {
  throw new OhNoooError();
} catch (e) {
  console.log(e.message);                 // undefined
  console.log(e instanceof OhNoooError);  // true
  console.log(e instanceof Error);        // false
}

Az elkapott kivételünk ugyanis nem számít klasszikus Errornak (nem abból származik), és elveszítette a message tulajdonságát is.

Íme egy módszer, amivel ezeket a hibákat orvosolni lehet:

OhNoooError.prototype = Object.create(Error.prototype);
OhNoooError.prototype.constructor = OhNoooError;

function OhNoooError() {
  this.message = "Oh, nooooo!";
  this.name = "OhNoooError";
}

A kimenet is szebb lesz:

try {
  throw new OhNoooError();
} catch (e) {
  console.log(e.message);                 // Oh, nooooo!
  console.log(e instanceof OhNoooError);  // true
  console.log(e instanceof Error);        // true
}

A lényeg tehát, hogy a message tulajdonságot kézzel kell beállítani. Ha az öröklést esetleg CoffeeScriptben az extends kulcsszóval valósítjuk meg, a message tulajdonsággal akkor is manuálisan kell elbánnunk.

 

Technorati-címkék: ,

WebStorm, ne keresgélj a node_modules mappában!

Idegesít, hogy szinte bármire keresel egy node.js projektben, a WebStorm legalább ezer találatot hoz a node_modules mappából?

Semmi gond, irány a Project ablak, jobb klikk a problémás mappán, majd Mark Directory As –> Excluded:

webstorm-exclude-folder-from-search

Nem fog eltűnni a mappa a Project ablakból, de már nem fognak innen bekerülni találatok a keresésbe.

Visszatenni is ugyanitt lehet: Mark Directory As –> Cancel Exclusion

webstorm-exclude-folder-from-search-cancel

 

Technorati-címkék: ,

Node.js frissítése Windows-on

Múlt héten megjelent a Node.js 0.10.36 verziója, ami sokak számára fontos lehet, mert egy olyan hibakereséssel kapcsolatos javítást is tartalmaz, amibe sokan belefutottak.

Windows esetén a legegyszerűbben úgy frissíthetjük a gépünkön lévő futtatókörnyezetet, hogy letöltjük a legfrissebb MSI fájlt, majd next-next-finish-sel végigmegyünk a telepítőn. Utána pedig a node –v paranccsal tudjuk ellenőrizni, hogy valóban sikerült-e a megfelelő verziót telepítenünk.

 

Technorati-címkék:

Ott a fájl, de mégis 404

Próbálnék betölteni egy ASPX fájlt, de sehogy nem akar sikerülni. Pontosabban IIS Expressen a forráskódot futtatva megy kiválóan, de IIS-re publisholva 404 lesz belőle.

Látszólag minden rendben van, ott van a fájl a szerveren, csak épp nem töltődik le. Bekapcsolom a Failed Request Tracinget, hátha látszik valami. Látszik bizony: 388 napló bejegyzés egyetlen HTTP kéréshez. Még szerencse, hogy van Request Summary nézet, ami rögtön kiemeli az egyetlen warningot:

MODULE_SET_RESPONSE_ERROR_STATUS

ModuleName: ManagedPipelineHandler

Notification: EXECUTE_REQUEST_HANDLER

HttpStatus: 404

HttpReason: Not Found

HttpSubStatus: 0

ErrorCode: The operation completed successfully. (0x0)

Önmagában nem éppen nagy segítség, de legalább kiderült belőle, hogy melyik modul a bűnös. Kis öröm. Kikeresem a bejegyzést a Complete Request Trace-ből és megnézem, mi van előtte. AspNetParse és AspNetCompile bejegyzések. Talán valami gond van az ASPX fájllal? Nem valószínű, hiszen IIS Expressben megy, ráadásul NuGet package-ből jött.

Azért csak megnézem a forráskódot. Rögtön az első sor gyanús: a @Page direktívában CodeFile szerepel. Szokatlan. Átírom CodeBehindra. Fordítok, telepítek.

Megjavul.

Technorati-címkék: ,,

Visual Studio: Unable to check out the current file

Érdekes hibába futottam bele, miközben egy szolgáltatás referenciát próbáltam felvenni Visual Studioba. Az URL egy .svc fájlra mutatott, aminek a szerkezetét az Add Service Reference dialógusablak gond nélkül felismerte, ám az ablak bezárásakor a Studio a következő hibaüzenettel örvendeztetett meg:

Unable to check out the current file.  The file may be read-only or locked, or you may need to check the file out manually.

Bár az adott projekt történetesen valóban source control alatt volt, Git-ről lévén szó a “check out” kifejezés nem tűnt helyénvalónak. Az igazság az, hogy a fenti hibaüzenet teljesen rossz és semmi köze a verziókezelőhöz, és a megoldás az, hogy nem közvetlenül az Add Service Reference, hanem azon belül az Advanced gombra, majd az Add Web Reference gombra kattintva megjelenő dialógusablakot kell használni az adott szolgáltatás esetén.

 

Technorati-címkék: ,,

WebAPI dokumentáció kibővítése

Írtam már arról, hogy a Microsoft.AspNet.WebApi.HelpPage NuGet csomaggal milyen könnyen generálhatunk dokumentációt a WebAPI-nkhoz. Mint minden készen kapott megoldással, ezzel is akkor kezdődnek a nehézségek, amikor az ember olyasmit szeretne, amire helyből nincs felkészítve. Szerencsére ebben az esetben a teljes forráskód rendelkezésre áll, így viszonylag egyszerűen testreszabhatjuk a működést és a megjelenést.

Példaként tegyük fel, hogy az API-nk úgy épül fel, hogy minden action hiba esetén a hozzá tartozó enum valamelyik elemét adja vissza hibakódnak a hozzá tartozó hibaüzenettel együtt, és a feladat az, hogy ezek a hibakódok megjelenjenek a dokumentációnkban.

Egy saját attribútummal – amit itt ErrorCodeType-nak hívtam, –  könnyen hozzákapcsolhatjuk a hibakódokat tartalmazó enumot (itt MyErrorCodeEnum) az actionhöz:

[ErrorCodeType(typeof(MyErrorCodeEnum))]

Ezt az információt a következő lépésekkel tehetjük bele a dokumentációba:

1. A Models mappába hozzunk létre egy új osztályt, ami majd egy konkrét hibaág leírására lesz alkalmas, hívjuk ezt ErrorCodeDescription-nek:

public class ErrorCodeDescription
{
  public int Code { get; set; }
  public string Message { get; set; }
}

 

2. A HelpPageApiModel.cs fájl írja le azt a view-modellt, ami alapján a dokumentáció oldal megjelenik. Ebbe az osztályba vegyünk fel egy új tulajdonságot, ami az összes hibalehetőséget fogja tartalmazni egy listában:

public List<ErrorCodeDescription> ErrorResponses { get; set; }

 

3. A HelpPageConfigurationExtensions.cs fájlban található az a GenerateApiModel metódus, ami felépíti a HelpPageApiModel típusú apiModel nevű változóban található view-modellt. Itt a meglévőek mintájára készíthetünk egy saját metódust, ami kiolvassa a csatolt attribútumot, és az alapján felépíti az ErrorResponses listát, például így:

private static void GenerateErrorResponses(HelpPageApiModel apiModel)
{
  ErrorCodeTypeAttribute attribute = apiModel.ApiDescription.ActionDescriptor
    .GetCustomAttributes<ErrorCodeTypeAttribute>().FirstOrDefault();
  Type enumType = attribute.ErrorCodeEnumType;
  string[] names = Enum.GetNames(attribute.ErrorCodeEnumType);

  foreach (string name in names)
  {
    apiModel.ErrorResponses.Add(new ErrorCodeDescription
    {
      Code = (int) Enum.Parse(enumType, name),
      Message = "TODO"
    });
  }
}

 

4. A modell kirenderelését a Views/Help/DisplayTemplates/HelpPageApiModel.cshtml nézet végzi. A meglévő kódok mintájára ide felvehetjük az alábbi blokkot, ami az ErrorResponses lista megjelenítését az ErrorResponses partial view-ra bízza:

@if (Model.ErrorResponses.Any()) 
{ 
  <h3>Error Responses</h3> 
  @Html.DisplayFor(m => m.ErrorResponses, "ErrorResponses") 
}

 

5. Természetesen létre kell hoznunk ugyanebben a mappában egy ErrorResponses.cshtml fájlt, ami @model-ként egy List<ErrorCodeDescription> példányt fog kapni, amit utána úgy jelenítünk meg a weboldalon, ahogy csak szeretnénk.

 

Ez elsőre sok lépésnek tűnik, de valójában egészen logikus, érdemes megbarátkozni vele.

 

Technorati-címkék: ,