The Constraint Entry 'httpMethod' on the Route Must Have a String Value - c#

I have an asp.net Web API project, and in my WebApiConfig file, I have the following route defined:
config.Routes.MapHttpRoute(
name: "Web API Get",
routeTemplate: "api/{controller}",
defaults: new { action = "Get" },
constraints: new { httpMethod = new HttpMethodConstraint("GET") }
);
For integration testing purposes, I want to make a request to an HttpSelfHostServer to verify that we are receiving the proper data back from the api call. I am making the HttpRequestMessage as follows:
var httpMethod = new HttpMethod("GET");
var request = new HttpRequestMessage(httpMethod, "http://localhost:XXXX/api/User/");
var results = _client.SendAsync(request).Result;
I would expect that this would call the Get method on the UserController and then return the results as defined in that method. However, I instead get the following exception:
System.InvalidOperationException: The constraint entry 'httpMethod' on the route with route template 'api/{controller}' must have a string value or be of a type which implements 'IHttpRouteConstraint'
This same url (http://localhost:XXXX/api/User/) works without error when I use it in the browser, so I am pretty sure the issue has to be in the way I am sending the request to the HttpSelfHostServer through the HttpClient. I have tried using the HttpMethod.Get constant instead, but that also threw the same error.
Does anyone have any idea how I could resolve this issue?

Make sure that you are using the proper type for your constraint:
constraints: new { httpMethod = new System.Web.Http.Routing.HttpMethodConstraint(HttpMethod.Get) }
I guess you were using System.Web.Routing.HttpMethodConstraint which is an entirely different class used for ASP.NET MVC routing and which has nothing to do with ASP.NET Web API routing.

Related

My WebAPI route keeps returning a 404 error when containing a period

I have been having issues setting up my custom WebApi route in C#. I am setting up a custom route that uses a company name a parameter. The route works fine, unless the company name ends with a "." I have tried setting double escape but keep getting a 404 error.
Route
config.Routes.MapHttpRoute(
name: "Contracts",
routeTemplate:"{company}/Contracts/{action}/{id}",
defaults: new {controller= "Contracts", id= RouteParameter.Optional}
);
Controller
public class ContractsController : ApiController
{
[HttpGet, ActionName("PrivacyStatements")]
public HttpResponseMessage GetPrivacyStatements(string Company)
{
//code for privacy statements
}
These will work:
http://localhost/Company%20Name/Contracts/PrivacyStatements
http://localhost/Company.%20Name/Contracts/PrivacyStatements
http://localhost/Contracts/PrivacyStatements?Company=Company%20Name%20Inc.
This does not work:
http://localhost/Company%20Name%20Inc./Contracts/PrivacyStatements
the "." before the "/" in "/Company Name Inc./" always gives me a 404.
Any help is appreciated.

Manually pass a url through the modelbinder to obtain the RouteData parameters

I have a complex ASP.NET MVC routing scenario and I want to be able to parse a URL that I pull from the 'Referrer' request header using the existing routes.
I have incoming requests that look like this:
http://hostname/{scope}/{controller}/{action}
With corresponding route mapping:
routes.MapRoute(
name: "scoped",
url: "{scope}/{controller}/{action}/{id}",
defaults: new { controller = "Equipment", action = "Index", id = UrlParameter.Optional, scope = "shared" }
);
In the OnActionExecuting method of the base class of my controllers I pull the resulting scope from the RouteData:
var scope= (filterContext.RouteData.Values["scope"] as string).ToLower();
I then use the scope to construct some filters for my database queries. It all worked perfectly fine until I moved all my Json-returning methods to a separate set of WebApi2 controllers. I now also have a route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
All ajax requests are now made to the api controllers, which means that I do not have the scope value available. I want to solve this by using the 'Referrer' URL from the request header, which is is usually a URL that does include the scope.
What I would like to do is something like this when the ApiController initializes:
public void PullCurrentScopeDomainFromRequestHeader(System.Net.Http.Headers.HttpRequestHeaders headers) {
var refererUrl = headers.GetValues("Referer").First();
//do some magic to get the scope
}
The difficulty is that the scope can also have a default value ("shared"), in case a url like "http://hostname/controller/action" get's passed in. The best (and DRYest) way to get the scope from any URL, would be by somehow using the "scoped" route that I mapped in the routing config to parse the URL somehow. I just have no idea how to do that. Can anyone help?
You just need to build up a fake HTTP context based on your URL and then use the static RouteTable to parse the URL into a RouteValueDictionary.
// Create a fake HttpContext using your URL
var uri = new Uri("http://hostname/controller/action", UriKind.Absolute);
var request = new HttpRequest(
filename: string.Empty,
url: uri.ToString(),
queryString: string.IsNullOrEmpty(uri.Query) ? string.Empty : uri.Query.Substring(1));
// Create a TextWriter with null stream as a backing stream
// which doesn't consume resources
using (var nullWriter = new StreamWriter(Stream.Null))
{
var response = new HttpResponse(nullWriter);
var httpContext = new HttpContext(request, response);
var fakeHttpContext = new HttpContextWrapper(httpContext);
// Use the RouteTable to parse the URL into RouteData
var routeData = RouteTable.Routes.GetRouteData(fakeHttpContext);
var values = routeData.Values;
// The values dictionary now contains the keys and values
// from the URL.
// Key | Value
//
// controller | controller
// action | action
// id | {}
}
Note that you can also use a specific route from the RouteTable by specifying its name.
var routeData = RouteTable.Routes["scoped"].GetRouteData(fakeHttpContext);

How to access UrlHelper.Action or similar from within Global asax

I am trying to prepare a 301 redirect for a typo I made 'recieved'
I am struggling to find a way of getting the url from the action and controller names.
I am aware of UrlHelper.Action but it does not exist within Global.asax. How do I gain access to this method?:
// Add permanent redirection for retired pages (Application_BeginRequest())
if (HttpContext.Current.Request.Url.LocalPath.ToLower().StartsWith("/blah/listrecieved"))
{
HttpContext.Current.Response.RedirectPermanent(/*Need url generated from action and controller*/);
}
Alternatively I have created a route, if that's how I should be getting the string, this is also fine but I am unsure of how:
routes.MapRoute(
name: "blah-list-received",
url: "blah/list-received",
defaults: new { controller = "Blah", action = "ListReceived" }
);
for example, it might look like this:
// Add permanent redirection for retired pages
if (HttpContext.Current.Request.Url.LocalPath.ToLower().StartsWith("/blah/listrecieved"))
{
HttpContext.Current.Response.RedirectPermanent(routes.GetUrl( "blah-list-received" ) );
}
You need to construct the UrlHelper yourself:
var url = new UrlHelper(HttpContext.Current.Request.RequestContext, RouteTable.Routes)
.Action("YourAction",
"YourController",
new { paramName = paramValue });
See MSDN

No action was found on the controller that matches the request

Please excuse my ignorance in this area. I have read many threads and still cannot get my routing correct.
I have a ProductsController like this:
public class ProductsController : ApiController
{
[ActionName("GetListOfStudents")]
public static List<Structures.StudentInfo> GetListOfStudents(string Username, string Password)
{
List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Password);
return si;
}
}
I have a console test program where I have defined the route:
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/products/GetListOfStudents",
defaults: new { controller = "products", action = "GetListOfStudents" });
But when I run call
GET http://localhost:8080/api/Products/GetListOfStudents
I get the error message:
MessageDetail=No action was found on the controller 'Products' that matches the name 'GetListOfStudents'.
I have been pulling my hair out and cannot work out what the correct route should be.
Would any kind person care to help me out?
Ok- thanks for the help peeps!
This what I did to get it working:
Removed the "static" from the GetListOfStudents function.
Added the route below.
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/products/GetListOfStudents/{username}/{password}",
defaults: new { controller = "products", action = "GetListOfStudents" }
);
Thanks everyone for your help!
When registering your global api access point, you should tell the config which route to use in the following manner:
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}
defaults: new { controller = "products", action = "GetListOfStudents" });
In this sample you explicitly tell the controller it should only go to the "products" controller, you can make it generic without specifying the control or the action, just omit the defaults, like this:
config.Routes.MapHttpRoute(
name: "ApiByAction",
routeTemplate: "api/{controller}/{action}
That should do the job :)
Your GetListOfStudents action requires two parameters, username and password. Yet, the route definition contains neither specification in the route template where the values for those parameters should come from, nor specification for those parameter defaults in the defaults: parameter definition.
So when request comes in, routing is able to find your controller, but it is unable to find the action that it can call with the request and route context that it has because it has no information for the username and password parameters.
The most important is:
ASP.Net's mvc not only seek action by name, also it will check method's signature, only the method is non-static, name matches and parameters matches, the action will be executed.
for your case, there are two ways to correct it.
one way is declare default value, mvc will use default value when parametr not found.
public List<Structures.StudentInfo> GetListOfStudents(string Username = null, string Password = null)
{
List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Password);
return si;
}
the second way is use override
public List<Structures.StudentInfo> GetListOfStudents()
{
return GetListOfStudents(null, null);
}
public List<Structures.StudentInfo> GetListOfStudents(string Username, string Password)
{
List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Password);
return si;
}
I had this problem and solved it by including the verb as part of the action (i.e. GetThis, GetThat) and manually creating routes. I was attempting to create routes using attributes, but that did not work. This SO question may be the answer as to why the attributes aren't working, I haven't gotten that straightened out yet. As an additional note for anyone else having the same problem, when debugging it locally, IE was crashing when the "no action found" xml was returned. Once I gave up and switched to Chrome, the message detail was returned, and it was obvious that my controller at least was being found, it was just a matter of getting the action to work...
If you want to call GetListOfStudents method without parameter you must set default value for parameter. such as
GetListOfStudents(string Username=null, string Password=null)
Otherwise you must call method with Parameters.
GET http://localhost:8080/api/Products/GetListOfStudents/Username/Password
One issue could be the order of the route declarations in your WebApiConfig.cs file. Have a look here about the precedence of routes. If you have two routes with the same amount of parameters, you may need to reorder the routes, or -- depending on how specific the route is -- hardcode the controller or action name
When sending, encode the password with base64.
Then when you about to use it decode it.
byte[] numArray = Convert.FromBase64String(password);
string Pass = Encoding.UTF8.GetString(numArray);
List<Structures.StudentInfo> si = StudentFunctions.GetListOfStudents(Username, Pass);
Works fine for me.

Passing querystring parameters without using OData conventions?

Is there a way to pass querystring parameters to an ASP.NET MVC4 Web Api controller without using the OData conventions outlined here?
http://www.asp.net/web-api/overview/web-api-routing-and-actions/paging-and-querying
I have some repository methods built using Dapper that don't support IQueryable and want to be able to manually paginate them without using the OData conventions, but whenever I try doing it the traditional ASP.NET way I get "route not found" errors.
For instance, here's a route:
context.Routes.MapHttpRoute(
name: "APIv1_api_pagination",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { area = AreaName, controller = "category", offset = 0, count = 100});
And here's the signature to match
public class CategoryController : ApiController
{
// GET /api/<controller>
public HttpResponseMessage Get(int id, int offset = 0, int count = 0)
And whenever I pass the following query:
http://localhost/api/v1/category/1?offset=10
I get the following error:
No action was found on the controller 'Category' that matches the
request.
Any suggestions on how to work with querystrings sanely in ASP.NET MVC4 Web Api?
When you start to use querystring you actually call exact method of controller with its parameters. What I prefer you to change your router like :
context.Routes.MapHttpRoute(
name: "APIv1_api_pagination",
routeTemplate: "api/v1/{controller}/{action}/{id}",
defaults: new { area = AreaName, controller = "category", offset = 0, count = 100});
and then change your method into
public HttpResponseMessage Items(int id, int offset = 0, int count = 0);
From now on whenever you query like
http://localhost/api/v1/category/Items?id=1&offset=10&count=0
it will run.
Another method came to my mind while writing this. I don't know if it works but try to change your router like
context.Routes.MapHttpRoute(
name: "APIv1_api_pagination",
routeTemplate: "api/v1/{controller}/{id}/{offset}/{count}",
defaults: new { area = AreaName, controller = "category", offset = RouteParameter.Optional, count = RouteParameter.Optional});
In this instance, the issue I was running into was the fact that I had multiple overloads for GET on my WebApi controller instance. When I removed those (and condensed everything down to one Get method with more optional parameters and control-flow inside the method itself) everything worked as expected.

Categories