My apologies if the question seems trivial but being new to WebAPI i cannot
get the client access Put Methods.
Here is my WebAPI Method
public class TestController : ApiController
{
[HttpPut]
public HttpResponseMessage Put(string id)
{
var response = new HttpResponseMessage();
response.Headers.Add("Message","Success");
return response;
}
}
Route Configuration
//configure the webapi so that it can self host
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{Controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional }
);
Client
var baseAddress="http://localhost:8000/";
var client= new HttpClient {BaseAddress = new Uri(baseAddress)};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.PutAsJsonAsync(baseAddress+"Test", "TestParam").Result;
Console.WriteLine(response.Content);
When i run the above code.
I get 404 Error for some reason meaning the client cannot contact the webapi method.
I have three questions.
1. How can i make sure the client can correctly call the web api method.
2. Is it possible to invoke the method directly from url?
I tried http://localhost:8000/test/Dummy but no luck.
3. If its possible to invoke the method how can i make sure the message gets displayed on the page saying "Success".
Your code would work if you didn't specify the action in the configured route. In that case the action is selected by the HTTP method (PUT, POST, DELETE, GET...). The route shpukd look like this:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
As you've specified the {action} part in the route, you must specify the action in the url. In that case, the url should be like this:
http://localhost:8000/Test/Put
Please, see Routing in ASP.NET Web API.
If you want to test your Web API methods one of the best tools available is Postman, which is a Chrome extenion. This solves your questions 1 & 2.
For Q3: as you're making an asycn call, you have to await for the response message, read it, and show the corresponding message. You can read this documentation if you don't know the async/await model.
Related
My route is correctly configured as I already saw it in another questions.
Web API uses MapHttpRoute, and it uses System.Web.Http. I decorated my Actions with [System.Web.Http.HttpPost] but it seems not to work and it returns the error message:
The requested resource does not support http method 'GET'
I tried this solution [System.Web.Http.AcceptVerbs("GET", "POST")] as I see here on the same question. The requested resource does not support HTTP method 'GET' and it worked.
But on the API Help Page, this is what I see
the METHOD of the Action is GET that that should be POST.
Maybe I am missing something that should not or should be implemented on the Action I am working with.
Here is my code in the Controller.
[HttpPost, Route("DestroySession/{userID}", Name = "DestroySession"), AcceptVerbs("GET" , "POST")]
public async Task<IHttpActionResult> DestroyUserSession(string userID)
{
SystemResult systemResult = new SystemResult();
await Task.Run(() => {
IAbstractLogic<UserInput, SystemResult> systemProcess = new LogoutLogic();
UserInput userInput = new UserInput
{
UserID = userID
};
systemResult = systemProcess.doProcess(userInput);
}).ConfigureAwait(false);
return Content(HttpStatusCode.OK, new
{
message = systemResult.ResultMessage, status = systemResult.ResultCode == 0
});
}
And here is my WebApiConfig
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Any help would be much appreciated. Regards
Whooosh! It seems that my code is working very fine. I just tested it in POSTMAN and it literally works. All I can say is that if your request is to POST something. you need a third party application to test it out. Testing it on the browser alone gives you so much problem.
From this blog article by Yusef: http://blogs.msdn.com/b/youssefm/archive/2013/01/28/writing-tests-for-an-asp-net-webapi-service.aspx
I'm trying to set up some unit test for a WebApi project but continue to get:
"No HTTP resrouce was found that matches the request URI http://localhost/api/Filter"
Test case:
[TestMethod]
public void TestMethod1()
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
HttpServer server = new HttpServer(config);
using (HttpMessageInvoker client = new HttpMessageInvoker(server))
{
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/Filter"))
{
request.Content = new StringContent(ValidJSONRequest);
request.Content.Headers.Add("content", "application/json");
using (HttpResponseMessage response = client.SendAsync(request, CancellationToken.None).Result)
{
Assert.AreEqual(ValidJSONResponse, response.Content.ReadAsStringAsync().Result);
}
}
};
}
NB. ValidJSONRequest/ValidJSONResponse are string containing JSON objects.
Running in IIS express this routing works perfectly and behaves as expected and I can't for the life of me work out what's going on? What am I missing?
Right, I'm still not sure exactly what's going on here but I've found a workaround.
This blog article contains some details - effectively the controllers context needs to be loaded up into memory... http://www.tugberkugurlu.com/archive/challenge-of-solving-an-asp-net-web-api-self-hosting-problem-no-http-resource-was-found-that-matches-the-request-uri
So how to fix it? Add this test case to the test class and it works fine.
[TestMethod]
public void Filter_Test()
{
FilterController controller = new FilterController();
}
The problem is that you're not specifying an id on your tested URL (http://localhost/api/Filter), and the configureed route doesn't have the id configured as optional.
So, either test a ULR that specifies an id, like http://localhost/api/Filter/1, or chagne the route configuration so that the id is optional, like this: instead of
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional } // optional id
);
In this way, the tested url will match the DefaultApi route.
Of course, you need a Postxxx method in your controller, because you're trying a POST action, and not specifying an action name neither in the tested URL, nor in the route definition. But, if you say it's working on local IIS, then this method must exist.
I am using the following code to get data from my api:
using (var client = new HttpClient())
{
try
{
string url = "http://localhost:58639/api/cars/" + carId + "?includeOwners=true";
var model = client
.GetAsync(url)
.Result
.Content.ReadAsAsync<Car[]>().Result;
Car c = model[0];
newCount = c.Persons.Count;
/* More Code */
}
}
using this route in WebApiConfig:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Is there a way to create the URL dynamically such that if the site was hosted somewhere other than localhost this would still work? I have attempted to do this with Url.RouteUrl and UrlHelper, yet I can't find a way to include the "?includeOwners=true" filter with them.
One way is to put the server part in a config file (web.config etc) and read it from there when you're constructing the url.
Just add the base url part in an appSetting and read it using ConfigurationManager.AppSettings["NameOfKey"]
Please try the below code:
string domainName = ((System.Web.HttpContextWrapper)Request.Properties["MS_HttpContext"]).Request.ServerVariables["HTTP_HOST"];
you can dynamically load the Host and Port or the whole domainname.
I have a WebApi service that needs to bill usage. It's an WebApi v1 / MVC 4 that I just upgraded to WebApi 2 / MVC 5. I have an ActionFilterAttribute that will determine the pricing rules for the request. The ActionFilter adds the billing information to the HttpActionContext.Request.Properties. The controller action then performs service requested, bills the usage and returns results.
My problem is I now have a dependency on Request in my controller, which is causing me a problem in unit testing (Structuremap). I was hoping to create a class that exposed properties that internally accessed the Request object, so I could inject fake classes for testing. My first attempt is giving my problems.
I'm hoping to find a better way to pass data from to the controller that I could easily unit test. If I'm doing it the recommended way, then I'll try to solve the structuremap problems. Also, this is my first WebApi project, so I could be doing things the hard way.
Here's some code in case I missed critical details:
ActionFilterAttribute:
public override void OnActionExecuting(HttpActionContext actionContext)
{
...
actionContext.Request.Properties.Add("PricingRule", pricingRule);
actionContext.Request.Properties.Add("ServiceUsage", serviceUsage);
actionContext.Request.Properties.Add("ServiceEndPoint", serviceEndPoint);
// Record how long it took to for pricing code to execute.
actionContext.Request.Headers.Add("PriceDuration", span.TotalMilliseconds.ToString(CultureInfo.InvariantCulture));
}
Controller:
public HttpResponseMessage GetServiceRequest([FromUri]string customerId, [FromUri]string apiKey)
{
....
var priceDuration = Request.Headers.GetValues("PriceDuration").FirstOrDefault();
object myObject;
Request.Properties.TryGetValue("PricingRule", out myObject);
var pricingRule = (PricingRule)myObject;
...
}
Thanks!
Your controller having dependency on Request is not too bad. It is just a property and you can set it like this to any request object of your liking, as you you 'arrange' your test.
var controller = new MyApiControllerClassToUnitTest();
controller.Configuration = new HttpConfiguration();
var route = controller.Configuration.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
var routeValues = new HttpRouteValueDictionary();
routeValues.Add("controller", controllerPrefix);
var routeData = new HttpRouteData(route, routeValues);
controller.Request = new HttpRequestMessage(HttpMethod.GET, "http://someuri");
controller.Request.Properties.Add(
HttpPropertyKeys.HttpConfigurationKey, controller.Configuration);
controller.Request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
I've the following ASP.NET WebAPI binding:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new {id = RouteParameter.Optional}
);
And my Controller looks like this:
public class ReferenceDataController : BaseController
{
[RequireUserToken(ApprovedDeviceToken = true, ValidUserToken = true)]
[HttpPost]
public IEnumerable<SynchronizeItem<IReferenceDataItem>> Sync([FromBody]IEnumerable<SynchronizeItem<IReferenceDataItem>> clientSyncItems, [FromUri]int referenceDataType)
{
// my code
}
On the client site I use the following code to send a request:
var client = new RestClient (baseUrl);
var request = new RestRequest (resource, method);
request.XmlSerializer = new JsonSerializer ();
request.RequestFormat = DataFormat.Json;
request.AddHeader ("X-Abc-DeviceToken", deviceToken);
if (!string.IsNullOrWhiteSpace (userToken))
request.AddHeader ("X-Abc-UserToken", userToken);
if (payload != null)
request.AddBody (payload);
if (parameters != null)
{
foreach (var parameter in parameters)
{
request.AddUrlSegment(parameter.Key, parameter.Value);
}
}
var response = client.Execute<T> (request);
My expectation is, sending a POST request to http://myhost/api/referencedata/sync?referencedatatype=countries with a body which contains an IEnumerable. If I remove the UrlSegment parameters on client site and the second argument on the webservice site, than it works.
How can I combine a body with payload and additional URL parameters?
You can define your action method as follow,
[RequireUserToken(ApprovedDeviceToken = true, ValidUserToken = true)]
[HttpPost]
public IEnumerable<SynchronizeItem<IReferenceDataItem>> Sync(IEnumerable<SynchronizeItem<IReferenceDataItem>> clientSyncItems, int referenceDataType)
{
// my code
}
No BodyAttribute or FromUriAttribute. In that way, Web API will try to use a MediaTypeFormatter to deserialize the body into the clientSyncItems collection and any additional value type from the query string (referenceDataType from the query string). The route as you defined it will take "sync" as Id (which would be ignored as it is not a parameter in your action).
You must also specify a content-type header so Web API can choose the right formatter (json or xml for example).