Created
February 26, 2012 15:41
-
-
Save CurtHagenlocher/1917426 to your computer and use it in GitHub Desktop.
UrlBuilder.cs
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
public class UrlBuilder : IDynamicMetaObjectProvider | |
{ | |
/// <summary> | |
/// Usage: UrlBuilder.Build(url, key1: value1, key2: value2) | |
/// </summary> | |
/// <remarks> | |
/// values will be converted to strings via "ToString()". Any url-encoding must happen before | |
/// this method is called. Pass null as the value at runtime in order to omit the parameter | |
/// entirely. Use String.Empty for query parameters which should be present but have no value. | |
/// If url is a string, the result will be a string. If it is a System.Uri, the result will | |
/// be a Uri. | |
/// </remarks> | |
public static readonly dynamic Build = new UrlBuilder(); | |
private UrlBuilder() | |
{ | |
} | |
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) | |
{ | |
return new UrlBuilderMetaObject(parameter, this); | |
} | |
class UrlBuilderMetaObject : DynamicMetaObject | |
{ | |
private static readonly MethodInfo _appendStringMethod = typeof(StringBuilder).GetMethod("Append", new[] { typeof(string) }); | |
private static readonly MethodInfo _appendCharMethod = typeof(StringBuilder).GetMethod("Append", new[] { typeof(char) }); | |
private static readonly MethodInfo _toStringMethod = typeof(object).GetMethod("ToString", Type.EmptyTypes); | |
private static readonly ConstructorInfo _stringBuilderConstructor1 = typeof(StringBuilder).GetConstructor(new[] { typeof(string) }); | |
private static readonly ConstructorInfo _stringBuilderConstructor2 = typeof(StringBuilder).GetConstructor(Type.EmptyTypes); | |
private static readonly ConstructorInfo _argumentExceptionConstructor = typeof(ArgumentException).GetConstructor(new[] { typeof(string), typeof(string) }); | |
private static readonly ConstructorInfo _uriConstructor = typeof(Uri).GetConstructor(new[] { typeof(Uri), typeof(string) }); | |
private static readonly Expression _questionExpression = Expression.Constant('?'); | |
private static readonly Expression _ampersandExpression = Expression.Constant('&'); | |
private static readonly Expression _urlExpression = Expression.Constant("url"); | |
private static readonly Expression _trueExpression = Expression.Constant(true); | |
private static readonly Expression _nullExpression = Expression.Constant(null, typeof(object)); | |
private static readonly Expression _exceptionExpression1 = Expression.Throw( | |
Expression.New( | |
_argumentExceptionConstructor, | |
Expression.Constant("Expected one non-named argument and zero or more named arguments"), | |
_urlExpression)); | |
private static readonly Expression _exceptionExpression2 = Expression.Throw( | |
Expression.New( | |
_argumentExceptionConstructor, | |
Expression.Constant("First argument must be either a string or a Uri"), | |
_urlExpression)); | |
internal UrlBuilderMetaObject(Expression parameter, UrlBuilder builder) | |
: base(parameter, BindingRestrictions.Empty, builder) | |
{ | |
} | |
public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) | |
{ | |
if (binder.CallInfo.ArgumentNames.Count != binder.CallInfo.ArgumentCount - 1) | |
{ | |
return new DynamicMetaObject( | |
_exceptionExpression1, | |
BindingRestrictions.GetInstanceRestriction(this.Expression, this.Value)); | |
} | |
bool isString = args[0].LimitType == typeof(string); | |
bool isUri = args[0].LimitType == typeof(Uri); | |
if (!isString && !isUri) | |
{ | |
return new DynamicMetaObject( | |
_exceptionExpression2, | |
BindingRestrictions.GetTypeRestriction(args[0].Expression, args[0].LimitType)); | |
} | |
var builder = Expression.Variable(typeof(StringBuilder), "builder"); | |
var flag = Expression.Variable(typeof(bool), "nextFlag"); | |
var block = new Expression[args.Length + 1]; | |
block[0] = Expression.Assign(builder, isString ? | |
Expression.New(_stringBuilderConstructor1, args[0].Expression) : | |
Expression.New(_stringBuilderConstructor2)); | |
for (int i = 1; i < args.Length; i++) | |
{ | |
var stringExpr = args[i].LimitType == typeof(string) ? args[i].Expression : Expression.Call(args[i].Expression, _toStringMethod); | |
var append = Expression.Block( | |
Expression.Call(builder, _appendCharMethod, Expression.Condition(flag, _ampersandExpression, _questionExpression)), | |
Expression.Assign(flag, _trueExpression), | |
Expression.Call(builder, _appendStringMethod, Expression.Constant(HttpUtility.UrlEncode(binder.CallInfo.ArgumentNames[i - 1]) + '=')), | |
Expression.Call(builder, _appendStringMethod, stringExpr)); | |
if (args[i].LimitType.IsValueType) | |
{ | |
block[i] = append; | |
} | |
else | |
{ | |
block[i] = Expression.IfThen(Expression.NotEqual(args[i].Expression, _nullExpression), append); | |
} | |
} | |
var builtString = Expression.Call(builder, _toStringMethod); | |
block[args.Length] = isString ? (Expression)builtString : Expression.New(_uriConstructor, args[0].Expression, builtString); | |
return new DynamicMetaObject( | |
Expression.Block(new[] { builder, flag }, block), | |
BindingRestrictions.GetTypeRestriction(args[0].Expression, args[0].LimitType)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment