I have a pair of Windows Phone apps that make use of Dropnet for Dropbox integration. One developed in Visual Studio Express 2012 targeting 7.1 and one in Visual Studio 2013 targeting 8.0.
Both are using Dropnet 1.9.6 and RestSharp 104.4.0 via NuGet.
The 7.1 project uses the following static class to interact with DropNet authentication (and file operations but I've excluded them for clarity).
public static class DropboxStorage
{
const string UserToken = "UserToken";
const string UserSecret = "UserSecret";
private static DropNetClient _Dropbox;
public static DropNetClient Dropbox
{
get
{
if (_Dropbox == null)
{
_Dropbox = new DropNetClient(AppKey, AppSecret);
if (IsAuthenticated)
{
_Dropbox.UserLogin = new UserLogin
{
Token = (string)IsolatedStorageSettings.ApplicationSettings[UserToken],
Secret = (string)IsolatedStorageSettings.ApplicationSettings[UserSecret]
};
}
_Dropbox.UseSandbox = true;
}
return _Dropbox;
}
}
public static bool IsAuthenticated
{
get
{
return IsolatedStorageSettings.ApplicationSettings.Contains(UserToken) &&
IsolatedStorageSettings.ApplicationSettings.Contains(UserSecret);
}
}
public static void StartAuthentication(Action<Uri> success, Action<Exception> failure)
{
Dropbox.GetTokenAsync(userLogin =>
{
var url = Dropbox.BuildAuthorizeUrl(userLogin);
if (success != null) success(new Uri(url, UriKind.RelativeOrAbsolute));
}, error =>
{
if (failure != null) failure(error);
});
}
public static void CheckAuthentication(Uri uri, Action success, Action<Exception> failure)
{
if (uri.LocalPath == "/1/oauth/authorize_submit")
{
Dropbox.GetAccessTokenAsync((accessToken) =>
{
IsolatedStorageSettings.ApplicationSettings[UserToken] = accessToken.Token;
IsolatedStorageSettings.ApplicationSettings[UserSecret] = accessToken.Secret;
IsolatedStorageSettings.ApplicationSettings.Save();
if (success != null) success();
},
(error) =>
{
if (failure != null) failure(error);
});
}
}
This is then accessed via a page with a WebBrowser control.
XAML
<phone:WebBrowser x:Name="Browser"
IsScriptEnabled="True"
Navigated="Browser_Navigated"/>
C#
public partial class DropboxPage : PhoneApplicationPage
{
public DropboxPage()
{
InitializeComponent();
DropboxStorage.StartAuthentication(
uri => Browser.Navigate(uri),
ex => ShowException(ex));
}
public void Browser_Navigated(object sender, NavigationEventArgs e)
{
Debug.WriteLine(e.Uri.OriginalString);
DropboxStorage.CheckAuthentication(
e.Uri,
() => NavigationService.GoBack(),
ex => ShowException(ex));
}
private void ShowException(Exception e)
{
MessageBox.Show(e.Message);
NavigationService.GoBack();
}
}
The Dropbox authentication web pages are displayed
Sign in
Allow app access
Success, app connected
and the code determines that authentication has been successful when the Dropbox url contains /1/oauth/authorize_submit.
This all works perfectly, however the 8.0 project behaves a little differently.
The first difference is the url returned by the BuildAuthorizeUrl call.
7.1 https://www.dropbox.com/1/oauth/authorize?oauth_token=<token>
8.0 https://api.dropbox.com/1/oauth/authorize?oauth_token=<token>
The second difference is that the Dropbox url doesn't change from /authorize?oauth_token= to /authorize_submit once the app has been connected so GetAccessTokenAsync is never called and the user token and user secret aren't stored.
My investigations to date suggest a couple of ways to fix this that aren't ideal
Remove the url validation from around the GetAccessTokenAsync call
and eat the resulting DropboxExceptions
Add a callback url to the initial BuildAuthorizeUrl call.
I'm currently doing 1. just to get things up and running but obviously this isn't a long term solution. I'd prefer to avoid using a callback url as it seems like an over complication for a mobile app.
What I'm after is a way of getting the 8.0 project to behave in the same way as the 7.1 project. I've had a look at the Dropnet source code and that contains https://api.dropbox.com as it's base url so I'm even less clear as to how the 7.1 code is working in the first place.
The difference in behaviour is caused by the 8.0 project using System.Windows.Interactivity to convert the Navigated event into a call to a Command on the ViewModel. This seems to prevent the website and WebBrowser control interacting correctly and redirecting to https://www.dropbox.com.
Current
XAML
<phone:WebBrowser x:Name="Browser"
IsScriptEnabled="True"
Visibility="{Binding IsAuthenticated, Converter={StaticResource BoolToInvisibilityConverter}}"
Source="{Binding AuthenticateUri}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigated" >
<i:InvokeCommandAction Command="{Binding BrowserNavigated}"
CommandParameter="{Binding Source, ElementName=Browser}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</phone:WebBrowser>
C#
BrowserNavigated = new RelayCommand<Uri>(u =>
{
RemoteStorage.CheckAuthentication(u, () => IsAuthenticated = true, ex => ShowException(ex));
});
Corrected
XAML
<phone:WebBrowser x:Name="Browser"
IsScriptEnabled="True"
Visibility="{Binding IsAuthenticated, Converter={StaticResource BoolToInvisibilityConverter}}"
Source="{Binding AuthenticateUri}"
Navigated="Browser_Navigated"/>
C#
private void Browser_Navigated(object sender, NavigationEventArgs e)
{
ViewModel.BrowserNavigated.Execute(e.Uri);
}
In both cases the AuthenticateUri property of the view model bound to the WebBrowser Source is set by the StartAuthentication method in the DropboxStorage class.
Having added the event handler the 8.0 project works as expected.
I also started seeing this problem not too long ago. My app worked fine on WP8 up until that point. I don't remember all the details but I looked at my checkin to fix it and I think an extra page load for the first url started happening. No warning from Dropbox about the change either.
To solve it I wait for a certain url to show up in the LoadCompleted event. You use the Navigated event which I think should also work.
I would modify your event handler like this:
public void Browser_Navigated(object sender, NavigationEventArgs e)
{
if (e.Uri.AbsolutePath == "/1/oauth/authorize_submit")
{
DropboxStorage.CheckAuthentication(
e.Uri,
() => NavigationService.GoBack(),
ex => ShowException(ex));
}
}
Related
I currently have two separate projects, one is a WPF application with .NET Framework 4.7.2 and the other is a console application with ASP.NET Core 3.1. The console application used to be .NET 4.7.2 as well however I have just finished moving it to Core.
The WPF Application sends an object to the console application and the console application does some stuff with it and returns a response. My issue currently is that the WPF application successfully sends the object, the console application receives it and does what it needs, however when it sends the response, the WPF application just hangs and must be completely stopped. Here is the code for both:
WPF Application (was working perfectly fine before moving the console to core):
static async Task SendRequest(UploadDetails u)
{
System.Diagnostics.Debug.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(u));
HttpResponseMessage response = await _client.PostAsJsonAsync(_clientUrl, u);
if (response.IsSuccessStatusCode)
{
var responseLink = await response.Content.ReadAsStringAsync();
responseLink = responseLink.Replace("\"", "");
System.Diagnostics.Debug.WriteLine("SUCCESSFUL UPLOAD"); //TODO: Add response
Messenger.Default.Send(new NotificationMessage(responseLink));
}
else
{
System.Diagnostics.Debug.WriteLine("UNSUCCESSFUL UPLOAD"); //TODO: Add response
}
}
Console Application (even sends correct response with postman):
[HttpPost("upload")]
public ActionResult Upload(UploadDetails data)
{
try
{
Task.Factory.StartNew(() => { _Uploader.Upload(data); });
return Ok("Started");
}
catch (Exception e)
{
return NotFound();
}
}
Any help would be greatly appreciated.
Without further information it is hard to determine the actual problem....
But just by looking at the code provided I see one problem:
You are sending JSON yet your action is not inspecting the body.
So I would make one change:
1 Your parameter should have an attribute [FromBody] like this:
[HttpPost]
public ActionResult Upload([FromBody] UploadDetails data)
Also you have to make sure your HttpClient has the correct headers when initialized like this:
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
The last thing I can think of is that you are calling your async method wrong in the WPF application....
For example this would hang on the "await _client.PostAsJsonAsync....." line
private void Button_Click(object sender, RoutedEventArgs e)
{
SendRequest(new UploadDetails { }).Wait();
}
This would NOT hang:
private async void Button_Click(object sender, RoutedEventArgs e)
{
await SendRequest(new UploadDetails { });
}
The reason is that the first one would block the main thread and the second one would not.
But without the code that calls the SendRquest it is impossible to know.
I am using visual studio app center to distribute my application the problem is I need my distribution groups to be notified when there is an update to my application and force them to update so that all of my distribution group will have the latest version. The code below I got it from the Microsoft website and I put it inside my App.xaml.cs under OnStart(). The problem is the notification for a new version of the is not displaying or notifying my distribution group.
protected override void OnStart ()
{
AppCenter.Start("android=<appsecret>;", typeof(Analytics), typeof(Crashes), typeof(Distribute));
Analytics.SetEnabledAsync(true);
Distribute.ReleaseAvailable = OnReleaseAvailable;
bool OnReleaseAvailable(ReleaseDetails releaseDetails)
{
string versionName = releaseDetails.ShortVersion;
string versionCodeOrBuildNumber = releaseDetails.Version;
string releaseNotes = releaseDetails.ReleaseNotes;
Uri releaseNotesUrl = releaseDetails.ReleaseNotesUrl;
var title = "Version " + versionName + " available!";
Task answer;
if (releaseDetails.MandatoryUpdate)
{
answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install");
}
else
{
answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install", "Ask Later");
}
answer.ContinueWith((task) =>
{
if (releaseDetails.MandatoryUpdate || (task as Task<bool>).Result)
{
Distribute.NotifyUpdateAction(UpdateAction.Update);
}
else
{
Distribute.NotifyUpdateAction(UpdateAction.Postpone);
}
});
return true;
}
}
If this is the document you followed, the only thing I see that it says to explicitly add to the OnStart() method is the AppCenter.Start(...) method call:
Open your App.xaml.cs (or your class that inherits from Xamarin.Forms.Application) in your shared or portable project and add the method below in the OnStart() override method.
AppCenter.Start("ios={Your Xamarin iOS App Secret};android={Your Xamarin Android App secret}", typeof(Distribute));
Then later on, in this section, it talks about customizing the update dialog, where it says:
You can customize the default update dialog's appearance by implementing the ReleaseAvailable callback. You need to register the callback before calling AppCenter.Start as shown in the following example:
Distribute.ReleaseAvailable = OnReleaseAvailable;
AppCenter.Start(...);
So, according to the above Distribute.ReleaseAvailable = OnReleaseAvailable; has to be called before AppCenter.Start. You are calling it after.
Also not sure if it is just a formatting error when you posted your question, but it looks like you are implementing the OnReleaseAvailable method inside the OnStart() method, which is not legal in C# (or not until C# 7 anyway, which I just realized... cool). So unless you're using C# 7, you will want to implement the OnReleaseAvailable method outside of the OnStart(), or any other, method. IOW it should be implemented as a direct member of the class. E.g.:
protected override void OnStart ()
{
Distribute.ReleaseAvailable = OnReleaseAvailable;
AppCenter.Start("android=<appsecret>;", typeof(Analytics), typeof(Crashes), typeof(Distribute));
Analytics.SetEnabledAsync(true);
}
bool OnReleaseAvailable(ReleaseDetails releaseDetails)
{
...
}
I am creating a Windows Phone 8.1 RT application. I have installed the facebook and facebook.client sdks for setting up login and sharing purposes. For login purpose I have followed the steps as mentioned here.
My App.xaml.cs OnActivated function looks like this:
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
var protocolArgs = args as ProtocolActivatedEventArgs;
if (protocolArgs != null)
{
LifecycleHelper.FacebookAuthenticationReceived(protocolArgs);
}
Session.OnFacebookAuthenticationFinished += OnFacebookAuthenticationFinished;
}
and here is OnFacebookAuthenticationFinished method
private async void OnFacebookAuthenticationFinished(AccessTokenData session)
{
await Session.CheckAndExtendTokenIfNeeded();
if (Constant.fbSignup)
{
User fbUser = new User();
Account userAccount = new Account();
try
{
FacebookClient fbClient = new FacebookClient(session.AccessToken);
dynamic result = await fbClient.GetTaskAsync("me?fields=id,first_name,last_name,email,location");
fbUser.FirstName = result.first_name;
fbUser.LastName = result.last_name;
userAccount.UserName = result.email;
fbUser.UserAccount = userAccount;
//fbUser.City = result.location.name;
Constant.User = fbUser;
RootFrame.Navigate(typeof(SignUpPage));
}
catch (Exception ex)
{
await new MessageDialog(ex.Message).ShowAsync();
}
}
The login works fine.
Now I want to share some content using the Session.ShowFeedDialog(). I have followed the steps mentioned here for creating AppRequests within the dialog.
I am calling the ShowFeedDialog method this way from a page StoreDetailsPage.xaml.cs
All the following code rests in StorePageDetails.xaml.cs
Session.ShowFeedDialog("", link, linkDescription, linkCaption);
The posting also works fine. But I need to check whether the post was successful or not. For this purpose I tried the
Session.OnFacebookFeedFinished = Success;
where success is
public delegate void FacebookDelegate(FBResult result);
void Success(FBResult result)
{
//Code to check if post was successful
}
So my problem is after ShowFeedDialog is closed the OnActivated event is called and success delegate method is never reached or not called.
I haven't used delegates before so I don't know if there is something wrong there. Also I haven't figured out what the logic for post verification should since I was not able to step into this function. So any suggestions would be much appreciated
I am currently trying to implement Azure Mobile Service into my Windows Phone 8.1 application.
I followed documentation on it Azure.Microsoft.com:
Get started with Mobile Services and I created a new Windows Phone 8.1 project with the service. I tried making a new table with the exact configuration as the sample as well as a new Class to matching the table name & fields. Below is the original code, I changed all of the TodoItem into 'Test'
I keep getting the error:
Error: Table 'Test' does not exist
I tried making both a Javascript and .NET version for back-end but they still failed
Am I unable to find my table because I am missing a step?
sealed partial class MainPage : Page
{
private MobileServiceCollection<Test, Test> items;
private IMobileServiceTable<Test> todoTable = App.MobileService.GetTable<Test>();
public MainPage()
{
this.InitializeComponent();
}
private async Task InsertTodoItem(Test todoItem)
{
await todoTable.InsertAsync(todoItem);
items.Add(todoItem);
}
private async Task RefreshTodoItems()
{
MobileServiceInvalidOperationException exception = null;
try
{
items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionAsync();
}
catch (MobileServiceInvalidOperationException e)
{
exception = e;
}
if (exception != null)
{
await new MessageDialog(exception.Message, "Error loading items").ShowAsync();
}
else
{
ListItems.ItemsSource = items;
this.ButtonSave.IsEnabled = true;
}
}
private async Task UpdateCheckedTodoItem(Test item)
{
await todoTable.UpdateAsync(item);
items.Remove(item);
ListItems.Focus(Windows.UI.Xaml.FocusState.Unfocused);
}
private async void ButtonRefresh_Click(object sender, RoutedEventArgs e)
{
ButtonRefresh.IsEnabled = false;
await RefreshTodoItems();
ButtonRefresh.IsEnabled = true;
}
private async void ButtonSave_Click(object sender, RoutedEventArgs e)
{
var todoItem = new Test { Text = TextInput.Text };
await InsertTodoItem(todoItem);
}
private async void CheckBoxComplete_Checked(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
Test item = cb.DataContext as Test;
await UpdateCheckedTodoItem(item);
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
await RefreshTodoItems();
}
}
For Javascript backend:
Go into the azure portal and select your mobile service. Go to the tab that says data. Click add a table name the table Test.
For .Net:
Open the project in Visual Studio. Add your Test class to the DataObjects folder. Right click the Controllers folder -> add -> controller. Select Microsoft Azure Mobile Services Table Controller. Chose your Test class for the model class and there should only be one option for the data context class.
You follow this article
Get started with Mobile Services
and you saw this
and with this you got the source code. This way you got two projects:
The .Net BackEnd project
The client app project
and when you did the changes, you only changed the client app project (because the MainPage belongs to the client app).
If you changed the TodoItem class to Test class in client app project, you need to do the same for the .NET Project, which requires the changes:
change the TodoItemController to TestController
change the TodoItem to Test
do a clean, then build and if do not have any error you can publish to Azure.
To help understand a bit it, I recommend to see the following samples
Connecting a Menu App to Azure Mobile Service
this article contains a step by step to create a simple backend like you are doing and provides tips that will help you.
How to create the Azure Mobile Service in Azure Portal
AzureMobileServices: Samples to help developers to use Azure Mobile Services
Currently i'm working on a Xamarin.Forms application with WCF. The app makes a connection to the WCF host and I get a response back, only I can't do anything with the results of the response.
My code of the method that's supposed to take care of the response is:
private static void ClientOnGetHelloDataCompleted(object sender, GetHelloDataCompletedEventsArgs getHelloDataCompletedEventArgs)
{
string msg = null;
if(getHelloDataCompletedEventArgs.Error != null)
{
msg = getHelloDataCompletedEventArgs.Error.Message;
}
else if(getHelloDataCompletedEventArgs.Cancelled != null)
{
msg = "Request was cancelled";
}
else
{
lblText.Text = getHelloDataCompletedEventArgs.Results.Name;
}
}
When I debug I can see Results.Name is filled, but for some reason it doesn't update the label named lblText.
This method is placed in de App.cs (Xamarin Forms portable project).
Anyone here that can help me with this problem?
you should refresh the U.I in the main thread, here is the fix
else
{
InvokeOnMainThread(() => lblText.Text = getHelloDataCompletedEventArgs.Results.Name);
}