WebSharper - Как предоставить клиенту динамически отображаемые объекты шаблонов стратегий на сервере? - PullRequest
0 голосов
/ 09 января 2019

Я нахожусь в процессе изучения WebSharper, и я изо всех сил пытаюсь заставить некоторую часть моей логики работать на стороне клиента.

У меня есть несколько серверных объектов с иерархией наследования, которые мне нужно предоставить клиентской стороне. Они имеют дело с созданием различных частей страницы в виде фрагментов документа, но на клиенте.

[<JavaScriptExport>]
type [<AbstractClass>] A() = 
    abstract member Doc: Map<string, A> -> Doc
    ...

[<JavaScriptExport>]
type [<AbstractClass>] B() =
    inherit A()

[<JavaScriptExport>]
type C() =
    inherit B()

Запись обновлена ​​, см. history для более старой версии.

В моем коде на стороне сервера У меня есть карта, связывающая каждый объект с заданным именем (как в шаблоне разработки стратегии):

let mutable objectMap : Map<string, A> = Map.empty

В какой-то момент карта заполняется данными. Это происходит один раз только на этапе инициализации приложения, но является результатом серверной логики на стороне сервера.

Теперь мне нужно использовать эти объекты только на стороне клиента, как в чрезмерно упрощенном фрагменте ниже:

[<JavaScriptExport>]
type C() =
   inherit B() // or inherit A()
   override this.Doc map =
       div [] [ map.["innerDoc"].Doc(map)

На стороне сервера у меня будет:

module ServerSide =
     let Main ctx endpoint ... =
         // this is the mapping that is being generated on the server, derived somehow from the `objectMap ` above:
         let serverMap : Map<string, something> = ...

         let document =
             [
                 div [ on.afterRender(fun _ -> ClientCode.fromServerMap(serverMap));][client <@ ClientCode.getDoc("someDoc") @>]
             ] |> Doc.Concat
         Page.Content document 

Модуль ClientCode будет скомпилирован в JS и будет выглядеть так:

[<JavaScript>]
moduel ClientCode =
    let _map : Var<Map<string, A>> = Var.Create <| Map.empty

    let fromServerMap (serverMap : something) =
        let clientMap : Map<string, A> = // TODO: get the information from server map
         _map.Set clientMap

    let getDoc (docName : string) =
        _map.View.Map(fun m -> m.[docName].Doc(m))
        |> Doc.EmbedView

До сих пор я узнал, что простой возврат карты через Rpc во время afterRender не будет работать - либо возвращаются общие объекты JS, либо я получаю сериализацию ошибка. Похоже, что это ожидаемое поведение для удаленного взаимодействия WebSharper и взаимодействия между сервером и клиникой.

Я знаю, что мог бы просто реализовать свои ClientModule.obtainObject, жестко закодировав A экземпляров внутри своей карты , и это сработает , если я это сделаю, но мне нужно избегать этой части. Модуль, который я разрабатываю, не должен знать точное отображение или реализацию типов, унаследованных от A (например, B и C), а также с какими именами они были связаны.

Какие еще подходы мне нужно использовать для передачи информации от серверной карты объектов клиенту? Может быть, использовать что-то вроде Quotation.Expr<A> в моем коде?

Обновление 1: Мне не обязательно создавать экземпляры объектов на сервере. Может быть, есть способ отправить информацию сопоставления клиенту и позволить ему каким-либо образом создать экземпляр?

Обновление 2: Вот репозиторий github с простым представлением того, что у меня работает до сих пор

Обновление 3: Альтернативным подходом было бы сохранить на сервере маппинг, который бы использовал имя моего типа объекта вместо его экземпляра (Map<string, string>). Теперь, если мой клиентский код видит ClientAode.C того, что является полным именем типа, возможно ли вызвать конструктор по умолчанию этого типа полностью из JavaScript?

1 Ответ

0 голосов
/ 16 января 2019

Вот еще один дубль

В этом случае я создаю словарь под названием types, который дает каждому классу уникальный идентификатор на основе номера файла и строки. Версии сервера и клиента немного отличаются. Версия сервера использует имя типа в качестве ключа, а клиент использует номер файла и строки в качестве ключа (Client.fs):

    let types = new System.Collections.Generic.Dictionary<string, string * A>()

    let registerType line (a:'a) =
        if IsClient 
        then types.Add(line                  , (line, a :> A) )     
        else types.Add(typedefof<'a>.FullName, (line, a :> A) )

    registerType (__SOURCE_FILE__ + __LINE__) <| C()
    registerType (__SOURCE_FILE__ + __LINE__) <| D()

    let fixType v =
        match types.TryGetValue v with
        | false, _         -> C() :> A
        | true , (line, a) -> a

    let fixMap (m:Map<string, string>) =
        m |> Seq.map (fun kvp -> kvp.Key, fixType kvp.Value) |> Map


[<JavaScript>]
module Client =

    let getDoc (m:Map<string, string>) (docName : string) =
        let m = ClientCode.fixMap m
        m.[docName].Doc(m)

На стороне сервера я изменил _map, который был Map<string,ClientCode.A> на Map<string, string>. Клиент делает то же самое, но в обратном порядке.

Словарь types действует буквально как словарь как для сервера, так и для клиента для перевода туда и обратно между уникальным именем и фактическим объектом.

(Site.fs):

[< JavaScript false >]
module Site =
    open WebSharper.UI.Html

    let HomePage _map ctx =
        Templating.Main ctx EndPoint.Home "Home" [
            Doc.ClientSide <@  Client.getDoc _map "C" @>
        ]

    let mutable _map : Map<string, string> = Map.empty

    let addMapping<'T> name = 
        match ClientCode.types.TryGetValue (typedefof<'T>.FullName) with
        | false,_         -> printfn "Could not map %s to type %s. It is not registered" name (typedefof<'T>.FullName)
        | true ,(line, a) -> 
        _map <- _map |> Map.add name line

    addMapping<ClientCode.C> "C"
    addMapping<ClientCode.D> "D"


    [<Website>]
    let Main =
        Application.MultiPage (fun ctx endpoint ->
            match endpoint with
            | EndPoint.Home -> HomePage _map ctx
        )
...