Skip to content

Websharper Proxy Project

WebSharper lets you bring your FSharp code to the client-side. For types in your WebSharper project to be used at both server and client sides, you annotate them with [<JavaScript>].

For types that were compiled without WebSharper, you need to define a proxy type, which will be compiled to javascript and be used in place of the compiled type. This is done easily, as this example from the official documentation shows:

[<Proxy(typeof<Dictionary<_, _>)>]
[<JavaScript>]
type MyDictionary<'K, 'V> () =
    let mutable count = 0
    member this.Count = count

I’m working on a application for which I want to be able to provide multiple front-ends: web, CLI, … To that end I put all core functionality in a library to be reused for the different front-ends, and it defines tens of type I need to handle at the client side of the WebSharper front-end. But as these types are in a different project that needs to stay WebSharper-unaware, the above procedure would require to copy the file and edit them to add annotations. This is time-consuming and error-prone, but luckily, as pointed out by Jand42 from the WebSharper team there’s a better solution: WebSharper Proxy projects!

You create your new proxy project with dotnet new websharper-prx -lang f# -n wsproxies (if you don’t have the WebSharper templates installed, issue the command dotnet new -i WebSharper.Templates). This creates a proxy project in the directory wsproxies with 3 files:

wsproxies.fsproj  Readme.md  wsconfig.json

The Readme.md file has all information to get you started, which is illustrated below. First you need to determine the assembly name in which the type you want to proxy are located. You assign the name to proxyTargetName in wsconfig.json.

After that, you copy or link the F# source files for which you want to define proxies and add them in wsproxies.fsproj. Under linux, you can softlink to the files:

ln -s ../lib/MyTypes.fs

and add it to the fsproj:

  <ItemGroup>
    <None Include="Readme.md" />
    <None Include="wsconfig.json" />
    <Compile Include="MyTypes.fs" />
  </ItemGroup>

You can build the project with dotnet build. If there are types references from MyTypes.fs that are not available, you will get an error. You then either need to add the missing types to the proxy project, or removes those from the file in the project. I had some types that make only sense on the server side. One approach is to split the file with the server-side only content extracted. For example, I had this file:

type Value =
    | Simple of string
    | LongText of string
     with
    static member GetAction (detailId:DetailId)(className:string)(value:string) : DBAction.DBAction<Value>=
      match className with
      |"SimpleDetailValue" -> DBAction.retn (Simple value)
      |"LongTextDetailValue" -> DBAction.retn (LongText value)

But DBAction only makes sense on the server side, to define requests for the database. I ended up splitting this file in a Base.fs file:

module Base
type Value =
    | Simple of string
    | LongText of string

and an BaseExt.fs file:

module BaseExt
open Base
type Value
     with
    static member GetAction (detailId:DetailId)(className:string)(value:string) : DBAction.DBAction<Value>=
      match className with
      |"SimpleDetailValue" -> DBAction.retn (Simple value)
      |"LongTextDetailValue" -> DBAction.retn (LongText value)

In the lib project, which uses the BaseExt functionality, I needed to add open BaseExt where relevant. But this allows me to soft-link to Base.fs and avoid error-prone duplication.

Using the proxy types in your WebSharper application is easy too: just include the wsproxies.fsproj in your app’s fsproj file:

  <ItemGroup>
    <ProjectReference Include="../wsproxies/wsproxies.fsproj" />
  </ItemGroup>

There might be some errors wrongly reported by your editor because the wsproxies library only include javascript and not dotnet code. In Visual Studio, you can, as shared by Jand42, go to Settings > Text Editor > F# > Performance and disable checkbox for "Enable in-memory cross project references", then code service will look at dll only and there will be no errors because of that.. I’m using VSCode though, and there I don’t know if there’s a similar fix. I’ll edit this if I learn more.

Important notes

A proxy requires a dotnet assembly backer

In the example above, we copy a file used in a dotnet assembly to make it available at the client side. If you add a file to the proxy that is not present in a dotnet assembly, you will get an error The namespace or module 'MyModel' is not defined. even though Ionide will probably give you the type hints. Don’t forget that, you might loose quite some time on this.

You can inpect the javascript generated

To output the generated javascript under a directory for you to inspect, add these lines to wsconfig.json:

  "outputDir": "Content",
  "javascriptExport": true

(from Intellifactory’s blogs)