Skip to content

Instantly share code, notes, and snippets.

@CurtHagenlocher
Created February 26, 2012 15:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CurtHagenlocher/1917426 to your computer and use it in GitHub Desktop.
Save CurtHagenlocher/1917426 to your computer and use it in GitHub Desktop.
UrlBuilder.cs
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