-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Add support for optional parameters in Blazor routes #19733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// 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.Components.Routing | ||
{ | ||
/// <summary> | ||
/// A route constraint that allows the value to be null or parseable as the specified | ||
/// type. | ||
/// </summary> | ||
/// <typeparam name="T">The type to which the value must be parseable.</typeparam> | ||
internal class OptionalTypeRouteConstraint<T> : RouteConstraint | ||
{ | ||
public delegate bool TryParseDelegate(string str, out T result); | ||
|
||
private readonly TryParseDelegate _parser; | ||
|
||
public OptionalTypeRouteConstraint(TryParseDelegate parser) | ||
{ | ||
_parser = parser; | ||
} | ||
|
||
public override bool Match(string pathSegment, out object convertedValue) | ||
{ | ||
// Unset values are set to null in the Parameters object created in | ||
// the RouteContext. To match this pattern, unset optional parmeters | ||
// are converted to null. | ||
if (string.IsNullOrEmpty(pathSegment)) | ||
{ | ||
convertedValue = null; | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return true; | ||
} | ||
|
||
if (_parser(pathSegment, out var result)) | ||
{ | ||
convertedValue = result; | ||
return true; | ||
} | ||
else | ||
{ | ||
convertedValue = null; | ||
return false; | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// 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; | ||
|
@@ -12,9 +12,29 @@ public TemplateSegment(string template, string segment, bool isParameter) | |
{ | ||
IsParameter = isParameter; | ||
|
||
// Process segments that are not parameters or do not contain | ||
// a token separating a type constraint. | ||
if (!isParameter || segment.IndexOf(':') < 0) | ||
{ | ||
Value = segment; | ||
// Set the IsOptional flag to true for segments that contain | ||
// a parameter with no type constraints but optionality set | ||
// via the '?' token. | ||
if (segment.IndexOf('?') == segment.Length - 1) | ||
captainsafia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
IsOptional = true; | ||
Value = segment.Substring(0, segment.Length - 1); | ||
} | ||
// If the `?` optional marker shows up in the segment but not at the very end, | ||
// then throw an error. | ||
else if (segment.IndexOf('?') >= 0 && segment.IndexOf('?') != segment.Length - 1) | ||
{ | ||
throw new ArgumentException($"Malformed parameter '{segment}' in route '{template}'. '?' character can only appear at the end of parameter name."); | ||
} | ||
else | ||
{ | ||
Value = segment; | ||
} | ||
|
||
Constraints = Array.Empty<RouteConstraint>(); | ||
} | ||
else | ||
|
@@ -25,6 +45,10 @@ public TemplateSegment(string template, string segment, bool isParameter) | |
throw new ArgumentException($"Malformed parameter '{segment}' in route '{template}' has no name before the constraints list."); | ||
} | ||
|
||
// Set the IsOptional flag to true if any type constraints | ||
// for this parameter are designated as optional. | ||
IsOptional = tokens.Skip(1).Any(token => token.EndsWith("?")); | ||
|
||
Value = tokens[0]; | ||
Constraints = tokens.Skip(1) | ||
.Select(token => RouteConstraint.Parse(template, segment, token)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this already works just fine, but I suspect you might have to trim the trailing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The constraint itself wouldn't be treated as optional, but the segment would be. The result of this is as follows:
You'd still get two separate constraints to match with, one optional and one not. |
||
|
@@ -38,6 +62,8 @@ public TemplateSegment(string template, string segment, bool isParameter) | |
|
||
public bool IsParameter { get; } | ||
|
||
public bool IsOptional { get; } | ||
|
||
public RouteConstraint[] Constraints { get; } | ||
|
||
public bool Match(string pathSegment, out object matchedParameterValue) | ||
|
Uh oh!
There was an error while loading. Please reload this page.