#c# #asp.net-core #asp.net-core-5.0
#c# #asp.net-ядро #asp.net-core-5.0
Вопрос:
Я пытаюсь создать конечную точку (POST), которая принимает 1 или более моделей, которые затем будут массово вставлены в БД.
Все в порядке, за исключением случаев, когда я пытаюсь вызвать CreatedAtRoute
список созданных объектов и маршрутов.
Когда я вызываю CreatedAtRoute
с несколькими маршрутами / моделями, я получаю время выполнения InvalidOperationException
, не совпадающие маршруты.
Пример
[HttpPost]
public async Task<ActionResult<IEnumerable<QaiStateModel>>> CreateQaiStateAsync(
IEnumerable<QaiStateCreationModel> inputQaiStates )
{
var qaiStates = _mapper.Map<IEnumerable<QaiState>>( inputQaiStates );
await _qaiService.AddQaiStatesAsync( qaiState ).ConfigureAwait( false );
var models = _mapper.Map<List<QaiStateModel>>( qaiState );
var ids = models.Select( m => new { qaiStateID = m.ID } );
return CreatedAtRoute( "GetQaiState", ids, models );
}
Для справки у меня есть следующая GET/{ID}
конечная точка:
[HttpGet( "{qaiStateID}", Name = "GetQaiState" )]
public async Task<ActionResult<QaiStateModel>> GetQaiStateAsync( int qaiStateID )
Вопрос
Возможно ли вернуть оба URI
для доступа к вновь добавленным ресурсам, а также список модельного представления этих ресурсов?
Я также не уверен, что это правильный способ справиться с этой ситуацией (в отношении того, что я должен возвращать).
Комментарии:
1. CreatedAtRoute подразумевает один ресурс <-> один маршрут. Вероятно, вам потребуется вернуть код состояния и создать экземпляр объекта вручную
2. @CamiloTerevinto Да, я подумал, что так и будет. Является ли то, что я пытаюсь сделать (массовая вставка), необычным вариантом использования REST API?
3. В этом нет ничего необычного, но REST предназначен для работы с ресурсом (а не с ресурсами), поэтому вам нужно немного выйти из шаблона
Ответ №1:
Измените CreatedAtRoute
, как показано ниже:
return CreatedAtRoute("GetQaiState", new { qaiStateID = ids },models);
Измените GetQaiStateAsync
метод, как показано ниже:
public async Task<ActionResult<QaiStateModel>> GetQaiStateAsync(List<int> qaiStateID )
Ответ №2:
Чтобы заполнить заголовок location ответа 201, вернуть созданные модели и передать идентификаторы по маршруту, я должен был сделать следующее:
- Создайте новый
GET
обработчик, который ожидает массив идентификаторов - Создайте a
ModelBinder
, который преобразует переданный массив идентификаторов из маршрута - Укажите
POST
обработчикамCreatedAtRoute
, чтобы они указывали на этотGET
обработчик. Кроме того, вновь созданныйID's
должен был быть объединен в список, разделенный запятыми, который передается вCreatedAtRoute
Шаг 1:
Теперь GET
обработчик ожидает array
, что через маршрут будет передан идентификатор. Обратите внимание на указанный маршрут ({ids})
. Здесь я указываю, что мне нужен массив значений, разделенных круглыми скобками ()
.
[HttpGet( "({ids})", Name = "GetQaiStateCollection" )]
public async Task<ActionResult<IEnumerable<QaiStateModel>>> GetQaiStateCollectionAsync(
[FromRoute]
[ModelBinder( BinderType = typeof( ArrayModelBinder ) )]
IEnumerable<int>? ids )
{
if ( ids is null )
return BadRequest( "Invalid ID's" );
var states = await _qaiService
.GetQaiStatesAsync( ids ).ConfigureAwait( false );
if ( ids.Count( ) != states.Count( ) )
return NotFound( "Failed to find one or more states matching the provided ID's" );
var models = _mapper.Map<IEnumerable<QaiStateModel>>( states );
return Ok( models );
}
Шаг 2:
Для преобразования маршрута http://localhost:32003/QaiStateCollection/(4,2,3)
в IEnumerable<int>
пользовательскую модель необходимо было создать связующее.
public class ArrayModelBinder : IModelBinder
{
public Task BindModelAsync( ModelBindingContext bindingContext )
{
// This model binder only works for enumerable types.
if ( !bindingContext.ModelMetadata.IsEnumerableType )
{
bindingContext.Result = ModelBindingResult.Failed( );
return Task.CompletedTask;
}
// Get the input value via the value provider.
var value = bindingContext.ValueProvider
.GetValue( bindingContext.ModelName ).ToString( );
if ( string.IsNullOrWhiteSpace( value ) )
{
// Returning null here will allow us to check for this
// and return a bad request.
bindingContext.Result = ModelBindingResult.Success( null );
return Task.CompletedTask;
}
var elementType = bindingContext.ModelType.GenericTypeArguments.First( );
var converter = TypeDescriptor.GetConverter( elementType );
var values = value.Split( new [ ] { "," }, StringSplitOptions.RemoveEmptyEntries )
.Select( v => converter.ConvertFromString( v.Trim( ) ) )
.ToArray( );
var typedValues = Array.CreateInstance( elementType, values.Length );
values.CopyTo( typedValues, 0 );
bindingContext.Model = typedValues;
bindingContext.Result = ModelBindingResult.Success( bindingContext.Model );
return Task.CompletedTask;
}
}
Шаг 3:
Здесь идентификаторы объединяются в список, разделенный запятыми, и передаются в CreatedAtRoute
. Также передается новое GET
имя обработчика CreatedAtRoute
.
[HttpPost]
public async Task<ActionResult<IEnumerable<QaiStateModel>>> CreateQaiStateCollectionAsync(
IEnumerable<QaiStateCreationModel> inputModels )
{
var qaiStates = _mapper.Map<IEnumerable<QaiState>>( inputModels );
await _qaiService.AddQaiStateCollectionAsync( qaiStates ).ConfigureAwait( false );
var models = _mapper.Map<IEnumerable<QaiStateModel>>( qaiStates );
var ids = string.Join( ",", models.Select( m => m.ID ) );
return CreatedAtRoute( "GetQaiStateCollection", new { ids }, models );
}
Комментарии:
1. эммм, я думаю, что привязка вашей пользовательской модели немного сложна для ваших требований. Почему бы не попробовать мое решение ниже?
2. @Rena Потому что у меня есть другой контроллер, который имеет аналогичное требование, за исключением того, что идентификаторы являются
Guid
«s». Ваше решение не будет работать для этого. Тем не менее, я не указывал это в качестве требования, так что это не ваша вина.3. Позвольте мне проверить. 🙂
4. @Rena Вы должны получить
InvalidOperationException
, если я правильно помню. Я все равно приму ваш ответ, поскольку он прямо ответил на мой первоначальный вопрос.5. Привет @WBuck, я проверил это в своем проекте. Это сработало хорошо.эмммм, возможно, в вашем проекте отсутствуют какие-либо другие вещи. Я использовал asp.net ядро 3.1. Вы могли бы проверить gif:i.stack.imgur.com/iSQIl.gif