Может ли кто-нибудь объяснить мне, как работает привязка модели в ASP.NET?

Я пытаюсь выполнить почтовый запрос из Reactjs в ASP.NET API, используя axios. Сначала я получил ошибку 400 Bad request. Но после того, как я изменил DTO в серверной части, это сработало, но полученные данные были пустыми или нулевыми (я думаю, это одна из тех ошибок привязки модели). Это мой файл React.Js:

const defaultImgPath = '/assets/ImgStarter.jpg';
const initialFieldValues = {
  productName: 'Fried spring rolls',
  productPrice: 50000,
  productStatus: 1,
  productDescription: 'A short description',
  imagePath: defaultImgPath,
  imgName: '',
  ProductImg: null
}

const ApiUploadImg = () => {
  const [input, setInput] = useState(initialFieldValues) 
  const handleAddProduct = (e) => {
    e.preventDefault();
    axios.post('http://localhost:5273/api/product', input)
    .then((response) => {
      console.info(response);
    })
    .catch((error) => {
      console.info(error.response);
    })

return (
    <div className='form-uload'>
      <form className='p-5' onSubmit = {handleAddProduct}>
        <Input
          inputType = "text"
          labelname = "Product name"
          name = "productName"
          setInput = {setInput}
          input = {input}
        />
        <Input
          inputType = "number"
          labelname = "Price"
          name = "productPrice"
          setInput = {setInput}
          input = {input}
        />
        <Input
          inputType = "text"
          labelname = "Status (Is available?)"
          name = "productStatus"
          setInput = {setInput}
          input = {input}
        />
        <Input
          inputType = "text"
          labelname = "Description"
          name = "productDescription"
          setInput = {setInput}
          input = {input}
        />
        <Input
          inputType = "file"
          labelname = "Image"
          name = "productImg"
          setInput = {setInput}
          input = {input}
          onChange = {handlePreviewImg}
        />
        <div>
          <img
            src = {input.imagePath}
            className='preview-img'
          />
        </div>
        <button type='submit' className='form-controls btn btn-primary'>Submit</button>
      </form>
    </div>
  )
  }

Мой компонент Input.js:

const Input = ({ setInput, input, labelname, name, inputType, onChange=null}) => {
    const [value, setValue] = useState('');

    const onChangeHanlder = (e) => {
        setInput({...input, [name]: e.target.value})
    }
    var id = {name};
    return (
        <Fragment>
            <label htmlFor = {id} className='font-semibold text-white'>
                {labelname}:&nbsp;
            </label>
            <input
                id = {id}
                type = {inputType}
                name = {name}
                className='rounded input_form form-control mt-2 w-100'
                // placeholder = {placeholder} 
                onChange = {onChange ? onChange : onChangeHanlder}
            />
        </Fragment>
    )
}

Моя функция API:

        [HttpPost]
        public async Task<IActionResult> Create([FromBody] CreateProductRequestDTO requestDTO)
        {
            if (!ModelState.IsValid) 
            {
                return BadRequest(ModelState);
            }
            var productModel = requestDTO.ToProductsFromCreateDTO();
            await _productRepo.CreateAsync(productModel);
            return CreatedAtAction(nameof(GetById), new {id = productModel.Id}, productModel.ToProductDTO());
        }

И главный герой, DTO, о котором я упоминал выше. Я действительно думал, что это будет последнее место, где может произойти ошибка, потому что я буквально копирую атрибут «name» из файла ReactJs, упомянутого выше, и создаю этот DTO (Решение (1)). Но это не сработало. Поэтому я вставляю атрибуты из класса Products, но, как я уже сказал, этот метод делает полученные данные пустыми (Решение (2))

    public class CreateProductRequestDTO
    {
        //Solution (1)
        // public string? ProductName {get; set; } = string.Empty;
        // public int ProductPrice {get; set; } 
        // public bool ProductStatus {get; set; }
        // public string? ProductDescription {get; set; } = string.Empty;

        //Solution (2)
        public string Name {get; set; } = string.Empty;
        public int Price {get; set; } 
        public bool Status {get; set; }
        public string Description {get; set; } = string.Empty;
    }

И если вам это нужно, объект «вход» будет выглядеть точно так же, как «initialFieldValues». Может кто-нибудь объяснить мне, что происходит? Редактировать: Это данные запроса:

И это ответ от ASP.NET. Как видите, он возвращает нулевое и пустое значение. (Пожалуйста, игнорируйте код состояния 201):

Что говорит ответ об ошибке? ASP.NET Core десериализует опубликованный вами JSON и заполнит свойства объекта. Если JSON не соответствует, он вернет 400 с описанием неисправности. Если запрос не соответствует правилам проверки, он вернет стандартный ответ ПроблемаДетали.

Panagiotis Kanavos 17.04.2024 13:02

Также опубликуйте handleAddProduct и сам контент в формате JSON. Это метод, который делает запрос и терпит неудачу. Вы можете легко просмотреть как содержимое запроса, так и ответ 400 на вкладке «Сеть» инструментов разработчика вашего браузера.

Panagiotis Kanavos 17.04.2024 13:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
76
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте использовать FromForm.

[HttpPost]
    public async Task<IActionResult> Create([FromForm] CreateProductRequestDTO requestDTO)
    {
        if (!ModelState.IsValid) 
        {
            return BadRequest(ModelState);
        }
        var productModel = requestDTO.ToProductsFromCreateDTO();
        await _productRepo.CreateAsync(productModel);
        return CreatedAtAction(nameof(GetById), new {id = productModel.Id}, productModel.ToProductDTO());
    }

Если это не поможет, попробуйте привести пример разобранного вызова.

Ответ принят как подходящий

ModelBinding является частью жизненного цикла запроса .net.

Я не уверен, используете ли вы .NetFramework или .net ядро, но идея та же, я попытаюсь объяснить с помощью .Net ядра.

Ваш контроллер получает Dto, который в данном случае является запросом POST, который вы явно указываете на чтение или привязку параметров из тела. Ядро .Net имеет связующее устройство по умолчанию, которое назначает все значения, которые вы передаете в своем запросе, на основе соглашения об именах. Например, ваш почтовый запрос содержит входные данные с именем «productPrice», но в вашем DTO указана цена.

<Input
          inputType = "number"
          labelname = "Price"
          name = "productPrice"
          setInput = {setInput}
          input = {input}

если вы соответствуете одному и тому же соглашению об именах, это должно работать

<Input
          inputType = "number"
          labelname = "Price"   //change the name here
          name = "Price"
          setInput = {setInput}
          input = {input}

Теперь, если вы хотите сохранить определение реакции в том виде, в котором оно есть, и хотите изменить привязку, не меняя контроллер и DTO, вы можете расширить ядро ​​.Net, чтобы зарегистрировать для вас связующее устройство модели. Я собираюсь обновить здесь только цену, чтобы вы могли понять.

Это пример:

public class YourDtoModelBinder : IModelBinder
{
    public VehicleRequestDtoModelBinder()
    {

    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        CreateProductRequestDTO requestDto = null;

        var request = bindingContext.HttpContext.Request ?? throw new ArgumentNullException(nameof(bindingContext));

        //reading the value you passed
        var priceValue = bindingContext.ValueProvider.GetValue("productPrice").FirstValue;
        CreateProductRequestDTO  requestDto = null;

        try
        {
            requestDto = new CreateProductRequestDTO 
            {
                Price = priceValue,
                //bind the rest of the values here
            };

            bindingContext.Result = ModelBindingResult.Success(requestDto);
        }
        catch (Exception ex)
        {
            bindingContext.Result = ModelBindingResult.Failed();
        }

        return Task.CompletedTask;
    }
}

}

Теперь вам нужно зарегистрировать привязку модели и реализовать поставщик привязки модели для ввода вашего dto и позволить ядру .net, чтобы каждый раз, когда оно видит ваш dto, использовало эту привязку модели.

public class YourDtoModelBinder: IModelBinderProvider
{
    /// <summary>
    /// Method that performs the request binding
    /// </summary>
    /// <param name = "context"></param>
    /// <returns></returns>
    /// <exception cref = "ArgumentNullException"></exception>
    public IModelBinder? GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
        if (context.Metadata.ModelType == typeof(CreateProductRequestDTO))
        {
            return new BinderTypeModelBinder(typeof(YourDtoModelBinder));
        }

        return null;
    }
}

наконец, при внедрении зависимостей в ядре .net вставьте привязку модели как часть списка привязок моделей по умолчанию, что дает вам гибкость в работе с вашим запросом и не меняет уже имеющиеся контракты, поэтому у вас есть 2 варианта: следуйте соглашению по умолчанию, измените пользовательский интерфейс и передайте правильные имена или зарегистрируйте связующее устройство модели с вашей собственной логикой.

services.AddControllers()
    .AddMvcOptions(mvcOptions =>
    {
        mvcOptions.ModelBinderProviders.Insert(0, new VehicleRequestDtoModelBinderProvider());
    });

Другие вопросы по теме