Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Adding PageActionDescriptorProvider
Browse files Browse the repository at this point in the history
Fixes #5353
  • Loading branch information
pranavkm committed Nov 24, 2016
1 parent 5b2a4ae commit 52ee9af
Show file tree
Hide file tree
Showing 19 changed files with 941 additions and 42 deletions.
Expand Up @@ -99,6 +99,33 @@ public bool IsAbsoluteTemplate
};
}

/// <summary>
/// Combines the prefix and route template for an attribute route.
/// </summary>
/// <param name="prefix">The prefix.</param>
/// <param name="template">The route template.</param>
/// <returns>The combined pattern.</returns>
public static string CombineTemplates(string prefix, string template)
{
var result = CombineCore(prefix, template);
return CleanTemplate(result);
}

/// <summary>
/// Determines if a template pattern can be used to override a prefix.
/// </summary>
/// <param name="template">The template.</param>
/// <returns><c>true</c> if this is an overriding template, <c>false</c> otherwise.</returns>
/// <remarks>
/// Route templates starting with "~/" or "/" can be used to override the prefix.
/// </remarks>
public static bool IsOverridePattern(string template)
{
return template != null &&
(template.StartsWith("~/", StringComparison.Ordinal) ||
template.StartsWith("/", StringComparison.Ordinal));
}

private static string ChooseName(
AttributeRouteModel left,
AttributeRouteModel right)
Expand All @@ -113,12 +140,6 @@ public bool IsAbsoluteTemplate
}
}

internal static string CombineTemplates(string left, string right)
{
var result = CombineCore(left, right);
return CleanTemplate(result);
}

private static string CombineCore(string left, string right)
{
if (left == null && right == null)
Expand All @@ -143,13 +164,6 @@ private static string CombineCore(string left, string right)
return left + "/" + right;
}

private static bool IsOverridePattern(string template)
{
return template != null &&
(template.StartsWith("~/", StringComparison.Ordinal) ||
template.StartsWith("/", StringComparison.Ordinal));
}

private static bool IsEmptyLeftSegment(string template)
{
return template == null ||
Expand Down
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Abstractions;
Expand All @@ -14,7 +13,6 @@
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing.Tree;

namespace Microsoft.AspNetCore.Mvc.Internal
{
Expand Down
36 changes: 18 additions & 18 deletions src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

@@ -0,0 +1,17 @@
// 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.

namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Allows customization of the of the <see cref="PageModel"/>.
/// </summary>
public interface IPageModelConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="PageModel"/>.
/// </summary>
/// <param name="model">The <see cref="PageModel"/>.</param>
void Apply(PageModel model);
}
}
@@ -0,0 +1,86 @@
// 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.Linq;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
/// <summary>
/// Application model component for RazorPages.
/// </summary>
public class PageModel
{
/// <summary>
/// Initializes a new instance of <see cref="PageModel"/>.
/// </summary>
/// <param name="relativePath">The application relative path of the page.</param>
/// <param name="viewEnginePath">The path relative to the base path for page discovery.</param>
public PageModel(string relativePath, string viewEnginePath)
{
if (relativePath == null)
{
throw new ArgumentNullException(nameof(relativePath));
}

if (viewEnginePath == null)
{
throw new ArgumentNullException(nameof(viewEnginePath));
}

RelativePath = relativePath;
ViewEnginePath = viewEnginePath;

Filters = new List<IFilterMetadata>();
Properties = new Dictionary<object, object>();
Selectors = new List<SelectorModel>();
}

/// <summary>
/// A copy constructor for <see cref="PageModel"/>.
/// </summary>
/// <param name="other">The <see cref="PageModel"/> to copy from.</param>
public PageModel(PageModel other)
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}

RelativePath = other.RelativePath;
ViewEnginePath = other.ViewEnginePath;

Filters = new List<IFilterMetadata>(other.Filters);
Properties = new Dictionary<object, object>(other.Properties);

Selectors = new List<SelectorModel>(other.Selectors.Select(m => new SelectorModel(m)));
}

/// <summary>
/// Gets or sets the application root relative path for the page.
/// </summary>
public string RelativePath { get; }

/// <summary>
/// Gets or sets the path relative to the base path for page discovery.
/// </summary>
public string ViewEnginePath { get; }

/// <summary>
/// Gets or sets the applicable <see cref="IFilterMetadata"/> instances.
/// </summary>
public IList<IFilterMetadata> Filters { get; }

/// <summary>
/// Stores arbitrary metadata properties associated with the <see cref="PageModel"/>.
/// </summary>
public IDictionary<object, object> Properties { get; }

/// <summary>
/// Gets or sets the <see cref="SelectorModel"/> instances.
/// </summary>
public IList<SelectorModel> Selectors { get; }
}
}
@@ -0,0 +1,127 @@
// 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 Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Razor.Evolution;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class PageActionDescriptorProvider : IActionDescriptorProvider
{
private static readonly string IndexFileName = "Index.cshtml";
private readonly RazorProject _project;
private readonly MvcOptions _mvcOptions;
private readonly RazorPagesOptions _pagesOptions;

public PageActionDescriptorProvider(
RazorProject project,
IOptions<MvcOptions> mvcOptionsAccessor,
IOptions<RazorPagesOptions> pagesOptionsAccessor)
{
_project = project;
_mvcOptions = mvcOptionsAccessor.Value;
_pagesOptions = pagesOptionsAccessor.Value;
}

public int Order { get; set; }

public void OnProvidersExecuting(ActionDescriptorProviderContext context)
{
foreach (var item in _project.EnumerateItems("/"))
{
if (item.Filename.StartsWith("_"))
{
// Pages like _PageImports should not be routable.
continue;
}

string template;
if (!PageDirectiveFeature.TryGetRouteTemplate(item, out template))
{
// .cshtml pages without @page are not RazorPages.
continue;
}

if (AttributeRouteModel.IsOverridePattern(template))
{
throw new InvalidOperationException(string.Format(
Resources.PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable,
item.Path));
}

AddActionDescriptors(context.Results, item, template);
}
}

public void OnProvidersExecuted(ActionDescriptorProviderContext context)
{
}

private void AddActionDescriptors(IList<ActionDescriptor> actions, RazorProjectItem item, string template)
{
var model = new PageModel(item.CombinedPath, item.PathWithoutExtension);
var routePrefix = item.BasePath == "/" ? item.PathWithoutExtension : item.BasePath + item.PathWithoutExtension;
model.Selectors.Add(CreateSelectorModel(routePrefix, template));

if (string.Equals(IndexFileName, item.Filename, StringComparison.OrdinalIgnoreCase))
{
model.Selectors.Add(CreateSelectorModel(item.BasePath, template));
}

for (var i = 0; i < _pagesOptions.Conventions.Count; i++)
{
_pagesOptions.Conventions[i].Apply(model);
}

var filters = new List<FilterDescriptor>(_mvcOptions.Filters.Count + model.Filters.Count);
for (var i = 0; i < _mvcOptions.Filters.Count; i++)
{
filters.Add(new FilterDescriptor(_mvcOptions.Filters[i], FilterScope.Global));
}

for (var i = 0; i < model.Filters.Count; i++)
{
filters.Add(new FilterDescriptor(model.Filters[i], FilterScope.Action));
}

foreach (var selector in model.Selectors)
{
actions.Add(new PageActionDescriptor()
{
AttributeRouteInfo = new AttributeRouteInfo()
{
Name = selector.AttributeRouteModel.Name,
Order = selector.AttributeRouteModel.Order ?? 0,
Template = selector.AttributeRouteModel.Template,
},
DisplayName = $"Page: {item.Path}",
FilterDescriptors = filters,
Properties = new Dictionary<object, object>(model.Properties),
RelativePath = item.CombinedPath,
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "page", item.PathWithoutExtension },
},
ViewEnginePath = item.Path,
});
}
}

private static SelectorModel CreateSelectorModel(string prefix, string template)
{
return new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Template = AttributeRouteModel.CombineTemplates(prefix, template),
}
};
}
}
}

0 comments on commit 52ee9af

Please sign in to comment.