Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can abstract class be a parameter in a controller's action?

I have an Action function inside of a Controller, which is being called with AJAX. That Action is taking in 1 parameter. Client side, I construct a JSON object which should serialize into that 1 parameter. The problem I ran into is that the parameter class is declared as abstract. Thus, it cannot be instantiated.

When AJAX hits that Action, I get the following:

Cannot create an abstract class.

Stack Trace:

[MissingMethodException: Cannot create an abstract class.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache) +98
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache) +241 System.Activator.CreateInstance(Type type, Boolean nonPublic) +69 ...............

Is there any way to pull off such a scenario without creating a different parameter object, "un-declaring" the parameter object as abstract, or digging into mechanics of MVC?

Update

I'm currently working with back-end developers to tweak their objects. Either way, I think that would be the ultimate solution. Thank you all for your answers.

like image 357
Dimskiy Avatar asked May 02 '11 19:05

Dimskiy


People also ask

Can an abstract class be used as a parameter?

Even though A is abstract, you can receive parameters of type A (which, in this case, would be objects of type B ). You cannot really pass an object of class A , though, since A is abstract and cannot be instantiated.

Can abstract types be parameterized C#?

Yes, we can define a parameterized constructor in an abstract class.

Can dependency injection work with abstract class?

By using an abstract class as the dependency-injection token in conjunction with the useClass @Injectable() option, we're able to keep the simplicity of the providedIn syntax while also allowing for the traditional override functionality of Providers. It's the best of both worlds!

Is action result is an abstract class?

ActionResult is an abstract class that represents the result of an action method. The class itself inherits from System. Object, and only adds one additional abstract method: ExecuteResult, which is an abstract method that the derived classes of ActionResult will implement themselves.


2 Answers

Update: Example now uses a AJAX JSON POST

If you must use an abstract type, you could provide a custom model binder to create the concrete instance. An example is shown below:

Model / Model Binder

public abstract class Student
{
    public abstract int Age { get; set; }
    public abstract string Name { get; set; }
}
public class GoodStudent : Student
{
    public override int Age { get; set; }
    public override string Name { get; set; }
}
public class BadStudent : Student
{
    public override int Age { get; set; }
    public override string Name { get; set; }
}
public class StudentBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var values = (ValueProviderCollection) bindingContext.ValueProvider;
        var age = (int) values.GetValue("Age").ConvertTo(typeof (int));
        var name = (string) values.GetValue("Name").ConvertTo(typeof(string));
        return age > 10 ? (Student) new GoodStudent { Age = age, Name = name } : new BadStudent { Age = age, Name = name };
    }
}

Controller Actions

public ActionResult Index()
{
    return View(new GoodStudent { Age = 13, Name = "John Smith" });
}
[HttpPost]
public ActionResult Index(Student student)
{
    return View(student);
}

View

@model AbstractTest.Models.Student

@using (Html.BeginForm())
{
    <div id="StudentEditor">
        <p>Age @Html.TextBoxFor(m => m.Age)</p>
        <p>Name @Html.TextBoxFor(m => m.Name)</p>
        <p><input type="button" value="Save" id="Save" /></p>
    </div>
}

<script type="text/javascript">
    $('document').ready(function () {
        $('input#Save').click(function () {
            $.ajax({
                url: '@Ajax.JavaScriptStringEncode(Url.Action("Index"))',
                type: 'POST',
                data: GetStudentJsonData($('div#StudentEditor')),
                contentType: 'application/json; charset=utf-8',
                success: function (data, status, jqxhr) { window.location.href = '@Url.Action("Index")'; }
            });
        });
    });

    var GetStudentJsonData = function ($container) {
             return JSON.stringify({
                 'Age': $container.find('input#Age').attr('value'),
                 'Name': $container.find('input#Name').attr('value')
             });
         };
</script>

Added to Global.asax.cs

protected void Application_Start()
{
    ...
    ModelBinders.Binders.Add(new KeyValuePair<Type, IModelBinder>(typeof(Student), new StudentBinder()));
}
like image 97
Joseph Sturtevant Avatar answered Oct 20 '22 19:10

Joseph Sturtevant


The framework has no way of knowing which specific implementation you want, neither it will take the responsibility of such decision. So you have two possibilities here:

  1. Use a concrete type as action parameter
  2. Write a custom model binder for this abstract class which based on some request parameters will return a specific instance.
like image 30
Darin Dimitrov Avatar answered Oct 20 '22 19:10

Darin Dimitrov