Search code examples
httpdeserializationputopenrasta

OpenRasta can't properly deserialize an entity on PUT


I'm experiencing some really strange behavior when handling an HTTP PUT in an OpenRasta handler. The handler method signature looks like this:

public CustomerResource Put(CustomerForm customerForm)

And here is the relevant ResourceSpace configuration:

ResourceSpace.Has.ResourcesOfType<CustomerListResource>()
    .AtUri("/customers")
    .HandledBy<CustomerHandler>()
    .RenderedByAspx("~/Views/Customer/CustomerListView.aspx")
    .And.AsJsonDataContract().ForMediaType("application/json;q=0.3")
    .And.AsXmlSerializer().ForMediaType("application/xml;q=0.2");

ResourceSpace.Has.ResourcesOfType<CustomerResource>()
    .AtUri("/customers/{id}")
    .HandledBy<CustomerHandler>()
    .RenderedByAspx("~/Views/Customer/CustomerEditView.aspx")
    .And.AsJsonDataContract().ForMediaType("application/json;q=0.3")
    .And.AsXmlSerializer().ForMediaType("application/xml;q=0.2");

// To support POST and PUT to /customers
ResourceSpace.Has.ResourcesOfType<CustomerForm>()
    .WithoutUri
    .RenderedByAspx("~/Views/Customer/CustomerEditView.aspx")
    .And.AsJsonDataContract().ForMediaType("application/json;q=0.3")
    .And.AsXmlSerializer().ForMediaType("application/xml;q=0.2");

CustomerForm looks like this:

[XmlRoot("customer", Namespace = ClientSettings.Namespace)]
public class CustomerForm : FormBase, ICustomer
{
    [XmlElement("contact-info")]
    public ContactInfo ContactInfo { get; set; }

    [XmlAttribute("id")]
    public int Id { get; set; }
}

ContactInfo looks like this:

[XmlRoot("contact-info", Namespace = ClientSettings.Namespace)]
public class ContactInfo
{
    [XmlElement("email")]
    public string Email{ get; set; }

    [XmlElement("first-name")]
    public string FirstName{ get; set; }

    [XmlElement("last-name")]
    public string LastName{ get; set; }

    [XmlElement("mobile-phone-number")]
    public string MobilePhoneNumber { get; set; }
}

My problem is that when CustomerForm is PUT to the server, OpenRasta is unable to deserialize it properly. What it does is deserialize it with a ContactInfo set to null although it is sent successfully from the client. I have even digged in to the IRequest.Entity.Stream to ensure that the XML I need is indeed there, and it is:

<?xml version="1.0" encoding="utf-8"?>
<customer id="1" xmlns="urn:namespace">
    <contact-info>
        <email>[email protected]</email>
        <first-name>0440a6d5f071478d8571bac1301552bc</first-name>
        <last-name>49069fb41eb141c79326dc64fa034573</last-name>
        <mobile-phone-number>59980075</mobile-phone-number>
    </contact-info>
</customer>

How can IRequest.Entity.Stream contain the necessary data while the deserialized CustomerForm object received in the Put(CustomerForm) method doesn't? I have tests that ensure serialization and deserialization works perfectly and I even have a Post(CustomerForm) in the exact same handler that also works. It's just when the HTTP method is PUT that CustomerForm has ContactInfo set to null.

I've thoroughly read the debug output from OpenRasta and all it says is:

    27-[2011-07-15 11:09:15Z] Information(0) Operation CustomerHandler::Put(CustomerForm customerForm) was selected with a codec score of 0
    27-[2011-07-15 11:09:15Z] Information(0) Loaded codec OpenRasta.Codecs.XmlSerializerCodec
    27-[2011-07-15 11:09:15Z] Verbose(0) Switching to full object media type reading.
27-[2011-07-15 11:09:15Z] Stop(1) Exiting PipelineRunner
27-[2011-07-15 11:09:15Z] Start(1) Entering PipelineRunner: Executing contributor OperationInterceptorContributor.WrapOperations
27-[2011-07-15 11:09:16Z] Stop(1) Exiting PipelineRunner
27-[2011-07-15 11:09:16Z] Start(1) Entering PipelineRunner: Executing contributor OperationInvokerContributor.ExecuteOperations
    27-[2011-07-15 11:09:16Z] Verbose(0) Ignoring constructor, following dependencies didn't have a registration:OpenRasta.OperationModel.Interceptors.IOperationInterceptor[]

The only weirdness I have spotted is that a MissingMethodException is thrown as a FirstChanceException (but never bubbles up) right after this step, but the stack trace of it is so short I have no idea what the problem might be:

2011-07-15T13:09:16.036 AppDomain.FirstChanceException
System.MissingMethodException: Member not found.
   at System.DefaultBinder.BindToMethod(BindingFlags bindingAttr, MethodBase[] match, Object[]& args, ParameterModifier[] modifiers, CultureInfo cultureInfo, String[] names, Object& state)

I have no idea why a MissingMethodException is being thrown and why it doesn't bubble if I don't subscribe to the AppDomain.FirstChanceException event, but it might be related to why my CustomerForm isn't deserialized correctly. However, since it does deserialize correctly on HTTP POST, I have my doubts.

Ideas?


Solution

  • The problem seems to be with how the URL is interpreted and mapped to the handler, because if I add a handler method that also takes an int id, like this:

    CustomerResource Put(int id, CustomerForm customerForm)
    

    It works. This is probably due to the following resource registration:

    ResourceSpace.Has.ResourcesOfType<CustomerResource>()
        .AtUri("/customers/{id}")
    

    Although I have this:

    ResourceSpace.Has.ResourcesOfType<CustomerForm>()
        .WithoutUri
    

    I've tried to modify the registration of CustomerForm to include an ID:

    ResourceSpace.Has.ResourcesOfType<CustomerForm>()
        .AtUri("/customers/{id}")
    

    And also to make the ID optional on both the CustomerResource and CustomerForm registrations:

    ResourceSpace.Has.ResourcesOfType<CustomerResource>()
        .AtUri("/customers/{*id}")
    
    ResourceSpace.Has.ResourcesOfType<CustomerForm>()
        .AtUri("/customers/{*id}")
    

    None of this helps, but adding the int id parameter to the handler method does, so I'm pleased as this makes OpenRasta successfully deserialize my entity.