0

I am trying to pass an interface parameter as part of my request object for POST and PUT APIs I have created, but I am not sure how to add a JSON deserializer to be able to specify which concrete class to use for which service. How do I specify that I want to use FooRequestData for one and FooOtherRequestData for the other?

    [ApiController]
    [Route("api/[controller]")]
    public class FooController
    {
        // POST api/<APIController>
        [HttpPost]
        public FooResponse Post(FooRequest request)
        {
            return FooService.Post(request);
        }

        // PUT api/<APIController>
        [HttpPut]
        public FooResponse Put(FooRequest request)
        {
            return FooService.Put(request);
        }
    }

    public class FooRequest : IRequest<Foo>
    {
        public IRequestData RequestData { get; set; }
    }

    public class FooRequestData : IRequestData
    {
        public string Boo { get; set; }
    }

    public class FooOtherRequestData : IRequestData
    {
        public string Hoo { get; set; }
    }

KateMak
  • 1,619
  • 6
  • 26
  • 42
  • There are various questions/answers here on StackOverflow around similar needs. They all boil down to you having to include enough data on your JSON to be able to figure out which type you're supposed to use. You could use `TypeNameHandling.Objects` to cause the type information to be included as a property on the JSON. You could create a converter that converts into one type if the `Boo` property is present and the other if `Hoo` is present. You could change your `FooRequest` model to have two optional/nullable properties, one for each possible type, and expect the right one to be populated. – StriplingWarrior Sep 08 '21 at 23:04

2 Answers2

0

I think that this answer to another question may work for you: creating a result filter and adding that to the controllers you want to specify behavior on.

Initially I thought a TextOutputFormatter would work, but there doesn't seem to be a way to check what controller or service the output formatting was requested from.

Andrew H
  • 875
  • 5
  • 20
-1

I understand what you are trying to do, however, it won't work. The issue is not in the API, but rather how interfaces get Serialized/Deserialized and converted into their inheriting classes.

For example, if IRequestData was originally a FooRequestData, but you now decide to transform it into a FooOtherRequestData it will throw an error as you cannot convert IRequestData to FooOtherRequestData if it wasn't a FooOtherRequestData to start with.

Instead, I would recommend one of two things:

Option 1: Keep Using interfaces

An interface doesn't actually remove properties like converting to a parent class does. The properties are still there, just not accessible. But when you return it as an json object it will still show all the properties.

Although that is passing between classes, but I'm fairly certain it extends to API requests as well.

When you want to use them as inputs, just passdown the interface as a requirements.

Option 2: Add a specific conversion

When working within the code you can add checks to see if it can be explicitly converted:

if(request.RequestData.GetType().IsAssignableTo(typeof(FooRequestData))) {
   var reqData = (FooRequestData)request.RequestData;
   ...
}
if(request.RequestData.GetType().IsAssignableTo(typeof(FooOtherRequestData))) {
  var reqData = (FooOtherRequestData)request.RequestData;
  ...
}
CorrieJanse
  • 2,374
  • 1
  • 6
  • 23
  • The option you've listed for "specific conversion" assumes that the `request` has already been serialized to have the right type. If that were the case, it'd be simpler to do `if(request.RequestData is FooRequestData reqData) {...}`. But this whole question is about how to get the JSON to serialize to the right concrete type in the first place, not how to cast a C# object to the right type. – StriplingWarrior Sep 08 '21 at 23:08