I'm using System.Reactive within my ios project and i'm aware i need to use ObserveOn in order to specify on which thread to execute the subscriber on. However i can't seem to get this working properly.
For all i can tell this should be working, or am i implementing it wrong?
public class UiContext : IScheduler
{
/// <inheritdoc />
public IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
{
NSOperationQueue.MainQueue.AddOperation(() => action(this, state));
return Disposable.Empty;
}
/// <inheritdoc />
public IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
NSOperationQueue.MainQueue.AddOperation(() => action(this, state));
return Disposable.Empty;
}
/// <inheritdoc />
public IDisposable Schedule<TState>(TState state, DateTimeOffset dueTime, Func<IScheduler, TState, IDisposable> action)
{
NSOperationQueue.MainQueue.AddOperation(() => action(this, state));
return Disposable.Empty;
}
/// <inheritdoc />
public DateTimeOffset Now { get; }
}
void SomeMethod()
{
WhenValidationChanged
.ObserveOn(new UiContext())
.SubscribeOn(new UiContext())
.Throttle(TimeSpan.FromMilliseconds(50))
.Subscribe(OnValidationChanged);
}
private void OnValidationChanged(object obj)
{
if (TableView.DataSource is InfoFieldsDataSource dataSource)
{
var validationErrors = dataSource.Items.OfType<InfoFieldViewModelBase>().Count(d => !d.IsValid);
// Exception is raised about not being executed on UI thread
_validationController.View.BackgroundColor = validationErrors > 0 ? UIColor.Green : UIColor.Red;
}
}
Calling .ObserveOn(new UiContext()) before .Throttle(TimeSpan.FromMilliseconds(50)) probably has no effect as Throttle can change the scheduler - each operator can change the scheduler. You should always do .ObserveOn just before the operator or subscribe call you want it applied to.
Related
The following simple multi-threaded program was meant to try out the CommunityToolkit Messenger package for which the documentation says (see: Messenger)
Both WeakReferenceMessenger and StrongReferenceMessenger also expose a Default property that offers a thread-safe implementation built-in into the package.
I had hoped this would mean I could send messages on one thread and receive them on other threads but a problem arose with what seems to be the IMessenger Interface. Details follow below.
This project starts with a vanilla TemplateStudio WinUI 3 (v1.1.5) desktop template that uses the CommunityToolkit Mvvm package (with Messenger) and a single page, MainPage. When the App launches, it starts a RandomMessageGenerator thread that periodically issues a TraceMessage using the WeakReferenceMessenger.Default channel from the Toolkit. The UI thread receives these messages and stores them in a List.
App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Multi_Window.Activation;
using Multi_Window.Contracts.Services;
using Multi_Window.Core.Contracts.Services;
using Multi_Window.Core.Services;
using Multi_Window.Services;
using Multi_Window.ViewModels;
using Multi_Window.Views;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using CommunityToolkit.Mvvm.Messaging.Messages;
using System.Diagnostics;
namespace Multi_Window;
// To learn more about WinUI 3, see https://docs.microsoft.com/windows/apps/winui/winui3/.
public partial class App : Application
{
// The .NET Generic Host provides dependency injection, configuration, logging, and other services.
// https://docs.microsoft.com/dotnet/core/extensions/generic-host
// https://docs.microsoft.com/dotnet/core/extensions/dependency-injection
// https://docs.microsoft.com/dotnet/core/extensions/configuration
// https://docs.microsoft.com/dotnet/core/extensions/logging
public IHost Host { get; }
public static T GetService<T>()
where T : class
{
if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
}
return service;
}
public static WindowEx MainWindow { get; } = new MainWindow();
public static ShellPage? ShellPage { get; set; }
private static readonly List<string> _traceMessages = new();
private Task? messageGenerator;
public App()
{
InitializeComponent();
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
ConfigureServices((context, services) =>
{
// Default Activation Handler
services.AddTransient<ActivationHandler<LaunchActivatedEventArgs>, DefaultActivationHandler>();
// Other Activation Handlers
// Services
services.AddTransient<INavigationViewService, NavigationViewService>();
services.AddSingleton<IActivationService, ActivationService>();
services.AddSingleton<IPageService, PageService>();
services.AddSingleton<INavigationService, NavigationService>();
// Core Services
services.AddSingleton<IFileService, FileService>();
// Views and ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<MainPage>();
// ** NOTE ** changed to Singleton so we can refer to THE ShellPage/ShellViewModel
services.AddSingleton<ShellPage>();
services.AddSingleton<ShellViewModel>();
// Configuration
}).
Build();
UnhandledException += App_UnhandledException;
System.AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Microsoft.UI.Xaml.Application.Current.UnhandledException += Current_UnhandledException;
}
private void RandomMessageGenerator()
{
var shutdown = false;
WeakReferenceMessenger.Default.Register<ShutDownMessage>(this, (r, m) => shutdown = true);
Debug.WriteLine($"RandomMessageGenerator started on thread {Environment.CurrentManagedThreadId}");
Random rnd = new();
// not a good way to control thread shutdown in general but will do for a quick test
while (shutdown == false)
{
Thread.Sleep(rnd.Next(5000));
var tm = new TraceMessage($"{DateTime.Now:hh:mm:ss.ffff} Timer event. (Th: {Environment.CurrentManagedThreadId})");
try
{
WeakReferenceMessenger.Default.Send(tm);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
break;
}
}
Debug.WriteLine($"RandomMessageGenerator closed at {DateTime.Now:hh:mm:ss.ffff} (Th: {Environment.CurrentManagedThreadId})");
}
private void Current_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) => throw new NotImplementedException();
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) => throw new NotImplementedException();
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
// TODO: Log and handle exceptions as appropriate.
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
throw new NotImplementedException();
}
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
await App.GetService<IActivationService>().ActivateAsync(args);
MainWindow.AppWindow.Closing += OnAppWindowClosing;
WeakReferenceMessenger.Default.Register<TraceMessage>(this, (r, m) =>
{
_traceMessages.Add(m.Value);
Debug.WriteLine(m.Value);
});
WeakReferenceMessenger.Default.Register<WindowClosedMessage>(this, (r, m) => OnStatusWindowClosed()); // StatusWindow closed events
WeakReferenceMessenger.Default.Register<App, TraceMessagesRequest>(this, (r, m) => m.Reply(_traceMessages)); // StatusWindow requests previous messages
messageGenerator = Task.Run(RandomMessageGenerator);
}
private void OnStatusWindowClosed()
{
if (ShellPage is not null && ShellPage.SettingsStatusWindow)
{
ShellPage.SettingsStatusWindow = false; // turn off toggle
if (ShellPage.NavigationFrame.Content is MainPage settingsPage) settingsPage.StatusWindowToggle.IsOn = false;
}
}
private async void OnAppWindowClosing(object sender, AppWindowClosingEventArgs e)
{
WeakReferenceMessenger.Default.UnregisterAll(this); // stop messages and avoid memory leaks
WeakReferenceMessenger.Default.Send(new ShutDownMessage(true)); // close all windows
MainWindow.AppWindow.Closing -= OnAppWindowClosing;
if (messageGenerator is not null) await messageGenerator;
}
}
The user may create a StatusWindow (a secondary Window on the UI thread) by toggling a switch on MainPage. The StatusWindow should open, request and load previous messages from the App, then register for new TraceMessages. All TraceMessages (including new ones) are displayed in a ListView on the StatusWindow.
MainPage.xaml.cs
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Multi_Window.ViewModels;
namespace Multi_Window.Views;
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; } = App.GetService<MainViewModel>();
public ShellPage ShellPage { get; } = App.GetService<ShellPage>();
public ShellViewModel ShellViewModel { get; } = App.GetService<ShellViewModel>();
public MainPage()
{
DataContext = ViewModel;
InitializeComponent();
}
private void StatusWindow_Toggled(object sender, RoutedEventArgs e)
{
if (StatusWindowToggle.IsOn && ShellPage.SettingsStatusWindow == false)
{
StatusWindow window = new() { Title = "Prosper Status" };
window.Activate();
ShellPage.SettingsStatusWindow = true;
}
else if (StatusWindowToggle.IsOn == false && ShellPage.SettingsStatusWindow == true)
WeakReferenceMessenger.Default.Send(new CloseWindowMessage(true));
}
}
MainPage.xaml
<Page
x:Class="Multi_Window.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<ToggleSwitch x:Name="StatusWindowToggle" x:FieldModifier="public" Grid.Row="2" Grid.Column="1" Header="Show Status Window"
Toggled="StatusWindow_Toggled" IsOn="{x:Bind ShellPage.SettingsStatusWindow, Mode=OneTime}" />
</Grid>
</Page>
StatusWindow.xaml.cs
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Multi_Window.Views;
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class StatusWindow : Window
{
private ObservableCollection<string> _traceMessages { get; } = new();
public StatusWindow()
{
InitializeComponent();
var sw = new WindowPrimitives(this);
sw.AppWindow.SetIcon("Assets/wip.ico");
WeakReferenceMessenger.Default.Register<CloseWindowMessage>(this, (r, m) => Close());
WeakReferenceMessenger.Default.Register<ShutDownMessage>(this, (r, m) => Close());
}
private void StatusWindow_Closed(object sender, WindowEventArgs args)
{
WeakReferenceMessenger.Default.UnregisterAll(this); // stop getting messages and avoid memory leaks
WeakReferenceMessenger.Default.Send(new WindowClosedMessage(true)); // acknowledge closure
}
private void StatusMessages_Loaded(object sender, RoutedEventArgs e)
{
// get current Trace messages
var messages = WeakReferenceMessenger.Default.Send<TraceMessagesRequest>();
if (messages != null && messages.Responses.Count > 0)
foreach (var response in messages.Responses)
foreach (var trace in response)
_traceMessages.Add(trace);
// register for Trace messages and, when they arrive, add them to list
WeakReferenceMessenger.Default.Register<TraceMessage>(this, (r, m) => _traceMessages.Add(m.Value));
}
}
StatusPage.xaml
<Window
x:Class="Multi_Window.Views.StatusWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Closed="StatusWindow_Closed"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="StatusMessages" x:FieldModifier="public" VerticalAlignment="Top" Margin="20" SelectionMode="None" BorderBrush="Black" BorderThickness="1"
ItemsSource="{x:Bind _traceMessages, Mode=OneWay}"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.IsHorizontalRailEnabled="True"
ScrollViewer.IsDeferredScrollingEnabled="False"
Loaded="StatusMessages_Loaded">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel VerticalAlignment="Bottom" ItemsUpdatingScrollMode="KeepLastItemInView"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
</Window>
Other Sundry Classes
// Allows Win32 access to a Window through WinAPI
public class WindowPrimitives
{
public IntPtr HWnd { get; }
private WindowId WindowId { get; }
public AppWindow AppWindow { get; }
public Window Window { get; }
public WindowPrimitives(Window window)
{
HWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
WindowId = Win32Interop.GetWindowIdFromWindow(HWnd);
AppWindow = AppWindow.GetFromWindowId(WindowId);
Window = window;
}
}
// Message Definitions
public class CloseWindowMessage : ValueChangedMessage<bool>
{
public CloseWindowMessage(bool value) : base(value) { }
}
public class WindowClosedMessage : ValueChangedMessage<bool>
{
public WindowClosedMessage(bool value) : base(value) { }
}
public class ShutDownMessage : ValueChangedMessage<bool>
{
public ShutDownMessage(bool value) : base(value) { }
}
public class TraceMessage : ValueChangedMessage<string>
{
public TraceMessage(string value) : base(value) { }
}
public class TraceMessagesRequest : CollectionRequestMessage<List<string>>
{
}
The problem is that, on the first new TraceMessage sent after the StatusWindow has been opened, the same WeakReferenceMessenger.Default.Send() method that has been happily sending messages between the RandomMessageGenerator thread and the UI thread throws a "The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))" exception and the RandomMessageGenerator thread dies.
The exception is thrown by the WeakReferenceMessenger.Default.Send(tm); statement in the RandomMessageGenerator() method. I assume the issue is in the IMessenger interface (the only interface involved here). Briefly, as I understand it, this interface builds a table of subscribed receivers for each message type. Each receiver is then signaled on each Send().
One possibility is that references in the receiver list are all assumed to marshal to the same thread. If that were so, none of the messages between threads would work but they do before the StatusWindow is opened so that's unlikely. Changing the list of receivers is an obvious place where threading issues might occur. As WeakReferenceMessenger.Default is thread-safe, I thought adding (and deleting) registered receivers would be thread-safe but doesn't seem to be the case here. Finally, it could be the message itself (a string in this case) that is at fault. I don't know for sure but assumed that the Send method took a private copy of the message to deliver to the marshaled thread.
Could any of you please help me understand the mistake I've made here?
I did find a solution for this particular issue. As expected, it's because an object is accessed from a thread other than the thread on which it was created.
To fix the error, add
public static DispatcherQueue UIDispatcherQueue = DispatcherQueue.GetForCurrentThread();
to the App class which will allow any thread access to the UI thread DispatcherQueue. Then, change the message registration in StatusWindow.xaml.cs to
// register for Trace messages and, when they arrive, add them to list
WeakReferenceMessenger.Default.Register<TraceMessage>(this, (r, m) => App.UIDispatcherQueue.TryEnqueue(() => _traceMessages.Add(m.Value)));
This will now marshal the _traceMessages.Add call in the message handler to the UI thread, which is where
private ObservableCollection<string> _traceMessages { get; } = new();
was constructed.
This was easy enough to figure out once I realized that the point where the exception was thrown and the exception message were both rather deceptive. Although the sending of the message is the cause of the message being received, it's the attempt to handle the message on the wrong thread that really throws the exception.
At any rate, this appears to show that the message receiver handler executes on the same thread as the sender. I was hoping that "thread-safe" in the documentation meant messages were automatically marshalled to the receiver's thread.
I have a test project with Room database. Using Asynctask I can successfully insert an object with some test data into the database. I'm trying to learn RxJava and replace Asynctask with RxJava's observer, but it doesn't work. I have read alot of documentation and watched tutorials, but I don't think I quite get it. Here's the relevant code:
Here I set my Room object with the data from my List:
for(ObjectForArray item: listToDatabase) {
myRoomEntity.setName( item.getName() );
Log.d( "TAG", myRoomEntity.getName() );
}
Then I try to use RxJava Observable to insert data into the database. This was originally and successfully done using Asynctask:
Observable<MyRoomEntity> myRX = Observable
.just(myRoomEntity)
.subscribeOn( Schedulers.io() )
.observeOn( AndroidSchedulers.mainThread() );
myRX.subscribe( new Observer<MyRoomEntity>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("TAG ONSUBSCRIBE", d.toString());
try {
myViewModel.insertDatabase( myRoomEntity );
Log.d( "TAG", "Populating database Success" );
}catch(Error error) {
Log.d( "TAG", error.toString() );
}
}
The OnNext, OnError and OnComplete are empty.
When I run the project it crashes with the error:
Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
I'm obviously using RxJava wrong since the point is to do asynchronous tasks away from the main thread.
i have use RX java in replace of Asyntask as it has been deprecated in android 9
there are multiple replacements that android provides like Executors, threads, Listenable Futures , Coroutines 🔥, so you are looking how to implement this with rxjava and how RX Java java helps your to migrate just add these dependencies first in gradle
implementation "io.reactivex.rxjava2:rxjava:2.2.20"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
once you import lets start working with RX java i will let you know where you can put background task, pre execute, on post execute like asynctask
lets start codding with Rx java first , i have comment in the method that will help you to put the code
Observable.fromCallable(new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
/// here is your background task
return true;
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Boolean>() {
#Override
public void onSubscribe(Disposable d) {
//// pre execute here is my progress dialog
showProgressDialog(getString(R.string.scanning));
}
#Override
public void onNext(Boolean aBoolean) {
//// here is on sucess you can do anystuff here like
if (aBoolean){
/// if its value true you can go ahead with this
}
}
#Override
public void onError(Throwable e) {
/// this helps you to go if there is any error show dialog whatever you wants here
Log.e("error of kind",e.getMessage() );
}
#Override
public void onComplete() {
/// when your task done means post execute
}
});
once its done lets start working with implementation
Observable.fromCallable(new Callable<Boolean>() {
#Override
public Boolean call() throws Exception {
/// here is your background task
uribitmap = getScannedBitmap(original, points);
uri = Utils.getUri(getActivity(), uribitmap);
scanner.onScanFinish(uri);
return true;
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Boolean>() {
#Override
public void onSubscribe(Disposable d) {
//// pre execute here is my progress dialog
showProgressDialog(getString(R.string.scanning));
}
#Override
public void onNext(Boolean aBoolean) {
//// here is on sucess you can do anystuff here like
if (aBoolean){
/// if its value true you can go ahead with this
}
}
#Override
public void onError(Throwable e) {
/// this helps you to go if there is any error show dialog whatever you wants here
Log.e("error of kind",e.getMessage() );
}
#Override
public void onComplete() {
/// when your task done means post execute
uribitmap.recycle();
dismissDialog();
}
});
now i will do this with executors :
/// pre execute you can trigger to progress dialog
showProgressDialog(getString(R.string.scanning));
ExecutorService executors = Executors.newSingleThreadExecutor();
executors.execute(new Runnable() {
#Override
public void run() {
//// do background heavy task here
final Bitmap uribitmap = getScannedBitmap(original, points);
uri = Utils.getUri(getActivity(), uribitmap);
scanner.onScanFinish(uri);
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
//// Ui thread work like
uribitmap.recycle();
dismissDialog();
}
});
}
});
You are getting this error because you are trying to insert an Object on the main (UI) thread.
You should do something like this:
Observable.fromCallable(() -> myViewModel.insertDatabase( myRoomEntity ))
.subscribeOn( Schedulers.io() )
.observeOn( AndroidSchedulers.mainThread() );
And then use an Observer to subscribe to the Observable.
Please try restructuring your code like this:
Completable.fromAction(() -> myViewModel.insertDatabase(myRoomEntity))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> Log.d("TAG", "Populating database Success"),
throwable -> Log.d("TAG", throwable.toString()))
Considerations:
If your myRoomEntity is not available before this whole construct gets subscribed, make sure you use defer http://reactivex.io/documentation/operators/defer.html
Your subscribe section handlers are operating on "main", that's why you were receiving a crash.
If possible, avoid unnecessary just calls
Say I have a bunch of transformations on an Observable:
operation()
.flatMap(toSomething())
.map(toSomethingElse())
.flatMap(toYetSomethingElse())
.subscribeOn(Schedulers.newThread())
.observeOn(AdroidSchedulers.mainThread())
.subscribe(observer);
Are all of these operations synchronous except for the last call to flatMap()? Or are all of the operations run on the thread that I told it to subscribe on?
I figured this out, with a test. The following test passes (which means the emissions on the Observable are all on the same background thread):
volatile long observableThreadId;
#Test
public void transformedObservables_shouldRunInSameThread() {
Observable.from(new String[]{"a", "b", "c"}) //
.flatMap(new Func1<String, Observable<Object>>() {
#Override public Observable<Object> call(String s) {
observableThreadId = Thread.currentThread().getId();
return Observable.from((Object) s);
}
}) //
.map(new Func1<Object, String>() {
#Override public String call(Object o) {
long id = Thread.currentThread().getId();
if (id != observableThreadId) {
throw new RuntimeException("Thread ID mismatch");
}
return (String) o;
}
}) //
.flatMap(new Func1<String, Observable<String>>() {
#Override public Observable<String> call(String s) {
long id = Thread.currentThread().getId();
if (id != observableThreadId) {
throw new RuntimeException("Thread ID mismatch");
}
return Observable.from(s);
}
}) //
.subscribeOn(Schedulers.newThread()) //
.observeOn(Schedulers.currentThread()) //
.subscribe(new Observer<String>() {
#Override public void onCompleted() {
assertThat(Thread.currentThread().getId()).isNotEqualTo(observableThreadId);
}
#Override public void onError(Throwable throwable) {
}
#Override public void onNext(String s) {
}
});
System.out.println("blah");
}
===============================
UPDATE:
A better answer can actually be found in the ReactiveX documentation on Scheduler:
By default, an Observable and the chain of operators that you apply to
it will do its work, and will notify its observers, on the same thread
on which its Subscribe method is called. The SubscribeOn operator
changes this behavior by specifying a different Scheduler on which the
Observable should operate. The ObserveOn operator specifies a
different Scheduler that the Observable will use to send notifications
to its observers.
... the SubscribeOn operator designates which thread the Observable will
begin operating on, no matter at what point in the chain of operators
that operator is called. ObserveOn, on the other hand, affects the
thread that the Observable will use below where that operator appears.
For this reason, you may call ObserveOn multiple times at various
points during the chain of Observable operators in order to change on
which threads certain of those operators operate.
I am receiving an error when using NServiceBus 4.0.3 with NHibernate 3.3.1 when it's trying to process a message
INFO NServiceBus.Unicast.Transport.TransportReceiver [(null)] <(null)> - Failed to process message
Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'NServiceBus.Impersonation.ExtractIncomingPrincipal' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
at NServiceBus.Unicast.Transport.TransportReceiver.ProcessMessage(TransportMessage message) in c:\BuildAgent\work\d4de8921a0aabf04\src\NServiceBus.Core\Unicast\Transport\TransportReceiver.cs:line 353
at NServiceBus.Unicast.Transport.TransportReceiver.TryProcess(TransportMessage message) in c:\BuildAgent\work\d4de8921a0aabf04\src\NServiceBus.Core\Unicast\Transport\TransportReceiver.cs:line 233
at NServiceBus.Transports.Msmq.MsmqDequeueStrategy.ProcessMessage(TransportMessage message) in c:\BuildAgent\work\d4de8921a0aabf04\src\NServiceBus.Core\Transports\Msmq\MsmqDequeueStrategy.cs:line 262
at NServiceBus.Transports.Msmq.MsmqDequeueStrategy.Action() in c:\BuildAgent\work\d4de8921a0aabf04\src\NServiceBus.Core\Transports\Msmq\MsmqDequeueStrategy.cs:line 197
2013-08-30 09:35:02,508 [9] WARN NServiceBus.Faults.Forwarder.FaultManager [(null)] <(null)> - Message has failed FLR and will be handed over to SLR for retry attempt: 1, MessageID=8aaed043-b744-49c2-965d-a22a009deb32.
I think it's fairly obvious what that I need to implement or register an "ExtractIncomingPrincipal", but I can't seem to find any documentation on how or whether there is a default one that I can use. I wouldn't have figured that I would have had to register any of the NServiceBus-related services as many of them are already being registered in my IoC implementation.
As requested, here is the EndpointConfig and supporting code I have currently:
[EndpointSLA("00:00:30")]
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization {
public void Init() {
Configure.With().ObjectBuilderAdapter().UseInMemoryTimeoutPersister().UseInMemoryGatewayPersister().InMemorySagaPersister().InMemorySubscriptionStorage();
}
}
//public class PrincipalExtractor : ExtractIncomingPrincipal {
// public IPrincipal GetPrincipal(TransportMessage message) {
// return Thread.CurrentPrincipal;
// }
//}
public class ObjectBuilderAdapter : IContainer {
readonly IDependencyInjector injector;
public ObjectBuilderAdapter(IDependencyInjectionBuilder dependencyInjectionBuilder) {
injector = dependencyInjectionBuilder.Create(); //This method does all the common service registrations that I am trying to re-use
//injector.RegisterType<ExtractIncomingPrincipal, PrincipalExtractor>();
}
public void Dispose() {
injector.Dispose();
}
public object Build(Type typeToBuild) {
return injector.Resolve(typeToBuild);
}
public IContainer BuildChildContainer() {
return new ObjectBuilderAdapter(new DependencyInjectorBuilder());
}
public IEnumerable<object> BuildAll(Type typeToBuild) {
return injector.ResolveAll(typeToBuild);
}
public void Configure(Type component, DependencyLifecycle dependencyLifecycle) {
injector.RegisterType(component);
}
public void Configure<T>(Func<T> component, DependencyLifecycle dependencyLifecycle) {
injector.RegisterType(component);
}
public void ConfigureProperty(Type component, string property, object value) {
if (injector is AutofacDependencyInjector) {
((AutofacDependencyInjector)injector).ConfigureProperty(component, property, value);
} else {
Debug.WriteLine("Configuring {0} for property {1} but we don't handle this scenario.", component.Name, property);
}
}
public void RegisterSingleton(Type lookupType, object instance) {
injector.RegisterInstance(lookupType, instance);
}
public bool HasComponent(Type componentType) {
return injector.IsRegistered(componentType);
}
public void Release(object instance) { }
}
public static class Extensions {
public static Configure ObjectBuilderAdapter(this Configure config) {
ConfigureCommon.With(config, new ObjectBuilderAdapter(new DependencyInjectorBuilder()));
return config;
}
}
I removed the IWantCustomInitialization (left over from something else I had tried earlier) interface implementation on the class and my service now processes the message. There are errors still (relating to trying to connect to Raven [even though I thought I am using everything in-memory), but it's processing the message.
Im getting a headache from the Orchard Token Providers and any help would be a blessing.
Ive mostly been copying the Comments Tokens.
The target is for an email to be sent when a 'Question' content item is published.
Here is my solution for the Content Type 'Question':
public interface ITokenProvider : IEventHandler
{
void Describe(dynamic context);
void Evaluate(dynamic context);
}
public class QuestionTokens : ITokenProvider
{
public QuestionTokens()
{
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public void Describe(dynamic context)
{
//presume this is correct
context.For("Content", T("Content Items"), T("Content Items"))
.Token("QuestionText", T("Question Text"), T("Text of the question"))
.Token("QuestionAuthor", T("Question Author"), T("Author of the question"));
//presume this is incorrect? correct for the content type?
context.For("Question", T("Questions"), T("Questions from users"))
.Token("QuestionText", T("Question Text"), T("Text of the question"))
.Token("QuestionAuthor", T("Question Author"), T("Author of the question"));
}
public void Evaluate(dynamic context)
{
Func<IContent, object> questionTextAccessorFromContent = (content) => {
var part = content.As<QuestionPart>();
return part.QuestionText;
};
Func<QuestionPart, object> questionTextAccessor = (part) =>
{
return part.QuestionText;
};
Func<IContent, object> authorAccessorFromContent = (content) => {
var part = content.As<QuestionPart>();
return part.Author;
};
Func<QuestionPart, object> authorAccessor = (part) =>
{
return part.Author;
};
//doesnt work
context.For<IContent>("Content")
.Token("QuestionText", (Func<IContent, object>)
(content => content.As<QuestionPart>().Record.QuestionText))
.Token("QuestionAuthor", (Func<IContent, object>)
(content => content.As<QuestionPart>().Record.Author));
//doesnt work
context.For<IContent>("Question")
.Token("QuestionText", (Func<IContent, object>)
(content => content.As<QuestionPart>().Record.QuestionText))
.Token("QuestionAuthor", (Func<IContent, object>)
(content => content.As<QuestionPart>().Record.Author));
//doesnt work
context.For<QuestionPart>("Question")
.Token("QuestionText", (Func<QuestionPart, object>)
(content => content.Record.QuestionText))
.Token("Author", (Func<QuestionPart, object>)
(content => content.Record.Author)); ;
}
private static string QuestionText(IContent question)
{
var questionPart = question.As<QuestionPart>();
return questionPart.QuestionText;
}
private static string Author(IContent question)
{
var questionPart = question.As<QuestionPart>();
return questionPart.Author;
}
}
This is the Action (when the Question is published), it sends a email with the body text:
(updated to test Medeiros suggestion)
<p>A question has been created by: {Content.QuestionAuthor}</p>
<p>A question has been created by: {Content.QuestionAuthor.Text}</p>
<p>A question has been created by: {Content.QuestionAuthor.Value}</p>
<p>A question has been created by: {Question.QuestionAuthor}</p>
<p>A question has been created by: {Question.QuestionAuthor.Text}</p>
<p>A question has been created by: {Question.QuestionAuthor.Value}</p>
<p>Message: {Content.QuestionText}</p>
<p>Message: {Content.QuestionText.Text}</p>
<p>Message: {Content.QuestionText.Value}</p>
<p>Message: {Question.QuestionText}</p>
<p>Message: {Question.QuestionText.Text}</p>
<p>Message: {Question.QuestionText.Value}</p>
All 'my' tokens are replaced with blank text. Other tokens like: {Content.ContentType} {User.Email} work just fine. Any mistakes or hints that anyone notices will be very useful.
Thanks, Matt
IVe re-written the token provider and it works sort of... (and now works completely)
public class QuestionTokens : Orchard.Tokens.ITokenProvider
{
private readonly IContentManager contentManager;
private readonly IWorkContextAccessor workContextAccessor;
public QuestionTokens(IContentManager contentManager, IWorkContextAccessor workContextAccessor)
{
this.workContextAccessor = workContextAccessor;
this.contentManager = contentManager;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
/// <summary>
/// Describes the specified context.
/// </summary>
/// <param name="context">The context.</param>
public void Describe(DescribeContext context)
{
context.For("Content", T("Content Items"), T("Content Items"))
.Token("QuestionText", T("Question Text"), T("Text of the question"))
.Token("QuestionAuthor", T("Question Author"), T("Author of the question"));
}
/// <summary>
/// Evaluates the specified context.
/// </summary>
/// <param name="context">The context.</param>
public void Evaluate(EvaluateContext context)
{
context.For<IContent>("Content")
.Token("QuestionText", content => QuestionText(content, context))
.Token("QuestionAuthor", content => Author(content, context));
}
private string QuestionText(IContent question, EvaluateContext context)
{
var questionPart = question.As<QuestionPart>();
return questionPart.QuestionText;
}
private string Author(IContent question, EvaluateContext context)
{
var questionPart = question.As<QuestionPart>();
return questionPart.Author;
}
}
I can now debug the code at least in the private QuestionText and Author methods. The value retrieved is 'null' due to the published event firing when this line is fired in the controller:
services.ContentManager.Create("Question");
so the content item has not been populated and the 'published' event is firing before its life has begun. Time to play about with its content type settings a bit to see if that makes a difference...
Finally all working as needed. The Events section 'published' event is still happening when i dont expect it to so I've changed the life of the content.
From a controller: (some code into a service removed so I don't have a post more code)
var item = services.ContentManager.New<Models.QuestionPart>("Question");
this.TryUpdateModel(item);
services.ContentManager.Create(item.ContentItem); //where the event is firing
Services.ContentManager.Publish(item.ContentItem); //where i expected the event to fire :)
Originally it was create, update and save. But the events restricted that. So now its new, update and create.
As for the tokens. They are simply:
{Content.QuestionAuthor} and {Content.QuestionText}. Nice and simple when everything is working, and everything is right.
Matthew, try adding a .Value for the: {Content.QuestionAuthor.Value}
If this doesn't work, try .Text.
Hope this helps.
The code in your original question looks accurate. You follow the correct Orchard Events bus convention of creating an interface inheriting from IEventHandler link.
Your change in the controller code is likely the reason you are getting values. My experience debugging the code is that it depends on what Event your rule is subscribing to. For example an event of type "Content Created" hits the debugger in the Evaluate method but all custom part properties are null. But an event of "When a Custom Form is submitted" hits and has values populated.
I spent a full day troubleshooting my code which looks similar to yours above and it wasn't until I started testing the other event types in my rule that it had data populated in the private functions.
Here's my code:
public interface ITokenProvider : IEventHandler
{
void Describe(dynamic context);
void Evaluate(dynamic context);
}
public class PersonTokens : ITokenProvider
{
private readonly IContentManager _contentManager;
public Localizer T { get; set; }
public PersonTokens(IContentManager contentManager)
{
_contentManager = contentManager;
T = NullLocalizer.Instance;
}
public void Describe(dynamic context)
{
context.For("Content", T("Content Items"), T("Content Items"))
.Token("FirstName", T("First Name"), T("The Person's First Name"))
.Token("LastName", T("Last Name"), T("The Person's Last Name"))
.Token("EmailAddress", T("Email Address"), T("The Person's Email Address"))
.Token("PhoneNumber", T("Phone Number"), T("The Person's Phone Number"));
}
public void Evaluate(dynamic context)
{
// Orchard.Tokens.Implementation.TokenManager.EvaluateContextImpl
context.For<IContent>("Content")
.Token("FirstName", (Func<IContent, object>)FirstName)
.Token("LastName", (Func<IContent, object>)(content => content.As<PersonPart>().Record.LastName))
.Token("EmailAddress", (Func<IContent, object>)(content => content.As<PersonPart>().EmailAddress))
.Token("PhoneNumber", (Func<IContent, object>)PhoneNumber); // left the PhoneNumber out of sample
}
private string FirstName(IContent person)
{
var personPart = person.As<PersonPart>();
return personPart.Record.FirstName;
}
}