1

Pretty new to MVC so hopefully this is a simple question.

I have written a custom binding attribute that requires access to the httpContext. In order to inject a mock httpContext during unit tests, I have written an InjectingMetadataProvider that populates the Context property on any of my custom attributes.

I have managed to get this to work in the following test:

[TestMethod]
public void Marker_ShouldBind_Id()
{
    // Arrange
    var formCollection = new NameValueCollection 
    { 
        { "id", "2" }
    };

    var context = new Mock<HttpContextBase>();
    context.Setup(c => c.User).Returns((IPrincipal)null); 

    var metaProvider = new InjectingMetadataProvider(context.Object);
    ModelMetadataProviders.Current = metaProvider;  //why do I need this?

    var bindingContext = new ModelBindingContext
    {
        ModelName     = string.Empty,
        ValueProvider = new NameValueCollectionValueProvider(formCollection, null),
        ModelMetadata = metaProvider.GetMetadataForType(null, typeof(Marker)),
    };

    var binder = new DefaultModelBinder();

    // Act
    var marker = (Marker)binder.BindModel(new ControllerContext(), bindingContext);

    // Assert
    marker.Id.Should().Be(2);
}

However, if I comment out the line that sets my InjectingMetadataProvider to ModelMetadataProviders.Current, then my InjectingMetadataProvider.CreateMetadata() override gets handed a blank list of attributes, and so the test fails because my custom attributes don't get their context set.

Why do I need to set it to Current when I'm using it explicitly anyway? I don't want to be setting static stuff in my tests.

I may be doing something stupid because I'm feeling in the dark a bit at the moment due to my unfamiliarity with the framework.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • Did the code below make sense as to why you must specify ModelMetadataProviders.Current? You can see this method in context [here](https://github.com/ASP-NET-MVC/ASP.NET-Mvc-3/blob/master/mvc3/src/SystemWebMvc/Mvc/DefaultModelBinder.cs). – barry Nov 19 '12 at 15:47

1 Answers1

1

Inside the DefaultModelBinder, a new binding context is created when calling BindComplexElementalModel. Notice that it gets the metadata from the ModelMetadataProviders.Current, and not your custom model metadata provider.

  internal ModelBindingContext CreateComplexElementalModelBindingContext(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
        BindAttribute bindAttr = (BindAttribute)GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)];
        Predicate<string> newPropertyFilter = (bindAttr != null)
            ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
            : bindingContext.PropertyFilter;

        ModelBindingContext newBindingContext = new ModelBindingContext() {
            ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType),
            ModelName = bindingContext.ModelName,
            ModelState = bindingContext.ModelState,
            PropertyFilter = newPropertyFilter,
            ValueProvider = bindingContext.ValueProvider
        };

        return newBindingContext;
    }
barry
  • 835
  • 6
  • 14