A Flurry of Library Updates: FSharp.Data.JsonSchema and Frank

I recently carved out some time to revisit some dormant projects. The primary driver was a tic-tac-toe app inspired by Scott Wlaschin‘s Enterprise Tic-Tac-Toe series of posts and presentation and a desire to learn and test out Datastar. An upcoming post will dive deeper into that topic. In this post, I want to share a high-level overview of what’s new with FSharp.Data.JsonSchema and Frank. Subsequent posts will dive into further details.

FSharp.Data.JsonSchema 3.0.1

FSharp.Data.JsonSchema is now three packages:

  • FSharp.Data.JsonSchema.Core: Core JSON Schema representation types that relies on only FSharp.SystemTextJson. This library can be uses to parse a JSON Schema into F# types and F# types into a JSON Schema. However, it no longer has a specific target.
  • FSharp.Data.JsonSchema.NJsonSchema: This is equivalent to the previous version with a dependency on NJsonSchema as the target. This should be backward compatible with previous versions of the library.
  • FSharp.Data.JsonSchema.OpenApi: The new target depends on the Microsoft.OpenApi library introduced with the net9.0 framework target. This target is intended for use with generating Open API documents from ASP.NET Core applications.

In addition, several long overdue bug fixes and enhancements should now be resolved:

  • Recursive Types (#15): Recursive F# types no longer cause infinite loops. Self-referential DUs, records with optional self-references, and recursion through collections all generate proper $ref: "#" schemas, and a follow-up fix in 3.0.1 resolved an NJsonSchema serialization failure where Ref("#") in nullable contexts tried to look up the root reference in the definitions dictionary instead of referencing the root schema directly
  • Choice types (#22): Choice<'A,'B> through Choice<'A,…,'G> now generate clean anyOf schemas instead of the verbose internal-tag encoding
  • Anonymous records: Inline object schemas, no $ref
  • DU encoding styles: InternalTag, AdjacentTag, ExternalTag, and Untagged via a new unionEncoding parameter on Generator.Create
  • Format annotations: Proper date-time, guid, uri, duration, date, time, and byte formats for DateTime, Guid, Uri, TimeSpan, DateOnly, TimeOnly, and byte[]

Frank 7.2.0

Frank has had a long and winding history as my favorite hobby project for trying out different approaches to encoding web applications. The computation expression approach starting in (IIRC) v5.0 has stuck. The goal is still to produce an HTTP resource-style set of builders that provides a consistent means of defining HTTP resources and the ASP.NET Core WebHost in which to run them while allowing for a lot of flexibility and extensibility. As such, I’ve added some additional libraries I’ve found useful to test out the extensibility and support the new tic-tac-toe hobby project mentioned above.

Packages

  • Frank: Added Metadata field to ResourceSpec, a list of (EndpointBuilder -> unit) convention functions applied during RouteEndpointBuilder.Build(). This generic extensibility point lets companion libraries (Auth, OpenApi, etc.) attach typed endpoint metadata without requiring changes to the core Frank library. This is a binary-breaking change but source-compatible with the empty default. Also added plugBeforeRouting, plugBeforeRoutingWhen, and plugBeforeRoutingWhenNot for middleware ordering control around UseRouting().
  • Frank.Analyzers: F# Analyzer (FSharp.Analyzers.SDK) that detects duplicate HTTP handler registrations within a resource block at compile time, enforcing the constraint of a single HTTP method per resource. It works in IDEs (Ionide, VS, Rider) and CLI (dotnet fsharp-analyzers) for CI/CD.
  • Frank.Auth: Adds WebHostBuilder registration and resource-level authorization via ResourceBuilder extensions, including requireAuth, requireClaim, requireRole, requirePolicy using AND semantics for resources and useAuthentication, useAuthorization, authorizationPolicy for WebHostBuilder.
  • Frank.OpenApi: Adds long-planned, declarative OpenAPI 3.0+ document generation, including a handler computation expression for pairing handlers with metadata (name, summary, tags, produces, accepts). F# type schemas (records, DUs, options, collections) via FSharp.Data.JsonSchema.OpenApi. useOpenApi on WebHostBuilder wires services and middleware. Includes Scalar UI to provide a web-based client for viewing and testing endpoints. Targets net9.0/net10.0.
  • Frank.Datastar: Native SSE implementation similar to the StarFederation.Datastar.FSharp library. Zero-copy buffer writing via IBufferWriter, zero external NuGet dependencies, full Datastar SDK ADR compliance. Added stream-based overloads (streamPatchElements, etc.) accepting TextWriter -> Task for zero-allocation HTML rendering. No breaking API changes. Targets net8.0/net9.0/net10.0.

New Samples

  • Frank.Datastar.Basic: RESTful hypermedia patterns (click-to-edit, search, bulk ops) using Frank.Datastar with F# string templates.
  • Frank.Datastar.Hox: Same patterns as Basic using the Hox view engine. Demonstrates stream-based SSE overloads via Render.toStream.
  • Frank.Datastar.Oxpecker: Same patterns as Basic using Oxpecker.ViewEngine.
  • Frank.OpenApi.Sample: Product catalog API demonstrating the handler CE with OpenAPI metadata, mixed plain/enriched handlers, useOpenApi, and Scalar UI.

Looking Ahead

I’ll spend some time in upcoming posts exploring each of these. In the meantime, I’d love feedback on any of the updates and changes. I hope I haven’t broken anyone with changes to FSharp.Data.JsonSchema, and there is a transition package with version 3.0.0 to make it easier to switch without changing package names. I don’t have any additional plans at the moment for either of these, but please open issues if you have ideas or bug reports.

Revisiting Microsoft Forms: InfoPath (and XForms)

In this third part of my series on Microsoft Forms solutions. You can find posts on WebForms here and WinForms here. In this post, I want to look back at InfoPath, and more specifically, the lost opportunity in forgoing the W3C‘s XForms recommendation.

Continue reading

Revisiting Microsoft Forms: WinForms

This is a series of posts on older Microsoft forms technologies and reflections on what is really good about them. When I first used these platforms, I had strong biases against them, which were encouraged by co-workers and friends. Having spent over a decade building software in .NET, I’ve come to appreciate at least certain aspects of these tools, some of which are moving forward to .NET 5. Windows Forms, or WinForms, is one of those platforms, and I would like to spend some time talking through some really nice aspects of the framework.

Continue reading

Azure Static Web Apps with Sapper + Azure Functions

In the last post, I walked through setting up a simple Sapper application on Microsoft’s new Azure Static Web Apps. In this post, I’ll walk through my experience adding and deploying an Azure Functions API.

TL;DR Azure Static Web Apps currently only supports Azure Functions v3 with Node.js v12 and HttpTrigger bindings for APIs. See the docs.

Continue reading

Revisiting Microsoft Forms: WebForms

This is the first of several reflections on Microsoft’s original forms solutions for .NET. In this post, I want to look back at ASP.NET WebForms, or more specifically System.Web and the Page life cycle. In hindsight, I think there were some really good ideas that were just hard to understand clearly given the dominance of OO, TDD, and DDD that were at the rising to the height of popularity while WebForms was the primary ASP.NET solution.

Continue reading

Azure App Service Static Web Apps with Svelte + Sapper

Microsoft just announced Azure App Service Static Web Apps at Microsoft Build 2020. If you have any interest in the JAMstack, then this is terrific news! While Microsoft provided a means for static website hosting in Azure already, we’ve been on our own to come up with solutions to deploy supporting APIs. With Azure Static Web Apps, that’s changed. You can now trigger a build and deploy of your static website and optional Azure Functions from a git commit!

I’ll dig into that fantastic combo in later posts. For the present, I’ll stick to something simple: publishing a Sapper generated static website.

Continue reading

LINQ to SQL and Entity Framework as Internal Object Databases

I’ve now used and/or tried LINQ to SQL, Entity Framework, and Fluent NHibernate and can now say I understand the differences as expressed in the ADO.NET Entity Framework Vote of No Confidence. Yet I still appreciate what the former two products from Microsoft offer. Well, I like LINQ to SQL anyway. After spending four hours today trying to create a simple example with Entity Framework and getting nowhere with many-to-many mappings despite several blogs’ assistance, I finally gave up. I think the problem with LINQ to SQL and Entity Framework is not in their usefulness but in the approach Microsoft has tried to take in marketing them as object-relational mapping technologies.

An object-relational mapping technology generally takes an object and maps it to a database, not the other way around. At least, I think that’s how it started. The abundance of MVC frameworks using the Active Record pattern seems to have changed that recently with generators creating models from database tables, though many of these actually create the tables in the database from the object definition, as well. Nevertheless, I’d disagree with Microsoft that LINQ to SQL and Entity Framework qualify as ORM technologies, though they do perform that role, as well.

LINQ to SQL and Entity Framework provide a language-integrated object database against which to create applications. This is huge! Let that sink in a bit. Now, I find nothing wrong with that approach. In fact, it’s quite nice! The trouble is that developers think, “Wow, I’ve got all these great objects ready to use!” Not so fast. You have entities that represent rows in tables, not business objects. Yes, LINQ to SQL and Entity Framework provide means of modifying those classes to mimic more class-like behavior (and that can indeed be a great benefit do easing domain model or active record development) but really should not be used for anything other than database records.

This greatly simplifies any data mapping you have to do between your domain objects and your database, and you can write everything in your OO language of choice. If you want something more automatic, you might try an object-to-object mapper (e.g. NBear–though I haven’t tried it myself and can’t imagine that object-to-object mapping would be that difficult).

As a final analysis, in case you care, I really like LINQ to SQL’s defaults. It’s super simple to get started and use, though it’s only for SQL Server. Entity Framework… I am just not a fan. If I can stay away, I will. Maybe someone will show me how to configure it so that it works for me, but so far it’s a FAIL. Also, keep tabs on DbLinq. It’s an open source tool that attempts to mimic LINQ to SQL for SQL Server and a number of other database technologies and should work on Mono. Of course, NHibernate is great for those who would rather connect to a real database, and I found Fluent NHibernate to be a great tool. I love its fluent interface for mapping to the database and its AutoMapping functionality. However, making everything virtual annoyed me and really makes me question how so many can prefer it for persistence ignorance when it so obviously requires that detail. (I get it’s a small sacrifice, but I wouldn’t code that way normally, so I am quite reminded that I’m connecting to a database through NHibernate.)

Another Oxite Indeed

Another Oxite, indeed. My Google Reader reported a flood of activity in the AppArch CodePlex wiki, the majority of which was updated patterns pages with “BETA – Published for Community Feedback. This page is a wiki.  Please provide your feedback in the comments below,” at the top. The Application Architecture Guide, v2 is now back in beta status after the flood of community feedback following its December 2008 “final” release.

I’m glad to see Microsoft respond to the community. I find great hope in Microsoft’s future in their willingness to listen and respond. If only they would do so sooner rather than later, they would have a much better reputation with the community. Nevertheless, I’m pleased with their desire to dialogue with the community to improve their guidance.

Now, if you are an architect or developer with experience in the areas for which Microsoft is offering guidance, speak up. Help provide the response for which Microsoft is asking. This is a great opportunity for us to bridge the relationship we have with the team at Microsoft providing the tools we use daily.