Url variable substitution in a POST DTO

I am currently using ServiceStack v4.5.12.

I have a the following DTO defined.

[Route("/thing/{Id}/point", "POST")]
[DataContract]
public class MyPostDto : IReturn<MyDto>
{
  public int Id { get; set; }
  // Other properties here.
}

When I use a test REST client (such as the Firefox add-on RESTer) to invoke the API with the URL /thing/1234/point and an appropriate JSON body, ServiceStack correctly invokes my service interface method with a correctly populated DTO. So far so good.

However, when I invoke the IRestClientAsync.PostAsync(dto) C# client side method, an InvalidOperationException is thrown with the message “None of the given rest routes matches ‘MyPostDto’ request: /thing/{Id}/point: Variable ‘Id’ does not match any property.

What am I doing wrong?

Any help will be much appreciated.
Thanks,
John

If you’re using [DataContract] you need to opt-in and annotate all Properties you want serialized with [DataMember], e.g:

[Route("/thing/{Id}/point", "POST")]
[DataContract]
public class MyPostDto : IReturn<MyDto>
{
    [DataMember]
    public int Id { get; set; }
    // Other properties here.
}

Otherwise you can use a unattributed POCO:

[Route("/thing/{Id}/point", "POST")]
public class MyPostDto : IReturn<MyDto>
{
    public int Id { get; set; }
    // Other properties here.
}

My apologies, as I was stripping down the actual code to create this example, left out the attribute detail. See below that I am using the DataMember attribute to change the serialized name of the POCO property.

[Route("/thing/{Id}/point", "POST")]
[DataContract]
public class MyPostDto : IReturn<MyDto>
{
  [DataMember(Name = "id")]
  public int Id { get; set; }
  // Other properties here.
}

After experimenting some more with this. I have discovered that if the Route contains {Id} (ie. the POCO property name), then the server side code is happy and works when tested with RESTer. However the client code fails with the aforementioned exception.

If on the other hand the Route contains {id} (ie. the DataMember name), the client code is able to make the PostAsync call successfully. However, the server side code fails to process the request with a "System.ArgumentException: Could not find property id on MyPostDto."

Thanks for your help,
John

Note: the {Id} property needs to match the Property name on the DTO not the alias on the attribute.

But I still can’t repro this issue, please provide a complete example including Service Implementation, DTOs and client source code that can repro this issue as well as the full StackTrace.

Sorry it has been so long since I last replied. Priorities changed and I had to focus on other issues. But I now have a solution with both a client and server project that hopefully demonstrates the issue I am seeing.

I am happy to zip it and send it to you, but I don’t see that I can attach files to these comments. How shall I get it to you?

Please put it on GitHub and send me the link.

Here is the link to the repo: https://github.com/johnsoncr34/ss-example

I have tried to simplify the original issue down to the essence of my question. The PointDtos.cs file contains, in part, the following where the comments on the three DTO classes state the issue/question:

  /// <summary>
  /// This works as expected since the route's variable place-holder exactly matches the DTO property name.
  /// </summary>
  [Route("/v1/works/point/{PointId}", "GET")]
  public class WorksPointGetDto : IReturn<PointDto>
  {
    public int PointId { get; set; }
  }
  /// <summary>
  /// Given that one might assume from the above example that the route's variable place-holder must match the 
  /// DTO property name exactly, why does this one work?  It gives the impression that ServiceStack is using the 
  /// DataMember attribute to resolve the variable place-holder, but because this does work, I am assuming that
  /// the DTO property name matching is being done case-insensitively.
  /// </summary>
  [Route("/v1/why/does/this/work/point/{pointid}", "GET")]
  [DataContract]
  public class WhyDoesThisWorkPointGetDto : IReturn<PointDto>
  {
    [DataMember(Name = "pointid")]
    public int PointId { get; set; }
  }
  /// <summary>
  /// 
  /// Why does this not work?
  /// 
  /// I assume this does not work because the route's variable place-holder does not match the DTO property name.
  /// However this is desirable since we have standardized on a kebab-case naming convention that is supported
  /// by the DataMember attribute.  Not being able to use the kebab-case name in the route causes the non-standard
  /// name to show up in SwaggerUI and the OpenAPI output from which we generate user documentation even though
  /// the DataMember attribute is honored by SwaggerUI in its parameters list resulting in a name case mismatch 
  /// between the parameter and route shown in SwaggerUI and OpenAPI.
  /// </summary>
  [Route("/v1/why/does/this/not/work/point/{point-id}", "GET")]
  [DataContract]
  public class WhyDoesThisNotWorkPointGetDto : IReturn<PointDto>
  {
    [DataMember(Name = "point-id")]
    public int PointId { get; set; }
  }

Thanks for you assistance,
John

The variable place holder needs to match the property name, so you should only be using {PointId} in your route rules. The variable place holder indicates which property the path segment should bind to, it has nothing to do with any specific serialization format of the DTO. It’s only matching the /path/info of the Request, any serialization customizations are irrelevant.

Do you have an issue with any valid Route? (The only valid Route is your first one).