为收集资源重载Post Handler方法

Overloading Post Handler Methods for a Collection Resource

I'm using OpenRasta 2.0.3.0.

I have a collection resource called ListOfActivity, and two distinct types of activity, FileUploadActivity and CommentActivity. Both activity classes inherit from ActivityBase.

If I issue a GET request to tickets{ticketId]\activities then I get back a ListOfActivity. I would like to be able to POST either kind of activity to the same URI to add items to the collection, but I am having trouble getting OpenRasta to resolve an appropriate method on the handler.

My (abbreviated) handler implementation is:

public class ActivityHandler : ServiceBase, IActivityHandler
{
    public ListOfActivityResource GetByTicketId(int ticketId)
    {
        ...
    }

    public OperationResult Post(FileUploadActivity fileUploadActivity, int ticketId)
    {
        ...
    }

    public OperationResult Post(CommentActivity commentActivity, int ticketId)
    {
        ...
    }
}

Here are the resource classes:

[XmlType(TypeName = "commentActivity")]
public class CommentActivity : ActivityBase
{
    [XmlElement("comment")]
    public string Comment { get; set; }
}

[XmlType(TypeName = "fileUploadActivity")]
public class FileUploadActivity : ActivityBase
{
    [XmlElement("content")]
    public string Content { get; set; }
}

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

    [XmlElement("when")]
    public DateTime When { get; set; }

    [XmlElement("who")]
    public string Who { get; set; }

    // this property name is purposefully not called 'TicketId' as it caused an 
    // issue with OpenRasta's magic URI <> method matching algorithm because the 
    // UriTemplate for activity resources contains a parameter of the same name
    [XmlElement("ticketId")]
    public int TicketIdValue { get; set; }
}

[XmlType(TypeName = "activities")]
[UriTemplate("tickets/{ticketId}/activities")]
public class ListOfActivity
{
    public ListOfActivity()
    {
        this.Activities = new List<ActivityBase>();
    }

    [XmlElement("fileUpload", typeof(FileUploadActivity))]
    [XmlElement("comment", typeof(CommentActivity))]
    public List<ActivityBase> Activities { get; set; }
}

A snippet of my configuration is:

ResourceSpace.Has
    .ResourcesOfType<ListOfActivity>()
    .AtUri("tickets/{ticketId}/activities")
    .HandledBy<ActivityHandler>()
    .AsXmlSerializer();

ResourceSpace.Has
    .ResourcesOfType<FileUploadActivity>()
    .AtUri("tickets/{ticketId}/activities")
    .HandledBy<ActivityHandler>()
    .AsXmlSerializer();

ResourceSpace.Has
    .ResourcesOfType<CommentActivity>()
    .AtUri("tickets/{ticketId}/activities")
    .HandledBy<ActivityHandler>()
    .AsXmlSerializer();

When I attempt to POST a CommentActivity, I get a 405 response. Here is what I see in the log:

Found 2 operation(s) with a matching name.  
Found 0 operation(s) with matching [HttpOperation] attribute.   
Operation ActivityHandler::Post(FileUploadActivity fileUploadActivity, Int32 ticketId) selected with 2 required members and 0 optional members, with codec XmlSerializerCodec with score 1. 
Operation ActivityHandler::Post(FileUploadActivity fileUploadActivity, Int32 ticketId) selected with 2 required members and 0 optional members, with codec XmlSerializerCodec with score 1. 
Operation ActivityHandler::Post(FileUploadActivity fileUploadActivity, Int32 ticketId) selected with 2 required members and 0 optional members, with codec XmlSerializerCodec with score 1. 
Operation ActivityHandler::Post(CommentActivity commentActivity, Int32 ticketId) selected with 2 required members and 0 optional members, with codec XmlSerializerCodec with score 1.   
Operation ActivityHandler::Post(CommentActivity commentActivity, Int32 ticketId) selected with 2 required members and 0 optional members, with codec XmlSerializerCodec with score 1.   
Operation ActivityHandler::Post(CommentActivity commentActivity, Int32 ticketId) selected with 2 required members and 0 optional members, with codec XmlSerializerCodec with score 1.   
Executing OperationResult OperationResult: type=MethodNotAllowed, statusCode=405.   
No response codec was searched for. The response entity is null or a response codec is already set. 
There was no response entity, not rendering.    
Writing http headers.

I have tried naming the URIs, and using HttpOperationAttribute, but it doesn't work (as I kind of suspected, as they're all the same URI).

Before the introduction of a second activity resource, the single post method was being resolved fine.

Am I doing something wrong here? Sebastien Lambla's answer to this question would seem to indicate that it should be possible.

You can't do that, that's just plain evil.

URIs should never be reused across resource registrations, OR gets terribly confused by it and can't make logical decisions anymore (we ought to add some warning / error on those conditions).

I'd first declare the resource type with what the resource is. Here it is "the list of activities", so we'll use that.

ResourceSpace.Has
  .ResourcesOfType<ListOfActivity>()
  .AtUri("tickets/{ticketId}/activities")
  .HandledBy<ActivityHandler>()
  .AsXmlSerializer();

When OpenRasta will look at your methods, it will try and find something that can deserialzie the types to the correct type. Here, as they share a base class and OpenRasta is a really cool framework, you just need to associate the types itself to the resources, without giving them their own identifier (uris).

ResourceSpace.Has
  .ResourcesOfType<ActivityBase>()
  .WithoutUri
  .AsXmlSerializer()

Now when you do your POST to the list of activities, OR will look at your handler, find all methods that match the http request (here the Post ones), and try to deserialie the request in each of them, and finally chose the method with the most inputs remaining.

If you understand that concept, you'll know that this means OR may try to deserialize for both methods, which means you need a request that's seekable (aka the default on asp.net, not the default on HttpListener). You need to be aware of that at the very least, and if it may be an issue, split the registrations independently to avoid this (such as having tickets/{ticketId}/comments and .../fileUploads or such).

URI names are for distinguishing between multiple URIs for one resource, not multiple resources for one URI (which will never ever work in OR).

I suspected something was wrong with my approach. Thanks for taking the time to put together an answer. The approach I have ended up with was to have the separate URIs as you suggested, but retain the original collection URI for pulling them back as a single collection.