Один контроллер с несколькими методами GET в ASP.NET Web API
В Web API у меня был класс схожей структуры:
public class SomeController : ApiController
{
[WebGet(UriTemplate = "{itemSource}/Items")]
public SomeValue GetItems(CustomParam parameter) { ... }
[WebGet(UriTemplate = "{itemSource}/Items/{parent}")]
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
Поскольку мы могли отображать отдельные методы, было очень просто получить правильный запрос в нужном месте. Для аналогичного класса, который был только один GET
метод, но также имел Object
параметр, который я успешно использовал IActionValueBinder
, Однако в описанном выше случае я получаю следующую ошибку:
Multiple actions were found that match the request:
SomeValue GetItems(CustomParam parameter) on type SomeType
SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType
Я пытаюсь подойти к этой проблеме, отвергая ExecuteAsync
метод ApiController
но безуспешно. Любой совет по этому вопросу?
Изменить: я забыл упомянуть, что теперь я пытаюсь переместить этот код в ASP.NET Web API, который имеет другой подход к маршрутизации. Вопрос в том, как заставить код работать на ASP.NET Web API?
20 ответов
Это лучший способ, который я нашел для поддержки дополнительных методов GET и поддержки обычных методов REST. Добавьте следующие маршруты в ваш WebApiConfig:
routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});
Я проверил это решение с тестовым классом ниже. Мне удалось ударить каждый метод в моем контроллере ниже:
public class TestController : ApiController
{
public string Get()
{
return string.Empty;
}
public string Get(int id)
{
return string.Empty;
}
public string GetAll()
{
return string.Empty;
}
public void Post([FromBody]string value)
{
}
public void Put(int id, [FromBody]string value)
{
}
public void Delete(int id)
{
}
}
Я проверил, что он поддерживает следующие запросы:
GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1
Обратите внимание: если ваши дополнительные действия GET не начинаются с "Get", вы можете добавить в метод атрибут HttpGet.
Перейти от этого:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
К этому:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Следовательно, теперь вы можете указать, в какое действие (метод) вы хотите отправить свой HTTP-запрос.
публикация в "http://localhost:8383/api/Command/PostCreateUser" вызывает:
public bool PostCreateUser(CreateUserCommand command)
{
//* ... *//
return true;
}
и публикация в "http://localhost:8383/api/Command/PostMakeBooking" вызывает:
public bool PostMakeBooking(MakeBookingCommand command)
{
//* ... *//
return true;
}
Я попробовал это в приложении-сервисе WEB API, работающем самостоятельно, и оно работает как шарм:)
Я считаю, что атрибуты удобнее использовать, чем добавлять их вручную с помощью кода. Вот простой пример.
[RoutePrefix("api/example")]
public class ExampleController : ApiController
{
[HttpGet]
[Route("get1/{param1}")] // /api/example/get1/1?param2=4
public IHttpActionResult Get(int param1, int param2)
{
Object example = null;
return Ok(example);
}
}
Вам также нужно это в вашем webapiconfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Некоторые полезные ссылки http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api Этот лучше объясняет маршрутизацию. http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
В VS 2019 это легко работает:
[Route("api/[controller]/[action]")] //above the controller class
И в коде:
[HttpGet]
[ActionName("GetSample1")]
public Ilist<Sample1> GetSample1()
{
return getSample1();
}
[HttpGet]
[ActionName("GetSample2")]
public Ilist<Sample2> GetSample2()
{
return getSample2();
}
[HttpGet]
[ActionName("GetSample3")]
public Ilist<Sample3> GetSample3()
{
return getSample3();
}
[HttpGet]
[ActionName("GetSample4")]
public Ilist<Sample4> GetSample4()
{
return getSample4();
}
Вы можете иметь несколько приемов, как указано выше.
Вам нужно определить дальнейшие маршруты в global.asax.cs следующим образом:
routes.MapHttpRoute(
name: "Api with action",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
С новым Web Api 2 стало проще иметь несколько методов get.
Если параметр передан GET
методы достаточно различны для системы маршрутизации атрибутов, чтобы различать их типы, как в случае с int
с и Guid
s вы можете указать ожидаемый тип в [Route...]
атрибут
Например -
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
// GET api/values/7
[Route("{id:int}")]
public string Get(int id)
{
return $"You entered an int - {id}";
}
// GET api/values/AAC1FB7B-978B-4C39-A90D-271A031BFE5D
[Route("{id:Guid}")]
public string Get(Guid id)
{
return $"You entered a GUID - {id}";
}
}
Для получения более подробной информации об этом подходе см. Здесь http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/
Другой вариант, чтобы дать GET
Методы разных маршрутов.
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
public string Get()
{
return "simple get";
}
[Route("geta")]
public string GetA()
{
return "A";
}
[Route("getb")]
public string GetB()
{
return "B";
}
}
Смотрите здесь для получения более подробной информации - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/
В ASP.NET Core 2.0 вы можете добавить атрибут Route к контроллеру:
[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
public SomeValue GetItems(CustomParam parameter) { ... }
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
Альтернатива ленивым / торопливым (Dotnet Core 2.2):
[HttpGet("method1-{item}")]
public string Method1(var item) {
return "hello" + item;}
[HttpGet("method2-{item}")]
public string Method2(var item) {
return "world" + item;}
Называя их:
локальный:5000 / API / имя контроллера / метод1-42
"привет42"
локальный:5000/api/ имя контроллера / метод2-99
"world99"
По умолчанию [Route("api/[controller]") будет сгенерирован.Net Core/Asp.Net Web API. Вам нужно немного изменить, просто добавьте [Action], например [Route("api/[controller]/[ действие]")]. Я упомянул фиктивное решение:
// Default generated controller
//
[Route("api/[controller]")
public class myApiController : Controller
{
[HttpGet]
public string GetInfo()
{
return "Information";
}
}
//
//A little change would do the magic
//
[Route("api/[controller]/[action]")]
public class ServicesController : Controller
{
[HttpGet]
[ActionName("Get01")]
public string Get01()
{
return "GET 1";
}
[HttpGet]
[ActionName("Get02")]
public string Get02()
{
return "Get 2";
}
[HttpPost]
[ActionName("Post01")]
public HttpResponseMessage Post01(MyCustomModel01 model)
{
if (!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
//.. DO Something ..
return Request.CreateResonse(HttpStatusCode.OK, "Optional Message");
}
[HttpPost]
[ActionName("Post02")]
public HttpResponseMessage Post02(MyCustomModel02 model)
{
if (!ModelState.IsValid)
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
//.. DO Something ..
return Request.CreateResonse(HttpStatusCode.OK, "Optional Message");
}
}
Я пытался использовать маршрутизацию атрибутов Web Api 2, чтобы учесть несколько методов Get, и я включил полезные предложения из предыдущих ответов, но в Controller я только украсил "специальный" метод (пример):
[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {
... также также не помещая [RoutePrefix] в верхней части контроллера:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
Я получаю ошибки, утверждающие, что не найдено ни одного маршрута, соответствующего представленному URI. Как только у меня было [Route], украшающее метод, так и [RoutePrefix], украшающее Контроллер в целом, это работало.
Вы пытались переключиться на WebInvokeAttribute и установить метод для "GET"?
Я считаю, что у меня была похожая проблема, и я переключился на явное указание того, какой метод (GET/PUT/POST/DELETE) ожидается для большинства, если не для всех, моих методов.
public class SomeController : ApiController
{
[WebInvoke(UriTemplate = "{itemSource}/Items"), Method="GET"]
public SomeValue GetItems(CustomParam parameter) { ... }
[WebInvoke(UriTemplate = "{itemSource}/Items/{parent}", Method = "GET")]
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
WebGet должен справиться с этим, но я видел, что у него есть некоторые проблемы с множественным Get, намного меньше множественным Get одного и того же типа возврата.
[Редактировать: все это недопустимо в случае заката WCF WebAPI и перехода на ASP.Net WebAPI в стеке MVC]
Я не уверен, что вы нашли ответ, но я сделал это, и это работает
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET /api/values/5
public string Get(int id)
{
return "value";
}
// GET /api/values/5
[HttpGet]
public string GetByFamily()
{
return "Family value";
}
Теперь в global.asx
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi2",
routeTemplate: "api/{controller}/{action}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
**Add Route function to direct the routine what you want**
public class SomeController : ApiController
{
[HttpGet()]
[Route("GetItems")]
public SomeValue GetItems(CustomParam parameter) { ... }
[HttpGet()]
[Route("GetChildItems")]
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
Указание базового пути в [Route]
атрибут, а затем добавив к базовому пути в [HttpGet]
работал у меня. Можешь попробовать:
[Route("api/TestApi")] //this will be the base path
public class TestController : ApiController
{
[HttpGet] //example call: 'api/TestApi'
public string Get()
{
return string.Empty;
}
[HttpGet("{id}")] //example call: 'api/TestApi/4'
public string GetById(int id) //method name won't matter
{
return string.Empty;
}
//....
Мне потребовалось время, чтобы понять, так как я не хотел использовать [Route]
много раз.
Простая альтернатива
Просто используйте строку запроса.
маршрутизация
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
контроллер
public class TestController : ApiController
{
public IEnumerable<SomeViewModel> Get()
{
}
public SomeViewModel GetById(int objectId)
{
}
}
Запросы
GET /Test
GET /Test?objectId=1
Заметка
Имейте в виду, что параметр строки запроса не должен быть "id" или каким-либо другим параметром в настроенном маршруте.
Ни один из приведенных выше примеров не помог мне. Ниже то, что я в итоге сделал.
public class ContainsConstraint : IHttpRouteConstraint
{
public string[] array { get; set; }
public bool match { get; set; }
/// <summary>
/// Check if param contains any of values listed in array.
/// </summary>
/// <param name="param">The param to test.</param>
/// <param name="array">The items to compare against.</param>
/// <param name="match">Whether we are matching or NOT matching.</param>
public ContainsConstraint(string[] array, bool match)
{
this.array = array;
this.match = match;
}
public bool Match(System.Net.Http.HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (values == null) // shouldn't ever hit this.
return true;
if (!values.ContainsKey(parameterName)) // make sure the parameter is there.
return true;
if (string.IsNullOrEmpty(values[parameterName].ToString())) // if the param key is empty in this case "action" add the method so it doesn't hit other methods like "GetStatus"
values[parameterName] = request.Method.ToString();
bool contains = array.Contains(values[parameterName]); // this is an extension but all we are doing here is check if string array contains value you can create exten like this or use LINQ or whatever u like.
if (contains == match) // checking if we want it to match or we don't want it to match
return true;
return false;
}
Для использования вышеупомянутого в вашем маршруте используйте:
config.Routes.MapHttpRoute("Default", "{controller}/{action}/{id}", new { action = RouteParameter.Optional, id = RouteParameter.Optional}, new { action = new ContainsConstraint( new string[] { "GET", "PUT", "DELETE", "POST" }, true) });
То, что происходит, - это подделка типа метода в методе, так что этот маршрут будет соответствовать только методам GET, POST, PUT и DELETE по умолчанию. "Истина" говорит, что мы хотим проверить соответствие элементов в массиве. Если бы это было ложно, вы бы сказали исключить тех из str. Затем вы можете использовать маршруты выше этого метода по умолчанию, например:
config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });
В приведенном выше это по существу ищет следующий URL => http://www.domain.com/Account/Status/Active
или что-то типа того.
Помимо вышесказанного, я не уверен, что стану слишком сумасшедшим. В конце дня это должно быть за ресурс. Но я вижу необходимость отображать дружественные URL по разным причинам. Я чувствую себя довольно уверенно, так как Web Api развивается, будет какое-то обеспечение. Если будет время, я построю более постоянное решение и выложу.
Не удалось заставить работать ни одно из вышеперечисленных решений по маршрутизации - кажется, что некоторые синтаксис изменился, и я все еще новичок в MVC - в крайнем случае, хотя я собрал этот действительно ужасный (и простой) хак, который заставит меня на данный момент - обратите внимание, это заменяет метод "public MyObject GetMyObjects(long id)" - мы меняем тип "id" на строку и меняем тип возвращаемого значения на object.
// GET api/MyObjects/5
// GET api/MyObjects/function
public object GetMyObjects(string id)
{
id = (id ?? "").Trim();
// Check to see if "id" is equal to a "command" we support
// and return alternate data.
if (string.Equals(id, "count", StringComparison.OrdinalIgnoreCase))
{
return db.MyObjects.LongCount();
}
// We now return you back to your regularly scheduled
// web service handler (more or less)
var myObject = db.MyObjects.Find(long.Parse(id));
if (myObject == null)
{
throw new HttpResponseException
(
Request.CreateResponse(HttpStatusCode.NotFound)
);
}
return myObject;
}
Если у вас есть несколько действий в одном файле, передайте один и тот же аргумент, например, Id для всех действий. Это потому, что действие только может идентифицировать Id, поэтому вместо присвоения имени аргументу только объявите Id следующим образом.
[httpget]
[ActionName("firstAction")] firstAction(string Id)
{.....
.....
}
[httpget]
[ActionName("secondAction")] secondAction(Int Id)
{.....
.....
}
//Now go to webroute.config file under App-start folder and add following
routes.MapHttpRoute(
name: "firstAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "secondAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Концепция нескольких методов в одном контроллере веб-API asp.net упрощает использование более одного метода в коде.
Мне удалось выполнить шаги, описанные в приведенных выше решениях, и я придумал этот окончательный код.
В WebApiConfig.cs настройте следующую конфигурацию маршрута в указанном порядке.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.Routes.MapHttpRoute(
name: "DefaultApiAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MapHttpAttributeRoutes();
}
}
Затем в вашем контроллере укажите [HttpGet] для GET или [HttpPost] для POST с [ActionName], см. пример кода ниже.
namespace WebRESTApi.Controllers
{
//[RoutePrefix("api/Test")]
public class TestController : ApiController
{
[HttpGet]
[ActionName("AllEmailWithDisplayname")]
public string AllEmailWithDisplayname()
{
return "values";
}
[HttpPost]
[ActionName("Authenticate")]
// POST: api/Authenticate
public object Authenticate([FromBody()] object Loginvalues)
{
return true;
}
[HttpPost]
[ActionName("ShowCredential")]
// POST: api/Showcredential
public object Showcredential([FromBody()] object Loginvalues)
{
return "Username: "
}
}
}
затем вы можете использовать различные методы через клиента или почтальона, используя формат
http://url/api/controller/имя действия
Измените WebApiConfig и добавьте в конце еще один Routes.MapHttpRoute следующим образом:
config.Routes.MapHttpRoute(
name: "ServiceApi",
routeTemplate: "api/Service/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Затем создайте контроллер следующим образом:
public class ServiceController : ApiController
{
[HttpGet]
public string Get(int id)
{
return "object of id id";
}
[HttpGet]
public IQueryable<DropDownModel> DropDowEmpresa()
{
return db.Empresa.Where(x => x.Activo == true).Select(y =>
new DropDownModel
{
Id = y.Id,
Value = y.Nombre,
});
}
[HttpGet]
public IQueryable<DropDownModel> DropDowTipoContacto()
{
return db.TipoContacto.Select(y =>
new DropDownModel
{
Id = y.Id,
Value = y.Nombre,
});
}
[HttpGet]
public string FindProductsByName()
{
return "FindProductsByName";
}
}
Вот как я это решил. Надеюсь, это кому-нибудь поможет.