Xamarin.Forms Consume Rest Service - rest

I'm new to Xamarin and developing native apps in general (I have made html5 apps in the past).
I have started on a Xamarin.Forms project and I'm trying to contact a REST like API (need to GET an URL which will return a json array).
Normally from C# I would use RestSharp and perform this call using the RestClient.
I'm not having any luck installing that package from Xamarin Studio though, but I have got the Microsoft HTTP Libraries installed.
I'm pretty sure this is a very trivial task to perform, I just haven't been able to adapt the samples I have found online to work for me.
Anyone who could post how this is done please (remember I'm new to this so don't expect me to understand everything that is different from say a normal console app)?

It is easy with HTTP Client and JSON.NET here is a example of a GET:
public async Task<List<Appointment>> GetDayAppointments(DateTime day)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + App.apiToken);
//Your url.
string resourceUri = ApiBaseAddress;
HttpResponseMessage result = await client.GetAsync (resourceUri, CancellationToken.None);
if (result.IsSuccessStatusCode) {
try {
return GetDayAppointmentsList(result);
} catch (Exception ex) {
Console.WriteLine (ex.Message);
}
} else {
if(TokenExpired(result)){
App.SessionExpired = true;
App.ShowLogin();
}
return null;
}
return null;
}
private List<Appointment> GetDayAppointmentsList(HttpResponseMessage result){
string content = result.Content.ReadAsStringAsync ().Result;
JObject jresponse = JObject.Parse (content);
var jarray = jresponse ["citas"];
List<Appointment> AppoinmentsList = new List<Appointment> ();
foreach (var jObj in jarray) {
Appointment newApt = new Appointment ();
newApt.Guid = (int)jObj ["id"];
newApt.PatientId = (string)jObj ["paciente"];
newApt.Name = (string)jObj ["nombre"];
newApt.FatherLstName = (string)jObj ["paterno"];
newApt.MotherLstName = (string)jObj ["materno"];
string strStart = (string)jObj ["horaIni"];
TimeSpan start;
TimeSpan.TryParse (strStart, out start);
newApt.StartDate = start;
string strEnd = (string)jObj ["horaFin"];
TimeSpan end;
TimeSpan.TryParse (strEnd, out end);
newApt.EndDate = end;
AppoinmentsList.Add (newApt);
}
return AppoinmentsList;
}

I use System.Net.WebClient and our asp.net WebAPI interface:
public string GetData(Uri uri)
{//uri like "https://webapi.main.cz/api/root"
string ret = "ERROR";
try
{
using (WebClient webClient = new WebClient())
{
//You can set webClient.Headers there
webClient.Encoding = System.Text.Encoding.UTF8;
ret = webClient.DownloadString(uri));//Test some data received
//In ret you can have JSON string
}
}
catch (Exception ex) { ret = ex.Message; }
return ret;
}
4
public string SendData(Uri uri, byte[] data)
{//uri like https://webapi.main.cz/api/PostCheckLicence/
string ret = "ERROR";
try
{
using (WebClient webClient = new WebClient())
{
webClient.Headers[HttpRequestHeader.Accept] = "application/octet-stream";
webClient.Headers[HttpRequestHeader.ContentType] = "text/bytes";
webClient.Encoding = System.Text.Encoding.ASCII;
byte[] result = webClient.UploadData(uri, data);
ret = Encoding.ASCII.GetString(result);
if (ret.Contains("\"ResultWebApi\":\"OK"))
{//In ret you can have JSON string
}
else
{
}
}
}
catch (Exception ex) { ret = ex.Message; }
return ret;
}
x

I've some examples in my Github repo. Just grab the classes there and give them a try. The API is really easy to use:
await new Request<T>()
.SetHttpMethod(HttpMethod.[Post|Put|Get|Delete].Method) //Obligatory
.SetEndpoint("http://www.yourserver.com/profilepic/") //Obligatory
.SetJsonPayload(someJsonObject) //Optional if you're using Get or Delete, Obligatory if you're using Put or Post
.OnSuccess((serverResponse) => {
//Optional action triggered when you have a succesful 200 response from the server
//serverResponse is of type T
})
.OnNoInternetConnection(() =>
{
// Optional action triggered when you try to make a request without internet connetion
})
.OnRequestStarted(() =>
{
// Optional action triggered always as soon as we start making the request i.e. very useful when
// We want to start an UI related action such as showing a ProgressBar or a Spinner.
})
.OnRequestCompleted(() =>
{
// Optional action triggered always when a request finishes, no matter if it finished successufully or
// It failed. It's useful for when you need to finish some UI related action such as hiding a ProgressBar or
// a Spinner.
})
.OnError((exception) =>
{
// Optional action triggered always when something went wrong it can be caused by a server-side error, for
// example a internal server error or for something in the callbacks, for example a NullPointerException.
})
.OnHttpError((httpErrorStatus) =>
{
// Optional action triggered when something when sending a request, for example, the server returned a internal
// server error, a bad request error, an unauthorize error, etc. The httpErrorStatus variable is the error code.
})
.OnBadRequest(() =>
{
// Optional action triggered when the server returned a bad request error.
})
.OnUnauthorize(() =>
{
// Optional action triggered when the server returned an unauthorize error.
})
.OnInternalServerError(() =>
{
// Optional action triggered when the server returned an internal server error.
})
//AND THERE'S A LOT MORE OF CALLBACKS THAT YOU CAN HOOK OF, CHECK THE REQUEST CLASS TO MORE INFO.
.Start();
And there's a couple of examples.

For all my Xamarin Forms app I use Tiny.RestClient.
It's easy to get it and easy to use it.
You have to download this nuget.
And after it just very easy to use it :
var client = new TinyRestClient(new HttpClient(), "http://MyAPI.com/api");
var cities = client.
GetRequest("City").
AddQueryParameter("id", 2).
AddQueryParameter("country", "France").
ExecuteAsync<City>> ();
Hopes that helps.

Related

Can two ASP . NET Core 5.0 web api cause "The content may be already have been read by another component" errpr 400 if they accessed same db be4?

My API was as follows:
[HttpPut("{id}")]
public async Task<ActionResult<HomeContextModel>> EditHomeContext(int id, string title, string context, string subcontext, IFormFile imageFile)
{
HomeContextModel homeContextModel = await _context.HomeContext.Include(x => x.Image).Include(x => x.Button).Include(x => x.Logo).ThenInclude(y => y.Image)
.FirstOrDefaultAsync(m => m.Context_Id == id);
//HomeContextModel homeContextModel = await GetHomeContextModel(id);
if (homeContextModel == null)
{
return BadRequest("Context Id cannot be null");
}
if (imageFile != null)
{
ImageModel imageModel = homeContextModel.Image;
if (imageModel != null)
{
string cloudDomain = "https://privacy-web.conveyor.cloud";
string uploadPath = _webHostEnvironment.WebRootPath + "\\Images\\";
if (!Directory.Exists(uploadPath))
{
Directory.CreateDirectory(uploadPath);
}
string filePath = uploadPath + imageFile.FileName;
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await imageFile.CopyToAsync(fileStream);
await fileStream.FlushAsync();
}
using (var memoryStream = new MemoryStream())
{
await imageFile.CopyToAsync(memoryStream);
imageModel.Image_Byte = memoryStream.ToArray();
}
imageModel.ImagePath = cloudDomain + "/Images/" + imageFile.FileName;
imageModel.Modify_By = "CMS Admin";
imageModel.Modity_dt = DateTime.Now;
//_context.Update(imageModel);
}
}
homeContextModel.Title = title;
homeContextModel.Context = context;
homeContextModel.SubContext = subcontext;
_context.Entry(homeContextModel).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!HomeContextModelExists(homeContextModel.Context_Id))
{
return NotFound();
}
else
{
throw;
}
}
return Ok("Home Context Edit Successfully");
}
It's an API for the Content Management System (CMS) to change the content of the Homepage using a Flutter webpage that make put request onto this API.
Everything works fine. In the last few days, where I tested and tested again during the development. So before today, I've wrapped up them and submitted to the necessary place (It's a university FYP).
Until now it cause me this error when I was using this to prepare my presentation:
Error 400 failed to read the request form Unexpected end of stream ..."
After all the tested I tried:
Internet solutions
restore the database
repair Microsoft VS 2019 (As this issue was fixed before after I
updated my VS 2019 from 16.8. to the latest 16.11.7)
Use the ASP .NET file which didn't caused this issue before
Then I realized it may be because of I used another older ASP file to accessed the same database before. Does this really cause this matter?
If yes, then now how should I solved it, with the action I already done (listed as above)?
EDIT: Additional description to the situation
The above API I set breakpoint before, on the first line, using Swagger to test it.
It turns out that it didn't go into the API and straightaway return the error 400
REST API can have parameters in at least two ways:
As part of the URL-path
(i.e. /api/resource/parametervalue)
As a query argument
(i.e. /api/resource?parameter=value)
You are passing your parameters as a query instead of a path as indicated in your code. And that is why it is not executing your code and returning 400.

Making POST request to Vuforia's Web Services always results in "Fail", even though PUT request always works using same approach/body

I am developing an Android app in Unity. I am trying to make UnityWebRequests to work with Vuforia's Web Services API. Currently every method works - GET/PUT/DELETE, but I cannot POST anything, I always get an error:
Error:Generic/unknown HTTP error
Response code:400
Even though according to Vuforia's documentation POST requires the same request body as PUT and I am generating it using the same approach:
public string CreateNewUpdateBody(Text name, Text width, RawImage image, Toggle active_flag, Text application_metadata)
{
dynamic BodyData = new System.Dynamic.ExpandoObject();
if (!string.IsNullOrEmpty(name.text))
{
BodyData.name = name.text; // mandatory for post
}
if (!string.IsNullOrEmpty(width.text))
{
BodyData.width = float.Parse(width.text); // mandatory for post
}
if (image.texture != null)
{
Texture2D texture = (Texture2D)image.texture;
BodyData.image = System.Convert.ToBase64String(ImageConversion.EncodeToJPG(texture)); // mandatory for post
}
if (active_flag.interactable)
{
BodyData.active_flag = active_flag.isOn;
}
if (!string.IsNullOrEmpty(application_metadata.text))
{
BodyData.application_metadata = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(application_metadata.text));
}
string json = JsonConvert.SerializeObject(BodyData);
Debug.Log("Body data: " + json);
return json;
}
Then I send the web request like this:
private IEnumerator PostTarget(MonoBehaviour mono, string postBody)
{
var request = UnityWebRequest.Post(url + "/targets", postBody);
SetHeaders(request); // Must be done after setting the body
Debug.Log("Starting request " + request.method + " " + request.url);
yield return request.SendWebRequest();
while (!request.isDone) yield return null;
if (request.isHttpError || request.isNetworkError)
{
Debug.LogError("Request was not completed");
Debug.LogError("Error:" + request.error + " Response code:" + request.responseCode);
Debug.LogError(request.downloadHandler.text); // result_code is always just "Fail"
mono.StopAllCoroutines();
yield break;
}
else
{
Debug.Log("Request completed successfuly!");
Debug.Log(request.downloadHandler.text);
}
response = JsonUtility.FromJson<ResponsePostNewTarget>(request.downloadHandler.text);
Debug.Log("\nCreated target with id: " + response.target_id);
}
Any thoughts or suggestions? I appreciate the time you take to read this.
If everything works BUT posting data, either 1 vuforia doesn't support it or 2 (most likely) you're missing something.
Try adding this to your request
private UploadHandler GetUploadHandler(string postBody)
{
UploadHandler handler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(postBody));
handler.contentType = "application/json";
return handler;
}
And call it after SetHeaders
request.uploadHandler = GetUploadHandler(postBody);

CodeFluentDuplicateException what i'm i missing

I have problem getting a "CodeFluent.Runtime.CodeFluentDuplicateException" and i'm probably missing something fundamental.
However i first followed this blog about using Servicestack and codefluents and made my own template.
I have no problems to get entities but doing a put give me an exception mentioned.
Ok maybe i have done some wrong in my template so i took another approach looking for answers i found a "complete" project using Webapi and a template, ready to use. Generate ASP .NET Web API Controllers using Templates.
This generates all the controllers and seems to work. However i have the same exeption when using the "put".
This is an example of generated controller code for Put
public HttpResponseMessage Put([FromBody]Country value)
{
if (value == null || !value.Save())
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
return Request.CreateResponse(HttpStatusCode.OK, value);
}
This is how i use the controller above inside a Xamarin.Forms solution.
public async Task UpdateAsync(Country update, bool isNewItem=false)
{
HttpClient client = new HttpClient();
// RestUrl = http://developer.xamarin.com:8081/api/todoitems{0}
var uri = new Uri(string.Format(Constants.RestUrl2, update.Id));
try
{
var json = JsonConvert.SerializeObject(update);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
if (isNewItem)
{
response = await client.PostAsync(uri, content);
}
else
{
response = await client.PutAsync(uri, content);
}
if (response.IsSuccessStatusCode)
{
Debug.WriteLine(#" TodoItem successfully saved.");
}
}
catch (Exception ex)
{
Debug.WriteLine(#" ERROR {0}", ex.Message);
}
}
Any suggestions of what i'm missing?
Thanks for any help
//Greg
I got the answer from the softfluent support
"..
Another case of duplicate exception is when you enable optimistic concurrency and the RowVersion of the instance you are updating is null. In this case, the stored procedure will insert the row instead of updating it.
.."
Changeing the concurrencyMode=None did the trick

No 'Access-Control-Allow-Origin', only errors on first call but works subsequently

I have an AngularJS app which is trying to auth with my Web Api. I receive the below error during the first call to my server if the user does not exist in my database, but does not happen on subsequent calls to the same method once the user exists in my db. (relevant code at the bottom)
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:1378' is therefore not allowed access. The response had HTTP status code 500.
The flow of the logic is:
AngularJS auths with Facebook when the user clicks login
App does an $http.post to my server for auth/login passing their credentials
Server polls Facebook API for user details
If user exists, update their profile and auth 'em
Else, create new membership user, update with FB details, and auth 'em
The only thing that's different if they don't exist in the database (which is when the defect occurs) is that the login method asynchronously calls a createUser method then returns data. No additional external calls are made.
API startup method enabling CORS:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
var cors = new EnableCorsAttribute("*","*","*");
config.EnableCors(cors);
ConfigureOAuth(app);
app_start.WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
API Controller:
[Route("Login")]
[HttpPost]
[AllowAnonymous]
public async Task<FacebookUserModel> Login(FacebookUserRequest user)
{
FacebookUserModel fbUser = new FacebookUserModel();
// Build FacebookUser object
try {
// Grab basic user details
string profileRequestUri = "https://graph.facebook.com/" + user.fbID + "?access_token=" + user.access_token;
HttpWebRequest profileRequest = (HttpWebRequest)WebRequest.Create(profileRequestUri);
profileRequest.Method = WebRequestMethods.Http.Get;
profileRequest.Accept = "application/json";
HttpWebResponse profileResponse = (HttpWebResponse)profileRequest.GetResponse();
Stream profileResponseStream = profileResponse.GetResponseStream();
StreamReader profileStreamReader = new StreamReader(profileResponseStream);
fbUser = JsonConvert.DeserializeObject<FacebookUserModel>(profileStreamReader.ReadToEnd());
} catch (Exception) ...
try {
// Grab profile picture
string pictureRequestUri = "https://graph.facebook.com/" + user.fbID + "/picture";
HttpWebRequest pictureRequest = (HttpWebRequest)WebRequest.Create(pictureRequestUri);
pictureRequest.Method = WebRequestMethods.Http.Get;
HttpWebResponse pictureResponse = (HttpWebResponse)pictureRequest.GetResponse();
fbUser.profilePictureUri = pictureResponse.ResponseUri.ToString();
} catch (Exception) ...
// If user exists, change password to new token and return)
if(userExists)
{
try {
IdentityUser identityUser = _repo.FindUser(ID, pass).Result;
FacebookUserModel dbUser = db.FacebookUserObjects.First(u => u.identityUserID == identityUser.Id);
db.Entry(dbUser).CurrentValues.SetValues(fbUser);
db.SaveChangesAsync();
fbUser.identityUserID = identityUser.Id;
return fbUser;
}
catch (Exception e)
{ return null; }
}
// Else, create the new user using same scheme
else
{
UserModel newUser = new UserModel
{
UserName = ID,
Password = pass,
ConfirmPassword = pass
};
// Create user in Identity & linked Facebook record
createUser(newUser, fbUser);
return fbUser;
}
}
private async void createUser(UserModel newUser, FacebookUserModel fbUser)
{
IdentityResult result = await _repo.RegisterUser(newUser);
var identityUser = await _repo.FindUser(newUser.UserName, newUser.Password);
fbUser.identityUserID = identityUser.Id;
db.FacebookUserObjects.Add(fbUser);
db.SaveChangesAsync();
}
AngularJS calls to my server:
var _login = function (fbID, fbToken) {
$http.post(serviceBase + 'auth/login', { "fbID": fbID, "access_token": fbToken }).then(function (response) {
var data = "grant_type=password&username=" + fbID + "&password=" + pass;
$http.post(serviceBase + 'auth/token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
.success(function (tokenResponse) {
authServiceFactory.bearerToken = tokenResponse.access_token;
})
.error(function (err) {
console.log("token error:", err);
});
authServiceFactory.userObject = response.data;
window.localStorage['userObject'] = JSON.stringify(authServiceFactory.userObject);
})
};
Why would I get the No 'Access-Control-Allow-Origin' error only on the first call, but not subsequent ones?
Update
I have a workaround in place that works, but I don't really like. The issue only arose when calling a second method from my login controller, so if I moved that code up into the login controller instead of a secondary method it works without the CORS error. This really bothers me though and is inefficient, I'd love to know a better way around it.
if you're working with angularjs you might want to check out satellizer. It makes the auth process really simple and has some awesome built in window popup control.
As far as the Access-Control-Allow-Origin calls it could be happening because you explicitly set headers on the one call and the other ones are falling back to the default http provider? Check out $http and see if providing those defaults might work around it.

Calling WorkFlow Soap Service from PCL

I have a WorkFlow Service hosted in a server: http://myServer.net/MyWorkflowService.xamlx
and it's working normally, I called it from a Windows Phone app before and working.
Now, I wanted to call it from a PCL Project (profile 78) for Xamarin.
I got this error:
A correlation query yielded an empty result set. Please ensure
correlation queries for the endpoint are correctly configured.
I added it as a service reference, and I call an Async Method, and subscribes for completed event:
example
TaskCompletionSource<MyResponse> tsk = new TaskCompletionSource<MyResponse>();
WorkFlowService.SubmitModel serviceModel = new WorkFlowService.SubmitModel()
{
List = MyList.ToArray<string>(),
Guid = Guid,
Description = Description,
userid = UserId
};
WorkFlowClient.SubmitCompleted += (sender, eventArgs) => {
if (eventArgs.Error != null)
{
Debug.WriteLine("Exception : DataService : Adding New" + eventArgs.Error.Message);
tsk.TrySetResult(new MyResponse() {
HasError = true
});
}
else
{
tsk.TrySetResult(new MyResponse()
{
HasError = false
});
}
};
WorkFlowClient.SubmitAsync(new WorkFlowService.SubmitRequest((serviceModel)));
return tsk.Task;
I should send an array of strings with my request, Should I provide ServiceReferences.ClientConfig file and what is the build action for it inside the PCL?!