Xamarin Forms - calling rest service from viewmodel of pcl - xamarin

I created a class that connected to the API to retrieve the required data using httpclient. That file was called in the code behind file of the view and worked perfectly. Than I decided to implement the MVVM approach. As a result, I moved the code that initialized the rest service class to the view-model.
After doing that, i stopped getting the data. To investigate, I stated the the debugging session with the breakpoint placed at the line where i initialize the rest service class. Than i executed that line. By doing that, I found out that a huge android mono exception is thrown and the debugging session if stopped. The app exits the debugging session.
This has happened for the first time since i stated developing my app in Xamarin Forms. I have no idea about why it is breaking like that. Your help will be greatly appreciated.
This is the code that was working properly.
In the view code behind file
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SubtaskPage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
PopulateSubtaskData();
}
private async void PopulateSubtaskData()
{
lstSubtasks.IsRefreshing = true;
try
{
RestService rs = new RestService();
SResponse = await rs.GetSubtasksAsync(Convert.ToInt32(Application.Current.Properties["UserId"]));
if (SResponse.Status == 1)
{
lstSubtasks.ItemsSource = SResponse.Subtasks;
}
else
{
await DisplayAlert("Error", SResponse.Message, "Ok");
}
}
catch (Exception E)
{
Debug.WriteLine(#"GetSubtasksAsync -> ERROR {0}", E.Message);
}
lstSubtasks.IsRefreshing = false;
}
}
The rest service class is as follows
This class is in a separate folder named "Services". ip and url have been changed for security reason.
class RestService
{
HttpClient client;
public List<Ticket> Tickets { get; private set; }
string Server1 = "server ip";
string Server2 = "server ip";
public RestService()
{
client = new HttpClient();
client.MaxResponseContentBufferSize = 256000;
}
public async Task<SubtasksResponse> GetSubtasksAsync(int UserId)
{
SubtasksResponse SubtaskResponse = new SubtasksResponse();
string ApiUrl = "URL";
string Url = "";
HttpResponseMessage response;
if (CrossConnectivity.Current.IsConnected)
{
Url = await GetActiveServerAsync();
if (Url != "")
{
var uri = string.Format(Url + ApiUrl, UserId);
try
{
response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
SubtaskResponse.Subtasks = JsonConvert.DeserializeObject<List<Ticket>>(content);
SubtaskResponse.Status = 1;
}
else
{
SubtaskResponse.Subtasks = null;
SubtaskResponse.Status = 0;
SubtaskResponse.Message = "Attempt to fetch data from server was unsuccessful. Please try again";
}
}
catch (Exception E)
{
SubtaskResponse.Subtasks = null;
SubtaskResponse.Status = 0;
SubtaskResponse.Message = "Error occured while fetching data from the server. Please try again";
}
}
else
{
SubtaskResponse.Subtasks = null;
SubtaskResponse.Status = 0;
SubtaskResponse.Message = "Remote Server Not Responding! Please try again later";
}
}
else
{
SubtaskResponse.Subtasks = null;
SubtaskResponse.Status = 0;
SubtaskResponse.Message = "No Network Connection Found! Please connect to a network and try again";
}
return SubtaskResponse;
}
}
}
This was working fine until I added the view model into the mix.
This is how I am calling the function in the view model.
async Task<SubtasksResponse> PopulateSubtaskList()
{
RestService rs = new RestService();
IsBusy = true;
_subtaskList = await rs.GetSubtasksAsync(Convert.ToInt32(Application.Current.Properties["UserId"]));
IsBusy = false;
return _subtaskList;
}
"RestService rs = new RestService();" this is the line where the code breaks.
Here is the image of the exception that occurs when the code breaks.
Hope you get the clear picture of the situation. Please let me know if additional information is required.
Thanks

Don't do this. If you want to call rest from a mvvm Xamarin Forms app I can advice Refit. All the difficult work is already done for you and abstracted away. With a few lines of code you are up and running.

BTW the error message you are showing probably has nothing to do with your code but is a bug in a recent Xamarin version. See here: https://bugzilla.xamarin.com/show_bug.cgi?id=56787

Found the answer on this page (https://releases.xamarin.com/common-issues-in-the-xamarin-15-2-2-release-being-tracked-by-the-xamarin-team/).
The solution is as follows
Download the missing Mono.Posix file and unzip the archive.
Right-click the Mono.Posix.dll file in Explorer and select Properties.
Check the Digital Signatures tab to ensure the file shows a valid Xamarin Inc. signature.
At the bottom of the General tab, if an Unblock checkbox appears, enable it and select OK. (This checkbox appears depending on how the file was downloaded.)
For Visual Studio 2017, copy the Mono.Posix.dll file into the “Xamarin.VisualStudio” extension directory. For example, for a default installation of the Enterprise edition, copy the file into:
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\Extensions\Xamarin.VisualStudio
For Visual Studio 2015, copy the file into the “Xamarin\Xamarin” extension directory:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\Xamarin\Xamarin\
Quit and restart Visual Studio.
For more detail, visit the link given above.

Related

Client-Side error when uploading image on server ASP.NET Core

I am struggling with uploading an image from thew client-side to a folder on the server-side in .Net Core.I used Postman to check if the method on the server-side is working and it does without any problem,but when I try to upload an image from the client-side,I get an error on the server-side of type NullReferenceException:Object reference not set to an instance of an object.This is the Post method on the server-side:
[HttpPost]
public async Task Post(IFormFile file)
{
if (string.IsNullOrWhiteSpace(_environment.WebRootPath))
{
_environment.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
}
var uploads = Path.Combine(_environment.WebRootPath, "uploads");
//var fileName = file.FileName.Split('\\').LastOrDefault().Split('/').LastOrDefault();
if (!Directory.Exists(uploads)) Directory.CreateDirectory(uploads);
if (file.Length > 0)
{
using (var fileStream = new FileStream(Path.Combine(uploads, file.FileName), FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
}
}
Apparently the method is thrown where I check if the length of the file is bigger than 0.On the client-side I get error "500 internal server error" and I tried to check using the debugger where exactly the error is thrown but i can't find anything that could resemble an error of some sort.This is the API method for the client-side:
public async Task UploadPictureAsync(MediaFile image)
{
User user = new User();
string pictureUrl = "http://10.0.2.2:5000/api/UploadPicture";
HttpContent fileStreamContent = new StreamContent(image.GetStream());
// user.Picture=GetImageStreamAsBytes(image.GetStream());
fileStreamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") {FileName=Guid.NewGuid() + ".Png",Name="image"};
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
using (var client = new HttpClient(clientHandler))
{
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileStreamContent);
var response = await client.PostAsync(pictureUrl, formData);
if(response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
}
}
}
}
The image is declared in the Model as byte array:
public byte[] Picture { get; set; }
Does someone understand why my POST method has this behavior since the server-side works perfectly but fails when I try to upload an image from the client-side?What I find weird though is that when i read the error and I look at the Content-Type it is "text/plain" instead of "form-data" and I have tried to set it at the MutipartFormDataContent like this:
formData.Headers.ContentType.MediaType = "multipart/form-data";
I also tried to set the MediaTypeHeaderValue on the client like this:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
I still get the wrong content type.
I have also tried a different approach with Stream instead of MediaFile but without any luck as it did not even hit the break point in debugger mode for the response.Any help would be appreciated! :)
I have managed to find the answer finalllyyyyy!!!The problem was on the client-side as I suspected and guess what,it was all about the correct name.It turns out that since on the server side I have IFormFile file I had to change the client side to take the parameter name "file" instead of image as well so that it could work.Thank you #Jason for the suggestions as I didn't understand the error from the first place and did some debugging on the server-side to help me figure it out.

Various errors using VisionServiceClient in XamarinForms

I am trying to create a simple Xamarin forms app which allows the user to browse for or take a photo and have azure cognitive services tag the photo using a custom vision model.
I am unable to get the client to successfully authenticate or find a resource per the error message in the exception produced by the VisionServiceClient. Am I missing something? What would be the correct values to use for the arguments to VisionServiceClient?
All keys have been removed from the below images, they are populated.
Exception thrown in VS2017:
'Microsoft.ProjectOxford.Vision.ClientException' in System.Private.CoreLib.dll
Call to VisionServiceClient:
private const string endpoint = #"https://eastus2.api.cognitive.microsoft.com/vision/prediction/v1.0";
private const string key = "";
VisionServiceClient visionClient = new VisionServiceClient(key, endpoint);
VisualFeature[] features = { VisualFeature.Tags, VisualFeature.Categories, VisualFeature.Description };
try
{
AnalysisResult temp = await visionClient.AnalyzeImageAsync(imageStream,
features.ToList(), null);
return temp;
}
catch(Exception ex)
{
return null;
}
VS Exception Error:
Azure Portal for cognitive services:
Custom Vision Portal:
It looks like you're confusing the Computer Vision and the Custom Vision APIs. You are attempting to use the client SDK for the former using the API key of the latter.
For .NET languages, you'll want the Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction NuGet package.
Your code will end up looking something like this:
ICustomVisionPredictionClient client = new CustomVisionPredictionClient()
{
ApiKey = PredictionKey,
Endpoint = "https://southcentralus.api.cognitive.microsoft.com"
};
ImagePrediction prediction = await client.PredictImageAsync(ProjectId, stream, IterationId);
Thank you to cthrash for the extended help and talking with me in chat. Using his post along with a little troubleshooting I have figured out what works for me. The code is super clunky but it was just to test and make sure I'm able to do this. To answer the question:
Nuget packages and classes
Using cthrash's post I was able to get both the training and prediction nuget packages installed, which are the correct packages for this particular application. I needed the following classes:
Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction
Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction.Models
Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training
Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training.Models
Endpoint Root
Following some of the steps Here I determined that the endpoint URL's only need to be the root, not the full URL provided in the Custom Vision Portal. For instance,
https://southcentralus.api.cognitive.microsoft.com/customvision/v2.0/Prediction/
Was changed to
https://southcentralus.api.cognitive.microsoft.com
I used both the key and endpoint from the Custom Vision Portal and making that change I was able to use both a training and prediction client to pull the projects and iterations.
Getting Project Id
In order to use CustomVisionPredictionClient.PredictImageAsync you need a Guid for the project id and an iteration id if a default iteration is not set in the portal.
I tested two ways to get the project id,
Using project id string from portal
Grab the project id string from the portal under the project settings.
For the first argument to PredictImageAsync pass
Guid.Parse(projectId)
Using the training client
Create a new CustomVisionTrainingClient
To get a list of <Project> use
TrainingClient.GetProjects().ToList()
In my case I only had a single project so I would just need the first element.
Guid projectId = projects[0].Id
Getting Iteration Id
To get the iteration id of a project you need the CustomVisionTrainingClient.
Create the client
To get a list of <Iteration> use
client.GetIterations(projectId).ToList()
In my case I had only a single iteration so I just need the first element.
Guid iterationId = iterations[0].Id
I am now able to use my model to classify images. In the code below, fileStream is the image stream passed to the model.
public async Task<string> Predict(Stream fileStream)
{
string projectId = "";
//string trainingEndpoint = "https://southcentralus.api.cognitive.microsoft.com/customvision/v2.2/Training/";
string trainingEndpoint = "https://southcentralus.api.cognitive.microsoft.com/";
string trainingKey = "";
//string predictionEndpoint = "https://southcentralus.api.cognitive.microsoft.com/customvision/v2.0/Prediction/";
string predictionEndpoint = "https://southcentralus.api.cognitive.microsoft.com";
string predictionKey = "";
CustomVisionTrainingClient trainingClient = new CustomVisionTrainingClient
{
ApiKey = trainingKey,
Endpoint = trainingEndpoint
};
List<Project> projects = new List<Project>();
try
{
projects = trainingClient.GetProjects().ToList();
}
catch(Exception ex)
{
Debug.WriteLine("Unable to get projects:\n\n" + ex.Message);
return "Unable to obtain projects.";
}
Guid ProjectId = Guid.Empty;
if(projects.Count > 0)
{
ProjectId = projects[0].Id;
}
if (ProjectId == Guid.Empty)
{
Debug.WriteLine("Unable to obtain project ID");
return "Unable to obtain project id.";
}
List<Iteration> iterations = new List<Iteration>();
try
{
iterations = trainingClient.GetIterations(ProjectId).ToList();
}
catch(Exception ex)
{
Debug.WriteLine("Unable to obtain iterations.");
return "Unable to obtain iterations.";
}
foreach(Iteration itr in iterations)
{
Debug.WriteLine(itr.Name + "\t" + itr.Id + "\n");
}
Guid iteration = Guid.Empty;
if(iterations.Count > 0)
{
iteration = iterations[0].Id;
}
if(iteration == Guid.Empty)
{
Debug.WriteLine("Unable to obtain project iteration.");
return "Unable to obtain project iteration";
}
CustomVisionPredictionClient predictionClient = new CustomVisionPredictionClient
{
ApiKey = predictionKey,
Endpoint = predictionEndpoint
};
var result = await predictionClient.PredictImageAsync(Guid.Parse(projectId), fileStream, iteration);
string resultStr = string.Empty;
foreach(PredictionModel pred in result.Predictions)
{
if(pred.Probability >= 0.85)
resultStr += pred.TagName + " ";
}
return resultStr;
}

System.MissingMethodException Method 'System.Net.Http.HttpClientHandler.set_Proxy' not found

This is a Xamarin solution and I am getting the error found in this message's title. Of course, I can easily confirm that there is a Proxy property on HttpClientHandler in the PCL project. And the solution builds without error. Only when I run does it produce this error (on either Droid or iOS) and does so at the point where it invokes the method in the PCL which instantiates the HttpClient. Note that it doesn't even get to that method. The error appears on the application start-up method; e.g., UIApplication.Main()
If I comment out the handler and instantiate HttpClient without a handler, it works fine as long as I'm on the open internet. But I'm trying to get this to work from behind a proxy.
Further investigation showed that the device projects had no references to System.Net.Http. So I added these -- and it indicates Xamarin.iOS and Xamarin.Android as the packages -- but it still produces the error.
I'm not clear what the error is telling me but I believe it means that the device project can't see System.Net.Http.HttpClientHandler?
private HttpClient GetHttpClient()
{
WebProxy proxy = new WebProxy(ProxyConfig.Url)
{
Credentials = new NetworkCredential(ProxyConfig.Username, ProxyConfig.Password)
};
// At runtime, when GetHttpClient is invoked, it says it cannot find the Proxy setter
HttpClientHandler handler = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true,
PreAuthenticate = true,
UseDefaultCredentials = false,
};
HttpClient client = new HttpClient(handler);
// This works when not behind a proxy
//HttpClient client = new HttpClient();
return client;
}
public async Task GetWeatherAsync(double longitude, double latitude, string username)
{
// MissingMethodException is thrown at this point
var client = GetHttpClient();
client.BaseAddress = new Uri(string.Format("http://api.geonames.org/findNearByWeatherJSON?lat={0}&lng={1}&username={2}", latitude, longitude, username));
try
{
var response = await client.GetAsync(client.BaseAddress);
if (response.IsSuccessStatusCode)
{
var JsonResult = response.Content.ReadAsStringAsync().Result;
var weather = JsonConvert.DeserializeObject<WeatherResult>(JsonResult);
SetValues(weather);
}
else
{
Debug.WriteLine(response.RequestMessage);
}
}
catch (HttpRequestException ex)
{
Debug.WriteLine(ex.Message);
}
catch (System.Net.WebException ex)
{
Debug.WriteLine(ex.Message);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Add the Microsoft.Net.Http NuGet package to your platform project too. If you run into an issue adding this, try installing the latest Microsoft.Bcl.Build package first. Then, after that is installed, add the HTTP package.

App.OnResume error in Xamarin forms on Android and IOS devices

We are using xamarin forms. After an Android or IOS device resumes from background, we are making a REST call in .net that is being triggered by a timer. The first attempt on IOS returns a "The Descriptor is not a socket" error and the Android returns a "Connection refused" error. The same code works fine in Windows. Future attempts (every few seconds) in all 3 platforms work fine. Has anyone seen this and have a fix?
Code
//app on resume event
protected async override void OnResume()
{
// Handle when your app resumes
if (MainPage is RootPage)
{
RootPage mainPage = MainPage as RootPage;
if (mainPage.Detail is NavigationPage)
{
NavigationPage nvPage = mainPage.Detail as NavigationPage;
if(nvPage.CurrentPage is ThingsPage)
{
ThingsPage thPage = nvPage.CurrentPage as ThingsPage;
thPage.TurnOnTimer();
}
}
}
}
//code on the page
public void TurnOnTimer()
{
if (viewModel != null)
{
viewModel.ContinueTimer = true;
viewModel.StartAnotherTimer();
}
}
//code in view model
public async void StartAnotherTimer()
{
while (ContinueTimer)
{
try
{
DevicesUpdate devicesUpdate = await DataSource.GetDevices(LocationID, ControllerID, lastDevicesUpdateReceivedAt);
}
catch (Exception ex)
{
}
// Update the UI (because of async/await magic, this is still in the UI thread!)
if (ContinueTimer)
{
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
}
public static async Task<DevicesUpdate> GetDevices(Guid locationID, Guid controllerID, DateTime lastUpdateReceivedAt)
{
DevicesUpdate devicesUpdate = await GetLastUpdatedDevices(controllerID, lastUpdateReceivedAt);
}
//code in view model
public static async Task<DevicesUpdate> GetLastUpdatedDevices(Guid controllerID,
DateTime lastUpdate)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
string url = string.Format("http://appname.azurewebsites.net/api/devices?controllerid={1}&lastUpdate={2}"
, Constants.WebServerURL, controllerID, lastUpdate);
System.Net.Http.HttpResponseMessage response = await client.GetAsync(new Uri(url));
string result = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
DevicesUpdate devices = JSONHelper.Deserialize<DevicesUpdate>(result);
return devices;
}
else
{
if (response.ReasonPhrase == "UserException")
{
throw new UserException(result);
}
else
{
//throw error because the response from rest api is not a success
throw new System.Net.Http.HttpRequestException(result);
}
}
}
You might have a few things happening here that's causing problems.
GetDevices doesn't return anything. (I hope you just left out the return for brevity sake)
You are never setting ContinueTimer to false.
What iOS version are you on? In later versions, you HAVE to use HTTPS or explicitly allow non-secure connections. This shouldn't be a problem because Azure has ssl.
If you plan on running this in the background, you need to register your app as a background process.
If you don't plan on running this in the background, you might have issues with previous attempts being ran (or still trying to execute, or just have failed) and then calling more.
What is the reason for calling the 3 second timer for the network calls? What if the call takes more than 3 seconds (then you are making duplicate calls even though the first might succeed).
If you want to make your network calls more robust, check out this Blog Post by Rob Gibbons about resilient network calls.
First thing I would do is remove it from the timer because it seems like the underlying sockets are having issues cross-thread.

Show ErrorList Windows in Visual Studio 2010 Addin

I have Win7 64 bits, Visual Studio 2010, and I have developed an Addin for Vs2010.
I try show messages in Error List Windows VS.
I use ErrorListProvider in OnBuildProjConfigDone build event for Addin
this._buildEvents.OnBuildProjConfigDone += new _dispBuildEvents_OnBuildProjConfigDoneEventHandler(_buildEvents_OnBuildProjConfigDone);
I get this error InvalidOperationException
The service 'Microsoft.VisualStudio.Shell.Interop.IVsTaskList' must be
installed for this feature to work. Ensure that this service is
available.
Connect
public partial class Connect : IDTExtensibility2, IDTCommandTarget, System.Windows.Forms.IWin32Window, IOleCommandTarget
OnBuildProjConfigDone
void _buildEvents_OnBuildProjConfigDone(string project, string projectConfig, string platform, string solutionConfig, bool success)
{
// Omitted
if (!resul)
{
project.DTE.ExecuteCommand("Build.Cancel");
var errorListHelper = new ErrorListHelper();
ErrorListProvider errorProvider = errorListHelper.GetErrorListProvider();
var newError = new ErrorTask();
newError.ErrorCategory = TaskErrorCategory.Message;
newError.Category = TaskCategory.BuildCompile;
newError.Text = "Cualquier mensaje de error aqui";
errorProvider.Tasks.Add(newError);
}
}
ErrorListHelper
public class ErrorListHelper : System.IServiceProvider
{
public ErrorListProvider GetErrorListProvider()
{
ErrorListProvider provider = new ErrorListProvider(this);
provider.ProviderName = "Provider";
provider.ProviderGuid = System.Guid.NewGuid();
return provider;
}
public object GetService(Type serviceType)
{
return Package.GetGlobalService(serviceType);
}
}
Suggestion by #JohnL: I put a breakpoint in my GetService method and Package.GetGlobalService is returning null.
Any suggestions?
Ryan Molden (MSFT) says:
Package.GetGlobalService is relying on at least one MPF package (from
the specific version of MPF you are referencing) having been loaded.
Since you yourself are an AddIn not a Package you can't guarantee that
in any way.
You should pass something like new
ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider))) as the argument to ErrorListProvide
Package.GetGlobalService is returning null.
I use this code in my Addin. I test it and I get not error, and I can show errors and warnings in ErrorList Windows VS. I'll testing more for safely.
public partial class Connect
{
ErrorListProvider _errorListProvider = null;
void CreateErrorListProvider()
{
if (_errorListProvider == null)
{
System.IServiceProvider serviceProvider = new ServiceProvider(_applicationObject as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
_errorListProvider = new ErrorListProvider(serviceProvider);
_errorListProvider.ProviderName = "custom Errors";
_errorListProvider.ProviderGuid = new Guid("xxxxxxxxxxxxxx");
}
}

Resources