Last active
April 1, 2021 16:39
-
-
Save yringler/eb10736533f2e63e54fcbac6a95d752e to your computer and use it in GitHub Desktop.
MVC Core: Prefer action which best matches query parameters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Mvc.ActionConstraints; | |
using Microsoft.AspNetCore.Mvc.ModelBinding; | |
using MoreLinq; | |
using System; | |
using System.Linq; | |
namespace Api.Constraints | |
{ | |
/// <summary> | |
/// Use amount of matching query parameters to find the right action. | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] | |
public class RouteWithQueryParamatersAttribute : Attribute, IActionConstraint | |
{ | |
public int Order => 0; | |
public bool Accept(ActionConstraintContext context) | |
{ | |
var queryKeys = context.RouteContext.HttpContext.Request.Query.Keys; | |
var actionIdsAndCount = context.Candidates.Select( | |
candidate => | |
{ | |
// Get a list of all of the actions parameters which come from the query string. | |
var paramaters = | |
candidate.Action.Parameters | |
.Where(paramater => paramater.BindingInfo.BindingSource.Id == BindingSource.Query.Id) | |
.ToList(); | |
return new | |
{ | |
ParamaterCount = paramaters.Count, | |
// The number of parameters which can be provided by the current query string. | |
MatchingCount = | |
paramaters.Count( | |
paramater => queryKeys.Contains(paramater.Name)), | |
ParamaterId = candidate.Action.Id | |
}; | |
}).ToList(); | |
/* | |
* If none of the candidates are able to use the query string (perhaps[1] because there isn't a query string), | |
* accept an action which doesn't need any query parameters. | |
* | |
* [1] It would be simplest to say that an action with no query parameters only matches | |
* a request with no query parameters. This isn't robust, though, because a query string could be used eg for authentication. | |
* Therefor, the rule is that an action with no query parameters matches a request with no query parameters *relevant to the actions*, | |
* as defined above. | |
*/ | |
if (actionIdsAndCount.All(action => action.MatchingCount == 0)) | |
return actionIdsAndCount.First(action => action.ParamaterId == context.CurrentCandidate.Action.Id).ParamaterCount == 0; | |
// Get the actions which have the highest percentage of matched query parameters. | |
var highest = actionIdsAndCount | |
// If an action doesn't accept any query parameters, it only matches a request without any | |
// query parameters, and that was already handled earlier. | |
.Where(item => item.ParamaterCount > 0) | |
// Get the matches with the greatest percentage of used parameters. | |
.MaxBy(item => item.MatchingCount / item.ParamaterCount) | |
// If multiple actions have the same percentage, prefer the action with the highest number | |
// of used parameters. | |
// A common case would be if one controller accepts 2 parameters, and another accepts the same 2 and another 1, and | |
// the request has those 3 parameters - both actions have %100 of their parameters matching, so prefer the one which has | |
// 3 paramaters matching. | |
.MaxBy(item => item.MatchingCount); | |
// The current action is accepted if it is one of the highest. | |
return highest.Any(action => action.ParamaterId == context.CurrentCandidate.Action.Id); | |
} | |
} | |
} |
Just add the class to your code, and add the attribute to the controller.
You don't have to register it.
That being said, I personally stopped using it. There are enough edge cases
etc where it doesn't work, I just just a different endpoint when I need
something like that.
…On Thu, Apr 1, 2021, 12:28 PM Amaan Khan ***@***.***> wrote:
***@***.**** commented on this gist.
------------------------------
how to use it? i mean where to register it?
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<https://gist.github.com/eb10736533f2e63e54fcbac6a95d752e#gistcomment-3689806>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA7B3KIC476FFMLNIGS4FKLTGSNLNANCNFSM42HM6VIA>
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
how to use it? i mean where to register it?