PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/test/Microsoft.Web.Mvc.Test/ModelBinding/Test/MutableObjectModelBinderTest.cs

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