I am building a new webapi for use at work. Where I have to develop a webapi2 application that fits the following endpoint schema
/file
/file/[data id]
/file/[data id]/documents
/file/[data id]/conditions
In my controller I have the following code:
public class FileController : ApiController
{
[HttpPost]
public HttpResponseMessage ImportFile()
{
var act = Request.Headers.Accept.ToString();
// test content type for "application/vnd.exp"
return Request.CreateResponse(HttpStatusCode.OK, $"Successful import # {DateTime.Now}");
}
[HttpPatch]
public HttpResponseMessage UpdateDataByFile(string dataId)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful save");
}
[HttpPatch]
public HttpResponseMessage UpdateDataIntake(string dataId)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful save");
}
[HttpGet]
public HttpResponseMessage GetDataConditionsForUser(string dataid)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful get");
}
}
My route config looks like the following:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "Files1",
routeTemplate: "{controller}/{action}"
);
routes.MapHttpRoute(
name: "Files2",
routeTemplate: "{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
}
}
My question is how do I configure the routing so that it matches the endpoint configuration
eg: "https://something.com/file/123412/document"
and have it hit the correct controller method?
Really confused about how to set routing in an environment like this.
You can do something like this-
routes.MapHttpRoute(
name: "File",
routeTemplate: "file",
defaults: new { controller = "File", action = "ImportFile" }
);
routes.MapHttpRoute(
name: "FileUpdate",
routeTemplate: "file/{dataId}",
defaults: new { controller = "File", action = "UpdateDataByFile" }
);
routes.MapHttpRoute(
name: "FileDocuments",
routeTemplate: "file/{dataId}/documents",
defaults: new { controller = "File", action = "UpdateDataIntake" }
);
routes.MapHttpRoute(
name: "FileConditions",
routeTemplate: "file/{dataId}/conditions",
defaults: new { controller = "File", action = "GetDataConditionsForUser" }
);
routes.MapHttpRoute(
name: "Files1",
routeTemplate: "{controller}/{action}"
);
routes.MapHttpRoute(
name: "Files2",
routeTemplate: "{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
That means you are using a custom route for each Action. You are only using these routes for urls that start like 'file/'. You have a lot of different ways that you can do this.
If you use {controller} in these templates and remove default: controller = "File" then you will match for other controllers.
If you can rename your Controller Actions to match your routing you may be able to fit them into a pattern, and use the {action} in the template.
routes.MapHttpRoute(
name: "File",
routeTemplate: "{controller}/{dataId}/{action}",
defaults: new {}
);
If you rename your last two Actions this should match them.
public class FileController : ApiController
{
[HttpPatch]
public HttpResponseMessage Documents(string dataId)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful save");
}
[HttpGet]
public HttpResponseMessage Conditions(string dataid)
{
var act = Request.Headers.Accept.ToString();
return Request.CreateResponse(HttpStatusCode.OK, "Successful get");
}
}
And if you set a default action in that route, you can also match your second Action
routes.MapHttpRoute(
name: "File",
routeTemplate: "{controller}/{dataId}/{action}",
defaults: new { action = "UpdateDataByFile" }
);
Related
in a webapi project's WebAPIConfig.cs, 2 routes are added
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 }
);
I try to create an apiController contains below functions
[HttpGet]
public string Get(int id)
{
return "get";
}
[HttpGet]
[ActionName("ByWait")]
public string[] ByWait(int id)
{
return "bywait";
}
I expects that
requesting /api/controllername/1234 returns "get", and
requesting /api/controllername/bywait/1234 returns "bywait".
However, the actual result is
/api/controllername/1234 >> throw exception Multiple actions were found that match the request
/api/controllername/bywait/1234 >> "by wait"
However can fix the issue?
s.t how to restrict the function ByWait only accepts request containing action so that it only response to /api/controllername/bywait/1234 and ignore /api/controllername/1234
Or there is other better solution?
Thanks
First you can change WebApiConfig:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}"
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then controller:
[HttpGet]
public string Get()
{
return "get-default";
}
[HttpGet]
public string Get(int id)
{
return "get" + id;
}
[HttpGet]
[Route("api/values/bywait/{id}")]
public string ByWait(int id)
{
return "bywait";
}
I have the following route in my WebApiConfig
config.Routes.MapHttpRoute(
name: "PaginateMessages",
routeTemplate: "api/Message/PaginateMessages/{conversationId}/{lastMessageId}",
defaults: new { controller = "Message", action = "PaginateMessages", conversationId = RouteParameter.Optional, lastMessageId = RouteParameter.Optional }
);
And I have the corresponding action in the Message controller:
[HttpGet]
public async Task<List<MessageDTO>> PaginateMessages(int conversationId, int lastMessageId)
{
return null;
}
However when I try to hit the endpoint I get a 404:
http://localhost:60162/api/Message/PaginateMessages/71/150
Is it not possible to have multiple route parameters as shown above?
You will need to add {controller} and {action} in route template.
config.Routes.MapHttpRoute(
name: "PaginateMessages",
routeTemplate: "api/{controller}/{action}/{conversationId}/{lastMessageId}",
defaults: new {controller = "Conversation", action = "GetConversation", conversationId = RouteParameter.Optional, lastMessageId = RouteParameter.Optional}
);
Alternative way in Web API 2 is to remove custom route configuration in WebApiConfig, and use Route attribute.
public class MessageController : ApiController
{
[HttpGet]
[Route("api/Message/PaginateMessages/{conversationId}/{lastMessageId}")]
public async Task<List<MessageDTO>> PaginateMessages(
int conversationId, int lastMessageId)
{
return null;
}
}
I am new in using web api and I am trying to call a specific method in my controller.
I have
global.asax
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
the WebApiConfig class with these routings
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
action="DefaultAction",
id = RouteParameter.Optional
}
);
and my controller
[HttpGet]
public HttpResponseMessage GetPatSummary(string PatId)
{
PatientSummary Pat = new PatientSummary();
HttpResponseMessage Response = new HttpResponseMessage();
string yourJson = Pat.GetPatient(PatId);
Response = this.Request.CreateResponse(HttpStatusCode.OK, yourJson);
return Response;
}
[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public IHttpActionResult GetPatient(int id)
{
Object Obj = new object();
if (Obj!=null)
{
return NotFound();
}
return Ok(Obj);
}
the URL I am using is
http://localhost/mdmwapi/api/MdmwPatientController/GetPatSummary/sgdgdgddhdhd1334254
but I get this error
A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by a literal string.
I am getting nut :-)
Use attribute routing
[HttpGet]
[Route("api/MdmwPatientController/GetPatSummary/{PatId}")]
public HttpResponseMessage GetPatSummary(string PatId)
{
PatientSummary Pat = new PatientSummary();
HttpResponseMessage Response = new HttpResponseMessage();
string yourJson = Pat.GetPatient(PatId);
Response = this.Request.CreateResponse(HttpStatusCode.OK, yourJson);
return Response;
}
then you can request it using
http://localhost/api/MdmwPatientController/GetPatSummary/yourpatid
also you can map any url using attribute routing this way
the solution is a combination of a new route and a mistake in the URL
the new routes are now these ones
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute
(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new
{
id = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "ApiMethodCall",
routeTemplate: "api/{controller}/{action}/{PatId}",
defaults: new
{
controller= "MdmwPatient",
action= "GetPatSummary"
}
);
and the error in the URL was that although the controller class name is MdmwPatientController I have to omit the "controller" suffix when calling from the test client, so the correct url is
http://localhost/mdmwapi/api/MdmwPatient/GetPatSummary/sgdgdgddhdhd1334254
I'd like have access to a method other than "GET", "PUSH", "PATCH", ....
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Employee",
routeTemplate: "api/employee/{employeeid}",
defaults: new { controller = "employee", employeeid = RouteParameter.Optional }
);
//for test : not work
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
//JSON Formatting
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
I have access to the employee controller :
[RoutePrefix("api/employee")]
public class EmployeeController : ApiController
{
public HttpResponseMessage Get() { }
public HttpResponseMessage Get(int employeeid) {}
public HttpResponseMessage Post([FromBody] EmployeeModel model){}
[HttpPut]
[HttpPatch]
public HttpResponseMessage Patch([FromBody] EmployeeModel model){}
[Route("initialisation")]
public HttpResponseMessage Initialisation() {}
}
I have access without any problem :
http://localhost/employee
http://localhost/employee/1
I'd like have access to the "Initialisation" method :
http://localhost/employee/initialisation
I added the route "DefaultApi" but when I try I get this error :
{
"$id": "1",
"message": "The request is invalid.",
"messageDetail": "The parameters dictionary contains a null entry for parameter 'employeeid' of non-nullable type 'System.Int32'
for method 'System.Net.Http.HttpResponseMessage Get(Int32)' in 'Pme.WebApi.Controllers.EmployeeController'.
An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}
Thanks,
Try the following:
Change
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
To
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{employeeid}",
defaults: new { action = "get", employeeid= RouteParameter.Optional }
);
The only change the name of last ID parameter from "id" to "employeeid"
I think this should work out. You can try modifying your WebApiConfig like this :
config.Routes.MapHttpRoute(
name: "DefaultApi",
//routeTemplate: "api/{controller}/{id}",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
This way, you keep it open for both, the controller name and the action name to be something dynamic and try hitting the URL that would be generated as per this format.
UPDATE :
Comment the "Employee" route code. Just keep the "DefaultApi" route active. Now try to hit this URL :
http://localhost:1955/api/Employee/Create/parameters
You obviously need to keep the host name as per yours and the parameter too.
Hope this helps.
You need to register attribute routing in your WebApiConfig
config.MapHttpAttributeRoutes();
See Attribute Routing in ASP.NET Web API 2
Alternatively you can specify exactly where your route should go
config.Routes.MapHttpRoute("Initialisation", "api/employee/initialisation",
new {controller = "employee", action = "initialisation"});
In my TestController I have the following:
[HttpGet]
public IEnumerable<String> Active()
{
var result = new List<string> { "active1", "active2" };
return result;
}
[HttpGet]
public String Active(int id)
{
var result = new List<string> { "active1", "active2" };
return result[id];
}
In RouteConfig the mapping is:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "", id = RouteParameter.Optional });
In a browser the following request works:
api/test/active/1
But this returns a Internal Server Error:
api/test/active
What do you have to do to return a action that may or maynot have a parameter in a similar manner to the default Get?
Update 1
As Cuong Le suggested, changing the ordering of routes helped, the routes are now:
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
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 }
);
I had to remove action = "" from the ActionApi route otherwise the standard Get on the other controllers stopped working (i.e. api/values)
api/test/active is now resolving, but I now get a 500 Internal Server Error for /api/test is it possile to have both resolves, so api/test would return "all" and /test/active only return "some"?
It is probably getting confused since you have two methods named action. Try deleting or renaming one of them and see if that works.
One way to do it is to provide a default value for the parameter,
[HttpGet]
public String Active(int id = 0)
{
var result = new List<string> { "active1", "active2" };
if (id == 0) {
return result;
} else {
return result[id];
}
}