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

Commit

Permalink
Moved AttributeRouting from MVC
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaybhargavb committed Nov 17, 2015
1 parent f6616db commit 4d69ad0
Show file tree
Hide file tree
Showing 33 changed files with 4,512 additions and 9 deletions.
6 changes: 6 additions & 0 deletions NuGetPackageVerifier.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"Microsoft.AspNet.Routing": { }
}
},
"adx-nonshipping": {
"rules": [],
"packages": {
"Microsoft.AspNet.Routing.DecisionTree.Sources": { }
}
},
"Default": { // Rules to run for packages not listed in any other set.
"rules": [
"AssemblyHasDocumentFileRule",
Expand Down
32 changes: 31 additions & 1 deletion Routing.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22115.0
VisualStudioVersion = 14.0.24711.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0E966C37-7334-4D96-AAF6-9F49FBD166E3}"
EndProject
Expand All @@ -20,6 +20,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
global.json = global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Routing.DecisionTree.Sources", "src\Microsoft.AspNet.Routing.DecisionTree.Sources\Microsoft.AspNet.Routing.DecisionTree.Sources.xproj", "{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Routing.DecisionTree.Sources.Tests", "test\Microsoft.AspNet.Routing.DecisionTree.Sources.Tests\Microsoft.AspNet.Routing.DecisionTree.Sources.Tests.xproj", "{09C2933C-23AC-41B7-994D-E8A5184A629C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -60,6 +64,30 @@ Global
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{DB94E647-C73A-4F52-A126-AA7544CCF33B}.Release|x86.ActiveCfg = Release|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Debug|x86.ActiveCfg = Debug|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Debug|x86.Build.0 = Debug|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Release|Any CPU.Build.0 = Release|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Release|x86.ActiveCfg = Release|Any CPU
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8}.Release|x86.Build.0 = Release|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|x86.ActiveCfg = Debug|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Debug|x86.Build.0 = Debug|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Any CPU.Build.0 = Release|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|x86.ActiveCfg = Release|Any CPU
{09C2933C-23AC-41B7-994D-E8A5184A629C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -68,5 +96,7 @@ Global
{1EE54D32-6CED-4206-ACF5-3DC1DD39D228} = {0E966C37-7334-4D96-AAF6-9F49FBD166E3}
{636D79ED-7B32-487C-BDA5-D2A1AAA97371} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
{DB94E647-C73A-4F52-A126-AA7544CCF33B} = {C3ADD55B-B9C7-4061-8AD4-6A70D1AE3B2E}
{ABD5AA59-6000-4A3D-A54F-4B636F725AE8} = {0E966C37-7334-4D96-AAF6-9F49FBD166E3}
{09C2933C-23AC-41B7-994D-E8A5184A629C} = {95359B4B-4C85-4B44-A75B-0621905C4CF6}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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;

namespace Microsoft.AspNet.Routing.DecisionTree
{
internal class DecisionCriterion<TItem>
{
public string Key { get; set; }

public Dictionary<object, DecisionTreeNode<TItem>> Branches { get; set; }

public DecisionTreeNode<TItem> Fallback { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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.AspNet.Routing.DecisionTree
{
internal struct DecisionCriterionValue
{
private readonly bool _isCatchAll;
private readonly object _value;

public DecisionCriterionValue(object value, bool isCatchAll)
{
_value = value;
_isCatchAll = isCatchAll;
}

public bool IsCatchAll
{
get { return _isCatchAll; }
}

public object Value
{
get { return _value; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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;

namespace Microsoft.AspNet.Routing.DecisionTree
{
internal class DecisionCriterionValueEqualityComparer : IEqualityComparer<DecisionCriterionValue>
{
public DecisionCriterionValueEqualityComparer(IEqualityComparer<object> innerComparer)
{
InnerComparer = innerComparer;
}

public IEqualityComparer<object> InnerComparer { get; private set; }

public bool Equals(DecisionCriterionValue x, DecisionCriterionValue y)
{
return x.IsCatchAll == y.IsCatchAll || InnerComparer.Equals(x.Value, y.Value);
}

public int GetHashCode(DecisionCriterionValue obj)
{
if (obj.IsCatchAll)
{
return 0;
}
else
{
return InnerComparer.GetHashCode(obj.Value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// 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;

namespace Microsoft.AspNet.Routing.DecisionTree
{
// This code generates a minimal tree of decision criteria that map known categorical data
// (key-value-pairs) to a set of inputs. Action Selection is the best example of how this
// can be used, so the comments here will describe the process from the point-of-view,
// though the decision tree is generally applicable to like-problems.
//
// Care has been taken here to keep the performance of building the data-structure at a
// reasonable level, as this has an impact on startup cost for action selection. Additionally
// we want to hold on to the minimal amount of memory needed once we've built the tree.
//
// Ex:
// Given actions like the following, create a decision tree that will help action
// selection work efficiently.
//
// Given any set of route data it should be possible to traverse the tree using the
// presence our route data keys (like action), and whether or not they match any of
// the known values for that route data key, to find the set of actions that match
// the route data.
//
// Actions:
//
// { controller = "Home", action = "Index" }
// { controller = "Products", action = "Index" }
// { controller = "Products", action = "Buy" }
// { area = "Admin", controller = "Users", action = "AddUser" }
//
// The generated tree looks like this (json-like-notation):
//
// {
// action : {
// "AddUser" : {
// controller : {
// "Users" : {
// area : {
// "Admin" : match { area = "Admin", controller = "Users", action = "AddUser" }
// }
// }
// }
// },
// "Buy" : {
// controller : {
// "Products" : {
// area : {
// null : match { controller = "Products", action = "Buy" }
// }
// }
// }
// },
// "Index" : {
// controller : {
// "Home" : {
// area : {
// null : match { controller = "Home", action = "Index" }
// }
// }
// "Products" : {
// area : {
// "null" : match { controller = "Products", action = "Index" }
// }
// }
// }
// }
// }
// }
internal static class DecisionTreeBuilder<TItem>
{
public static DecisionTreeNode<TItem> GenerateTree(IReadOnlyList<TItem> items, IClassifier<TItem> classifier)
{
var itemDescriptors = new List<ItemDescriptor<TItem>>();
for (var i = 0; i < items.Count; i++)
{
itemDescriptors.Add(new ItemDescriptor<TItem>()
{
Criteria = classifier.GetCriteria(items[i]),
Index = i,
Item = items[i],
});
}

var comparer = new DecisionCriterionValueEqualityComparer(classifier.ValueComparer);
return GenerateNode(
new TreeBuilderContext(),
comparer,
itemDescriptors);
}

private static DecisionTreeNode<TItem> GenerateNode(
TreeBuilderContext context,
DecisionCriterionValueEqualityComparer comparer,
IList<ItemDescriptor<TItem>> items)
{
// The extreme use of generics here is intended to reduce the number of intermediate
// allocations of wrapper classes. Performance testing found that building these trees allocates
// significant memory that we can avoid and that it has a real impact on startup.
var criteria = new Dictionary<string, Criterion>(StringComparer.OrdinalIgnoreCase);

// Matches are items that have no remaining criteria - at this point in the tree
// they are considered accepted.
var matches = new List<TItem>();

// For each item in the working set, we want to map it to it's possible criteria-branch
// pairings, then reduce that tree to the minimal set.
foreach (var item in items)
{
var unsatisfiedCriteria = 0;

foreach (var kvp in item.Criteria)
{
// context.CurrentCriteria is the logical 'stack' of criteria that we've already processed
// on this branch of the tree.
if (context.CurrentCriteria.Contains(kvp.Key))
{
continue;
}

unsatisfiedCriteria++;

Criterion criterion;
if (!criteria.TryGetValue(kvp.Key, out criterion))
{
criterion = new Criterion(comparer);
criteria.Add(kvp.Key, criterion);
}

List<ItemDescriptor<TItem>> branch;
if (!criterion.TryGetValue(kvp.Value, out branch))
{
branch = new List<ItemDescriptor<TItem>>();
criterion.Add(kvp.Value, branch);
}

branch.Add(item);
}

// If all of the criteria on item are satisfied by the 'stack' then this item is a match.
if (unsatisfiedCriteria == 0)
{
matches.Add(item.Item);
}
}

// Iterate criteria in order of branchiness to determine which one to explore next. If a criterion
// has no 'new' matches under it then we can just eliminate that part of the tree.
var reducedCriteria = new List<DecisionCriterion<TItem>>();
foreach (var criterion in criteria.OrderByDescending(c => c.Value.Count))
{
var reducedBranches = new Dictionary<object, DecisionTreeNode<TItem>>(comparer.InnerComparer);
DecisionTreeNode<TItem> fallback = null;

foreach (var branch in criterion.Value)
{
var reducedItems = new List<ItemDescriptor<TItem>>();
foreach (var item in branch.Value)
{
if (context.MatchedItems.Add(item))
{
reducedItems.Add(item);
}
}

if (reducedItems.Count > 0)
{
var childContext = new TreeBuilderContext(context);
childContext.CurrentCriteria.Add(criterion.Key);

var newBranch = GenerateNode(childContext, comparer, branch.Value);
if (branch.Key.IsCatchAll)
{
fallback = newBranch;
}
else
{
reducedBranches.Add(branch.Key.Value, newBranch);
}
}
}

if (reducedBranches.Count > 0 || fallback != null)
{
var newCriterion = new DecisionCriterion<TItem>()
{
Key = criterion.Key,
Branches = reducedBranches,
Fallback = fallback,
};

reducedCriteria.Add(newCriterion);
}
}

return new DecisionTreeNode<TItem>()
{
Criteria = reducedCriteria.ToList(),
Matches = matches,
};
}

private class TreeBuilderContext
{
public TreeBuilderContext()
{
CurrentCriteria = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
MatchedItems = new HashSet<ItemDescriptor<TItem>>();
}

public TreeBuilderContext(TreeBuilderContext other)
{
CurrentCriteria = new HashSet<string>(other.CurrentCriteria, StringComparer.OrdinalIgnoreCase);
MatchedItems = new HashSet<ItemDescriptor<TItem>>();
}

public HashSet<string> CurrentCriteria { get; private set; }

public HashSet<ItemDescriptor<TItem>> MatchedItems { get; private set; }
}

// Subclass just to give a logical name to a mess of generics
private class Criterion : Dictionary<DecisionCriterionValue, List<ItemDescriptor<TItem>>>
{
public Criterion(DecisionCriterionValueEqualityComparer comparer)
: base(comparer)
{
}
}
}
}
Loading

0 comments on commit 4d69ad0

Please sign in to comment.