Currently, I am able to send messages to a single queue "devQueue". When the message arrives to a "devQueue", it needs to be sent to the "localQueue" as well. I am finding this implementation challenging. I tried to call a different class "local_send" from "class1" class so I can connect to the other destination which is "localQueue" (as shown in the code below) but with no luck. Is there any proton function that would be useful or can I use the reference variable from on_connection_open() within "class1" class inside v_message() function? Any help or idea in this direction would be greatly appreciated.
Currently the code is not getting into "local_send" class and hence the messages are not being sent to "localQueue".
class class1 : public proton::messaging_handler {
std::string url;
std::string devQueue;
std::string localQueue;
std::vector<proton::message> msgVector;
local_send snd;
std::vector<proton::message> msgV;
public:
class1(const std::string& u, const std::string& devQueue, const std::string& localQueue) :
url(u), devQueue(devQueue), localQueue(localQueue), snd(msgVector, localQueue) {}
void on_container_start(proton::container& c) override {
c.connect(url);
}
void on_connection_open(proton::connection& c) override {
c.open_receiver(devQueue);
}
void on_message(proton::delivery &d, proton::message &msg) override {
msgV.push_back(msg);
// Here, the messages are checked if they have a valid format or not and v_message() is called
}
void v_message(const pack& msg){
this->msgVector.push_back(msg);
//I need to send the message to localQueue and hence we are running another class from here (but that is not working :( )
local_send snd(msgVector, localQueue);
proton::container(snd).run();
}
void on_sendable(proton::sender &s) override {
for(auto msg: msgVector){
s.send(msg);
}
}
};
// Do I even need this class to send message to another destination?
class local_send : public proton::messaging_handler {
std::string localQueue;
std::vector<proton::message> msgVector;
public:
local_send(std::vector<proton::message> msgVector, const std::string& localQueue) :
msgVector(msgVector), localQueue(localQueue) {}
void on_connection_open(proton::connection& c) override {
c.open_receiver(localQueue);
}
void on_sendable(proton::sender &s) override {
for(auto msg: msgVector){
s.send(msg);
}
}
};
You need to call open_sender to create a channel for sending messages. You can do this all in one handler. Pseudo code:
proton::sender snd;
on_connection_open(conn) {
conn.open_receiver("queue1");
snd = conn.open_sender("queue2");
}
on_message(dlv, msg) {
// Relay message from queue1 to queue2
snd.send(msg);
}
This snippet doesn't use on_sendable. The send is buffered in the library. If you prefer, you can use a vector of messages as you've done in your code and use on_sendable (in the same handler) to send when there's credit.
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.
In the source code of EventHubClient (from Azure SDK) (LINK) I see that there are diagnostic traces in part of the function calls. I want to find out how I can enable this so that the traces show up somewhere.
I want to find out how I can enable this so that the traces show up somewhere.
You need to implement a EventListener to save the logs to anywhere you want. Following is a sample of writing logs to a file.
public sealed class FileEventListener : EventListener
{
private string _filePath;
public FileEventListener(string name)
{
this._filePath = name;
}
private void WriteToFile(string message)
{
File.AppendAllText(_filePath, DateTime.Now.ToString() + message + "\n");
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
WriteToFile(string.Format(eventData.Message, eventData.Payload.ToArray()));
}
protected override void OnEventSourceCreated(EventSource eventSource)
{
}
}
After implemented your own EventListener, you could use it in your application.
EventListener myFileListener = new FileEventListener("D:\\log.txt");
myFileListener.EnableEvents(EventHubsEventSource.Log, EventLevel.LogAlways);
I have a MainForm class (as you'd expect, it is a form) that has a text box on it. I also have another class called 'Application_Server' That does a load of other stuff (not just form-background related, quite a lot of network based stuff etc.).
The Application_Server class runs in it's own thread, but needs to be able to update the controls on the form, for this question, we will stick with just the textbox.
The problem is that even though I am executing the command to set the text of the textBox control via 'Invoke' I am still getting the following exception during runtime:
Additional information: Cross-thread operation not valid: Control
'DebugTextBox' accessed from a thread other than the thread it was
created on.
What could be causing this? I am definitely invoking a delegate within MainForm.
Here are the relevant code segments (cut down for readability):
MainForm.h:
public ref class MainForm : public System::Windows::Forms::Form {
delegate void del_updateDebugText(String^ msg);
del_updateDebugText^ updateDebugText = gcnew del_updateDebugText(this, &MainForm::postDebugMessage);
private: void postDebugMessage(String^ message);
};
MainForm.cpp:
void EagleEye_Server::MainForm::postDebugMessage(String^ message)
{
Monitor::Enter(DebugTextBox);
if (this->DebugTextBox->InvokeRequired)
{
this->Invoke(updateDebugText, gcnew array<Object^> { message });
}
else
{
this->DebugTextBox->AppendText(message);
}
Monitor::Exit(DebugTextBox);
}
And finally, the code calling it:
void ServerAppManager::postDebugMessage(System::String^ message)
{
mainFormHandle->updateDebugText(message);
}
void ServerAppManager::applicationStep()
{
postDebugMessage("Starting\n");
// This is Run in seperate thread in MainForm.cpp
while (s_appState == ApplicationState::RUN)
{
postDebugMessage("Testing\n");
}
}
Thanks!
From background worker called bwSearch we do the call as following from the DoWork event handler:
private: System::Void bwSearch_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
//... logic
UpdateTxtOutput("Some message");
//... more logic
}
I have a RitchTextBox called txtOutput, also the windows form control containing this code is called frmMain, the UpdateTxtOutput is defined in three parts as follows:
delegate void UpdateTxtOutputDelegate(String^ text);
void UpdateTxtOutput(String^ text)
{
UpdateTxtOutputDelegate^ action = gcnew UpdateTxtOutputDelegate(this, &frmMain::Worker);
this->BeginInvoke(action, text);
}
void Worker(String^ text)
{
txtOutput->AppendText("\t" + text + "\n");
}
I managed to get it working by simplifying the method within the 'MainForm' class to:
void EagleEye_Server::MainForm::postDebugMessage(String^ message)
{
Monitor::Enter(DebugTextBox);
DebugTextBox->AppendText(message);
Monitor::Exit(DebugTextBox);
}
And then moving the 'Invoke' call to the method calling the delegate, not pretty but it works for now. I think the issue may have been caused by the form getting stuck inside an Invoke loop. I say this as I noticed that the form would lock up and stop responding after it hit the recursive Invoke statement.
i started to work with .net remoting, read myself through tutorials and explanations, saw now at least three examples on the web and they looked all similar to my code. i can't find the reason for the error I get. (RemotingException was unhandled "Attempted to call a method declared on type 'System.IFormattable' on an object which exposes 'HES.MyProcess'.")
I tried to fix this for six hours now, unsuccessfully looking up the internet, reading through lots of pages...
Maybe you guys can help me out ?
MarshalByRefObject deriving class looks like:
public class MyProcess : MarshalByRefObject, IMyProcess
{
//public System.Diagnostics.Process process {get; set;}
public MyProcess()
{
// TODO: Complete member initialization
// this.process = Process.GetCurrentProcess();
}
public string GetProcessId()
{ Console.WriteLine("I'm on..");
return "test";
// return this.process.Id;
}
}
My interface loooks like this:
interface IMyProcess
{
string GetProcessId();
}
My server looks like this:
namespace HES
{
public class HES_Starter
{
public static void Main(string[] args)
{
// using TCP protocol
TcpChannel channel = new TcpChannel(_port);
//second value is for security settings
ChannelServices.RegisterChannel(channel, false);
Console.WriteLine("HES Server here... on PID: " + Process.GetCurrentProcess().Id);
//Type, objectUri to access the object remotely, mode
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(HES.MyProcess), "HESProcess",
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
}
and finally my client like that:
namespace Service_Provider
{
public class Program
{
public static void Main(string[] args)
{
TcpChannel channel = new TcpChannel();
//second value is for security settings
ChannelServices.RegisterChannel(channel, false);
Console.WriteLine("HES Client here...");
IMyProcess remoteProcess = (IMyProcess)Activator.GetObject(
typeof(IMyProcess), "tcp://localhost:8050/HESProcess");
Console.WriteLine(remoteProcess);
Console.WriteLine(remoteProcess.GetProcessId());
Console.ReadLine();
}
}
}
does anybody have a clue what i'm doing wrong ?
I mean from the exception I can see that the client knows that the object is an remote object in the 'HES' namespace. And in debug I can see that the object
remoteProcess = {System.Runtime.Remoting.Proxies.__TransparentProxy}
is a proxy...
I don't know what i'm doing wrong here.
I'm trying to get windows workflows working, and I've become a little stumped.
I've gotten a single workflow working, but now I am trying to do something a little more complex: start a workflow, where each activity itself contains a workflow. (Picture something like the main program starts the activities "Input, logic, and output", and then each of those have additional activities like "prompt user, get input, etc.")
I've had it working fine, with the example from here (http://msdn.microsoft.com/en-us/magazine/gg535667.aspx), when I am not passing any parameters from the main program to the activites. My question is, how exactly does the 'Variables' and 'metadata.SetVariablesCollection' work in the NativeActivity, and how to I get the parameters to the low level activities?
This is what I am currently trying:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Collections.ObjectModel;
using System.Activities.Statements;
namespace Project1
{
internal class MainProgram
{
internal static void Main(string[] args)
{
try
{
var act = new SimpleSequence();
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Variables.Add(new Variable<string> ("stringArg", "TEXT"));
WorkflowInvoker.Invoke(act);
}
catch (Exception ex)
{
System.Console.WriteLine("EXCEPTION: {0}", ex);
}
}
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
protected override void Execute(CodeActivityContext context)
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
public class SimpleSequence : NativeActivity
{
Collection<Activity> activities;
Collection<Variable> variables;
Variable<int> current = new Variable<int> { Default = 0 };
public Collection<Activity> Activities
{
get
{
if (this.activities == null)
this.activities = new Collection<Activity>();
return this.activities;
}
set
{
this.activities = value;
}
}
public Collection<Variable> Variables
{
get
{
if (this.variables == null)
this.variables = new Collection<Variable>();
return this.variables;
}
set
{
this.variables = value;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.SetChildrenCollection(this.activities);
metadata.SetVariablesCollection(this.variables);
metadata.AddImplementationVariable(this.current);
}
protected override void Execute(NativeActivityContext context)
{
if (this.Activities.Count > 0)
context.ScheduleActivity(this.Activities[0], onChildComplete);
}
void onChildComplete(NativeActivityContext context, ActivityInstance completed)
{
int currentExecutingActivity = this.current.Get(context);
int next = currentExecutingActivity + 1;
if (next < this.Activities.Count)
{
context.ScheduleActivity(this.Activities[next], this.onChildComplete);
this.current.Set(context, next);
}
}
}
}
}
This ends up throwing the following exception:
EXCEPTION: System.Activities.InvalidWorkflowException: The following errors were encountered while processing the workflow tree:
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
at System.Activities.Validation.ActivityValidationServices.ThrowIfViolationsExist(IList`1 validationErrors)
at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at System.Activities.WorkflowApplication.RunInstance(WorkflowApplication instance)
at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
at Project1.MainProgram.Main(String[] args) in c:\users\user\documents\visual studio 2010\Projects\ModelingProject1\Project1\MainProgram.cs:line 25
I know, I only pass 1 parameter, but the exception still says that I am missing 3 parameters. I am missing something as to how to do this properly.
You're correctly declaring stringArg as an InArgument but you're not passing any value to it when calling it inside SimpleSequence.
You can pass something using the constructor, while constructing the all activity itself, like this:
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
public WriteSomeText(string stringArg)
{
this.stringArg = stringArg;
}
protected override void Execute(CodeActivityContext context
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
// Calling the activity like this:
internal static void Main(string[] args)
{
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
}
};
WorkflowInvoker.Invoke(act);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
Also notice that is a best practice to use the constructor to initialize collections:
public SimpleSequence()
{
activities = new Collection<Activity>();
variables = new Collection<Variable>();
}
This way is even more intuitive to initialize the activity:
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
},
Variables =
{
new Variable<int>("myNewIntVar", 10),
// ....
}
};
EDIT:
There are a couple of other ways to approach the problem. This is your best friend while starting in the WF4 world.
Check WF\Basic\CustomActivities\Code-Bodied for a little push with this particular case.