Wednesday, April 30, 2008
EntLib3 Replace Handler, ExceptionHandlingException
There is one exception best practice to remember to implement for the @replaceExceptionType configuration to work: your derived exception must implement the three recommended constructors default, message, message and inner exception. If you forget, the entlib HandleException call will throw an "unable to handle exception" ExceptionHandlingException error.
Note the [[fault contract type]] syntax to be able to specify handlers for generic exceptions:
<add type="System.ServiceModel.FaultException`1[[MyServiceAgent.Proxy.GetCustomerListFault, MyServiceAgent.Proxy, Version=1.0.0.0]], System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="ThrowNewException" name="MyApplicationException">
You have to edit the .config file manually as the EntLib3 editor does not support generics
Wednesday, April 23, 2008
Generic INotifyPropertyChanged Setter
protected void SetValueWithChangeNotification<T>(ref T oldValue, T newValue)
{
StackFrame stackFrame = new StackFrame(1);
string stackName = stackFrame.GetMethod().Name;
string propertyName = stackName.Substring(4); //starts with "set_"
//PropertyInfo property = this.GetType().GetProperty(propertyName);
//T oldValue = (T)property.GetValue(this, null);
if (oldValue.Equals(newValue)==false)
{
oldValue = newValue;
RaisePropertyChanged(propertyName);
}
}
Call the method from your property setters like this:
public decimal NominatedValue
{
get { return _nominatedValue; }
set { SetValueWithChangeNotification(ref _nominatedValue, value);}
}
Note that while it is possible to use property.GetValue(this, null) to get the old value, you cannot use property.SetValue(this, newValue, null) to set it as it would cause a infinite recursive call chain.
The private property member is set before raising the property changed event, just to be sure. WinForms UI code runs on a single thread, so no event subscribers should be able to handle the event until the code returns, but this way even multi-threaded code should work fine.
You can always implement the method in a base class if you like, or in a static utility method by passing the object instance as an extra parameter.
Thursday, April 17, 2008
WCF: Design of Client Domain Objects
There are two models for this: annotating the BEs with XML serialization attributes to decorate the WSDL data contract, or creating separate Mapper classes [Fowler] in addition to the BE classes. The mapper will then map to and from the service proxy DTO objects [Fowler] at run-time. Note that the mapper model will be required whenever the BE is significantly different from the data contracts. Thus, BEs should be implemented using the first model to the maximum extent possible, only using the second model for advanced mapping requirements.
The decorator option is easier, performs better, and gives less code to maintain - but requires knowledge of XML serialization and will not give compilation errors when the data contract changes (even if the proxy is updated). Good integration test coverage is needed to make this option viable, i.e. to detect that the DTO's XML payload has changed.
Easier, less code: Only one BE class to implement and maintain as the BE is a Decorator object [GoF] wrapping the "DTO" XML. Note that there is no auto-generation of these classes and that there are no mappers or DTO objects. The BE classes are shared directly by the service proxies.
Performs better: There will only be one de-serialization step directly into the BE because it wraps the XML directly. This also applies to any nested child objects and collections - there will be no looping code to create and map the BE structure. The same applies to sending BE objects to the services - they will be directly serialized into "DTO" XML.
Contract change risks: As the BE classes are not auto-generated from the data contracts, changes to the contracts will not be automatically reflected in the serialization attributes on the BE properties. As (de)serialization happens at run-time, any mismatch between the XML and the serialization attributes cannot be detected by the compiler. Thus, adequate integration tests are required to detect that BE properties are not being set/read during (de)serialization. Breaking data contract changes will require that the serialization attributes are updated accordingly.
The mapper option is also quite easy, but performs worse and requires more code to be implemented and maintained (two classes: BE and mapper). However, it requires no knowledge of XML serialization and it will give compilation errors when the proxy + DTO objects are updated after data contract changes. Note that good unit testing is required to ensure that the mappings to and from the DTO objects are correct, including the complete object graph (nested child objects and collections).
Quite easy, more code: Two classes to implement and maintain, one BE class and one related mapper class. The mapper class must cover nested child objects and collections, both to and from the DTO objects. The DTO objects are auto-generated and will never be manually maintained by developers.
Performs worse: In addition to the de-serialization step of the DTO objects, the service agent must execute the mapping code to fill the BE object with data from the DTO object. Add to this extra transformation looping through nested child objects and collections, and the performance gets proportionally worse with the complexity of the object graph.
Contract change risks: The DTO objects are auto-generated based on the WSDL and are thus always in synch with the data contracts. Any breaking changes to the DTO classes will cause the mapper classes to give compilation errors. Breaking data contract changes will require that the mapper classes are updated accordingly - both to and from the DTO classes.
To complement this BE design approach, read the programming models for the Decorator option and the Mapper option to learn about the technical details and issues of each option.
Wednesday, April 16, 2008
WCF: Client Domain Objects, Decorator Pattern
One option for implementing the BE objects, described in this article, is to apply the Decorator pattern to the WSDL scema-based DTO objects to enhance them into full-fledged domain objects. Read ‘Design of Client Domain Objects’ for an intro this option and the related Mapper option.
If you do not like this Decorator option due to the weak [XML attribute] link to the data contracts (see end of article), you can always use a derived decorator class instead of a partial class. Make all your BE enhancements in the derived class using a combination of DTO overrides and new domain logic. If you prefer explicit, debuggable code, you're better off using the Mapper option.
Service Proxy
Use the SVCUTIL.EXE /REFERENCE switch when generating the proxy code and messages to reuse your BE objects as DTOs and to avoid getting new, duplicate DTO objects generated (might not compile due to name clashes). Alternatively, just remove the DTO code from the generated proxy. Make sure that the proxy code reference the .NET namespace and interface assembly that contains your BE objects.
Business Entities
You can use the data contract DTO objects generated by SVCTIL.EXE /DCONLY to get started. This will give you data-binding enabled (/EDB) anemic entity objects with all the properties in place, annotated with XML serialization attributes. Just ensure that the output files from SVCUTIL.EXE do not overwrite your code. The DTO objects become your generated BE objects - ready for decoration.
The BE objects must be manually maintained whenever you make changes to the generated BE code, e.g. to add EntLib3 validation attributes on the properties. Keep your additions to the generated BE in a partial class as far as possible, e.g. add new properties and methods in a partial class extending the decorated class. If all your decorator code can be isolated into a partial class, then the generated BE code can be re-generated without losing your extended BE code.
Our programming model for decorator-based client domain objects is based on using two partial classes; one T.schema.cs class file for all code based originally on the data contracts, and one T.cs class file for all added aspects. T is the type name of the BE.
Note that BE constructors will not be called during deserialization. Create a method to be used as a surrogate constructor to be run after deserialization and mark it with [OnDeserialized]. This is a good place for resetting e.g. the dirty flag:
[OnDeserialized]
void DeserializationCtor(StreamingContext context)
{
_isDirty = false;
}
Your BE class must still have a constructor to initialize the object for normal use. The constructor must initialize all properties that must have a defined initial value. In addition, the c'tor must initialize all child objects that are exposed as (de-normalized) public properties, to avoid null reference exceptions when using the BE (make it simple to use the BE). This also applies to child collections; create the collection to make it ready for use.
public NominationType()
{
//NOTE: WILL NOT BE CALLED ON DE-SERIALIZATION
//initialize all normalized valueobjects with members
this.NominatedValueField = new UnitValueType();
this.NominatedValueField.Unit = String.Empty;
}
Also initialize all fields that are required by the XSD schema, failure to initialize these fields will give serialization errors:
[DataMemberAttribute(IsRequired=true, . . .) ]
Don't break the DRY principle by adding the value object initialization code multiple places.
Dirty Tracking
If your BE implements INotifyPropertyChanged to enable data-binding, then the event is a good place to add dirty-tracking: Simple Dirty Tracking in Domain Objects
Converting XML Data Types to/from Strongly Typed Properties
Note that xs:date becomes string in .NET as there is no Date only type, just DateTime. Do not let this kind of weak type design leak into your BE objects. Contain the weak typing inside your BE object by converting to/from strongly typed properties.
private string _demandDateData; //XML xs:date datatype
public DateTime DemandDate
{
get
{
return DateTime.ParseExact(this._demandDateData, WSDateFormat, null);
}
set
{
string xsDate = value.ToString(WSDateFormat);
if (this._demandDateData.Equals(xsDate) != true)
{
this._demandDateData = xsDate;
this.RaisePropertyChanged("DemandDate");
}
}
}
This keeps your BE object within the "Simple vs Easy" principle.
De-normalization of BE Value Objects
The data contracts provided by the web-services will most likely contain some DDD value objects for numeric types annotated with some related attributes (e.g. money with currency) and other similar cases. These value objects will become child objects of the BE object, which is not very data-binding friendly as most controls (grids, etc) needs the data to be non-nested properties of the bound object. In addition, one control can only have one binding source. Thus, unlike master-details type binding that can be resolved using multiple binding sources, nested value objects must be de-normalized and projected as normal properties directly on the BE objects.
Start by making the value object private and give the property a new name (e.g. suffix with Data) while specifying the original name in the serialization attribute to keep the link to the XML. Keep the RaisePropertyChanged parameter equal to the property name.
[DataMemberAttribute(Name = "NominatedValue", IsRequired = true, EmitDefaultValue = false, Order = 2)]
private UnitValueType NominatedValueData
{
get { return this.NominatedValueField; }
set { . . . }
}
Then create the de-normalized public properties (in a partial class) as required by the BE and data-binding needs. The new public properties should be wrappers exposing an element of the original private member value object, using a friendly name and the correct data type. Make sure that the new-old value comparison is correct. Keep the RaisePropertyChanged parameter equal to the new property name.
public Decimal NominatedValue
{
get { return this.NominatedValueField.Value; }
set
{
if (this.NominatedValueField.Value.Equals(value) != true)
{
this.NominatedValueField.Value = value;
this.RaisePropertyChanged("NominatedValue");
}
}
}
public string NominatedValueUnit
{
get { return this.NominatedValueField.Unit; }
set { . . . this.RaisePropertyChanged("NominatedValueUnit"); }
}
As can be seen from the example, the new public properties are named by concatenating the value object name and the exposed property name; except for when the property name is just repeating the last part of the value object (i.e. NominatedValue.Value).
Remember to initialize all the value object children in the BE c'tor; both create the child objects and set their initial values.
Keeping the BE Synchronized with the Data-Contract Schema
It is imperative that the XML serialization attributes in code are correct according to the data contract XSD when using the decorator approach on proxy DTO objects to implement BE objects. There are three typical changes that can break the mapping between the BE code and the payload XML:
- Changes to the code by renaming properties without updating the XML serialization attributes accordingly
- Changes to the data contract XSD schema without adding properties with correct XML serialization attributes
- Changes to the data contract XSD schema without updating the XML serialization attributes accordingly
[DataMemberAttribute(Name = "NominatedValue", . . .) ]
private UnitValueType NominatedValueData
The next two issues are caused by adding new items to the data contract or changing some existing items. The latter is not recommended and is definitively not a recommended practice for published contracts:
- It is a proven practice that published contracts are not changed, except for non-breaking changes in minor versions.
- For new, non-breaking versions of the contract, items must only be added at the end of the existing contract.
[DataMemberAttribute(. . . , Order=<order number in XML xs:sequence element for item>) ]
For new major versions of data contracts, you must consider creating a new version of the BE class in a new .NET namespace. This allows you to do controlled versioning of your domain objects in synchronization with the versioning policy of the service contracts.
However, during the development of the web-services, the contracts are bound to get breaking changes as the data contracts evolve (which is OK as they are still not published). For this reason, wait as long as you can to make changes to the generated BE code, stick to working on the partial class used to decorate the BE. This will allow you to re-generate the schema-bound parts of your BE to keep up with the changes in the data contracts.
A side note: deriving your BE objects from the DTO objects is an alternative to partial classes that strictly enforces the separation of decorator code from generated code, and allows the generated code to be auto-generated over and over again - like for the Mapper option. Alas, there are drawbacks to inheriting the DTOs also, as none of the generated proxy artifacts are geared towards this. Look into using an AOP framework to do dynamic subclassing if chosing to derive your BE objects.
For data contracts under development, any combination of adding, changing, removing and reordering of data contract items can be expected to occur. The XML serialization attributes must then be updated accordingly.
Tuesday, April 15, 2008
WCF+SCSF: Client Domain Objects, Mapper Pattern
One option for implementing the BE objects, described in this article, is to apply the Mapper pattern to the WSDL scema-based DTO objects to transform them into domain objects. Read ‘Design of Client Domain Objects’ for an intro this option and the related Decorator option.
Service Proxy
The mapper option has no special requirements for the service proxy or its message and data contracts, as there are no shared objects between the proxy and the business entities, i.e. the proxy is totally self-contained. The mapper objects do all transformation to/from the DTO objects and the BE objects.
CAB EntityTranslatorService, CAB EntityMapperTranslator
The EntityTranslatorService class is a service that provides a registry of translators and translation services between two classes. The user must implement the translators and register them with the service.
For more information see: ms-help://MS.VSCC.v80/MS.VSIPCC.v80/ms.practices.scsf.2007may/SCSF/html/03-01-090-How_to_Translate_Between_Business_Entities_and_Service_Entities.htm
The EntityMapperTranslator class is a base helper implementation of an IEntityTranslator that provides placeholders to translate from business entities to service and viceversa.
For more information see: ms-help://MS.VSCC.v80/MS.VSIPCC.v80/ms.practices.scsf.2007may/SCSF/html/03-01-090-How_to_Translate_Between_Business_Entities_and_Service_Entities.htm
Mappers and the Translator Service
A mapper must be derived from the CAB class EntityMapperTranslator<BE, DTO> and implement two methods:
DTO = BusinessToService(BE)
BE = ServiceToBusiness(DTO)
A mapper must traverse the complete object graph to translate all child collections and objects when the mapped object is a DDD "aggregate root".
Always add a mapper for collections of the mapped DTO and BE objects. A list mapper must iterate the collection and use the applicable object translator to map each item in the list.
Each module must register its own mappers with the CAB translator service from the CAB module AddServices:
public override void AddServices()
{
AddEntityTranslators();
_workItem.RootWorkItem.Services.AddNew<MyService, IMyService>();
}
private void AddEntityTranslators()
{
IEntityTranslatorService translator = _workItem.RootWorkItem.Services.Get<IEntityTranslatorService>();
translator.RegisterEntityTranslator(new MyTranslator());
translator.RegisterEntityTranslator(new MyListTranslator());
. . .
}
The mappers are used by the service agents to map DTO objects to BE objects and vice verca. Each CAB service that depends on a service agent must have a [ServiceDependency] on IEntityTranslatorService:
[InjectionConstructor]
public MyService([ServiceDependency]IEntityTranslatorService translator)
{
_translator = translator;
_myServiceAgent = new MyServiceAgent(_translator);
}
MyService is a CAB service and will thus get the EntityTranslatorService injected by the DI-container. It then creates the required service agents using the translator service reference. MyServiceAgent is not a CAB service, thus it is not subject to any DI-container dependency resolving magic.
Service Agents Perform the Mapping
The service agents use the EntityTranslatorService to map the DTO objects returned by the service proxy into BE objects:
AllocationTypeList allocationTypeList;
NominationTypeList nominationTypeList = _proxy.GetNominationAndAllocation (periodType, out allocationTypeList);
nominationList = _translator.Translate <List<Nomination>>(nominationTypeList);
allocationList = _translator.Translate <List<Allocation>>(allocationTypeList);
The reverse mapping must be performed on all BE objects passed as DTO objects into the service proxies.
NEVER LET A DTO OBJECT BE USED OUTSIDE SERVICE AGENTS.
Mapping from DTO to BE
Your BE class must have a constructor to initialize the object for normal use. The constructor must initialize all properties that must have a defined initial value. In addition, the c'tor must initialize all child objects that are exposed as public properties, to avoid null reference exceptions when using the BE (make it simple to use the BE). This also applies to child collections; create the collection to make it ready for use. Don't break the DRY principle by adding the initialization code multiple places.
public Nomination()
{
//initialize all contained valueobjects with members
this.NominatedValueField = new UnitValueType();
this.NominatedValueField.Unit = String.Empty;
. . .
}
The mapper code will create a new instance of the BE class and initialize it based on values from the DTO object. This can involve the whole specter from simple value copying to complex conversion logic.
Mapping from BE to DTO
Your mapper class must initialize the created DTO object before using it. The mapper must initialize all properties that must have a defined initial value. In addition, the mapper must initialize all child objects that are exposed as (de-normalized) public properties, to avoid null reference exceptions when mapping the BE. This also applies to child collections; create the collection to make it ready for use.
//initialize all normalized valueobjects with members
dto.NominatedValueField = new UnitValueType();
dto.NominatedValueField.Unit = String.Empty;
Also initialize all DTO fields that are required by the XSD schema, failure to initialize these fields will give serialization errors:
[DataMemberAttribute(IsRequired=true, . . .)]
The mapper code will create a new instance of the DTO class and initialize it based on values from the BE object. This can involve the whole specter from simple value copying to complex conversion logic.
Converting XML Data Types to/from Strongly Typed Properties
Note that xs:date becomes string in .NET as there is no Date only type, just DateTime. DO NOT LET THIS KIND OF WEAK TYPE DESIGN LEAK INTO YOUR BE OBJECTS. Contain the weak typing in your DTO object by converting to/from strongly typed properties in the mapper.
dto.DemandDate = value.DemandDate.ToString(WSDateFormat);
entity.DemandDate = DateTime.ParseExact(value.DemandDate, WSDateFormat, null);
This keeps your BE object within the "Simple vs Easy" principle.
Dirty Tracking
If your BE implements INotifyPropertyChanged to enable data-binding, then the event is a good place to add dirty-tracking: Simple Dirty Tracking in Domain Objects.
Monday, April 14, 2008
Simple Dirty Tracking in Domain Objects
If you cannot change the BE code, then consider implementing non-intrusive logic to compare a unit-of-work BE with its original values; either by reading the original BE from the repository and perform a recursive Equals(originalEntity) check of the BE's object graph, or by using an extended identity map that has original copies of the unit-of-work BE objects and performing the same Equals(originalEntity) check. The latter approach is better for client-side dirty-tracking; while the former is better suited for server-side implementations (get - compare - save).
Never implement dirty-tracking in client applications by using click events and the like, to set the dirty flag from view or presenter code. Implementing dirty-tracking in client code breaks both the SRP and DRY principles, first by making dirty-tracking a responsibility of the view/presenter instead of the BE object, and then by repeating dirty-tracking code across all places where the BE object is used.
If your BE objects implements INotifyPropertyChanged to enable data-binding, then the event is a good place to add dirty-tracking. Note that this dirty-tracking mechanism depends on all changes to the BE state being done through the property setters. Luckily, the _isDirty flag can also be set directly from anywhere within the BE code if necessary.
protected void RaisePropertyChanged(string propertyName)
{
//NOTE: must be here to run even if no handlers are registered
this._isDirty = true;
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
If your BE is a DDD "aggregate root" and thus contains a complex object graph, then your BE should implement an IsDirty method, in addition to the private _isDirty member, that iterates through all child objects and checks their IsDirty status. Each object within the aggregate root must implement its own dirty tracking mechanism; either as just an IsDirty property for simple objects, or both an _isDirty flag and an IsDirty method for iterating its children also for complex objects.
public bool IsDirty()
{
if (this._isDirty) return true;
bool isDirty = this._isDirty;
foreach (Allocation allocation in _allocationList)
{
if (allocation.IsDirty) isDirty = true;
break;
}
return isDirty;
}
You could add event handlers to listen for the INotifyPropertyChanged event from contained child objects, but this is not recommended as you would need to add and remove a dynamic number of event handlers based on how many objects there are inside the aggregate root throughout its complex object graph - also when objects are added or removed from child collections.
Friday, April 11, 2008
DELL Support, XPS Motherboard
I called DELL's XPS premier support hotline, and an engineer and the required parts was dispatched to come to my home last friday. Due to this I had to be at home, waiting for the guy from DELL's Norwegian hardware service provider InfoCare. The day passed by, no engineer, no call, no information whatsoever. I contacted DELL premier XPS support again, and got a call this monday: "unfortunately, there had been a problem with dispatching the parts - sorry for not informing you". Yeah, right.
As I am on business travel three days a week currently, the engineer was dispatched to come this friday. This morning the InfoCare engineer called to say they were coming just after lunch. Guess what, at 15:30 he called again to tell me that "something came up, hehe". I'm really feeling that DELL premier support is doing their best to solve my problem to my satisfaction. Afterall, InfoCare's slogan is "The Art of Service" (sic!).
So, next week I have to spend another day home from work, waiting for the engineer to come and repair my XPS. Two days home from work, so far :-(
I will buy my next PC in a shop where I can talk with real people; and deliver and pick up the PC for repair myself - even if it takes weeks to fix it.
Thursday, April 10, 2008
Simple vs Easy Revisited
I am again gently enforcing these two simple rules:
The objects must be simple to use, not (just) easy to make. E.g. do not expose a date as string even if XML xs:date types becomes strings in .NET and strings are easy to code for you - rather make it simple to use the object by converting the string to and from a real .NET DateTime value.
There is no such thing as "throw-away code" if it enters the source control system. You will not have time, due to time preasure, to come back at a later time to do things right in throw-away code that you write due to time preasure. Therefore all code that you check-in must be production quality code.
In addition, I try to enforce this principle: There should be no green code (comments) in the code. All comments should be considered a missed opportunity to use 'Refactor - Extract Method' and use of the comment as the basis for the new method name.
Tuesday, April 08, 2008
CAB/SCSF: Unit Testing Presenters, Views, Events
Use the TestableRootWorkItem like this:
private MockRepository _mocks;
private TestableRootWorkItem _workitem;
private MyViewPresenter _presenter;
private IMyView _view;
private IMyService _service;
[TestFixtureSetUp]
public void Initialize()
{
_mocks = new MockRepository();
_service = _mocks.CreateMock<IMyService>();
_view = _mocks.CreateMock<IMyView>();
_workitem = new TestableRootWorkItem();
_workitem.Services.Add(_service);
//perform the CAB DI-container magic
_presenter = _workitem.Items.AddNew<MyViewPresenter>();
_presenter.View = _view;
}
Note: do not try to access the workitem or any injected dependencies such as [ServiceDependency] inside constructors, things might not have been resolved by the DI container yet. This especially applies to stuff from base classes and things that are not resolved using an [InjectionConstructor].
When testing that event publishing actually triggers the subscribers, you can add fake event publishers and subscribers to help with the mocking. In addition, your unit-test should verify that the raised event causes presenters and views to be loaded by checking the content of the Items and SmartPart collections afterwards.
Implement your CAB/SCSF event-based unit-test like this:
[TestFixture]public class ModuleTestFixture
{
. . .
[TestFixtureSetUp]
public void Initialize()
{
_workitem = new TestableRootWorkItem();
_eventPublisher = _workitem.Items.AddNew<MockEventPublisher>(_eventPublisherId);
_eventSubscriber = _workitem.Items.AddNew<MockEventSubscriber>(_eventSubscriberId);
_moduleInitializer = new Module(_workitem);
_moduleInitializer.Load();
}
[Test]
public void ShouldLaunchPriceConfigViewOnEvent()
{
int itemPresentersCount = FindItemsByTypeRecursive <PriceConfigurationViewPresenter>(_workitem);
Assert.AreEqual(0, itemPresentersCount);
int smartPartViewsCount = FindSmarPartsByTypeRecursive <PriceConfigurationView>(_workitem);
Assert.AreEqual(0, smartPartViewsCount);
_eventPublisher.OnNewOrder(new NewOrderEventArgs(123, Constants.OperationNames.StartPriceConfigurationView));
Assert.AreEqual(true, _eventPublisher.HasRaisedNewOrder, "EventPublication <NewOrder> failed");
Assert.AreEqual(true, _eventSubscriber.HasHandledNewOrder, "EventSubscription <NewOrder> failed");
itemPresentersCount = FindItemsByTypeRecursive <PriceConfigurationViewPresenter>(_workitem);
Assert.AreEqual(1, itemPresentersCount);
smartPartViewsCount = FindSmarPartsByTypeRecursive <PriceConfigurationView>(_workitem);
Assert.AreEqual(1, smartPartViewsCount);
}
private int FindItemsByTypeRecursive<T>(WorkItem workItem)
{
ICollection<T> items = workItem.Items.FindByType<T>();
int typeCount = items.Count;
foreach (System.Collections.Generic.KeyValuePair<string, WorkItem> item in workItem.WorkItems)
{
typeCount += FindItemsByTypeRecursive<T>(item.Value);
}
return typeCount;
}
private int FindSmarPartsByTypeRecursive<T>(WorkItem workItem)
{
ICollection<T> items = workItem.SmartParts.FindByType<T>();
int typeCount = items.Count;
foreach (System.Collections.Generic.KeyValuePair<string, WorkItem> item in workItem.WorkItems)
{
typeCount += FindSmarPartsByTypeRecursive<T>(item.Value);
}
return typeCount;
}
}
public class MockEventPublisher
{
[EventPublication(Constants.EventTopicNames.NewOrder, PublicationScope.Global)]
public event System.EventHandler<NewOrderEventArgs> NewOrder;
public virtual void OnNewOrder(NewOrderEventArgs eventArgs)
{
if (NewOrder != null)
{
_hasRaisedNewOrder = true;
NewOrder(this, eventArgs);
}
}
public bool HasRaisedNewOrder
{
get { return _hasRaisedNewOrder; }
set { _hasRaisedNewOrder = value; }
}
private bool _hasRaisedNewOrder = false;
}
public class MockEventSubscriber
{
[EventSubscription(Constants.EventTopicNames.NewOrder)]
public void OnNewOrder(object sender, NewOrderEventArgs eventArgs)
{
if (eventArgs.OperationName == Constants.OperationNames.StartPriceConfigurationView)
{
_hasHandledNewOrder = true;
}
}
public bool HasHandledNewOrder
{
get { return _hasHandledNewOrder; }
set { _hasHandledNewOrder = value; }
}
private bool _hasHandledNewOrder = false;
}
If you have issues with understanding that a WorkItem is just a dependency injection container or what all the different collections such as Items, Services, SmartParts, WorkSpaces, etc are for; I recommend reading Rich Newman's Introduction to CAB/SCSF. Read part 18 first.