WorkItem State injection troubles

Topics: CAB & Smart Client Software Factory
Feb 16, 2006 at 7:07 PM
originally posted by: sklett

I'm not spamming the board, I promise. I've read the help over and over, looked at the sample and I just can't figure this one out.

I'm creating two workitems in my ModuleInit class's Load() method. Here is the code from that method:
<code>
private WorkItem rootWorkItem;

ServiceDependency
public WorkItem RootWorkItem
{
set { rootWorkItem = value; }
}

public override void Load()
{
// get the items from the DAL, store them in the rootWorkItem state so that
// they can be injected into the child WorkItems
InventoryBO.InventoryItemCollection items = DAL.GetItems();
rootWorkItem.State"InventoryItems" = items;

AddInventoryItemWorkItem addInventoryWorkitem = rootWorkItem.Items.AddNew<AddInventoryItemWorkItem>("AddInventoryItemWorkItem");

InventoryWorkItem workitem = rootWorkItem.WorkItems.AddNew<InventoryWorkItem>("InventoryWorkItem");
workItem.State"InventoryItems" = items;

workitem.Run();
}
</code>


Now, the above code is not exactly what is in the help file, in the help file it states that I can add:
State("InventoryItems")
before adding my work item to the RootWorkItem, but in the ModuleInit class, I can't use State[] attribute, so then I read in the help file that I could do it the way I am.



Then in my InventoryWorkItem class I have this:
<code>
State("InventoryItems")
public InventoryItemCollection InventoryItems
{
set { State"InventoryItems" = value; }
get { return (InventoryItemCollection)State"InventoryItems"; }
}
</code>

This throws a ModuleLoadException.


What I don't understand is how to achieve child WorkItem state injection. I must be confused on what the "root" work item is. From what I have read, I thought it was a WorkItem that is automatically created by the CAB, then in ModuleInit when I'm adding MY WorkItems to the rootWorkItem they are in effect "child" WorkItems of the "Root" WorkItem.

What I'm trying to achieve is a shared state where my BusinessObjects are available to all my WorkItems so that I can modify the Model in one WorkItem and have the changes reflected in others.

I have worked around this by using code in my views like this:
<code>
private WorkItem parentWorkItem;

ServiceDependency
public WorkItem ParentWorkItem
{
set { parentWorkItem = value; }
}


private void InventoryView_Load(object sender, EventArgs e)
{
// get all the items
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = parentWorkItem.Parent.State"InventoryItems";
}
</code>

But that is kinda ugly. :0(


I have looked at the SCBat samples, but they seem to follow a different approach than mine which is based on BankTeller.

Anyway, if anyone can help me solve this or at least give me some pointers, that would be really great.

Thanks for reading!
Steve
Feb 17, 2006 at 5:40 AM
originally posted by: DLorenz

In order to pass down state, the child workitem has to reinject the state back into itself. Here is an example (in VB.NET, since that is what I'm working with).

ParentWorkItem:

me.State("Something") = someObject
'Create child workitem


ChildWorkItem:

<State("Something")> _
public property Something() as SomeObject
Get
return _someObject
End Get
Set (value as SomeObject)
_someObject = value
End Set
End Property

...

Public Sub OnRunStarted()
Me.State("Something") = _someObject
End Sub


Then all the SmartParts/Controllers will have access to the same state for the ChildWorkItem as they would for the Parent. However, if you pass down primitive types, the parent's state won't get changes made by the child.

Hope this helps!
Feb 17, 2006 at 6:01 AM
originally posted by: sklett

Thanks for the post. I'm not seeing the advantage or the "State Injection" in your code. I'm probably not understanding something, but it looks like what this does is inject the parent WI's state into a member, then in the OnRunStarted you use this member to set the child WI's state.

I think I could also do this and avoid the need for the property with attributes part:
this.State"InventoryItems" = this.Parent.State"InventoryItems";

What I was hoping for was a way to inject parent WI State into child WI State without having to execute anything. Most of my WorkItems never have Run() called, just Show() and it would be cleaner if the injection could happen during object creation.

Maybe I was confused by the help file, I was looking at these lines:

"To inject state into child SmartParts in a child WorkItem
In the parent WorkItem, set the state so that adding a child WorkItem to the container injects the state into it, as shown in the following code.C
public void ShowCustomerDetails(Customer custmr)
{
// set state for injection into child WorkItem
State"Customer" = custmr;
ChildWorkItem myChild = this.WorkItems.AddNew<ChildWorkItem>();
myChild.Run();
}


In the child WorkItem, use the State attribute to indicate that a parent WorkItem should inject the property into the child WorkItem. This allows the child SmartParts to access it.C
// in child WorkItem
State("Customer")
public Customer TheCustomer
{
get { return (Customer)State"Customer"; }
set { State"Customer" = value; }
}
"


However, when I try and add the above code to my child WI, I get an exception:
Message: "Exception has been thrown by the target of an invocation."
InnerException: "Object reference not set to an instance of an object."

My Code:
<code>
State("InventoryItems")
public InventoryItemCollection TheCustomer
{
get { return (InventoryItemCollection)State"InventoryItems"; }
set { State"InventoryItems" = value; }
}
</code>

When I step through in debug, State in the set method is null.

I'm still unclear what I should expect this to work like. From the help file, it looks like this would inject parent WI state into the child WI state during object initialization and that is exactly what I want.

Thanks for the post, I appreciate your help, I still just have some more quesitons :)
Feb 17, 2006 at 6:23 AM
originally posted by: DLorenz

In the parent workitem, you do the following code before running the child, right?:

this.State("InventoryItems") = someValue;


You are also calling this.Workitems.AddNew(Of ChildWorkItem) when creating the child, right? If not, then the parent's state wouldn't get passed down.
Feb 17, 2006 at 6:32 AM
originally posted by: sklett

In my InventoryModuleInit : ModuleInit class in the Load() method I have this code
<code>
InventoryBO.InventoryItemCollection items = new DAL.GetItems();

// create the default work item and start it
rootWorkItem.State"InventoryItems" = items;
InventoryWorkItem workitem = rootWorkItem.WorkItems.AddNew<InventoryWorkItem>("InventoryWorkItem");

workitem.Run();
</code>

Here is the code for rootWorkItem:
<code>
private WorkItem rootWorkItem;

ServiceDependency
public WorkItem RootWorkItem
{
set { rootWorkItem = value; }
}
</code>

So to answer your question: Yes, I think I am doing that, but I'm not certain. I think the way I'm doing it is correct, but....
Feb 17, 2006 at 6:42 AM
originally posted by: DLorenz

That looks correct to me. I've had issues with this as well. One solution is to call Parent.State, like you did before. The other way is to do what I did. The main reason it is dying is because the State collection wasn't instantiated yet for whatever reason. That is why you can store the state on a class level variable in the child, and then reinject it during the child's Run phase. Though, if it works in the code example(s), I don't know why it isn't working for us.
Feb 17, 2006 at 6:55 AM
originally posted by: sklett

I actually haven't been able to find a code example that illustrates ChildWork item state injection. I would like to, it would give me something to aid in my debugging ;)

Hopefully one of the developers here will catch this thread and throw out a solution for us.

Thanks for you help, if I find out anything I will let you know, in the meantime I will work on other stuff until this gets solved.
Feb 17, 2006 at 12:05 PM
originally posted by: marianoszklanny

As DLorenz pointed out, the State collection of the child work item is not yet initialized at the moment of injecting the values of the properties with the State attribute.

When WorkItems.AddNew is called, the ObjectBuilder starts building the new work item. To create it, the OB uses a pipeline of strategies that allows multiple operations to be executed during the process of creation. This pipeline is divided in stages, in the OB the defaults are PreCreation, Creation, Initialization and PostInitialization.

If we take a look at the strategies within the Initialization stage, we have:

-PropertySetterStrategy
-PropertyReflectionStrategy
-MethodReflectionStrategy
-MethodExecutionStrategy

These strategies are executed in order by the OB in the creation process. In the PropertySetterStrategy, the values of the public properties with attributes are injected. In your case, the OB tries to inject the property

State("InventoryItems")
public InventoryItemCollection InventoryItems
{
set { State"InventoryItems" = value; }
get { return (InventoryItemCollection)State"InventoryItems"; }
}

But the problem here is that the State collection of the child work item is not initialized until the MethodExecutionStrategy is executed. That’s why you get the null reference exception.

As regards the example code in the help, it does not work as expected.

I can suggest you two workarounds besides the presented by DLorenz:
1- Set the child work item state manually:

In the parent work item:

public override void Load()
{
// get the items from the DAL, store them in the rootWorkItem state so that
// they can be injected into the child WorkItems
InventoryBO.InventoryItemCollection items = DAL.GetItems();

AddInventoryItemWorkItem addInventoryWorkitem = rootWorkItem.Items.AddNew<AddInventoryItemWorkItem>("AddInventoryItemWorkItem");

InventoryWorkItem workitem = rootWorkItem.WorkItems.AddNew<InventoryWorkItem>("InventoryWorkItem");
workItem.State"InventoryItems" = items;

workitem.Run();
}

In the child work item:

// no attribute here
public InventoryItemCollection InventoryItems
{
set { State"InventoryItems" = value; }
get { return (InventoryItemCollection)State"InventoryItems"; }
}

(continued)
Feb 17, 2006 at 12:05 PM
originally posted by: sklett

Hi Mariano,

Thank you for your suggestions. Your second suggestion sounds better to me ;) However, I don't really understand what you mean with this part:
"create a new attribute (e.g: “InheritState”) for the work items and append the state copy operation in the ObjectBuilder pipeline, after the initialization of the collections for the work items with the InheritState attribute."

Which is really the whole thing, but anyway, are you saying I should change some of the CAB code? I just want to make sure before I go in and possible break something. Is there any chance that you could supply me with a filename or small snippet of code to help guide me in the correct direction? To be honest, I really don't understand how 95% of the CAB is working (yet) so I'm a little reluctant to make changes.
Feb 17, 2006 at 12:06 PM
originally posted by: marianoszklanny

2- This is an idea I haven’t implemented yet: create a new attribute (e.g: “InheritState”) for work items and append the state copy operation in the ObjectBuilder pipeline, after the initialization of the collections for the work items with the InheritState attribute. This way, you would forget about injecting state into child work items:


In the child work item:

InheritState // Inherit the state from the parent work item
public class InventoryWorkItem : WorkItem
{
//… no State properties here, not needed
}

In the parent work item:

public override void Load()
{
// get the items from the DAL, store them in the rootWorkItem state so that
// they can be injected into the child WorkItems
InventoryBO.InventoryItemCollection items = DAL.GetItems();

AddInventoryItemWorkItem addInventoryWorkitem = rootWorkItem.Items.AddNew<AddInventoryItemWorkItem>("AddInventoryItemWorkItem");

InventoryWorkItem workitem = rootWorkItem.WorkItems.AddNew<InventoryWorkItem>("InventoryWorkItem");
// workitem has inherited the state!

workitem.Run();
}



I hope this helps
Mariano Szklanny
http://staff.southworks.net/blogs/mariano/default.aspx
Feb 17, 2006 at 12:52 PM
originally posted by: WalkingDisaster

You wouldn't have to modify the CAB code, just override AddBuilderStrategies in your Application (for example FormShellApplication):

protected override void AddBuilderStrategies(Microsoft.Practices.ObjectBuilder.Builder builder)
{
base.AddBuilderStrategies(builder);
builder.Strategies.AddNew<InheritStateBuilderStrategy>
(Microsoft.Practices.ObjectBuilder.BuilderStage.Initialization);
}
Feb 18, 2006 at 3:42 AM
originally posted by: marianoszklanny

sklett, I recommend you to have a look at the Hands On Lab N-8: Object Builder.
There you can find more information about ObjectBuilder and how to add custom strategy/attribute.

Cheers,
Mariano Szklanny
http://staff.southworks.net/blogs/mariano
Feb 18, 2006 at 9:28 AM
originally posted by: sklett

I agree, I will look into this today and try and get a good grasp of it.
Feb 22, 2006 at 4:57 AM
originally posted by: marianoszklanny

I've implemented the InheritState attribute and the ObjectBuilder strategy, you can download the code from:
http://staff.southworks.net/blogs/mariano/archive/2006/02/20/CABInheritState.aspx

Also I wrote a brief explanation about State injection (parent-child workitem)
http://staff.southworks.net/blogs/mariano/archive/2006/02/17/210.aspx


Cheers,
Mariano Szklanny
http://staff.soutworks.net/blogs/mariano