Implementing a PWNodeProcessMethod C#
// 1. Use Global.ACKinds.TPWNodeMethod to publish the Workflow-Class to the iPlus-Type-System.
[ACClassInfo("mycompany.erp", "en{'Example WFNode'}de{'Example WFNode'}", Global.ACKinds.TPWNodeMethod, Global.ACStorableTypes.Required, false, PWProcessFunction.PWClassName, true)]
public class PWOrder : PWNodeProcessMethod, IACMyConfigCache
{
    public const string PWClassName = "PWOrder";

    #region c´tors
    static PWOrder()
    {
        // 2. Register a ACMethod-Template for this Workflow-Class
        ACMethod method = new ACMethod(ACStateConst.SMStarting);
        Dictionary<string, string> paramTranslation = new Dictionary<string, string>();
        method.ParameterValueList.Add(new ACValue("SkipIfNoLines", typeof(bool), false, Global.ParamOption.Required));
        paramTranslation.Add("SkipIfNoLines", "en{'Skip if no lines'}de{'Überspringe wenn keine Positionen'}");
        var wrapper = new ACMethodWrapper(method, "en{'Configuration'}de{'Konfiguration'}", typeof(PWOrder), paramTranslation, null);
        ACMethod.RegisterVirtualMethod(typeof(PWOrder), ACStateConst.SMStarting, wrapper);

        // 3. Register static Method-Invocation-Handler for Client/Proxy-Side
        RegisterExecuteHandler(typeof(PWOrder), HandleExecuteACMethod_PWOrder);
    }

    public PWOrder(ACClass acType, IACObject content, IACObject parentACObject, ACValueList parameter, string acIdentifier = "")
        : base(acType, content, parentACObject, parameter, acIdentifier)
    {
    }

    public override bool ACDeInit(bool deleteACClassTask = false)
    {
        // 15. Reset local members to make this instance reusable before it will be added to the component pool
        using (ACMonitor.Lock(_20015_LockValue))
        {
            _CountLines = 0;
        }
        ClearMyConfiguration();
        return base.ACDeInit(deleteACClassTask);
    }

    public override void Recycle(IACObject content, IACObject parentACObject, ACValueList parameter, string acIdentifier = "")
    {
        // 15. Reset local members to make this instance reusable when it was taken from the component pool
        using (ACMonitor.Lock(_20015_LockValue))
        {
            _CountLines = 0;
        }
        base.Recycle(content, parentACObject, parameter, acIdentifier);
    }

    #endregion


    #region Execute-Helper-Handlers
    public static bool HandleExecuteACMethod_PWOrder(out object result, IACComponent acComponent, string acMethodName, gip.core.datamodel.ACClassMethod acClassMethod, params object[] acParameter)
    {
        return HandleExecuteACMethod_PWNodeProcessMethod(out result, acComponent, acMethodName, acClassMethod, acParameter);
    }
    #endregion


    #region Properties
    // 16. The data context with which the class works is stored in the Root-Workflow-Node of class PWProcFuncOrder
    public InOrder CurrentInOrder
    {
        get
        {
            if (ParentRootWFNode == null)
                return null;
            PWProcFuncOrder pWProcFunc = ParentRootWFNode as PWProcFuncOrder;
            if (pWProcFunc == null)
                return null;
            return pWProcFunc.CurrentInOrder;
        }
    }

    private ACMethod _MyConfiguration;
    // 10. Implementation of IACMyConfigCache for reading the parameters for this workflow-node
    public ACMethod MyConfiguration
    {
        get
        {
            using (ACMonitor.Lock(_20015_LockValue))
            {
                if (_MyConfiguration != null)
                    return _MyConfiguration;
            }
            // Call NewACMethodWithConfiguration to get a new instance of ACMethod() with parameters that are filled from Config-Store-Hierarchy
            var myNewConfig = NewACMethodWithConfiguration();
            using (ACMonitor.Lock(_20015_LockValue))
            {
                if (_MyConfiguration == null)
                    _MyConfiguration = myNewConfig;
                return _MyConfiguration;
            }
        }
    }

    // 11. Implementation of IACMyConfigCache for resetting the local cache (_MyConfiguration)
    public void ClearMyConfiguration()
    {
        using (ACMonitor.Lock(_20015_LockValue))
        {
            _MyConfiguration = null;
        }
        HasRules.ValueT = 0;
    }

    // 12. Provide a Property for a easy access to the configured value in ParameterValueList
    protected bool SkipIfNoLines
    {
        get
        {
            var method = MyConfiguration;
            if (method != null)
            {
                var acValue = method.ParameterValueList.GetACValue("SkipIfNoLines");
                if (acValue != null)
                {
                    return acValue.ParamAsBoolean;
                }
            }
            return false;
        }
    }

    private int _CountLines = 0;
    // 13. Access private fields via ACMonitor.Lock()
    public int CountLines
    {
        get
        {
            using (ACMonitor.Lock(_20015_LockValue)) { return _CountLines; }
        }
    }
    #endregion


    #region Methods
    // 4. Override SMStarting and use the ACMethodState-Attribute!
    [ACMethodState("en{'Executing'}de{'Ausführend'}", 20, true)]
    public override void SMStarting()
    {
        string csv = GetOrderDataCSV();
        if (   String.IsNullOrEmpty(csv)
            || (CountLines <= 0 && SkipIfNoLines))
        {
            if (CurrentACState == ACStateEnum.SMStarting)
                CurrentACState = ACStateEnum.SMCompleted;
            return;
        }

        if (ParentPWGroup != null && this.ContentACClassWF != null)
        {
            // 5. RefPAACClassMethod is a reference to the virtual "WriteOrder"-Method
            ACClassMethod refPAACClassMethod = null;
            using (ACMonitor.Lock(this.ContextLockForACClassWF))
            {
                refPAACClassMethod = this.ContentACClassWF.RefPAACClassMethod;
            }

            if (refPAACClassMethod != null)
            {
                PAProcessModule module = ParentPWGroup.AccessedProcessModule;
                if (module == null)
                {
                    Msg msg = new Msg("AccessedProcessModule is null", this, eMsgLevel.Error, PWClassName, "SMStarting", 1010);
                    if (IsAlarmActive(ProcessAlarm, msg.Message) == null)
                        Messages.LogError(this.GetACUrl(), "SMStarting(10)", msg.Message);
                    OnNewAlarmOccurred(ProcessAlarm, msg, true);
                    SubscribeToProjectWorkCycle();
                    return;
                }

                // 6. With TypeACSignature() you get ACMethod for "WriteOrder"
                ACMethod paramMethod = refPAACClassMethod.TypeACSignature();
                if (!(bool)ExecuteMethod("GetConfigForACMethod", paramMethod, true))
                    return;
                // 7. Set all necessary parameters
                paramMethod.ParameterValueList["Content"] = csv;

                RecalcTimeInfo();
                if (CreateNewProgramLog(paramMethod) <= CreateNewProgramLogResult.ErrorNoProgramFound)
                    return;
                _ExecutingACMethod = paramMethod;

                // 8. Start asynchronous task on PAFOrder.
                if (!module.TaskInvocationPoint.AddTask(paramMethod, this))
                {
                    SubscribeToProjectWorkCycle();
                    return;
                }
                else
                    UnSubscribeToProjectWorkCycle();
                UpdateCurrentACMethod();
            }
        }

        // 9. Switch to State SMRunning
        // (If module.AddTask was exceuted syncronously then state is maybe already Runnning.)
        if (IsACStateMethodConsistent(ACStateEnum.SMStarting) < ACStateCompare.WrongACStateMethod)
            CurrentACState = ACStateEnum.SMRunning;
    }

    // Optional override: Handle the Function-Result
    public override void TaskCallback(IACPointNetBase sender, ACEventArgs e, IACObject wrapObject)
    {
        if (e != null)
        {
            IACTask taskEntry = wrapObject as IACTask;
            ACMethodEventArgs eM = e as ACMethodEventArgs;
            if (taskEntry.State == PointProcessingState.Deleted)
            { /* Task completed: Place here your code */
            }
            else if (   eM.ResultState == Global.ACMethodResultState.InProcess 
                        && taskEntry.State == PointProcessingState.Accepted)
            { /* Task running: Place here your code */
            }
            // Starting of a Method failed
            else if (taskEntry.State == PointProcessingState.Rejected)
            { /* Task rejected: Place here your code */
            }
        }
        base.TaskCallback(sender, e, wrapObject);
    }

    private string GetOrderDataCSV()
    {
        int countLines = 0;
        if (CurrentInOrder == null)
            return null;
        StringBuilder sb = new StringBuilder();
        // 17. Always access the data context with a new EF-Database-Instance because the Entities in the root workflow-node are in DETACHED-State! 
        using (MyCompanyDB dbApp = new MyCompanyDB())
        {
            InOrder inOrder = dbApp.InOrder.Include(c => c.InOrderPos_InOrder)
                                    .Include("InOrderPos_InOrder.Material")
                                    .Where(c => c.InOrderID == CurrentInOrder.InOrderID)
                                    .FirstOrDefault();
            if (inOrder != null)
            {
                sb.AppendLine(String.Format("{0};{1};", inOrder.InOrderNo, inOrder.InOrderDate));
                foreach (var line in inOrder.InOrderPos_InOrder)
                {
                    countLines++;
                    sb.AppendLine(String.Format("{0};{1};", line.Material.MaterialNo, line.TargetQuantity));
                }
            }
        }
        using (ACMonitor.Lock(_20015_LockValue)) { _CountLines = countLines; }

        return sb.ToString();
    }

    protected override void DumpPropertyList(XmlDocument doc, XmlElement xmlACPropertyList)
    {
        base.DumpPropertyList(doc, xmlACPropertyList);
        // 14. Dump private fields for diagnostic
        XmlElement xmlChild = xmlACPropertyList["CountLines"];
        if (xmlChild == null)
        {
            xmlChild = doc.CreateElement("CountLines");
            if (xmlChild != null)
                xmlChild.InnerText = CountLines.ToString();
            xmlACPropertyList.AppendChild(xmlChild);
        }
    }
    #endregion
}