report

Hosting Power BI Content - 4/6

With all the data we need and the logic to make them available it is now time to present them to the user. Being data driven, our app needs to render dynamic HTML markup. In my case, the app is deplo

October 27, 2017

With all the data we need and the logic to make them available it is now time to present them to the user. Being data driven, our app needs to render dynamic HTML markup. In my case, the app is deployed on premises, in a Windows Server 2008 R2 machine. Not exactly yesterday's release, but enough to serve the purpose. The server already hosts ASP.NET applications, so all the needed resources are available.

The straight choice would be ASP.NET, which doesn't need presentations. Another more recent alternative would be WPF (xbap or silverlight), great front-end technology. I have used both of them in the past so it wouldn't a big deal. As an eager explorer of the last cutting-edge technologies, I chose to follow a different path, WebSharper, not without good technical reasons though.

  • At this time, ASP.NET and WPF are still great C# lovers. Nothing wrong with that, I am a C# user since .NET 1.0 and a fan too. But after having studied F#, I reckon it is a further significant step forward. And we are assisting to a trend where new C# releases contain features that make it step up toward F#, and F# releases that include adapters to better interact with (read: drop a rope down toward) C#. WebSharper first class citizen language is F#, even if it also supports C#

  • In the era of functional and reactive programming, new paradigms have appeared in programming languages that offer easier management of important functions traditionally handled by imperative constructs. WebSharper with its latest UI.Next versions offers F# based functional reactive programming

  • WebSharper includes a F# to javascript transpiler that allows us to develop both front and back end logic in F#. Writing client logic that calls server functions is almost indistinguishable from a local call. ASP.NET is eminently a back end technology. WPF is great in the front-end realm, but requires windows and IE on client side and relies on WCF services to reach the server from client code which is extremely powerful but not the lightest and quickest way

WebSharper is not the only open source framework offering these features of course. There are in fact several community efforts out there that deserve attention. Just to mention some I know and like and are probably less popular but bright and innovative:

  • elm is a reactive language that compiles to javascript

  • elixir is a functional language based on the glorious erlang

  • phoenix is a full fledged web framework created in elixir

  • fable is a F# to javascript transpiler

  • suave is a web server framework based on F#

  • Apologies to anyone who feels neglected, feel free to raise your hand

WebSharper includes all I need in one project, even if at the time of writing I find it a little awkward yet, especially when it comes to the documentation, which in my opinion would need more examples and a clearer structure. The version used at the time of writing is 4.0.192.106

Main.fs

In this post we'll see the server side rendering, while the next one is dedicated to the client-side. Main.fs contains more than one F# modules and defines the two available web endpoints: Home and About

[code language="fsharp"] type EndPoint = | [] Home | [] About [/code]

The Site module defines the entry point logic

[code language="fsharp"] module Site = let private HomePage (ctx: Context) = let path = sprintf "%sRep.html" ctx.RootFolder Templating.Home ctx (State.getWorkspaces()) (File.ReadAllText(path).Replace(@"${Root}", Util.appPath ctx))

let private AboutPage (ctx: Context) = Templating.About ctx

let Main = Application.MultiPage (fun ctx endpoint -> match endpoint with | EndPoint.Home -> HomePage ctx | EndPoint.About -> AboutPage ctx ) [/code]

The WebSharper runtime dispatches the web request base on the url to one of the two endpoints, managed by the functions HomePage and AboutPage. About page is simply some info page; we won't say more about it. Instead we'll follow HomePage. To construct the home page, we use the WebSharper templating engine. This is what the call Templating.Home() does. Two parameters are passed to Templating.Home:

  • The State (State.getWorkspaces is in the State module seen in the previous post)

  • A string containing static context from another html file (Rep.html). This string contains the html of the embedding page. It is passed as hidden content in the main page. The meaning of this markup will become clear when looking at the client logic, don't bother about it right now

Like other template engines, WebSharper templating processes an HTML file and allows the logic to define dinamically runtime values that are used as replacement of placeholders in the markup; placeholder must obey a specific syntax defined by the template engine. Let's start with the template (Template1.html). This markup contains templating tags (${...}) and elements (those with an attribute named ws-...). These are dinamically filled at rendering time by our code using the WebSharper Templating API

[code language="fsharp"] module Templating = open WebSharper.UI.Next.Html

type Template = Templating.Template<"Template1.html">

let Home (ctx: Context) workspaces html = Content.Page( Template() .Body(FrontEnd.bodyHome workspaces html) .HomeActive("active") .Root(Util.appPath ctx) .Doc())

[/code]

The templating API relies on one of the most powerful F# features: type providers. The Template type is generated automatically for us by parsing the html file (Template1.html) and converting all the templating tags to members, very easy to deal with in the code (Body, HomeActive and Root in the example).

We are now going to focus on the Body replacement, which is returned by the FrontEnd.bodyHome function

[code language="fsharp"] module FrontEnd = open WebSharper.UI.Next.Html open State open WebSharper.UI.Next.Html.Tags

let private renderReport groupId (r:Report) : Doc = divAttr [ attr.``data-`` "groupId" groupId attr.``data-`` "reportId" r.id attr.``data-`` "embedUrl" r.embedUrl ] [ aAttr [attr.href "#"; on.click <@ Client.reportClicked @>] [text r.name] ] :> Doc

let private renderReports groupId (reports:Map<string, Report>) = reports |> Map.toSeq |> Seq.map (fun (_, r) -> renderReport groupId r) |> Doc.Concat

let private renderWorkspace i (w:Workspace) : Doc = divAttr [attr.``class`` "panel panel-default"] [divAttr [attr.``class`` "panel-heading"] [h4Attr [attr.``class`` "panel-title"] [aAttr [attr.``data-`` "toggle" "collapse"; attr.``data-`` "parent" "#accordion"; attr.href (sprintf "#collapse%d" (i + 1))] [text w.name] ] ] ;divAttr [attr.``class`` "panel-collapse collapse"; attr.id (sprintf "collapse%d" (i + 1))] [divAttr [attr.``class`` "panel-body"] [w.reports |> renderReports w.id] ] ] :> Doc

let private renderWorkspaces (workspaces:Workspaces) = workspaces |> Map.toSeq |> Seq.mapi (fun i (_, w) -> renderWorkspace i w) |> Doc.Concat

let private powerBIAccordion (workspaces:Workspaces) = divAttr [attr.``class`` "container"] [ h5 [text "Select Workspace to see the list of reports in it, then click on the report to open it"] divAttr [attr.``class`` "panel-group"; attr.id "accordion"] [workspaces |> renderWorkspaces] ]

let private home workspaces rptPageHtml : Doc list = [ divAttr [attr.id "embedReportHtml"; attr.hidden "true"] [text rptPageHtml] powerBIAccordion workspaces ]

let bodyHome workspaces rptPageHtml : Doc list = [ //Doc.WebControl(new Web.Require<Resources.BootStrapJS>()) divAttr [attr.``class`` "page-header"] [ h2Attr [attr.``class`` "text-center"] [text "Power BI Reports"] ] divAttr [attr.``class`` "jumbotron"] (home workspaces rptPageHtml) ]

let bodyAbout () : Doc list = //Doc.WebControl(new Web.Require<Resources.BootStrapJS>()) //:: (About.paragraphs |> List.map (fun (title, content) -> [h2 [text title] :> Doc; pAttr [attr.``class`` "about-text"] content :> Doc]) |> List.concat) [/code]

This code uses WebSharper.UI.Next primitives to render well-known html elements (h, a, div,...). To be noted:

  • You may recognize bootstrap attributes

  • The div element embedReportHtml contains an inner HTML markup which will e used by browser logic to dinamically create the embedding page. We'll see this in detail in a specific post

  • The renderReport() function embeds in a div the report metadata (as custom data-... attributes) and a javascript call through a quotation (). This will also be analysed in more detail on a post in this series

Summary

With this post we have completed the server part of our application

  • Main.fs generates html content based on the application data model managed by

  • State.fs, which keeps the data cached and ready for the users, optimizing the access to the PBI cloud services with the help of

  • PowerBI.fs, which encapsulates the complexity of the PBI .NET API for an easier use

Conclusion

Starting with the next post, we'll begin to concentrate on the client part of our application, starting from the main page and then continuing with the embedding page.

Related articles