DynamicActivity - How to invoke a workflow that stored in Database? - c#

This is proof of concept project - The goal is to create an application that receive some system wide events and based on some business rules invokes a specific workflow.
The workflows are created separately and the xaml source is stored in a database.
Following is the code that used to invoke the workflow:
public void RaiseEvent(IEvent e, IEventData eventData)
{
var typeName = e.GetType().FullName;
// Query Db for all workflows for the event
var repo = new WorkflowRepository();
var workflows = repo.GetActiveWorkflowsByEvent(typeName);
foreach (var wf in workflows)
{
var condition =
ConditionEvaluator.PrepareCondition(wf.Condition.Expression, eventData);
var okToStart = ConditionEvaluator.Evaluate(condition);
if (okToStart)
{
// Next line is throwing an exeption
object o = XamlServices.Parse(wf.WorkflowDefinition.Expression);
DynamicActivity da = o as DynamicActivity;
WorkflowInvoker.Invoke(da,
new Dictionary<string, object>
{{ "EventData", eventData }});
}
}
We have created very simple workflow that runs without problems on its own. But when xaml is being loaded using XamlService.Parse it throw following exception:
System.Xaml.XamlObjectWriterException was unhandled
Message='No matching constructor found on type 'System.Activities.Activity'.
You can use the Arguments or FactoryMethod directives to construct this type.'
Line number '1' and line position '30'.
Any idea what is wrong?
Thank you.

Not sure what is causing your problem, I have used XamlServices.Load() in the past without any problems, but the easiest way of loading a workflow XAML at runtime is by using the ActivityXamlServices.Load(). See here for an example.

Ok I have solved this by using ActivityXamlServices
So Instead of this line:
object o = XamlServices.Parse(wf.WorkflowDefinition.Expression);
I am using following snippet:
var mStream = new memoryStream(
ASCIIEncoding.Default.GetBytes(wf.WorkflowDefinition.Expression));
object o = ActivityXamlServices.Load(mStream);

Related

System.Diagnostics.EventLog doesn't contain correct message

In some cases, when retrieving event logs from System.Diagnostics.EventLog, message like this
The description for Event ID '10016' in Source 'DCOM' cannot be found...
is returned. I found out that this response is also returned by Get-EventLog command in Powershell.
The actual message should look like this:
The application-specific permission settings do not grant Local Activation permission...
and is returned by Get-WinEvent command.
Is there any way to retrieve the second message in .Net Framework project? (without calling an independent Powershell script)?
UPDATE
I implemented the suggested solution, but now I stumbled on a different problem - how can I retrieve Audit Success and Audit Failure information? The EventLogEntry had an enum that contained them, but EventRecord doesn't
Update 2
I found a way to deal with Audits. EventRecord has a Keywords property, I compared it to StandardEventKeywords enum
As mentioned in the comments, Get-WinEvent uses the EventLogReader class to enumerate the events queried, and then calls EventRecord.FormatDescription() on each resulting record to render the localized message.
Here's a sample console application to fetch and print the rendered message of each of the first 10 Warning (Level=3) events in the Application log:
using System;
using System.Diagnostics.Eventing.Reader;
class Program
{
static void Main(string[] args)
{
// construct an EventLogQuery object from a log path + xpath query
var xpath = "*[System[Level=3]]";
var query = new EventLogQuery("Application", PathType.LogName, xpath);
// instantiate an EventLogReader over the query
var reader = new EventLogReader(query);
// read the events one by one
var counter = 0;
EventRecord record = null;
while ((record = reader.ReadEvent()) is EventRecord && ++counter <= 10)
{
// call FormatDescription() to render the message in accordance with your computers locale settings
var renderedMessage = record.FormatDescription();
Console.WriteLine(renderedMessage);
}
}
}
Beware that it's entirely possible for FormatDescription() to return an empty string - this will occur when the event logging provider didn't provide a message template for the given event id.
Thank you Mathias R. Jessen. Posting your suggestion as an answer to help other community members.
You can try and mimic what Get-WinEvent does: enumerate events with an EventLogReader, then call FormatDescription() on the resulting events to render the message
You can refer to EventLogReader Class and EventRecord.FormatDescription Method
Thanks to Mathias R. Jensen, I created something similar using XML query
static void Main(string[] args)
{
string logName = "System";
string sourceName = "DCOM";
string query = $#"<QueryList>
<Query Id=""0"" Path=""{logName}"">
<Select Path=""{logName}"">
*[System[Provider[#EventSourceName=""{sourceName}""]]]
and
*[System[TimeCreated[#SystemTime>=""{DateTime.UtcNow.AddMinutes(-10).ToString("O")}""]]]
</Select>
</Query>
</QueryList>";
EventLogQuery elq = new EventLogQuery("System", PathType.LogName, query);
EventLogReader elr = new EventLogReader(elq);
EventRecord entry;
while ((entry = elr.ReadEvent()) != null)
{
Console.WriteLine(entry.FormatDescription());
}
Console.ReadLine();
}

In C#, what will happen for an removed object's Action?

I want to make a deep Copy for my Class TreeNode. Here is my code:
public TreeNode(TreeNode node, GUIStyle inPointStyle, GUIStyle outPointStyle, Action<ConnectionPoint> OnClickInPoint, Action<ConnectionPoint> OnClickOutPoint)
{
this.rect = new Rect(node.rect);
this.style = new GUIStyle(node.style);
this.inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint);
this.outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint);
this.defaultNodeStyle = new GUIStyle(node.defaultNodeStyle);
this.selectedNodeStyle = new GUIStyle(node.selectedNodeStyle);
this.allDecorations = new List<GameObject>(node.allDecorations);
this.objs = new Dictionary<GameObject, IndividualSettings>(node.objs);
this.name = String.Copy(node.name);
this.RemoveClonedObj = new Action(node.RemoveClonedObj);
this.OnChangeView = new Action<TreeNode>(node.OnChangeView);
this.OnRemoveNode = new Action<TreeNode>(node.OnRemoveNode);
this.OnCopyNode = new Action<TreeNode>(node.OnCopyNode);
this.PreviewTree = new Action<TreeNode, bool> (node.PreviewTree);
}
However, the Rider gave me the warning:
It seems the Rider was saying that my "new" is meaningless.
If I follow Rider's instruction, usethis.RemoveClonedObj = node.RemoveClonedObj; what will happen for my copyed TreeNode's Actions aftering removing the orginal TreeNode? Will they be removed as well? If so, why does Rider give me such warning?
In C# 2.0 or above, the following codes are equivalent (DelegateType is a delegate type, as its name suggests):
newDelegate = new DelegateType(oldDelegate);
newDelegate = oldDelegate;
(See MSDN - How to: Declare, Instantiate, and Use a Delegate (C# Programming Guide))
Also, Microsoft specifies (see here) that such operation will always create a new instance of DelegateType, which has the same invocation list as the oldDelegate. They do not refer to the same object (don't be confused by the = assignment):
The binding-time processing of a delegate_creation_expression of the form new D(E), where D is a delegate_type and E is an expression, consists of the following steps:
If E is a method group, the delegate creation expression is processed in the same way as a method group conversion (Method group conversions) from E to D.
If E is an anonymous function, the delegate creation expression is processed in the same way as an anonymous function conversion (Anonymous function conversions) from E to D.
If E is a value, E must be compatible (Delegate declarations) with D, and the result is a reference to a newly created delegate of type D that refers to the same invocation list as E. If E is not compatible with D, a compile-time error occurs.
So regarding your question
What will happen for my copyed TreeNode's Actions aftering removing the orginal TreeNode? Will they be removed as well?
Nothing will happen to them. They will not be removed.
By the way, since you are trying to make a deep copy of your tree-node, I suspect whether it is the correct way. Though you have created a new instance of your delegate, the class instance associated with it (the instance on which member methods will be invoked) stays the same.
Do not link instance methods to each other. This will lead to memory leaks.
Even after the original node is removed and no longer needed by your code, due to the reference from the copy the original instance will live in the memory and not be garbage collected.
I suspect this is not what you want, Test code for this
class Program
{
static void Main(string[] args)
{
First t = new First();
Second s = new Second();
t.Print = s.TestMethod;
s.test = "change";
s = null;
t.Print("Hell"); // can debug and see that the function call goes through and string test is = "change"
}
}
public class First
{
public string s;
public Action<string> Print;
}
public class Second
{
public string test = "created";
public void TestMethod (string test)
{
var res = "hello" + test + test;
}
}
Either your methods on the node should be part of the Node object, this way you do not have to assign them to new nodes, or they should be in a separate class, preferably static, so that creation of new nodes does not lead to a memory issue.

How to serialize tensor input required by dnnclassifier (serving_input_reciever)

I want to be able to use the dnnclassifier (estimator) on top of IIS using tensorflowsharp. The model has previously been trained in python. I got so far that I can now generate PB files, know the correct input/outputs, however I am stuck in tensorflowsharp using string inputs.
I can create a valid .pb file of the iris dataset. It uses the following feate_spec:
{'SepalLength': FixedLenFeature(shape=(1,), dtype=tf.float32, default_value=None), 'SepalWidth': FixedLenFeature(shape=(1,), dtype=tf.float32, default_value=None), 'PetalLength': FixedLenFeature(shape=(1,), dtype=tf.float32, default_value=None), 'PetalWidth': FixedLenFeature(shape=(1,), dtype=tf.float32, default_value=None)}
I have created a simple c# console to try and spin it up. The input should be an "input_example_tensor" and the output is located in "dnn/head/predictions/probabilities". This I discoved after alex_zu provided help using the saved_model_cli command here.
As far as I am aware all tensorflow estimator API's work like this.
Here comes the problem: the input_example_tensor should be of a string format which will be parsed internally by the ParseExample function. Now i am stuck. I have found TFTensor.CreateString, but this doesn't solve the problem.
using System;
using TensorFlow;
namespace repository
{
class Program
{
static void Main(string[] args)
{
using (TFGraph tfGraph = new TFGraph()){
using (var tmpSess = new TFSession(tfGraph)){
using (var tfSessionOptions = new TFSessionOptions()){
using (var metaGraphUnused = new TFBuffer()){
//generating a new session based on the pb folder location with the tag serve
TFSession tfSession = tmpSess.FromSavedModel(
tfSessionOptions,
null,
#"path/to/model/pb",
new[] { "serve" },
tfGraph,
metaGraphUnused
);
//generating a new runner, which will fetch the tensorflow results later
var runner = tfSession.GetRunner();
//this is in the actual tensorflow documentation, how to implement this???
string fromTensorflowPythonExample = "{'SepalLength': [5.1, 5.9, 6.9],'SepalWidth': [3.3, 3.0, 3.1],'PetalLength': [1.7, 4.2, 5.4],'PetalWidth': [0.5, 1.5, 2.1],}";
//this is the problem, it's not working...
TFTensor rawInput = new TFTensor(new float[4]{5.1f,3.3f,1.7f,0.5f});
byte[] serializedTensor = System.Text.Encoding.ASCII.GetBytes(rawInput.ToString());
TFTensor inputTensor = TensorFlow.TFTensor.CreateString (serializedTensor);
runner.AddInput(tfGraph["input_example_tensor"][0], inputTensor);
runner.Fetch("dnn/head/predictions/probabilities", 0);
//start the run and get the results of the iris example
var output = runner.Run();
TFTensor result = output[0];
//printing response to the client
Console.WriteLine(result.ToString());
Console.ReadLine();
}
}
}
}
}
}
}
This example will give the following error:
An unhandled exception of type 'TensorFlow.TFException' occurred in TensorFlowSharp.dll: 'Expected serialized to be a vector, got shape: []
[[Node: ParseExample/ParseExample = ParseExample[Ndense=4, Nsparse=0, Tdense=[DT_FLOAT, DT_FLOAT, DT_FLOAT, DT_FLOAT], dense_shapes=[[1], [1], [1], [1]], sparse_types=[], _device="/job:localhost/replica:0/task:0/device:CPU:0"](_arg_input_example_tensor_0_0, ParseExample/ParseExample/names, ParseExample/ParseExample/dense_keys_0, ParseExample/ParseExample/dense_keys_1, ParseExample/ParseExample/dense_keys_2, ParseExample/ParseExample/dense_keys_3, ParseExample/Const, ParseExample/Const, ParseExample/Const, ParseExample/Const)]]'
How can I serialize tensors in such a way that I can use the pb file correctly?
I also posted the issue on github, here you can find the iris example python file, pb file and the console applications. In my opinion solving this creates a
neat solution for all tensorflow users having ancient production environments (like me).
The Expected serialized to be a vector, got shape: [] error can be fixed by using an overload of the TFTensor.CreateString function: Instead of directly taking a string, the model apparently expects a vector containing a single string:
TFTensor inputTensor = TFTensor.CreateString(new byte[][] { bytes }, new TFShape(1));
The input_example_tensor in your case now expects a serialized Example protobuf message (see also the docs and the example.proto file).
Using the protobuf compiler, I've generated a C# file containing the Example class. You can download it from here: https://pastebin.com/iLT8MUdR. Specifically, I used this online tool with CSharpProtoc and replaced the import "tensorflow/core/example/feature.proto"; line by the messages defined in that file.
Once you've added the file to your project, you'll need a package reference to Google.Protobuf. Then, you can pass serialized examples to the model like this:
Func<float, Tensorflow.Feature> makeFeature = (float x) => {
var floatList = new Tensorflow.FloatList();
floatList.Value.Add(x);
return new Tensorflow.Feature { FloatList = floatList };
};
var example = new Tensorflow.Example { Features = new Tensorflow.Features() };
example.Features.Feature.Add("SepalLength", makeFeature(5.1f));
example.Features.Feature.Add("SepalWidth", makeFeature(3.3f));
example.Features.Feature.Add("PetalLength", makeFeature(1.7f));
example.Features.Feature.Add("PetalWidth", makeFeature(0.5f));
TFTensor inputTensor = TFTensor.CreateString(
new [] { example.ToByteArray() }, new TFShape(1));
runner.AddInput(tfGraph["input_example_tensor"][0], inputTensor);
runner.Fetch("dnn/head/predictions/probabilities", 0);
//start the run and get the results of the iris example
var output = runner.Run();
TFTensor result = output[0];

Replace method's Body with Body of another method using Mono.Cecil?

With Mono.Cecil it looks quite simple when we can just set the Body of the target MethodDefinition to the Body of the source MethodDefinition. For simple methods, that works OK. But for some methods whereas a custom type is used (such as to init a new object), it won't work (with an exception thrown at the time writing the assembly back).
Here is my code:
//in current app
public class Form1 {
public string Test(){
return "Modified Test";
}
}
//in another assembly
public class Target {
public string Test(){
return "Test";
}
}
//the copying code, this works for the above pair of methods
//the context here is of course in the current app
var targetAsm = AssemblyDefinition.ReadAssembly("target_path");
var mr1 = targetAsm.MainModule.Import(typeof(Form1).GetMethod("Test"));
var targetType = targetAsm.MainModule.Types.FirstOrDefault(e => e.Name == "Target");
var m2 = targetType.Methods.FirstOrDefault(e => e.Name == "Test");
var m1 = mr1.Resolve();
var m1IL = m1.Body.GetILProcessor();
foreach(var i in m1.Body.Instructions.ToList()){
var ci = i;
if(i.Operand is MethodReference){
var mref = i.Operand as MethodReference;
ci = m1IL.Create(i.OpCode, targetType.Module.Import(mref));
}
else if(i.Operand is TypeReference){
var tref = i.Operand as TypeReference;
ci = m1IL.Create(i.OpCode, targetType.Module.Import(tref));
}
if(ci != i){
m1IL.Replace(i, ci);
}
}
//here the source Body should have its Instructions set imported fine
//so we just need to set its Body to the target's Body
m2.Body = m1.Body;
//finally write to another output assembly
targetAsm.Write("modified_target_path");
The code above was not referenced from anywhere, I just tried it myself and found out it works for simple cases (such as for the 2 methods Test I posted above). But if the source method (defined in the current app) contains some Type reference (such as some constructor init ...), like this:
public class Form1 {
public string Test(){
var u = new Uri("SomeUri");
return u.AbsolutePath;
}
}
Then it will fail at the time writing the assembly back. The exception thrown is ArgumentException with the following message:
"Member 'System.Uri' is declared in another module and needs to be imported"
In fact I've encountered a similar message before but it's for method calls like (string.Concat). And that's why I've tried importing the MethodReference (you can see the if inside the foreach loop in the code I posted). And really that worked for that case.
But this case is different, I don't know how to import the used/referenced types (in this case it is System.Uri) correctly. As I know the result of Import should be used, for MethodReference you can see that the result is used to replace the Operand for each Instruction. But for Type reference in this case I totally have no idea on how.
All my code posted in my question is fine BUT not enough. Actually the exception message:
"Member 'System.Uri' is declared in another module and needs to be imported"
complains about the VariableDefinition's VariableType. I just import the instructions but not the Variables (which are just referenced exactly from the source MethodBody). So the solution is we need to import the variables in the same way as well (and maybe import the ExceptionHandlers as well because an ExceptionHandler has CatchType which should be imported).
Here is just the similar code to import VariableDefinition:
var vars = m1.Body.Variables.ToList();
m1.Body.Variables.Clear();
foreach(var v in vars){
var nv = new VariableDefinition(v.Name, targetType.Module.Import(v.VariableType));
m1.Body.Variables.Add(nv);
}

How can I access UD fields in Epicor10 business objects outside of Epicor?

In Epicor 9 it was fairly easy to open Visual Studio and create a project and use the Epicor libraries to access its Business Objects (BOs). So, for instance the Part could be accessed by including the library Epicor.Mfg.Part and newing up a Part object. Then it was easy to get information for a part by calling Part.GetByID("partnum");. This would return a PartDataSet.
It is different but not so difficult to do the same thing in Epicor 10. However, I have noticed that the PartDataSet does not contain any UD fields, even UD fields that have been properly set up in Epicor10.
How can the UD fields be accessed when tapping into Epicor 10 through its business objects?
EDIT:
using Erp.BO;
using Erp.Proxy.BO;
// ...
var binding = Epicor.ServiceModel.StandardBindings.NetTcp.UsernameWindowsChannel();
var cc = new ClientCredentials();
var cred = cc.UserName;
cred.UserName = "****";
cred.Password = "****";
DnsEndpointIdentity ep = new DnsEndpointIdentity("****");
var quoteBo = new QuoteImpl(binding, new Uri("net.tcp://****/Erp/BO/Quote.svc"), cc, ep);
var qds = new QuoteDataSet();
var hed = qds.QuoteHed.NewQuoteHedRow(); // type: QuoteDataSet.QuoteHedRow
// I am not getting UserDefinedColumns as a member of hed.
// This gives me a compiler error.
qds.QuoteHed[0].UserDefinedColumns["Custom_c"] = "value";
It is still fairly easy, the DS returned by the call to the BO will be defined in the contract DLL found on both the client and the server, as this file needs to be distributed to the client machines the UD fields are not added to it. It would cause too many client updates.
This means the Visual Studio cannot look at the contract assembly to determine the field names. Instead, you access the field using the columnName indexer i.e:
class Program
{
static void Main(string[] args)
{
// Hard-coded LogOn method
// Reference: Ice.Core.Session.dll
Ice.Core.Session session = new Ice.Core.Session("manager", "manager", "net.tcp://AppServer/MyCustomerAppserver-99999-10.0.700.2");
// References: Epicor.ServiceModel.dll, Erp.Contracts.BO.ABCCode.dll
var abcCodeBO = Ice.Lib.Framework.WCFServiceSupport.CreateImpl<Erp.Proxy.BO.ABCCodeImpl>(session, Erp.Proxy.BO.ABCCodeImpl.UriPath);
// Call the BO methods
var ds = abcCodeBO.GetByID("A");
var row = ds.ABCCode[0];
System.Console.WriteLine("CountFreq is {0}", row.CountFreq);
System.Console.WriteLine("CustomField_c is {0}", row["CustomField_c"]);
System.Console.ReadKey();
}
}
UserDefinedColumns is defined in Epicor.ServiceModel but is inaccessible as it is an internal property of Ice.IceRow which Erp.Tablesets.QuoteHedRow inherits from.
When you've found the specific record your looking for and have an object containing all of the columns for the record you should see an additional object named UserDefinedColumns. It works like a dictonary that is of type <string, object>. So for instance to set a value out you would do something like this:
myPartDs.Part[0].UserDefinedColumns["MyUdColumn_c"] = "some value";
If you need to pull a value out then you will have to parse it to whatever type it needs to be because they are stored as objects.

Categories

Resources