Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 214
Arkiliknam Avatar asked Jan 11 '12 16:01

Arkiliknam


People also ask

How does model binding work in MVC?

How model binding works. When MVC receives an HTTP request, it routes it to a specific action method of a controller. It determines which action method to run based on what is in the route data, then it binds values from the HTTP request to that action method's parameters.

What are model classes in MVC?

These classes are the " M odel" part of the M VC app. These model classes are used with Entity Framework Core (EF Core) to work with a database. EF Core is an object-relational mapping (ORM) framework that simplifies the data access code that you have to write.

What are the properties of personmodel in MVC?

Note: For beginners in ASP.Net MVC, please refer my article ASP.Net MVC Hello World Tutorial with Sample Program example. Following is a Model class named PersonModel with four properties i.e. PersonId, Name, Gender and City. /// Gets or sets PersonId.

What is initialcreate in mvcmoviecontext?

The InitialCreate argument is the migration name. Any name can be used, but by convention, a name is selected that describes the migration. Because this is the first migration, the generated class contains code to create the database schema. The database schema is based on the model specified in the MvcMovieContext class.


2 Answers

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.

like image 110
archil Avatar answered Sep 28 '22 05:09

archil


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; }
like image 45
Arkiliknam Avatar answered Sep 28 '22 04:09

Arkiliknam