Пользовательская привязка модели через тело в ASP.Net Core - PullRequest
6 голосов
/ 22 мая 2019

Я бы хотел связать объект в контроллере через тело HTTP Post.

Это работает так

public class MyModelBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException("No context found");

            string modelName = bindingContext.ModelName;
            if (String.IsNullOrEmpty(modelName)) {
                bindingContext.Result = ModelBindingResult.Failed();
                return Task.CompletedTask;
            }

            string value = bindingContext.ValueProvider.GetValue(modelName).FirstValue;
...

modelName равно viewModel (честно, я не знаю почему, но это работает ...)

Мой контроллер выглядит следующим образом:

    [HttpPost]
    [Route("my/route")]
    public IActionResult CalcAc([ModelBinder(BinderType = typeof(MyModelBinder))]IViewModel viewModel)
    {
   ....

, т.е. он работает, когда я делаю этот запрос HTTP-Post

url/my/route?viewModel=URLparsedJSON

Однако я хотел бы пропустить его через тело запроса, т.е.

public IActionResult Calc([FromBody][ModelBinder(BinderType = typeof(MyModelBinder))]IViewModel viewModel)

В моем Modelbinder тогда modelName равно "", а ValueProvider возвращает ноль ... Что я делаю не так?

ОБНОВЛЕНИЕ

Пример;Предположим, у вас есть интерфейс IGeometry и множество реализаций различных 2D-фигур, таких как Circle: IGeometry или Rectangle: IGeometry или Polygon: IGeometry.IGeometry сам имеет метод decimal getArea().Теперь мой URL должен вычислить площадь для любой фигуры, которая реализует IGeometry, которая будет выглядеть следующим образом:

    [HttpPost]
    [Route("geometry/calcArea")]
    public IActionResult CalcArea([FromBody]IGeometry geometricObject)
    {
         return Ok(geometricObject.getArea());
         // or for sake of completness
         // return Ok(service.getArea(geometricObject));
    }

проблема в том, что вы не можете привязать интерфейс, который выдает ошибку, вам нужноучебный класс!Вот где используется специальное связующее.Предположим, что ваш IGeometry также имеет следующее свойство string Type {get; set;} в привязке пользовательской модели, вы просто будете искать этот тип в переданном json и привязывать его к правильной реализации.Что-то вроде

if (bodyContent is Rectangle) // that doesn't work ofc, but you get the point
var boundObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Rectangle>(jsonString);

ASP.Net EF

В ASP.Net EF привязка пользовательской модели выглядит следующим образом

 public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)

здесь выполучите тело HTTPPost-запроса, например,

 string json = actionContext.Request.Content.ReadAsStringAsync().Result;

в ASP.Net Core, у вас нет actionContext, только bindingContext, где я не могу найти тело HTTP-сообщения.

ОБНОВЛЕНИЕ 2

Хорошо, я нашел тело, смотри принятый ответ.Теперь внутри метода контроллера у меня действительно есть объект типа IGeometry (интерфейс), который создается внутри привязки пользовательской модели!Мой метод контроллера выглядит следующим образом:

    [HttpPost]
    [Route("geometry/calcArea")]
    public IActionResult CalcArea([FromBody]IGeometry geometricObject)
    {
         return Ok(service.getArea(geometricObject));
    }

А мой внедренный сервис вот так

    public decimal getArea(IGeometry viewModel)
    {
        return viewModel.calcArea();
    }

IGeometry, с другой стороны, выглядит так

public interface IGeometry
{
    string Type { get; set; } // I use this to correctly bind to each implementation
    decimal calcArea();

...

Затем каждый класс просто вычисляет площадь соответственно, поэтому

public class Rectangle : IGeometry
{
    public string Type {get; set; }
    public decimal b0 { get; set; }
    public decimal h0 { get; set; }

    public decimal calcArea()
    {
        return b0 * h0;
    }

или

public class Circle : IGeometry
{
    public string Type {get; set; }
    public decimal radius { get; set; }

    public decimal calcArea()
    {
        return radius*radius*Math.Pi;
    }

1 Ответ

3 голосов
/ 28 мая 2019

Я нашел решение.Тело HTTP Post-запроса с использованием ASP.NET Core можно получить в привязке пользовательской модели, используя следующие строки кода:

string json;
using (var reader = new StreamReader(bindingContext.ActionContext.HttpContext.Request.Body, Encoding.UTF8))
   json = reader.ReadToEnd();

Я нашел решение, посмотрев на старые проекты EF.Там тело находится внутри ActionContext, который передается отдельно в качестве аргумента в методе BindModel.Я обнаружил, что тот же ActionContext является частью ModelBindingContext в ASP.Net Core, где вместо строки вы получаете IO.Stream (легко преобразовать: -))

...