-
Notifications
You must be signed in to change notification settings - Fork 84
#55 add IIS rewrite map support with tests #168
Conversation
Hi @david-peden-q2, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution! TTYL, DNFBOT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks pretty solid, but there are some general issues we need to verify before we get this in.
{ | ||
if (string.IsNullOrEmpty(key)) | ||
{ | ||
throw new ArgumentException($"{nameof(key)} cannot be empty or null", nameof(key)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sufficient to just throw new ArgException(nameof(key))
{ | ||
if (string.IsNullOrEmpty(name)) | ||
{ | ||
throw new ArgumentException("name cannot be empty or null", nameof(name)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sufficient to just throw new ArgException(nameof(key))
} | ||
if (string.IsNullOrEmpty(value)) | ||
{ | ||
throw new ArgumentException($"{nameof(value)} cannot be empty or null", nameof(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
private const char CloseBrace = '}'; | ||
private readonly IDictionary<string, IISRewriteMap> _rewriteMaps; | ||
|
||
public InputParser(IDictionary<string, IISRewriteMap> rewriteMaps = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer this not to be a default parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you prefer? two constructors? if so, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was more of a style thing ASP.NET uses in general
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so replace with an explicit default constructor and non-null one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
IISRewriteMap rewriteMap; | ||
if (_rewriteMaps != null && _rewriteMaps.TryGetValue(parameter, out rewriteMap)) | ||
{ | ||
Pattern pattern = ParseString(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use var for all local parameters if possible :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not great for readability but ok
var rewriteMaps = new Dictionary<string, IISRewriteMap>(); | ||
foreach (XElement mapElement in mapsElement.Elements(RewriteTags.RewriteMap)) | ||
{ | ||
var map = new IISRewriteMap(mapElement.Attribute(RewriteTags.Name)?.Value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think this should be nullable; if the map doesn't have a name, we should probably throw.
{ | ||
map.AddOrUpdateEntry(addElement.Attribute(RewriteTags.Key)?.Value, addElement.Attribute(RewriteTags.Value)?.Value); | ||
} | ||
rewriteMaps.Add(map.Name, map); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we have two rewrite maps of the same name? We should decide whether we throw or overwrite an entry (IMO throwing is better).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dictionary.Add does throw. are you suggesting we throw an explicit exception?
if (xmlRoot != null) | ||
{ | ||
var rewriteMapParser = new RewriteMapParser(); | ||
_inputParser = new InputParser(rewriteMapParser.Parse(xmlRoot)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
{ | ||
return value; | ||
} | ||
var exception = new Exception($"Rewrite map entry not found: '{key}'"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This style seems a bit odd. I'd refer to how we normally do exception handling with logging errors
{ | ||
string key = _pattern.Evaluate(context, ruleMatch, condMatch); | ||
string value; | ||
if (_rewriteMap.TryGetEntry(key, out value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is wrong. If we don't find an entry in our map, we shouldn't be throwing, it just means it wasn't found in our server variables. Ex I have a url: /foo/bar which maps to foo?bar=1. If our url doesn't equal /foo/bar, it's not an invalid url, it just means it wasn't found in our map.
{ | ||
public IDictionary<string, IISRewriteMap> Parse(XElement xmlRoot) | ||
{ | ||
var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check here if xmlRoot is null (or nullable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is already a null check at the call site. should the check be in both places? i'll go ahead and add it but just wanted to point that out.
|
||
namespace Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite | ||
{ | ||
public class RewriteMapParser |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want this to be a static helper rather than an object?
|
||
if (xmlRoot != null) | ||
{ | ||
var rewriteMapParser = new RewriteMapParser(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again can probably make this static.
Name = name; | ||
} | ||
|
||
public void AddOrUpdateEntry(string key, string value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SetEntry?
private const char CloseBrace = '}'; | ||
private readonly IDictionary<string, IISRewriteMap> _rewriteMaps; | ||
|
||
public InputParser(IDictionary<string, IISRewriteMap> rewriteMaps = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
|
||
public bool TryGetEntry(string key, out string value) | ||
{ | ||
return _map.TryGetValue(key, out value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the only caller of this method does _rewriteMap.TryGetEntry(key, out value) ? value : null
, so why not build that in and just do a simple GetEntry or an indexer?
/// <param name="reader">The text reader stream.</param> | ||
/// <param name="rewriteMaps">An optional set of rewrite maps to uztilize when parsing rules. | ||
/// Rewrite maps in this collection will overwrite (supercede) duplicate named entries in the XML.</param> | ||
public static RewriteOptions AddIISUrlRewrite(this RewriteOptions options, TextReader reader, IEnumerable<IISRewriteMap> rewriteMaps = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make a second method rather than using a default parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If RewriteMaps are part of the xml file, why pass them in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For scenarios where there are other sources of rewrite maps. E.g., we have static rules (configured in xml) but have dynamically constructed rewrite maps (from an api/database call). This allows for composing rewrite maps outside of those statically defined in the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that a supported IIS scenario? We're not trying to add functionality beyond IIS at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't know but i doubt it. it's a minor addition and something i can account for by purely using the programmatic api. i won't lose sleep over cutting it but it is useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cut it for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will also need to be rebased on dev
@@ -0,0 +1,36 @@ | |||
using System; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copyright and license header needed here
{ | ||
if (xmlRoot == null) | ||
{ | ||
throw new ArgumentException(nameof(xmlRoot)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be an argument null exception
@@ -0,0 +1,22 @@ | |||
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copyright and license header needed here
using Microsoft.AspNetCore.Rewrite.Internal.IISUrlRewrite; | ||
using Microsoft.AspNetCore.Rewrite.Internal.PatternSegments; | ||
using Microsoft.Extensions.Logging.Testing; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this new line
using System.IO; | ||
using System.Linq; | ||
using System.Xml.Linq; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove new line
@@ -0,0 +1,43 @@ | |||
using System.Collections.Generic; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copyright and license header
var maps = new IISRewriteMapCollection { map }; | ||
|
||
string inputString = $"{{{expectedMapName}:{{R:1}}}}"; | ||
Pattern pattern = new InputParser(maps).ParseInputString(inputString); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var
Pattern pattern = new InputParser(maps).ParseInputString(inputString); | ||
Assert.Equal(1, pattern.PatternSegments.Count); | ||
|
||
PatternSegment segment = pattern.PatternSegments.Single(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var
return; | ||
} | ||
default: | ||
IISRewriteMap rewriteMap = _rewriteMaps?[parameter]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var
var mapsElement = xmlRoot.Descendants(RewriteTags.RewriteMaps).SingleOrDefault(); | ||
if (mapsElement == null) | ||
{ | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Should we throw here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. It's valid not to have a rewrite map. Am I missing your point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I was talking about. It just seems like and odd scenario
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i could move the test for the element outside of the parser and never end up in that scenario but this seems to be the preferred style throughout the project. i definitely don't think it should throw, though. is the structure what's objectionable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it should stay here. If its a valid scenario it can stay
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Remove this extra new line
Hi guys, do you have any idea when it get's released? Thank you 👍 |
https://github.com/aspnet/Home/wiki/Roadmap Q2 is our next release. |
No description provided.