JSON SerializeObject fails to serialize class with abstract member on iOS hardware - xamarin.forms

When I try to run my Xamarin Forms application on an actual iPhone, it fails with a System.MissingMethodException: "Constructor on type 'System.ComponentModel.ReferenceConverter' not found."
It works fine on the iOS Simulators as well as on Android hardware and virtual devices.
I've been able to narrow it down to the following scenario: serializing an object with an interface referenced member:
public class Widget {
public ISprocket Sprocket { get; set; };
}
public interface ISprocket {
int SprocketId { get; set; }
}
Executing the lines:
var w = new Widget { Sprocket = new Sprocket { SprocketId = 1 } };
string result = JsonConvert.SerializeObject(w);
works everywhere (seemingly) except on my iPhone 8+, where it throws a System.MissingMethodException
I'm using the most current versions of XF (2.5.0.122203) and JSON.NET (10.0.3).
To recreate this problem, create a new Xamarin Forms solution with an iOS client called "TestBed". Add Newtonsoft.Json via nuget, then create a page
"TestBedPage" with the following content:
using System;
using Newtonsoft.Json;
using Xamarin.Forms;
namespace TestBed
{
public class TestBedPage : ContentPage
{
public TestBedPage()
{
try
{
var m = new Widget { Sprocket = new Sprocket { SprocketId = 1 } };
var st = JsonConvert.SerializeObject(m);
Content = new Label { Text = st };
} catch (Exception ex) {
Content = new Label { Text = ex.ToString() };
}
}
}
public class Widget
{
public string Title { get; set; } = string.Empty;
public ISprocket Sprocket { get; set; }
}
public interface ISprocket
{
int SprocketId { get; set; }
}
public class Sprocket : ISprocket
{
public int SprocketId { get; set; }
}
}
Deploy it to an iPhone and run. You should get a screenful of MissingMethodException stack trace.
Does anyone have any idea what's going on here?

The issue is that something is trying to instantiate via Activator class an instance of System.ComponentModel.ReferenceConverter via its System.Type ctor but since it isn't referenced directly in you application it gets removed by the managed linker.
You need to provide a custom linker configuration file ,add it to your Xamarin.iOS project.
Refer to here

Related

Prism.Unity.Forms 8.1 container.resolve stoped working after upgrade from version 7.2

I'm not a developer, but I managed to make an Xamarin.forms android app with prism 7.2, but recently I needed to add more features to the app and updated all NuGet packages, which updated Prism to version 8.
After the upgrade when I try to resolve on a tabbedpage:
_unityContainer.Resolve<IMyTabbedPageSelectedTab>().SetSelectedTab(0);
I get the following Unity.ResolutionFailedException: 'Resolution failed with error: No public constructor is available for type XXX.Interfaces.IMyTabbedPageSelectedTab.'
My interface is:
public interface IMyTabbedPageSelectedTab
{
int SelectedTab { get; set; }
void SetSelectedTab(int tabIndex);
}
My MainPageViewModel:
public class MainPageViewModel : ViewModelBase, IMyTabbedPageSelectedTab
{
private readonly IUnityContainer _unityContainer;
private int _selectedTab;
public int SelectedTab
{
get { return _selectedTab; }
set
{
SetProperty(ref _selectedTab, value);
Title = $"My Tabbed Page - Tab [{SelectedTab + 1}]";
}
}
public MainPageViewModel(INavigationService navigationService, IUnityContainer unityContainer): base(navigationService)
{
Title = $"My Tabbed Page - Tab [{SelectedTab + 1}]";
this._unityContainer = unityContainer;
_unityContainer.RegisterInstance<IMyTabbedPageSelectedTab>(this, new ContainerControlledLifetimeManager());
}
public void SetSelectedTab(int tabIndex)
{
SelectedTab = tabIndex;
}
}
Can someone shed some light on what may be the problem?

How to get data from Web API to Xamarin forms Picker?

I'm developing a Xamarin APP and I want to load a Picker with data from a Web API that has Server Database. I tried to Google this but most of the articles don't show the content of source "Services class" that use Get async Method, Model class and ViewModel. I would be very grateful if someone could help me with an example.
This is my Controller in ASP.NET Web API
// GET: api/TipoUsers
public IQueryable<TipoUser> GetTipoUsers()
{
return db.TipoUsers;
}
Model class
public class TipoUsuario
{
public int IdTipoUsuario { get; set; }
public string Nome { get; set; }
}
ViewModel class
public class UsuarioViewModel
{
public ObservableCollection<TipoUsuario> tipos { get; set; }
public UsuarioViewModel() {
Task<List<TipoUsuario>> task = ApiService.ObterTipoUsuarios();
tipos = new ObservableCollection<TipoUsuario>(task.Result);
}
}
Xaml Page
<Picker Title="Selecione o Tipo de Usuario"
ItemsSource="{Binding tipos}"
ItemDisplayBinding="{Binding Nome}"/>
Xaml.cs
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class UsuarioPage : ContentPage
{
public UsuarioPage()
{
InitializeComponent();
BindingContext = new UsuarioViewModel();
}
}
}
Service class
public class ApiService
{
public const string Url = "http://thisismysite:44342/";
public static async Task<List<TipoUsuario>> GetTipoUsers()
{
try
{
HttpClient client = new HttpClient();
string url = Url + "/api/TipoUsers";
string response = await client.GetStringAsync(url);
List<TipoUsuario> tipos = JsonConvert.DeserializeObject<List<TipoUsuario>>(response);
return tipos;
}
catch (Exception)
{
throw;
}
}
}
when I debug the app it just doesn't load the screen.
This can happen for a few reasons, I would check your async method isn’t throwing an exception that you aren’t able to see. Async methods return a Task object and if an exception is thrown inside it will be visible in the returned object in Task.Exception.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library
Also property changed events aren’t called when you set an ObserableCollection to a new instance, you want to add and remove from the collection.
You want to change:
public UsuarioViewModel() {
Task<List<TipoUsuario>> task = ApiService.ObterTipoUsuarios();
tipos = new ObservableCollection<TipoUsuario>(task.Result);
}
to something like:
public UsuarioViewModel() {
Task<List<TipoUsuario>> task = ApiService.ObterTipoUsuarios();
var temptipos = task.Result;
foreach(var tipo in temptipos)
{
tipos.Add(tipo);
}
}

Auto Mapper Constructor initialization Mapping Issue

I have the following Mapping configurations:-
Initialized Data:-
private static IEnumerable<Source> InitializeData()
{
var source= new[]
{
new Source("John", "Doe", "1111111111"),
new Source("Jack", "Handsome", "2222222222"),
new Source("Joe", "Mackenze", "3333333333")
};
return source;
}
Source Model:
public class Source
{
private string First { get; set; }
private string Last { get; set; }
private string Phone { get; set; }
public Source(string first, string last, string phone)
{
First = first;
Last = last;
Phone = phone;
}
}
Destination Model
public class Destination
{
public string First { get; set; }
public string Last { get; set; }
public string Phone { get; set; }
}
Main
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.AllowNullCollections = true;
cfg.CreateMap<Source, Destination>().ReverseMap();
});
var mapper = new Mapper(config);
var source= InitializeData();
var people = mapper.Map<IEnumerable<Destination>>(source);
foreach (var p in people)
{
Console.WriteLine("Name: {0}-{1} Phone: {2}", p.First, p.Last, p.Phone);
}
Console.ReadLine();
}
Problem descriptions:
I have been struggled to understand the AutoMapper mapping between source and destination models.
My source model has a constructor to initialize or accept data from outside. It works fine when I removed the source constructor from the model that's mean flat mapping works fine but constructor initialization has the issue. When I debug in VS2019, it shows the number of records but all fields are empty/null.
What is wrong with the above mapping. I have gone through the AutoMapper reference docs but do not get a hold on this issue.
I highly appreciate your help!
Try calling AssertConfigurationIsValid. Check http://docs.automapper.org/en/latest/Configuration-validation.html.
Your Source properties are private. I assume you meant public.

NullReferenceException for DependencyService on Android

I am attempting to get the phone service information on a Xamarin project.
When I call var carrierHelper = DependencyService.Get<ICarrierHelper>();, I get a System.NullReferenceException: Object reference is not set to an instance of an object.
ICarrierHelper.cs
using System;
namespace MyProject
{
public interface ICarrierHelper
{
string GetPhoneCarrier();
}
}
MyProject.Droid/Helpers/CarrierHelper.cs
using MyProject.Droid;
using Android.Content;
using Android.Telephony;
using Xamarin.Forms;
[assembly: Dependency(typeof(CarrierHelper))]
namespace MyProject.Droid
{
public class CarrierHelper : ICarrierHelper
{
public string GetPhoneCarrier()
{
Console.WriteLine("****************************** MAIN ACTIVITY " + MainActivity.Current.ToString());
TelephonyManager mgr = MainActivity.Current.GetSystemService(Context.TelephonyService) as TelephonyManager;
Console.WriteLine("****************************** TELEPHONY MANAGER " + mgr.ToString());
return mgr.NetworkOperatorName;
//return mgr.SimOperatorName;
}
}
}
I have breakpoints in the Droid CarrierHelper.cs that never catch. So, I can only assume that the DependencyService can't find my file at all.
UPDATE (Adding more information and console logs.)
MyDevice.cs
using System;
using Plugin.DeviceInfo;
using Xamarin.Forms;
namespace MyProject
{
public class MyDevice
{
private static MyDevice current;
public static MyDevice Current
{
get
{
if (current == null)
current = new MyDevice();
return current;
}
}
public string platform { get; set; }
public string manufacturer { get; set; }
public string model { get; set; }
public string name { get; set; }
public string os_version { get; set; }
public string carrier { get; set; }
public string app_version { get; set; }
public string app_build { get; set; }
public MyDevice()
{
switch (Device.RuntimePlatform)
{
case Device.iOS:
platform = "iOS";
break;
case Device.Android:
platform = "Android";
break;
default:
platform = "UNKNOWN";
break;
}
manufacturer = CrossDeviceInfo.Current.Manufacturer;
model = CrossDeviceInfo.Current.Model;
name = CrossDeviceInfo.Current.DeviceName;
os_version = CrossDeviceInfo.Current.Version;
app_version = CrossDeviceInfo.Current.AppVersion;
app_build = CrossDeviceInfo.Current.AppBuild;
var carrierHelper = DependencyService.Get<ICarrierHelper>();
Console.WriteLine("----- CARRIER HELPER " + carrierHelper);
carrier = DependencyService.Get<ICarrierHelper>().GetPhoneCarrier();
Console.WriteLine("----- CARRIER: " + carrier);
}
}
}
The following Console logs print:
----- CARRIER HELPER MyProject.Droid.CarrierHelper
UPDATE (Adding stack trace and info.)
Here is the stack trace:
at MyProject.Droid.CarrierHelper.GetPhoneCarrier () [0x00001] in /MyProject/Helpers/CarrierHelper.cs:14
at MyProject.MyDevice..ctor () [0x000d2] in /MyProject.Core/Models/MyDevice.cs:57
at MyProject.MyDevice.get_Current () [0x0000d] in /MyProject.Core/Models/MyDevice.cs:15
at MyProject.UserDataStore+d__16.MoveNext () [0x00057] in /MyProject.Core/Services/UserDataStore.cs:197
UserDataStore.cs
public async Task UpdateLocation(double lat, double lng)
{
try
{
if (!CrossConnectivity.Current.IsConnected)
throw new Exception("Not currently connected to the internet.");
var model = new
{
access_token = $"{Settings.AuthToken}",
latitude = lat,
longitude = lng,
device = MyDevice.Current,
format = "json"
};
var json = JsonConvert.SerializeObject(model);
var location_content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync(users_url + location_url, location_content);
if (!response.IsSuccessStatusCode)
throw new Exception(response.StatusCode.ToString() + " " + response.ReasonPhrase);
}
catch (Exception e)
{
Console.WriteLine("ERROR: " + e.Message); // this is where the stack trace is generated
await LocationDatabase.Connection.SaveLocationAsync(new Location(lat, lng));
await AppEventDatabase.Connection.SaveEventAsync(new AppEvent("location", e.Message, lat, lng));
}
}
This is the error message from the file above:
ERROR: Object reference not set to an instance of an object.
You have a catch-22 situation. GetPhoneCarrier() requires MyDevice.Current which is implemented via a lazy-loaded constructor for MyDevice. That constructor resolves ICarrierHelper and calls GetPhoneCarrier(), which requires MyDevice.Current...
I'm assuming that the resolution works out that it has already tried resolving that, and it hasn't completed, so it returns null.
I would suggest that you do less in the MyDevice constructor. Avoid resolving that dependency until you actually need it. That way, the constructor can complete, avoiding the catch-22.
Also beware that your approach to lazy loading isn't thread safe. Consider using Lazy<T> which nicely encapsulates double-checked thread-safe locking.

Web API error failed to serialize the response body

Im fairly new to ASP.NET MCV 4 as well as Mongo DB and trying to build web API.
I thought I had finally got it right but when I start the app and enter: http://localhost:50491/api/document into my browser I get this error message
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
Here is my code
This is the Document Class
public class Document
{
[BsonId]
public ObjectId DocumentID { get; set; }
public IList<string> allDocs { get; set; }
}
This is where the Connection to the DB is made:
public class MongoConnectionHelper
{
public MongoCollection<BsonDocument> collection { get; private set; }
public MongoConnectionHelper()
{
string connectionString = "mongodb://127.0.0.1";
var server = MongoServer.Create(connectionString);
if (server.State == MongoServerState.Disconnected)
{
server.Connect();
}
var conn = server.GetDatabase("cord");
collection = conn.GetCollection("Mappings");
}
Here is the ApiController Class:
public class DocumentController : ApiController
{
public readonly MongoConnectionHelper docs;
public DocumentController()
{
docs = new MongoConnectionHelper();
}
public IList<BsonDocument> getAllDocs()
{
var alldocs = (docs.collection.FindAll().ToList());
return alldocs;
}
}
I read futher on and the error message suggested:
Type 'MongoDB.Bson.BsonObjectId' with data contract name 'BsonObjectId:http://schemas.datacontract.org/2004/07/MongoDB.Bson' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
That is all good and well but how do I do that?
Either a) don't serialize your document classes over Web API, and create some DTOs meant to be serialized, or b) use something else as ID.
If you want an easy auto-generated ID, and you're OK with the fact that it will consume slightly more space, you can resort to the following "hack":
public class Document
{
public Document()
{
Id = ObjectId.GenerateNewId().ToString();
}
public string Id { get; set; }
}
This way, you'll get MongoIDs, but they'll be stored as a string.
If you need Web API2 responce in XML format , you need to handle the default Id like below
eg: ObjectId("507f191e810c19729de860ea")
Either you need to remove the Id from serialization.
[DataContract]
public class Document
{
[BsonId]
public string Id { get; set; }
[DataMember]
public string Title { get; set; } //other properties you use
}
Or You can change the Type of ID with custom logic
public class GuidIdGenerator : IIdGenerator
{
public object GenerateId(object container, object document)
{
return Guid.NewGuid();
}
public bool IsEmpty(object id)
{
return string.IsNullOrEmpty(id.ToString());
}
}
public class Document
{
[BsonId(IdGenerator = typeof(GuidIdGenerator))]
public string Id { get; set; }
public string Title { get; set; } //other properties you use
}

Resources