Pull to refresh

Comments 32

У меня свой велосипед. Кроме перечисленного — я еще беру метаданные контроллеров MVC (через GetApiExplorer), и генерирую такие вот штуки на каждый контроллер:

module api {
    export var someController = {
        someMethod: function(id : number) : JQueryPromise<void> {
            return api.processRequest(`/api/someController/someMethod?id=${id}`);
        }
    }
}


За счет этого, кроме того что не надо это руками писать, мне еще не надо ставить аттрибуты на viewmodels — я просто генерирую интерфейсы для всего что входит и выходит из контроллеров.

Чтобы избавится от проблемы курицы и яйца (компиляция C#, генерация TS, и компиляция TS в одном проекте) — я просто вынес MVC-код в отдельную сборку.

Кстати, твоя библиотека как-то связана с TypeLite ( http://type.litesolutions.net/ )?
У меня свой велосипед

Вот у меня тоже началось со своего велосипеда.

Просто генерирую интерфейсы для всего что входит и выходит из контроллеров

Боюсь, если я так сделаю на живом проекте сейчас — ничего хорошего не получится :) Слишком много легаси-кода, слишком много всяких недокументированных методов. Повылазиет всякого… ну его :)

MVC-код в отдельную сборку

Мне такое решение не нравится, поэтому я предпочел хирургически препарировать MSBuild-скрипт (он же .csproj).

TypeLite

Впервые слышу о ней. Обязательно посмотрю.
Про атрибуты забыл дописать:
Для некоторых экшнов я сделал такую генерацию и вот в следующем посте расскажу-покажу как. А ставить-не ставить атрибуты… ну тут палка о двух концах. В конце-то концов нам нужен всего лишь список методов (при том крайне желательно, чтобы мы могли его контролировать!).
С другой стороны плюс атрибутов в том, что можно много всякого-разного понастраивать индивидуально для метода. Это можно, было бы, конечно, изобразить Fluent-конфигурацией или, упаси б-же, XML, но пока что мне не хочется сильно разделять TypeScript и C#-код, которые его представляет.
С третьей стороны — я писал библиотечку для широкого круга пользователей и кто его знает — не все же используют именно jq-промисы. Кому-то может вообще понадобятся дополнительные фичи вроде добавления в список параметров id-шника индикатора загрузки, или еще какой радости. Или вообще обернуть это в какой-нибудь ангуляровский интерфейс для запросов к серверу. Если я сделаю фиксированное решение и скажу «делайте как я сказал» — меня, боюсь, многие матом будут крыть. :) А так — можно просто дописать пак атрибутов специально для MVC и дать возможность каждому выбрать.

P.S: А вообще у моего [TsClass] можно задать дефолтный код-генератор для всех методов класса. Так-то.
Я в своем проекте использую DataContract для помечания ViewModel и все свойства, необходимые на клиенте, явно помечаю DataMember-атрибутами. При конвертации в json использую JSON.Net, который из коробки прекрасно работает с dataconract'ом. Ну и ModelBinder для всего этого написан.

Это позволяет не боятся серверных рефаткорингов даже при использовании чистого js и явно указывать, какие свойства нужны на клиенте (часть свойств вью-моделей нужны только для серверного рендеринга, гонят туда-сюда данные смысла нет). Ну и кроме того в шарпе принято свойство называть с большой буквы, а в js — с маленькой.

Отсюда вопрос: поддерживает ли ваша библиотечка dataconract?
Кстати, при конвертации в typescript generic-классы и классы с наследованием корректно генерируются?
Эм… я боюсь, мы как-то не поняли друг друга. Библиотечка тайпинги пишет, а не занимается сериализацией :) В свете этого не очень понятен вопрос про DataContract.

Но на всякий случай: да, тайпинги для generic-классов она тоже пишет.
Попробую пояснить на примере: у меня есть ViewModel вида

[DataContract]
public class OrderViewModel
{
    [DataMember(Name = "itemName")]
    public string ItemName { get; set; }

    [DataMember(Name = "quantity")]
    public int Quantity { get; set; }
    public decimal Subtotal { get; set; }
    public bool IsPaid { get; set; }
    public string ClientName { get; set; }
    public string Address { get; set; }
}


из нее я бы хотел получить интерфейс вида

interface OrderViewModel {	
    itemName: string;		
    quantity: number;		
}


А в идеале даже без явного указания Name:

[DataContract]
public class OrderViewModel
{
    [DataMember]
    public string ItemName { get; set; }

    [DataMember]
    public int Quantity { get; set; }
    public decimal Subtotal { get; set; }
    public bool IsPaid { get; set; }
    public string ClientName { get; set; }
    public string Address { get; set; }
}

interface OrderViewModel {	
    itemName: string;		
    quantity: number;		
}
Спасибо :)
Но атрибут [TsInterface] все равно нужен. DataContract может использоваться много для чего, и для всех классов, использующих его, генерить ts-интерфейсы не нужно, правильнее явно помечать классы, для которых нужны интерфейсы.
Правда мне все еще непонятно зачем вы используете DataContract, в то время как Json.NET прекрасно справляется и без них. И к тому же имеет свой, куда более гибкий [JsonProperty]

P.S: совсем забыл — да, и с наследованием у Reinforced.Typings тоже все хорошо.
Начал использовать еще до внедрения TypeScript'a. Это помогало не разломать фронтенд при рефакторинге бекэнда. С автоматической генерацией ts-интерфейсов эта проблема решается, но у нас проект большой и чистый js еще долго будет жить с нами.

Кроме того мне кажется важным явно помечать свойства, с которыми нужно работать на клиенте. Это во-первых, позволяет немного сократить объем ненужного траффика между клиентом и сервером, а во-вторых, на клиенте работать с чистыми интерфейсами без лишних серверных свойств.
Ну тут вот фиг знает. На сколько я соображаю в системном дизайне, единственная роль ViewModel-и — хранить в себе данные, которые отображаются. Переиспользовать их же на клиенте (а особенно — переиспользовать на клиенте их часть) — в некотором роде нарушение SRP. Т.е. да, я за то, чтобы View рендерился от одной модели, а на клиент уходила другая (в общем случае). Хотя на практике возникают ситуации, когда в рендер и на клиент отправляется одна и та же модель — и чувствуешь себя с этим SRP как идиот. Для таких случаев можно и [TsInterface] пошалить.

Кстати о птичках — коль пошла такая пляска — можете потыкать у Json.NET свойство атрибута… как же его… по-моему NullValueHandling — и эта радость не будет отправлять на клиент null-поля (которые там будут undefined, но при проверке через if (object.Field) это не страшно).

Ну и чтобы не разносить на два комментария — вы упомянули, что не для всех классов, которые помечены [DataContract] нужно генерировать интерфейсы. Но тогда получается что надо классы еще чем-то помечать. Мол — ты, типа, для этих генерируй, а для этих не генерируй. Можно пометить их тем же [TsInterface], что и является дефолтной практикой для R.T.
А вот, кстати, переименования автоматического у меня нет. Как-то совсем даже забыл про него. Привык на сервере и на клиенте использовать PascalCase (и можете бить меня за это тапками). Но в следующей версии сделаю.
ViewModel хранит в себе данные, да. Часть из них мне удобнее рендерить серверно, а часть на клиенте (начали использовать реакт, но при этом разом перевести весь проект на реат невозможно). Что в этом плохого? А что такое SRP не просветите? :)

Кстати о птичках — коль пошла такая пляска — можете потыкать у Json.NET свойство атрибута… как же его… по-моему NullValueHandling — и эта радость не будет отправлять на клиент null-поля (которые там будут undefined, но при проверке через if (object.Field) это не страшно).

В моем случае это не поможет. У меня поля не Null, они заполнены, но на клиенте не используются.

Ну и чтобы не разносить на два комментария — вы упомянули, что не для всех классов, которые помечены [DataContract] нужно генерировать интерфейсы. Но тогда получается что надо классы еще чем-то помечать. Мол — ты, типа, для этих генерируй, а для этих не генерируй. Можно пометить их тем же [TsInterface], что и является дефолтной практикой для R.T.

Не имею ничего против аттрибута [TsInterface] :)

А вот, кстати, переименования автоматического у меня нет. Как-то совсем даже забыл про него. Привык на сервере и на клиенте использовать PascalCase (и можете бить меня за это тапками). Но в следующей версии сделаю.

Стараюсь все же придерживаться общепринятых стандартов. В этом есть свои плюсы)

SRP = Single Responsibility Principle, SOLID же.

Не имею ничего против аттрибута [TsInterface] :)

А вот в комментарии ниже вы его против лишний раз поставить. :)
Против TsProperty, но не против TsInterface :)
Про SRP скорее теперь скорее согласен.
Кстати про важные на клиенте свойства — в моем [TsInterface] вы можете явно сказать AutoExportProperties = false и пометить [TsProperty] те проперти (и через [TsField] те филды), которые должны быть экспортированы. При желании указав для них кодогенератор. И даже автоматическую букву I перед интерфейсами можете отключить. :)
При наличии этой информации, фич-реквест еще актуален?
Актуален. При таком подходе мне на каждое свойство придется вешать по 2 аттрибута: TsField для корректной конвертации в ts и DataMember/JsonProperty для корректной сериализации/десериализации в Json.
Два аттрибута — это уже перебор, на мой взгляд.
… и я не теряю надежды.
Как вариант — вы можете расставить [TsIgnore] на все остальные свойства :)
Как вариант, да. Хотя и выглядит слегка костыльно. Но автоматическое переименование в CamelCase все равно нужно.
Я тебе советую взять JSON.NET, в него легко встраивается конвертация имен: PersonName => personName при сериализации, и обратно при десериализации, делается автоматом без доп. аттрибутов.

http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_CamelCasePropertyNamesContractResolver.htm
Я и так использую JSON.NET, у меня нет цели избаваиться от аттрибутов, я, наоборот, считаю их полезными.
Вообще, меня давно и прочно будоражит идея тесной интеграции Back-End и Front-End на .NET стеке, что в свою очередь даже вылилось в попытку с наскоку написать целый транслятор из C# в JavaScript.

Чем не устроили существующие трансляторы, коих сейчас и так много существует List of languages that compile to JS.

Темой тесной интеграции я тоже занимался, правда давно уже: Универсальный код C# под .NET и JavaScript. Сейчас бы, понятное дело, использовал другие инструменты.
Чем не устроили существующие трансляторы

Вы бы знали сколько раз мне задавали этот вопрос. Самый честный ответ: потому что я несколько по-своему вижу Use-Cases транслятора и имею свои представления о usability и flexibility такой штуки. Обратите внимание — ни один из предложенных вариантов не используют как «стандарт индустрии де-факто» и, в общем-то, все понимают почему (затруднена интеграция с MVC-проектом — тот же Script# подменяет mscorlib — то есть в одну сборку с проектом транслируемые классы уже не положишь, затруднена отладка, не очень понятно что у этих продуктов с расширяемостью, ну и много других мелких нюансов — не могу сходу перечислить). У меня свое видение, как это все должно работать, отлаживаться и тестироваться, по сему я и предпочел попытаться сделать свое. К слову, получилось в некотором роде похоже на DuoCode.
Ну так не лучше ли взять какой-нибудь существующий, на рослине или нет, и доработать? С нуля довольно много придется разрабатывать. А Script# по факту давно устарел уже, сейчас его уже не имеет смысла использовать.
Я на Roslyn и написал уже, собсно, ту самую версию, которая ушла в анабиоз (при том вот сразу через пару дней после выхода Roslyn-а — пришлось Microsoft.CodeAnalyzis выдирать с мясом из исходников). Так что мне не привыкать много разрабатывать :).
Там, на самом деле, затык в том, что в TS называется тайпинги. Т.е. придется переписывать интерфейсы для внешний библиотек, что руками делать долго и скучно.
А на самом деле, первоначальная версия транслятора внезапно хороша тем, что там есть парсер JavaScript, который превосходно разбирает даже минифицированный jquery в AST. Не спрашивайте зачем он там нужен, но он есть.
Commits on Apr 24, 2014
@bkiers
Added an ECMAScript grammar.


А я свою грамматику (на Coco\R) написал где-то в марте прошлого года. Время, беспощадная ж ты штука.
А вообще то ли у меня руки не оттуда, то ли лыжи не едут… Но в общем была у меня какая-то грамматика для ANTLR-а, из которой я сгенерил все необходимое, сделал playground для тестирования, нажал F5 — и получил зацикливание на простеньком js-файле. С тех пор я ANTLR-ом не пользуюсь.
Ну так вообще-то в апреле появилась грамматика для 4 версии ANTLR. Судя по этому сайту, грамматика ECMAScript существует аж с 2008 года: www.antlr3.org/grammar/list.html

Но в общем была у меня какая-то грамматика для ANTLR-а, из которой я сгенерил все необходимое, сделал playground для тестирования, нажал F5 — и получил зацикливание на простеньком js-файле. С тех пор я ANTLR-ом не пользуюсь.

Скорее всего вы не разобрались с устройством ANTLR и его грамматик. Я вот тоже раньше не разбирался в лексических режимах, предикатах и других вещах. А после того, как прочитал книгу и попрактиковался я понял, что это очень мощный инструмент, обладающий большими возможностями по разбору формальных языков, в том числе и контекстно-зависимых. Сейчас вот на работе занимаюсь грамматикой для PHP.

Я как бы и не говорю что «виновата скамейка», нет :) Просто с ANTLR-ом у меня был неудачный первый опыт (который во многом влияет на восприятие технологии как таковой) и как-то так получилось что я перешел на Coco\R и вот я здесь.

www.antlr3.org/grammar/list.html

Дадада. Вот оттуда я ее скачал и сгенерил парсер, который зациклился на самом простом js-файле.
в Web essentials для visual studio сейчас есть возможность сгенерить ts-файлик с C# класса + при изменении класса файлик будет изменяться автоматичски
О, а покажите ссылку? Мне что-то не по глазам, а как гуглу задать этот вопрос — не знаю :)

P.S.: я еще допилю в свое творение fluent-конфигурацию (просто пока сильно больно времени нет) и будет следующая статья.
pnovikov, очень полезный проект! Вы упомянули knockout ViewModel в секции про генераторы. Это в качестве примера было или такой генератор вами уже написан был?

Это в качестве примера был.
Пожалуйте в wiki на github. Там больше информации, но на (ломаном) английском.

Sign up to leave a comment.

Articles