public async override Task <ActionResult> Index(ContentModel contentModel)
        {
            if (contentModel is null)
            {
                throw new ArgumentNullException(nameof(contentModel));
            }

            var model = new MatchListingViewModel(contentModel.Content, Services?.UserService)
            {
                AppliedMatchFilter = _queryStringParser.ParseFilterFromQueryString(Request.QueryString),
                DateTimeFormatter  = _dateFormatter
            };

            string pageTitle = "Stoolball matches";

            if (Request.RawUrl.StartsWith("/clubs/", StringComparison.OrdinalIgnoreCase))
            {
                var club = await _clubDataSource.ReadClubByRoute(Request.RawUrl).ConfigureAwait(false);

                if (club == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " for " + club.ClubName;
                model.AppliedMatchFilter.TeamIds.AddRange(club.Teams.Select(x => x.TeamId.Value));
            }
            else if (Request.RawUrl.StartsWith("/teams/", StringComparison.OrdinalIgnoreCase))
            {
                var team = await _teamDataSource.ReadTeamByRoute(Request.RawUrl).ConfigureAwait(false);

                if (team == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " for " + team.TeamName;
                model.AppliedMatchFilter.TeamIds.Add(team.TeamId.Value);
            }
            else if (Request.RawUrl.StartsWith("/competitions/", StringComparison.OrdinalIgnoreCase))
            {
                var competition = await _competitionDataSource.ReadCompetitionByRoute(Request.RawUrl).ConfigureAwait(false);

                if (competition == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " in the " + competition.CompetitionName;
                model.AppliedMatchFilter.CompetitionIds.Add(competition.CompetitionId.Value);
            }
            else if (Request.RawUrl.StartsWith("/locations/", StringComparison.OrdinalIgnoreCase))
            {
                var location = await _matchLocationDataSource.ReadMatchLocationByRoute(Request.RawUrl).ConfigureAwait(false);

                if (location == null)
                {
                    return(new HttpNotFoundResult());
                }
                pageTitle += " at " + location.NameAndLocalityOrTown();
                model.AppliedMatchFilter.MatchLocationIds.Add(location.MatchLocationId.Value);
            }

            // Remove from date from filter if it's the default, and describe the remainder in the feed title.
            var clonedFilter = model.AppliedMatchFilter.Clone();

            if (clonedFilter.FromDate == DateTimeOffset.UtcNow.Date)
            {
                clonedFilter.FromDate = null;
            }
            // Remove to date filter if it's a rolling date
            // (if user has set a specific end date a exactly year in the future unfortunately we'll miss it, but this is only for the description)
            if (clonedFilter.UntilDate == DateTimeOffset.UtcNow.Date.AddDays(365).AddDays(1).AddSeconds(-1))
            {
                clonedFilter.UntilDate = null;
            }
            model.Metadata.PageTitle   = pageTitle + _matchFilterHumanizer.MatchingFilter(clonedFilter);
            model.Metadata.Description = $"New or updated stoolball matches on the Stoolball England website";
            if (model.AppliedMatchFilter.PlayerTypes.Any())
            {
                model.Metadata.PageTitle   = $"{model.AppliedMatchFilter.PlayerTypes.First().Humanize(LetterCasing.Sentence)} {model.Metadata.PageTitle.ToLower(CultureInfo.CurrentCulture)}";
                model.Metadata.Description = $"New or updated {model.AppliedMatchFilter.PlayerTypes.First()} stoolball matches on the Stoolball England website";
            }
            model.Matches = await _matchDataSource.ReadMatchListings(model.AppliedMatchFilter, MatchSortOrder.LatestUpdateFirst).ConfigureAwait(false);

            return(View(Request.QueryString["format"] == "tweet" ? "MatchTweets" : "MatchesRss", model));
        }