// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.AspNetCore.Mvc.RazorPages; namespace Microsoft.AspNetCore.Mvc.ApplicationModels { public class PageConventionCollection : Collection { /// /// Initializes a new instance of the class that is empty. /// public PageConventionCollection() { } /// /// Initializes a new instance of the class /// as a wrapper for the specified list. /// /// The list that is wrapped by the new collection. public PageConventionCollection(IList conventions) : base(conventions) { } /// /// Creates and adds an that invokes an action on the /// for the page with the speciifed name. /// /// The name of the page e.g. /Users/List /// The . /// The added . public IPageApplicationModelConvention AddPageApplicationModelConvention( string pageName, Action action) { EnsureValidPageName(pageName); if (action == null) { throw new ArgumentNullException(nameof(action)); } return Add(new PageApplicationModelConvention(pageName, action)); } /// /// Creates and adds an that invokes an action on /// instances for all page under the specified folder. /// /// The path of the folder relative to the Razor Pages root. e.g. /Users/ /// The . /// The added . public IPageApplicationModelConvention AddFolderApplicationModelConvention(string folderPath, Action action) { EnsureValidFolderPath(folderPath); if (action == null) { throw new ArgumentNullException(nameof(action)); } return Add(new FolderApplicationModelConvention(folderPath, action)); } /// /// Creates and adds an that invokes an action on the /// for the page with the speciifed name. /// /// The name of the page e.g. /Users/List /// The . /// The added . public IPageRouteModelConvention AddPageRouteModelConvention(string pageName, Action action) { EnsureValidPageName(pageName); if (action == null) { throw new ArgumentNullException(nameof(action)); } return Add(new PageRouteModelConvention(pageName, action)); } /// /// Creates and adds an that invokes an action on /// instances for all page under the specified folder. /// /// The path of the folder relative to the Razor Pages root. e.g. /Users/ /// The . /// The added . public IPageRouteModelConvention AddFolderRouteModelConvention(string folderPath, Action action) { EnsureValidFolderPath(folderPath); if (action == null) { throw new ArgumentNullException(nameof(action)); } return Add(new FolderRouteModelConvention(folderPath, action)); } /// /// Removes all instances of the specified type. /// /// The type to remove. public void RemoveType() where TPageConvention : IPageConvention { RemoveType(typeof(TPageConvention)); } /// /// Removes all instances of the specified type. /// /// The type to remove. public void RemoveType(Type pageConventionType) { for (var i = Count - 1; i >= 0; i--) { var pageConvention = this[i]; if (pageConvention.GetType() == pageConventionType) { RemoveAt(i); } } } // Internal for unit testing internal static void EnsureValidPageName(string pageName) { if (string.IsNullOrEmpty(pageName)) { throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); } if (pageName[0] != '/' || pageName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(Resources.FormatInvalidValidPageName(pageName), nameof(pageName)); } } // Internal for unit testing internal static void EnsureValidFolderPath(string folderPath) { if (string.IsNullOrEmpty(folderPath)) { throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); } if (folderPath[0] != '/') { throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(folderPath)); } } private TConvention Add(TConvention convention) where TConvention: IPageConvention { base.Add(convention); return convention; } private class PageRouteModelConvention : IPageRouteModelConvention { private readonly string _path; private readonly Action _action; public PageRouteModelConvention(string path, Action action) { _path = path; _action = action; } public void Apply(PageRouteModel model) { if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) { _action(model); } } } private class FolderRouteModelConvention : IPageRouteModelConvention { private readonly string _folderPath; private readonly Action _action; public FolderRouteModelConvention(string folderPath, Action action) { _folderPath = folderPath.TrimEnd('/'); _action = action; } public void Apply(PageRouteModel model) { var viewEnginePath = model.ViewEnginePath; if (PathBelongsToFolder(_folderPath, viewEnginePath)) { _action(model); } } } private class PageApplicationModelConvention : IPageApplicationModelConvention { private readonly string _path; private readonly Action _action; public PageApplicationModelConvention(string path, Action action) { _path = path; _action = action; } public void Apply(PageApplicationModel model) { if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) { _action(model); } } } private class FolderApplicationModelConvention : IPageApplicationModelConvention { private readonly string _folderPath; private readonly Action _action; public FolderApplicationModelConvention(string folderPath, Action action) { _folderPath = folderPath.TrimEnd('/'); _action = action; } public void Apply(PageApplicationModel model) { var viewEnginePath = model.ViewEnginePath; if (PathBelongsToFolder(_folderPath, viewEnginePath)) { _action(model); } } } // Internal for unit testing internal static bool PathBelongsToFolder(string folderPath, string viewEnginePath) { if (folderPath == "/") { // Root directory covers everything. return true; } return viewEnginePath.Length > folderPath.Length && viewEnginePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase) && viewEnginePath[folderPath.Length] == '/'; } } }