Attribute Based Routing/Versioning in ASP.Net WEB API 2.0 - c#

I'm trying to use versioning in Asp.Net Web API.
Following is the structure of the project.
To support versioning I've added Microsoft.AspNet.WebApi.Versioning NuGet package.
Following is the code snippet of WebApiConfig:
public static void Register(HttpConfiguration config)
{
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof(ApiVersionRouteConstraint)
}
};
config.MapHttpAttributeRoutes(constraintResolver);
config.AddApiVersioning();
// Web API configuration and services
// Web API routes
//config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
And below is the code from controller:
[ApiVersion("1.0")]
[RoutePrefix("api/v{version:apiVersion}/employeemanagement")]
public class EmployeeManagementController : ApiController
{
[Route("GetTest")]
[HttpGet]
public string GetTest()
{
return "Hello World";
}
[Route("GetTest2")]
[HttpGet]
public string GetTest2()
{
return "Another Hello World";
}
[Route("saveemployeedata")]
[HttpPost]
public async Task<GenericResponse<int>> SaveEmployeeData(EmployeeData employeeData, ApiVersion apiVersion)
{
//code goes here
}
[Route("updateemployeedata")]
[HttpPost]
public async Task<GenericResponse<int>> UpdateEmployeeData([FromBody]int id, ApiVersion apiVersion)
{
//code goes here
}
}
If I use [FromBody] in UpdateEmployeeData, it gives following error:
{
"Message": "The request is invalid.",
"MessageDetail": "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Threading.Tasks.Task`1[AlphaTest.API.Models.ResponseModels.GenericResponse`1[System.Int32]] UpdateEmployeeData(Int32, Microsoft.Web.Http.ApiVersion)' in 'AlphaTest.API.Controllers.V1.EmployeeManagementController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
Following is the URL & data, I'm passing to generate above error:
http://localhost:53963/api/v1.0/EmployeeManagement/updateemployeedata
If I remove[FromBody] it gives me 404 Not found error.
Please help me understand what I'm doing wrong here, which is causing above mentioned error.

You could use object that contains property called Id as parameter of the action UpdateEmployeeData not directly the int Id, like :
public class Request
{
public int Id { get; set; }
}
The action will be :
[Route("updateemployeedata")]
[HttpPost]
public async Task<GenericResponse<int>> UpdateEmployeeData([FromBody]Request request, ApiVersion apiVersion)
{
//code goes here
}
I hope you find this helpful.

Related

Multiple actions were found that match the request:***

I am trying to call WEBAPI from postman .My AI method is post but when i execute it i get below given error
Multiple actions were found that match the request:***
Below is my Code:
webapi route.config
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Api Default",
routeTemplate: "api/{controller}/{method}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}
}
APIController:
public class MembershipController : ApiController
{
[System.Web.Http.ActionName("Upload")]
public HttpResponseMessage Upload(string employeedetails)
{
`Some Code`
}
[System.Web.Http.ActionName("BulkUpload")]
[System.Web.Http.HttpPost]
public HttpResponseMessage BulkUpload(string employeedetails)
{
`Some Code`
}
[System.Web.Http.ActionName("Transfer")]
public HttpResponseMessage Transfer(string employeedetails)
{
`Some Code`
}
}
I am not getting whats going wrong method has different action name and route config is also fully qualified api url which contain controller method and id as optional parameter.To identify url this should be sufficient but it's not working.
Am i missing anything?
If this is RESTful API, you cannot have three HttpPost, unless you differentiate them by the URL slugs.
Easiest way is to use attribute route. E.g.
public class MembershipController : ApiController
{
// POST api/Membership/data/Upload
[Route("api/Membership/{employeedetails}/Upload")]
[HttpPost]
public HttpResponseMessage Upload(string employeedetails)
{
`Some Code`
}
// POST api/Membership/data/Bulk
[Route("api/Membership/{employeedetails}/Bulk")]
[HttpPost]
public HttpResponseMessage BulkUpload(string employeedetails)
{
`Some Code`
}
// POST api/Membership/data/Transfer
[Route("api/Membership/{employeedetails}/Transfer")]
[HttpPost]
public HttpResponseMessage Transfer(string employeedetails)
{
`Some Code`
}
}
Solution 1:
I have added Route Config in WebApiConfig class
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Membership",
routeTemplate: "api/{controller}/{method}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "Api Default",
routeTemplate: "api/{controller}/{method}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional }
//);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}
}
Solution 2
public class MembershipController : ApiController
{
[System.Web.Http.ActionName("Upload")]
public HttpResponseMessage Upload([FromBody]string employeedetails)
{
`Some Code`
}
[System.Web.Http.HttpRoute("api/membeship/BulkUpload")]
[System.Web.Http.HttpPost]
public HttpResponseMessage BulkUpload(string employeedetails)
{
`Some Code`
}
[System.Web.Http.HttpRoute("api/membeship/Transfer")]
public HttpResponseMessage Transfer([FromBody]string employeedetails)
{
`Some Code`
}
}
If we compare Solution 1 and solution 2 then Solution 1 will work but it need a query string parameter where as sollution 2 will also work for post parameters(FormBody)
I am looking in details what does solution 2 makes a difference. Because when we remove HTTPRoute From solution 2 then it's also need query string parameter only and if we try to pass parameter using post then it get passed as null value. Very soon i will find it out and will share detail analysis on stack overflow.
Happy Coding

How to pass multiple params to WebAPI?

I have the following URL:
http://localhost/api/values/100/some+string+here
In the WebAPI app ValuesController, I have this:
[HttpGet]
[Route("api/values/{p1}/{p2}")]
public HttpResponseMessage Get (string p1, string p2) {
...
}
For the caller, it never hits the web api. Instead, it comes back with a 404.
Any idea what is wrong?
You are using Attribute Routing in ASP.NET Web API 2. Make sure you configure your web api to use Attribute routing with MapHttpAttributeRoutes.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Next make sure you defined your controller properly
public class ValuesController : ApiController {
//GET api/values/100/some-string-here
[HttpGet]
[Route("api/values/{p1}/{p2}")]
public HttpResponseMessage Get (string p1, string p2) {
...
}
}
You could even use RoutePrefix
[RoutePrefix("api/values")]
public class ValuesController : ApiController {
//GET api/values/100/some-string-here
[HttpGet]
[Route("{p1}/{p2}")]
public HttpResponseMessage Get (string p1, string p2) {
...
}
}
Also if like in your example you want the first parameter to be an integer. then you can use a route constraint and update method.
[RoutePrefix("api/values")]
public class ValuesController : ApiController {
//GET api/values/100/some-string-here
[HttpGet]
[Route("{p1:int}/{p2}")]
public HttpResponseMessage Get (int p1, string p2) {
...
}
}
UPDATE:
Created an integration test for the Values Controller and was able to confirm that the action was called
[TestMethod]
public async Task HttpClient_Should_Get_OKStatus_From_Action_With_Multiple_Parameters() {
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
using (var server = new HttpServer(config)) {
var client = new HttpClient(server);
string url = "http://localhost/api/values/100/some+string+here";
using (var response = await client.GetAsync(url)) {
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
}

WebApi hard coded controller routing

I am trying to write a self hosted WebAPI server. I want all routes to go to a single controller. This controller can pick out the controller part of the url and use this to decide an appropriate response.
I have the following route configuration:
_configuration.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: string.Concat("api/Home", "/{id}"),
defaults: new { id = RouteParameter.Optional, controllerName="Home" }
);
My controller class is called "HomeController". I'm trying to redirect all URLs to it.
Here is the code for HomeController. For now I have commented out the calls to external logic (remote controller). It should just be returning a string on the Get action.
public class HomeController : ApiController
{
private IExternalController<string> remoteController;
public HomeController()
{
remoteController = GlobalKernelConfiguration.GetStandardKernel().Get<IExternalController<string>>();
}
public string Get()
{
return "HELLO FROM INTERNAL"; //remoteController.Get();
}
public string Get(int id)
{
return remoteController.Get(id);
}
public void Delete(int id)
{
remoteController.Delete(id);
}
public void Post(string value)
{
remoteController.Post(value);
}
public void Put(int id, string value)
{
remoteController.Put(id, value);
}
}
I would expect http://localhost:9000/api/[AnythingHere] to route to the home controller but I get the following error when trying the following url: http://localhost:9000/api/Home
{"Message":"No HTTP resource was found that matches the request URI 'http://loca
lhost:9000/api/Home'.","MessageDetail":"No route providing a controller name was
found to match request URI 'http://localhost:9000/api/Home'"}
As #CodeCaster suggested in the comments the problem was caused by not using the correct parameter in the routing options.
This is what I had before
_configuration.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: string.Concat("api/Home", "/{id}"),
defaults: new { id = RouteParameter.Optional, controllerName="Home" }
);
this is what I have now:
public static void AddControllerRoute(string controllerName)
{
_configuration.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: string.Concat("api/Home", "/{id}"),
defaults: new { id = RouteParameter.Optional, controller ="Home" }
);
}
notice that the defaults parameter was changed and now uses "controller" instead of "controllerName" this solved the problem and it's now working.

Overlapping routes with WebApi

I'm trying to add a special route to the default WebApi sample:
The regular ones are
/api/values (retrieves all values)
/api/values/{id} (retrieves a specific value (by numeric id))
Now I want to add this api:
/api/values/special
The previous api (/api/values/{id}) should serve all requests with a numeric id, but /api/values/special should serve requests that call that specific url.
So far I got this for routing:
config.Routes.MapHttpRoute("SpecialValues", "api/values/special", new { controller = "values", action = "SpecialValues" });
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And this for implementation:
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
// GET api/values/special
public IEnumerable<string> SpecialValues()
{
return new string[] { "special1", "special2" };
}
}
But it will render: The requested resource does not support http method 'GET'.
If I call /api/values/special and I add [HttpGet] to the SpecialValues method it will work
but /api/values will stop working saying that there are multiple matching actions.
The changes to WebApiConfig is not needed. Attach a Route attribute and a HttpGet attribute. You can read more about it here.
[HttpGet]
[Route("api/{controller}/special")]
public IEnumerable<string> SpecialValues()
{
return new string[] { "special1", "special2" };
}
WebApiConfig -
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Attribute routing.
config.MapHttpAttributeRoutes();
// Convention-based routing.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
yes, as mentioned above, attribute based routing is the only way to go here...
this post may also interests you
Overload web api action method based on parameter type

Single controller with multiple GET methods in ASP.NET Web API

In Web API I had a class of similar structure:
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) { ... }
}
Since we could map individual methods, it was very simple to get the right request at the right place. For similar class which had only a single GET method but also had an Object parameter, I successfully used IActionValueBinder. However, in the case described above I get the following error:
Multiple actions were found that match the request:
SomeValue GetItems(CustomParam parameter) on type SomeType
SomeValue GetChildItems(CustomParam parameter, SomeObject parent) on type SomeType
I am trying to approach this problem by overriding the ExecuteAsync method of ApiController but with no luck so far. Any advice on this issue?
Edit: I forgot to mention that now I am trying to move this code on ASP.NET Web API which has a different approach to routing. The question is, how do I make the code work on ASP.NET Web API?
This is the best way I have found to support extra GET methods and support the normal REST methods as well. Add the following routes to your 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)});
I verified this solution with the test class below. I was able to successfully hit each method in my controller below:
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)
{
}
}
I verified that it supports the following requests:
GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1
Note That if your extra GET actions do not begin with 'Get' you may want to add an HttpGet attribute to the method.
Go from this:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
To this:
config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
Hence, you can now specify which action (method) you want to send your HTTP request to.
posting to "http://localhost:8383/api/Command/PostCreateUser" invokes:
public bool PostCreateUser(CreateUserCommand command)
{
//* ... *//
return true;
}
and posting to "http://localhost:8383/api/Command/PostMakeBooking" invokes:
public bool PostMakeBooking(MakeBookingCommand command)
{
//* ... *//
return true;
}
I tried this in a self hosted WEB API service application and it works like a charm :)
I find attributes to be cleaner to use than manually adding them via code. Here is a simple example.
[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);
}
}
You also need this in your 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 }
);
Some Good Links
http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api
This one explains routing better.
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
In VS 2019, this works with ease:
[Route("api/[controller]/[action]")] //above the controller class
And in the code:
[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();
}
You can have multiple gets like above mentioned.
You need to define further routes in global.asax.cs like this:
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 }
);
With the newer Web Api 2 it has become easier to have multiple get methods.
If the parameter passed to the GET methods are different enough for the attribute routing system to distinguish their types as is the case with ints and Guids you can specify the expected type in the [Route...] attribute
For example -
[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}";
}
}
For more details about this approach, see here http://nodogmablog.bryanhogan.net/2017/02/web-api-2-controller-with-multiple-get-methods-part-2/
Another options is to give the GET methods different routes.
[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";
}
}
See here for more details - http://nodogmablog.bryanhogan.net/2016/10/web-api-2-controller-with-multiple-get-methods/
In ASP.NET Core 2.0 you can add Route attribute to the controller:
[Route("api/[controller]/[action]")]
public class SomeController : Controller
{
public SomeValue GetItems(CustomParam parameter) { ... }
public SomeValue GetChildItems(CustomParam parameter, SomeObject parent) { ... }
}
The lazy/hurry alternative (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;}
Calling them :
localhost:5000/api/controllername/method1-42
"hello42"
localhost:5000/api/controllername/method2-99
"world99"
I was trying to use Web Api 2 attribute routing to allow for multiple Get methods, and I had incorporated the helpful suggestions from previous answers, but in the Controller I had only decorated the "special" method (example):
[Route( "special/{id}" )]
public IHttpActionResult GetSomethingSpecial( string id ) {
...without also also placing a [RoutePrefix] at the top of the Controller:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
I was getting errors stating that no Route was found matching the submitted URI. Once I had both the [Route] decorating the method as well as [RoutePrefix] decorating the Controller as a whole, it worked.
By default [Route("api/[controller]") will generated by .Net Core/Asp.Net Web API.You need to modify little bit,just add [Action] like [Route("api/[controller]/[action]")].
I have mentioned a dummy solution:
// 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");
}
}
I am not sure if u have found the answer, but I did this and it works
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";
}
Now in 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 }
);
Have you tried switching over to WebInvokeAttribute and setting the Method to "GET"?
I believe I had a similar problem and switched to explicitly telling which Method (GET/PUT/POST/DELETE) is expected on most, if not all, my methods.
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) { ... }
}
The WebGet should handle it but I've seen it have some issues with multiple Get much less multiple Get of the same return type.
[Edit: none of this is valid with the sunset of WCF WebAPI and the migration to ASP.Net WebAPI on the MVC stack]
**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) { ... }
}
Specifying the base path in the [Route] attribute and then adding to the base path in the [HttpGet] worked for me. You can try:
[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;
}
//....
Took me a while to figure since I didn't want to use [Route] multiple times.
None of the above examples worked for my personal needs. The below is what I ended up doing.
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;
}
To use the above in your route use:
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) });
What happens is the constraint kind of fakes in the method so that this route will only match the default GET, POST, PUT and DELETE methods. The "true" there says we want to check for a match of the items in array. If it were false you'd be saying exclude those in the strYou can then use routes above this default method like:
config.Routes.MapHttpRoute("GetStatus", "{controller}/status/{status}", new { action = "GetStatus" });
In the above it is essentially looking for the following URL => http://www.domain.com/Account/Status/Active or something like that.
Beyond the above I'm not sure I'd get too crazy. At the end of the day it should be per resource. But I do see a need to map friendly urls for various reasons. I'm feeling pretty certain as Web Api evolves there will be some sort of provision. If time I'll build a more permanent solution and post.
Couldn't make any of the above routing solutions work -- some of the syntax seems to have changed and I'm still new to MVC -- in a pinch though I put together this really awful (and simple) hack which will get me by for now -- note, this replaces the "public MyObject GetMyObjects(long id)" method -- we change "id"'s type to a string, and change the return type to 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;
}
If you have multiple Action within same file then pass the same argument e.g. Id to all Action. This is because action only can identify Id, So instead of giving any name to argument only declare Id like this.
[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 }
);
Simple Alternative
Just use a query string.
Routing
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Controller
public class TestController : ApiController
{
public IEnumerable<SomeViewModel> Get()
{
}
public SomeViewModel GetById(int objectId)
{
}
}
Requests
GET /Test
GET /Test?objectId=1
Note
Keep in mind that the query string param should not be "id" or whatever the parameter is in the configured route.
The concept of multiple methods in a single asp.net web api controller makes it easier to have more than 1 method in code.
I was able to implement following the steps in the above solutions and came up with this final code
In the WebApiConfig.cs ,set up the following Route config, in this order
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();
}
}
Then in your controller reference the [HttpGet] for GET or [HttpPost] for POST with [ActionName] see sample code below
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: "
}
}
}
you can then consume the different methods via client or postman using the format
http://url/api/controller/actionname
Modify the WebApiConfig and add at the end another Routes.MapHttpRoute like this:
config.Routes.MapHttpRoute(
name: "ServiceApi",
routeTemplate: "api/Service/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then create a controller like this:
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";
}
}
This is how I solved it. I hope it will help someone.

Categories

Resources