Step-2 Fsharp-PhoneCat Views Using Razor

This is step 2 of my blog series on Web Application Development in Fsharp using ASP.NET MVC

In the last blog post we have created web-api endpoints which serve the data for the home page and in this blog post we are going to create views of the phones and manufacturers using Razor.

Phone View

As we did it in the step-1 we are going to start with the controller which serves the phone view. Let’s get started by creating a source file in Web project under the folders Controllers with the name PhoneController

1
2
3
4
5
type PhoneController(phones : seq<Phone>) =
  inherit Controller()
  member this.Show (id : string) =
    let phone = phones |> Seq.find (fun p -> p.Id = id) |> PhoneViewModel.ToPhoneViewModel
    this.View(phone)

The action method Show just picks the phone with the given id, converts the selected phone to a view model and returns the view.

The phones data which is being passed to the controller is actually a domain model representing the data that are required to show in the UI. Let’s create it in the Domain project. Create a source file in the Domain project and name it as Catalog.

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
32
33
34
35
36
37
namespace PhoneCat.Domain
open PhoneCat.Domain.Measures

module Catalog =

  type Display = {
    ScreenResolution : string
    ScreenSize : float<inch>
    TouchScreen : bool
  }

  type Storage = {
    Flash : float<MB>
    Ram : float<MB>
  }

  type Android = {
    OS : string
    UI : string
  }

  type Camera = {
    Features : seq<string>
    Primary : string
  }

  type Phone = {
    Id : string
    Name : string
    Description : string
    Android : Android
    Camera : Camera
    Display : Display
    Weight : float<g>
    Storage : Storage
    Images : seq<string>
  }

It’s a typical type definition in fsharp which describes the domain model Phone. Also if you remember we have already created a type with the same name Phone in the previous step. Though they share the name both are being used for different purposes. One for showing all the details of a phone and other one is for showing only few details about a phone.

This is what we call us bounded context in DDD. In fsharp we can easily achieve it using modules with less verbosity.

We have also used another awesome feature of fsharp called units-of-measure which help us avoid failures in unit-coversion by providing strong typed data.

In the later posts we will be extending the domain based on theses measure types right now it just expresses the domain correct. These measure types are not created yet so lets create them by adding a source file in Domain project with the name Measures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace PhoneCat.Domain

module Measures =
  [<Measure>] type inch

  [<Measure>] type g

  [<Measure>] type MB

  let private toUOM (measureString : string) stringToReplace (uom : float<_>) =
    let value =
      if measureString = "" then 0.
      else measureString.Replace(stringToReplace, "") |> float
    value |> ((*) uom)

  let toInch (inchStr : string) = toUOM inchStr "inches" 1.0<inch>
  let toGram (weightStr : string) = toUOM weightStr "grams" 1.0<g>
  let toMB (storageStr : string) = toUOM storageStr "MB" 1.0<MB>

We have defined types to represents measures in inch, gram and megabytes. We have also added few utility methods to translate the raw string to strongly typed data which does the following

1
2
"200.5inches" to 200.5<inch>
"1200MB" to 1200.<MB>

With the domain model in place the next step is to populate this domain model from the data-store. To do it we are going to reuse the PhoneTypeProvider that we have created in the step-1. Since it has all the properties that we needed, we just need to add a function which does the mapping.

Open TypeProviders and add the below function

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
let ToCatalogPhone(phone : PhoneTypeProvider.Root) =
  let android = { OS = phone.Android.Os; UI = phone.Android.Ui }

  let camera =
    { Features = phone.Camera.Features
      Primary = phone.Camera.Primary }

  let display =
    { ScreenResolution = phone.Display.ScreenResolution
      ScreenSize = toInch phone.Display.ScreenSize
      TouchScreen = phone.Display.TouchScreen }

  let storage =
    { Flash = toMB phone.Storage.Flash
      Ram = toMB phone.Storage.Ram}

  { Id = phone.Id
    Name = phone.Name
    Description = phone.Description
    Android = android
    Camera = camera
    Display = display
    Weight = toGram phone.SizeAndWeight.Weight
    Storage = storage
    Images = phone.Images}

We are leveraging the utility functions toGram, toMB defined above as part of measure types and do seamless mapping of domain model from data store.

The next step is wiring the controller with the domain model and the data-store. We are going to follow the same Composition Root design as we did in step-1

Add a source file in the Web project with the name MvcInfrastructure and add the following code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace PhoneCat.Web

open PhoneCat.DataAccess
open PhoneCat.Domain
open PhoneCat.Web.Controllers
open System
open System.Web.Mvc

module MvcInfrastructure =
  type CompositionRoot(phones : seq<PhoneTypeProvider.Root>) =
    inherit DefaultControllerFactory() with
      override this.GetControllerInstance(requestContext, controllerType) =
        if controllerType = typeof<HomeController> then
          let homeController = new HomeController()
          homeController :> IController
        else if controllerType = typeof<PhoneController> then
          let phones' = phones |> Seq.map TypeProviders.ToCatalogPhone
          let phoneController = new PhoneController(phones')
          phoneController :> IController
        else
          raise <| ArgumentException((sprintf "Unknown controller type requested: %A" controllerType))

Similar to step-1 we are creating the composition root by passing the phones data and doing the wiring of different components.

Since we have created our custom ControllerFactory, we need to update the MVC configuration to use it when the framework creates the controllers.

Update the “`Global.asax.fs“ file as below

1
2
3
4
5
6
7
8
member x.Application_Start() =
  let phones = GitHubRepository.getPhones()
  AreaRegistration.RegisterAllAreas()
  GlobalConfiguration.Configure(new Action<HttpConfiguration>(fun config -> Global.RegisterWebApi(config, phones)))
  Global.RegisterFilters(GlobalFilters.Filters)
  Global.RegisterRoutes(RouteTable.Routes)
  ControllerBuilder.Current.SetControllerFactory(MvcInfrastructure.CompositionRoot(phones))
  BundleConfig.RegisterBundles BundleTable.Bundles

In the last step the we have retrieved the phones data inside the RegisterWebApi method and in this step we have moved it outside so that it can be used by both MVC Controllers and Web-Api controllers.

We have also changed the signature of RegisterWebApi method. In the earlier step it gets the configuration parameter alone and now it has two parameters httpConfiguration and phones

The next pending task in Phone View is translating the Phone Domain model to Phone View Model. Add the Phone View model in the PhoneController created before and update it us below

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
32
33
34
35
36
37
38
type PhoneViewModel =
  {
    Name : string
    Description : string
    Os : string
    Ui : string
    Flash : string
    Ram : string
    Weight : string
    ScreenResolution : string
    ScreenSize : string
    TouchScreen : string
    Primary : string
    Features : seq<string>
    Images : seq<string>
  }

  with static member ToPhoneViewModel(phone : Phone) =
    let roundToDigits (value:float) = String.Format("{0:0.00}", value)
    let concatWithSpace str2 str1 = str1 + " " + str2
    let uomToString (measureValue: float<_>) measureName =
      measureValue |> float |> roundToDigits |> concatWithSpace measureName

    {
      Name = phone.Name
      Description = phone.Description
      Os = phone.Android.OS
      Ui = phone.Android.UI
      Flash = phone.Storage.Flash |> int |> sprintf "%d MB"
      Ram =  phone.Storage.Ram |> int |> sprintf "%d MB"
      Weight = uomToString phone.Weight "grams"
      ScreenResolution = phone.Display.ScreenResolution
      ScreenSize = uomToString phone.Display.ScreenSize "inches"
      TouchScreen = if phone.Display.TouchScreen then "Yes" else "No"
      Primary = phone.Camera.Primary
      Features = phone.Camera.Features
      Images = phone.Images
    }

With all in place, we just need to create a razor view with the name Show.cshtml inside the View -> Phone directory of Web project and update it as mentioned here.

That’s it. Phone View is up and running! Click the Phone Links in the Home Page it will take you to the Phones View Page.

Manufacturers View


Manufactures View follows the similar steps that we have used to create the Phone View. It displays the Phones manufactured by a selected manufacturer in the home page.

Let’s start from the controller. Create a controller in the Web project with the name ManufacturerController

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
namespace PhoneCat.Web.Controllers

open System.Web
open System.Web.Mvc
open PhoneCat.Domain
open System

type ManufacturerViewModel = {
  Name : string
  Phones : seq<Phone>
}
with static member ToManufacturerViewModel (name, phones) =
      {Name = ManufacturerName.ToString name; Phones = phones}

type ManufacturerController
  (
    getPhones : ManufacturerName -> (ManufacturerName * seq<Phone>)
  ) =
  inherit Controller()

  member this.Show (id : string) =

    let viewModel =
      id
      |> ManufacturerName.ToManufacturerName
      |> getPhones
      |> ManufacturerViewModel.ToManufacturerViewModel

    this.View(viewModel)

The ManufacturerController depends on the function getPhones which gives the Phones manufactured by the given manufacturer. The action method Show converts given id to the manufaturer name, gets the Phones, converts them to ManufacturerViewModel and renders the view.

The domain model Phone mentioned here is the one that we have already created in step-1. The getPhones domain function is yet to be created.

Open Phones file in the Domain project and add the getPhonesOfManufacturer function.

1
2
3
4
5
let getPhonesOfManufacturer (phones : seq<Phone>) (manufacturerName) =
    let phones' =
      phones
      |> Seq.filter (fun p -> ManufacturerName.ToManufacturerName p.Name = manufacturerName)
    (manufacturerName, phones')

The getPhonesOfManufacturer function takes a sequence of phones and a manufacturer name, filters them for the given manufacturer and return a tuple that contain the manufacturer name and the sequence of phones.

As we have already created the data-store functions which serves the required data, we just need to wire up the controller.

Update MvcInfrastructure file in the Web project to create ManufacturerController

1
2
3
4
5
6
7
8
9
10
11
module MvcInfrastructure =
  type CompositionRoot(phones : seq<PhoneTypeProvider.Root>) =
    inherit DefaultControllerFactory() with
      override this.GetControllerInstance(requestContext, controllerType) =
        // ... Existing code ignored for brevity 
        else if controllerType = typeof<ManufacturerController> then
          let getPhonesByManufactuerName = phones |> Seq.map TypeProviders.ToPhone |> Phones.getPhonesOfManufacturer
          let manufacturerController = new ManufacturerController(getPhonesByManufactuerName)
          manufacturerController :> IController
        else
          raise <| ArgumentException((sprintf "Unknown controller type requested: %A" controllerType))

Thanks to the partial function we have partially applied the first parameter alone for the Phones.getPhonesOfManufacturer function which has the signature

1
seq<Phone> -> ManufacturerName -> seq<Phone>

and created a new function on the fly with the signature

1
ManufacturerName -> seq<Phone>

which is the exactly the function that is needed by the ManufacturerController

The final step is to create a razor view with the name Show.cshtml inside the View -> Manufacturer directory of Web project and update it as mentioned here.

Summary

In this blog post we have seen how to create MVC Razor views in fsharp. You can find the source code in the github repository. In the later posts we will be adding more interactivity. Stay tuned !!

Comments