PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/test/System.Web.Http.Test/ModelBinding/Binders/MutableObjectModelBinderTest.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 748 lines | 534 code | 139 blank | 75 comment | 13 complexity | edd33c242432006ea3418448bca5dd00 MD5 | raw file
  1. using System.Collections.Generic;
  2. using System.ComponentModel;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Web.Http.Controllers;
  6. using System.Web.Http.Metadata;
  7. using System.Web.Http.Metadata.Providers;
  8. using System.Web.Http.Validation;
  9. using Moq;
  10. using Xunit;
  11. using Assert = Microsoft.TestCommon.AssertEx;
  12. namespace System.Web.Http.ModelBinding.Binders
  13. {
  14. public class MutableObjectModelBinderTest
  15. {
  16. [Fact]
  17. public void BindModel()
  18. {
  19. // Arrange
  20. Mock<IModelBinder> mockDtoBinder = new Mock<IModelBinder>();
  21. ModelBindingContext bindingContext = new ModelBindingContext
  22. {
  23. ModelMetadata = GetMetadataForObject(new Person()),
  24. ModelName = "someName"
  25. };
  26. HttpActionContext context = ContextUtil.CreateActionContext();
  27. context.ControllerContext.Configuration.ServiceResolver.SetService(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(ComplexModelDto), mockDtoBinder.Object) { SuppressPrefixCheck = true });
  28. mockDtoBinder
  29. .Setup(o => o.BindModel(context, It.IsAny<ModelBindingContext>()))
  30. .Returns((HttpActionContext cc, ModelBindingContext mbc2) =>
  31. {
  32. return true; // just return the DTO unchanged
  33. });
  34. Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
  35. mockTestableBinder.Setup(o => o.EnsureModelPublic(context, bindingContext)).Verifiable();
  36. mockTestableBinder.Setup(o => o.GetMetadataForPropertiesPublic(context, bindingContext)).Returns(new ModelMetadata[0]).Verifiable();
  37. TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
  38. testableBinder.MetadataProvider = new CachedDataAnnotationsModelMetadataProvider();
  39. // Act
  40. bool retValue = testableBinder.BindModel(context, bindingContext);
  41. // Assert
  42. Assert.True(retValue);
  43. Assert.IsType<Person>(bindingContext.Model);
  44. Assert.True(bindingContext.ValidationNode.ValidateAllProperties);
  45. mockTestableBinder.Verify();
  46. }
  47. [Fact]
  48. public void CanUpdateProperty_HasPublicSetter_ReturnsTrue()
  49. {
  50. // Arrange
  51. ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadWriteString");
  52. // Act
  53. bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
  54. // Assert
  55. Assert.True(canUpdate);
  56. }
  57. [Fact]
  58. public void CanUpdateProperty_ReadOnlyArray_ReturnsFalse()
  59. {
  60. // Arrange
  61. ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyArray");
  62. // Act
  63. bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
  64. // Assert
  65. Assert.False(canUpdate);
  66. }
  67. [Fact]
  68. public void CanUpdateProperty_ReadOnlyReferenceTypeNotBlacklisted_ReturnsTrue()
  69. {
  70. // Arrange
  71. ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyObject");
  72. // Act
  73. bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
  74. // Assert
  75. Assert.True(canUpdate);
  76. }
  77. [Fact]
  78. public void CanUpdateProperty_ReadOnlyString_ReturnsFalse()
  79. {
  80. // Arrange
  81. ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyString");
  82. // Act
  83. bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
  84. // Assert
  85. Assert.False(canUpdate);
  86. }
  87. [Fact]
  88. public void CanUpdateProperty_ReadOnlyValueType_ReturnsFalse()
  89. {
  90. // Arrange
  91. ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyInt");
  92. // Act
  93. bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata);
  94. // Assert
  95. Assert.False(canUpdate);
  96. }
  97. [Fact]
  98. public void CreateModel()
  99. {
  100. // Arrange
  101. ModelBindingContext bindingContext = new ModelBindingContext
  102. {
  103. ModelMetadata = GetMetadataForType(typeof(Person))
  104. };
  105. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  106. // Act
  107. object retModel = testableBinder.CreateModelPublic(null, bindingContext);
  108. // Assert
  109. Assert.IsType<Person>(retModel);
  110. }
  111. [Fact]
  112. public void EnsureModel_ModelIsNotNull_DoesNothing()
  113. {
  114. // Arrange
  115. HttpActionContext actionContext = ContextUtil.CreateActionContext();
  116. ModelBindingContext bindingContext = new ModelBindingContext
  117. {
  118. ModelMetadata = GetMetadataForObject(new Person())
  119. };
  120. Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
  121. TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
  122. // Act
  123. object originalModel = bindingContext.Model;
  124. testableBinder.EnsureModelPublic(null, bindingContext);
  125. object newModel = bindingContext.Model;
  126. // Assert
  127. Assert.Same(originalModel, newModel);
  128. mockTestableBinder.Verify(o => o.CreateModelPublic(actionContext, bindingContext), Times.Never());
  129. }
  130. [Fact]
  131. public void EnsureModel_ModelIsNull_CallsCreateModel()
  132. {
  133. // Arrange
  134. ModelBindingContext bindingContext = new ModelBindingContext
  135. {
  136. ModelMetadata = GetMetadataForType(typeof(Person))
  137. };
  138. Mock<TestableMutableObjectModelBinder> mockTestableBinder = new Mock<TestableMutableObjectModelBinder> { CallBase = true };
  139. mockTestableBinder.Setup(o => o.CreateModelPublic(null, bindingContext)).Returns(new Person()).Verifiable();
  140. TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object;
  141. // Act
  142. object originalModel = bindingContext.Model;
  143. testableBinder.EnsureModelPublic(null, bindingContext);
  144. object newModel = bindingContext.Model;
  145. // Assert
  146. Assert.Null(originalModel);
  147. Assert.IsType<Person>(newModel);
  148. mockTestableBinder.Verify();
  149. }
  150. [Fact]
  151. public void GetMetadataForProperties_WithBindAttribute()
  152. {
  153. // Arrange
  154. string[] expectedPropertyNames = new[] { "FirstName", "LastName" };
  155. HttpActionContext actionContext = ContextUtil.CreateActionContext();
  156. ModelBindingContext bindingContext = new ModelBindingContext
  157. {
  158. ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion))
  159. };
  160. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  161. // Act
  162. IEnumerable<ModelMetadata> propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(actionContext, bindingContext);
  163. string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
  164. // Assert
  165. Assert.Equal(expectedPropertyNames, returnedPropertyNames);
  166. }
  167. [Fact]
  168. public void GetMetadataForProperties_WithoutBindAttribute()
  169. {
  170. // Arrange
  171. string[] expectedPropertyNames = new[] { "DateOfBirth", "DateOfDeath", "ValueTypeRequired", "FirstName", "LastName", "PropertyWithDefaultValue" };
  172. HttpActionContext actionContext = ContextUtil.CreateActionContext();
  173. ModelBindingContext bindingContext = new ModelBindingContext
  174. {
  175. ModelMetadata = GetMetadataForType(typeof(Person))
  176. };
  177. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  178. // Act
  179. IEnumerable<ModelMetadata> propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(actionContext, bindingContext);
  180. string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray();
  181. // Assert
  182. Assert.Equal(expectedPropertyNames, returnedPropertyNames);
  183. }
  184. [Fact]
  185. public void GetRequiredPropertiesCollection_MixedAttributes()
  186. {
  187. // Arrange
  188. HttpActionContext actionContext = ContextUtil.CreateActionContext();
  189. ModelBindingContext bindingContext = new ModelBindingContext
  190. {
  191. ModelMetadata = GetMetadataForObject(new ModelWithMixedBindingBehaviors())
  192. };
  193. // Act
  194. HashSet<string> requiredProperties;
  195. Dictionary<string, ModelValidator> requiredValidators;
  196. HashSet<string> skipProperties;
  197. MutableObjectModelBinder.GetRequiredPropertiesCollection(actionContext, bindingContext, out requiredProperties, out requiredValidators, out skipProperties);
  198. // Assert
  199. Assert.Equal(new[] { "Required" }, requiredProperties.ToArray());
  200. Assert.Equal(new[] { "Never" }, skipProperties.ToArray());
  201. }
  202. [Fact]
  203. public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing()
  204. {
  205. // Arrange
  206. HttpActionContext context = ContextUtil.CreateActionContext();
  207. context.ModelState.AddModelError("foo.bar", "Some existing error.");
  208. ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
  209. ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
  210. ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);
  211. // Act
  212. EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
  213. handler(validationNode, e);
  214. // Assert
  215. Assert.False(context.ModelState.ContainsKey("foo"));
  216. }
  217. [Fact]
  218. public void NullCheckFailedHandler_ModelStateValid_AddsErrorString()
  219. {
  220. // Arrange
  221. HttpActionContext context = ContextUtil.CreateActionContext();
  222. ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
  223. ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
  224. ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);
  225. // Act
  226. EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
  227. handler(validationNode, e);
  228. // Assert
  229. Assert.True(context.ModelState.ContainsKey("foo"));
  230. Assert.Equal("A value is required.", context.ModelState["foo"].Errors[0].ErrorMessage);
  231. }
  232. [Fact]
  233. public void NullCheckFailedHandler_ModelStateValid_CallbackReturnsNull_DoesNothing()
  234. {
  235. // Arrange
  236. HttpActionContext context = ContextUtil.CreateActionContext();
  237. ModelMetadata modelMetadata = GetMetadataForType(typeof(Person));
  238. ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo");
  239. ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */);
  240. // Act
  241. ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.ValueRequiredErrorMessageProvider;
  242. try
  243. {
  244. ModelBinderConfig.ValueRequiredErrorMessageProvider = (ec, mm, value) => null;
  245. EventHandler<ModelValidatedEventArgs> handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */);
  246. handler(validationNode, e);
  247. }
  248. finally
  249. {
  250. ModelBinderConfig.ValueRequiredErrorMessageProvider = originalProvider;
  251. }
  252. // Assert
  253. Assert.True(context.ModelState.IsValid);
  254. }
  255. [Fact]
  256. public void ProcessDto_BindRequiredFieldMissing_RaisesModelError()
  257. {
  258. // Arrange
  259. ModelWithBindRequired model = new ModelWithBindRequired
  260. {
  261. Name = "original value",
  262. Age = -20
  263. };
  264. ModelMetadata containerMetadata = GetMetadataForObject(model);
  265. HttpActionContext context = ContextUtil.CreateActionContext();
  266. ModelBindingContext bindingContext = new ModelBindingContext
  267. {
  268. ModelMetadata = containerMetadata,
  269. ModelName = "theModel"
  270. };
  271. ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
  272. ModelMetadata nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name");
  273. dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, ""));
  274. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  275. // Act & assert
  276. testableBinder.ProcessDto(context, bindingContext, dto);
  277. Assert.False(bindingContext.ModelState.IsValid);
  278. }
  279. [Fact]
  280. public void ProcessDto_Success()
  281. {
  282. // Arrange
  283. DateTime dob = new DateTime(2001, 1, 1);
  284. PersonWithBindExclusion model = new PersonWithBindExclusion
  285. {
  286. DateOfBirth = dob
  287. };
  288. ModelMetadata containerMetadata = GetMetadataForObject(model);
  289. HttpActionContext context = ContextUtil.CreateActionContext();
  290. ModelBindingContext bindingContext = new ModelBindingContext
  291. {
  292. ModelMetadata = containerMetadata
  293. };
  294. ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties);
  295. ModelMetadata firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName");
  296. dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, ""));
  297. ModelMetadata lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName");
  298. dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, ""));
  299. ModelMetadata dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth");
  300. dto.Results[dobProperty] = null;
  301. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  302. // Act
  303. testableBinder.ProcessDto(context, bindingContext, dto);
  304. // Assert
  305. Assert.Equal("John", model.FirstName);
  306. Assert.Equal("Doe", model.LastName);
  307. Assert.Equal(dob, model.DateOfBirth);
  308. Assert.True(bindingContext.ModelState.IsValid);
  309. }
  310. [Fact]
  311. public void SetProperty_PropertyHasDefaultValue_SetsDefaultValue()
  312. {
  313. // Arrange
  314. HttpActionContext context = ContextUtil.CreateActionContext();
  315. ModelBindingContext bindingContext = new ModelBindingContext
  316. {
  317. ModelMetadata = GetMetadataForObject(new Person())
  318. };
  319. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "PropertyWithDefaultValue");
  320. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
  321. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
  322. ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
  323. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  324. // Act
  325. testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  326. // Assert
  327. var person = Assert.IsType<Person>(bindingContext.Model);
  328. Assert.Equal(123.456m, person.PropertyWithDefaultValue);
  329. Assert.True(context.ModelState.IsValid);
  330. }
  331. [Fact]
  332. public void SetProperty_PropertyIsReadOnly_DoesNothing()
  333. {
  334. // Arrange
  335. ModelBindingContext bindingContext = new ModelBindingContext
  336. {
  337. ModelMetadata = GetMetadataForType(typeof(Person))
  338. };
  339. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty");
  340. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
  341. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
  342. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  343. // Act
  344. testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
  345. // Assert
  346. // If didn't throw, success!
  347. }
  348. [Fact]
  349. public void SetProperty_PropertyIsSettable_CallsSetter()
  350. {
  351. // Arrange
  352. Person model = new Person();
  353. HttpActionContext context = ContextUtil.CreateActionContext();
  354. ModelBindingContext bindingContext = new ModelBindingContext
  355. {
  356. ModelMetadata = GetMetadataForObject(model)
  357. };
  358. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
  359. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
  360. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode);
  361. ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
  362. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  363. // Act
  364. testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  365. // Assert
  366. validationNode.Validate(context);
  367. Assert.True(context.ModelState.IsValid);
  368. Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth);
  369. }
  370. [Fact]
  371. public void SetProperty_PropertyIsSettable_SetterThrows_RecordsError()
  372. {
  373. // Arrange
  374. Person model = new Person
  375. {
  376. DateOfBirth = new DateTime(1900, 1, 1)
  377. };
  378. ModelBindingContext bindingContext = new ModelBindingContext
  379. {
  380. ModelMetadata = GetMetadataForObject(model)
  381. };
  382. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath");
  383. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
  384. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode);
  385. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  386. // Act
  387. testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult, requiredValidator: null);
  388. // Assert
  389. Assert.Equal(@"Date of death can't be before date of birth.
  390. Parameter name: value", bindingContext.ModelState["foo"].Errors[0].Exception.Message);
  391. }
  392. [Fact]
  393. public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorNotPresent_RegistersValidationCallback()
  394. {
  395. // Arrange
  396. HttpActionContext context = ContextUtil.CreateActionContext();
  397. ModelBindingContext bindingContext = new ModelBindingContext
  398. {
  399. ModelMetadata = GetMetadataForObject(new Person()),
  400. };
  401. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth");
  402. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo");
  403. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
  404. ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
  405. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  406. // Act
  407. testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  408. // Assert
  409. Assert.True(context.ModelState.IsValid);
  410. validationNode.Validate(context, bindingContext.ValidationNode);
  411. Assert.False(context.ModelState.IsValid);
  412. }
  413. [Fact]
  414. public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorPresent_AddsModelError()
  415. {
  416. // Arrange
  417. HttpActionContext context = ContextUtil.CreateActionContext();
  418. ModelBindingContext bindingContext = new ModelBindingContext
  419. {
  420. ModelMetadata = GetMetadataForObject(new Person()),
  421. ModelName = "foo"
  422. };
  423. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired");
  424. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired");
  425. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
  426. ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
  427. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  428. // Act
  429. testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  430. // Assert
  431. Assert.False(bindingContext.ModelState.IsValid);
  432. Assert.Equal("Sample message", bindingContext.ModelState["foo.ValueTypeRequired"].Errors[0].ErrorMessage);
  433. }
  434. [Fact]
  435. public void SetProperty_SettingNullableTypeToNull_RequiredValidatorNotPresent_PropertySetterThrows_AddsRequiredMessageString()
  436. {
  437. // Arrange
  438. HttpActionContext context = ContextUtil.CreateActionContext();
  439. ModelBindingContext bindingContext = new ModelBindingContext
  440. {
  441. ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
  442. ModelName = "foo"
  443. };
  444. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute");
  445. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute");
  446. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
  447. ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
  448. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  449. // Act
  450. testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  451. // Assert
  452. Assert.False(bindingContext.ModelState.IsValid);
  453. Assert.Equal(1, bindingContext.ModelState["foo.NameNoAttribute"].Errors.Count);
  454. Assert.Equal(@"This is a different exception.
  455. Parameter name: value", bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message);
  456. }
  457. [Fact]
  458. public void SetProperty_SettingNullableTypeToNull_RequiredValidatorPresent_PropertySetterThrows_AddsRequiredMessageString()
  459. {
  460. // Arrange
  461. HttpActionContext context = ContextUtil.CreateActionContext();
  462. ModelBindingContext bindingContext = new ModelBindingContext
  463. {
  464. ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()),
  465. ModelName = "foo"
  466. };
  467. ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name");
  468. ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.Name");
  469. ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode);
  470. ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault();
  471. TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder();
  472. // Act
  473. testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  474. // Assert
  475. Assert.False(bindingContext.ModelState.IsValid);
  476. Assert.Equal(1, bindingContext.ModelState["foo.Name"].Errors.Count);
  477. Assert.Equal("This message comes from the [Required] attribute.", bindingContext.ModelState["foo.Name"].Errors[0].ErrorMessage);
  478. }
  479. private static ModelMetadata GetMetadataForCanUpdateProperty(string propertyName)
  480. {
  481. CachedDataAnnotationsModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
  482. return metadataProvider.GetMetadataForProperty(null, typeof(MyModelTestingCanUpdateProperty), propertyName);
  483. }
  484. private static ModelMetadata GetMetadataForObject(object o)
  485. {
  486. CachedDataAnnotationsModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
  487. return metadataProvider.GetMetadataForType(() => o, o.GetType());
  488. }
  489. private static ModelMetadata GetMetadataForType(Type t)
  490. {
  491. CachedDataAnnotationsModelMetadataProvider metadataProvider = new CachedDataAnnotationsModelMetadataProvider();
  492. return metadataProvider.GetMetadataForType(null, t);
  493. }
  494. private class Person
  495. {
  496. private DateTime? _dateOfDeath;
  497. public DateTime DateOfBirth { get; set; }
  498. public DateTime? DateOfDeath
  499. {
  500. get { return _dateOfDeath; }
  501. set
  502. {
  503. if (value < DateOfBirth)
  504. {
  505. throw new ArgumentOutOfRangeException("value", "Date of death can't be before date of birth.");
  506. }
  507. _dateOfDeath = value;
  508. }
  509. }
  510. [Required(ErrorMessage = "Sample message")]
  511. public int ValueTypeRequired { get; set; }
  512. public string FirstName { get; set; }
  513. public string LastName { get; set; }
  514. public string NonUpdateableProperty { get; private set; }
  515. [DefaultValue(typeof(decimal), "123.456")]
  516. public decimal PropertyWithDefaultValue { get; set; }
  517. }
  518. private class PersonWithBindExclusion
  519. {
  520. [HttpBindNever]
  521. public DateTime DateOfBirth { get; set; }
  522. [HttpBindNever]
  523. public DateTime? DateOfDeath { get; set; }
  524. public string FirstName { get; set; }
  525. public string LastName { get; set; }
  526. public string NonUpdateableProperty { get; private set; }
  527. }
  528. private class ModelWithBindRequired
  529. {
  530. public string Name { get; set; }
  531. [HttpBindRequired]
  532. public int Age { get; set; }
  533. }
  534. [HttpBindRequired]
  535. private class ModelWithMixedBindingBehaviors
  536. {
  537. public string Required { get; set; }
  538. [HttpBindNever]
  539. public string Never { get; set; }
  540. [HttpBindingBehavior(HttpBindingBehavior.Optional)]
  541. public string Optional { get; set; }
  542. }
  543. private sealed class MyModelTestingCanUpdateProperty
  544. {
  545. public int ReadOnlyInt { get; private set; }
  546. public string ReadOnlyString { get; private set; }
  547. public string[] ReadOnlyArray { get; private set; }
  548. public object ReadOnlyObject { get; private set; }
  549. public string ReadWriteString { get; set; }
  550. }
  551. private sealed class ModelWhosePropertySetterThrows
  552. {
  553. [Required(ErrorMessage = "This message comes from the [Required] attribute.")]
  554. public string Name
  555. {
  556. get { return null; }
  557. set { throw new ArgumentException("This is an exception.", "value"); }
  558. }
  559. public string NameNoAttribute
  560. {
  561. get { return null; }
  562. set { throw new ArgumentException("This is a different exception.", "value"); }
  563. }
  564. }
  565. public class TestableMutableObjectModelBinder : MutableObjectModelBinder
  566. {
  567. public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata)
  568. {
  569. return base.CanUpdateProperty(propertyMetadata);
  570. }
  571. protected override bool CanUpdateProperty(ModelMetadata propertyMetadata)
  572. {
  573. return CanUpdatePropertyPublic(propertyMetadata);
  574. }
  575. public virtual object CreateModelPublic(HttpActionContext context, ModelBindingContext bindingContext)
  576. {
  577. return base.CreateModel(context, bindingContext);
  578. }
  579. protected override object CreateModel(HttpActionContext context, ModelBindingContext bindingContext)
  580. {
  581. return CreateModelPublic(context, bindingContext);
  582. }
  583. public virtual void EnsureModelPublic(HttpActionContext context, ModelBindingContext bindingContext)
  584. {
  585. base.EnsureModel(context, bindingContext);
  586. }
  587. protected override void EnsureModel(HttpActionContext context, ModelBindingContext bindingContext)
  588. {
  589. EnsureModelPublic(context, bindingContext);
  590. }
  591. public virtual IEnumerable<ModelMetadata> GetMetadataForPropertiesPublic(HttpActionContext context, ModelBindingContext bindingContext)
  592. {
  593. return base.GetMetadataForProperties(context, bindingContext);
  594. }
  595. protected override IEnumerable<ModelMetadata> GetMetadataForProperties(HttpActionContext context, ModelBindingContext bindingContext)
  596. {
  597. return GetMetadataForPropertiesPublic(context, bindingContext);
  598. }
  599. public virtual void SetPropertyPublic(HttpActionContext context, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, ModelValidator requiredValidator)
  600. {
  601. base.SetProperty(context, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  602. }
  603. protected override void SetProperty(HttpActionContext actionContext, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, ModelValidator requiredValidator)
  604. {
  605. SetPropertyPublic(actionContext, bindingContext, propertyMetadata, dtoResult, requiredValidator);
  606. }
  607. }
  608. }
  609. }