Skip to content

Commit 5c6fbc7

Browse files
author
Jani Giannoudis
committed
added action reflector
updated c# code analysis nuget updated version to 0.9.0-beta.3
1 parent 47ec32a commit 5c6fbc7

6 files changed

+366
-80
lines changed

Client.Scripting/ActionReflector.cs

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Collections.Generic;
6+
using PayrollEngine.Client.Model;
7+
8+
namespace PayrollEngine.Client.Scripting;
9+
10+
/// <summary>
11+
/// Action reflector
12+
/// </summary>
13+
public static class ActionReflector
14+
{
15+
/// <summary>
16+
/// Load action infos from assembly file
17+
/// </summary>
18+
/// <param name="assemblyName">Assembly file name</param>
19+
/// <returns>Assembly and list of action infos.</returns>
20+
public static (Assembly Assembly, List<ActionInfo> Actions) LoadFrom(string assemblyName)
21+
{
22+
if (string.IsNullOrWhiteSpace(assemblyName))
23+
{
24+
throw new ArgumentException(nameof(assemblyName));
25+
}
26+
if (!File.Exists(assemblyName))
27+
{
28+
throw new PayrollException($"Invalid action assembly file {assemblyName}");
29+
}
30+
31+
var assembly = Assembly.LoadFrom(assemblyName);
32+
var actions = LoadFrom(assembly);
33+
return (assembly, actions);
34+
}
35+
36+
/// <summary>
37+
/// Load action infos from assembly
38+
/// </summary>
39+
/// <param name="assembly">Assembly</param>
40+
/// <returns>List of action infos.</returns>
41+
public static List<ActionInfo> LoadFrom(Assembly assembly)
42+
{
43+
if (assembly == null)
44+
{
45+
throw new ArgumentNullException(nameof(assembly));
46+
}
47+
48+
// setup action cache
49+
var actions = new List<ActionInfo>();
50+
foreach (var type in assembly.GetTypes().Where(x => !x.IsGenericType && !x.IsNested))
51+
{
52+
53+
// action provider attribute
54+
var providerAttribute = GetProviderAttribute(type);
55+
if (providerAttribute == null)
56+
{
57+
continue;
58+
}
59+
60+
foreach (var typeMethod in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
61+
{
62+
// action attribute
63+
var actionAttribute = GetActionAttribute(typeMethod);
64+
if (actionAttribute == null)
65+
{
66+
continue;
67+
}
68+
69+
// action attribute
70+
var actionInfo = new ActionInfo(providerAttribute.Type)
71+
{
72+
Namespace = providerAttribute.Namespace,
73+
Name = GetPropertyValue<string>(actionAttribute,
74+
nameof(ActionAttribute.Name)),
75+
Description = GetPropertyValue<string>(actionAttribute,
76+
nameof(ActionAttribute.Description)),
77+
Categories = [.. GetPropertyValue<string[]>(actionAttribute,
78+
nameof(ActionAttribute.Categories))],
79+
Parameters = [],
80+
Issues = []
81+
};
82+
actions.Add(actionInfo);
83+
84+
// action parameter attributes (optional)
85+
var parameterAttributes = GetActionParameterAttributes(typeMethod,
86+
typeof(ActionParameterAttribute));
87+
if (parameterAttributes != null)
88+
{
89+
foreach (var parameterAttribute in parameterAttributes)
90+
{
91+
var name = GetPropertyValue<string>(parameterAttribute,
92+
nameof(ActionParameterAttribute.Name));
93+
94+
// test parameter
95+
if (!typeMethod.GetParameters().Any(x => string.Equals(x.Name, name)))
96+
{
97+
throw new PayrollException($"Invalid action parameter {actionInfo.Name}.{name}");
98+
}
99+
100+
var actionParameterInfo = new ActionParameterInfo
101+
{
102+
Name = name,
103+
Description = GetPropertyValue<string>(parameterAttribute,
104+
nameof(ActionParameterAttribute.Description)),
105+
ValueReferences = [],
106+
ValueSources = [],
107+
ValueTypes = []
108+
};
109+
110+
var valueReferences = GetPropertyValue<string[]>(parameterAttribute,
111+
nameof(ActionParameterAttribute.ValueReferences));
112+
if (valueReferences != null)
113+
{
114+
actionParameterInfo.ValueReferences.AddRange(valueReferences);
115+
}
116+
var valueSources = GetPropertyValue<string[]>(parameterAttribute,
117+
nameof(ActionParameterAttribute.ValueSources));
118+
if (valueSources != null)
119+
{
120+
actionParameterInfo.ValueSources.AddRange(valueSources);
121+
}
122+
var valueTypes = GetPropertyValue<string[]>(parameterAttribute,
123+
nameof(ActionParameterAttribute.ValueTypes));
124+
if (valueTypes != null)
125+
{
126+
actionParameterInfo.ValueTypes.AddRange(valueTypes);
127+
}
128+
129+
actionInfo.Parameters.Add(actionParameterInfo);
130+
}
131+
}
132+
133+
// action issue attributes (optional)
134+
var issuesAttributes = GetActionParameterAttributes(typeMethod, typeof(ActionIssueAttribute));
135+
if (issuesAttributes != null)
136+
{
137+
foreach (var issueAttribute in issuesAttributes)
138+
{
139+
actionInfo.Issues.Add(new()
140+
{
141+
Name = GetPropertyValue<string>(issueAttribute,
142+
nameof(ActionIssueAttribute.Name)),
143+
Message = GetPropertyValue<string>(issueAttribute,
144+
nameof(ActionIssueAttribute.Message)),
145+
ParameterCount = GetPropertyValue<int>(issueAttribute,
146+
nameof(ActionIssueAttribute.ParameterCount))
147+
});
148+
}
149+
}
150+
}
151+
}
152+
153+
// order by action name
154+
actions.Sort((x, y) => string.CompareOrdinal(x.FullName, y.FullName));
155+
return actions;
156+
}
157+
158+
/// <summary>
159+
/// Get action provider attribute
160+
/// </summary>
161+
/// <param name="type">Action member type</param>
162+
private static ActionProviderAttribute GetProviderAttribute(MemberInfo type)
163+
{
164+
var providerAttributeName = typeof(ActionProviderAttribute).FullName;
165+
foreach (var typeAttribute in type.GetCustomAttributes())
166+
{
167+
// provider attribute type
168+
if (string.Equals(providerAttributeName, typeAttribute.GetType().FullName))
169+
{
170+
return typeAttribute as ActionProviderAttribute;
171+
}
172+
}
173+
return null;
174+
}
175+
176+
/// <summary>
177+
/// Get action attribute
178+
/// </summary>
179+
/// <param name="method">Action method</param>
180+
private static Attribute GetActionAttribute(MemberInfo method)
181+
{
182+
var actionAttributeName = nameof(ActionAttribute);
183+
var actionAttributeNamespace = typeof(ActionAttribute).Namespace;
184+
if (actionAttributeNamespace == null)
185+
{
186+
return null;
187+
}
188+
foreach (var methodAttribute in method.GetCustomAttributes())
189+
{
190+
// action attribute type
191+
var methodTypeName = methodAttribute.GetType().FullName;
192+
if (methodTypeName != null &&
193+
methodTypeName.StartsWith(actionAttributeNamespace) &&
194+
methodTypeName.EndsWith(actionAttributeName))
195+
{
196+
return methodAttribute;
197+
}
198+
}
199+
return null;
200+
}
201+
202+
/// <summary>
203+
/// Get action parameters attribute
204+
/// </summary>
205+
/// <param name="method">Action method</param>
206+
/// <param name="attributeType">Attribute type</param>
207+
private static List<Attribute> GetActionParameterAttributes(MemberInfo method, Type attributeType)
208+
{
209+
var attributes = new List<Attribute>();
210+
var attributeName = attributeType.FullName;
211+
foreach (var methodAttribute in method.GetCustomAttributes())
212+
{
213+
// matching type
214+
if (string.Equals(attributeName, methodAttribute.GetType().FullName))
215+
{
216+
attributes.Add(methodAttribute);
217+
}
218+
}
219+
return attributes.Any() ? attributes : null;
220+
}
221+
222+
/// <summary>
223+
/// Get property value
224+
/// </summary>
225+
/// <typeparam name="T">Value type</typeparam>
226+
/// <param name="source">Source object</param>
227+
/// <param name="propertyName">Property name</param>
228+
private static T GetPropertyValue<T>(object source, string propertyName)
229+
{
230+
if (source == null)
231+
{
232+
return default;
233+
}
234+
var property = source.GetType().GetProperty(propertyName);
235+
if (property == null)
236+
{
237+
return default;
238+
}
239+
return (T)property.GetValue(source);
240+
}
241+
}

Client.Scripting/Function/CaseInputActions.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ public void SetFieldStartReadOnly(CaseChangeActionContext context, object field)
159159
/// <param name="context">The action context</param>
160160
/// <param name="field">The target field</param>
161161
/// <param name="format">The format string</param>
162-
/// <remarks>see www.learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
162+
/// <remarks>see https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
163163
[ActionParameter("field", "The target field", valueTypes: [StringType])]
164-
[ActionParameter("format", "The format (www.learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
164+
[ActionParameter("format", "The format (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
165165
valueTypes: [StringType])]
166166
[CaseBuildAction("SetFieldStartFormat", "Set field start format", "FieldInput", "FieldStart")]
167167
public void SetFieldStartFormat(CaseChangeActionContext context, object field, object format)
@@ -264,7 +264,7 @@ public void SetFieldEndReadOnly(CaseChangeActionContext context, object field) =
264264
/// <param name="context">The action context</param>
265265
/// <param name="field">The target field</param>
266266
/// <param name="format">The format string</param>
267-
/// <remarks>see www.learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
267+
/// <remarks>see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
268268
[ActionParameter("field", "The target field", valueTypes: [StringType])]
269269
[ActionParameter("format", "The format (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
270270
valueTypes: [StringType])]
@@ -370,9 +370,9 @@ public void SetFieldValueHelp(CaseChangeActionContext context, object field, obj
370370
/// <param name="context">The action context</param>
371371
/// <param name="field">The target field</param>
372372
/// <param name="mask">The text mask</param>
373-
/// <remarks>see www.learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask</remarks>
373+
/// <remarks>see https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask</remarks>
374374
[ActionParameter("field", "The target field", valueTypes: [StringType])]
375-
[ActionParameter("mask", "The value mask (www.learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask)",
375+
[ActionParameter("mask", "The value mask (https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask)",
376376
valueTypes: [StringType])]
377377
[CaseBuildAction("SetFieldValueMask", "Set field value mask", "FieldInput", "FieldValue")]
378378
public void SetFieldValueMask(CaseChangeActionContext context, object field, object mask)
@@ -429,9 +429,9 @@ public void SetFieldValuePickerOpenYear(CaseChangeActionContext context, object
429429
/// <param name="context">The action context</param>
430430
/// <param name="field">The target field</param>
431431
/// <param name="culture">The culture</param>
432-
/// <remarks>see www.learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
432+
/// <remarks>see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
433433
[ActionParameter("field", "The target field", valueTypes: [StringType])]
434-
[ActionParameter("culture", "The culture (www.learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c)",
434+
[ActionParameter("culture", "The culture (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c)",
435435
valueTypes: [StringType])]
436436
[CaseBuildAction("SetFieldCulture", "Set field value culture", "FieldInput", "FieldValue")]
437437
public void SetFieldCulture(CaseChangeActionContext context, object field, object culture)
@@ -521,9 +521,9 @@ public void SetFieldStepSize(CaseChangeActionContext context, object field, obje
521521
/// <param name="context">The action context</param>
522522
/// <param name="field">The target field</param>
523523
/// <param name="format">The text format</param>
524-
/// <remarks>see www.learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
524+
/// <remarks>see https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
525525
[ActionParameter("field", "The target field", valueTypes: [StringType])]
526-
[ActionParameter("format", "The value format (learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
526+
[ActionParameter("format", "The value format (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
527527
valueTypes: [StringType])]
528528
[CaseBuildAction("SetFieldFormat", "Set field value format", "FieldInput", "FieldValue")]
529529
public void SetFieldFormat(CaseChangeActionContext context, object field, object format)
@@ -675,8 +675,8 @@ public void SetStartReadOnly(CaseChangeActionContext context) =>
675675
/// <summary>Set start format</summary>
676676
/// <param name="context">The action context</param>
677677
/// <param name="format">The format string</param>
678-
/// <remarks>see www.learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
679-
[ActionParameter("format", "The format (www.learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
678+
/// <remarks>see https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
679+
[ActionParameter("format", "The format (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
680680
valueTypes: [StringType])]
681681
[CaseBuildAction("SetStartFormat", "Set start format", "FieldInput", "FieldStart")]
682682
public void SetStartFormat(CaseChangeActionContext context, object format) =>
@@ -740,7 +740,7 @@ public void SetEndReadOnly(CaseChangeActionContext context) =>
740740
/// <summary>Set end format</summary>
741741
/// <param name="context">The action context</param>
742742
/// <param name="format">The format string</param>
743-
/// <remarks>see www.learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
743+
/// <remarks>see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
744744
[ActionParameter("format", "The format (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
745745
valueTypes: [StringType])]
746746
[CaseBuildAction("SetEndFormat", "Set end format", "FieldInput", "FieldEnd")]
@@ -802,8 +802,8 @@ public void SetValueHelp(CaseChangeActionContext context, object help) =>
802802
/// <summary>Set value mask</summary>
803803
/// <param name="context">The action context</param>
804804
/// <param name="mask">The text mask</param>
805-
/// <remarks>see www.learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask</remarks>
806-
[ActionParameter("mask", "The value mask (www.learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask)",
805+
/// <remarks>see https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask</remarks>
806+
[ActionParameter("mask", "The value mask (https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.maskedtextbox.mask)",
807807
valueTypes: [StringType])]
808808
[CaseBuildAction("SetValueMask", "Set value mask", "FieldInput", "FieldValue")]
809809
public void SetValueMask(CaseChangeActionContext context, object mask) =>
@@ -842,8 +842,8 @@ public void SetValueYearPicker(CaseChangeActionContext context) =>
842842
/// <summary>Set value culture</summary>
843843
/// <param name="context">The action context</param>
844844
/// <param name="culture">The culture</param>
845-
/// <remarks>see www.learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
846-
[ActionParameter("culture", "The culture (www.learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c)",
845+
/// <remarks>see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c</remarks>
846+
[ActionParameter("culture", "The culture (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c)",
847847
valueTypes: [StringType])]
848848
[CaseBuildAction("SetCulture", "Set value culture", "FieldInput", "FieldValue")]
849849
public void SetCulture(CaseChangeActionContext context, object culture) =>
@@ -878,8 +878,8 @@ public void SetStepSize(CaseChangeActionContext context, object stepSize) =>
878878
/// <summary>Set value format</summary>
879879
/// <param name="context">The action context</param>
880880
/// <param name="format">The text format</param>
881-
/// <remarks>see www.learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
882-
[ActionParameter("format", "The value format (learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
881+
/// <remarks>see https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings</remarks>
882+
[ActionParameter("format", "The value format (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)",
883883
valueTypes: [StringType])]
884884
[CaseBuildAction("SetFormat", "Set value format", "FieldInput", "FieldValue")]
885885
public void SetFormat(CaseChangeActionContext context, object format) =>

Client.Scripting/PayrollEngine.Client.Scripting.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@
175175
</ItemGroup>
176176

177177
<ItemGroup>
178-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
179-
<PackageReference Include="PayrollEngine.Client.Core" Version="0.9.0-beta.1" />
178+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
179+
<PackageReference Include="PayrollEngine.Client.Core" Version="0.9.0-beta.3" />
180180
</ItemGroup>
181181

182182
<!-- include xml documention files and json schemas to the nuget package -->

0 commit comments

Comments
 (0)