How does Elmish.WPF marshall secondary windows into the main function? - f#

How do the various #Windows (mainWindow, createWindow1, and createWindow2) get marshalled as parameters when calling the Elmish.WPF NewWindow code sample Program.fs as seen here ...
let main mainWindow (createWindow1: Func<#Window>) (createWindow2: Func<#Window>) =
let logger =
LoggerConfiguration()
.MinimumLevel.Override("Elmish.WPF.Update", Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Elmish.WPF.Bindings", Events.LogEventLevel.Verbose)
.MinimumLevel.Override("Elmish.WPF.Performance", Events.LogEventLevel.Verbose)
.WriteTo.Console()
.CreateLogger()
let createWindow1 () = createWindow1.Invoke()
let createWindow2 () =
let window = createWindow2.Invoke()
window.Owner <- mainWindow
window
let init () = App.init
let bindings = App.bindings createWindow1 createWindow2
WpfProgram.mkSimple init App.update bindings
|> WpfProgram.withLogger (new SerilogLoggerFactory(logger))
|> WpfProgram.startElmishLoop mainWindow
???
What plumbing is happening in .NET, WPF, Elmish.WPF that organizes the reference found at the top of NewWindows.xaml to NewWindow.Core so that the F# function main is called with the windows marshalled and passed in in the right order?

As I finished the above question I determined what I think is the answer. Left the question in case it helps someone else. -RCHF
For reference I have thoroughly read the the Elmish.WPF Tutorial.
In arriving at this answer I found Getting started with Elmish.WPF, item #7 and #8 helpful.
In Elmish.WPF, the MainWindow.xaml`` file calls the C# code-behind file MainWindow.xaml.cs*must* have aStartElmish``` function with the following code …
private void StartElmish(object sender, EventArgs e)
{
this.Activated -= StartElmish;
Program.main(MainWindow, () => new Window1(), () => new Window2());
}
Here you see references to all three windows in the Elmish.WPF NewWindow code sample (see C# XAML) and F# Core) being marshalled into the subordinate F# main function.

Related

How do I create a C# style async member in F#?

In C# you can annotate methods with async like this:
class Foo
{
public async void Bar()
{
}
}
This is different to an F# async; I believe that in F# these are called tasks.
So, how do I write a C#-style async member function in F#?
// Not real code
type Foo () =
member async this.Bar () =
()
The solution must compile to IL with the same public interface as the C# above.
A C# async method is just a method returning a value of type Task<T>. The C# compiler uses the async keyword to determine that you are allowed to use await inside the code block, but as far as I know, it is not represented in any way in the compiled code.
You say "the solution must compile to the same IL" - that's not going to be easily possible, because F# implements asynchronous operations differently. However, you can get it to compile to IL that has the same public interface using something like this:
type Foo () =
member this.Bar () = Async.StartAsTask <| async {
// yadda yadda
}
The asynchronous operation is implemented using standard F# async workflow, so under the cover, this creates an F# Async<T>, but the Async.StartAsTask operation turns that into a Task<T> type, which is what C# expects.
EDIT: Another alternative is to use the TaskBuilder computation expression, which lets you directly create .NET tasks using something like this:
type Foo () =
member this.Bar () = task {
// yadda yadda
}
Async.StartAsTask should do it. This will give you Threading.Tasks.Task<'a>
let bar =
async{
return ()
}
|>Async.StartAsTask

F# XUnit test deadlocks when initializer has dependency

I am having problems with a test in a netcoreapp2.2 .net core test project.
Before the tests starts I need to fetch some data that will be shared between the tests.
However, when running the following test from command line it will hang.
Executing the test like this:
dotnet test --filter "Test async initialization"
The faulty code looks like this:
let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
let luke =
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
While if I put the creation of the HttpClient inside the luke fetcher like this it works:
let luke =
let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
This means I can't share the same HttpClient between different fetchers.
Anyone knows what is going on, and how to share the same client between multiple functions?
The problem is caused because the "initialization" code isn't really initialization code. Those are just two static fields that will be evaluated only when requested. If you debug the unit test you'll see that c and luke execute only when execution reaches the line
Assert.NotNull(luke)
If you use a decompiler like JustDecompile you'll see that the module's code is placed in a static class called Tests$ whose static constructor initializes its own c and luke properties. Test async initialization is placed in a Tests class with its own c and luke properties that delegate to the Tests$ class.
Long story sort, none of that "initialization" code runs until the value of luke is requested. I don't know why that ends up blocking the test, most likely there's a conflict with the test runner. It's enough that the initialization code doesn't run at initialization.
To make the initialization code run when it should, a "classic" test type can be used :
namespace MyTests
open System
open Xunit
open System.Net.Http
open Xunit.Abstractions
type Tests() =
static let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
static let luke =
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
static do
//Pity we can't actually print here
printfn "Even more initialization!"
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
The static bindings in this case are executed before any of the tests, as they should, and the code doesn't block. This initialization will happen only once.
To capture output the test class constructor should accept an ITestOutputHelper parameter. That's easy to do now that we have a test class :
type Tests(output:ITestOutputHelper) =
...
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
output.WriteLine "It worked!"
Per-test initialization should go in a do block :
type Tests(output:ITestOutputHelper) =
do
output.WriteLine "This prints before each test"

Render component in Fable-Elmish

I have set up a standard SAFE application with the dotnet new SAFE command resulting in the two Server and Client projects (and the Shared folder).
The Client project has the Client.fs file with the view function and the bootstrapping code, which is just as the template generates it:
Program.mkProgram init update view
#if DEBUG
|> Program.withConsoleTrace
|> Program.withHMR
#endif
|> Program.withReact "elmish-app"
#if DEBUG
|> Program.withDebugger
#endif
|> Program.run
I have now added a simple component:
type MyComponentProps = {
data : int
}
type MyComponent(initialProps) =
inherit Component<MyComponentProps, obj>(initialProps)
do
base.setInitState({ data = initialProps.data })
override this.render() =
h1 [] [str ("Hello World " + this.props.data.ToString())]
override this.componentDidMount() =
// Do something useful here... ;)
console.log("Component Did Mount!")
But I cannot figure out how to properly instantiate this component in the view function. I really think this ought to work:
let view (model : Model) (dispatch : Msg -> unit) =
MyComponent({data = 42}) :> ReactElement
But this results in the browser in the - now - dreaded:
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state}). If you meant to render a collection of children, use an array instead. I really don't get that since the type is ReactElement which should be fine...
Instantiating the component manually and calling render() does sort of work, but of course componentDidMount() is not called:
let myComponent = MyComponent({data = 42})
myComponent.render()
So: How do I get MyComponent properly instantiated and "injected" into React?
You can use ofType from Fable.Helpers.React
open Fable.Helpers.React
let view (model : Model) (dispatch : Msg -> unit) =
ofType<MyComponent,_,_> { data = 42 } []

Missing method exception while InvokeMember

I'm creating a COM component in F#. The component is expected to be used from scripting.
The component code:
namespace WebUIPlugin
open System
open System.Windows
open System.Runtime.InteropServices
[<Guid("BAEF0C5B-EFA5-4868-8342-7A1E6F8F7AF4")>]
type IPlugin =
[<DispId(1)>]
abstract OpenFormFromFile : path:string -> unit
[<Guid("8D71E2DB-D718-4595-B856-58D14EEAEBB2");
ClassInterface(ClassInterfaceType.None);
ComVisible(true)>]
type Plugin = class
new () = {}
interface IPlugin with
member this.OpenFormFromFile(path) =
let showWindow =
let window = Window()
window.Show
UI.spawn showWindow |> ignore
end
end
I'm registering it with regasm /codebase Plugin.dll and it works well from scripting cscript test.js.
test.js is following:
var obj = new ActiveXObject("WebUIPlugin.Plugin");
obj.OpenFormFromFile("");
It even stops on breakpoint in OpenFormFromFile. So good so far.
Unfortunately, I cannot make it work from F#/C#:
let objectType = Type.GetTypeFromProgID("WebUIPlugin.Plugin")
let handler = Activator.CreateInstance(objectType)
objectType.InvokeMember("OpenFormFromFile", BindingFlags.InvokeMethod, null, handler, [|""|]) |> ignore
var objectType = Type.GetTypeFromProgID("WebUIPlugin.Plugin");
dynamic handler = Activator.CreateInstance(objectType);
objectType.InvokeMember("OpenFormFromFile", BindingFlags.InvokeMethod, null, handler, new object[]{""});
The code throws exception:
An unhandled exception of type System.MissingMethodException occurred in mscorlib.dll
Additional information: Attempted to access a missing member.
Everything (component, test C# and F# projects, regasm, cscript) is either in x64 or x86 consistently. With the same result - WSH script works, .NET assembly does not.

Awaiting an IAsyncOperation in F#

I have the following code in F#:
let CreateSampleDataFromJson<'T>(path) =
let uri = new Uri(path)
async {
let file = StorageFile.GetFileFromApplicationUriAsync(uri)
let jsonText = FileIO.ReadTextAsync(file)
return JsonObject<'T>.Parse(jsonText)
}
The problem I'm having is that file is an IAsyncOperation<StorageFile> and not a StorageFile as ReadTextAsync expects.
In C# you can do something similar to this:
var file = await StorageFile.GetFileFromApplicationUriAsync(uri)
i.e.
public async Task<T> CreateSampleDataFromUrl<T>(string path)
{
var uri = new Uri(path);
var file = await StorageFile.GetFileFromApplicationUriAsync(uri);
var jsonText = await FileIO.ReadTextAsync(file);
return JsonObject<T>.Parse(jsonText);
}
The problem is that I don't know how to await an IAsyncOperation in F#. The usual let! doesn't work. i.e. the following fails to compile:
async {
let! file = StorageFile.GetFileFromApplicationUriAsync(uri)
With the compiler error:
error FS0001: This expression was expected to have type Async<'a> but here has type IAsyncOperation<StorageFile>
I found a document that said there's an AsTask() extension method defined in the System.WindowsRuntimeSystemExtensions class which I can use as follows:
let! file = StorageFile.GetFileFromApplicationUriAsync(uri).AsTask() |> Async.AwaitTask
Is there a standard way of doing this or something available in an F# library somewhere that makes this a bit nicer?
Your solution seems fine by me. If you're looking for a nicer syntax, how about rolling it into a function like this (without the possibly gratuitous type annotations):
let await<'a> (op: IAsyncOperation<'a>) : Async<'a> =
op.AsTask() |> Async.AwaitTask
This will give you the almost exact same syntax you'd see in c#:
async {
let! file = await <| StorageFile.GetFileFromApplicationUriAsync(uri)
...
}
The compiler errors you were getting with your previous approaches are to be expected. All async workflow cares about is the F#-specific Async type. This type gives you a way to interop with the rest of .NET world through Tasks, but that's it. IAsyncOperation is from a 'different part of the world', I wouldn't expect F# core libraries to support it anytime soon.

Resources