Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Tips DuoCode

Ta C# till webben, jag älskar Javascript för vyprogrammering, ett dynamiskt språk löser så många problem för ramverk som Knockout eller Angular där du i ett statiskt typat språk måste använda reflection för att komma runt vissa problem med att pussla ihop vyn med modellen. Dock är det många problem med dynamiska språk för domänmodeller som tex VyModellen i Knockout. Även kontraktet Server > Klient kan bli problematiskt, man kan tex lösa det som jag gjorde här http://andersmalmgren.com/2014/02/05/typed-javascript-contrac....

Verktyg för att kompilera från ett språk till Javacript har ju funnits länge, tex Coffescript eller det statiskt typade TypeScript. Jag stötte just på ett relativt nytt sådant ramverkt, DuoCode http://duoco.de/ som kompilerar C# kod till javascript

Det är baserat på Microsoft Roslyn och verkar mycket kompetent, tex testade jag snabbt att skriva en Type mapping till Knockout och på några minuter hade jag en fungerade Vymodell

using Knockout; namespace ViewModels { public class FooViewModel { private readonly Observable<string> bar; private readonly Observable<string> computed; public FooViewModel() { bar = Global.Observable("HelloWorld"); //Translates to ko.observable("HelloWorld") on client computed = Global.Computed(() => bar.Get() + "COMPUTED"); } public Observable<string> Bar { get { return bar; } } public Observable<string> Computed { get { return computed; } } } }

Enligt utvecklarna har de fullt Runtime-stöd vilket verkar stämma då jag testade lite reflection, i teorin betyder detta att man kan ta en light weight IoC som tex Ninject och kompilera den i DuoCode, vilket då ger dig en riktig IoC att använda i ditt Knockout eller Angular projekt, ska testa detta lite mer men borde fungera.

Det ballaste är ju att du kan återanvända kod mellan server och klient så mitt exempel ovan från min blogg med typade kontrakt mellan klient server blir ju en barnlek.

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Just DuoCode kostar pengar efter Betan så tittar även lite på detta Open Source alternativ som också använder Roslyn, kräver VS2015
https://github.com/kswoll/WootzJs

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Testade med alla light weight dependency injection bibiotek jag kunde hitta men ingen kunde kompilera under DuoCode så skrev en egen superlight weight och den fungerar faktiskt. Måste såklart utökas med life time management etc men coolt med äkta DI i ett javascript projekt, får RequireJS mfl att kännas riktigt b.

public class SimpleInjector { private readonly Dictionary<Type, Type> bindings = new Dictionary<Type, Type>(); public void Bind<TInterface, TType>() where TType : TInterface { bindings[typeof(TInterface)] = typeof(TType); } public object Get(Type type) { var targetType = bindings.ContainsKey(type) ? bindings[type] : type; if (targetType.IsInterface || targetType.IsAbstract) throw new Exception("Can only inject concrete none abstract types"); var constructor = targetType.GetConstructors() .OrderByDescending(c => c.GetParameters().Length) .First(); var parameters = constructor.GetParameters() .Select(p => Get(p.ParameterType)).ToArray(); object instance = null; if (parameters.Any()) instance = Activator.CreateInstance(targetType, parameters); else instance = Activator.CreateInstance(targetType); return instance; } }

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Jobbade vidare med detta inatt, har implementerat en liten state machine motor, så nu kan man skriva kod som

public class MyViewModel { private readonly IService service; public MyViewModel(IService service) { this.service = service; } public IEnumerable<IResult> Save() //Click handler { var confirm = new ConfirmResult("Proceed?"); yield return confirm; if(confirm.Result == ConfirmResults.Cancel) yield break; yield return service.SendCommandAsync(new FooCommand()); } }

Notera även Dep. injection för IService, får RequireJS att kännas rätt B va?

Bara jag som förstår storheten i detta?

Trädvy Permalänk
Medlem
Plats
ekerö
Registrerad
Nov 2009

Najs! detta har helt klart sina användningsområden. Man har ju sett projekt tidigare som kompilerar c# -> js, vad är fördelen med DuoCode? är det integrerat bra med VS?

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Den är baserat på Roslyn så det abstrakta trädet det är baserat på är vädlgit komplett och stabilt, vilket gör att denna kompilator är mer stabil än tidigare versioner. Integrationen funkar sådär just nu faktiskt Men det är ju beta, jag har inte hunnit testa open source alternativet jag nämner ovan ännu. MEn open source är ju inte känt för att integrera bra i IDE

Trädvy Permalänk
Medlem
Plats
i din garderob
Registrerad
Sep 2007

Intressant! Det har funnits något liknande för Scala ett tag (live-test här: http://www.scala-js-fiddle.com/) men jag har aldrig kommit till skott med att testa det i en riktig produkt

En kul grej man kan göra med det här, åtminstone i Scala-versionen, är att bygga statiska bryggor mellan klient- och serversidan: Om man på serversidan har en REST-endpoint som förväntar sig vissa inparametrar kan man alltså statiskt garantera att kompilerande klientkod skriven med Scala.js fungerar. Man överbryggar separationen av server och klient helt enkelt

Bilanaloger är som Volvo — varenda svenne kör med dem

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Yepp, det är precis en sådan sak som är grymt, det går ju även att lösa på andra sätt tex som jag gjort här, http://andersmalmgren.com/2014/02/05/typed-javascript-contrac.... Men det blir ju ännu mer statiskt på detta vis.

Jag har paketerat mitt jobb som nuget paket btw, För knockout

Install-Package Knockout.DuoCode

För att kunna använda state machinetänket ovan med yield return IResult så krävs mitt Convention over configuration bibliotek

Install-Package Knockout.BindingConventions

Sedan DuoCode binding

Install-Package Knockout.BindingConventions.DuoCode

varning Duocode är Beta och kommer bli licensprodukt, så jag skulle inte använda detta skarpt förrens de offentligjort betalmodell. Då är det bättre att titta på Open Source-alternativet jag pratar om ovan. Fungerar bara i 2015 dock

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

En grej som är så liten men så skönt är att slippa marshalla contexten av 'this' hela tiden. .bind(this) eller var self = this; etc. Det där löses ju under huven då kompilatorn hela tiden kör .bind(this) på allt som är i klassinstans context

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Tänkte köra detta i ett lite mindre inhouse-projekt, lite riskabelt, men det är så pass litet system att jag skulle kunna skriva om det till vanilla javascript på några timmar om licenspriset för DuoCode visar springa iväg.

Jag har även fixat en liten enkel Dep.injector

https://github.com/AndersMalmgren/DuoCode.SimpleInjector

Install-Package DuoCode.SimpleInjector

Helt okej prestanda, men kanske inte skulle använda den för att skapa flera tusen objekt i sekunden (Men det är ju inte Dep.injection tänkt för heller)

Trädvy Permalänk
Datavetare
Plats
Stockholm
Registrerad
Jun 2011

Är ju definitivt lätt att se fördelarna med statisk typning när projekten blir lite större och statiskt typning ger ju också möjlighet att redan vid kompilering upptäcka felaktig användning av gränssnitt man definierat mellan t.ex. server/klient.

Men exakt vad är fördelen att köra C#->JS över t.ex. TypeScript->JS? TypeScript är ett strikt super-set av JS så där blir det en helt naturligt integration.

Tittar man på genererad JS från DuoCode och även från öppna projekt som t.ex. Bridge.NET så trycker man på att det så långt det går är en ett-till-ett mappning mellan uttryck i C# och genererade uttryck i JS. Detta borde bara ha ett värde om man förväntar sig att systemet kommer utvecklas både i C# och i JS, vilket lite tar bort fördelarna med då det som skrivs i JS inte får samma statiska check.

Att man stödjer reflection är trevligt ur ett perspektiv, men det är också ett rejält hinder för en lång rad optimering av genererad JS som skulle vara möjlig utan reflection.

Jämför man DuoCode med ramverk som GWT (Google Web Toolkit, Java->JS, open-source och funnits sedan 2008) så är hela motivationen att skriva i Java i stället för JS just för att Java mycket bättre lämpar sig för väldigt stora projekt. Sedan använder man JS "bara" som ett "compiler-target", d.v.s. genererad kod fyller samma funktion som maskinkod som från en C eller C++ kompilator.

I GWT har man explicit uteslutit reflection just därför att det omöjliggör en lång rad prestandaoptimeringar. Google V8 JS-motor presterar ju riktigt bra och ihop med GWT-kompilatorn får man program som går att köra i en webbläsare och med Chrome har man en runtime som inte presterar alls långt från något som CLR eller en JVM.

Så är det något större värde i att kombinera C# och JS över att bara kör TypeScript? Om tanken är att allt ska vara i C# vad är då poängen att generera en JS som är optimerad för att läsas av människor i stället för att optimera för runtime-hastighet? Med GWT är förväntningen allt skrivs i Java även om det rent tekniskt är möjligt att skriva delar i JS (går även att säga åt kompilatorn att generera kod som är optimerad för att läsas av en människa, men det ska mest ses som motsvarigheten till debug-bygge).

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

C# är ett mycket mer moget språk än Typescript. Men framför allt så kan du dela kod mellan backend och klient, som tex CQRS-kontrakt eller validering.

Nu är jag inget expert på Typescript men finns det lika bra CLR-stöd som i DuoCode och WootzJS? Type injection, och andra fördelar kräver ju en välutvecklad runtime.

edit: Du kan debugga i C# via sourcemap så att JS-koden ska vara "läsbar" ser jag ingen vits med

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

Och bara min statemachine gör ju det värt att använda

public class MyViewModel { private readonly IService service; public MyViewModel(IService service) { this.service = service; } public IEnumerable<IResult> Save() //Click handler { var confirm = new ConfirmResult("Proceed?"); yield return confirm; if(confirm.Result == ConfirmResults.Cancel) yield break; yield return service.SendCommandAsync(new FooCommand()); } }

Trädvy Permalänk
Medlem
Plats
Göteborg
Registrerad
Maj 2004
Skrivet av CyberVillain:

public IEnumerable<IResult> Save() //Click handler { var confirm = new ConfirmResult("Proceed?"); yield return confirm; if(confirm.Result == ConfirmResults.Cancel) yield break; yield return service.SendCommandAsync(new FooCommand()); } }

OT:
Det är inte första gången jag ser konstruktionen med IEnumerable<IResult> (eller liknande), men har fortfarande inte riktigt lyckats förstå principen bakom. Har du någon bra länk eller lust att förklara hur det går till? Skulle gärna baka in något liknande på ett par ställen på jobbet, men ingen av förklaringarna jag lyckats Googla fram skriver på ett sätt som gör att jag förstår...
(Eller så är jag bara dum i huvudet... )

as far as we can tell, the massacre went well...

Trädvy Permalänk
Medlem
Plats
i din garderob
Registrerad
Sep 2007
Skrivet av jovnas:

OT:
Det är inte första gången jag ser konstruktionen med IEnumerable<IResult> (eller liknande), men har fortfarande inte riktigt lyckats förstå principen bakom. Har du någon bra länk eller lust att förklara hur det går till? Skulle gärna baka in något liknande på ett par ställen på jobbet, men ingen av förklaringarna jag lyckats Googla fram skriver på ett sätt som gör att jag förstår...
(Eller så är jag bara dum i huvudet... )

Inget speciellt där. IResult är ett interface som implementerats av ConfirmResult och vad nu SendCommandAsync behagar returnera. Kanske tänker du på generics överlag?

Bilanaloger är som Volvo — varenda svenne kör med dem

Trädvy Permalänk
Datavetare
Plats
Stockholm
Registrerad
Jun 2011
Skrivet av jovnas:

OT:
Det är inte första gången jag ser konstruktionen med IEnumerable<IResult> (eller liknande), men har fortfarande inte riktigt lyckats förstå principen bakom. Har du någon bra länk eller lust att förklara hur det går till? Skulle gärna baka in något liknande på ett par ställen på jobbet, men ingen av förklaringarna jag lyckats Googla fram skriver på ett sätt som gör att jag förstår...
(Eller så är jag bara dum i huvudet... )

Gissar att du tänker hur flödet av denna kod hänger ihop med att man får en sekvens av IResult. Du är inte den enda som tycker det kan vara svårt att följa programflödet i sådan här kod, rent tekniskt använder man något som kallas continuation som i sin tur fyller samma syfte i funktionella språk som "goto" fyller i imperativa språk. Just den aspekten gör programflödet väldigt lurigt att följa, skulle älska se något försöka skriva "certification evidence" på den nivå som krävs för programvara för kritisk funktioner (eng. safety critical) för den kod vi har här

1. public IEnumerable<IResult> Save() //Click handler 2. { 3. var confirm = new ConfirmResult("Proceed?"); 4. yield return confirm; 5. if(confirm.Result == ConfirmResults.Cancel) 6. yield break; 7. yield return service.SendCommandAsync(new FooCommand()); 8. } 9. }

Från signaturen är det rätt uppenbart att vi får en sekvens av IResult, däremot går inte flödet att full ut beskriva utan att man också ser stället denna kod anropas från. Första raden kommer köras, 4. kommer däremot göra rätt mycket du inte direkt ser... I nuvarande .NET (d.v.s detta är en implementationsdetalj och kan kommas att ändras) så kommer man skapa ett objekt som innehåller metod med de rader vi ännu inte kört och confirm (rad 3.) kommer läggas till som medlemsvariabel till det objektet eftersom det refereras senare.

Vi hoppar nu tillbaka till det kontext som anropade denna funktion, som nu får sitt första IResult att jobba med, d.v.s. resultatet av new ConfirmResult("Proceed?"). I något läge kommer den som jobbar med returvärdet från Save() att vilja ha nästa IResult i sekvensen och anropar därför MoveNext() metoden på IEnumerable. I fallet här kommer vi nu hoppa in i metoden till det objekt som skapades vid 4. och börja köra från 5..

Har tillståndet på confirm satts till ConfirmResullts.Cancel så körs 6. vilket leder till att MoveNext() returnerar false, d.v.s. det finns inga fler element.

Annars kommer vi till 7. och undrar om det inte är ett fel här. Något som slutar på "Async" bör producera ett asynkront resultat, vilket i C# betyder Task<IResult> (det resten av världen kallar future). Det matchar inte signaturen på metoden, så ska nog stå

// asynkron version, fungerar ens detta? // Om det fungerar gör man en liknande paketering av anropet till service.SendCommandAsync() som vid 4. // och sedan återanvänds denna tråd av annat till dess att service.SendCommandAsync() har ett resultat yield return await service.SendCommandAsync(new FooCommand());

eller

// synkron version där vi väntar på att Task<IResult> verkligen har ett resultat att leverera. yield return service.SendCommandAsync(new FooCommand()).Result;

Det som är kvar (ett implicit anrop till yield break) packas upp i ett nytt objekt, MoveNext() returnerar true och vårt andra IResult är nu aktuellt objekt för IEnumerable iteratorn.

Vid något tillfället anropas MoveNext() igen och då körs koden till objektet som skapades vid 7., vilket bara är det implicita anropet till yield break, vilket leder till att MoveNext() evalueras till false och man är klar med sekvensen.

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

SendCommandAsync var ett dåligt namn, användaren av APIet (den person som skirivt metoden Click) behöver inte veta hur den eexekveras. Ett bättre namn hade varit SendCommand. Anledningen varför jag använder egenskrivna IResults är för att DuoCode inte stödjer Tasks i nuvarande version. Sedan vill jag ha lite customlogik också

Skrivet av jovnas:

OT:
Det är inte första gången jag ser konstruktionen med IEnumerable<IResult> (eller liknande), men har fortfarande inte riktigt lyckats förstå principen bakom. Har du någon bra länk eller lust att förklara hur det går till? Skulle gärna baka in något liknande på ett par ställen på jobbet, men ingen av förklaringarna jag lyckats Googla fram skriver på ett sätt som gör att jag förstår...
(Eller så är jag bara dum i huvudet... )

Det hela är ganska enkelt, du returnerar en lista med IResults, sedan är det upp till ramverket att exekvera dessa när det passar bäst. Är det WinForms eller WPF gör man antagligen en Invoke på guitråden etc.

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003
Skrivet av Yoshman:

// asynkron version, fungerar ens detta?
// Om det fungerar gör man en liknande paketering av anropet till service.SendCommandAsync() som vid 4.
// och sedan återanvänds denna tråd av annat till dess att service.SendCommandAsync() har ett resultat
yield return await service.SendCommandAsync(new FooCommand());

Det fungerar pga IResult ser ut såhär

Citat:

public interface IResult
{
void Execute(ResultContext context);
event EventHandler<ResultCompletionEventArgs> Completed;
}

Completed triggas i detta fall asynkront av SendCommandResult-koden när kommandot har returnerat

Iteratorn

internal class ResultIterator { private readonly ResultContext context; private readonly IEnumerator<IResult> enumerator; public ResultIterator(IEnumerable<IResult> result, ResultContext context) { this.context = context; enumerator = result.GetEnumerator(); } public void Execute() { MoveNext(); } private void MoveNext() { if (enumerator.MoveNext()) Global.window.setTimeout(new Action(HandleNext), 1); } private void HandleNext() { enumerator.Current.Completed += Current_Completed; enumerator.Current.Execute(context); } private void Current_Completed(object sender, ResultCompletionEventArgs e) { enumerator.Current.Completed -= Current_Completed; if (!e.WasCancelled) { MoveNext(); } } }

Detta är en fattigmansversion av Invoke i WPF och Winforms, man ger helt enkelt GUI-tråden lite tid att uppdatera

Global.window.setTimeout(new Action(HandleNext), 1);

Trädvy Permalänk
Medlem
Plats
Stockholm
Registrerad
Dec 2003

DuoCode har släpps för ett tag sedan och de har även en gratis Community licens. De gav mig även en Pro-licens då de verkar intresserade av mina libs skrivna för deras DuoCode-projekt :D. De är mest intresserad av detta lib

https://github.com/AndersMalmgren/DuoCode.SimpleInjector

En typbaserad DI för Javascript, riktigt nice. Får AMD-tänket i vanilla javascript att verka ritkigt omoget

Det är lite synd att det kostar pengar för kommersiella projekt, tror det kommer göra att det aldrig blir i närheten lika stort som tex, TypeScript. Men det är mycket trevligt att utveckla avancerade web-projekt i C# med näsintill full CLR