PageRenderTime 3076ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/Subsonic.Metro/MichMan.Utilities/UriInferer.cs

#
C# | 338 lines | 241 code | 48 blank | 49 comment | 35 complexity | ce44ea8c3cca00a02e7893760ff38176 MD5 | raw file
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved
  5. // Developer: Michael Antonio
  6. using System;
  7. using System.Reflection;
  8. using System.Linq;
  9. using System.Collections.Generic;
  10. using System.Text;
  11. using System.Collections;
  12. using MichMan.Utilities;
  13. namespace MichMan.Utilities
  14. {
  15. /// <summary>
  16. /// Specify the format for a URI. Something like "{host}/api/v1.0/{fname}"
  17. /// You can specify query params in the format, or use the QueryParam attribute, or both.
  18. /// Query string params specified with the QueryParam attribute go after the UriFormat.
  19. /// </summary>
  20. [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
  21. public class UriFormatAttribute : Attribute
  22. {
  23. public UriFormatAttribute(string format)
  24. {
  25. Format = format;
  26. UriKind = System.UriKind.RelativeOrAbsolute;
  27. }
  28. public UriFormatAttribute(string format, UriKind uriKind)
  29. {
  30. Format = format;
  31. UriKind = uriKind;
  32. }
  33. public virtual string Format { get; set; }
  34. public virtual UriKind UriKind { get; set; }
  35. }
  36. /// <summary>
  37. /// The idea is to have an easy way to specify headers.
  38. /// TODO: Implement
  39. /// </summary>
  40. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  41. public class HttpHeaderAttribute : FormattableAttribute
  42. {
  43. public HttpHeaderAttribute(string name)
  44. {
  45. Name = name;
  46. }
  47. public string Name { get; set; }
  48. public bool Required { get; set; }
  49. }
  50. /// <summary>
  51. /// A property that is used to fill in part of the UriFormat.
  52. /// </summary>
  53. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  54. public class UriParamAttribute : FormattableAttribute
  55. {
  56. public UriParamAttribute(string name)
  57. {
  58. Name = name;
  59. }
  60. public string Name { get; set; }
  61. }
  62. /// <summary>
  63. /// A list of params all with the same name. E.G. "id=foo&id=bar&id=baz"
  64. /// </summary>
  65. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  66. public class QueryParamListAttribute : QueryParamAttribute
  67. {
  68. public QueryParamListAttribute(string name)
  69. : base(name)
  70. {
  71. }
  72. public override string ToString(object value)
  73. {
  74. if (value == null)
  75. {
  76. if (Required)
  77. {
  78. // Cannot successfully construct the query parameter.
  79. throw new InvalidOperationException(String.Format("Required parameter \"{0}\" missing.", Name));
  80. }
  81. return null;
  82. }
  83. List<object> values = new List<object>();
  84. values.AddRange(((IEnumerable)value).Cast<object>());
  85. StringBuilder builder = new StringBuilder();
  86. foreach (var v in values)
  87. {
  88. if (builder.Length != 0)
  89. {
  90. builder.Append("&");
  91. }
  92. builder.Append(base.ToString(v));
  93. }
  94. return builder.ToString();
  95. }
  96. }
  97. /// <summary>
  98. /// Use this attribute to delcare query parameter properties on your request objects and they will be picked up automatically.
  99. /// </summary>
  100. [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  101. public class QueryParamAttribute : FormattableAttribute
  102. {
  103. public QueryParamAttribute(string name)
  104. {
  105. Name = name;
  106. Required = false;
  107. Order = int.MaxValue;
  108. }
  109. /// <summary>
  110. /// Query string parameter name.
  111. /// </summary>
  112. public string Name { get; set; }
  113. /// <summary>
  114. /// Throws an InvalidOperationException if this is set and the property is null.
  115. /// </summary>
  116. public bool Required { get; set; }
  117. /// <summary>
  118. /// Allows you to optionally order parameters.
  119. /// </summary>
  120. public int Order { get; set; }
  121. /// <summary>
  122. /// Override this to do something like pairs of params or something more complex.
  123. /// See QueryParamList class for an example.
  124. /// </summary>
  125. /// <param name="value"></param>
  126. /// <returns></returns>
  127. public override string ToString(object value)
  128. {
  129. if (value == null)
  130. {
  131. if (Required)
  132. {
  133. // Cannot successfully construct the query parameter.
  134. throw new InvalidOperationException(String.Format("Required parameter \"{0}\" missing.", Name));
  135. }
  136. return null;
  137. }
  138. string strValue = base.ToString(value);
  139. if (String.IsNullOrEmpty(strValue) && Required)
  140. {
  141. // Cannot successfully construct the query parameters.
  142. throw new InvalidOperationException(String.Format("Required parameter \"{0}\" missing.", Name));
  143. }
  144. return String.Format("{0}={1}", Name, strValue);
  145. }
  146. }
  147. public class UriInferer
  148. {
  149. public UriInferer(object target)
  150. {
  151. Target = target;
  152. }
  153. public object Target { get; set; }
  154. public static Uri InferUri(object o)
  155. {
  156. return new UriInferer(o).BuildUri();
  157. }
  158. public static Dictionary<string, string> InferHeaders(object o)
  159. {
  160. return new UriInferer(o).BuildHeaders();
  161. }
  162. public Dictionary<string, string> BuildHeaders()
  163. {
  164. var headers = new Dictionary<string, string>();
  165. foreach (var prop in Target.GetType().GetProperties())
  166. {
  167. HttpHeaderAttribute httpHeaderAttribute = prop.GetCustomAttributes(typeof(HttpHeaderAttribute), true).Cast<HttpHeaderAttribute>().SingleOrDefault();
  168. if (httpHeaderAttribute == null)
  169. {
  170. continue;
  171. }
  172. string strValue = null;
  173. // Note that this returns null or the value for nullable types. No conversion necessary.
  174. object value = prop.GetValue(Target, null);
  175. if (value != null || httpHeaderAttribute.Required)
  176. {
  177. strValue = httpHeaderAttribute.ToString(value);
  178. }
  179. if (strValue != null)
  180. {
  181. headers.Add(httpHeaderAttribute.Name, strValue);
  182. }
  183. }
  184. return headers;
  185. }
  186. public Uri BuildUri()
  187. {
  188. return BuildQueryParameters(BuildPath());
  189. }
  190. public Uri BuildPath()
  191. {
  192. UriFormatAttribute uriFormatAttribute = Target.GetType().GetTypeInfo().GetCustomAttributes(typeof(UriFormatAttribute), true).Cast<UriFormatAttribute>().SingleOrDefault();
  193. if (uriFormatAttribute == null)
  194. {
  195. return null;
  196. }
  197. string format = uriFormatAttribute.Format;
  198. foreach (var prop in Target.GetType().GetProperties())
  199. {
  200. UriParamAttribute uriParamAttribute = prop.GetCustomAttributes(typeof(UriParamAttribute), true).Cast<UriParamAttribute>().SingleOrDefault();
  201. if (uriParamAttribute == null)
  202. {
  203. continue;
  204. }
  205. string strValue = null;
  206. // Note that this returns null or the value for nullable types. No conversion necessary.
  207. object value = prop.GetValue(Target, null);
  208. if (value == null)
  209. {
  210. strValue = string.Empty;
  211. }
  212. else
  213. {
  214. strValue = uriParamAttribute.ToString(value);
  215. }
  216. format = format.Replace("{" + uriParamAttribute.Name + "}", strValue);
  217. }
  218. if (String.IsNullOrEmpty(format))
  219. {
  220. return null;
  221. }
  222. return new Uri(format, uriFormatAttribute.UriKind);
  223. }
  224. private class QueryParam
  225. {
  226. public string Param{ get; set; }
  227. public int Order { get; set; }
  228. }
  229. /// <summary>
  230. /// If any properties are annotated with the QueryParam attribute, use them here.
  231. /// </summary>
  232. public Uri BuildQueryParameters(Uri uri)
  233. {
  234. if (uri == null)
  235. {
  236. return null;
  237. }
  238. UriFormatAttribute uriFormatAttribute = (UriFormatAttribute)Target.GetType().GetTypeInfo().GetCustomAttributes(typeof(UriFormatAttribute), true).SingleOrDefault();
  239. List<QueryParam> qParams = new List<QueryParam>();
  240. foreach (var prop in Target.GetType().GetProperties())
  241. {
  242. QueryParamAttribute queryParamAttribute = (QueryParamAttribute)prop.GetCustomAttributes(true).SingleOrDefault(a => typeof(QueryParamAttribute).GetTypeInfo().IsAssignableFrom(a.GetType().GetTypeInfo()));
  243. if (queryParamAttribute == null)
  244. {
  245. continue;
  246. }
  247. // Note that this returns null or the value for nullable types. No conversion necessary.
  248. object value = prop.GetValue(Target, null);
  249. string strValue = queryParamAttribute.ToString(value);
  250. if (strValue == null)
  251. {
  252. continue;
  253. }
  254. QueryParam qp = new QueryParam()
  255. {
  256. Param = strValue,
  257. Order = queryParamAttribute.Order,
  258. };
  259. qParams.Add(qp);
  260. }
  261. // Now we have all the annotated query parameters.
  262. if (qParams.Count == 0)
  263. {
  264. return uri;
  265. }
  266. // Sort the list by order
  267. qParams = qParams.OrderBy(qp => qp.Order).ToList();
  268. StringBuilder queryString = new StringBuilder();
  269. queryString.Append(uri.ToString().Contains("?") ? "&" : "?");
  270. foreach (var param in qParams)
  271. {
  272. if (queryString.Length > 2)
  273. {
  274. queryString.Append("&");
  275. }
  276. queryString.Append(param.Param);
  277. }
  278. UriKind kind;
  279. if (uriFormatAttribute != null)
  280. {
  281. kind = uriFormatAttribute.UriKind;
  282. }
  283. else
  284. {
  285. kind = uri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative;
  286. }
  287. return new Uri( uri.ToString() + queryString, kind);
  288. }
  289. }
  290. }