开发者

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

开发者 https://www.devze.com 2023-03-02 13:46 出处:网络
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 paramet

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 publicOn开发者_运维技巧ly, 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.


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()));
}


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.


You will have to create a child class of the abstract class and pass that instead. Abstract classes are fundamentally not permitted to be instantiated by themselves. However, if you have a C# method like:

protected void Foo(MyAbstractClass param1)

...then you can still pass Foo an instance of a type that derives from MyAbstractClass. So you can create a concrete child class MyChildClass : MyAbstractClass and pass that to your method, and it should still work. You won't have to change the Foo method, but you will need some access to the C# code so you can create MyChildClass.

If you're working with generics -- for example, if your method signature is:

protected void Foo(IEnumerable<MyAbstractClass> param1)

...then it becomes more complicated, and you'll want to look into covariance and contravariance in C# generics.


If you have access to the controller could you add another class that inherits the abstract class without specifing any member, and use that to serialize deserialize, and then return its base onto the other layer?

I know this is not good practice but some hack however I don't know if abstract classes can be serialized in some way.


Nope - it makes no sense to have attempt to deserialize JSON to an object of an abstract class. Can't you make it a proper class?

0

精彩评论

暂无评论...
验证码 换一张
取 消