11

I have a http Action that will needs to take in two different object model. The Implementation looks at the two model object and know what to do at that point. Can I use a generic object?

[HttpPost]
public IHttpActionResult InsertData(string accessKey, [FromBody] T content)
{
    try
    {
        MobileAppService ms = new MobileAppService();
        ResultStatus resultStatus = ms.ProcessAppLogging(t);
        return Ok(resultStatus.ResultCode);
    }
    catch (Exception e)
    {
        Elmah.ErrorSignal.FromCurrentContext().Raise(e);
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Jefferson
  • 173
  • 2
  • 12
  • 32
  • 2
    What is your question? – Kenneth K. Jan 23 '20 at 21:47
  • 1
    Set it's type to `object` and manually deserialize it, then validate if object is correct. – Genotypek Jan 23 '20 at 22:11
  • 1
    Normally your HTTP endpoints have a specific model in mind... It's unclear what the goal here is, or why generics are considered as a solution. –  Jan 23 '20 at 22:13
  • Do you mean JSON the object? – Jefferson Jan 23 '20 at 22:15
  • None of these comments mentioned JSON. And JSON is not a verb. –  Jan 23 '20 at 22:18
  • I have two models but wanted to use the same web API. – Jefferson Jan 23 '20 at 22:19
  • Then make two endpoints. Generics is not appropriate for this purpose. –  Jan 23 '20 at 22:21
  • @jefferson See: https://stackoverflow.com/questions/29398241/generic-web-api-method –  Jan 23 '20 at 22:32
  • 2
    @Jefferson I want to caution you though. I feel this is ill-advised, and you will be *much* better served simply making two endpoints. Can you use generics? I'm dubious. Will it be beneficial? I doubt it, and expect it will prove to be a hindrance in the future. This is my opinion though; someone else may come along and say "sure! this is how!" –  Jan 23 '20 at 22:34
  • @Amy frombody can take an interface instead of a concrete class? – Jefferson Jan 24 '20 at 14:04
  • @Jefferson Have you tried on the option of using Method overloading? – Eugene Ogongo Jan 29 '20 at 16:37
  • Yes I have that working just wanted it to be more abstract – Jefferson Jan 29 '20 at 18:50
  • I was thinking maybe an `Interface` approach to this. It's not straightforward but looks like it has been covered here: https://stackoverflow.com/questions/27214221/rest-webapi-interface-as-parameter-in-api-call – scgough Feb 05 '20 at 14:14

2 Answers2

16

Controller having a generic action method

With default implementations of the framework, No, you cannot have such action:

public IHttpActionResult InsertData([FromBody] T model)

This is the exception which you receive:

Cannot call action method 'XXXX' on controller 'XXXX' because the action method is a generic method.

But the framework is very extensible and if you get a very good understanding of Routing and Action Selection in ASP.NET Web API and learn how routing, controller selection, action selection, parameter binding and action invocation work, then you can implement some customization for the framework to support generic action method selection and invocation.

In your custom logic, to be able to execute the action in run-time, you need to resolve T at run-time. To do so, you can rely on attributes to limit it to some known types or you can rely on some context information like route data, header values, some special values like $type in body an so on.

Providing a custom implementation to handle generic action method is too broad for this post. That said, let me share other solutions.

Derive from a generic base controller

You can have a base generic controller then having two non-generic controllers derived from the base controller and handle the request by a single method which is implemented in the base controller. I assume you have seen this post which has already suggested the same solution:

public class MyBaseController<T> : ApiController
{
    public IHttpActionResult InsertData([FromBody] T model)
    {
        //Write the generic code here, for example:
        dbContext.Set<T>().Add(model);
        dbContext.SaveChanges();
        return some value;            
    }
}

Then:

public class ProductController : MyBaseController<Product> { }
public class CustomerController : MyBaseController<Customer> { }

Rely on dynamic and resolve the type later based on context information

Another option is having the following action method:

public IHttpActionResult InsertData([FromBody] dynamic model)

Then based on some context information like route values, header values, some special values like $type in body an so on, you can resolve model type and call your private generic method:

public IHttpActionResult InsertData([FromBody] dynamic model)
{
    Type t = resolve type base on context information
    object data = create an instance of t base on the model values;

    var method = this.GetType().GetMethod(nameof(InsertDataPrivate),
        BindingFlags.NonPublic | BindingFlags.Instance);
    var result = (int)method.MakeGenericMethod(t)
       .Invoke(this, new object[] { data });

    return Ok(result);
}
private int InsertDataPrivate<T>(T model) where T
{
    //Write the generic code here, for example:
    dbContext.Set<T>().Add(model);
    dbContext.SaveChanges();
    return some value;
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
0

Don't mess up with all the objects, just do a cast.

Content-Type: application/json

[ApiController]
public class AdWordsController : ControllerBase
{
    [HttpPost("/Metrics/")]
    [EnableCors("CorsPolicy")]
    public IActionResult GetMetrics(dynamic data)
    {
        return new JsonResult(Metrics((String)data.customerId, (String)data.from, (String)data.to));
    }
user11658885
  • 105
  • 1
  • 7