Wordpress RSS Parser Follow Up to my earlier question.

This is an RSS Parser intended only to parse the feed Wordpress provides (and even then, only what I actually want to display on my website). System.Linq.Xml and System.ServiceModel.Syndication are unavailable because I'm targeting .Net Core instead of the full framework. 

I think I've achieved a more idiomatic implementation this time. Where can I improve? Are there opportunities to compose higher order functions that I'm missing?

Rss.fs

module Rss
    open System

    type Item = {
        Title: string
        Link: Uri
        Comments: Uri
        PublishDate: DateTimeOffset
        Description: string
    }

    type Channel = {
        Title: string
        Link: Uri
        Description: string
        LastBuildDate: DateTimeOffset
        Items: seq&lt;Item&gt;
    }

    type RssFeed = {
        Version: double
        Channel: Channel
    }

    open System.Xml
    let parse input =

        // Helper Functions
        let innerText (elementName: string) (node: XmlNode) =
            let element = node.Item elementName
            element.InnerText

        let titleText node =
            node |&gt; innerText "title"

        let descriptionText node =
            node |&gt; innerText "description"

        let elementAsDateTimeOffset elementName node =
            node |&gt; innerText elementName |&gt; DateTimeOffset.Parse

        let elementAsUri elementName node =
            node |&gt; innerText elementName |&gt; Uri

        let linkUri node =
            node |&gt; elementAsUri "link"

        let itemNodeToItem node = {
            Title = node |&gt; titleText
            Link =  node |&gt; linkUri
            Comments = node |&gt; elementAsUri "comments"
            PublishDate = node |&gt; elementAsDateTimeOffset "pubDate"
            Description = node |&gt; descriptionText
         }

        // Load the document and begin parsing
        let document = XmlDocument()
        document.LoadXml input

        let rss = document.DocumentElement
        let channelNode = rss.FirstChild

        let items =
            channelNode.ChildNodes
            |&gt; Seq.cast&lt;XmlNode&gt;
            |&gt; Seq.filter (fun node -&gt; node.Name.Equals "item")
            |&gt; Seq.map itemNodeToItem

        let channel = {
            Title = channelNode |&gt; titleText
            Link = channelNode |&gt; linkUri
            Description = channelNode |&gt; descriptionText
            LastBuildDate = channelNode |&gt; elementAsDateTimeOffset "lastBuildDate"
            Items = items
        }

        let version =
            (rss.Attributes.ItemOf "version").Value
            |&gt; Double.Parse

        let rssFeed = {Version = version; Channel = channel}
        rssFeed


Controllers/BlogController.fs

namespace theupsyde.Controllers

open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
open Rss
open System.Net

type BlogController () =
    inherit Controller()

    let fetchUrl url = async {
        let req = WebRequest.Create(Uri(url)) 
        use! resp = req.GetResponseAsync() |&gt; Async.AwaitTask
        use stream = resp.GetResponseStream() 
        use reader = new IO.StreamReader(stream) 
        return reader.ReadToEnd()
    }

    let rssFeed =
        fetchUrl "https://christopherjmcclellan.wordpress.com/feed/" 
        |&gt; Async.RunSynchronously
        |&gt; Rss.parse

    let recent =
        rssFeed.Channel.Items

    member this.Index () =
        this.View(rssFeed.Channel)

    member this.Recent() =
        this.PartialView("BlogPosts", recent)

    member this.Latest () =
        let latest = 
            recent
            |&gt; Seq.head

        this.PartialView("BlogPost", latest)
The Rss helper functions are inside the parse function, even though they don't use any of the state inside that scope. Consider moving these up to the module level, even though they are not used anywhere else. By doing this the main function is shorter and easier to follow. You could also make those functions private when you pull them out.
Seq.cast can throw an exception. Even though you know that every item will be an XmlNode in this case, it can be safer to filter out objects that are not of the required type while casting. Unfortunately, F# doesn't have a function for this in the Seq module but you can always use the one already in .NET: channelNode.ChildNodes |&gt; System.Linq.Enumerable.OfType&lt;XmlNode&gt;

You fetch the RSS feed in the constructor of BlogController. This will happen once for each request. Using Async.RunSynchronously will block a thread, so you lose the benefit of using Async in the first place. You should avoid Async.RunSynchronously whenever you can. You can pull out the RSS fetching and parsing into another function that returns an async. Call this function from each controller method, do more async processing if necessary and then use Async.StartAsTask to return a task from the method. ASP.NET will then be able to run your code without unnecessary blocking.


EDIT:

I'm not too familiar with ASP.NET MVC, but based on this issue I think something like this should work:

let fetchRss() = async {
    let! content = fetchUrl "https://christopherjmcclellan.wordpress.com/feed/" 
    return Rss.parse content }

type BlogController() =
    inherit Controller()

    member this.Index() = async {
        let! rss = fetchRss()
        return this.View(rss.Channel) }