Пользовательские имена методов в веб-интерфейсе ASP.NET

Я перехожу из веб-API WCF в новый веб-API ASP.NET MVC 4. У меня есть UserController, и я хочу иметь метод с именем Authenticate. Я вижу примеры того, как делать GetAll, GetOne, Post и Delete, однако что, если я хочу добавить дополнительные методы в эти сервисы? Например, у моего UserService должен быть метод под названием Authenticate, где они передают имя пользователя и пароль, однако это не работает.

public class UsersController : BaseApiController
{
    public string GetAll()
    {
        return "getall!";
    }

    public string Get(int id)
    {
        return "get 1! " + id;
    }

    public User GetAuthenticate(string userName, string password, string applicationName)
    {
        LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
            userName, password, applicationName));

        //check if valid leapfrog login.
        var decodedUsername = userName.Replace("%40", "@");
        var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
        var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);

        if (leapFrogUsers.Count > 0)
        {
            return new User
            {
                Id = (uint)leapFrogUsers[0].Id,
                Guid = leapFrogUsers[0].Guid
            };
        }
        else
            throw new HttpResponseException("Invalid login credentials");
    }
}

Я могу перейти к myapi/api/users/, и он будет вызывать GetAll, и я могу перейти к myapi/api/users/1, и он вызовет Get, однако, если я вызову myapi/api/users/authenticate? username = {0} & password = {1}, то он вызовет Get (NOT Authenticate) и ошибку:

Словарь параметров содержит нулевую запись для параметра 'id' типа, отличного от nullable 'System.Int32' для метода 'System.String Get (Int32)' в 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController', Необязательный параметр должен быть ссылочным типом, нулевым типом или быть объявлен как необязательный параметр.

Как я могу вызвать пользовательские имена методов, такие как Authenticate?

Ответ 1

По умолчанию конфигурация маршрута следует за соглашениями RESTFul, что означает, что он будет принимать только имена Get, Post, Put и Delete (посмотрите на маршрут в global.asax = > по умолчанию, он не позволяет вам указывать какие-либо действия name = > он использует HTTP-глагол для отправки). Поэтому, когда вы отправляете запрос GET на /api/users/authenticate, вы в основном вызываете действие Get(int id) и передаете id=authenticate, который явно падает, потому что ваше действие Get ожидает целое число.

Если вы хотите иметь разные имена действий, чем стандартные, вы можете изменить определение маршрута в global.asax:

Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { action = "get", id = RouteParameter.Optional }
);

Теперь вы можете перейти к /api/values/getauthenticate для аутентификации пользователя.

Ответ 2

Это лучший метод, с которым я до сих пор пришел, чтобы включить дополнительные методы 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.

Ответ 3

Я дни в мире MVC4.

Для чего он стоит, у меня есть SiteAPIController, и мне нужен особый метод, который можно вызвать как:

http://localhost:9000/api/SitesAPI/Disposition/0

С разными значениями для последнего параметра, чтобы получить запись с разными настройками.

В итоге я работал у меня:

Метод в SiteAPIController:

// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
    Site site = db.Sites.Where(s => s.Disposition == disposition).First();
    return site;
}

И это в WebApiConfig.cs

// this was already there
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

// this i added
config.Routes.MapHttpRoute(
    name: "Action",
    routeTemplate: "api/{controller}/{action}/{disposition}"
 );

До тех пор, пока я называет {disposition} как {id}, я встречал:

{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}

Когда я переименовал его в {disposition}, он начал работать. Таким образом, по-видимому, имя параметра совпадает со значением в заполнителе.

Не стесняйтесь редактировать этот ответ, чтобы сделать его более точным/пояснительным.

Ответ 4

Web Api по умолчанию ожидает URL в форме api/{controller}/{id}, чтобы переопределить эту маршрутизацию по умолчанию. вы можете настроить маршрутизацию любым из двух способов.

Первый вариант:

Добавьте ниже регистрацию маршрута в WebApiConfig.cs

config.Routes.MapHttpRoute(
    name: "CustomApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

Украсьте свой метод действий с помощью HttpGet и параметров ниже

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

 {

// your code here

}

для вызова вышеуказанного метода url будет как ниже

http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1&param2=value2&param3=value3

Второй вариант Добавьте префикс маршрута в класс Controller и Украсьте свой метод действий с помощью HttpGet, как показано ниже. В этом случае не нужно менять любой WebApiConfig.cs. Он может иметь маршрутизацию по умолчанию.

[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{

[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
                        string param2, string param3)

{

// your code here

}

}

для вызова вышеуказанного метода url будет как ниже

http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1&param2=value2&param3=value3

Ответ 6

Если вы используете ASP.NET 5 с ASP.NET MVC 6, большинство из этих ответов просто не будут работать, потому что вы обычно позволяете MVC создайте для вас соответствующую коллекцию маршрутов (используя стандартные соглашения RESTful), что означает, что вы не найдете никакого вызова Routes.MapRoute() для редактирования по желанию.

Метод ConfigureServices(), вызываемый файлом Startup.cs, будет регистрировать MVC с помощью структуры Injection Dependency, встроенной в ASP.NET 5: таким образом, когда вы вызываете ApplicationBuilder.UseMvc() позже в этом классе, среда MVC автоматически добавит эти маршруты по умолчанию для вашего приложения. Мы можем посмотреть, что происходит за капотом, посмотрев на реализацию метода UseMvc() в исходном коде рамки:

public static IApplicationBuilder UseMvc(
    [NotNull] this IApplicationBuilder app,
    [NotNull] Action<IRouteBuilder> configureRoutes)
{
    // Verify if AddMvc was done before calling UseMvc
    // We use the MvcMarkerService to make sure if all the services were added.
    MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);

    var routes = new RouteBuilder
    {
        DefaultHandler = new MvcRouteHandler(),
        ServiceProvider = app.ApplicationServices
    };

    configureRoutes(routes);

    // Adding the attribute route comes after running the user-code because
    // we want to respect any changes to the DefaultHandler.
    routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
        routes.DefaultHandler,
        app.ApplicationServices));

    return app.UseRouter(routes.Build());
}

Хорошо, что структура теперь обрабатывает всю сложную работу, повторяя все действия Controller и настраивая маршруты по умолчанию, тем самым сохраняя вам некоторую избыточную работу.

Плохо, там мало или вообще нет документации о том, как вы могли бы добавить свои собственные маршруты. К счастью, вы можете легко сделать это, используя подход на основе Конвенции и/или на основе атрибутов (aka Attribute Routing).

Конвенция-Based

В вашем классе Startup.cs замените это:

app.UseMvc();

с этим:

app.UseMvc(routes =>
            {
                // Route Sample A
                routes.MapRoute(
                    name: "RouteSampleA",
                    template: "MyOwnGet",
                    defaults: new { controller = "Items", action = "Get" }
                );
                // Route Sample B
                routes.MapRoute(
                    name: "RouteSampleB",
                    template: "MyOwnPost",
                    defaults: new { controller = "Items", action = "Post" }
                );
            });

основанный на атрибуты

В MVC6 большое значение имеет то, что вы также можете определять маршруты на основе каждого контроллера, украшая класс Controller и/или Action с помощью соответствующих RouteAttribute и/или HttpGet/HttpPost параметры шаблона, такие как:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;

namespace MyNamespace.Controllers
{
    [Route("api/[controller]")]
    public class ItemsController : Controller
    {
        // GET: api/items
        [HttpGet()]
        public IEnumerable<string> Get()
        {
            return GetLatestItems();
        }

        // GET: api/items/5
        [HttpGet("{num}")]
        public IEnumerable<string> Get(int num)
        {
            return GetLatestItems(5);
        }       

        // GET: api/items/GetLatestItems
        [HttpGet("GetLatestItems")]
        public IEnumerable<string> GetLatestItems()
        {
            return GetLatestItems(5);
        }

        // GET api/items/GetLatestItems/5
        [HttpGet("GetLatestItems/{num}")]
        public IEnumerable<string> GetLatestItems(int num)
        {
            return new string[] { "test", "test2" };
        }

        // POST: /api/items/PostSomething
        [HttpPost("PostSomething")]
        public IActionResult Post([FromBody]string someData)
        {
            return Content("OK, got it!");
        }
    }
}

Этот контроллер будет обрабатывать следующие запросы:

 [GET] api/items
 [GET] api/items/5
 [GET] api/items/GetLatestItems
 [GET] api/items/GetLatestItems/5
 [POST] api/items/PostSomething

Также обратите внимание, что если вы используете два подхода togheter, маршруты, основанные на атрибутах (если они определены), будут отменять действия, основанные на Конвенции, и оба они переопределяют маршруты по умолчанию, определенные UseMvc().

Для получения дополнительной информации вы также можете прочитать следующее сообщение.