Один контроллер с несколькими методами 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с и Guids вы можете указать ожидаемый тип в [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";
        }
}

Вот как я это решил. Надеюсь, это кому-нибудь поможет.

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