Может ли абстрактный класс быть параметром в действии контроллера?
У меня есть функция Action внутри контроллера, который вызывается с AJAX. Это действие принимает один параметр. Клиент, я создаю объект JSON, который должен быть сериализован в этот параметр. Проблема, с которой я столкнулся, заключается в том, что класс параметра объявлен как абстрактный. Таким образом, он не может быть создан. Когда AJAX попадает в это действие, я получаю следующее:
Невозможно создать абстрактный класс.
Трассировка стека:
[MissingMethodException: невозможно создать абстрактный класс.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean & canBeCached, RuntimeMethodHandleInternal & т е р, Логическое & 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, Boolean nonPublic) +69...............
Есть ли способ снять такой сценарий, не создавая другой объект параметра, "не объявляя" объект параметра абстрактным или копаясь в механике MVC? Спасибо.
UPDATE: В настоящее время я работаю с сторонними разработчиками, чтобы настроить их объекты. В любом случае, я думаю, что это будет окончательное решение. Спасибо всем за ваши ответы.
Ответы
Ответ 1
Обновление: Пример теперь использует AJAX JSON POST
Если вы должны использовать абстрактный тип, вы можете предоставить настраиваемое связующее устройство для создания конкретного экземпляра. Пример показан ниже:
Модель/модель 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 };
}
}
Действия с контроллером
public ActionResult Index()
{
return View(new GoodStudent { Age = 13, Name = "John Smith" });
}
[HttpPost]
public ActionResult Index(Student student)
{
return View(student);
}
Просмотр
@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>
Добавлено в Global.asax.cs
protected void Application_Start()
{
...
ModelBinders.Binders.Add(new KeyValuePair<Type, IModelBinder>(typeof(Student), new StudentBinder()));
}
Ответ 2
Вам нужно будет создать дочерний класс абстрактного класса и передать это вместо этого. Абстрактные классы в принципе не допускаются к созданию самих себя. Однако, если у вас есть метод С#, например:
protected void Foo(MyAbstractClass param1)
... тогда вы все равно можете передать Foo экземпляр типа, который происходит из MyAbstractClass. Таким образом, вы можете создать конкретный дочерний класс MyChildClass : MyAbstractClass
и передать его вашему методу, и он все равно должен работать. Вам не придется менять метод Foo
, но вам потребуется некоторый доступ к С# -коду, чтобы вы могли создать MyChildClass
.
Если вы работаете с дженериками - например, если ваша подпись метода:
protected void Foo(IEnumerable<MyAbstractClass> param1)
..., тогда это становится более сложным, и вы захотите изучить ковариацию и контравариантность в генераторах С#.
Ответ 3
У рамки нет возможности узнать, какую конкретную реализацию вы хотите, и она не возьмет на себя ответственность за такое решение. Таким образом, у вас есть две возможности:
- Используйте конкретный тип как параметр действия
- Напишите настраиваемое связующее устройство для этого абстрактного класса, который на основе некоторых параметров запроса вернет конкретный экземпляр.
Ответ 4
Если у вас есть доступ к контроллеру, вы можете добавить еще один класс, наследующий абстрактный класс, без указания какого-либо члена, и использовать его для сериализации десериализации, а затем вернуть его базу на другой уровень?
Я знаю, что это не хорошая практика, но некоторые хаки, однако я не знаю, могут ли абстрактные классы быть сериализованы каким-то образом.
Ответ 5
Нет - нет смысла пытаться десериализовать JSON для объекта абстрактного класса. Разве вы не можете сделать его подходящим классом?