Multiple Get methods in ASP.Net Web API - asp.net-web-api

Hi I've 2 different get methods with the below signature and my routing is as follows:
[ActionName("Data")]
public Dictionary<int, string> GetData(int ID)
{
}
[ActionName("Name")]
public Dictionary<int, string> GetName(int ID)
{
}
var route = routes.MapRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But api/{controller}/Data/9 and api/{controller}/Name/10 are both calling the first method GetData.
Can someone please guide me to fix this.

You will have to decorate your controllerclass with the [RoutePrefix("api/ControllerName")] as well as do the following:
[Route("Data")]
public Dictionary<int, string> GetData(int ID)
{
}
[Route("Name")]
public Dictionary<int, string> GetName(int ID)
{
}
var route = routes.MapRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);

You can get a little more fancy with the Route attribute.
[Route("Name/{ThisIsMyIdVariableName}"), HttpGet]
public HttpResponseMessage GetName(int ThisIsMyIdVariableName) { /*code here*/ }
Now you can keep the idea of a pretty url without having to use routes.MapRoute and can call the url like domain.com/Name/19. You can use multiple variable names wrapped with {} in the route and keep your urls pretty and restful when passing multiple variables.
This approach is also very useful if you have the need to version your api controller.

I know this should be a comment instead of an answer, but I don't have the required reputation to comment yet, and I'd like to help. Are both of the above methods returning data based on their id (PK) value? The reason both methods are calling the first method in your Web API server is because they're both appending the same URL extension to the server. A quick and easy way to differentiate these methods could be to change one of the 2 to accept a string that you could then convert back to an int. For instance:
//api/Customer/5
public Dictionary<int, string> GetData(int ID)
{
}
//api/Customer?CustID={custID]
public Dictionary<int, string> GetName(string custID)
{
//convert string to int and continue code
}
This might not be best practice, but this is what I've done in the past when needing to differentiate between GET/POST methods in my web api server. For deserializing, you could use a class like JsonConvert if you're using Json, etc. Then your calls to your controller will add the appropriate URL extensions as defined in the comments I left above the methods. Let me know if you'd like additional explanation.

Related

Default routing in web api

When I was working with Web API 2, I found that there is slight change in routing of web api.
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I understand the working of routing. Also I found that, by default there is no method name in routing although we can modify the routing and can add add pattern for action/method. But my question is what was the idea behind creating such routing.
Thank you
Susheel
There are no actions because the idea is that you can have a method for each HTTP verb. e.g.
public IHttpActionResult Get()
public IHttpActionResult Get(int id)
public IHttpActionResult Post()
public IHttpActionResult Put(int id)
The verb used determines which method is called.
The idea is that controllers are supposed to be very specific about what they relate to.
So you may have a "PersonController" which deals with adding and updating people, and an "OrdersController" which deals with orders.
Obviously this doesn't work out quite as straightforward in practice because controllers end up being more wide ranging than CRUD operations for an entity type.

Expand MVC API to all controllers

I added the AspNet.WebAPI to my existing MVC project with help from this post:
How to add Web API to an existing ASP.NET MVC (5) Web Application project?
It basically adds the following controller:
public class APIController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
...
And this in the App_Start:
namespace WebApplication1
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// TODO: Add any additional configuration code.
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// WebAPI when dealing with JSON & JavaScript!
// Setup json serialization to serialize classes to camel (std. Json format)
var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
formatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
}
This works, but for just for mysite/api/API
How do I expand this to work with the rest of my existing controllers (for example mysite/api/CLIENTs)?
From looking at the code, it seems it should just work. I even tried putting "Get" functions in the other controllers but no luck. Actually I don't know why /API works--is it reading the file name of the controller?
So basically, I am looking for advice on where to go next--do I put code in each individual controller, or put many calls in my existing "APIController", or neither.
The controller it added is just an example of a WebAPI controller. You can add more. For example:
public class ClientsController : ApiController
{
public IEnumerable<Client> Get()
{
// return a collection of clients
}
// etc.
}
Since they inherit from different base classes, you'll need to keep your MVC controllers and API controllers separate. So just add API controllers for your API methods.

Attribute Routing in WEB API overwrite route in Controller

I'm trying to add attribute routing to a ApiController like so:
public class PhoneNumbersController : ApiController
{
// GET BY ID
public PhoneNumber GetById(int id)
{
return PhoneNumbersSelect(id)[0];
}
// GET BY ID TypeOFPhoneNumbers/Id
[Route("api/typeOfPhoneNumbers/{id}")]
public TypeOfPhoneNumber GetTypeOfPhoneNumberById(int id)
{
return TypeOfPhoneNumbersSelect(id)[0];
}
}
My config looks like this:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I'm getting a 500 Error when trying to call api/phoneNumbers/1. If I comment out the GetTypeOfPhoneNumbersId() function the default controller GetById() works fine. What am I doing wrong? Am I not allowed to declare a unique route in the ApiController because of the way the config is set up?
Also, as I just found out, calling the api/typeOfPhoneNumbers/1 returns a 404 error, so that routing doesn't work at all.
Thanks for any help!
I believe you miss the controller name (phoneNumbers) in your route, this is a working code (I've tested it)
public class PhoneNumbersController : ApiController
{
// GET BY ID
[HttpGet]
public PhoneNumber GetById(int id)
{
return PhoneNumbersSelect(id)[0];
}
// GET BY ID TypeOFPhoneNumbers/Id
[HttpGet]
[Route("api/phoneNumbers/typeOfPhoneNumbers/{id:int}")]
public TypeOfPhoneNumber GetTypeOfPhoneNumberById(int id)
{
return TypeOfPhoneNumbersSelect(id)[0];
}
}
Could you try to access TypeOFPhoneNumbers's resource like that : ~api/typeOfPhoneNumbers?id=1

Spring MVC: how to indicate whether a path variable is required or not?

I am doing a Spring web. For a controller method, I am able to use RequestParam to indicate whether a parameter it is required or not. For example:
#RequestMapping({"customer"})
public String surveys(HttpServletRequest request,
#RequestParam(value="id", required = false) Long id,
Map<String, Object> map)
I would like to use PathVariable such as the following:
#RequestMapping({"customer/{id}"})
public String surveys(HttpServletRequest request,
#PathVariable("id") Long id,
Map<String, Object> map)
How can I indicate whether a path variable is required or not? I need to make it optional because when creating a new object, there is no associated ID available until it is saved.
Thanks for help!
VTTom`s solution is right, just change "value" variable to array and list all url possibilities: value={"/", "/{id}"}
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
There's no way to make it optional, but you can create two methods with one having the #RequestMapping({"customer"}) annotation and the other having #RequestMapping({"customer/{id}"}) and then act accordingly in each.
I know this is an old question, but searching for "optional path variable" puts this answer high so i thought it would be worth pointing out that since Spring 4.1 using Java 1.8 this is possible using the java.util.Optional class.
an example would be (note the value must list all the potential routes that needs to match, ie. with the id path variable and without. Props to #martin-cmarko for pointing that out)
#RequestMapping(method=GET, value={"/", "/{id}"})
public void get(#PathVariable Optional<Integer> id) {
if (id.isPresent()) {
id.get() //returns the id
}
}
VTToms answer will not work as without id in path it will not be matched (i.e will not find corresponding HandlerMapping) and consequently controller will not be hit. Rather you can do -
#RequestMapping({"customer/{id}","customer"})
public String surveys(HttpServletRequest request, #PathVariable Map<String, String> pathVariablesMap, Map<String, Object> map) {
if (pathVariablesMap.containsKey("id")) {
//corresponds to path "customer/{id}"
}
else {
//corresponds to path "customer"
}
}
You can also use java.util.Optional which others have mentioned but it requires requires Spring 4.1+ and Java 1.8..
There is a problem with using 'Optional'(#PathVariable Optional id) or Map (#PathVariable Map pathVariables) in that if you then try to create a HATEOAS link by calling the controller method it will fail because Spring-hateoas seems to be pre java8 and has no support for 'Optional'. It also fails to call any method with #PathVariable Map annotation.
Here is an example that demonstrates the failure of Map
#RequestMapping(value={"/subs","/masterclient/{masterclient}/subs"}, method = RequestMethod.GET)
public List<Jobs> getJobListTest(
#PathVariable Map<String, String> pathVariables,
#RequestParam(value="count", required = false, defaultValue = defaultCount) int count)
{
if (pathVariables.containsKey("masterclient"))
{
System.out.println("Master Client = " + pathVariables.get("masterclient"));
}
else
{
System.out.println("No Master Client");
}
//Add a Link to the self here.
List list = new ArrayList<Jobs>();
list.add(linkTo(methodOn(ControllerJobs.class).getJobListTest(pathVariables, count)).withSelfRel());
return list;
}
I know this is an old question, but as none of the answers provide some updated information and as I was passing by this, I would like to add my contribution:
Since Spring MVC 4.3.3 introduced Web Improvements,
#PathVariable(required = false) //true is default value
is legal and possible.
#RequestMapping(path = {"/customer", "/customer/{id}"})
public String getCustomerById(#PathVariable("id") Optional<Long> id)
throws RecordNotFoundException
{
if(id.isPresent()) {
//get specific customer
} else {
//get all customer or any thing you want
}
}
Now all URLs are mapped and will work.
/customer/123
/customer/1000
/customer - WORKS NOW !!

MVC3 REST service - how do I access the request body content for a PUT or POST request?

I am creating an ASP.NET MVC3 restful web service to allow reports to be uploaded from a set of servers. When a new report is created, I want the client app to do a PUT to
http://MyApp/Servers/[ServerName]/Reports/[ReportTime]
passing the content of the report as XML in the body of the request.
My question is: how do I access the content of the report in my controller? I would imagine that it is available somewhere in the HttpContext.Request object but I am reluctant to access that from my controller as it is not possible(?) to unit test that. Is it possible to tweak the routing to allow the content to be passed as one or more parameters into the controller method? The outcome needs to be RESTful, i.e. it has to PUT or POST to a URL like the one above.
Currently my routing is:
routes.MapRoute(
"SaveReport",
"Servers/{serverName}/Reports/{reportTime",
new { controller = "Reports", action = "Put" },
new { httpMethod = new HttpMethodConstraint("PUT") });
Is there any way to modify this to pass content from the HTTP request body into the controller method?
The controller method is currently:
public class ReportsController : Controller
{
[HttpPut]
public ActionResult Put(string serverName, string reportTime)
{
// Code here to decode and save the report
}
}
The object I am trying to PUT to the URL is:
public class Report
{
public int SuccessCount { get; set; }
public int FailureOneCount { get; set; }
public int FailureTwoCount { get; set; }
// Other stuff
}
This question looks similar but doesn't have any answer.
Thanks in advance
Seems like you just need to use the standard ASP.NET MVC model binding capability with the slight wrinkle that you would doing an HTTP PUT instead of the more common HTTP POST. This article series has some good samples to see how model binding is used.
Your controller code would then look like:
public class ReportsController : Controller
{
[HttpPut]
public ActionResult Put(Report report, string serverName, string reportTime)
{
if (ModelState.IsValid)
{
// Do biz logic and return appropriate view
}
else
{
// Return invalid request handling "view"
}
}
}
EDIT: ====================>>>
Jon added this code to his comment as part of the fix so I added it to the answer for others:
Create a custom ModelBinder:
public class ReportModelBinder : IModelBinder
{
public object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var xs = new XmlSerializer(typeof(Report));
return (Report)xs.Deserialize(
controllerContext.HttpContext.Request.InputStream);
}
}
Modify the Global.asax.cs to register this model binder against the Report type:
ModelBinders.Binders[typeof(Report)] = new Models.ReportModelBinder();

Resources