Passing Complex Objects to ASP.NET Web API Using FromUri - c#

I want to bind the URL parameters to my Point object using attribute routing and the [FromUri] attribute such that the following URL is possible:
/foo-1,2
public IHttpActionResult PostFoo(
[FromBody] string content,
[FromUri] Point point)
{
}
public class Point
{
public int A { get; set; }
public int B { get; set; }
// ...Other properties omitted for simplicity
}
I have tried the following Route attributes but none of these work:
[Route("foo-{a},{b}")]
[Route("foo-{A},{B}")]
[Route("foo-{point.A},{point.B}")]
Note that I can't use query string parameters because a badly built third party service does not accept ampersands in their URL's (Yes it's that bad). So I'm trying to build all query string parameters into the URL itself.

The two Options I'm aware of is:
Use URL Rewriter to Globally take care of every and all routes. The advantage is that (I would hope) your publisher does have some type of standard url you can transform into a friendly MVC route.
If not then you'll probably have to write your own RouteHandler. Not sure if you could use this globally, but you'd have to register it a lot (not that hard really).
public class CustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var acceptValue = requestContext.HttpContext.Request.Headers["Accept"];
if( /* do something with the accept value */)
{
// Set the new route value in the
// requestContext.RouteData.Values dictionary
// e.g. requestContext.RouteData.Values["action"] = "Customer";
}
return base.GetHttpHandler(requestContext);
}
}
Then register it:
RouteTable.Routes.MapRoute(
name: "Custom",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
).RouteHandler = new CustomRouteHandler();

Related

Extracting route parameters from MVC 5 route templates

My project is using the following code to generate MVC routed URLs for the given routeName and routeValues:
public class RouteBuilder
{
public string GetUrl(string routeName, object routeValues)
{
return RouteTable.Routes.GetVirtualPath(
new RequestContext(new MockHttpContextBase(string.Empty), new RouteData()),
routeName,
new RouteValueDictionary(routeValues)
)?.VirtualPath;
}
}
For example, given the following route definition:
public MyController : Controller
{
[Route("Foo", "{bar}/{quux}")]
public IActionResult MyControllerMethod(MyModel model) { }
}
we call:
// yields "zod/baz"
var routedUrl = RouteBuilder.GetUrl("Foo", new
{
bar = "zod",
quux = "baz",
});
The problem comes in with the route values object passed to GetUrl. Out of necessity it can be any type of object, including an anonymous one as demonstrated in the example. But this is problematic because if there are any typos in the object's definition, the route template won't match and the URL won't be built:
// yields null!
var routedUrl = RouteBuilder.GetUrl("Foo", new
{
baz = "zod", // oops, typo in 1st param name!
quux = "baz",
});
This is even more of an issue because it can only be caught at runtime.
A potential solution is this:
public class MyControllerMethodRouteParameters
{
string bar { get; set; }
string quux { get; set; }
}
public MyController : Controller
{
[Route("Foo", "{" + nameof(MyControllerMethodRouteParameters.bar) + "}/{" + nameof(MyControllerMethodRouteParameters.quux ) + "}")]
public IActionResult MyControllerMethod(MyModel model) { }
}
public class RouteBuilder
{
private string GetUrl(string routeName, object routeValues) { /* as before */ }
public string GetUrl(MyControllerMethodRouteParameters params)
{
return GetUrl(GetRouteNameFor(params.GetType(), params));
}
}
But it has a large drawback in terms of developer effort: when adding a new controller method, you also have to remember to add a route parameters class, a GetUrl overload for it, and a routename => route parameters type mapping. Easy to forget to do, and it also feels like unnecessary repetition since all of the necessary parameters and their types are already defined in the RouteAttribute.
So my idea was to generate the needed code via a T4 template, by reflecting over the assembly, grabbing all RouteAttributes and pulling the route name and template out of them, then parsing the template for the property names of the route parameters class. It's this last hurdle that I've fallen at, because while I know I can probably write a regex(es) to match and extract the route params from the template, I would prefer to use existing functionality to do this.
Problem is, I can't find anything that seems like it'll do this for me. The closest appears to be the internal class System.Web.Mvc.Routing.InlineRouteTemplateParser but my experiments with it were not particularly fruitful. Is there anything I can or should be using to achieve this, or should I just give in to the dark regex god?

'Multiple actions were found' error with RESTful Web API Routing

I have the following class:
public class GetLogsRequestDto
{
public LogLevel Level { get; set; }
public LogSortOrder SortOrder { get; set; }
}
I have a Web API Controller (LogsController) with the following 2 actions:
async Task<IHttpActionResult> Get( [FromUri]int id )
async Task<IHttpActionResult> Get( [FromUri]GetLogsRequestDto dto )
The first for retrieving a specific log, and the second for retrieving a list of logs. When I make a GET request for a specific log via: /logs/123, it calls the 1st action correctly, and likewise if I make a GET request for /logs it calls the 2nd action correctly (the properties defined in that class are optional and don't need to always be provided).
However, I wanted to change the first GET method so it uses a class instead of the int id parameter, like this (note it's specifying a different (singular) type to the 2nd action above):
async Task<IHttpActionResult> Get( [FromUri]GetLogRequestDto dto )
This GetLogRequestDto class looks like this:
public class GetLogRequestDto
{
[Required]
[Range( 100, int.MaxValue )]
public int Id { get; set; }
}
My reasoning behind this approach was so that I can have validation of the model go through my standard ModelStateValidationActionFilter, and also put any specific validation attributes inside this class, rather than when using the 'int id' parameter approach, then having to perform validation.
When I implement this approach though and attempt to call /logs/1, I get the following error:
Multiple actions were found that match the request
It's not differentiating between the 2 different types used as params in these 2 methods.
The default route I have configured is:
config.Routes.MapHttpRoute(
name: "controller-id",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I can't figure out why there is a problem - why it works one way but not the other.
Using a complex type for handling a single basic type parameter (that is also part of the route) in GET requests is not a great idea.
By using this approach the framework will not be able to bind your route parameter to that complex type (the route definition requires an id parameter that must be a simple type).
I strongly suggest you to revert your changes and make the id parameter again an int.
As an alternative approach you may follow this great post and implement an action filter that may validate your method parameters decorated by validation attributes even if they are simple types.
Here it is an excerpt from Mark Vincze's blog post representing the action filter attribute used to validate action parameters:
public class ValidateActionParametersAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
var parameters = descriptor.MethodInfo.GetParameters();
foreach (var parameter in parameters)
{
var argument = context.ActionArguments[parameter.Name];
EvaluateValidationAttributes(parameter, argument, context.ModelState);
}
}
base.OnActionExecuting(context);
}
private void EvaluateValidationAttributes(ParameterInfo parameter, object argument, ModelStateDictionary modelState)
{
var validationAttributes = parameter.CustomAttributes;
foreach (var attributeData in validationAttributes)
{
var attributeInstance = CustomAttributeExtensions.GetCustomAttribute(parameter, attributeData.AttributeType);
var validationAttribute = attributeInstance as ValidationAttribute;
if (validationAttribute != null)
{
var isValid = validationAttribute.IsValid(argument);
if (!isValid)
{
modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name));
}
}
}
}
}

Nopcommerce RegisterRoutes

Hi I have problem with routes in plugin, in nopcommerce 3.6
I have in folder Controller TestPohodaController.cs contains method ImportProductInfo()
There is my RegisterRoutes:
namespace Nop.Plugin.Test.Pohoda
{
public partial class RouteProvider : IRouteProvider
{
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Plugin.Test.Pohoda.ImportProductInfo",
"Plugins/TestPohoda/ImportProductInfo",
new { controller = "TestPohoda", action = "ImportProductInfo" },
new[] { "Nop.Plugin.Test.Pohoda.Controllers" }
);
}
public int Priority
{
get
{
return 0;
}
}
}
}
Installation to nopCommerce is ok, but when I go to mypage/Plugins/TestPohoda/ImportProductInfo page return 404.
I need url of TestPohodaController to call this controller from economic system. Can You help me please? Thanks.
ASP.NET MVC Routing evaluates routes from top to bottom. So if two routes match, the first one it hits (the one closer to the 'top' of the RegisterRoutes method) will take precedence over the subsequent one.
With that in mind, you need to do two things to fix your problem:
Your default route should be at the bottom.
Your routes need to have constraints on them if they contain the same number of segments:
What's the difference between:
example.com/1
and
example.com/index
To the parser, they contain the same number of segments, and there's no differentiator, so it's going to hit the first route in the list that matches.
To fix that, you should make sure the routes that use ProductIds take constraints:
routes.MapRoute(
"TestRoute",
"{id}",
new { controller = "Product", action = "Index3", id = UrlParameter.Optional },
new { id = #"\d+" } //one or more digits only, no alphabetical characters
);
You don't need to start with Plugins for your route url. it is enough
to follow this pattern {controller}/{Action}/{parameter}
Make sure also namespace for the controller is correct as you define
in the routing. Nop.Plugin.Test.Pohoda.Controllers
You can define an optional productId parameter as well. so it will
work for mypage/TestPohoda/ImportProductInfo or
mypage/TestPohoda/ImportProductInfo/123
You can also set the priority higher than 0 which is priority of the
default routeprovider in the nop.web. this way you ensure that your
plugin reads it first. Indeed it is not necessary as you have
specific url. this is only required if you have similar route url
Try using this route
namespace Nop.Plugin.Test.Pohoda
{
public partial class RouteProvider : IRouteProvider
{
public void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Plugin.Test.Pohoda.ImportProductInfo",
"TestPohoda/ImportProductInfo/{productId}",
new { controller = "TestPohoda", action = "ImportProductInfo" , productId = = UrlParameter.Optional },
new[] { "Nop.Plugin.Test.Pohoda.Controllers" }
);
}
public int Priority
{
get
{
return 1;
}
}
}
}
We will have a look at how to register plugin routes. ASP.NET routing is responsible for mapping incoming browser requests to particular MVC controller actions. You can find more information about routing here. So follow the next steps:
If you need to add some custom route, then create RouteProvider.cs file. It informs the nopCommerce system about plugin routes. For example, the following RouteProvider class adds a new route which can be accessed by opening your web browser and navigating to http://www.yourStore.com/Plugins/PaymentPayPalStandard/PDTHandler URL (used by PayPal plugin):
public partial class RouteProvider : IRouteProvider
{
public void RegisterRoutes(IRouteBuilder routeBuilder)
{
routeBuilder.MapRoute("Plugin.Payments.PayPalStandard.PDTHandler", "Plugins/PaymentPayPalStandard/PDTHandler",
new { controller = "PaymentPayPalStandard", action = "PDTHandler" });
}
public int Priority
{
get
{
return -1;
}
}
}
It could be cache problem, try to restart IIS
actually you do nota have to register route by default you can call your method
/TestPohoda/ImportProductInfo

How to pass/receive multiple args to a RESTful Web API GET method?

The usual examples of GET RESTful methods that take a parameter (returning a scalar value rather than a dataset) are shown like so:
public string Get(int id)
{
//get and return the value
}
...where the val passed is typically an ID, so you can use it to get a scalar value based on that unique value.
What, though, if you want to pass multiple values, such as a string and an int? Is it simply a matter of defining a method like so:
public string Get(string someString, int someInt)
{
//get and return the value
}
...and calling it like so:
//const string uri = "http://192.112.183.42:80/api/platypusItems/someString/someInt";, zB:
const string uri = "http://192.112.183.42:80/api/platypusItems/DuckbilledPlatypisAreGuysToo/42";
var webRequest = (HttpWebRequest) WebRequest.Create(uri);
?
IOW, will the routing mechanism figure out that, since two args are passed, it should call the Get() method with two args ("convention over configuration"), or is there more that has to be done to route things appropriately?
If you use Web API 2, then you can use Attribute Routing to route requests like http://192.112.183.42:80/api/platypusItems/DuckbilledPlatypisAreGuysToo/42
public class ItemsController : ApiController
{
[Route("api/{controller}/{id}")]
public string GetItemById(int id)
{
// Find item here ...
return item.ToString();
}
[Route("api/{controller}/{name}/{id}")]
public string GetItemByNameAndId(string name, int id)
{
// Find item here ...
return item.ToString();
}
}
http://192.112.183.42:80/api/platypusItems/DuckbilledPlatypisAreGuysToo/42 will be mapped to GetItemByNameAndId while http://192.112.183.42:80/api/platypusItems/42 will be mapped to GetItemById.
Note, that you need to enable attribute routing in configuration like this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
But generally you should pass arguments as additional parameters. It is especially easy with GET requests. This will work in Web API 1&2:
public class ItemsController : ApiController
{
public string GetItemById(int id)
{
// Find item here ...
return item.ToString();
}
public string GetItemByNameAndId(string name, int id)
{
// Find item here ...
return item.ToString();
}
}
Assuming that you have default mapping configuration, http://192.112.183.42:80/api/platypusItems/42 will be mapped to GetItemById while http://192.112.183.42:80/api/platypusItems/42?name=DuckbilledPlatypisAreGuysToo will be mapped to GetItemByNameAndId because Web API can map 2 parameters instead of 1 for GetItemById.
More information can be found in Mike Wasson articles on Attribute Routing, Routing and Action Selection and Routing in Web API.

Custom method names in ASP.NET Web API

I'm converting from the WCF Web API to the new ASP.NET MVC 4 Web API. I have a UsersController, and I want to have a method named Authenticate. I see examples of how to do GetAll, GetOne, Post, and Delete, however what if I want to add extra methods into these services? For instance, my UsersService should have a method called Authenticate where they pass in a username and password, however it doesn't work.
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");
}
}
I can browse to myapi/api/users/ and it will call GetAll and I can browse to myapi/api/users/1 and it will call Get, however if I call myapi/api/users/authenticate?username={0}&password={1} then it will call Get (NOT Authenticate) and error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
How can I call custom method names such as Authenticate?
By default the route configuration follows RESTFul conventions meaning that it will accept only the Get, Post, Put and Delete action names (look at the route in global.asax => by default it doesn't allow you to specify any action name => it uses the HTTP verb to dispatch). So when you send a GET request to /api/users/authenticate you are basically calling the Get(int id) action and passing id=authenticate which obviously crashes because your Get action expects an integer.
If you want to have different action names than the standard ones you could modify your route definition in global.asax:
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
Now you can navigate to /api/users/getauthenticate to authenticate the user.
This is the best method I have come up with so far to incorporate extra GET methods while supporting 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.
I am days into the MVC4 world.
For what its worth, I have a SitesAPIController, and I needed a custom method, that could be called like:
http://localhost:9000/api/SitesAPI/Disposition/0
With different values for the last parameter to get record with different dispositions.
What Finally worked for me was:
The method in the SitesAPIController:
// 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;
}
And this in the 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}"
);
For as long as I was naming the {disposition} as {id} i was encountering:
{
"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."
}
When I renamed it to {disposition} it started working. So apparently the parameter name is matched with the value in the placeholder.
Feel free to edit this answer to make it more accurate/explanatory.
Web Api by default expects URL in the form of api/{controller}/{id}, to override this default routing. you can set routing with any of below two ways.
First option:
Add below route registration in WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Decorate your action method with HttpGet and parameters as below
[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
string param2, string param3)
{
// your code here
}
for calling above method url will be like below
http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1&param2=value2&param3=value3
Second option
Add route prefix to Controller class and Decorate your action method with HttpGet as below.
In this case no need change any WebApiConfig.cs. It can have default routing.
[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{
[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
string param2, string param3)
{
// your code here
}
}
for calling above method url will be like below
http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1&param2=value2&param3=value3
In case you're using ASP.NET 5 with ASP.NET MVC 6, most of these answers simply won't work because you'll normally let MVC create the appropriate route collection for you (using the default RESTful conventions), meaning that you won't find any Routes.MapRoute() call to edit at will.
The ConfigureServices() method invoked by the Startup.cs file will register MVC with the Dependency Injection framework built into ASP.NET 5: that way, when you call ApplicationBuilder.UseMvc() later in that class, the MVC framework will automatically add these default routes to your app. We can take a look of what happens behind the hood by looking at the UseMvc() method implementation within the framework source code:
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());
}
The good thing about this is that the framework now handles all the hard work, iterating through all the Controller's Actions and setting up their default routes, thus saving you some redundant work.
The bad thing is, there's little or no documentation about how you could add your own routes. Luckily enough, you can easily do that by using either a Convention-Based and/or an Attribute-Based approach (aka Attribute Routing).
Convention-Based
In your Startup.cs class, replace this:
app.UseMvc();
with this:
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" }
);
});
Attribute-Based
A great thing about MVC6 is that you can also define routes on a per-controller basis by decorating either the Controller class and/or the Action methods with the appropriate RouteAttribute and/or HttpGet / HttpPost template parameters, such as the following:
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!");
}
}
}
This controller will handle the following requests:
[GET] api/items
[GET] api/items/5
[GET] api/items/GetLatestItems
[GET] api/items/GetLatestItems/5
[POST] api/items/PostSomething
Also notice that if you use the two approaches togheter, Attribute-based routes (when defined) would override Convention-based ones, and both of them would override the default routes defined by UseMvc().
For more info, you can also read the following post on my blog.
See this article for a longer discussion of named actions. It also shows that you can use the [HttpGet] attribute instead of prefixing the action name with "get".
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Web APi 2 and later versions support a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API. For example, you can easily create URIs that describe hierarchies of resources.
For example:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Will perfect and you don't need any extra code for example in WebApiConfig.cs.
Just you have to be sure web api routing is enabled or not in WebApiConfig.cs , if not you can activate like below:
// Web API routes
config.MapHttpAttributeRoutes();
You don't have to do something more or change something in WebApiConfig.cs. For more details you can have a look this article.
Just modify your WebAPIConfig.cs as bellow
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional });
Then implement your API as bellow
// GET: api/Controller_Name/Show/1
[ActionName("Show")]
[HttpGet]
public EventPlanner Id(int id){}

Categories

Resources