0

I am attempting to make a WCF call using ReactiveUI commands and capturing the resulting string with ObservableAsPropertyHelper. With the code below I am receiving the following error message -

"The calling thread cannot access this object because a different thread owns it"

The WCF call returns but errors on accessing the ObservableForProperty - Message and/or when raising its PropertyChanged

Let me know if anyone needs other details/code.

ViewModel: UserService.Authenticate is proxy call to a WCF endpoint

public class LoginViewModel : ReactiveObject, IRoutableViewModel
{
  public LoginViewModel(IScreen hostScreen , MainViewModel appRootViewModel, IUserService userService)
    {
        HostScreen = hostScreen;

        UserService = userService;
        Application = appRootViewModel;
        var canLogin = this.WhenAny(x => x.LoginName, x => x.Password, (l, p) =>
            !String.IsNullOrWhiteSpace(l.Value) && !String.IsNullOrWhiteSpace(p.Value));

        LoginCommand = new ReactiveCommand(canLogin);

        var loggedIn = LoginCommand.RegisterAsync(_ => Observable.Start(() =>
            {
                var request = new Request
                {
                    UserIdentity = new User.Identity
                    {
                        Login = LoginName,
                        Password = new User.Password { Old = Password }
                    }

                };
                var authenticationResult = UserService.Authenticate(request).Authenticated;

                return authenticationResult ? "Login Succeeded...Continuing"
                    : "Login Failed...Please try again";


            }));
        loggedIn.Subscribe(s =>
        {
            if (s == "Login Succeeded...Continuing to Analytics")
            {
                HostScreen.Router.Navigate.Execute(Application);
            }

        });

            message = new ObservableAsPropertyHelper<string>(loggedIn,
            s =>
            {

                raisePropertyChanged("Message");

            });

view code behind:

public partial class LoginView : IViewFor<LoginViewModel>
{
   public LoginView()
    {
        InitializeComponent();

        this.WhenAnyValue(x => x.ViewModel).BindTo(this, x => x.DataContext);
        this.Bind(ViewModel, model => model.Password, x => x.password.Text);
        this.Bind(ViewModel, model => model.LoginName, view => view.userName.Text);
        this.OneWayBind(ViewModel, model => model.Message, x => x.message.Content);
        this.OneWayBind(ViewModel, x => x.LoginCommand, x => x.login.Command);
    }

    public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(LoginViewModel), typeof(LoginView), new PropertyMetadata(null));


    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (LoginViewModel)value; }
    }

    public LoginViewModel ViewModel
    {
        get
        {
            return (LoginViewModel)GetValue(ViewModelProperty);
        }
        set
        {
            SetValue(ViewModelProperty,
                value);
        }
    }

}

}

denmerc
  • 1
  • 3

2 Answers2

0

Most of your code is correct (except for where you set message, just use loggedIn.ToProperty), but as I recall, WCF tries to "help you out" by fiddling with SynchronizationContexts, you need to disable this (I'm not sure how to do this though)

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • I've added [ServiceBehavior(UseSynchronizationContext = false)] [CallbackBehavior(UseSynchronizationContext = false)] to my service class and that did not help - in that its giving me the same result. A thing to note is that this works when I replace the authenticate call with a method stub - so Paul you are correct in that it has something to do with WCF – denmerc Jan 31 '14 at 17:19
  • also does your suggestion about ToProperty work with regular properties or does the backing field need to be a ObservableAsPropertyHelper message for it to work? On first pass, I am getting a null exception on message even when setting the initial value param like so --> loggedIn.ToProperty(this, x => x.Message, string.Empty); – denmerc Jan 31 '14 at 18:23
  • Another observation is if successfully authenticated and routed to the MainView - I get {"The calling thread must be STA, because many UI components require this."} Somehow I'm thrown out of the current context because of WCF - whether I stay on the LoginView or navigate to another view? – denmerc Jan 31 '14 at 20:42
0

Update: Fixed by telling the observer callbacks to run on the current synchronization context.

.ObserveOn(SynchronizationContext.Current)

So below is the LoginCommand Observable code to fix the above issues. Last line is the edit.

var loggedIn = LoginCommand.RegisterAsync(_ => Observable.Start(() =>
            {


                Session<NullT> init = new Session<NullT>
                {
                    SqlKey = System.Configuration.ConfigurationManager.AppSettings["sharedKey"].ToString()

                };

                var initResponse = UserService.Initialize(init);
                var authenticationResult = false;
                if (initResponse.SessionOk)
                {
                    initResponse.UserIdentity = new User.Identity
                    {
                        Login = LoginName,
                        Password = new User.Password { Old = Password }
                    };


                    authenticationResult = UserService.Authenticate(initResponse).Authenticated;
                    return authenticationResult ? "Login Succeeded"
                        : "Login Failed...Please try again";
                }
                else return "Failed to Initialize.";


            }).ObserveOn(SynchronizationContext.Current));
denmerc
  • 1
  • 3