WebAPI accepts broken JSON send via Fiddler - c#

For some reason I can send these two JSON requests to my WebAPI and it'll accept and create an User. One with double quotes and one without.
{"Username":"Bob", "FirstName":"Foo", "LastName":"Bar", "Password":"123", "Headline":"Tuna"}
{Username:"Bob", FirstName:"Foo", LastName:"Bar", Password:"123", Headline:"Tuna"}
This is the method which creates an User.
// localhost:12345/api/controller/create
[ActionName("create")]
public HttpResponseMessage PostUser(User user)
{
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.UserId }));
return response;
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
However I'm able to make a successful POST request without the /create at the end of URL. I'm assuming that the reason for the success POST request is because whether or not the action name is present it will search for a Post in the method name.
So my question is, what's the purpose of the action name then? How can I make it so it's a must in the URL? Also why is it accepting both JSON requests and how can I make it accept one or the other.
EDIT:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Routing by Action-name
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{username}",
defaults: new { username = RouteParameter.Optional }
);
Thank you for your time
/twice

How can I make it so it's a must in the URL?
By changing your route definition in ~/App_Start/WebApiConfig.cs and making the action name explicitly appear in your route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Also why is it accepting both JSON requests and how can I make it accept one or the other.
Because the Web API uses JSON.NET as JSON serializer which accepts both of them.

If you look in App_Start for the route that's registered for your API calls you'll see:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
You'll notice that no action is specified. If you want to map to action names, you can change it to:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
But then you'll always be required to set an action (unless it's a GET which is the default). You can no longer POST directly toe controller, you'll always need to add an action.
Per this link the default behavior is to look for a method that starts with the HTTP method used:
To find the action, Web API looks at the HTTP method, and then looks
for an action whose name begins with that HTTP method name. For
example, with a GET request, Web API looks for an action that starts
with "Get...", such as "GetContact" or "GetAllContacts". This
convention applies only to GET, POST, PUT, and DELETE methods. You can
enable other HTTP methods by using attributes on your controller.
We’ll see an example of that later.
Another option is to create different controllers for your different GET calls, which is what I've done in the past.

Related

Routing Config for Web-API

I am creating a Web-API service using .Net 4.5.2.
I want to have the following URIs:
/api/v1/timeseries/{id}
/api/v1/timeseries/approval/{id}
With this, I expect to have two controllers:
TimeSeriesController
TimeSeriesApprovalController
Using default routing as below, I achieve my first desired outcome (/api/v1/timeseries/{id}), but I'm not sure how to achieve the second outcome. Can someone please show me how to amend the route config to deal with the second URI?
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
One option would be to use your existing routes, and use the url like:
/api/v1/timeseriesapproval/{id}
Note that this perfectly matches your existing route:
"api/v1/{controller}/{id}",
where controller matches timeseriesapproval.
Another option would be to setup a new route (prior to your existing one) specific for this need:
config.Routes.MapHttpRoute(name: "PutThisBeforeYourExistingOneApi",
routeTemplate: "api/v1/timeseries/approval/{id}",
defaults: new { controller = "TimeSeriesApproval", id = RouteParameter.Optional } );

How to serve POST request to http://domain_name.com using ASP.NET using a controller

I need to serve POST requests to http://domain_name.com using custom code (i.e. controller)
I understand that I can configure to serve POST requests to a route like
http://domain_name.com/api/SomeController/id
using
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
But I want to serve POST request to http://domain_name.com
How do I do that ?
Please do not downvote without pointing me to answer or a reason.
You can map some controller as default
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{id}",
defaults: new {controller="values", id = RouteParameter.Optional }
);

web api route with optional parameter

I am currently playing around with some things...According to this link, I need to construct a route that is open to the following format
webServiceURL/version/devices/deviceLibraryIdentifier/registrations/passTypeIdentifier?passesUpdatedSince=tag
so I defined the route like so
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "{version}/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{passesUpdatedSince}",
defaults: new { controller = "SerialNumbers", action = "GET", passesUpdatedSince = RouteParameter.Optional }
);
However, the following route fails for the url
http://localhost/v1/devices/24358235235loji200/registrations/pass.com.mypass?passesUpdatedSince=12a512
How can I configure the route so that the above url can reach my controller?
My controller looks like
[HttpGet]
public HttpResponseMessage Get(string passesUpdatedSince ="")
{
//do stuff
}
UPDATE
Thanks to the comments, I've made the following changes.
the route
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}",
defaults: new { controller = "SerialNumbers", action = "GET" }
);
My controller is as follows
public HttpResponseMessage Get(string deviceLibraryIdentifier,
string passTypeIdentifier,
string passesUpdatedSince = "")
{
//do stuff
}
According to the Apple docs, is it right to assume the following the webservice calls could look like
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass?passesUpdatedSince=159025
as these are returning 404.
These, however, do work.
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass/?passesUpdatedSince=1415l
http://localhost:31472/v1/devices/23lk5235232oijlk/registrations/pass.com.mypass/
So would there be a way to get it to work without the presence of the / near the end of the url?
It does look like the device is unable to recognize the route. I get the following message
Get serial #s task (for device 2523ff2fswtsfdh6544, pass type pass.com.mypass, last updated (null); with web service url https://weburl) encountered error: Unexpected response code 404
Because part of the URI had periods in it (pass.com.mypass), this always returned a 404
I had to add the
<modules runAllManagedModulesForAllRequests="true" />
in my web.config. And after that, everything worked as expected
For the route, try:
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "{version}/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}",
defaults: new { controller = "SerialNumbers", action = "GET" }
);
Note that you should actually have a hard-coded value where {version} is, according to the link you gave us (https://developer.apple.com/library/ios/documentation/PassKit/Reference/PassKit_WebService/WebService.html#//apple_ref/doc/uid/TP40011988-CH0-SW4).
A hard-coded version would look like this:
config.Routes.MapHttpRoute(
name: "DefaultApi3",
routeTemplate: "v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}",
defaults: new { controller = "SerialNumbers", action = "GET" }
);
Your controller action also needs to be able to accept all parameters of the route:
[HttpGet]
public HttpResponseMessage Get(string deviceLibraryIdentifier,
string passTypeIdentifier,
string passesUpdatedSince ="")
{
//do stuff
}

How do I get ASP.NET Web API working with versioning and the Help Page extension?

I've implemented a versioning framework into my WebAPI application, and would very much like to get it working with the new Help Page extension from Microsoft.
Microsoft.AspNet.WebApi.HelpPage
SDammann.WebApi.Versioning
Quite simply, I don't know how to get them both working together. I have 2 projects:
AdventureWorks.Api (The main host/root application)
AdventureWorks.Api.v1 (A class library containing the first version of the API)
The versioning works as expected.
I've tried installing the HelpPage package on the root application, and when I browse to the help page, it appears none of the controllers are being found. Internally I believe it uses:
Configuration.Services.GetApiExplorer().ApiDescriptions
This returns no results, so I get an error.
Can anyone assist me in getting both of these packages working together?
Edit:
In the beginning, I wasn't sure this was a routing problem, but recent comments seem to suggest otherwise. Here is my RouteConfig.cs
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "SldExportAliasApi",
routeTemplate: "api/v{version}/sld-export/{id}",
defaults: new { id = RouteParameter.Optional, controller = "Export" }
);
routes.MapHttpRoute(
name: "LinkRoute",
routeTemplate: "api/v{version}/link/{system}/{deployment}/{view}",
defaults: new { controller = "Link" }
);
routes.MapHttpRoute(
name: "DefaultSubParameterApi",
routeTemplate: "api/v{version}/{controller}/{id}/{param}",
defaults: new { id = RouteParameter.Optional, param = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v{version}/{controller}/{action}/{id}",
defaults: new { action = "Index", id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
You need to get a documentation XML file from your project AdventureWorks.Api.v1 project and place it in the bin folder of the AdventureWorks.Api project:
Then add these lines to your Application_Start method:
// enable API versioning
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new RouteVersionControllerSelector(GlobalConfiguration.Configuration));
GlobalConfiguration.Configuration.Services.Replace(typeof(IApiExplorer), new VersionedApiExplorer(GlobalConfiguration.Configuration));
GlobalConfiguration.Configuration.Services.Replace(typeof(IDocumentationProvider),
new XmlCommentDocumentationProvider(System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) +
"\\Adventure.Api.v1.XML"));
Then you can view your API with the documentation.
Sometimes the version number does not get to be picked up correctly, and replaced by ???
To fix this add:
if (api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace != null)
{
var versionName = api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace.Replace(".Controllers", "").Split('.').Last();
api.RelativePath = api.RelativePath.Replace("v???", versionName);
}
to your ApiGroup.cshtml exactly at this place:
#foreach (var api in Model)
{
if (api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace != null)
{
var versionName = api.ActionDescriptor.ControllerDescriptor.ControllerType.Namespace.Replace(".Controllers", "").Split('.').Last();
api.RelativePath = api.RelativePath.Replace("v???", versionName);
}
<tr>
<td class="api-name">#api.HttpMethod.Method #api.RelativePath</td>
<td class="api-documentation">
#if (api.Documentation != null)
{
<p>#api.Documentation</p>
}
else
{
<p>No documentation available.</p>
}
</td>
</tr>
}
This should do the trick!
I couldn't figure out how to comment on a post :( I think this should probably be a comment under the marked answer for this question but SDamman is updated and all I needed to do was this
// enable API versioning
GlobalConfiguration.Configuration.Services.Replace(typeof(System.Web.Http.Dispatcher.IHttpControllerSelector),
new SDammann.WebApi.Versioning.RouteVersionedControllerSelector(GlobalConfiguration.Configuration));
GlobalConfiguration.Configuration.Services.Replace(typeof(IApiExplorer), new SDammann.WebApi.Versioning.VersionedApiExplorer(GlobalConfiguration.Configuration));
There is a type called VersionedApiExplorer and it works great. Hope this helps the solution is much easier now.
EDIT:
I realized after trying to get help working again myself that my answer wasn't obvious at all.
The ONLY thing you need to do to get help pages working is replace the global configs IApiExplorer, that's it. Just do it right after you change the handler per sdammans instructions.
I agree with #mortware, the default routing for web api would mean your url should look something like "site/api/controllerName/" if you're using default Get()/Post() methods. If you're using specificly named methods then the route looks something like "site/api/controllerName/methodName".
I've also run into difficulty with the parameter names. Eg, if in your route specified in /App_Start/WebApiConfig.cs you have;
// Controller with ID
// To handle routes like `/api/VTRouting/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
// Controllers with Actions
// To handle routes like `/api/VTRouting/route`
config.Routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
then the method parameter for your http verb must have a parameter called "id", eg;
// url: site/api/controller/<int>
public HttpResponseMessage Get(int id) { return null; /*dummy*/ }
// url: site/api/controller/<int>
public HttpResponseMessage Post(int id) { return null; /*dummy*/ }
// url: site/api/controller/SomeAction/<int>
public HttpResponseMessage SomeAction(int id) { return null; /*dummy*/ }
If you have something like;
public HttpResponseMessage Get(int myID) { return null; /*dummy*/ }
It will not work as the "myID" parameter doesn't match that {id} specified in the route. As #OakNinja pointed out, we'll need your routing in the WebApiConfig.cs to help you pinpoint the exact cause

how would one route this using Asp.Net WebAPI?

I'm making a Restful service in Which I have a list of items, which could be queried as such:
GET Api/Items
Which lists all items.
But of course I'd also need these items to be listed as 'most popular', or 'belonging to user x' or 'belonging to category Y'
When glancing at the stackoverflow 2.0 api to see how they solved this they named their URLS as following:
GET Api/Items/MostPopular
And this methodology I'd like to adopt as well as it does seem to make sense and looks good.
However, How can I configure Web-API to allow this URL syntax as well?
The default route is as following:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
So I would guess that I need to add my extra routing in front of it.
I could do it like this: (If this even works)
config.Routes.MapHttpRoute(
name: "SpecializedApi",
routeTemplate: "api/{controller}/MostPopular",
defaults: new { id = RouteParameter.Optional }
);
But then it would add the MostPopular bit for all my controllers which I don't like.
Does something like this work?
config.Routes.MapHttpRoute(
name: "SpecializedApi",
routeTemplate: "api/Items/MostPopular",
defaults: new { id = RouteParameter.Optional }
);
And is this really the way to go as my routing table would quickly become very big and possibly unmaintainable?
The best would be to add another get action and configure a generic route rather then a specific route.
First add action for most popular
// add action for Most Popular
[ActionName("MostPopular")]
public MyResult GetMostPopular()
{
return null;
}
Setup route to handle the action.
// Controller with ID
// To handle routes like `/api/Items/1`
config.Routes.MapHttpRoute(
name: "ControllerAndId",
routeTemplate: "api/{controller}/{id}",
defaults: null,
constraints: new { id = #"^\d+$" } // Only integers
);
// Controllers with Actions
// To handle routes like `/api/Items/MostPopular`
config.Routes.MapHttpRoute(
name: "ControllerAndAction",
routeTemplate: "api/{controller}/{action}"
);
Probably the best maintainable if you do not deviate from the default to much,
however you should specify the controller and action in the route like this:
config.Routes.MapHttpRoute(
name: "SpecializedApi",
routeTemplate: "api/Items/MostPopular/{id}",
defaults: new { controller = "wheretogo",
action = "wichactiontotake",
id = RouteParameter.Optional
}
);
or this works too:
config.Routes.MapHttpRoute(
name: "SpecializedApi",
routeTemplate: "api/test/{action}/{id}.html",
defaults: new { controller = "test" }
);
Check out this link when using fake files for configurating the IIS:
http://haacked.com/archive/2008/11/26/asp.net-mvc-on-iis-6-walkthrough.aspx

Categories