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 18, 2016
1 parent 3b4d8e2 commit 8d636bf
Show file tree
Hide file tree
Showing 17 changed files with 938 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,33 @@ public static AttributeRouteModel CombineAttributeRouteModel(
};
}

/// <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 @@ private static string ChooseName(
}
}

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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),
}
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.IO;
using Microsoft.AspNetCore.Razor.Evolution;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public static class PageDirectiveFeature
{
public static bool TryGetRouteTemplate(RazorProjectItem projectItem, out string template)
{
const string PageDirective = "@page";

string content;
using (var streamReader = new StreamReader(projectItem.Read()))
{
content = streamReader.ReadToEnd();
}

if (content.StartsWith(PageDirective, StringComparison.Ordinal))
{
var newLineIndex = content.IndexOf(Environment.NewLine, PageDirective.Length);
template = content.Substring(PageDirective.Length, newLineIndex - PageDirective.Length).Trim();
return true;
}

template = null;
return false;
}
}
}
Loading

0 comments on commit 8d636bf

Please sign in to comment.