0

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>5867ca8a5a5548428c4bc90c1f7e41d6@example.com</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?

Asbjørn Ulsberg
  • 8,721
  • 3
  • 45
  • 61
  • Can you deserialize the content you have pasted here manually? It shouldn't matter either way but you never know, – SerialSeb Aug 15 '11 at 11:16
  • Yea, the objects serialize and deserialize just fine. I can even deserialize the object manually from `IRequest.Entity.Stream`, but OpenRasta isn't doing it automatically for me. – Asbjørn Ulsberg Sep 15 '11 at 15:38
  • Well that is indeed a strange one. The MissingMethodException inside the binder may well be part of the problem. Thing is, the way the codec works doesn't seem to me like it'd be the source of the problem. What I may think however is that, because you reuse a handler across two resources (something you ought to avoid, as selecting the correct operation becomes a bit of a mess), the codec may end up trying to deserialzie the wrong type (is id not populated by any chance?). Can you send a repro project on the mailing list so i can investigate further? – SerialSeb Sep 16 '11 at 19:23

2 Answers2

1

Sounds wacky but have you tried moving the id to the top of your CustomerForm object?

[XmlRoot("customer", Namespace = ClientSettings.Namespace)]
public class CustomerForm : FormBase, ICustomer
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("contact-info")]
    public ContactInfo ContactInfo { get; set; }
}

Openrasta uses the (awful) .net data contract xml serializer which is sensitive to element positions. We actually wrote our own serialiser which simply reverts to the traditional 'dot net' xml serializer.

Vman
  • 3,016
  • 2
  • 24
  • 20
  • Thanks for the suggestion, but as you can see from my `ResourceSpace` config, I'm not using `DataContractSerializer` but the (almost just as bad) old-school `XmlSerializer`: `AsXmlSerializer().ForMediaType("application/xml;q=0.2")`. I don't see why the position of the elements should matter, though. As I wrote, OpenRasta deserializes perfectly on a POST. – Asbjørn Ulsberg Jul 15 '11 at 14:19
1

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.

Asbjørn Ulsberg
  • 8,721
  • 3
  • 45
  • 61
  • 1
    Aaaah. Ok, that makes a lot of sense. You see, models can either be built from key/value pairs (which can come from the URI parameters or codecs that support reading key/value pairs, like for html forms), or they can be built by a codec that only support building full objects (the datacontract serializers do that). You can't mix and match at this stage (maybe that's something we ought to add), so adding Id to the CustomerResource won't work. I'm not keen on making it work either, for security reasons. That said, {*id} is not a syntax we support so am surprised it doesnt thorw on you :) – SerialSeb Oct 21 '11 at 08:20