Я некоторое время не писал API (последний раз в asp.core 2.0), поэтому сегодня решил создать новый в версии .core 2.1 и, к сожалению, обнаружил, что мои действия не работают так, как они работали в предыдущей версии.
Через несколько часов я понял, что проблема связана с [Route("api/[controller]")]
, и, поскольку [ApiController]
не работает без него, я прокомментировал их обоих, и все работает ОТЛИЧНО.
** Так может ли кто-нибудь объяснить мне, что мне делать, чтобы этот код работал с неподключенным «[ApiController]» и теми же вызовами URL-адресов действий?
//[Route("api/[controller]")]
//[ApiController]
public class TestController : ControllerBase
{
[HttpGet("api/[controller]")]
public string A1()
{
return "A1()";
}
[HttpGet]
public string A2(int id)
{
return $"A2(int {id})";
}
[HttpGet]
public string A3(string p1,string p2)
{
return $"A3(string {p1},string {p2})";
}
[Route("api/[controller]/A4/{id}")]
[HttpGet]
public string A4(int id)
{
return $"A4(int {id})";
}
[HttpGet("api/[controller]/A5/{id}")]
public string A5(int id)
{
return $"A5(int {id})";
}
}
` StartUp.css
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{……///code
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{controller=Home}/{action=Index}/{id?}");
});
}
Тест1: Я внес изменения на основе ответа @chris-pratt
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet("")]
public string A1()
{
return "A1()";
}
[HttpGet("A2")]
[HttpGet("A2/{id}")]
public string A2([FromQuery]int id)
{
return $"A2(int {id})";
}
[HttpGet("A3")]
public string A3([FromQuery]string p1, [FromQuery]string p2)
{
return $"A3(string {p1},string {p2})";
}
[HttpGet("A4")]
public string A4([FromQuery]int id)
{
return $"A4(int {id})";
}
[HttpGet("A5")]
public string A5([FromQuery]int id)
{
return $"A5(int {id})";
}
}
URL звонков:
5 и 6 теперь одинаковы
Ни [Route("api/[controller]")]
, ни [ApiController]
не являются вашей реальной проблемой. Атрибут [Route]
на уровне класса контроллера указывает префикс маршрута для всех действий в этом контроллере. Когда вы снимаете его, вы возвращаетесь к маршруту по умолчанию в MVC, /{controller}/{action}/{id?}
.
Также важно понимать, что использование атрибутивной маршрутизации переопределяет маршрут по умолчанию на уровне применения. Другими словами, если вы не применяете Route
к своему контроллеру, то действия там будут использовать маршрут по умолчанию. Если вы примените атрибут Route
или один из атрибутов метода HTTP, например HttpGet
, к действию, то только это отдельное действие будет использовать настраиваемый маршрут атрибута. Однако, если вы примените атрибут Route
к своему контроллеру, файл все действия в контроллере будут использовать маршрутизацию атрибутов, даже если вы явно не применяете атрибут. Поэтому важно, чтобы вы давали каждому действию свой собственный уникальный маршрут.
Ваша первая проблема заключается в том, что вы применили тот же маршрут к некоторым своим действиям, что и к контроллеру. Конечным результатом является то, что маршрут для этого действия на самом деле оказывается /api/test/api/test
. Вам нужно только указать часть маршрута после префикса, то есть [HttpGet("a1")]
. Если вы хотите, чтобы это был просто префикс без собственного дополнительного сегмента маршрута, вы просто используете пустой маршрут, то есть [HttpGet("")]
или просто [HttpGet]
. Просто убедитесь, что вы делаете это только один раз для каждого метода HTTP.
Теперь разница с применением [ApiController]
заключается в том, что, среди прочего, он переключает привязку по умолчанию с FromForm
на FromBody
. Однако это будет применяться только к ссылочным типам в качестве параметров, таких как классы. Типы значений, такие как строки, будут несвязанными. Поскольку вы хотите получить их из строки запроса, по-видимому, вам следует добавить к ним [FromQuery]
:
[HttpGet]
public string A3([FromQuery]string p1, [FromQuery]string p2)
Наконец, у вас также есть куча повторяющихся маршрутов. Как я отметил выше, не указание маршрута равносильно указанию пустого маршрута в действии, что означает, что все, что у вас есть, это префикс маршрута, установленный на контроллере. Вы должны убедиться, что каждое действие имеет уникальный маршрут, на который оно отвечает. Например, приведенный выше метод должен фактически иметь что-то вроде [HttpGet("a3")]
, что затем даст вам предполагаемый маршрут /api/test/a3?p1=test&p2=test
.
Спасибо за ответ и подробное объяснение. К сожалению, если у контроллера есть атрибут [ApiController]
, то каждое действие должно иметь определенный маршрут, иначе появится ошибка InvalidOperationException. Я внес некоторые изменения в свой код в соответствии с изменениями вашего ответа, которые вы можете увидеть в обновленном вопросе, но все же 3/6 URL-адресов не работают.
Я внес некоторые изменения в ваш код, пожалуйста, посмотрите мое предлагаемое решение. Теперь маршрутизация настроена так, что каждый метод на контроллере будет формироваться как имя маршрута, а теги [FromRoute]
в ваших параметрах указывают, что они будут исходить из маршрута, указанного под глаголом HTTP.
[Route("api/[controller]/[action]")]
public class TestController : Controller
{
public string A1()
{
return "A1()";
}
[HttpGet]
[Route("{id}")]
public string A2([FromRoute]int id)
{
return $"A2(int {id})";
}
[HttpGet]
[Route("{p1}/{p2}")]
public string A3([FromRoute]string p1,[FromRoute]string p2)
{
return $"A3(string {p1},string {p2})";
}
[HttpGet]
[Route("{id}")]
public string A4([FromRoute]int id)
{
return $"A4(int {id})";
}
[HttpGet("{id}")]
public string A5([FromRoute]int id)
{
return $"A5(int {id})";
}
}
Теперь ваш маршрут должен выглядеть примерно так: http://локальный: 5200/апи/тест/A2/5 в качестве примера.
Для 5 и 6 вам не нужно использовать [FromQuery]
. Для 2 и 3, если вы все еще хотите использовать вместе маршрутизацию строки запроса и атрибута, вам нужно установить для свойства SuppressInferBindingSourcesForParameters
значение true, чтобы отключить правила вывода по умолчанию. См. Вывод исходного параметра привязки.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressInferBindingSourcesForParameters = true;
});
Контроллер веб-API:
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet("")]
public string A1()
{
return "A1()";
}
[HttpGet("A2")]
[HttpGet("A2/{id}")]
public string A2(int id)
{
return $"A2(int {id})";
}
[HttpGet("A3")]
public string A3([FromQuery]string p1, [FromQuery]string p2)
{
return $"A3(string {p1},string {p2})";
}
[HttpGet("A4/{id}")]
public string A4(int id)
{
return $"A4(int {id})";
}
[HttpGet("A5/{id}")]
public string A5(int id)
{
return $"A5(int {id})";
}
}
для # 3 вы говорите маршрутизатору связать {id} в шаблоне маршрута с параметром метода, но затем указываете параметр метода для привязки к [FromQuery]... и кажется, что [FromQuery] выигрывает ( у вас нет ?id=1 в URL №3). URL-адреса № 4 и № 5 не соответствуют пути маршрута (URL-адрес заканчивается на A4/1, но в маршруте указано «A4» ... вы, вероятно, имели в виду Route («A4/{id}»), а затем удалите это [FromQuery] в сигнатуре метода)