4

I've been struggling a lot with that, I found some questions but none could answer my needs. I will try to post a better question and some of the things I tried.

Here is the situation: I have an APIGateway and a WebApp. The WebApp sends POST requests to the APIGateway, so far so good. I use the FromBody attribute to send larger objects, and that was fine too until I introduced interfaces :))

Here's some code:

WebApp:

public interface ICommand
{
    Guid CorrelationId { get; set; }

    Guid SocketId { get; set; }
}

public class Command : ICommand
{       
    public Command(Guid CorrelationId, Guid SocketId)
    {
        this.CorrelationId = CorrelationId;
        this.SocketId = SocketId;
    }

    public Guid CorrelationId { get; set; } = new Guid();

    public Guid SocketId { get; set; } = new Guid();
}    

public interface IDocument
{
    Guid Id { get; set; }

    ulong Number { get; set; }
}

public class Document : IDocument
{
    public Guid Id { get; set; } = new Guid();

    public ulong Number { get; set; } = 0;
}

public interface ICreateDocumentCommand : ICommand
{
    IDocument Document { get; set; }
}

public class  CreateDocumentCommand : Command, ICreateDocumentCommand
{
    public CreateDocumentCommand(IDocument Document, ICommand Command) : base(Command.CorrelationId, Command.SocketId)
    {
        this.Document = Document;
    }

    public IDocument Document { get; set; }
}

APIGateway:

[HttpPost]
public async Task<IActionResult> Create([FromBody]CreateDocumentCommand documentCommand)
{
    if (documentCommand == null)
    {
       return StatusCode(403);
    }
    return Json(documentCommand.Document.Id);
}

Use case:

public class InventoryList : Document
{
    public Guid WarehouseId { get; set; } = new Guid();
}

// Example document class
////////////////////////////////////////
// Example POST Request

ICommand command = new Command(messageId, socketId);
switch (item.GetType().Name)
{
    case "InventoryList":
    command = new CreateDocumentCommand((InventoryList)item, command);
    break;
}
string result = await PostAsync($"{apiGatewayAddress}{item.GetType().BaseType.Name}/Create", command, accessToken);

My POST sending function:

public async Task<string> PostAsync<T>(string uri, T item, string authorizationToken = null, string authorizationMethod = "Bearer")
{
    JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };

    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri);
    requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

    requestMessage.Content = new StringContent(JsonConvert.SerializeObject(item, typeof(T), jsonSerializerSettings), System.Text.Encoding.UTF8, "application/json");
    return await _client.SendAsync(requestMessage).Result.Content.ReadAsStringAsync();
}

As you can see I have included TypeNameHandling.All in the JSON serialization settings, the request is sent and the Create in the APIGateway gets called. However the parameter documentCommand is NULL.

I've read this: Asp.Net Core Post FromBody Always Null

This: ASP.NET Core MVC - Model Binding : Bind an interface model using the attribute [FromBody] (BodyModelBinder)

This: Casting interfaces for deserialization in JSON.NET

Tried all kind of magic tricks, created new constructors, marked them with [JSONConstructor], still no success. Also I tried changing the APIGateway Cerate method parameter type to ICreateDocumentCommand and again I got a null. I've been searching some model binding tricks online however I couldn't find anything for binding with FromBody. I also found some solution including DI but I am looking for a simple solution. I hope that we will be able to find one :)

Kaloyan Manev
  • 406
  • 6
  • 20

1 Answers1

1

Turns out, passing interfaces or classes with interfaces inside as JSON is not that easy. I added a custom JSONConverter and it works now!

Kaloyan Manev
  • 406
  • 6
  • 20
  • It would be great if you shared your custom JsonConverter in your answer. – Doug Wilson Oct 22 '18 at 15:55
  • @DougWilson Something went wrong and then I created separate controllers and types for the different types of documents. Like CreateDocumentCommand -> CreateInvoiceCommand, CreateSomeOtherDocumentCommand. And these classes have the document of the respective type inside them. That was what I was trying to run away from... Anyway at least it works. – Kaloyan Manev Oct 24 '18 at 16:49