WebApi Odata Windows Store App EndSaveChanges exception - asp.net-web-api

I am trying to create a Windows Store App using a WebApi Odata controller. After some effort I have all the Get requests working, I am now moving onto the CRUD methods, and am getting the following Exception on the EndSaveChanges of the Data Service Context.
<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">No HTTP resource was found that matches the request URI 'http://localhost:56317/odata/ESFClients(guid'f04ad636-f896-4de4-816c-388106cd39ce')'.</m:message>
<m:innererror>
<m:message>No routing convention was found to select an action for the OData path with template '~/entityset/key'.</m:message>
<m:type></m:type>
<m:stacktrace></m:stacktrace>
</m:innererror>
</m:error>
Now I think this is a bug in WebApi from this http://aspnetwebstack.codeplex.com/workitem/822 and its hiding the actual error. To make sure it wasn't my Odata Endpoint I created a quick console app to get an entry, update it and Patch it back, which worked all ok. My WebApi Odata Controller derives from ODataController with
public HttpResponseMessage Patch([FromODataUri] Guid key, Delta<ESFClient> patch)
As the method.
In my windows application I have a extension method on the DataServiceContext for the Save Changes.
public static async Task<DataServiceResponse> SaveChangesAsync(this DataServiceContext context, SaveChangesOptions options)
{
var queryTask = Task.Factory.FromAsync<DataServiceResponse>(context.BeginSaveChanges(options, null, null),
queryAsyncResult =>
{
var results = context.EndSaveChanges(queryAsyncResult);
return results;
});
return await queryTask;
}
And calling the update like so from a blank Windows Store XAML page.
public async Task UpdateWeekNo()
{
var container = new ESFOdataService.Container(new Uri("http://localhost:56317/odata/"));
var clients = (DataServiceQuery<ESFClient>)from p in container.ESFClients where p.UserID == new Guid("f04ad636-f896-4de4-816c-388106cd39ce") select p;
var result = await clients.ExecuteAsync();
var updatedClient = result.Single();
if (updatedClient != null)
{
updatedClient.WeekNo = 19;
container.UpdateObject(updatedClient);
await container.SaveChangesAsync(SaveChangesOptions.PatchOnUpdate); // Use PATCH not MERGE.
}
}
So does anyone come across the same issue, or know how I can find out the actual error. One interesting point is that if I debug the controller while running the Windows App, the patch method does not get called.

Ok, so I have finally solved this. Just a recap for those who could experience the same issue. I have an Odata WebApi controller, Windows 8 Store Application using WCF Client Library, with the reference created from Add Service Reference. When trying to update (patch) a record an exception was being thrown at the EndSaveChanges. This is because for some reason Post Tunneling is enabled by default on my context. Setting this to false allowed everything to work.
Context.UsePostTunneling = false;
Context.IgnoreResourceNotFoundException = true;

Related

PostAsync hanging in Xamarin Forms works on emulator but hangs on actual Mobile phone

I have Xamarin Forms project where I'm trying to POST and GET data to/from a Web API but when I'm making an async/await call, it works on the emulator (not without its original problems!) but when I try it on my actual phone mobile (Samsung S8+), it just hangs indefinitely.
Note that I'm only concentrating on the Android part right now, not iOS, not that the problem should make any difference in either.
This is the code I'm using:
IDataService.cs
Task<TResponse> PostDataAsync<TRequest, TResponse>(string uri, TRequest data)
where TRequest : class
where TResponse : class;
DataService.cs:
public async Task<TResponse> PostDataAsync<TRequest, TResponse>(string
additionalUri, TRequest data)
where TRequest : class
where TResponse : class
{
return await WebClient
.PostData<TRequest, TResponse>
(string.Concat(this.Uri, additionalUri), data);
}
WebClient.cs
using (var client = new HttpClient())
{
var jsonData = JsonConvert.SerializeObject(data);
using (var response = await client.PostAsync(
uri,
new StringContent(jsonData,
Encoding.UTF8,
"application/json" )))
{
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(content);
}
}
}
Method 1:
LoginPageViewModel.cs
public DelegateCommand SignInCommand => _signInCommand ??
(this._signInCommand = new DelegateCommand(SignInCommandAction));
private async void SignInCommandAction()
{
try
{
....
var user = await this._dataService
.PostDataAsync<LoginRequestDto,
LoginResponseDto>(#"Accounts/Login", loginRequestDto);
....
}
...
}
Method2:
LoginPageViewModel.cs
public DelegateCommand SignInCommand => _signInCommand ??
(this._signInCommand =
new DelegateCommand(async () => await SignInCommandAction()));
private async Task SignInCommandAction()
{
try
{
....
var user = await this._dataService
.PostDataAsync<LoginRequestDto,
LoginResponseDto>(#"Accounts/Login", loginRequestDto);
....
}
...
}
The PostDataAsync works with both methods when I call my local web API i.e. http://10.0.2.2/MyApp/api/ but both methods still hangs when calling external my web service from web provider i.e. http://myapp-123-site.atempurl.com/api/ which is a temp url for testing purpose.
The same apply to my GetDataAsync which is not demonstrated in question but I just thought I'd mention it.
Based on the above, you would think that my async/await code is correct since it works when calling the local web api but then what's causing it to hang when calling the remote web api.
As mentioned, I did enable my INTERNET permission in the manifest.
Any suggestions welcomed?
Thanks.
UPDATE-1:
Note that I've just tried to call a GET opertation within the same function and this is working in the emulator but hanging with the actual mobile.
using (var client = new HttpClient())
{
using (var response = await client.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return Newtonsoft.Json.JsonConvert
.DeserializeObject<TResponse>(content);
}
}
}
UPDATE-2:
This is somehow working and I have no idea why! The only thing that comes to mind is that I upgraded my libraries. This included PRISM which may have been at the source of the problem but I have no idea.
Sorry I can't provide more details. I could role back my code and try to see if it's hanging again but I just don't have the time to go and experiment some more considering the amount of time I've already spent on this. Sorry.
The requested url is an IP or a domain name.
If it is ip, only the IP of the public network can be accessed by devices on multiple network segments.
If it is a domain name, it needs to support the domain name resolution service.
If you do not have these environments for a while, you need the IP of the device and the IP of the server on the same network segment.
The PostDataAsync works with both methods when I call my local web API i.e. http://10.0.2.2/MyApp/api/ but both methods still hangs when calling external my web service from web provider i.e. http://myapp-123-site.atempurl.com/api/ which is a temp url for testing purpose.
From this phenomenon , the reason should be the temp url. From this domain name (myapp-123-site.atempurl.com) can not find the right local IP (10.0.2.2).And when you test in local network , I guess this will work.However the network of actual mobile can be not the same with local network , such as using 3G/4G network , then this will not working.

WebAPI return 404 on PUT/DELETE operations

This seems to be a fairly common issue, but none of the SO articles I have looked at have solved this for me.
I am working on a ASP.NET WebForms/MVC application running on IIS on Windows 10 (so not IIS Express) which is using jQuery AJAX to invoke a WebAPI application on a separate server. To get around CORS issues, and to add additional processing to all API calls, we implemented a server-side proxy using MVC controllers, so each call would end up somewhere like this:
[HttpPost]
public ActionResult Timesheets_Submit(Timesheet data)
{
var processedData = ProcessTheRequestInSomeWay(data);
var client = new SdkClient();
var results = client.Timesheets.Post(processedData);
return Json(results);
}
And this all worked quite successfully.
However, we are getting rather fed up of having to implement new server-side proxy methods each time we add a new API endpoint, so we decided to create a transparent server-side proxy using WebAPI, and have that do the real work.
The transparent server-side proxy is implemented like this:
public class TransparentProxyDelegatingHandler : DelegatingHandler
{
private static readonly Uri BaseUri = new Uri("https://my.apiserver.com");
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-Forwarded-For", request.GetClientIpAddress());
request.RequestUri = new Uri(BaseUri, request.RequestUri.PathAndQuery.Replace("/Proxy", string.Empty));
ProcessRequestInSomeWay(request);
var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return response;
}
}
So a request to POST /Proxy/Timesheets will get translated into a call to POST https://my.apiserver.com/Timesheets and the response returned pretty much as-is.
The problem that I am having is that calls which use the PUT and DELETE verbs are being rejected as 404 Not Found by my UI (not by the API, I can still invoke that directly using e.g. Fiddler/Postman); the original proxy used those verbs, so it's not like they haven't been configured, it's just when I'm calling the delegating handler. The handler never gets invoked, so there's something happening in the routing engine that is causing MVC PUT/DELETE requests to work, but WebAPI PUT/DELETE requests to fail.
It turns out I was not registering the TransparentProxyDelegatingHandler correctly; I was registering it like this in my WebApiConfig:
configuration.MessageHandlers.Add(new TransparentProxyDelegatingHandler());
but as it turns out (thanks to https://blog.kloud.com.au/2013/11/24/do-it-yourself-web-api-proxy/), what I really wanted was:
configuration.Routes.MapHttpRoute(name: "proxy", routeTemplate: "proxy/{*path}",
handler: HttpClientFactory.CreatePipeline(
innerHandler: new HttpClientHandler(),
handlers: new DelegatingHandler[]
{
new TransparentProxyDelegatingHandler(),
}),
defaults: new { path = RouteParameter.Optional },
constraints: null);
I'm guessing that what was going on was that because I didn't have any actual ApiController implementations wired up to WebApi, it wasn't resolving correctly somehow in the early stages of the pipeline.

ASP MVC: HTTP Error 405.0 - Method Not Allowed

I have ASP MVC 4 Visual Studio 2017 project running in my Windows 10 machine. When my page loads, it makes jquery ajax post call to load contents of the page. Now when i run the project, it is giving me HTTP Error 405.0 - Method Not Allowed error page.
Error Screenshot
Here is what i did so far:
Checked WebDAV and make sure it is not running.
Made sure all the handlers has appropriate verbs. Actually i added 'GET,HEAD,POST,DEBUG,PUT,DELETE' in all handlers tag in %windir%\system32\inetsrv\config\applicationhost.config file just to make sure i don't miss anyone.
Note: This site was working previously when i ran through my Visual Studio 2017. I configure project to use SSL and that is when i started running into this 405 error. I changed the project not to use SSL but still i am running into this 405 error.
Can you guys suggest how i can fix this issue?
Thanks
Razor Code:
<ul class="partialContents" data-url="#Url.Action("LoadUser", "BaseWeb")"></ul>
Jquery:
$(".partialContents").each(function (index, item){
var url = $(item).data("url");
if (url && url.length > 0) {
$(item).load(url);
}
});
Controller:
public async Task<ActionResult> LoadUser()
{
var httpClient = await GetHttpClient(); <--returns WebAPI running locally
UserModel requestModel = new UserModel();
requestModel.Email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Name).Value;
var response = await httpClient.PostAsJsonAsync("api/user/", requestModel);
var result = await response.Content.ReadAsStringAsync();
var userModel = JsonConvert.DeserializeObject<List<UserModel>>(result);
return PartialView("_LoadUser", userModel);
}

Calling an ASP.Net RESTful POST controller API method with HTTPClient.PostAsync

A colleague has written an Azure Mobile Service API which includes the following controller method:
public class SegmentationController : ApiController
{
// [...]
// POST api/<controller>/id
public async Task<string> Post(string id)
{
// [...]
I am trying to call that from a Windows Universal app. The calls to GET methods work without issue but I am failing to call that POST method. Here is what I've tried:
response = await client.PostAsync("api/segmentation/", new StringContent(item.Id));
// 405 Method Not Allowed
response = await client.PostAsync("api/segmentation/" + item.Id, new StringContent(""));
// 500 Internal Server Error
response = await client.PostAsync("api/segmentation/", new StringContent("id=" + item.Id));
// 405 Method Not Allowed
response = await client.PostAsync("api/segmentation/", new StringContent("{\"id\":" + item.Id + "}"));
// 405 Method Not Allowed
(N.B. System.Collections.Specialized.NameValueCollection used in Marc's answer is not available on WinRT / Windows Universal.)
It is possible that my second call is correct and that the error is in the server side code; we are exploring that possibility.
What is the correct way to make a POST call to an ASP.Net RESTful API method which expects a parameter called "id" of type string?
Your parameter is the problem. You have two options:
Use a query parameter instead of body. e.g. api/segmentation?id=abc
Add [FromBody] Attribute to your parameter. e.g. public async Task<string> Post([FromBody]string id)
Now your parameter is read from body. by default only complex types are read from body.
For more details see Parameter Binding in ASP.NET Web API
It was a server error. Once we had added error reporting code we could see that the problem was the server failing to load a C++ DLL it relied on due to an x64 /x86 mismatch on Azure. The call style that now works is the second one I list in the question:
response = await client.PostAsync("api/segmentation/" + item.Id, new StringContent(""));

What is the correct way to call patch from an OData client in Web Api 2

Following the OData samples created by the web api team, my controller has the following for supporting Patch:
public HttpResponseMessage Patch([FromODataUri] int key, Delta<Foo> item)
{
var dbVersion = myDb.GetById(key);
if(dbVersion == null)
throw Request.EntityNotFound();
item.Patch(dbVersion);
myDb.Update(dbVersion);
return Request.CreateResponse(HttpStatusCode.NoContent);
}
and using the auto-generated client (derived from DataServiceContext), I submit a patch request like this:
var foo = svcContainer.Foos.Where (f => f.Id == 1).SingleOrDefault();
foo.Description = "Updated Description";
svcContainer.UpdateObject(foo);
svcContainer.SaveChanges(SaveChangesOptions.PatchOnUpdate);
However, tracing the call in fiddler, I see that all other properties of Foo are serialized and sent to the service. Is that the correct behavior? I expected only the Id and Description to be sent over the wire. Also, if I debug the service method and call
GetChangedPropertyNames on item, all its property names are returned.
Should I be creating some sort of Delta instance on the client?
I understand the disconnected nature of the service and thus the service side does not have a context for tracking changes, but it seems to me the api team added support for patch for a reason, so I'd like to know if the client ought to be invoking the update in a different manner.
Update
The link YiDing provided explains how to create a true PATCH request from the client (using the Microsoft.OData.Client.DataServiceContext created by the Microsoft.OData.Client 6.2.0 and above.
For convenience, here is the code snippet:
var svcContainer = new Default.Container(<svcUri>);
var changeTracker = new DataServiceCollection<Foo>(svcContainer.Foos.Where(f => f.Id == 1));
changeTracker[0].Description = "Patched Description";
svcContainer.SaveChanges();
The DataServiceCollection implements property tracking, and using this pattern, only the updated properties are sent to the service.
Without using DataServiceCollection and simply using
svcContainer.UpdateObject(foo);
svcContainer.SaveChanges();
all properties are still sent over the wire despite documentation to the contrary, at least as of Microsoft.OData.Client 6.7.0
The client side property tracking is now supported from Microsoft.OData.Client version 6.2.0. It will detect only the modified properties of an entity and send the update request as PATCH instead of PUT to meet the requirement of your scenario. Please refer to this blog post for more details:
https://devblogs.microsoft.com/odata/tutorial-sample-client-property-tracking-for-patch/

Resources