I am trying out the example snippet titled "Adding client-side functionality" from the following page :
https://developers.websharper.com/docs/v4.x/fs/overview
It looks a bit outdated and doesn't compile as is, so based on the original repository where that code was snipped from, this is what I have now.
namespace TestSuaveWs
open WebSharper
open WebSharper.Sitelets
open WebSharper.UI
open WebSharper.UI.Html
open WebSharper.UI.Client
module Server =
[<Rpc>]
let DoWork (s: string) =
async {
return System.String(List.ofSeq s |> List.rev |> Array.ofList)
}
[<JavaScript>]
module Client =
open WebSharper.JavaScript
open WebSharper.Html.Client
let Main () =
let input = Input [ Attr.Value "" ]
let output = H1 []
Div [
input
Button [ Text "Send" ]
|>! OnClick (fun _ _ ->
async {
let! data = Server.DoWork input.Value
output.Text <- data
}
|> Async.Start
)
HR []
H4 [ Class "text-muted" ] -- Text "The server responded:"
Div [ Class "jumbotron" ] -< [ output ]
]
module TheSite =
open WebSharper.UI.Server
[<Website>]
let MySite =
Application.SinglePage (fun ctx ->
Content.Page(
Body = [
h1 [] [ text "Say Hi to the server" ]
div [] [ client <# Client.Main() #> ]
]
)
)
open global.Suave
open Suave.Web
open WebSharper.Suave
let webPart = WebSharperAdapter.ToWebPart(MySite, RootDirectory="../..")
Then there's the main program.
namespace TestSuaveWs
module Main =
open System
open System.Threading
open Suave
[<EntryPoint>]
let main argv =
let cts = new CancellationTokenSource()
let conf = { defaultConfig with cancellationToken = cts.Token }
let listening, server = startWebServerAsync conf TheSite.webPart
Async.Start(server, cts.Token)
printfn "Make requests now"
Console.ReadKey true |> ignore
cts.Cancel()
0
The program runs, and I can see the text "Say Hi to the server" on localhost:8080, but there is nothing below that text. A picture on the page with the example shows what it should look like. There's supposed to be a text input field, a button, and a reply text.
When I open Developer Tools in my Chrome browser, I can see that there's a bunch of similar messages, differing only in the javascript filename, that says "Failed to load resource: the server responded with a status of 404 WebSharper.Main.min.js:1 (Not Found)"
There are no *.js files in the bin\Debug folder. I am running in VS 2019, with .NET Framework 4.7.1.
What am I missing?
I wasn't aware that this very example was available as a template named "WebSharper 4 Suave-hosted Site", which I only found after downloading and installing the WebSharper vsix. That template spun up a project doing exactly what I tried to achieve. So that's the answer. I wish this was hinted at in the documentation page.
Related
This never prints any events to the console (after "listening for new security events..."), though when I fire up the Event Viewer app it shows security events coming in. Thoughts on what I could be doing wrong?
open System
open System.Diagnostics
open System.Security.Principal
open System.Threading
let logName = "security"
let DumpEventLog desc =
use log = new System.Diagnostics.EventLog (logName, desc)
log.EnableRaisingEvents <- true;
printfn "listening for new security events...";
log.EntryWritten.Add (fun ent ->
let ent = ent.Entry in
printfn "entry written: %d %s %s" ent.InstanceId (ent.TimeGenerated.ToString())
ent.Message);
[<EntryPoint>]
let main _argv =
let isAdministrator =
let id = WindowsIdentity.GetCurrent () in
let p = WindowsPrincipal id in
p.IsInRole WindowsBuiltInRole.Administrator
in
let () =
if not isAdministrator
then printfn "need admin privs to run!"
else DumpEventLog "."
in
while true do
Thread.Sleep 5000
done;
0
I think it has something to do with the way your program is written. For example, when DumpEventLog() exits, the log is disposed - but then the program sleeps for five seconds. I don't see how it would catch an event in that state.
This works for me:
open System.Diagnostics
open System.Threading
[<EntryPoint>]
let main _argv =
use log = new EventLog ("security", ".")
log.EnableRaisingEvents <- true
printfn "listening for new security events..."
log.EntryWritten.Add (fun e ->
printfn "Entry written: %d %O %s" e.Entry.InstanceId e.Entry.TimeGenerated e.Entry.Message)
Thread.Sleep Timeout.Infinite
0
I'm trying to write some logs in an Expecto test, however I can't figure out how to get anything to be logged. Is there a very simple example of this somewhere? Currently I have:
module Test
open Expecto
open Expecto.Logging
open Expecto.Logging.Message
open Hopac
open Logary.Configuration
open Logary.Adapters.Facade
open Logary.Targets
[<Tests>]
let tests =
test "A simple test" {
let logger = Log.create "asdf.qwer"
let one = 1
ignore (logger.logWithAck Debug (eventX "asdf"))
Expect.equal one 1 "Should equals 1"
}
[<EntryPoint>]
let main argv =
let logary =
Config.create "MyProject.Tests" "localhost"
|> Config.targets [ LiterateConsole.create LiterateConsole.empty "console" ]
|> Config.processing (Events.events |> Events.sink ["console";])
|> Config.build
|> run
LogaryFacadeAdapter.initialise<Expecto.Logging.Logger> logary
// Invoke Expecto:
runTestsInAssemblyWithCLIArgs [] argv
I am quite new to using WebSharper and I might be doing things the wrong way.
My goal is to be able to update the contents of my page as a result of user actions by updating a Var<Doc> variable representing a portion of the page to be updated. I'd be happy to know if I could update a Var<Doc> from server-side code and have it reflect in the user's browser.
Below is a quick example:
let TestPage ctx =
let clientPart = Var.Create <| Doc.Empty
clientPart .Value <- div [] [ text "This content is dynamically inserted" ]
Templating.Main ctx EndPoint.Home "Home" [
h1 [] [text "Below is a dynamically inserted content:"]
div [] [ client <# clientPart .View |> Doc.EmbedView #> ]
]
The error I receive is:
System.Exception: Error during RPC JSON conversion ---> System.Exception: Failed to look up translated field name for write' in type WebSharper.UI.Elt with fields: docNode, elt, rvUpdates, updates
The WebSharper 4 documentation regarding Views also states:
It will only be run while the resulting View is included in the document using one of these methods:
Doc.BindView
Doc.EmbedView
textView
and etc.
A similar error is produced if I try this instead:
type SomeTemplate = Template<"SomeTemplate.html">
clientDoc.Value <- SomeTemplate().Doc()
In the above code, Templating.Main is the same as in the default WebSharper project:
module Templating =
...
let Main ctx action (title: string) (body: Doc list) =
let t = MainTemplate().Title(title).MenuBar(MenuBar ctx action).With("Body", body)
let doc : Doc = t.Doc()
doc |> Content.Page
Here is an example calling an RPC on the server side and storing it into a client Var<>:
module ServerFunctions =
let mutable ServerState = ("Zero", 0)
let [< Rpc >] addToState n = async {
let state, counter = ServerState
let newCounter = counter + n
let newState = if newCounter = 0 then "Zero" else "NonZero"
ServerState <- newState, newCounter
return newState
}
[< JavaScript >]
module ClientFunctions =
open WebSharper
open WebSharper.UI
open WebSharper.UI.Html
open ServerFunctions
let zeroState = Var.Create "do not know"
let clientDoc() =
div [] [
h1 [] [ text "State of zero on server:" ]
h2 [] [ text zeroState.V ]
Doc.Button "increment" [] (fun () -> async { let! state = addToState 1
zeroState.Set state
} |> Async.Start)
Doc.Button "decrement" [] (fun () -> async { let! state = addToState -1
zeroState.Set state
} |> Async.Start)
]
module Server =
open global.Owin
open Microsoft.Owin.Hosting
open Microsoft.Owin.StaticFiles
open Microsoft.Owin.FileSystems
open WebSharper.Owin
open WebSharper.UI.Server
open WebSharper.UI.Html
type EndPointServer =
| [< EndPoint "/" >] Hello
| About
let url = "http://localhost:9006/"
let rootdir = #"..\website"
let site() = WebSharper.Application.MultiPage(fun context (s:EndPointServer) ->
printfn "Serving page: %A" s
Content.Page(
Title= ( sprintf "Test %A" s)
, Body = [ h1 [] [ text <| sprintf "%A" s ]
Html.client <# ClientFunctions.clientDoc() #> ])
)
I want to build a simple counter with Suave.
[<EntryPoint>]
let main argv =
let mutable counter = 0;
let app =
choose
[
GET
>=> choose
[
path "/" >=> OK "Hello, world. ";
path "/count" >=> OK (string counter)
]
POST
>=> choose
[
path "/increment"
>=> (fun context -> async {
counter <- counter + 1
return Some context
})
]
]
startWebServer defaultConfig app
0
However, with my current solution, the count at /count never updates.
I think this is because the WebPart is computed when the app is launched, instead of for each request.
What is the best way to achieve this in Suave?
You are right in the assumption that Webparts are values, so computed once. (See this).
You need to use a closure to get what you want:
path "/count" >=> (fun ctx ->
async {
let c = counter in return! OK (string c) ctx
})
So I have my server set up very simply. If the path is of the form /article/something, it should serve up the static file something.html within the folder static. For some reason, the Files.file webpart is apparently returning None. I tacked on the OK "File Displayed" webpart to verify that this is the case. The OK never executes.
let app =
choose [
pathScan "/article/%s" (fun article ->
let name = sprintf "%s.html" article
Console.WriteLine name
Files.file name >=> OK "File Displayed")
]
let config =
{ defaultConfig with homeFolder = Some (Path.GetFullPath "./static") }
[<EntryPoint>]
let main args =
startWebServer config app
0
Interestingly enough, the Console.WriteLine name line executes perfectly and I see something.html in the console window when I execute this. It appears the problem is exclusively Files.file name returning None.
The file something.html definitely exists in the static folder, so that's not the problem .
Any ideas on what might be causing this?
Here are some parts to troubleshoot static file serving issues
let troubleShootExtensionPart extensionToCheck :WebPart =
fun ctx ->
match extensionToCheck with
| null | "" -> ServerErrors.INTERNAL_ERROR "Extension Error not supplied, part is not set up correctly"
| x when not <| x.StartsWith "." -> ServerErrors.INTERNAL_ERROR "Extensions start with a '.', part is not set up correctly"
| _ ->
let mtm = ctx.runtime.mimeTypesMap
match mtm extensionToCheck with
| None ->
sprintf "%s is not supported by the mime types map, compose your mime type with the `defaultMimeTypesMap`" extensionToCheck
|> RequestErrors.FORBIDDEN
| Some x ->
sprintf "%s is supported and uses '%s', compression on? : %A" extensionToCheck x.name x.compression
|> OK
|> fun wp -> wp ctx
example consumption with a wildcard so if no routes match you get some diagnostic info
#if DEBUG
pathScan "/checkExtension/%s" (fun name -> troubleShootExtensionPart name)
// catch all
(fun ctx -> sprintf "404, also homeFolder resolves to %s" (Path.GetFullPath ".") |> RequestErrors.NOT_FOUND |> fun wp -> wp ctx)
#endif