[英]WebSharper - How to expose dynamically mapped strategy-pattern objects on the server to the client?
I am at the process of learning WebSharper, and I am struggling with making some of my logic work on the client-side.我正在学习 WebSharper,我正在努力使我的一些逻辑在客户端工作。
I have a few server-side objects with inheritance hierarchy, which I need to expose to the client-side.我有一些具有继承层次结构的服务器端对象,我需要将它们公开给客户端。 They deal with generating different parts of the page as doc fragments -- but on the client.他们处理将页面的不同部分生成为文档片段——但在客户端上。
[<JavaScriptExport>]
type [<AbstractClass>] A() =
abstract member Doc: Map<string, A> -> Doc
...
[<JavaScriptExport>]
type [<AbstractClass>] B() =
inherit A()
[<JavaScriptExport>]
type C() =
inherit B()
The post is updated , see history for the older version.帖子已更新,请参阅旧版本的历史记录。
In my server-side code I have a map associating each object with a given name (as in the strategy design pattern):在我的服务器端代码中,我有一个将每个对象与给定名称相关联的映射(如在策略设计模式中):
let mutable objectMap : Map<string, A> = Map.empty
At some point, the map gets filled with data.在某些时候,地图会充满数据。 This happens once only in the application initialization phase, but is a result of the server-side backend logic.这仅在应用程序初始化阶段发生一次,但它是服务器端后端逻辑的结果。
Now, I need to use those objects on the client-side only, like in the overly-simplified snippet below:现在,我只需要在客户端使用这些对象,就像下面过于简化的代码段一样:
[<JavaScriptExport>]
type C() =
inherit B() // or inherit A()
override this.Doc map =
div [] [ map.["innerDoc"].Doc(map)
On the server-side end I would have:在服务器端,我会有:
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
The ClientCode
module would be something that gets compiled to JS and would look like this: 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
So far I've found out that simply returning the map via an Rpc
during the afterRender
would not work -- either generic JS objects are being returned, or I am receiving a serialization error.到目前为止,我发现在afterRender
期间通过Rpc
简单地返回地图是行不通的——要么是返回了通用 JS 对象,要么是我收到了序列化错误。 Looks like this is the expected behavior for the WebSharper remoting and clinet-server communication.看起来这是 WebSharper 远程处理和客户端服务器通信的预期行为。
I know I could just implement my ClientModule.obtainObject
by hardCoding the A
instances inside my map and it does work if I do so, but I need to avoid that part.我知道我可以通过在我的地图中硬编码A
实例来实现我的ClientModule.obtainObject
并且如果我这样做它确实可以工作,但我需要避免那部分。 The module I am developing does not have to know the exact mapping or implementation of the types inheriting from A
(like B
and C
for example), nor what names they have been associated with.我正在开发的模块不必知道从A
继承的类型(例如B
和C
)的确切映射或实现,也不必知道它们与哪些名称相关联。
What other approaches I need to use to pass the information from the server-side object map to the client?我需要使用哪些其他方法将信息从服务器端对象映射传递到客户端? Maybe use something like Quotation.Expr<A>
in my code?也许在我的代码中使用Quotation.Expr<A>
类的东西?
Update 1: I do not necessarily need to instantiate the objects on the server.更新 1:我不一定需要实例化服务器上的对象。 Maybe there is a way to send the mapping information to the client and let it do the instantiation somehow?也许有一种方法可以将映射信息发送到客户端并让它以某种方式进行实例化?
Update 2: Here is a github repo with a simple representation of what I have got working so far更新 2:这是一个github 存储库,其中简单表示了我目前所做的工作
Update 3: An alternative approach would be to keep on the server a mappping that would use the name of my object type instead of an instance of it ( Map<string, string>
).更新 3:另一种方法是在服务器上保留一个映射,该映射将使用我的对象类型的名称而不是它的实例( Map<string, string>
)。 Now if my client code sees ClientAode.C
of whatever the full type name is, is it possible to invoke the default constructor of that type entirely from JavaScript?现在,如果我的客户端代码看到ClientAode.C
的完整类型名称是什么,是否可以完全从 JavaScript 调用该类型的默认构造函数?
Here is another take这是另一种看法
In this case I create a dictionary called types
that gives each class a unique identifier based on the file and line number.在这种情况下,我创建了一个名为types
的字典,它根据文件和行号为每个类提供一个唯一标识符。 The server and client versions are slightly different.服务器和客户端版本略有不同。 The server version uses the type name as the key while the client uses the file & line number as a key (Client.fs):服务器版本使用类型名称作为键,而客户端使用文件和行号作为键(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)
On the server side I changed the _map
that was Map<string,ClientCode.A>
to Map<string, string>
.在服务器端,我将Map<string,ClientCode.A>
的_map
更改为Map<string, string>
。 The client does the same thing but in reverse.客户端做同样的事情,但相反。
The dictionary types
acts literally as a dictionary for both the server and the client to translate back and forth between unique name and actual object.字典types
实际上充当服务器和客户端的字典,以便在唯一名称和实际对象之间来回转换。
(Site.fs): (站点.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
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.