Add support for suppressing inbound and outbound routing
This commit is contained in:
parent
c69e48f06b
commit
8fa95d66d4
|
|
@ -26,5 +26,15 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
/// route by provided route data.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines if the route entry associated with this model participates in link generation.
|
||||
/// </summary>
|
||||
public bool SuppressLinkGeneration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines if the route entry associated with this model participates in path matching (inbound routing).
|
||||
/// </summary>
|
||||
public bool SuppressPathMatching { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
|
||||
foreach (var action in context.Actions.OfType<ControllerActionDescriptor>())
|
||||
{
|
||||
if (action.AttributeRouteInfo != null && action.AttributeRouteInfo.SuppressPathMatching)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var extensionData = action.GetProperty<ApiDescriptionActionData>();
|
||||
if (extensionData != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,9 +42,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Name = other.Name;
|
||||
Order = other.Order;
|
||||
Template = other.Template;
|
||||
SuppressLinkGeneration = other.SuppressLinkGeneration;
|
||||
SuppressPathMatching = other.SuppressPathMatching;
|
||||
}
|
||||
|
||||
public IRouteTemplateProvider Attribute { get; private set; }
|
||||
public IRouteTemplateProvider Attribute { get;}
|
||||
|
||||
public string Template { get; set; }
|
||||
|
||||
|
|
@ -52,6 +54,16 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines if this model participates in link generation.
|
||||
/// </summary>
|
||||
public bool SuppressLinkGeneration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that determines if this model participates in path matching (inbound routing).
|
||||
/// </summary>
|
||||
public bool SuppressPathMatching { get; set; }
|
||||
|
||||
public bool IsAbsoluteTemplate
|
||||
{
|
||||
get
|
||||
|
|
@ -96,6 +108,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Template = combinedTemplate,
|
||||
Order = right.Order ?? left.Order,
|
||||
Name = ChooseName(left, right),
|
||||
SuppressLinkGeneration = left.SuppressLinkGeneration || right.SuppressLinkGeneration,
|
||||
SuppressPathMatching = left.SuppressPathMatching || right.SuppressPathMatching,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// action by expected route values, and then use the TemplateBinder to generate the link.
|
||||
foreach (var routeInfo in routeInfos)
|
||||
{
|
||||
if (routeInfo.SuppressLinkGeneration)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var defaults = new RouteValueDictionary();
|
||||
foreach (var kvp in routeInfo.ActionDescriptor.RouteValues)
|
||||
{
|
||||
|
|
@ -117,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
// We're creating one AttributeRouteMatchingEntry per group, so we need to identify the distinct set of
|
||||
// groups. It's guaranteed that all members of the group have the same template and precedence,
|
||||
// so we only need to hang on to a single instance of the RouteInfo for each group.
|
||||
var groups = GroupRouteInfos(routeInfos);
|
||||
var groups = GetInboundRouteGroups(routeInfos);
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var handler = _handlerFactory(group.ToArray());
|
||||
|
|
@ -135,9 +140,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IGrouping<RouteInfo, ActionDescriptor>> GroupRouteInfos(List<RouteInfo> routeInfos)
|
||||
private static IEnumerable<IGrouping<RouteInfo, ActionDescriptor>> GetInboundRouteGroups(List<RouteInfo> routeInfos)
|
||||
{
|
||||
return routeInfos.GroupBy(r => r, r => r.ActionDescriptor, RouteInfoEqualityComparer.Instance);
|
||||
return routeInfos
|
||||
.Where(routeInfo => !routeInfo.SuppressPathMatching)
|
||||
.GroupBy(r => r, r => r.ActionDescriptor, RouteInfoEqualityComparer.Instance);
|
||||
}
|
||||
|
||||
private static List<RouteInfo> GetRouteInfos(IReadOnlyList<ActionDescriptor> actions)
|
||||
|
|
@ -194,8 +201,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
try
|
||||
{
|
||||
RouteTemplate parsedTemplate;
|
||||
if (!templateCache.TryGetValue(action.AttributeRouteInfo.Template, out parsedTemplate))
|
||||
if (!templateCache.TryGetValue(action.AttributeRouteInfo.Template, out var parsedTemplate))
|
||||
{
|
||||
// Parsing with throw if the template is invalid.
|
||||
parsedTemplate = TemplateParser.Parse(action.AttributeRouteInfo.Template);
|
||||
|
|
@ -203,6 +209,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
routeInfo.RouteTemplate = parsedTemplate;
|
||||
routeInfo.SuppressPathMatching = action.AttributeRouteInfo.SuppressPathMatching;
|
||||
routeInfo.SuppressLinkGeneration = action.AttributeRouteInfo.SuppressLinkGeneration;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -243,6 +251,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public string RouteName { get; set; }
|
||||
|
||||
public RouteTemplate RouteTemplate { get; set; }
|
||||
|
||||
public bool SuppressPathMatching { get; set; }
|
||||
|
||||
public bool SuppressLinkGeneration { get; set; }
|
||||
}
|
||||
|
||||
private class RouteInfoEqualityComparer : IEqualityComparer<RouteInfo>
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class AttributeRouteEntries
|
||||
{
|
||||
public List<InboundRouteEntry> InboundEntries { get; } = new List<InboundRouteEntry>();
|
||||
|
||||
public List<OutboundRouteEntry> OutboundEntries { get; } = new List<OutboundRouteEntry>();
|
||||
}
|
||||
}
|
||||
|
|
@ -365,9 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
AttributeRouteModel action,
|
||||
AttributeRouteModel controller)
|
||||
{
|
||||
var combinedRoute = AttributeRouteModel.CombineAttributeRouteModel(
|
||||
controller,
|
||||
action);
|
||||
var combinedRoute = AttributeRouteModel.CombineAttributeRouteModel(controller, action);
|
||||
|
||||
if (combinedRoute == null)
|
||||
{
|
||||
|
|
@ -375,11 +373,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
else
|
||||
{
|
||||
return new AttributeRouteInfo()
|
||||
return new AttributeRouteInfo
|
||||
{
|
||||
Template = combinedRoute.Template,
|
||||
Order = combinedRoute.Order ?? DefaultAttributeRouteOrder,
|
||||
Name = combinedRoute.Name,
|
||||
SuppressLinkGeneration = combinedRoute.SuppressLinkGeneration,
|
||||
SuppressPathMatching = combinedRoute.SuppressPathMatching,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
|
|
@ -15,10 +14,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public void CopyConstructor_CopiesAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var route = new AttributeRouteModel(new HttpGetAttribute("/api/Products"));
|
||||
|
||||
route.Name = "products";
|
||||
route.Order = 5;
|
||||
var route = new AttributeRouteModel(new HttpGetAttribute("/api/Products"))
|
||||
{
|
||||
Name = "products",
|
||||
Order = 5,
|
||||
SuppressLinkGeneration = true,
|
||||
SuppressPathMatching = true,
|
||||
};
|
||||
|
||||
// Act
|
||||
var route2 = new AttributeRouteModel(route);
|
||||
|
|
@ -277,6 +279,80 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Assert.Equal(expectedName, combined.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Combine_SetsSuppressLinkGenerationToFalse_IfNeitherIsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var left = new AttributeRouteModel
|
||||
{
|
||||
Template = "Template"
|
||||
};
|
||||
var right = new AttributeRouteModel();
|
||||
var combined = AttributeRouteModel.CombineAttributeRouteModel(left, right);
|
||||
|
||||
// Assert
|
||||
Assert.False(combined.SuppressLinkGeneration);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public void Combine_SetsSuppressLinkGenerationToTrue_IfEitherIsTrue(bool leftSuppress, bool rightSuppress)
|
||||
{
|
||||
// Arrange
|
||||
var left = new AttributeRouteModel
|
||||
{
|
||||
Template = "Template",
|
||||
SuppressLinkGeneration = leftSuppress,
|
||||
};
|
||||
var right = new AttributeRouteModel
|
||||
{
|
||||
SuppressLinkGeneration = rightSuppress,
|
||||
};
|
||||
var combined = AttributeRouteModel.CombineAttributeRouteModel(left, right);
|
||||
|
||||
// Assert
|
||||
Assert.True(combined.SuppressLinkGeneration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Combine_SetsSuppressPathGenerationToFalse_IfNeitherIsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var left = new AttributeRouteModel
|
||||
{
|
||||
Template = "Template",
|
||||
};
|
||||
var right = new AttributeRouteModel();
|
||||
var combined = AttributeRouteModel.CombineAttributeRouteModel(left, right);
|
||||
|
||||
// Assert
|
||||
Assert.False(combined.SuppressPathMatching);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public void Combine_SetsSuppressPathGenerationToTrue_IfEitherIsTrue(bool leftSuppress, bool rightSuppress)
|
||||
{
|
||||
// Arrange
|
||||
var left = new AttributeRouteModel
|
||||
{
|
||||
Template = "Template",
|
||||
SuppressPathMatching = leftSuppress,
|
||||
};
|
||||
var right = new AttributeRouteModel
|
||||
{
|
||||
SuppressPathMatching = rightSuppress,
|
||||
};
|
||||
var combined = AttributeRouteModel.CombineAttributeRouteModel(left, right);
|
||||
|
||||
// Assert
|
||||
Assert.True(combined.SuppressPathMatching);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> CombineNamesTestData
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -545,6 +545,231 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.IsType<RouteCreationException>(exception.InnerException);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEntries_DoesNotCreateOutboundEntriesForAttributesWithSuppressForLinkGenerationSetToTrue()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>()
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/get/{id}",
|
||||
Name = "BLOG_LINK1",
|
||||
SuppressLinkGeneration = true,
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/{snake-cased-name}",
|
||||
Name = "BLOG_INDEX2",
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/",
|
||||
Name = "BLOG_HOME",
|
||||
SuppressPathMatching = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var builder = CreateBuilder();
|
||||
var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
|
||||
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
builder.OutboundEntries,
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_INDEX2", e.RouteName);
|
||||
Assert.Equal("blog/{snake-cased-name}", e.RouteTemplate.TemplateText);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_HOME", e.RouteName);
|
||||
Assert.Equal("blog/", e.RouteTemplate.TemplateText);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEntries_DoesNotCreateOutboundEntriesForAttributesWithSuppressForLinkGenerationSetToTrue_WhenMultipleAttributesHaveTheSameTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>()
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/get/{id}",
|
||||
Name = "BLOG_LINK1",
|
||||
SuppressLinkGeneration = true,
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/get/{id}",
|
||||
Name = "BLOG_LINK2",
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/",
|
||||
Name = "BLOG_HOME",
|
||||
SuppressPathMatching = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var builder = CreateBuilder();
|
||||
var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
|
||||
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
builder.OutboundEntries,
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_LINK2", e.RouteName);
|
||||
Assert.Equal("blog/get/{id}", e.RouteTemplate.TemplateText);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_HOME", e.RouteName);
|
||||
Assert.Equal("blog/", e.RouteTemplate.TemplateText);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetEntries_DoesNotCreateInboundEntriesForAttributesWithSuppressForPathMatchingSetToTrue()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>()
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/get/{id}",
|
||||
Name = "BLOG_LINK1",
|
||||
SuppressLinkGeneration = true,
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/{snake-cased-name}",
|
||||
Name = "BLOG_LINK2",
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/",
|
||||
Name = "BLOG_HOME",
|
||||
SuppressPathMatching = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var builder = CreateBuilder();
|
||||
var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
|
||||
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
builder.InboundEntries,
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_LINK1", e.RouteName);
|
||||
Assert.Equal("blog/get/{id}", e.RouteTemplate.TemplateText);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_LINK2", e.RouteName);
|
||||
Assert.Equal("blog/{snake-cased-name}", e.RouteTemplate.TemplateText);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEntries_DoesNotCreateInboundEntriesForAttributesWithSuppressForPathMatchingSetToTrue_WhenMultipleAttributesHaveTheSameTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var actions = new List<ActionDescriptor>()
|
||||
{
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/get/{id}",
|
||||
Name = "BLOG_LINK1",
|
||||
SuppressPathMatching = true,
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/get/{id}",
|
||||
Name = "BLOG_LINK2",
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
AttributeRouteInfo = new AttributeRouteInfo
|
||||
{
|
||||
Template = "blog/",
|
||||
Name = "BLOG_HOME",
|
||||
SuppressLinkGeneration = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var builder = CreateBuilder();
|
||||
var actionDescriptorProvider = CreateActionDescriptorProvider(actions);
|
||||
var route = CreateRoute(CreateHandler().Object, actionDescriptorProvider.Object);
|
||||
|
||||
// Act
|
||||
route.AddEntries(builder, actionDescriptorProvider.Object.ActionDescriptors);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
builder.InboundEntries,
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_LINK2", e.RouteName);
|
||||
Assert.Equal("blog/get/{id}", e.RouteTemplate.TemplateText);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
Assert.Equal("BLOG_HOME", e.RouteName);
|
||||
Assert.Equal("blog/", e.RouteTemplate.TemplateText);
|
||||
});
|
||||
}
|
||||
|
||||
private static TreeRouteBuilder CreateBuilder()
|
||||
{
|
||||
var services = new ServiceCollection()
|
||||
|
|
|
|||
|
|
@ -1052,6 +1052,19 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("ApiExplorerReload/NewIndex", description.RelativePath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiExplorer_DoesNotListActionsSuppressedForPathMatching()
|
||||
{
|
||||
// Act
|
||||
var body = await Client.GetStringAsync("ApiExplorerInboundOutbound/SuppressedForLinkGeneration");
|
||||
var result = JsonConvert.DeserializeObject<List<ApiExplorerData>>(body);
|
||||
|
||||
// Assert
|
||||
var description = Assert.Single(result);
|
||||
Assert.Empty(description.ParameterDescriptions);
|
||||
Assert.Equal("ApiExplorerInboundOutbound/SuppressedForLinkGeneration", description.RelativePath);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetSortedMediaTypes(ApiExplorerResponseType apiResponseType)
|
||||
{
|
||||
return apiResponseType.ResponseFormats
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -126,5 +127,44 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.Equal("From Header - HelloWorld", body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionModelSuppressedForPathMatching_CannotBeRouted()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("Home/CannotBeRouted");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionModelNotSuppressedForPathMatching_CanBeRouted()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetStringAsync("Home/CanBeRouted");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello world", response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionModelSuppressedForLinkGeneration_CannotBeLinked()
|
||||
{
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => Client.GetStringAsync("Home/RouteToSuppressLinkGeneration"));
|
||||
Assert.Equal("No route matches the supplied values.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionModelSuppressedForPathMatching_CanBeLinked()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("Home/RouteToSuppressPathMatching");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/Home/CannotBeRouted", response.Headers.Location.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// 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.Reflection;
|
||||
using ApiExplorerWebSite.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace ApiExplorerWebSite
|
||||
{
|
||||
// Disables ApiExplorer for a specific controller type.
|
||||
// This is part of the test that validates that ApiExplorer can be configured via
|
||||
// convention
|
||||
public class ApiExplorerInboundOutboundConvention : IApplicationModelConvention
|
||||
{
|
||||
private readonly TypeInfo _type;
|
||||
|
||||
public ApiExplorerInboundOutboundConvention(Type type)
|
||||
{
|
||||
_type = type.GetTypeInfo();
|
||||
}
|
||||
|
||||
public void Apply(ApplicationModel application)
|
||||
{
|
||||
foreach (var controller in application.Controllers)
|
||||
{
|
||||
if (controller.ControllerType == _type)
|
||||
{
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
if (action.ActionName == nameof(ApiExplorerInboundOutBoundController.SuppressedForPathMatching))
|
||||
{
|
||||
action.Selectors[0].AttributeRouteModel.SuppressPathMatching = true;
|
||||
}
|
||||
else if (action.ActionName == nameof(ApiExplorerInboundOutBoundController.SuppressedForLinkGeneration))
|
||||
{
|
||||
action.Selectors[0].AttributeRouteModel.SuppressLinkGeneration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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 Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ApiExplorerWebSite.Controllers
|
||||
{
|
||||
public class ApiExplorerInboundOutBoundController : Controller
|
||||
{
|
||||
[HttpGet("ApiExplorerInboundOutbound/SuppressedForLinkGeneration")]
|
||||
public void SuppressedForLinkGeneration()
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ApiExplorerInboundOutbound/SuppressedForPathMatching")]
|
||||
public void SuppressedForPathMatching()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ApiExplorerWebSite.Controllers;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
|
@ -25,6 +26,8 @@ namespace ApiExplorerWebSite
|
|||
options.Conventions.Add(new ApiExplorerVisibilityEnabledConvention());
|
||||
options.Conventions.Add(new ApiExplorerVisibilityDisabledConvention(
|
||||
typeof(ApiExplorerVisbilityDisabledByConventionController)));
|
||||
options.Conventions.Add(new ApiExplorerInboundOutboundConvention(
|
||||
typeof(ApiExplorerInboundOutBoundController)));
|
||||
|
||||
var jsonOutputFormatter = options.OutputFormatters.OfType<JsonOutputFormatter>().First();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace ApplicationModelWebSite
|
||||
{
|
||||
|
|
@ -17,5 +20,40 @@ namespace ApplicationModelWebSite
|
|||
{
|
||||
return ControllerContext.ActionDescriptor.Properties["source"].ToString() + " - " + helloWorld;
|
||||
}
|
||||
|
||||
[HttpGet("Home/CannotBeRouted", Name = nameof(SuppressPathMatching))]
|
||||
[HttpGet("Home/CanBeRouted")]
|
||||
[SuppressPatchMatchingConvention]
|
||||
public object SuppressPathMatching()
|
||||
{
|
||||
return "Hello world";
|
||||
}
|
||||
|
||||
[HttpGet("Home/SuppressLinkGeneration", Name = nameof(SuppressLinkGeneration))]
|
||||
[SuppressLinkGenerationConvention]
|
||||
public object SuppressLinkGeneration() => "Hello world";
|
||||
|
||||
[HttpGet("Home/RouteToSuppressLinkGeneration")]
|
||||
public IActionResult RouteToSuppressLinkGeneration() => RedirectToRoute(nameof(SuppressLinkGeneration));
|
||||
|
||||
[HttpGet("Home/RouteToSuppressPathMatching")]
|
||||
public IActionResult RouteToSuppressPathMatching() => RedirectToRoute(nameof(SuppressPathMatching));
|
||||
|
||||
private class SuppressPatchMatchingConvention : Attribute, IActionModelConvention
|
||||
{
|
||||
public void Apply(ActionModel model)
|
||||
{
|
||||
var selector = model.Selectors.First(f => f.AttributeRouteModel.Template == "Home/CannotBeRouted");
|
||||
selector.AttributeRouteModel.SuppressPathMatching = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class SuppressLinkGenerationConvention : Attribute, IActionModelConvention
|
||||
{
|
||||
public void Apply(ActionModel model)
|
||||
{
|
||||
model.Selectors[0].AttributeRouteModel.SuppressLinkGeneration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue