Arkiliknam
Arkiliknam

Reputation: 1825

ASP.NET MVC Posting models to an action with an an Interface

My understanding is that ASP.NET MVC only allows you to POST objects to Actions in the Controller, where the Action's arguments accept the posted object as a Concrete class.

Is there any way around this, or a good alternative?

In my case, I have an action which accepts an interface as an argument:

public ActionResult SaveAdjustment(IModel model)
{
    switch (model.SubsetType)
    {
        // factory like usage
    }
}

And for this action, I have numerous views, all strongly typed to objects that implement IModel, all which I want to be able to post to this one method.

Of course, running this give me the error:

Cannot create an instance of an interface

Is there a nice work around to this? Or do I need to create an Action method for each and send them over to a method like this?

Upvotes: 5

Views: 4515

Answers (2)

Arkiliknam
Arkiliknam

Reputation: 1825

Although I mentioned this as a possible solution in my original question, its the solution I have gone with in the end and I actually quite like it now. This way I didn't need to touch the model default binding implementation and I think this approach is a more readable/understandable approach than what I was originally asking for.

In case its not clear why I wanted to go for this approach, I have added an example of how I can use this for its OO benifits.

    [HttpPost]
    public ActionResult SaveModelA(ModelA model)
    {
        return SaveModel(model);
    }

    [HttpPost]
    public ActionResult SaveModelB(ModelB model)
    {
        return SaveModel(model);
    }

    private ActionResult SaveModel(IModel model)
    {
        IExampleService exampleService;
        IRequirements requirements;

        switch (model.SubsetType)
        {
            case SubsetType.ModelA:
                myService = new ModelAService();
                requirements = new ModelARequirements
                {
                    ModelASpecificProperty = "example"
                };
                break;
            case SubsetType.ModelB:
                myService = new ModelBService();
                requirements = new ModelBRequirements
                {
                    ModelBSpecificProperty1 = "example",
                    ModelBSpecificProperty2 = "example2",
                    ModelBSpecificProperty3 = "example3"
                };
                break;                
            default:
                throw new InvalidEnumArgumentException();
        }

        var serviceResonse = exampleService.ExecuteExample(model, requirements);

        return RedirectToAction("Index", new
        {
            ExampleData = serviceResponse.ExampleDate
        });
    }

In case it isn't clear in the code:

ModelA : IModel
ModelB : IModel
ModelARequirements : IModelRequirements
ModelBRequirements : IModelRequirements
ModelAService : IExampleService
ModelBService : IExampleService

// and IModel defines a property SubsetType SubsetType { get; }

Upvotes: 2

archil
archil

Reputation: 39501

MVC generally binds models when posting from Request.Form, that is collection of name=value pairs. The reason that in default implementation there's no support of binding interfaces or abstract classes is obvious - mvc cannot determine which concrete class to create from name=value pairs. If you got hidden field on client side, or any other parameter anywhere by which you are able to determine which type of concrete class to create, you can simply create custom model binder. I believe you can override DefaultModelBinder's CreateModel method and reuse all other built in binding functionality

public class IModelModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
    {
        //Create and return concrete instance
    }
}

And model binder registration in global.asax

ModelBinders.Binders.Add(typeof(IModel?), new IModelModelBinder());

Actually, controllers and actions in mvc are meant to be thin, and some kind of service layer should be thick. As action logic you are trying to implement may get complicated soon, I would recommend moving it into separate service.

Upvotes: 4

Related Questions