Xamarin.Form HttpClient for WebAPI failed in iOS - asp.net-web-api

I'm creating Xamarin.Forms for Android and iOS using WebAPI as the web service. The whole thing went well in Android but I hit error in iOS, particularly when doing "JsonConvert.DeserializeObject". Below is the sample code
Model
public class WsObjTest
{
public string name { get; set; }
public string code { get; set; }
public string age { get; set; }
}
WebAPI
[HttpGet]
public WsObjTest HelloWorld()
{
WsObjTest wsObjTtest = new WsObjTest();
wsObjTtest.name = "John Doe";
wsObjTtest.code = "ABC123";
wsObjTtest.age = "18";
return wsObjTtest ;
}
In my Xamarin.Forms, this is how I call and deserialize the response
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(apiURL.Replace("##action##", "HelloWorld"));
return JsonConvert.DeserializeObject<WsObjTest>(response); //ERROR HERE
I got the response as below, but error thrown at the return statement.
"{\"code\":\"ABC123\",\"name\":\"John Doe\",\"age\":\"18\"}"
The error message is below
Unhandled Exception:
System.MemberAccessException: Cannot create an abstract class:
System.Reflection.Emit.DynamicMethod occurred
I think it's because iOS doesn't support JIT compilation or dynamic methods of some sort? May I know if there is anyway to overcome this error? Thanks.

Based on https://learn.microsoft.com/en-us/xamarin/ios/internals/limitations, it is not possible to use any facilities that require code generation at runtime in Xamarin.iOS because code on the iPhone is statically compiled ahead of time instead of being compiled on demand by a JIT compiler.
What I have to do now is read and create the object manually as below. Should anybody got better solution please share with me.
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(apiURL.Replace("##action##", "HelloWorld"));
JObject jObject = (JObject) JsonConvert.DeserializeObject(response);
WsObjTest wsObjTest = new WsObjTest();
wsObjParent.name = jObject["name"].ToString();
wsObjParent.code = jObject["code"].ToString();
wsObjParent.age = jObject["age"].ToString();
return wsObjTest;
Extracted from the link
Since the iOS kernel prevents an
application from generating code dynamically, Xamarin.iOS does not
support any form of dynamic code generation. These include:
The System.Reflection.Emit is not available.
No support for
System.Runtime.Remoting.
No support for creating types dynamically (no
Type.GetType ("MyType`1")), although looking up existing types
(Type.GetType ("System.String") for example, works just fine).
Reverse
callbacks must be registered with the runtime at compile time.
So the System.Reflection.Emit thus the error that I received.

Related

Use MVC's model binding from application code

As a sample of what I'm trying to accomplish, here in MapPost I'm manually parsing the body of the HTTP request.
// Program.cs
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
Type[] types = new[] { typeof(SampleDto1), typeof(SampleDto2), <other unknown types> };
foreach (var type in types)
{
app.MapPost(type.Name, async (HttpContext httpContext) =>
{
var request = await JsonSerializer.DeserializeAsync(
httpContext.Request.Body,
type,
new JsonSerializerOptions(JsonSerializerDefaults.Web),
httpContext.RequestAborted);
return Results.Ok(request);
});
}
app.Run();
internal record SampleDto1(string Input) { }
internal record SampleDto2(string Input) { }
This works, yay! However, ... ASP.NET Core's MVC has all these sophisticated ModelBinding functionality and I really would like to use that. Because that opens up possibilities for binding to querystring parameters and other sources instead of only the request body.
Basically I want to replace the call to JsonSerializer with a call to framework code.
I've been browsing the ASP.NET Core source code and at first the DefaultModelBindingContext looked promising. However, I soon stumbled on some internal classes which I couldn't access from my code.
Long story short, .. is it at all possible to plug-in to MVC's model binding from application code?
Update: Although it doesn't show from the initial question, the solution should work dynamically with any request type. Not only SampleDto1 and SampleDto2. That's why explicit parameter binding from Minimal API won't do the trick.
You could try the codes :
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
builder.Services.AddSingleton<Service>();
app.MapPost("/{id}", ([FromRoute] int id,
[FromQuery(Name = "p")] int page,
[FromBody]SampleDto1 sample1,
[FromBody] SampleDto2 sample2,
[FromServices] Service service,
[FromHeader(Name = "Content-Type")] string contentType)
=> { });
app.Run();
internal record SampleDto1(string Input) { }
internal record SampleDto2(string Input) { }
You could read the official document for more details:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#explicit-parameter-binding

Connecting to localhost with Restsharp on Android

I created a Web API and would like to access it in my Android XAMARIN App. It's a XAMARIN Forms App, which references a .NET Standard 2.0 library with the newest RestSharp nuget package installed.
Unfortunately I get the error:
Error: ConnectFailure (No route to host)
whenever I do the following:
public async Task<List<ETFPortfolio>> GetPortfolios()
{
var client = new RestClient("http://10.0.2.2:51262/api/");
var request = new RestRequest("portfolio", Method.GET);
request.AddHeader("Accept", "application/json");
var response = client.Execute(request); // Result with the error stated above
throw new Exception();
}
My WebAPI controller is set up like this:
[Route("api/[controller]")]
public class PortfolioController : Controller
{
// GET api/portfolio
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
var handler = new MyHandler();
var portfolioList = await handler.Handle();
return Ok(portfolioList);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
I found a similar question, unfortunately there were no answers yet.
Error: ConnectFailure (No route to host)
Is there anything I'm missing here or could check out to make this work?
Since you are using a real device, your API url should not be a localhost but an IP address of the computer on which you have the API running.
localhost will work only on the emulator running on the same machine with your API.
P.S.: I wrote a blogpost on this topic, you might be interested in checking it as well.

How to create SubCommunities using the Social Business Toolkit Java API?

In the SDK Javadoc, the Community class does not have a "setParentCommunity" method but the CommunityList class does have a getSubCommunities method so there must be a programmatic way to set a parent Community's Uuid on new Community creation. The REST API mentions a "rel="http://www.ibm.com/xmlns/prod/sn/parentcommunity" element". While looking for clues I check an existing Subcommunity's XmlDataHandler's nodes and found a link element. I tried getting the XmlDataHandler for a newly-created Community and adding a link node with href, rel and type nodes similar to those in the existing Community but when trying to update or re-save the Community I got a bad request error. Actually even when I tried calling dataHandler.setData(n) where n was set as Node n=dataHandler.getData(); without any changes, then calling updateCommunity or save I got the same error, so it appears that manipulating the dataHandler XML is not valid.
What is the recommended way to specify a parent Community when creating a new Community so that it is created as a SubCommunity ?
The correct way to create a sub-community programatically is to modify the POST request body for community creation - here is the link to the Connections 45 infocenter - http://www-10.lotus.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.5+API+Documentation#action=openDocument&res_title=Creating_subcommunities_programmatically_ic45&content=pdcontent
We do not have support in the SBT SDK to do this using CommunityService APIs. We need to use low level Java APIs using Endpoint and ClientService classes to directly call the REST APIs with the appropriate request body.
I'd go ahead and extend the class CommunityService
then go ahead and add CommunityService
https://github.com/OpenNTF/SocialSDK/blob/master/src/eclipse/plugins/com.ibm.sbt.core/src/com/ibm/sbt/services/client/connections/communities/CommunityService.java
Line 605
public String createCommunity(Community community) throws CommunityServiceException {
if (null == community){
throw new CommunityServiceException(null, Messages.NullCommunityObjectException);
}
try {
Object communityPayload;
try {
communityPayload = community.constructCreateRequestBody();
} catch (TransformerException e) {
throw new CommunityServiceException(e, Messages.CreateCommunityPayloadException);
}
String communityPostUrl = resolveCommunityUrl(CommunityEntity.COMMUNITIES.getCommunityEntityType(),CommunityType.MY.getCommunityType());
Response requestData = createData(communityPostUrl, null, communityPayload,ClientService.FORMAT_CONNECTIONS_OUTPUT);
community.clearFieldsMap();
return extractCommunityIdFromHeaders(requestData);
} catch (ClientServicesException e) {
throw new CommunityServiceException(e, Messages.CreateCommunityException);
} catch (IOException e) {
throw new CommunityServiceException(e, Messages.CreateCommunityException);
}
}
You'll want to change your communityPostUrl to match...
https://greenhouse.lotus.com/communities/service/atom/community/subcommunities?communityUuid=2fba29fd-adfa-4d28-98cc-05cab12a7c43
and where the Uuid here is the parent uuid.
I followed #PaulBastide 's recommendation and created a SubCommunityService class, currently only containing a method for creation. It wraps the CommunityService rather than subclassing it, since I found that preferrable. Here's the code in case you want to reuse it:
public class SubCommunityService {
private final CommunityService communityService;
public SubCommunityService(CommunityService communityService) {
this.communityService = communityService;
}
public Community createCommunity(Community community, String superCommunityId) throws ClientServicesException {
Object constructCreateRequestBody = community.constructCreateRequestBody();
ClientService clientService = communityService.getEndpoint().getClientService();
String entityType = CommunityEntity.COMMUNITY.getCommunityEntityType();
Map<String, String> params = new HashMap<>();
params.put("communityUuid", superCommunityId);
String postUrl = communityService.resolveCommunityUrl(entityType,
CommunityType.SUBCOMMUNITIES.getCommunityType(), params);
String newCommunityUrl = (String) clientService.post(postUrl, null, constructCreateRequestBody,
ClientService.FORMAT_CONNECTIONS_OUTPUT);
String communityId = newCommunityUrl.substring(newCommunityUrl.indexOf("communityUuid=")
+ "communityUuid=".length());
community.setCommunityUuid(communityId);
return community;
}
}

WebApi Odata Windows Store App EndSaveChanges exception

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;

Using the WebApiContrib ProtoBufFormatter on the Client Side

I'm using the WebApiContrib.Formatting.ProtoBuf NuGet package (http://nuget.org/packages/WebApiContrib.Formatting.ProtoBuf/0.9.5.0) to add support for Protocol Buffers in my Web API project.
The server side seems to work great, but I've been unable to get the Web API Client libraries to deserialize a server response.
System.InvalidOperationException: Type is not expected, and no contract can be inferred: System.Collections.Generic.IEnumerable`1[[WebAPITest1.Protocol.Messages.Product, WebAPITest1.Protocol.Messages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Here is my client code:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(#"http://localhost:60500/");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
var response = httpClient.GetAsync("/api/products?$orderby=Name").Result;
Assert.IsTrue(response.IsSuccessStatusCode);
var products = response.Content.ReadAsAsync<IEnumerable<Product>>(new[]{new ProtoBufFormatter()}).Result;
Assert.IsTrue(products.Count() > 0);
Ideas?
Have you tried with something other than IEnumerable<Product>? In particular, maybe a top-level object that has a set of Products ? For example:
[ProtoContract]
public class NeedsBetterName {
[ProtoMember(1)]
public List<Product> Products {get;set;}
}
That might be happier. Actually, I added better support for "naked enumerables" earlier this month (in r629) - however, I have not yet deployed this build anywhere.

Resources