Luke Willis
Luke Willis

Reputation: 8580

How to define a REST method in C# with abstract parameter

The API Call

I am making a REST API call with the following message body:

{"Method":{"Token":"0","Value":"0"}}

400 Response

I am getting a 400 Bad Request response from the api with the following body:

{"Message":"The request is invalid.","ModelState":{"request.Method.Token":["Could not create an instance of type Namespace.ActionMethod. Type is an interface or abstract class and cannot be instantiated. Path 'ActionMethod.Token'."]}}

Code Information

The method which is receiving the api call looks like this:

public MethodResponse MakeMethodCall([Required] [FromBody] MethodRequest request)

MethodRequest has a Method property which is an abstract type.

public class MethodRequest
{
    public ActionMethod Method { get; set; }
}

public abstract class ActionMethod
{
    public string Token { get; set; }
}

public class FirstMethod : ActionMethod
{
    public string Value { get; set; }
}

Question

How can I call the REST API and have it recognize that the type of Method is FirstMethod, instead of it trying to instantiate the abstract type ActionMethod?

Note that I will need to have more implementations of ActionMethod in the future (ie. SecondMethod), so the solution will need to include an extensible ActionMethod (interface would also be fine).


EDIT

It would also be reasonable to include an enum to identify which implementation of ActionMethod was being targeted by the API call.

I'm currently using a solution which has an ActionMethodType enum and both FirstMethod and SecondMethod fields. I'm checking these fields based on the value of ActionMethodType. This works, but I would like to have a single [Required] field into which I could pass any implementation of ActionMethod.

Upvotes: 2

Views: 4113

Answers (3)

solidau
solidau

Reputation: 4081

If I understand you correctly, you could implement this with a custom model binder and a factory pattern.

public class MethodRequestBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
                        ModelBindingContext bindingContext)
    {
        HttpRequestBase request = controllerContext.HttpContext.Request;

        //use the request object to make a call to your factory for the 
        //appropriate ActionMethod subtype you want to create, or however 
        //else you see fit.
        var curActionMethod = MyFactory.Get(request.QueryString);
        var boundObj = new MethodRequest()
        {
            Method = curActionMethod
        }

        return boundObj;
    }
}

register your model binder in app_start:

ModelBinders.Binders.Add(typeof(MethodRequest), new MethodRequestBinder());

now, just decorate your controller action method:

public ActionResult Index([ModelBinder(typeof(MethodRequestBinder))] MethodRequest request)
{ 
    //etc..
}

I used this as a starting point: http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder

Upvotes: 2

Robert Levy
Robert Levy

Reputation: 29083

Can't be done. How would the framework know to instantiate FirstMethod for this parameter? What if you had another subclass of ActionMethod that also had a Value property? Now it's even more ambiguous for the framework to figure out on it's own. You could do a bunch of work, creating a custom formatter (http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx) but ultimately it would be easier to just have a single class that includes all possible properties a client could send OR have separate API endpoints for the client to call using different concrete types as the parameter.

Upvotes: 4

Jon
Jon

Reputation: 3255

Remove the abstract keyword from your ActionMethod, or mark the Token property abstract and override it in the inherited classes:

public abstract class ActionMethod
{
    public abstract string Token { get; set; }
}

public class FirstMethod : ActionMethod
{
    public string Value { get; set; }

    public override string Token
    {
        get;
        set;
    }
}

Upvotes: -1

Related Questions