Я пытаюсь выполнить почтовый запрос из 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}:
</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):
Также опубликуйте handleAddProduct
и сам контент в формате JSON. Это метод, который делает запрос и терпит неудачу. Вы можете легко просмотреть как содержимое запроса, так и ответ 400 на вкладке «Сеть» инструментов разработчика вашего браузера.
Попробуйте использовать 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());
});
Что говорит ответ об ошибке? ASP.NET Core десериализует опубликованный вами JSON и заполнит свойства объекта. Если JSON не соответствует, он вернет 400 с описанием неисправности. Если запрос не соответствует правилам проверки, он вернет стандартный ответ ПроблемаДетали.