objects |> functions


Quatro - Step 4

Dependencies

Our dependencies don't change that much for this project; we won't change it to reference Microsoft.AspNetCore.App, but we'll add the RavenDB distributed cache, the MiniGuid package, and the ASP.NET Core session package. The new references are:

1: 
2: 
3: 
AspNetCore.DistributedCache.RavenDB
Microsoft.AspNetCore.Session
MiniGuid

Then, run paket install to register these as as part of this project.

The App.fs File

For Uno, we made a lot of changes to Startup.cs; since we initialized everything a bit more functionally, we'll make these changes in App.fs instead.

First up, we'll add some opens:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
    open AspNetCore.DistributedCache.RavenDB
    open System.Security.Cryptography.X509Certificates
    // ...
    let services (svc : IServiceCollection) =
      let config = svc.BuildServiceProvider().GetRequiredService<IConfiguration> ()
      let cfg = config.GetSection "RavenDB"
      let store =
        let st =
          new DocumentStore
           (Urls = [| cfg.["Url"] |],
            Database = cfg.["Database"],
            Certificate =
              match cfg.["Certificate"] with
              | null -> null
              | _ -> new X509Certificate2 (cfg.["Certificate"], cfg.["Password"]))
        st.Conventions.CustomizeJsonSerializer <-
          fun x ->
              x.Converters.Add (CompactUnionJsonConverter ())
        st.Initialize ()

As with our others, we're adding certificate capability to the connection, and then initializing it into a variable, which allows us to use our initialized document store to create indexes, register it with DI, and use it to set up our session cache.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
      IndexCreation.CreateIndexes (typeof<Categories_ByWebLogIdAndSlug>.Assembly, store)
      svc.AddSingleton(store)
        .AddDistributedRavenDBCache(fun opts -> opts.Store <- store)
        .AddSession(
          fun opts ->
              opts.Cookie.Name        <- ".Quatro.Session"
              opts.Cookie.IsEssential <- true)
        .AddGiraffe ()
      |> ignore

The above is pretty much a C#-to-F# translation of the remainder of ConfigureServices from Uno, except for the cookie name and the .AddGiraffe () call. The fact that the latter wasn't on our previous Giraffe steps is technically an error, though this is the first step where it will bite us.

...then Testing

Before we tackle ConfigureApp, we need to actually write the handlers that we need for this step. Our handlers won't be one-liners, but they'll still be rather simple. First, create a file Handlers.fs, and add it to the build order just before App.fs. Make the first line module Quatro.Handlers, then we can write our session counter handler:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
  open Giraffe
  open Microsoft.AspNetCore.Http

  let home : HttpHandler =
    fun next ctx ->
        let count =
          (ctx.Session.GetInt32 "Count"
           |> Option.ofNullable
           |> function Some x -> x | None -> 0) + 1
        ctx.Session.SetInt32 ("Count", count)
        text (sprintf "You have visited this page %i times this session" count) next ctx

While we're at it, we'll put in a stub for seed as well, simply returning text "" next ctx.

...then Back to App.fs

Now that we have our two handlers, we need to define routes for Giraffe, so it knows at which path each one should be executed. This is done through Giraffe's routing functions. The simplest set of functions that will get us our results is:

1: 
2: 
3: 
4: 
5: 
6: 
    let webApp : HttpHandler =
      choose [
        route "/"     >=> Handlers.home
        route "/seed" >=> Handlers.seed
        RequestErrors.NOT_FOUND "Not Found"
        ]

Notice the type on webApp - it's our familiar HttpHandler. The >=> operator is what lets us chain these HttpHandlers together. Defined as we did above, we have a nice central place to view all our routes. However, because each of them is an HttpHandler, we could just as easily define routers within handler modules, and attach them to a route group. This is another case where composition gives us flexibility to structure things the way that makes the most sense for the application.

With our router defined, we can now plug it into the application pipeline. We'll also enable sessions, and tie in a quick-and-dirty error handler (defined in our code, but not shown here).

1: 
2: 
3: 
4: 
    let app (app : IApplicationBuilder) =
      app.UseSession()
        .UseGiraffeErrorHandler(errorHandler)
        .UseGiraffe webApp

Data Seeding

The /seed endpoint is familiar. However, as this was the step where we implemented single-case DUs to make our domain less capable of representing an invalid state, there are several changes that will need to be made to the function as it was defined in Tres. Review it to see how we define these.

If we build and run the seed at this point, then look at our documents, something jumps right out at us. Our single-case DUs are serialized as...

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
"WebLogId": {
    "WebLogId": "WebLogs/pmzIKMQtoxtuFGdkXQSBLQFRCS"
},
"AuthorId": {
    "UserId": "Users/DFCHSWudsgsuDMzAHUneCHAjua"
},
// ...
"PublishedOn": {
    "Ticks": 637008955351836900
},
"UpdatedOn": {
    "Ticks": 637008955351836900
},
// ...
"Tags": [
    {
        "Tag": "candidate"
    },
    {
        "Tag": "congress"
    },
    {
        "Tag": "election"
    },
    {
        "Tag": "president"
    },
    {
        "Tag": "result"
    }
]

This is better than Case and Fields pairs, but it's still not quite what we want; we want to be able to write a query that says Where(fun x -> x.WebLogId = [our-id]), not Where(fun x -> x.WebLogId.WebLogId = [our-id]). Plus, the entire reason we're using MiniGuids is to save space, and this takes up more space than we're saving!

Thankfully, though, Json.NET comes to the rescue once again. We can write some really simple converters that will take care of our single-case DUs. Create Data.fs and add it to the list of compiled files between Indexes.fs and Handlers.fs. The top line should be module Quatro.Data, then create a Converters module below it; we'll put all the converters there. The converters themselves don't have much to them; here are converters for Ticks and WebLogIds:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
    /// JSON converter for Ticks
    type TicksJsonConverter () =
      inherit JsonConverter<Ticks> ()
      override __.WriteJson (w : JsonWriter, v : Ticks, _ : JsonSerializer) =
        let (Ticks x) = v
        w.WriteValue x
      override __.ReadJson (r: JsonReader, _, _, _, _) =
        (string >> int64 >> Ticks) r.Value
    /// JSON converter for WebLogId
    type WebLogIdJsonConverter () =
      inherit JsonConverter<WebLogId> ()
      override __.WriteJson (w : JsonWriter, v : WebLogId, _ : JsonSerializer) =
        (WebLogId.toString >> w.WriteValue) v
      override __.ReadJson (r: JsonReader, _, _, _, _) =
        (string >> WebLogId) r.Value

Once we get all our converters defined, we will define an all property on the Converters module. We'll also add the FSharpLu converter to this list, so all will be all the JsonConverters we need for our application, and we can use them consistently throughout the application. (Interestingly, the default serialization of ArticleContent is exactly what we wrote for IArticleContent in Tres, so it's the one type for which we don't need a converter here.)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
    // add
    open Microsoft.FSharpLu.Json
    // ...
    let all : JsonConverter seq =
      seq {
        // other converters
        yield TicksJsonConverter ()
        yield WebLogIdJsonConverter ()
        yield CompactUnionJsonConverter true
        }

Remember our discussion from step 3 about converter order: Json.NET will pick the first converter in registration order that can convert a particular type it's trying to serialize or deserialize. CompactUnionJsonConverter converts anything that looks like a DU, so it needs to be last in the registration order.

Now, we need to let RavenDB know about all these new converters. In App.fs, within the services function, we can remove the open for Microsoft.FSharpLu.Json, add one for Data (our new module). The configuration of the JSON serializers now looks like this:

1: 
2: 
st.Conventions.CustomizeJsonSerializer <-
  fun x -> Converters.all |> List.ofSeq |> List.iter x.Converters.Add

We won't be using it in this application, but Giraffe also uses Json.NET to serialize objects for JSON API requests. Using Converters.all to configure both RavenDB and Giraffe will ensure that, when we are looking at a JSON representation of our domain items, they are consistent and as we expect them to be.

Now, if we run our application, delete all the existing documents from O2F4, and rerun the seed, we should see documents that look more like this:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
"WebLogId": "WebLogs/xGBJypaCimEuLlDDFIpmXWYyQI",
"AuthorId": "Users/UtavDmCkZWzeZQlTAdEvyCtkRb",
// ...
"PublishedOn": 637009756452740200,
"UpdatedOn": 637009756452740200,
// ...
"Tags": [
    "candidate",
    "congress",
    "election",
    "president",
    "result"
]

...which is exactly what we want.

Conclusion

This is the most code we've written so far for this step. However, the majority of it has been at the seams between F# and the object-oriented .NET world. And, each of these pieces are small enough for us to completely understand. On top of that, the result is a system where, while illegal states could still be represented, it would be a lot more work; we are doing our best to make sure we fall into the pit of success.


Back to Step 4

namespace Raven
namespace Raven.Client
namespace Raven.Client.Documents
namespace Raven.Client.Documents.Indexes
Multiple items
type Categories_ByWebLogIdAndSlug =
  inherit AbstractJavaScriptIndexCreationTask
  new : unit -> Categories_ByWebLogIdAndSlug

--------------------
new : unit -> Categories_ByWebLogIdAndSlug
Multiple items
type AbstractJavaScriptIndexCreationTask =
  inherit AbstractIndexCreationTask
  member CreateIndexDefinition : unit -> IndexDefinition
  member Fields : Dictionary<string, IndexFieldOptions> with get, set
  member IsMapReduce : bool
  member Maps : HashSet<string> with get, set

--------------------
AbstractJavaScriptIndexCreationTask() : AbstractJavaScriptIndexCreationTask
namespace Giraffe
namespace Microsoft
namespace Microsoft.AspNetCore
namespace Microsoft.AspNetCore.Http
val home : next:HttpFunc -> ctx:HttpContext -> HttpFuncResult
type HttpHandler = HttpFunc -> HttpFunc
val next : HttpFunc
val ctx : HttpContext
val count : int
(extension) ISession.GetInt32(key: string) : System.Nullable<int>
module Option

from Microsoft.FSharp.Core
val ofNullable : value:System.Nullable<'T> -> 'T option (requires default constructor and value type and 'T :> System.ValueType)
union case Option.Some: Value: 'T -> Option<'T>
val x : int
union case Option.None: Option<'T>
(extension) ISession.SetInt32(key: string, value: int) : unit
val text : str:string -> HttpHandler
val sprintf : format:Printf.StringFormat<'T> -> 'T
val seed : next:HttpFunc -> ctx:HttpContext -> HttpFuncResult
Multiple items
union case WebLogId.WebLogId: string -> WebLogId

--------------------
type WebLogId = | WebLogId of string
Multiple items
val string : value:'T -> string

--------------------
type string = System.String
val toString : x:WebLogId -> string
val x : WebLogId
val y : string
Multiple items
union case Ticks.Ticks: int64 -> Ticks

--------------------
type Ticks = | Ticks of int64
Multiple items
val int64 : value:'T -> int64 (requires member op_Explicit)

--------------------
type int64 = System.Int64

--------------------
type int64<'Measure> = int64
module Configure

from Quatro.App
module Indexes

from Quatro
namespace Microsoft.AspNetCore.Builder
namespace Microsoft.Extensions
namespace Microsoft.Extensions.Configuration
namespace Microsoft.Extensions.DependencyInjection
namespace Microsoft.FSharpLu
namespace Microsoft.FSharpLu.Json
namespace AspNetCore
namespace AspNetCore.DistributedCache
namespace AspNetCore.DistributedCache.RavenDB
namespace System
namespace System.Security
namespace System.Security.Cryptography
namespace System.Security.Cryptography.X509Certificates
val services : svc:IServiceCollection -> unit
val svc : IServiceCollection
type IServiceCollection =
  inherit IList<ServiceDescriptor>
  inherit ICollection<ServiceDescriptor>
  inherit IEnumerable<ServiceDescriptor>
  inherit IEnumerable
val config : IConfiguration
(extension) IServiceCollection.BuildServiceProvider() : ServiceProvider
(extension) IServiceCollection.BuildServiceProvider(validateScopes: bool) : ServiceProvider
(extension) IServiceCollection.BuildServiceProvider(options: ServiceProviderOptions) : ServiceProvider
type IConfiguration =
  member GetChildren : unit -> IEnumerable<IConfigurationSection>
  member GetReloadToken : unit -> IChangeToken
  member GetSection : key:string -> IConfigurationSection
  member Item : string -> string with get, set
val cfg : IConfigurationSection
IConfiguration.GetSection(key: string) : IConfigurationSection
val store : IDocumentStore
val st : DocumentStore
Multiple items
type DocumentStore =
  inherit DocumentStoreBase
  new : unit -> DocumentStore
  member AggressivelyCacheFor : cacheDuration:TimeSpan * ?database:string -> IDisposable + 1 overload
  member BulkInsert : ?database:string * ?token:CancellationToken -> BulkInsertOperation
  member Changes : ?database:string -> IDatabaseChanges + 1 overload
  member DisableAggressiveCaching : ?database:string -> IDisposable
  member Dispose : unit -> unit
  member GetLastDatabaseChangesStateException : ?database:string * ?nodeTag:string -> Exception
  member GetRequestExecutor : ?database:string -> RequestExecutor
  member Identifier : string with get, set
  member Initialize : unit -> IDocumentStore
  ...

--------------------
DocumentStore() : DocumentStore
Multiple items
type X509Certificate2 =
  inherit X509Certificate
  new : unit -> X509Certificate2 + 12 overloads
  member Archived : bool with get, set
  member Extensions : X509ExtensionCollection
  member FriendlyName : string with get, set
  member GetNameInfo : nameType:X509NameType * forIssuer:bool -> string
  member HasPrivateKey : bool
  member Import : rawData:byte[] -> unit + 5 overloads
  member IssuerName : X500DistinguishedName
  member NotAfter : DateTime
  member NotBefore : DateTime
  ...

--------------------
X509Certificate2() : X509Certificate2
   (+0 other overloads)
X509Certificate2(rawData: byte []) : X509Certificate2
   (+0 other overloads)
X509Certificate2(fileName: string) : X509Certificate2
   (+0 other overloads)
X509Certificate2(handle: nativeint) : X509Certificate2
   (+0 other overloads)
X509Certificate2(certificate: X509Certificate) : X509Certificate2
   (+0 other overloads)
X509Certificate2(rawData: byte [], password: string) : X509Certificate2
   (+0 other overloads)
X509Certificate2(rawData: byte [], password: System.Security.SecureString) : X509Certificate2
   (+0 other overloads)
X509Certificate2(fileName: string, password: string) : X509Certificate2
   (+0 other overloads)
X509Certificate2(fileName: string, password: System.Security.SecureString) : X509Certificate2
   (+0 other overloads)
X509Certificate2(rawData: byte [], password: string, keyStorageFlags: X509KeyStorageFlags) : X509Certificate2
   (+0 other overloads)
property DocumentStoreBase.Conventions: Conventions.DocumentConventions
property Conventions.DocumentConventions.CustomizeJsonSerializer: System.Action<Newtonsoft.Json.JsonSerializer>
val x : Newtonsoft.Json.JsonSerializer
property Newtonsoft.Json.JsonSerializer.Converters: Newtonsoft.Json.JsonConverterCollection
System.Collections.ObjectModel.Collection.Add(item: Newtonsoft.Json.JsonConverter) : unit
Multiple items

--------------------
new : ?tupleAsHeterogeneousArray:bool * ?usePropertyFormatterForValues:bool -> CompactUnionJsonConverter
DocumentStore.Initialize() : IDocumentStore
type IndexCreation =
  static member CreateIndexes : assemblyToScan:Assembly * store:IDocumentStore * ?conventions:DocumentConventions * ?database:string -> unit + 1 overload
  static member CreateIndexesAsync : assemblyToScan:Assembly * store:IDocumentStore * ?conventions:DocumentConventions * ?database:string * ?token:CancellationToken -> Task + 1 overload
IndexCreation.CreateIndexes(indexes: System.Collections.Generic.IEnumerable<AbstractIndexCreationTask>, store: IDocumentStore,?conventions: Conventions.DocumentConventions,?database: string) : unit
IndexCreation.CreateIndexes(assemblyToScan: System.Reflection.Assembly, store: IDocumentStore,?conventions: Conventions.DocumentConventions,?database: string) : unit
val typeof<'T> : System.Type
(extension) IServiceCollection.AddSingleton<'TService,'TImplementation (requires reference type and reference type and 'TImplementation :> 'TService)>() : IServiceCollection
(extension) IServiceCollection.AddSingleton<'TService (requires reference type)>() : IServiceCollection
(extension) IServiceCollection.AddSingleton(serviceType: System.Type) : IServiceCollection
(extension) IServiceCollection.AddSingleton<'TService (requires reference type)>(implementationFactory: System.Func<System.IServiceProvider,'TService>) : IServiceCollection
(extension) IServiceCollection.AddSingleton<'TService,'TImplementation (requires reference type and reference type and 'TImplementation :> 'TService)>(implementationFactory: System.Func<System.IServiceProvider,'TImplementation>) : IServiceCollection
(extension) IServiceCollection.AddSingleton<'TService (requires reference type)>(implementationInstance: 'TService) : IServiceCollection
(extension) IServiceCollection.AddSingleton(serviceType: System.Type, implementationType: System.Type) : IServiceCollection
(extension) IServiceCollection.AddSingleton(serviceType: System.Type, implementationFactory: System.Func<System.IServiceProvider,obj>) : IServiceCollection
(extension) IServiceCollection.AddSingleton(serviceType: System.Type, implementationInstance: obj) : IServiceCollection
val AddDistributedRavenDBCache : this:IServiceCollection * options:System.Action<DistributedRavenDBCacheOptions> -> IServiceCollection
val opts : DistributedRavenDBCacheOptions
property DistributedRavenDBCacheOptions.Store: IDocumentStore
val opts : SessionOptions
property SessionOptions.Cookie: AspNetCore.Http.CookieBuilder
property AspNetCore.Http.CookieBuilder.Name: string
property AspNetCore.Http.CookieBuilder.IsEssential: bool
val ignore : value:'T -> unit
val errorHandler : ex:exn -> 'a -> (HttpFunc -> AspNetCore.Http.HttpContext -> HttpFuncResult)
val ex : exn
type exn = System.Exception
val error : string
System.Exception.GetType() : System.Type
property System.Exception.Message: string
property System.Exception.StackTrace: string
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...
System.Console.WriteLine() : unit
   (+0 other overloads)
System.Console.WriteLine(value: string) : unit
   (+0 other overloads)
System.Console.WriteLine(value: obj) : unit
   (+0 other overloads)
System.Console.WriteLine(value: uint64) : unit
   (+0 other overloads)
System.Console.WriteLine(value: int64) : unit
   (+0 other overloads)
System.Console.WriteLine(value: uint32) : unit
   (+0 other overloads)
System.Console.WriteLine(value: int) : unit
   (+0 other overloads)
System.Console.WriteLine(value: float32) : unit
   (+0 other overloads)
System.Console.WriteLine(value: float) : unit
   (+0 other overloads)
System.Console.WriteLine(value: decimal) : unit
   (+0 other overloads)
val clearResponse : next:HttpFunc -> ctx:AspNetCore.Http.HttpContext -> HttpFuncResult
module ServerErrors

from Giraffe.HttpStatusCodeHandlers
val INTERNAL_ERROR : x:'b -> (HttpFunc -> AspNetCore.Http.HttpContext -> HttpFuncResult)
val webApp : HttpHandler
val choose : handlers:HttpHandler list -> next:HttpFunc -> HttpFunc
val route : path:string -> next:HttpFunc -> ctx:AspNetCore.Http.HttpContext -> HttpFuncResult
module Handlers

from Quatro
val home : next:HttpFunc -> ctx:AspNetCore.Http.HttpContext -> HttpFuncResult
val seed : next:HttpFunc -> ctx:AspNetCore.Http.HttpContext -> HttpFuncResult
module RequestErrors

from Giraffe.HttpStatusCodeHandlers
val NOT_FOUND : x:'a -> (HttpFunc -> AspNetCore.Http.HttpContext -> HttpFuncResult)
val app : app:IApplicationBuilder -> unit
val app : IApplicationBuilder
type IApplicationBuilder =
  member ApplicationServices : IServiceProvider with get, set
  member Build : unit -> RequestDelegate
  member New : unit -> IApplicationBuilder
  member Properties : IDictionary<string, obj>
  member ServerFeatures : IFeatureCollection
  member Use : middleware:Func<RequestDelegate, RequestDelegate> -> IApplicationBuilder
(extension) IApplicationBuilder.UseSession() : IApplicationBuilder
(extension) IApplicationBuilder.UseSession(options: SessionOptions) : IApplicationBuilder
Multiple items
module Data

from Quatro

--------------------
namespace Microsoft.FSharp.Data
module Converters

from Quatro.Data
module Domain

from Quatro
namespace Newtonsoft
namespace Newtonsoft.Json
Multiple items
type TicksJsonConverter =
  inherit JsonConverter<Ticks>
  new : unit -> TicksJsonConverter
  override ReadJson : r:JsonReader * Type * Ticks * bool * JsonSerializer -> Ticks
  override WriteJson : w:JsonWriter * v:Ticks * JsonSerializer -> unit


 JSON converter for Ticks


--------------------
new : unit -> TicksJsonConverter
Multiple items
type JsonConverter =
  member CanConvert : objectType:Type -> bool
  member CanRead : bool
  member CanWrite : bool
  member ReadJson : reader:JsonReader * objectType:Type * existingValue:obj * serializer:JsonSerializer -> obj
  member WriteJson : writer:JsonWriter * value:obj * serializer:JsonSerializer -> unit

--------------------
type JsonConverter<'T> =
  inherit JsonConverter
  member CanConvert : objectType:Type -> bool
  member ReadJson : reader:JsonReader * objectType:Type * existingValue:obj * serializer:JsonSerializer -> obj + 1 overload
  member WriteJson : writer:JsonWriter * value:obj * serializer:JsonSerializer -> unit + 1 overload

--------------------
type JsonConverterAttribute =
  inherit Attribute
  new : converterType:Type -> JsonConverterAttribute + 1 overload
  member ConverterParameters : obj[]
  member ConverterType : Type

--------------------
JsonConverter() : JsonConverter

--------------------
JsonConverter() : JsonConverter<'T>

--------------------
JsonConverterAttribute(converterType: System.Type) : JsonConverterAttribute
JsonConverterAttribute(converterType: System.Type, [<System.ParamArray>] converterParameters: obj []) : JsonConverterAttribute
val w : JsonWriter
type JsonWriter =
  member AutoCompleteOnClose : bool with get, set
  member Close : unit -> unit
  member CloseAsync : ?cancellationToken:CancellationToken -> Task
  member CloseOutput : bool with get, set
  member Culture : CultureInfo with get, set
  member DateFormatHandling : DateFormatHandling with get, set
  member DateFormatString : string with get, set
  member DateTimeZoneHandling : DateTimeZoneHandling with get, set
  member FloatFormatHandling : FloatFormatHandling with get, set
  member Flush : unit -> unit
  ...
val v : Ticks
Multiple items
type JsonSerializer =
  new : unit -> JsonSerializer
  member Binder : SerializationBinder with get, set
  member CheckAdditionalContent : bool with get, set
  member ConstructorHandling : ConstructorHandling with get, set
  member Context : StreamingContext with get, set
  member ContractResolver : IContractResolver with get, set
  member Converters : JsonConverterCollection
  member Culture : CultureInfo with get, set
  member DateFormatHandling : DateFormatHandling with get, set
  member DateFormatString : string with get, set
  ...

--------------------
JsonSerializer() : JsonSerializer
val x : int64
JsonWriter.WriteValue(value: obj) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Uri) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: byte []) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<System.TimeSpan>) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<System.Guid>) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<System.DateTimeOffset>) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<System.DateTime>) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<decimal>) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<sbyte>) : unit
   (+0 other overloads)
JsonWriter.WriteValue(value: System.Nullable<byte>) : unit
   (+0 other overloads)
val __ : TicksJsonConverter
val r : JsonReader
type JsonReader =
  member Close : unit -> unit
  member CloseInput : bool with get, set
  member Culture : CultureInfo with get, set
  member DateFormatString : string with get, set
  member DateParseHandling : DateParseHandling with get, set
  member DateTimeZoneHandling : DateTimeZoneHandling with get, set
  member Depth : int
  member FloatParseHandling : FloatParseHandling with get, set
  member MaxDepth : Nullable<int> with get, set
  member Path : string
  ...
property JsonReader.Value: obj
Multiple items
type WebLogIdJsonConverter =
  inherit JsonConverter<WebLogId>
  new : unit -> WebLogIdJsonConverter
  override ReadJson : r:JsonReader * Type * WebLogId * bool * JsonSerializer -> WebLogId
  override WriteJson : w:JsonWriter * v:WebLogId * JsonSerializer -> unit


 JSON converter for WebLogId


--------------------
new : unit -> WebLogIdJsonConverter
Multiple items
union case WebLogId.WebLogId: string -> WebLogId

--------------------
module WebLogId

from Quatro.Domain

--------------------
type WebLogId = | WebLogId of string
val v : WebLogId
val __ : WebLogIdJsonConverter
val all : seq<JsonConverter>
Multiple items
type JsonConverter =
  member CanConvert : objectType:Type -> bool
  member CanRead : bool
  member CanWrite : bool
  member ReadJson : reader:JsonReader * objectType:Type * existingValue:obj * serializer:JsonSerializer -> obj
  member WriteJson : writer:JsonWriter * value:obj * serializer:JsonSerializer -> unit

--------------------
type JsonConverter<'T> =
  inherit JsonConverter
  member CanConvert : objectType:Type -> bool
  member ReadJson : reader:JsonReader * objectType:Type * existingValue:obj * serializer:JsonSerializer -> obj + 1 overload
  member WriteJson : writer:JsonWriter * value:obj * serializer:JsonSerializer -> unit + 1 overload

--------------------
type JsonConverterAttribute =
  inherit Attribute
  new : converterType:Type -> JsonConverterAttribute + 1 overload
  member ConverterParameters : obj[]
  member ConverterType : Type

--------------------
JsonConverterAttribute(converterType: System.Type) : JsonConverterAttribute
JsonConverterAttribute(converterType: System.Type, [<System.ParamArray>] converterParameters: obj []) : JsonConverterAttribute
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
    interface IReadOnlyList<'T>
    interface IReadOnlyCollection<'T>
    interface IEnumerable
    interface IEnumerable<'T>
    member GetSlice : startIndex:int option * endIndex:int option -> 'T list
    member Head : 'T
    member IsEmpty : bool
    member Item : index:int -> 'T with get
    member Length : int
    member Tail : 'T list
    ...
val ofSeq : source:seq<'T> -> 'T list
val iter : action:('T -> unit) -> list:'T list -> unit
Fork me on GitHub