// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web.Http.Controllers; using System.Web.Http.Metadata; using System.Web.Http.Metadata.Providers; using System.Web.Http.Validation; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Http.ModelBinding.Binders { public class MutableObjectModelBinderTest { [Fact] public void BindModel() { // Arrange Mock mockDtoBinder = new Mock(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new Person()), ModelName = "someName" }; HttpActionContext context = ContextUtil.CreateActionContext(); context.ControllerContext.Configuration.Services.Replace(typeof(ModelBinderProvider), new SimpleModelBinderProvider(typeof(ComplexModelDto), mockDtoBinder.Object) { SuppressPrefixCheck = true }); mockDtoBinder .Setup(o => o.BindModel(context, It.IsAny())) .Returns((HttpActionContext cc, ModelBindingContext mbc2) => { return true; // just return the DTO unchanged }); Mock mockTestableBinder = new Mock { CallBase = true }; mockTestableBinder.Setup(o => o.EnsureModelPublic(context, bindingContext)).Verifiable(); mockTestableBinder.Setup(o => o.GetMetadataForPropertiesPublic(context, bindingContext)).Returns(new ModelMetadata[0]).Verifiable(); TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object; testableBinder.MetadataProvider = new DataAnnotationsModelMetadataProvider(); // Act bool retValue = testableBinder.BindModel(context, bindingContext); // Assert Assert.True(retValue); Assert.IsType(bindingContext.Model); Assert.True(bindingContext.ValidationNode.ValidateAllProperties); mockTestableBinder.Verify(); } [Fact] public void CanUpdateProperty_HasPublicSetter_ReturnsTrue() { // Arrange ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadWriteString"); // Act bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata); // Assert Assert.True(canUpdate); } [Fact] public void CanUpdateProperty_ReadOnlyArray_ReturnsFalse() { // Arrange ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyArray"); // Act bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata); // Assert Assert.False(canUpdate); } [Fact] public void CanUpdateProperty_ReadOnlyReferenceTypeNotBlacklisted_ReturnsTrue() { // Arrange ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyObject"); // Act bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata); // Assert Assert.True(canUpdate); } [Fact] public void CanUpdateProperty_ReadOnlyString_ReturnsFalse() { // Arrange ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyString"); // Act bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata); // Assert Assert.False(canUpdate); } [Fact] public void CanUpdateProperty_ReadOnlyValueType_ReturnsFalse() { // Arrange ModelMetadata propertyMetadata = GetMetadataForCanUpdateProperty("ReadOnlyInt"); // Act bool canUpdate = MutableObjectModelBinder.CanUpdatePropertyInternal(propertyMetadata); // Assert Assert.False(canUpdate); } [Fact] public void CreateModel() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForType(typeof(Person)) }; TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act object retModel = testableBinder.CreateModelPublic(null, bindingContext); // Assert Assert.IsType(retModel); } [Fact] public void EnsureModel_ModelIsNotNull_DoesNothing() { // Arrange HttpActionContext actionContext = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new Person()) }; Mock mockTestableBinder = new Mock { CallBase = true }; TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object; // Act object originalModel = bindingContext.Model; testableBinder.EnsureModelPublic(null, bindingContext); object newModel = bindingContext.Model; // Assert Assert.Same(originalModel, newModel); mockTestableBinder.Verify(o => o.CreateModelPublic(actionContext, bindingContext), Times.Never()); } [Fact] public void EnsureModel_ModelIsNull_CallsCreateModel() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForType(typeof(Person)) }; Mock mockTestableBinder = new Mock { CallBase = true }; mockTestableBinder.Setup(o => o.CreateModelPublic(null, bindingContext)).Returns(new Person()).Verifiable(); TestableMutableObjectModelBinder testableBinder = mockTestableBinder.Object; // Act object originalModel = bindingContext.Model; testableBinder.EnsureModelPublic(null, bindingContext); object newModel = bindingContext.Model; // Assert Assert.Null(originalModel); Assert.IsType(newModel); mockTestableBinder.Verify(); } [Fact] public void GetMetadataForProperties_WithBindAttribute() { // Arrange string[] expectedPropertyNames = new[] { "FirstName", "LastName" }; HttpActionContext actionContext = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForType(typeof(PersonWithBindExclusion)) }; TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act IEnumerable propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(actionContext, bindingContext); string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray(); // Assert Assert.Equal(expectedPropertyNames, returnedPropertyNames); } [Fact] public void GetMetadataForProperties_WithoutBindAttribute() { // Arrange string[] expectedPropertyNames = new[] { "DateOfBirth", "DateOfDeath", "ValueTypeRequired", "FirstName", "LastName", "PropertyWithDefaultValue" }; HttpActionContext actionContext = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForType(typeof(Person)) }; TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act IEnumerable propertyMetadatas = testableBinder.GetMetadataForPropertiesPublic(actionContext, bindingContext); string[] returnedPropertyNames = propertyMetadatas.Select(o => o.PropertyName).ToArray(); // Assert Assert.Equal(expectedPropertyNames, returnedPropertyNames); } [Fact] public void GetRequiredPropertiesCollection_MixedAttributes() { // Arrange HttpActionContext actionContext = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new ModelWithMixedBindingBehaviors()) }; // Act HashSet requiredProperties; Dictionary requiredValidators; HashSet skipProperties; MutableObjectModelBinder.GetRequiredPropertiesCollection(actionContext, bindingContext, out requiredProperties, out requiredValidators, out skipProperties); // Assert Assert.Equal(new[] { "Required" }, requiredProperties.ToArray()); Assert.Equal(new[] { "Never" }, skipProperties.ToArray()); } [Fact] public void NullCheckFailedHandler_ModelStateAlreadyInvalid_DoesNothing() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); context.ModelState.AddModelError("foo.bar", "Some existing error."); ModelMetadata modelMetadata = GetMetadataForType(typeof(Person)); ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo"); ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */); // Act EventHandler handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */); handler(validationNode, e); // Assert Assert.False(context.ModelState.ContainsKey("foo")); } [Fact] public void NullCheckFailedHandler_ModelStateValid_AddsErrorString() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelMetadata modelMetadata = GetMetadataForType(typeof(Person)); ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo"); ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */); // Act EventHandler handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */); handler(validationNode, e); // Assert Assert.True(context.ModelState.ContainsKey("foo")); Assert.Equal("A value is required.", context.ModelState["foo"].Errors[0].ErrorMessage); } [Fact] public void NullCheckFailedHandler_ModelStateValid_CallbackReturnsNull_DoesNothing() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelMetadata modelMetadata = GetMetadataForType(typeof(Person)); ModelValidationNode validationNode = new ModelValidationNode(modelMetadata, "foo"); ModelValidatedEventArgs e = new ModelValidatedEventArgs(context, null /* parentNode */); // Act ModelBinderErrorMessageProvider originalProvider = ModelBinderConfig.ValueRequiredErrorMessageProvider; try { ModelBinderConfig.ValueRequiredErrorMessageProvider = (ec, mm, value) => null; EventHandler handler = MutableObjectModelBinder.CreateNullCheckFailedHandler(modelMetadata, null /* incomingValue */); handler(validationNode, e); } finally { ModelBinderConfig.ValueRequiredErrorMessageProvider = originalProvider; } // Assert Assert.True(context.ModelState.IsValid); } [Fact] public void ProcessDto_BindRequiredFieldMissing_RaisesModelError() { // Arrange ModelWithBindRequired model = new ModelWithBindRequired { Name = "original value", Age = -20 }; ModelMetadata containerMetadata = GetMetadataForObject(model); HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = containerMetadata, ModelName = "theModel" }; ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); ModelMetadata nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name"); dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, "")); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act & assert testableBinder.ProcessDto(context, bindingContext, dto); Assert.False(bindingContext.ModelState.IsValid); } [Fact] public void ProcessDto_Success() { // Arrange DateTime dob = new DateTime(2001, 1, 1); PersonWithBindExclusion model = new PersonWithBindExclusion { DateOfBirth = dob }; ModelMetadata containerMetadata = GetMetadataForObject(model); HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = containerMetadata }; ComplexModelDto dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); ModelMetadata firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName"); dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, "")); ModelMetadata lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName"); dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, "")); ModelMetadata dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth"); dto.Results[dobProperty] = null; TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.ProcessDto(context, bindingContext, dto); // Assert Assert.Equal("John", model.FirstName); Assert.Equal("Doe", model.LastName); Assert.Equal(dob, model.DateOfBirth); Assert.True(bindingContext.ModelState.IsValid); } [Fact] public void SetProperty_PropertyHasDefaultValue_SetsDefaultValue() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new Person()) }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "PropertyWithDefaultValue"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode); ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault(); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert var person = Assert.IsType(bindingContext.Model); Assert.Equal(123.456m, person.PropertyWithDefaultValue); Assert.True(context.ModelState.IsValid); } [Fact] public void SetProperty_PropertyIsReadOnly_DoesNothing() { // Arrange ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForType(typeof(Person)) }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult, requiredValidator: null); // Assert // If didn't throw, success! } [Fact] public void SetProperty_PropertyIsSettable_CallsSetter() { // Arrange Person model = new Person(); HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(model) }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode); ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault(); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert validationNode.Validate(context); Assert.True(context.ModelState.IsValid); Assert.Equal(new DateTime(2001, 1, 1), model.DateOfBirth); } [Fact] public void SetProperty_PropertyIsSettable_SetterThrows_RecordsError() { // Arrange Person model = new Person { DateOfBirth = new DateTime(1900, 1, 1) }; ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(model) }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(null, bindingContext, propertyMetadata, dtoResult, requiredValidator: null); // Assert Assert.Equal(@"Date of death can't be before date of birth. Parameter name: value", bindingContext.ModelState["foo"].Errors[0].Exception.Message); } [Fact] public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorNotPresent_RegistersValidationCallback() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new Person()), }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode); ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault(); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.True(context.ModelState.IsValid); validationNode.Validate(context, bindingContext.ValidationNode); Assert.False(context.ModelState.IsValid); } [Fact] public void SetProperty_SettingNonNullableValueTypeToNull_RequiredValidatorPresent_AddsModelError() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new Person()), ModelName = "foo" }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode); ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault(); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.False(bindingContext.ModelState.IsValid); Assert.Equal("Sample message", bindingContext.ModelState["foo.ValueTypeRequired"].Errors[0].ErrorMessage); } [Fact] public void SetProperty_SettingNullableTypeToNull_RequiredValidatorNotPresent_PropertySetterThrows_AddsRequiredMessageString() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()), ModelName = "foo" }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode); ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault(); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.False(bindingContext.ModelState.IsValid); Assert.Equal(1, bindingContext.ModelState["foo.NameNoAttribute"].Errors.Count); Assert.Equal(@"This is a different exception. Parameter name: value", bindingContext.ModelState["foo.NameNoAttribute"].Errors[0].Exception.Message); } [Fact] public void SetProperty_SettingNullableTypeToNull_RequiredValidatorPresent_PropertySetterThrows_AddsRequiredMessageString() { // Arrange HttpActionContext context = ContextUtil.CreateActionContext(); ModelBindingContext bindingContext = new ModelBindingContext { ModelMetadata = GetMetadataForObject(new ModelWhosePropertySetterThrows()), ModelName = "foo" }; ModelMetadata propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name"); ModelValidationNode validationNode = new ModelValidationNode(propertyMetadata, "foo.Name"); ComplexModelDtoResult dtoResult = new ComplexModelDtoResult(null /* model */, validationNode); ModelValidator requiredValidator = context.GetValidators(propertyMetadata).Where(v => v.IsRequired).FirstOrDefault(); TestableMutableObjectModelBinder testableBinder = new TestableMutableObjectModelBinder(); // Act testableBinder.SetPropertyPublic(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.False(bindingContext.ModelState.IsValid); Assert.Equal(1, bindingContext.ModelState["foo.Name"].Errors.Count); Assert.Equal("This message comes from the [Required] attribute.", bindingContext.ModelState["foo.Name"].Errors[0].ErrorMessage); } private static ModelMetadata GetMetadataForCanUpdateProperty(string propertyName) { DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider(); return metadataProvider.GetMetadataForProperty(null, typeof(MyModelTestingCanUpdateProperty), propertyName); } private static ModelMetadata GetMetadataForObject(object o) { DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider(); return metadataProvider.GetMetadataForType(() => o, o.GetType()); } private static ModelMetadata GetMetadataForType(Type t) { DataAnnotationsModelMetadataProvider metadataProvider = new DataAnnotationsModelMetadataProvider(); return metadataProvider.GetMetadataForType(null, t); } private class Person { private DateTime? _dateOfDeath; public DateTime DateOfBirth { get; set; } public DateTime? DateOfDeath { get { return _dateOfDeath; } set { if (value < DateOfBirth) { throw new ArgumentOutOfRangeException("value", "Date of death can't be before date of birth."); } _dateOfDeath = value; } } [Required(ErrorMessage = "Sample message")] public int ValueTypeRequired { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string NonUpdateableProperty { get; private set; } [DefaultValue(typeof(decimal), "123.456")] public decimal PropertyWithDefaultValue { get; set; } } private class PersonWithBindExclusion { [HttpBindNever] public DateTime DateOfBirth { get; set; } [HttpBindNever] public DateTime? DateOfDeath { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string NonUpdateableProperty { get; private set; } } private class ModelWithBindRequired { public string Name { get; set; } [HttpBindRequired] public int Age { get; set; } } [HttpBindRequired] private class ModelWithMixedBindingBehaviors { public string Required { get; set; } [HttpBindNever] public string Never { get; set; } [HttpBindingBehavior(HttpBindingBehavior.Optional)] public string Optional { get; set; } } private sealed class MyModelTestingCanUpdateProperty { public int ReadOnlyInt { get; private set; } public string ReadOnlyString { get; private set; } public string[] ReadOnlyArray { get; private set; } public object ReadOnlyObject { get; private set; } public string ReadWriteString { get; set; } } private sealed class ModelWhosePropertySetterThrows { [Required(ErrorMessage = "This message comes from the [Required] attribute.")] public string Name { get { return null; } set { throw new ArgumentException("This is an exception.", "value"); } } public string NameNoAttribute { get { return null; } set { throw new ArgumentException("This is a different exception.", "value"); } } } public class TestableMutableObjectModelBinder : MutableObjectModelBinder { public virtual bool CanUpdatePropertyPublic(ModelMetadata propertyMetadata) { return base.CanUpdateProperty(propertyMetadata); } protected override bool CanUpdateProperty(ModelMetadata propertyMetadata) { return CanUpdatePropertyPublic(propertyMetadata); } public virtual object CreateModelPublic(HttpActionContext context, ModelBindingContext bindingContext) { return base.CreateModel(context, bindingContext); } protected override object CreateModel(HttpActionContext context, ModelBindingContext bindingContext) { return CreateModelPublic(context, bindingContext); } public virtual void EnsureModelPublic(HttpActionContext context, ModelBindingContext bindingContext) { base.EnsureModel(context, bindingContext); } protected override void EnsureModel(HttpActionContext context, ModelBindingContext bindingContext) { EnsureModelPublic(context, bindingContext); } public virtual IEnumerable GetMetadataForPropertiesPublic(HttpActionContext context, ModelBindingContext bindingContext) { return base.GetMetadataForProperties(context, bindingContext); } protected override IEnumerable GetMetadataForProperties(HttpActionContext context, ModelBindingContext bindingContext) { return GetMetadataForPropertiesPublic(context, bindingContext); } public virtual void SetPropertyPublic(HttpActionContext context, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, ModelValidator requiredValidator) { base.SetProperty(context, bindingContext, propertyMetadata, dtoResult, requiredValidator); } protected override void SetProperty(HttpActionContext actionContext, ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, ModelValidator requiredValidator) { SetPropertyPublic(actionContext, bindingContext, propertyMetadata, dtoResult, requiredValidator); } } } }