Search code examples
c#postxamarinxamarin.androidhttp-post

Make a post to a login form of a webpage using C# in Xamarin and Android


I just started learning C#, and I have also never experienced in developing mobile environments. I have experience in Java and I could adopt some knowledge from there.

I have VisualStudio 2017RC, and started a Xamarin.Android project. Furthermore there is a ASP.NET site for registration and login, and token generation (SQLite user storage). My goal is to make a view in xamarin where the user can type his/her name and password then can login to the website where he/she get an access token.

I have already made a LoginView, it was a piece of cake... Now I have to post the filled data to the website and log in to the page using c# in Xamarin.Android. Lets see how far I have got to.

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    SetContentView(Resource.Layout.LoginWebview);

    //code for getting the login details(emial, password) from the user
    //code for getting the access token
    //apiBaseUri is fixed value

    MainAsync(email, password, apiBaseUri, loginToken).Wait();
}


public static async Task MainAsync(string email, string password, string apiBaseUri, string loginToken)
{
    var token = await GetApiTokenAsync(email, password, apiBaseUri, loginToken);
}

public static async Task<string> GetApiTokenAsync(string email, string password, string apiBaseUri, string tokenValue)
{
    var token = string.Empty;

    var handler = new HttpClientHandler()
    {
        AllowAutoRedirect = false
    };


    using (var client = new HttpClient(handler))
    {
        client.BaseAddress = new Uri(apiBaseUri);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.Timeout = TimeSpan.FromSeconds(60);

        //set cookie
        client.DefaultRequestHeaders.Add("Set-Cookie", CookieManager.Instance.GetCookie(apiBaseUri));

        //setup login data
        var formContent = new FormUrlEncodedContent(new[]
        {
 //new KeyValuePair<string, string>("grant_type", "password"),
 new KeyValuePair<string, string>("Email", username),
 new KeyValuePair<string, string>("Password", password),
 new KeyValuePair<string,string>("__RequestVerificationToken",tokenValue),
 new KeyValuePair<string,string>("RememberMe","false")
          });

        //send request               
        HttpResponseMessage responseMessage = await client.PostAsync("/Login?returnurl=%2FGame%2Fkz%2FToken", formContent);
        var responseJson = await responseMessage.Content.ReadAsStringAsync();
        var jObject = JObject.Parse(responseJson);
        token = jObject.GetValue("access_token").ToString();

        return token;
    }

}
}

Well at line

HttpResponseMessage responseMessage = await client.PostAsync("/Login?returnurl=%2FGame%2Fkz%2FToken", formContent);

The program hangs and I got no access token back.

Let's see the issue from the website point of view: in the webbrowser the login form can be opened with the following link: (I will use some dummy value ("game") in the url in order to keep it private) http://www.game.nl/PlayGame/kz/Account/Login?ReturnUrl=%2FGame%2Fkz%2Ftoken

With opening this website I can type the email and the password. Then If I click on the log in button I got an access token (which will be used for later purposes). I would like to get to this point, but of course in c# with android phone

I am going to list some additional info about the website because I think they are also relevant to the solution. The details are the following, if I log in with the website. (I check the details with Google Chrome developer tool, at network tab)

General:

FormData:

  • Email:example@company.nl
  • Password:chocolate09#
  • __RequestVerificationToken:CfDJ8L1aKkmfKmBLlQIwHTacxWKOtRekEcOfxKQGBrXUEkE2XpWyxYmMKOTlgZ7gCZq3FEx8ILF_VgQcsjBztZhCwUUYSV966s1BgjFs5I8IefGSoei5hfzK-WQQr8Ztjpn3oGq40neXx4_iBUUMRjFpKz_DdBqzQ
  • RememberMe:false

The source of the form data:

Email=example%40company.nl&Password=chocolate09%23&__RequestVerificationToken=CfDJ8L1aKkmfKmBLlQIwHTacxWKOtRekEcOfxKQGBrXUEkE2XpWyxYmMKOTlgZ7gCZq3FEx8ILF_VgQcsjBztZhCwUUYSV966s1BgjFs5I8IefGSoei5hfzK-WQQr8Ztjpn3oGq40neXx4_iBUUMRjFpKz_DdBqzQ&RememberMe=false

So I am stuck at this point, and I do not know why it hangs at

await client.PostAsync("/Login?returnurl=%2FGame%2Fkz%2FToken", formContent);

The last lines of the VS console output:

02-11 00:07:01.404 D/Mono    ( 2853): [0x982bf930] hill climbing, change max number of threads 2
02-11 00:07:07.829 D/Mono    ( 2853): [0x9847f930] worker finishing
Thread finished: <Thread Pool> #4
The thread 'Unknown' (0x4) has exited with code 0 (0x0).

Solution

  • Your problem I believe is this line in OnCreate:

    MainAsync(email, password, apiBaseUri, loginToken).Wait();

    Specifically the .Wait() statement. OnCreate is always called on the UI thread. Whenever you use async/await and wait for the result using .Wait() or .Result bad things happen and your app will hang as the continuation cannot jump back to the UI thread - and you will see this in the call to PostAsync as this is the first call that will actually use a different thread to run on.

    I guess the reason you are calling .Wait() is because OnCreate is not an async method - well the good news is it can be. I would mark it as async and await the login (you can mark void methods as async, there are pitfalls with doing so, so be sure to catch all exceptions inside the method!)

    // Mark the method as async
    protected override async void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
    
        SetContentView(Resource.Layout.LoginWebview);
    
        // remove the .Wait() and await the method call
        await MainAsync(email, password, apiBaseUri, loginToken);
    }