I have this architecture in my project and sometimes UI thread is getting blocked, can someone please explain what is happening with the below code. Thanks
I am making a service call asyncronously from xamarin.forms viewmodel
Following is the flow
View--->ViewModel---ClassA--->ClassB--Make a service call from here
Code
Scenario 1
public partial class HomePage : ContentPage
{
private HomeVM model;
public HomePage()
{
InitializeComponent();
model = new HomeVM();
model.MainText = ReturnBool().Result;
this.BindingContext = model;
}
public async Task<string> ReturnBool()
{
IsBusy = true;
var r = await new WS().ReturnBool();
IsBusy = false;---------------------------------------Not hitting the breakpoint here
return r;
}
}
public interface IWS
{
Task<string> ReturnBool();
}
public class WS : IWS
{
public Task<string> ReturnBool()
{
return ServiceOperations.ReturnBool();
}
}
internal class ServiceOperations
{
public async static Task<string> ReturnBool()
{
var uri = new Uri(string.Format("http://testmyapi.azurewebsites.net/", string.Empty));
try
{
HttpClient client = new HttpClient();
client.BaseAddress = uri;
HttpResponseMessage response = null;
response = await client.GetAsync("/api/Values/Get");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
string str = JsonConvert.DeserializeObject<string>(content);
return str;
}
else {
return null;
}
}
catch (Exception)
{
return null;
}
}
}
Scenario 2
public partial class HomePage : ContentPage
{
private HomeVM model;
public HomePage()
{
InitializeComponent();
model = new HomeVM();
this.BindingContext = model;
}
}
public class HomeVM : BaseVM
{
private string mainText;
public string MainText
{
get { return mainText; }
set
{
mainText = value;
RaisePropertyChanged("MainText");
}
}
public HomeVM()
{
MainText = ReturnBool().Result;
}
public async Task<string> ReturnBool()
{
IsBusy = true;
var r = await new WS().ReturnBool();
IsBusy = false;---------------------------------------Not hitting the breakpoint here
return r;
}
}
public interface IWS
{
Task<string> ReturnBool();
}
public class WS : IWS
{
public Task<string> ReturnBool()
{
return ServiceOperations.ReturnBool();
}
}
internal class ServiceOperations
{
public async static Task<string> ReturnBool()
{
var uri = new Uri(string.Format("http://testmyapi.azurewebsites.net/", string.Empty));
try
{
HttpClient client = new HttpClient();
client.BaseAddress = uri;
HttpResponseMessage response = null;
response = await client.GetAsync("/api/Values/Get");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
string str = JsonConvert.DeserializeObject<string>(content);
return str;
}
else {
return null;
}
}
catch (Exception)
{
return null;
}
}
}
You are using ReturnBool().Result in the constructor. The return call will block your UI thread. Move that code to the controller action methods without using ".Result" part. Ensure that the methods are async and always return a Task.
Related
I have a function that returns a status like this:
public async Task<Result> UpdateWeight(string id, int weight)
{
var data = await service.GetData(id);
if (data != null)
{
var user = await GetInfo(data.UserId);
var changedWeight = await UpdateWeight(newWeight, user);
if (!changedWeight)
{
return new ChangeWeightResult("Weight not updated");
}
return new ChangeWeightResult(newWeight);
}
return new ChangeWeightResult("Error changing weight");
}
And I'm trying to set up a unit test (xUnit - NSubstitute) for it.
public async Task UpdateUserAvatar_WhenCalled_ReturnChangedAvatarSuccess()
{
//Arrange
var id = "id";
var newWeight = 30;
var data = new DataEntity
{
Id = id
};
var user = new User
{
UserId = "id"
};
service.GetData(Arg.Any<string>()).Returns(data);
service.GetUser(Arg.Any<string>()).Returns(user);
//Act
var result = await service.UpdateWeight(data.Id, newWeight;
//Assert
result.IsSuccessful.Should().BeTrue();
result.Weight.Should().Be(newWeight);
}
However I keep stumble upon error such as null for the memory cache (CacheEntryFake) when I don't return the User or
"Cannot return value of type Task 1 for IMemoryCache.CreateEntry"
These are all the functions that I called within the function that I want to test
public async Task<DataEntity> GetData(string id)
{
var data = await memoryCache.GetOrCreateAsync(id, CacheFactory);
return data;
}
internal async Task<DataEntity> CacheFactory(ICacheEntry cache)
{
var data = await GetDataFromDb(cache.Key.ToString());
if (IsExpiredSession(data))
{
cache.Dispose();
}
else
{
cache.SetAbsoluteExpiration(relative);
}
return data;
}
private async Task<bool> GetInfo(string id)
{
if(setting.CacheTimeout > 0)
{
return await
memoryCache.GetOrCreateAsync(id, InfoCacheFactory):
}
return id;
}
I am working on a login page in Xamarin forms and I need to consume an asmx webservice in order to connect to the sql server. I used this example: https://github.com/fabiosilvalima/FSL.ConsummingAsmxServicesInXamarinForms, and tried to apply the same steps for my app. but I got an error.
here's my code:
ILogin.cs:
namespace App33.Models
{
public interface ILogin
{
string Error { get; set; }
bool ValidUser { get; set; }
}
}
ILoginSoapService.cs
public interface ILoginSoapService
{
Task<List<ILogin>> Login(string namee, string passs);
}
in App.xaml.cs
private static ILoginSoapService _loginSoapService;
public static ILoginSoapService LoginSoapService
{
get
{
if (_loginSoapService == null)
{
_loginSoapService = DependencyService.Get<ILoginSoapService>();
}
return _loginSoapService;
}
Main.xaml.cs
public MainPage()
{
InitializeComponent();
}
async void OnButtonClicked(object sender, EventArgs e)
{
var entr_usrname = this.FindByName<Entry>("username");
string usrname = entr_usrname.Text;
var entr_pass = this.FindByName<Entry>("Password");
string pass = entr_pass.Text;
var state = await App.LoginSoapService.Login(usrname,pass);
if (state[0].ValidUser == true)
{
await DisplayAlert("Alert", "You have been alerted", "OK");
}
}
this is for the portable app. my webservice is added to the web reference as LoginWs. it has the following codes:
Result.cs:
public class Result
{
public string Error { get; set; }
public bool ValidUser { get; set; }
}
WebService1.asmx.cs:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
// [System.Web.Script.Services.ScriptService]
public class WebService1 : System.Web.Services.WebService
{
[WebMethod]
public Result Login(string userName, string userPass)
{
SqlConnection conn=new SqlConnection (new DBConnection().ConnectionString);
Result result = new Result();
try
{
SqlCommand cmd = new SqlCommand("SELECT userName, password FROM users where CONVERT(VARCHAR, username)=#username and CONVERT(VARCHAR, password)=#password");
cmd.Parameters.AddWithValue("username", userName);
cmd.Parameters.AddWithValue("password", userPass);
cmd.Connection = conn;
if (conn.State==System.Data.ConnectionState.Closed)
{
conn.Open();
}
SqlDataReader dr = cmd.ExecuteReader();
if (dr.HasRows)
{
result.ValidUser = true;
return result;
}
else
{
result.ValidUser = false;
}
}
catch(Exception ex)
{
result.Error = ex.ToString();
}
finally
{
conn.Close();
}
return result;
}
}
}
now in App.Android:
Result.cs
namespace App33.Droid.LoginWs
{
public partial class Result : ILogin
{
}
}
LoginSoapService.cs
[assembly: Dependency(typeof(App33.Droid.LoginSoapService))]
namespace App33.Droid
{
public sealed class LoginSoapService :ILoginSoapService
{
LoginWs.WebService1 service;
public LoginSoapService()
{
service = new LoginWs.WebService1()
{
// Url = "http://codefinal.com/FSL.ConsummingAsmxServicesInXamarinForms/Customers.asmx" //remote server
Url = "http://192.168.0.106/site2/WebService1.asmx" //localserver - mobile does not understand "localhost", just that ip address
};
}
public async Task<List<ILogin>> Login( string namee,string pass)
{
return await Task.Run(() =>
{
var result = service.Login(namee,pass);
return new List<ILogin>(result);
});
}
}
}
the error i'm getting is in this line:return new List(result);. it says: Error CS1503 Argument 1: cannot convert from 'App33.Droid.LoginWs.Result' to 'int'. I can't figure out what the peoblem is. sorry for the long question. any help is appreciated.
I think this is because my json gives me an array, but I don't know how it solved, this is what I did. (I'm new at this)
running in Visual Studio 2019 (xamarin.form) with web services but the url is hidden for security, so don't pay attention in that.
---my-json---
{
"cuentas":[
{
"cuenta":"0500",
"usuario":41
},
{
"cuenta":"0508",
"usuario":6
},
{
"cuenta":"0522",
"usuario":41
},
{
"cuenta":"0532",
"usuario":41
},
null
]
}
---WSClient.cs---
class WSClient
{
public async Task<T> Post<T>(string url, StringContent c)
{
var client = new HttpClient();
var response = await client.PostAsync(url, c);
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(json);
}
}
----Cuenta.cs---
public class Cuenta
{
public string cuenta { get; set; }
public int usuario { get; set; }
}
------MainPage.xaml.cs-----
private async void BtnCall_Clicked(object sender, EventArgs e)
{
WSClient client = new WSClient();
string dato = "";
StringContent content = new StringContent(dato, Encoding.UTF8, "application/json");
var result = await client.Post<Cuenta>("http://www.***", content);
if (result != null) {
lblCuenta.Text = result.cuenta;
lblUsuario.Text = result.cuenta;
}
}
It doesn't show me anything and it doesn't give me any mistakes... any advice?
( I can see the json in the console if I use WriteLine in "WSClient" )
your class should look like this (using json2csharp.com)
public class Cuenta
{
public string cuenta { get; set; }
public int usuario { get; set; }
}
public class RootObject
{
public List<Cuenta> cuentas { get; set; }
}
var result = await client.Post<RootObject>("http://www.***", content);
Been trying to unit test this method but can't figure out how to do it.
public bool ValidateCaptcha(string captchaResponse, string captchaChallenge,
string hostip)
{
var strPrivateKey = _secConfiguration.CaptchaPrivateKey;
var strParameters = "verify?privatekey=" + strPrivateKey +
"&remoteip=" + hostip +
"&challenge=" + captchaChallenge+
"&response=" + captchaResponse;
var url = CaptchaUrl + strParameters;
var request = CreateHttpWebRequest(url);
request.Proxy.Credentials = CredentialCache.DefaultCredentials;
request.Method = "POST";
request.ContentType = "text/html";
request.ContentLength = 0;
var response = GetHttpWebResponse(request);
var writer = response.GetResponseStream();
var reader = new StreamReader(writer);
var responseFromServer = reader.ReadToEnd();
var serverResponse = responseFromServer.Split('\n');
return serverResponse[0] == "true";
}
private HttpWebResponse GetHttpWebResponse(HttpWebRequest request)
{
return (HttpWebResponse) request.GetResponse();
}
private HttpWebRequest CreateHttpWebRequest(string url)
{
return (HttpWebRequest) WebRequest.Create(url);
}
I had planned to moq the dependencies and have a couple of wrap classes
public class WrapHttpWebRequest : IHttpWebRequest
{
private readonly HttpWebRequest _request;
public WrapHttpWebRequest(HttpWebRequest request)
{
_request = request;
}
public string Method
{
get { return _request.Method; }
set { _request.Method = value; }
}
public IHttpWebResponse GetResponse()
{
return new WrapHttpWebResponse((HttpWebResponse)_request.GetResponse());
}
}
and
public class WrapHttpWebResponse : IHttpWebResponse
{
private WebResponse _response;
public WrapHttpWebResponse(HttpWebResponse response)
{
_response = response;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (_response != null)
{
((IDisposable)_response).Dispose();
_response = null;
}
}
}
public Stream GetResponseStream()
{
return _response.GetResponseStream();
}
}
But can't find a way to inject them. Any ideas how can I do this? Thanks.
You could create testable version of your class under test.
Make the method virtual and override it in the testable version.
protected virtual HttpWebRequest CreateHttpWebRequest(string url)
{
return (HttpWebRequest)WebRequest.Create(url);
}
public class TesableClassUnderTest : ClassUnderTest
{
public HttpWebRequest HttpWebRequestFake { get; set; }
protected override HttpWebRequest CreateHttpWebRequest(string url)
{
if (HttpWebRequestFake != null)
return HttpWebRequestFake;
return base.CreateHttpWebRequest(url);
}
}
Then in test set the fake object to your own value:
[TestMethod]
public void test()
{
TesableClassUnderTest cut = new TesableClassUnderTest();
cut.HttpWebRequestFake = CreateFakeHttpWebRequest();
cut.ValidateCaptcha(...)
Assert...
}
I have tried for days but I couldn't reach any successful result. I need to post images with their information (s.t. created user name).
This is my method;
[HttpPost]
public Task<HttpResponseMessage> PostFile(string createdByName)
{
HttpRequestMessage request = this.Request;
if (!request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = System.Configuration.ConfigurationSettings.AppSettings["TempUploadDir"];
var provider = new MultipartFormDataStreamProvider(root);
var task = request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(o =>
{
AddImages(provider.BodyPartFileNames);
string file1 = provider.BodyPartFileNames.First().Value;
// this is the file name on the server where the file was saved
return new HttpResponseMessage()
{
Content = new StringContent("File uploaded.")
};
}
);
return task;
}
And this my TypeFormatterClass which is added global.asax
public class MultiFormDataMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
{
public MultiFormDataMediaTypeFormatter()
: base()
{
this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
}
protected override bool CanReadType(Type type)
{
return true;
}
protected override bool CanWriteType(Type type)
{
return false;
}
protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
{
var contents = formatterContext.Request.Content.ReadAsMultipartAsync().Result;
return Task.Factory.StartNew<object>(() =>
{
return new MultiFormKeyValueModel(contents);
});
}
class MultiFormKeyValueModel : IKeyValueModel
{
IEnumerable<HttpContent> _contents;
public MultiFormKeyValueModel(IEnumerable<HttpContent> contents)
{
_contents = contents;
}
public IEnumerable<string> Keys
{
get
{
return _contents.Cast<string>();
}
}
public bool TryGetValue(string key, out object value)
{
value = _contents.FirstDispositionNameOrDefault(key).ReadAsStringAsync().Result;
return true;
}
}
}
When I post images and "createdByName" I can reach images but I couldn't parameters. How can I do this?
Thank you.
To get your createdByName field, inside your ContinueWith :
var parts = o.Result;
HttpContent namePart = parts.FirstDispositionNameOrDefault("createdByName");
if (namePart == null)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
string name = namePart.ReadAsStringAsync().Result;
For a more detailed example, see :
http://www.asp.net/web-api/overview/working-with-http/html-forms-and-multipart-mime#multipartmime