PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/test/System.Web.Http.Integration.Test/ModelBinding/DefaultActionValueBinderTest.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 1049 lines | 769 code | 168 blank | 112 comment | 7 complexity | 7d3b831749ebb569a1bb1a8b51661534 MD5 | raw file
  1. using System.Collections.Generic;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Net.Http;
  6. using System.Net.Http.Formatting;
  7. using System.Net.Http.Headers;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using System.Web.Http.Controllers;
  12. using System.Web.Http.Routing;
  13. using System.Web.Http.ValueProviders;
  14. using Microsoft.TestCommon;
  15. using Newtonsoft.Json;
  16. using Newtonsoft.Json.Linq;
  17. using Xunit;
  18. namespace System.Web.Http.ModelBinding
  19. {
  20. public class DefaultActionValueBinderTest
  21. {
  22. [Fact]
  23. public void BindValuesAsync_Uses_DefaultValues()
  24. {
  25. // Arrange
  26. HttpActionContext context = ContextUtil.CreateActionContext(
  27. ContextUtil.CreateControllerContext(),
  28. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
  29. CancellationToken cancellationToken = new CancellationToken();
  30. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  31. // Act
  32. provider.BindValuesAsync(context, cancellationToken).Wait();
  33. // Assert
  34. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  35. expectedResult["id"] = 0;
  36. expectedResult["firstName"] = "DefaultFirstName";
  37. expectedResult["lastName"] = "DefaultLastName";
  38. Assert.Equal(expectedResult, context.ActionArguments, new DictionaryEqualityComparer());
  39. }
  40. [Fact]
  41. public void BindValuesAsync_WithObjectContentInRequest_Works()
  42. {
  43. // Arrange
  44. ActionValueItem cust = new ActionValueItem() { FirstName = "FirstName", LastName = "LastName", Id = 1 };
  45. HttpActionContext context = ContextUtil.CreateActionContext(
  46. ContextUtil.CreateControllerContext(),
  47. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  48. context.ControllerContext.Request = new HttpRequestMessage
  49. {
  50. Content = new ObjectContent<ActionValueItem>(cust, new JsonMediaTypeFormatter())
  51. };
  52. CancellationToken cancellationToken = new CancellationToken();
  53. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  54. // Act
  55. provider.BindValuesAsync(context, cancellationToken).Wait();
  56. // Assert
  57. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  58. expectedResult["item"] = cust;
  59. Assert.Equal(expectedResult, context.ActionArguments, new DictionaryEqualityComparer());
  60. }
  61. #region Query Strings
  62. [Fact]
  63. public void BindValuesAsync_ConvertEmptyString()
  64. {
  65. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  66. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  67. {
  68. Method = HttpMethod.Get,
  69. RequestUri = new Uri("http://localhost?A1=&A2=&A3=&A4=")
  70. }),
  71. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetTestEmptyString") });
  72. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  73. // Act
  74. provider.BindValuesAsync(actionContext, CancellationToken.None).Wait();
  75. // Assert
  76. ConvertEmptyStringContainer arg = (ConvertEmptyStringContainer) actionContext.ActionArguments["x"];
  77. Assert.NotNull(arg);
  78. Assert.Equal(String.Empty, arg.A1);
  79. Assert.Null(arg.A2);
  80. Assert.Null(arg.A3);
  81. Assert.Null(arg.A4);
  82. }
  83. [Fact]
  84. public void BindValuesAsync_Query_String_Values_To_Simple_Types()
  85. {
  86. // Arrange
  87. CancellationToken cancellationToken = new CancellationToken();
  88. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  89. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  90. {
  91. Method = HttpMethod.Get,
  92. RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
  93. }),
  94. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
  95. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  96. // Act
  97. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  98. // Assert
  99. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  100. expectedResult["id"] = 5;
  101. expectedResult["firstName"] = "queryFirstName";
  102. expectedResult["lastName"] = "queryLastName";
  103. Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
  104. }
  105. [Fact]
  106. public void BindValuesAsync_Query_String_Values_To_Simple_Types_With_FromUriAttribute()
  107. {
  108. // Arrange
  109. CancellationToken cancellationToken = new CancellationToken();
  110. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  111. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  112. {
  113. Method = HttpMethod.Get,
  114. RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
  115. }),
  116. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromUri") });
  117. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  118. // Act
  119. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  120. // Assert
  121. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  122. expectedResult["id"] = 5;
  123. expectedResult["firstName"] = "queryFirstName";
  124. expectedResult["lastName"] = "queryLastName";
  125. Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
  126. }
  127. [Fact]
  128. public void BindValuesAsync_Query_String_Values_To_Complex_Types()
  129. {
  130. // Arrange
  131. CancellationToken cancellationToken = new CancellationToken();
  132. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  133. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  134. {
  135. Method = HttpMethod.Get,
  136. RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
  137. }),
  138. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
  139. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  140. // Act
  141. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  142. // Assert
  143. Assert.True(actionContext.ModelState.IsValid);
  144. Assert.Equal(1, actionContext.ActionArguments.Count);
  145. ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
  146. Assert.Equal(5, deserializedActionValueItem.Id);
  147. Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
  148. Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
  149. }
  150. [Fact]
  151. public void BindValuesAsync_Query_String_Values_To_Post_Complex_Types()
  152. {
  153. // Arrange
  154. CancellationToken cancellationToken = new CancellationToken();
  155. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  156. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  157. {
  158. Method = HttpMethod.Get,
  159. RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&lastName=queryLastName")
  160. }),
  161. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexTypeUri") });
  162. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  163. // Act
  164. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  165. // Assert
  166. Assert.True(actionContext.ModelState.IsValid);
  167. Assert.Equal(1, actionContext.ActionArguments.Count);
  168. ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
  169. Assert.Equal(5, deserializedActionValueItem.Id);
  170. Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
  171. Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
  172. }
  173. [Fact]
  174. public void BindValuesAsync_Query_String_Values_To_Post_Enumerable_Complex_Types()
  175. {
  176. // Arrange
  177. CancellationToken cancellationToken = new CancellationToken();
  178. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  179. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  180. {
  181. Method = HttpMethod.Get,
  182. RequestUri = new Uri("http://localhost?items[0].id=5&items[0].firstName=queryFirstName&items[0].lastName=queryLastName")
  183. }),
  184. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostEnumerableUri") });
  185. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  186. // Act
  187. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  188. // Assert
  189. Assert.True(actionContext.ModelState.IsValid);
  190. Assert.Equal(1, actionContext.ActionArguments.Count);
  191. IEnumerable<ActionValueItem> items = Assert.IsAssignableFrom<IEnumerable<ActionValueItem>>(actionContext.ActionArguments.First().Value);
  192. ActionValueItem deserializedActionValueItem = items.First();
  193. Assert.Equal(5, deserializedActionValueItem.Id);
  194. Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
  195. Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
  196. }
  197. [Fact]
  198. public void BindValuesAsync_Query_String_Values_To_Post_Enumerable_Complex_Types_No_Index()
  199. {
  200. // Arrange
  201. CancellationToken cancellationToken = new CancellationToken();
  202. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  203. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  204. {
  205. Method = HttpMethod.Get,
  206. RequestUri = new Uri("http://localhost?id=5&firstName=queryFirstName&items.lastName=queryLastName")
  207. }),
  208. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostEnumerableUri") });
  209. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  210. // Act
  211. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  212. // Assert
  213. Assert.True(actionContext.ModelState.IsValid);
  214. Assert.Equal(1, actionContext.ActionArguments.Count);
  215. IEnumerable<ActionValueItem> items = Assert.IsAssignableFrom<IEnumerable<ActionValueItem>>(actionContext.ActionArguments.First().Value);
  216. Assert.Equal(0, items.Count()); // expect unsuccessful bind but proves we don't loop infinitely
  217. }
  218. [Fact]
  219. public void BindValuesAsync_Query_String_Values_To_ComplexType_Using_Prefixes()
  220. {
  221. // Arrange
  222. CancellationToken cancellationToken = new CancellationToken();
  223. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  224. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  225. {
  226. Method = HttpMethod.Get,
  227. RequestUri = new Uri("http://localhost?item.id=5&item.firstName=queryFirstName&item.lastName=queryLastName")
  228. }),
  229. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
  230. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  231. // Act
  232. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  233. // Assert
  234. Assert.Equal(1, actionContext.ActionArguments.Count);
  235. ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
  236. Assert.Equal(5, deserializedActionValueItem.Id);
  237. Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
  238. Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
  239. }
  240. [Fact]
  241. public void BindValuesAsync_Query_String_Values_To_ComplexType_Using_FromUriAttribute()
  242. {
  243. // Arrange
  244. CancellationToken cancellationToken = new CancellationToken();
  245. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  246. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  247. {
  248. Method = HttpMethod.Get,
  249. RequestUri = new Uri("http://localhost?item.id=5&item.firstName=queryFirstName&item.lastName=queryLastName")
  250. }),
  251. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItemFromUri") });
  252. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  253. // Act
  254. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  255. // Assert
  256. Assert.Equal(1, actionContext.ActionArguments.Count);
  257. ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(actionContext.ActionArguments.First().Value);
  258. Assert.Equal(5, deserializedActionValueItem.Id);
  259. Assert.Equal("queryFirstName", deserializedActionValueItem.FirstName);
  260. Assert.Equal("queryLastName", deserializedActionValueItem.LastName);
  261. }
  262. [Fact]
  263. public void BindValuesAsync_Query_String_Values_Using_Custom_ValueProviderAttribute()
  264. {
  265. // Arrange
  266. CancellationToken cancellationToken = new CancellationToken();
  267. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  268. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  269. {
  270. Method = HttpMethod.Get
  271. }),
  272. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromCustom") });
  273. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  274. // Act
  275. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  276. // Assert
  277. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  278. expectedResult["id"] = 99;
  279. expectedResult["firstName"] = "99";
  280. expectedResult["lastName"] = "99";
  281. Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
  282. }
  283. [Fact]
  284. public void BindValuesAsync_Query_String_Values_Using_Prefix_To_Rename()
  285. {
  286. // Arrange
  287. CancellationToken cancellationToken = new CancellationToken();
  288. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  289. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  290. {
  291. Method = HttpMethod.Get,
  292. RequestUri = new Uri("http://localhost?custid=5&first=renamedFirstName&last=renamedLastName")
  293. // notice the query string names match the prefixes in GetFromNamed() and not the actual parameter names
  294. }),
  295. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromNamed") });
  296. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  297. // Act
  298. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  299. // Assert
  300. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  301. expectedResult["id"] = 5;
  302. expectedResult["firstName"] = "renamedFirstName";
  303. expectedResult["lastName"] = "renamedLastName";
  304. Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
  305. }
  306. [Fact]
  307. public void BindValuesAsync_Query_String_Values_To_Complex_Types_With_Validation_Error()
  308. {
  309. // Arrange
  310. CancellationToken cancellationToken = new CancellationToken();
  311. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  312. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  313. {
  314. Method = HttpMethod.Get,
  315. RequestUri = new Uri("http://localhost?id=100&firstName=queryFirstName&lastName=queryLastName")
  316. }),
  317. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
  318. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  319. // Act
  320. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  321. // Assert
  322. Assert.False(actionContext.ModelState.IsValid);
  323. }
  324. #endregion Query Strings
  325. #region RouteData
  326. [Fact]
  327. public void BindValuesAsync_RouteData_Values_To_Simple_Types()
  328. {
  329. // Arrange
  330. CancellationToken cancellationToken = new CancellationToken();
  331. HttpRouteData route = new HttpRouteData(new HttpRoute());
  332. route.Values.Add("id", 6);
  333. route.Values.Add("firstName", "routeFirstName");
  334. route.Values.Add("lastName", "routeLastName");
  335. HttpActionContext controllerContext = ContextUtil.CreateActionContext(
  336. ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
  337. {
  338. Method = HttpMethod.Get,
  339. RequestUri = new Uri("http://localhost")
  340. }),
  341. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
  342. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  343. // Act
  344. provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
  345. // Assert
  346. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  347. expectedResult["id"] = 6;
  348. expectedResult["firstName"] = "routeFirstName";
  349. expectedResult["lastName"] = "routeLastName";
  350. Assert.Equal(expectedResult, controllerContext.ActionArguments, new DictionaryEqualityComparer());
  351. }
  352. [Fact]
  353. public void BindValuesAsync_RouteData_Values_To_Simple_Types_Using_FromUriAttribute()
  354. {
  355. // Arrange
  356. CancellationToken cancellationToken = new CancellationToken();
  357. HttpRouteData route = new HttpRouteData(new HttpRoute());
  358. route.Values.Add("id", 6);
  359. route.Values.Add("firstName", "routeFirstName");
  360. route.Values.Add("lastName", "routeLastName");
  361. HttpActionContext controllerContext = ContextUtil.CreateActionContext(
  362. ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
  363. {
  364. Method = HttpMethod.Get,
  365. RequestUri = new Uri("http://localhost")
  366. }),
  367. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Get") });
  368. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  369. // Act
  370. provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
  371. // Assert
  372. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  373. expectedResult["id"] = 6;
  374. expectedResult["firstName"] = "routeFirstName";
  375. expectedResult["lastName"] = "routeLastName";
  376. Assert.Equal(expectedResult, controllerContext.ActionArguments, new DictionaryEqualityComparer());
  377. }
  378. [Fact]
  379. public void BindValuesAsync_RouteData_Values_To_Complex_Types()
  380. {
  381. // Arrange
  382. CancellationToken cancellationToken = new CancellationToken();
  383. HttpRouteData route = new HttpRouteData(new HttpRoute());
  384. route.Values.Add("id", 6);
  385. route.Values.Add("firstName", "routeFirstName");
  386. route.Values.Add("lastName", "routeLastName");
  387. HttpActionContext controllerContext = ContextUtil.CreateActionContext(
  388. ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
  389. {
  390. Method = HttpMethod.Get,
  391. RequestUri = new Uri("http://localhost")
  392. }),
  393. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItem") });
  394. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  395. // Act
  396. provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
  397. // Assert
  398. Assert.Equal(1, controllerContext.ActionArguments.Count);
  399. ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(controllerContext.ActionArguments.First().Value);
  400. Assert.Equal(6, deserializedActionValueItem.Id);
  401. Assert.Equal("routeFirstName", deserializedActionValueItem.FirstName);
  402. Assert.Equal("routeLastName", deserializedActionValueItem.LastName);
  403. }
  404. [Fact]
  405. public void BindValuesAsync_RouteData_Values_To_Complex_Types_Using_FromUriAttribute()
  406. {
  407. // Arrange
  408. CancellationToken cancellationToken = new CancellationToken();
  409. HttpRouteData route = new HttpRouteData(new HttpRoute());
  410. route.Values.Add("id", 6);
  411. route.Values.Add("firstName", "routeFirstName");
  412. route.Values.Add("lastName", "routeLastName");
  413. HttpActionContext controllerContext = ContextUtil.CreateActionContext(
  414. ContextUtil.CreateControllerContext(route, new HttpRequestMessage()
  415. {
  416. Method = HttpMethod.Get,
  417. RequestUri = new Uri("http://localhost")
  418. }),
  419. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetItemFromUri") });
  420. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  421. // Act
  422. provider.BindValuesAsync(controllerContext, cancellationToken).Wait();
  423. // Assert
  424. Assert.Equal(1, controllerContext.ActionArguments.Count);
  425. ActionValueItem deserializedActionValueItem = Assert.IsType<ActionValueItem>(controllerContext.ActionArguments.First().Value);
  426. Assert.Equal(6, deserializedActionValueItem.Id);
  427. Assert.Equal("routeFirstName", deserializedActionValueItem.FirstName);
  428. Assert.Equal("routeLastName", deserializedActionValueItem.LastName);
  429. }
  430. #endregion RouteData
  431. #region ControllerContext
  432. [Fact]
  433. public void BindValuesAsync_ControllerContext_CancellationToken()
  434. {
  435. // Arrange
  436. CancellationToken cancellationToken = new CancellationToken();
  437. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  438. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  439. {
  440. Method = HttpMethod.Get
  441. }),
  442. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("GetFromCancellationToken") });
  443. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  444. // Act
  445. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  446. // Assert
  447. Assert.Equal(1, actionContext.ActionArguments.Count);
  448. Assert.Equal(cancellationToken, actionContext.ActionArguments.First().Value);
  449. }
  450. #endregion ControllerContext
  451. #region Body
  452. [Fact]
  453. public void BindValuesAsync_Body_To_Complex_Type_Json()
  454. {
  455. // Arrange
  456. CancellationToken cancellationToken = new CancellationToken();
  457. string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
  458. StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
  459. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  460. HttpActionContext context = ContextUtil.CreateActionContext(
  461. ContextUtil.CreateControllerContext(request),
  462. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  463. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  464. // Act
  465. provider.BindValuesAsync(context, cancellationToken).Wait();
  466. // Assert
  467. Assert.Equal(1, context.ActionArguments.Count);
  468. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
  469. Assert.Equal(7, deserializedActionValueItem.Id);
  470. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  471. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  472. }
  473. [Fact]
  474. public void BindValuesAsync_Body_To_Complex_Type_Json_With_Validation_Error()
  475. {
  476. // Arrange
  477. CancellationToken cancellationToken = new CancellationToken();
  478. string jsonString = "{\"Id\":\"100\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
  479. StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
  480. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  481. HttpActionContext context = ContextUtil.CreateActionContext(
  482. ContextUtil.CreateControllerContext(request),
  483. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  484. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  485. // Act
  486. provider.BindValuesAsync(context, cancellationToken).Wait();
  487. // Assert
  488. Assert.False(context.ModelState.IsValid);
  489. }
  490. [Fact]
  491. public void BindValuesAsync_Body_To_Complex_Type_FormUrlEncoded()
  492. {
  493. // Arrange
  494. CancellationToken cancellationToken = new CancellationToken();
  495. string formUrlEncodedString = "Id=7&FirstName=testFirstName&LastName=testLastName";
  496. StringContent stringContent = new StringContent(formUrlEncodedString, Encoding.UTF8, "application/x-www-form-urlencoded");
  497. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  498. HttpActionContext context = ContextUtil.CreateActionContext(
  499. ContextUtil.CreateControllerContext(request),
  500. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  501. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  502. // Act
  503. provider.BindValuesAsync(context, cancellationToken).Wait();
  504. // Assert
  505. Assert.Equal(1, context.ActionArguments.Count);
  506. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
  507. Assert.Equal(7, deserializedActionValueItem.Id);
  508. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  509. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  510. }
  511. [Fact]
  512. public void BindValuesAsync_Body_To_Complex_Type_FormUrlEncoded_With_Validation_Error()
  513. {
  514. // Arrange
  515. CancellationToken cancellationToken = new CancellationToken();
  516. string formUrlEncodedString = "Id=101&FirstName=testFirstName&LastName=testLastName";
  517. StringContent stringContent = new StringContent(formUrlEncodedString, Encoding.UTF8, "application/x-www-form-urlencoded");
  518. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  519. HttpActionContext context = ContextUtil.CreateActionContext(
  520. ContextUtil.CreateControllerContext(request),
  521. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  522. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  523. // Act
  524. provider.BindValuesAsync(context, cancellationToken).Wait();
  525. // Assert
  526. Assert.False(context.ModelState.IsValid);
  527. }
  528. [Fact]
  529. public void BindValuesAsync_Body_To_Complex_Type_Xml()
  530. {
  531. // Arrange
  532. CancellationToken cancellationToken = new CancellationToken();
  533. MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
  534. ActionValueItem item = new ActionValueItem() { Id = 7, FirstName = "testFirstName", LastName = "testLastName" };
  535. ObjectContent<ActionValueItem> tempContent = new ObjectContent<ActionValueItem>(item, new XmlMediaTypeFormatter());
  536. StringContent stringContent = new StringContent(tempContent.ReadAsStringAsync().Result);
  537. stringContent.Headers.ContentType = mediaType;
  538. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  539. HttpActionContext context = ContextUtil.CreateActionContext(
  540. ContextUtil.CreateControllerContext(request),
  541. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  542. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  543. // Act
  544. provider.BindValuesAsync(context, cancellationToken).Wait();
  545. // Assert
  546. Assert.Equal(1, context.ActionArguments.Count);
  547. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
  548. Assert.Equal(item.Id, deserializedActionValueItem.Id);
  549. Assert.Equal(item.FirstName, deserializedActionValueItem.FirstName);
  550. Assert.Equal(item.LastName, deserializedActionValueItem.LastName);
  551. }
  552. [Fact]
  553. public void BindValuesAsync_Body_To_Complex_Type_Xml_Structural()
  554. {
  555. // Arrange
  556. CancellationToken cancellationToken = new CancellationToken();
  557. MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
  558. // Test sending from a non .NET type (raw xml).
  559. // The default XML serializer requires that the xml root name matches the C# class name.
  560. string xmlSource =
  561. @"<ActionValueItem xmlns='http://schemas.datacontract.org/2004/07/System.Web.Http.ModelBinding' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'>
  562. <FirstName>testFirstName</FirstName>
  563. <Id>7</Id>
  564. <LastName>testLastName</LastName>
  565. </ActionValueItem>".Replace('\'', '"');
  566. StringContent stringContent = new StringContent(xmlSource);
  567. stringContent.Headers.ContentType = mediaType;
  568. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  569. HttpActionContext context = ContextUtil.CreateActionContext(
  570. ContextUtil.CreateControllerContext(request),
  571. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  572. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  573. // Act
  574. provider.BindValuesAsync(context, cancellationToken).Wait();
  575. // Assert
  576. Assert.Equal(1, context.ActionArguments.Count);
  577. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
  578. Assert.Equal(7, deserializedActionValueItem.Id);
  579. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  580. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  581. }
  582. [Fact]
  583. public void BindValuesAsync_Body_To_Complex_Type_Xml_With_Validation_Error()
  584. {
  585. // Arrange
  586. CancellationToken cancellationToken = new CancellationToken();
  587. MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/xml");
  588. ActionValueItem item = new ActionValueItem() { Id = 101, FirstName = "testFirstName", LastName = "testLastName" };
  589. var tempContent = new ObjectContent<ActionValueItem>(item, new XmlMediaTypeFormatter());
  590. StringContent stringContent = new StringContent(tempContent.ReadAsStringAsync().Result);
  591. stringContent.Headers.ContentType = mediaType;
  592. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  593. HttpActionContext context = ContextUtil.CreateActionContext(
  594. ContextUtil.CreateControllerContext(request),
  595. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  596. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  597. // Act
  598. provider.BindValuesAsync(context, cancellationToken).Wait();
  599. // Assert
  600. Assert.False(context.ModelState.IsValid);
  601. }
  602. [Fact]
  603. public void BindValuesAsync_Body_To_Complex_And_Uri_To_Simple()
  604. {
  605. // Arrange
  606. string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
  607. StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
  608. HttpRequestMessage request = new HttpRequestMessage()
  609. {
  610. RequestUri = new Uri("http://localhost/ActionValueController/PostFromBody?id=123"),
  611. Content = stringContent
  612. };
  613. HttpActionContext context = ContextUtil.CreateActionContext(
  614. ContextUtil.CreateControllerContext(request),
  615. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostFromBodyAndUri") });
  616. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  617. // Act
  618. provider.BindValuesAsync(context, CancellationToken.None).Wait();
  619. // Assert
  620. Assert.Equal(2, context.ActionArguments.Count);
  621. Assert.Equal(123, context.ActionArguments["id"]);
  622. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments["item"]);
  623. Assert.Equal(7, deserializedActionValueItem.Id);
  624. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  625. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  626. }
  627. [Fact]
  628. public void BindValuesAsync_Body_To_Complex_Type_Using_FromBodyAttribute()
  629. {
  630. // Arrange
  631. CancellationToken cancellationToken = new CancellationToken();
  632. string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
  633. StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
  634. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  635. HttpActionContext context = ContextUtil.CreateActionContext(
  636. ContextUtil.CreateControllerContext(request),
  637. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostFromBody") });
  638. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  639. // Act
  640. provider.BindValuesAsync(context, cancellationToken).Wait();
  641. // Assert
  642. Assert.Equal(1, context.ActionArguments.Count);
  643. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
  644. Assert.Equal(7, deserializedActionValueItem.Id);
  645. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  646. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  647. }
  648. [Fact]
  649. public void BindValuesAsync_Body_To_Complex_Type_Using_Formatter_To_Deserialize()
  650. {
  651. // Arrange
  652. CancellationToken cancellationToken = new CancellationToken();
  653. string jsonString = "{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}";
  654. StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
  655. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  656. HttpActionContext context = ContextUtil.CreateActionContext(
  657. ContextUtil.CreateControllerContext(request),
  658. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostComplexType") });
  659. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  660. // Act
  661. provider.BindValuesAsync(context, cancellationToken).Wait();
  662. // Assert
  663. Assert.Equal(1, context.ActionArguments.Count);
  664. ActionValueItem deserializedActionValueItem = Assert.IsAssignableFrom<ActionValueItem>(context.ActionArguments.First().Value);
  665. Assert.Equal(7, deserializedActionValueItem.Id);
  666. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  667. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  668. }
  669. [Fact]
  670. public void BindValuesAsync_Body_To_IEnumerable_Complex_Type_Json()
  671. {
  672. // ModelBinding will bind T to IEnumerable<T>, but JSON.Net won't. So enclose JSON in [].
  673. // Arrange
  674. CancellationToken cancellationToken = new CancellationToken();
  675. string jsonString = "[{\"Id\":\"7\",\"FirstName\":\"testFirstName\",\"LastName\":\"testLastName\"}]";
  676. StringContent stringContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
  677. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  678. HttpActionContext context = ContextUtil.CreateActionContext(
  679. ContextUtil.CreateControllerContext(request),
  680. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostEnumerable") });
  681. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  682. // Act
  683. provider.BindValuesAsync(context, cancellationToken).Wait();
  684. // Assert
  685. Assert.Equal(1, context.ActionArguments.Count);
  686. IEnumerable<ActionValueItem> items = Assert.IsAssignableFrom<IEnumerable<ActionValueItem>>(context.ActionArguments.First().Value);
  687. ActionValueItem deserializedActionValueItem = items.First();
  688. Assert.Equal(7, deserializedActionValueItem.Id);
  689. Assert.Equal("testFirstName", deserializedActionValueItem.FirstName);
  690. Assert.Equal("testLastName", deserializedActionValueItem.LastName);
  691. }
  692. [Fact]
  693. public void BindValuesAsync_Body_To_JToken()
  694. {
  695. // Arrange
  696. CancellationToken cancellationToken = new CancellationToken();
  697. MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/json");
  698. ActionValueItem item = new ActionValueItem() { Id = 7, FirstName = "testFirstName", LastName = "testLastName" };
  699. string json = "{\"a\":123,\"b\":[false,null,12.34]}";
  700. JToken jt = JToken.Parse(json);
  701. var tempContent = new ObjectContent<JToken>(jt, new JsonMediaTypeFormatter());
  702. StringContent stringContent = new StringContent(tempContent.ReadAsStringAsync().Result);
  703. stringContent.Headers.ContentType = mediaType;
  704. HttpRequestMessage request = new HttpRequestMessage() { Content = stringContent };
  705. HttpActionContext context = ContextUtil.CreateActionContext(
  706. ContextUtil.CreateControllerContext(request),
  707. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("PostJsonValue") });
  708. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  709. // Act
  710. provider.BindValuesAsync(context, cancellationToken).Wait();
  711. // Assert
  712. Assert.Equal(1, context.ActionArguments.Count);
  713. JToken deserializedJsonValue = Assert.IsAssignableFrom<JToken>(context.ActionArguments.First().Value);
  714. string deserializedJsonAsString = deserializedJsonValue.ToString(Formatting.None);
  715. Assert.Equal(json, deserializedJsonAsString);
  716. }
  717. #endregion Body
  718. [Fact]
  719. public void BindValuesAsync_FromUriAttribute_DecoratedOn_Type()
  720. {
  721. // Arrange
  722. CancellationToken cancellationToken = new CancellationToken();
  723. HttpActionContext actionContext = ContextUtil.CreateActionContext(
  724. ContextUtil.CreateControllerContext(new HttpRequestMessage()
  725. {
  726. Method = new HttpMethod("Patch"),
  727. RequestUri = new Uri("http://localhost?x=123&y=456&data.description=mypoint")
  728. }),
  729. new ReflectedHttpActionDescriptor() { MethodInfo = typeof(ActionValueController).GetMethod("Patch") });
  730. DefaultActionValueBinder provider = new DefaultActionValueBinder();
  731. // Act
  732. provider.BindValuesAsync(actionContext, cancellationToken).Wait();
  733. // Assert
  734. Dictionary<string, object> expectedResult = new Dictionary<string, object>();
  735. expectedResult["point"] = new Point { X = 123, Y = 456, Data = new Data { Description = "mypoint" } };
  736. Assert.Equal(expectedResult, actionContext.ActionArguments, new DictionaryEqualityComparer());
  737. }
  738. }
  739. [FromUri]
  740. public class Point
  741. {
  742. public int X { get; set; }
  743. public int Y { get; set; }
  744. public Data Data { get; set; }
  745. public override bool Equals(object obj)
  746. {
  747. Point other = obj as Point;
  748. if (other != null)
  749. {
  750. return other.X == X && other.Y == Y && other.Data.Description == other.Data.Description;
  751. }
  752. return false;
  753. }
  754. public override int GetHashCode()
  755. {
  756. return base.GetHashCode();
  757. }
  758. }
  759. public class Data
  760. {
  761. public string Description { get; set; }
  762. }
  763. public class ActionValueController : ApiController
  764. {
  765. // Demonstrates complex parameter that has FromUri declared on the type
  766. public void Patch(Point point) { }
  767. // Demonstrates parameter that can come from route, query string, or defaults
  768. public ActionValueItem Get(int id = 0, string firstName = "DefaultFirstName", string lastName = "DefaultLastName")
  769. {
  770. return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
  771. }
  772. // Demonstrates an explicit override to obtain parameters from URL
  773. public ActionValueItem GetFromUri([FromUri] int id = 0,
  774. [FromUri] string firstName = "DefaultFirstName",
  775. [FromUri] string lastName = "DefaultLastName")
  776. {
  777. return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
  778. }
  779. // Complex objects default to body. But we can bind from URI with an attribute.
  780. public ActionValueItem GetItem([FromUri] ActionValueItem item)
  781. {
  782. return item;
  783. }
  784. // Demonstrates ModelBinding a Item object explicitly from Uri
  785. public ActionValueItem GetItemFromUri([FromUri] ActionValueItem item)
  786. {
  787. return item;
  788. }
  789. // Demonstrates use of renaming parameters via name
  790. public ActionValueItem GetFromNamed([FromUri(Name = "custID")] int id,
  791. [FromUri(Name = "first")] string firstName,
  792. [FromUri(Name = "last")] string lastName)
  793. {
  794. return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
  795. }
  796. public void GetTestEmptyString([FromUri] ConvertEmptyStringContainer x)
  797. {
  798. }
  799. // Demonstrates use of custom ValueProvider via attribute
  800. public ActionValueItem GetFromCustom([ValueProvider(typeof(ActionValueControllerValueProviderFactory), Name = "id")] int id,
  801. [ValueProvider(typeof(ActionValueControllerValueProviderFactory), Name = "customFirstName")] string firstName,
  802. [ValueProvider(typeof(ActionValueControllerValueProviderFactory), Name = "customLastName")] string lastName)
  803. {
  804. return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
  805. }
  806. // Demonstrates ModelBinding to the CancellationToken of the current request
  807. public string GetFromCancellationToken(CancellationToken cancellationToken)
  808. {
  809. return cancellationToken.ToString();
  810. }
  811. // Demonstrates ModelBinding to the ModelState of the current request
  812. public string GetFromModelState(ModelState modelState)
  813. {
  814. return modelState.ToString();
  815. }
  816. // Demonstrates binding to complex type from body
  817. public ActionValueItem PostComplexType(ActionValueItem item)
  818. {
  819. return item;
  820. }
  821. // Demonstrates binding to complex type from uri
  822. public ActionValueItem PostComplexTypeUri([FromUri] ActionValueItem item)
  823. {
  824. return item;
  825. }
  826. // Demonstrates binding to IEnumerable of complex type from body or Uri
  827. public ActionValueItem PostEnumerable(IEnumerable<ActionValueItem> items)
  828. {
  829. return items.FirstOrDefault();
  830. }
  831. // Demonstrates binding to IEnumerable of complex type from body or Uri
  832. public ActionValueItem PostEnumerableUri([FromUri] IEnumerable<ActionValueItem> items)
  833. {
  834. return items.FirstOrDefault();
  835. }
  836. // Demonstrates binding to JsonValue from body
  837. public JToken PostJsonValue(JToken jsonValue)
  838. {
  839. return jsonValue;
  840. }
  841. // Demonstrate what we expect to be the common default scenario. No attributes are required.
  842. // A complex object comes from the body, and simple objects come from the URI.
  843. public ActionValueItem PostFromBodyAndUri(int id, ActionValueItem item)
  844. {
  845. return item;
  846. }
  847. // Demonstrates binding to complex type explicitly marked as coming from body
  848. public ActionValueItem PostFromBody([FromBody] ActionValueItem item)
  849. {
  850. return item;
  851. }
  852. // Demonstrates how body can be shredded to name/value pairs to bind to simple types
  853. public ActionValueItem PostToSimpleTypes(int id, string firstName, string lastName)
  854. {
  855. return new ActionValueItem() { Id = id, FirstName = firstName, LastName = lastName };
  856. }
  857. // Demonstrates binding to ObjectContent<T> from request body
  858. public ActionValueItem PostObjectContentOfItem(ObjectContent<ActionValueItem> item)
  859. {
  860. return item.ReadAsAsync<ActionValueItem>().Result;
  861. }
  862. public class ActionValueControllerValueProviderFactory : ValueProviderFactory
  863. {
  864. public override IValueProvider GetValueProvider(HttpActionContext actionContext)
  865. {
  866. return new ActionValueControllerValueProvider();
  867. }
  868. }
  869. public class ActionValueControllerValueProvider : IValueProvider
  870. {
  871. public bool ContainsPrefix(string prefix)