Want a way to call a WCF Workflow Service Application using REST instead of SOAP. I've defined a REST endpoint with custom webhttpbehavior and from that call I am trying to load the XAMLX and run it.
My first attempt fails with
Expression Activity type 'CSharpReference`1' requires compilation in order to run. Please ensure that the workflow has been compiled
I found code to compile the Expression before invoking the workflow and then I get errors like.
The type or namespace name 'Activities' does not exist in the namespace 'System'
I found some other code to set
AttachableMemberIdentifier and AttachablePropertyServices
then I got
The type or namespace name 'Activities' does not exist in the namespace 'System.ServiceModel'
Even though I'm adding the assemblyreferences for all 3 namespaces (my local solution, System.ServiceModel and System.Activities)
The xamlx is the simple out of box one that it generates with GetData passing in an int and returns the int.ToString()
What am I missing?
Code Following:
namespace WFStarterSolution
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class RestService
{
static void CompileExpressions(DynamicActivity activity)
{
var activityName = activity.Name;
// Split activityName into Namespace and Type.Append _CompiledExpressionRoot to the type name
// to represent the new type that represents the compiled expressions.
// Take everything after the last . for the type name.
var activityType = activityName.Split('.').Last() + "_CompiledExpressionRoot";
// Take everything before the last . for the namespace.
var activityNamespace = string.Join(".", activityName.Split('.').Reverse().Skip(1).Reverse());
// Create a TextExpressionCompilerSettings.
var settings = new TextExpressionCompilerSettings
{
Activity = activity,
Language = "C#",
ActivityName = activityType,
ActivityNamespace = activityNamespace,
RootNamespace = null,//"CSharpExpression",
GenerateAsPartialClass = false,
AlwaysGenerateSource = true,
ForImplementation = true
};
// Compile the C# expression.
var results = new TextExpressionCompiler(settings).Compile();
// Any compilation errors are contained in the CompilerMessages.
if (results.HasErrors)
{
var cm = results.CompilerMessages.Aggregate(" ", (current, e) => current + (e.Number + " - "+e.Message + " : Line Number "+e.SourceLineNumber + ""));
throw new Exception("Compilation failed."+cm);
}
// Create an instance of the new compiled expression type.
var compiledExpressionRoot =
Activator.CreateInstance(results.ResultType,
new object[] { activity }) as ICompiledExpressionRoot;
// Attach it to the activity.
CompiledExpressionInvoker.SetCompiledExpressionRoot(
activity, compiledExpressionRoot);
}
[OperationContract]
public string DoWork()
{
// call WFService XAMLX somehow
var filepath = AppDomain.CurrentDomain.BaseDirectory;
try
{
var serviceImplementation = XamlServices.Load(filepath + "WFService.xamlx");
var service = serviceImplementation as WorkflowService;
if (service == null)
{
return "Failed";
}
else
{
var activity = service.Body;
var operand1 = new InArgument();
var dyanamicActivity = new DynamicActivity { Name = "WFServiceName", Implementation = () => activity};
var p = new DynamicActivityProperty
{
Name = "data",
Type = typeof(InArgument),
Value = operand1
};
dyanamicActivity.Properties.Add(p);
var impl = new AttachableMemberIdentifier(typeof(TextExpression), "NamespacesForImplementation");
var namespaces = new List { "WFStarterSolution" };
var ar = new[]
{
new AssemblyReference
{
Assembly = typeof (DynamicActivity).Assembly
},
new AssemblyReference
{
Assembly = typeof (RestService).Assembly
},
new AssemblyReference
{
Assembly = typeof (ServiceContractAttribute).Assembly
}
};
TextExpression.SetReferencesForImplementation(dyanamicActivity, ar);
AttachablePropertyServices.SetProperty(dyanamicActivity, impl, namespaces);
CompileExpressions(dyanamicActivity);
var iDict = new Dictionary() { { "data", 45} };
var output = WorkflowInvoker.Invoke(dyanamicActivity, iDict);
return "success";
}
}
catch (Exception ex)
{
return ex.Message+""+ex.StackTrace;
}
}
}
}
UPDATE *
If I add the following in to the AssemblyReference array
,new AssemblyReference
{
Assembly = typeof (WorkflowService).Assembly
}
, it compiles fine... but still gives the me original error of
Expression Activity type 'CSharpReference`1' requires compilation in order to run. Please ensure that the workflow has been compiled
Related
Trying to compile simple C# code at runtime on .NET Core but have this error:
System.PlatformNotSupportedException: 'Operation is not supported on
this platform.'
on this line:
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
My code:
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
string code = #"
using System;
namespace First
{
public class Program
{
public static void Main()
{
" +
"Console.WriteLine(\"Hello, world!\");"
+ #"
}
}
}
";
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("First.Program");
MethodInfo main = program.GetMethod("Main");
main.Invoke(null, null);
I recommend using the Roslyn compiler. You'll need to add references Microsoft.CodeAnalysis and Microsoft.CodeAnalysis.CSharp for the following example to work. Note, that the RoslynCompiler class loads the assembly dynamically. You can modify the class fairly easily to use a FileStream instead of a MemoryStream if you want to save the compilation to disk for reuse.
Sample Usage of RoslynCompiler Class (below)
string code = #"
using System;
namespace First
{
public class Program
{
public static void Main()
{
Console.WriteLine(\"Hello, world!\");
}
public static void WithParams(string message)
{
Console.WriteLine(message);
}
}
}
";
var compiler = new RoslynCompiler("First.Program", code, new[] {typeof(Console)});
var type = compiler.Compile();
type.GetMethod("Main").Invoke(null, null);
//result: Hellow World!
// pass an object array to the second null parameter to pass arguments
type.GetMethod("WithParams").Invoke(null, new object[] {"Hi there from invoke!"});
//result: Hi from invoke
Roslyn Compiler Class (Quick and Dirty Example)
public class RoslynCompiler
{
readonly CSharpCompilation _compilation;
Assembly _generatedAssembly;
Type? _proxyType;
string _assemblyName;
string _typeName;
public RoslynCompiler(string typeName, string code, Type[] typesToReference)
{
_typeName = typeName;
var refs = typesToReference.Select(h => MetadataReference.CreateFromFile(h.Assembly.Location) as MetadataReference).ToList();
//some default refeerences
refs.Add(MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll")));
refs.Add(MetadataReference.CreateFromFile(typeof(Object).Assembly.Location));
//generate syntax tree from code and config compilation options
var syntax = CSharpSyntaxTree.ParseText(code);
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
allowUnsafe: true,
optimizationLevel: OptimizationLevel.Release);
_compilation = CSharpCompilation.Create(_assemblyName = Guid.NewGuid().ToString(), new List<SyntaxTree> { syntax }, refs, options);
}
public Type Compile()
{
if (_proxyType != null) return _proxyType;
using (var ms = new MemoryStream())
{
var result = _compilation.Emit(ms);
if (!result.Success)
{
var compilationErrors = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error)
.ToList();
if (compilationErrors.Any())
{
var firstError = compilationErrors.First();
var errorNumber = firstError.Id;
var errorDescription = firstError.GetMessage();
var firstErrorMessage = $"{errorNumber}: {errorDescription};";
var exception = new Exception($"Compilation failed, first error is: {firstErrorMessage}");
compilationErrors.ForEach(e => { if (!exception.Data.Contains(e.Id)) exception.Data.Add(e.Id, e.GetMessage()); });
throw exception;
}
}
ms.Seek(0, SeekOrigin.Begin);
_generatedAssembly = AssemblyLoadContext.Default.LoadFromStream(ms);
_proxyType = _generatedAssembly.GetType(_typeName);
return _proxyType;
}
}
}
Performance Tip
If performance matters, use delegates as opposed to Invoke as follows to achieve near pre-compiled throughput:
void Main()
{
string code = #"OMITTED EXAMPLE CODE FROM SAMPLE ABOVE";
var compiler = new RoslynCompiler("First.Program", code, new[] { typeof(Console) });
var type = compiler.Compile();
// If perf matters used delegates to get near pre-compiled througput vs Invoke()
var cachedDelegate = new DynamicDelegateCacheExample(type);
cachedDelegate.Main();
//result: Hellow world!
cachedDelegate.Main("Hi there from cached delegate!");
//result: Hi there from cached delegate!
}
public class DynamicDelegateCacheExample
{
delegate void methodNoParams();
delegate void methodWithParamas(string message);
private static methodNoParams cachedDelegate;
private static methodWithParamas cachedDelegateWeithParams;
public DynamicDelegateCacheExample(Type myDynamicType)
{
cachedDelegate = myDynamicType.GetMethod("Main").CreateDelegate<methodNoParams>();
cachedDelegateWeithParams = myDynamicType.GetMethod("WithParams").CreateDelegate<methodWithParamas>();
}
public void Main() => cachedDelegate();
public void Main(string message) => cachedDelegateWeithParams(message);
}
With .net core netstandard and publishing to a self contained exe there are a couple more tricks you'll need;
public static ModuleMetadata GetMetadata(this Assembly assembly)
{
// based on https://github.com/dotnet/runtime/issues/36590#issuecomment-689883856
unsafe
{
return assembly.TryGetRawMetadata(out var blob, out var len)
? ModuleMetadata.CreateFromMetadata((IntPtr)blob, len)
: throw new InvalidOperationException($"Could not get metadata from {assembly.FullName}");
}
}
#pragma warning disable IL3000
public static MetadataReference GetReference(this Assembly assembly)
=> (assembly.Location == "")
? AssemblyMetadata.Create(assembly.GetMetadata()).GetReference()
: MetadataReference.CreateFromFile(assembly.Location);
#pragma warning restore IL3000
public static Assembly Compile(string source, IEnumerable<Type> references)
{
var refs = new HashSet<Assembly>(){
typeof(object).Assembly
};
foreach (var t in references)
refs.Add(t.Assembly);
foreach (var a in AppDomain.CurrentDomain.GetAssemblies()
.Where(a => !a.IsDynamic
&& a.ExportedTypes.Count() == 0
&& (a.FullName.Contains("netstandard") || a.FullName.Contains("System.Runtime,"))))
refs.Add(a);
var options = CSharpParseOptions.Default
.WithLanguageVersion(LanguageVersion.Latest);
var compileOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);
var compilation = CSharpCompilation.Create("Dynamic",
new[] { SyntaxFactory.ParseSyntaxTree(source, options) },
refs.Select(a => a.GetReference()),
compileOptions
);
using var ms = new MemoryStream();
var e = compilation.Emit(ms);
if (!e.Success)
throw new Exception("Compilation failed");
ms.Seek(0, SeekOrigin.Begin);
var context = new AssemblyLoadContext(null, true);
return context.LoadFromStream(ms);
}
// for dynamically implementing some interface;
public static C CompileInstance<C>(string source, IEnumerable<Type> references)
{
var assembly = Compile(source, references);
var modelType = assembly.DefinedTypes.Where(t => typeof(C).IsAssignableFrom(t)).Single();
return (C)Activator.CreateInstance(modelType);
}
I have the following code.
[HttpGet]
public async Task<List<TenantManagementWebApi.Entities.SiteCollection>> Get()
{
var tenant = await TenantHelper.GetActiveTenant();
var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
string domainUrl = tenant.TestSiteCollectionUrl;
string tenantName = domainUrl.Split('.')[0];
string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
using (var context = new OfficeDevPnP.Core.AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant(tenantAdminUrl, tenant.Email, keyVaultHelper.SecretValue))
{
Tenant tenantOnline = new Tenant(context);
SPOSitePropertiesEnumerable siteProps = tenantOnline.GetSitePropertiesFromSharePoint("0", true);
context.Load(siteProps);
context.ExecuteQuery();
List<TenantManagementWebApi.Entities.SiteCollection> sites = new List<TenantManagementWebApi.Entities.SiteCollection>();
foreach (var site in siteProps)
{
if(site.Template.Contains("SITEPAGEPUBLISHING#0") || site.Template.Contains("GROUP#0"))
{
string strTemplate= default(string);
if(site.Template.Contains("SITEPAGEPUBLISHING#0"))
{
strTemplate = "CommunicationSite";
};
if (site.Template.Contains("GROUP#0"))
{
strTemplate = "Modern Team Site";
};
try
{
Guid id = Guid.NewGuid();
Entities.SiteCollection sc = new Entities.SiteCollection()
{
Id = id.ToString(),
Owner = site.Owner,
Template = strTemplate,
Title = site.Title,
Active = false,
Url = site.Url
};
var added = await siteCollectionStore.AddAsync(sc);
sites.Add(sc);
}
catch (System.Exception ex)
{
throw ex;
}
}
}
return sites;
};
}
However the following lines, I am repeating them on every method:
var tenant = await TenantHelper.GetActiveTenant();
var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
string domainUrl = tenant.TestSiteCollectionUrl;
string tenantName = domainUrl.Split('.')[0];
string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
I will have lots of API controllers on my project
Is there an easy way (not refactor as a method), to make my code cleaner and inject the variables I need without copying and pasting every single time?
I'm having a problem passing a generic collection to a WCF service method when invoked using reflection. Specifically, the collection is of type List<KeyValuePair<string,string>>.
My goal is to be able to execute methods of a WCF service dynamically during runtime without adding any references to the service whatsoever in my client app. A user should be able to add a service during runtime and the app should just magically be able to handle it.
Service Interface
[ServiceContract]
public interface ITestService
{
[OperationContract]
string ProcessSimpleType(string value);
[OperationContract]
string ProcessGenericCollection(List<KeyValuePair<string, string>> genericCol);
}
Service Implementation
public class TestService : ITestService
{
public string ProcessSimpleType(string value)
{
return value;
}
public string ProcessGenericCollection(List<KeyValuePair<string, string>> genericCol)
{
return "Hello World!";
}
}
Client Code
try
{
Uri mexAddress = new Uri("http://localhost:8732/TestService/?wsdl");
MetadataExchangeClientMode mexMode = MetadataExchangeClientMode.HttpGet;
string contractName = "ITestService";
string operationName = "ProcessGenericCollection";
List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
list.Add(new KeyValuePair<string, string>("key", "value"));
object[] operationParameters = new object[] { list };
MetadataExchangeClient mexClient = new MetadataExchangeClient(mexAddress, mexMode);
mexClient.ResolveMetadataReferences = true;
MetadataSet metaSet = mexClient.GetMetadata();
WsdlImporter importer = new WsdlImporter(metaSet);
Collection<ContractDescription> contracts = importer.ImportAllContracts();
ServiceEndpointCollection allEndpoints = importer.ImportAllEndpoints();
ServiceContractGenerator generator = new ServiceContractGenerator();
var endpointsForContracts = new Dictionary<string, IEnumerable<ServiceEndpoint>>();
foreach (ContractDescription contract in contracts)
{
generator.GenerateServiceContractType(contract);
endpointsForContracts[contract.Name] = allEndpoints.Where(
se => se.Contract.Name == contract.Name).ToList();
}
if (generator.Errors.Count != 0)
throw new Exception("There were errors during code compilation.");
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BracingStyle = "C";
CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");
CompilerParameters compilerParameters = new CompilerParameters(
new string[] {
"System.dll", "System.ServiceModel.dll",
"System.Runtime.Serialization.dll" });
compilerParameters.GenerateInMemory = true;
CompilerResults results = codeDomProvider.CompileAssemblyFromDom(
compilerParameters, generator.TargetCompileUnit);
if (results.Errors.Count > 0)
{
throw new Exception("There were errors during generated code compilation");
}
else
{
Type clientProxyType = results.CompiledAssembly.GetTypes().First(
t => t.IsClass &&
t.GetInterface(contractName) != null &&
t.GetInterface(typeof(ICommunicationObject).Name) != null);
ServiceEndpoint se = endpointsForContracts[contractName].First();
object instance = results.CompiledAssembly.CreateInstance(
clientProxyType.Name,
false,
System.Reflection.BindingFlags.CreateInstance,
null,
new object[] { se.Binding, se.Address },
CultureInfo.CurrentCulture, null);
var methodInfo = instance.GetType().GetMethod(operationName);
//Invoking the ProcessGenericCollection via reflection will throw an exception
object retVal = methodInfo.Invoke(instance, BindingFlags.InvokeMethod, null, operationParameters, null);
Console.WriteLine(retVal.ToString());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
The error that is thrown is:
{"Object of type
'System.Collections.Generic.List1[System.Collections.Generic.KeyValuePair2[System.String,System.String]]'
cannot be converted to type
'System.Collections.Generic.KeyValuePairOfstringstring[]'."}
Keep in mind, this works gloriously when testing against the ProcessSimpleType(...) method and passing in a simple type. My issue is only with ProcessGenericCollection(...). Has anyone ever encountered this issue, and if so, how did you overcome it?
Thanks to a colleague for providing the solution. For those of you with a similar issue, I inserted the following:
...
...
WsdlImporter importer = new WsdlImporter(metaSet);
//BEGIN INSERT
XsdDataContractImporter xsd = new XsdDataContractImporter();
xsd.Options = new ImportOptions();
xsd.Options.ImportXmlType = true;
xsd.Options.GenerateSerializable = true;
xsd.Options.ReferencedTypes.Add(typeof(KeyValuePair<string, string>));
xsd.Options.ReferencedTypes.Add(typeof(System.Collections.Generic.List<KeyValuePair<string, string>>));
importer.State.Add(typeof(XsdDataContractImporter), xsd);
//END INSERT
Collection<ContractDescription> contracts = importer.ImportAllContracts();
...
...
I'm working on creating an open source project for creating .NET UML Sequence Diagrams that leverages a javascript library called js-sequence-diagrams. I am not sure Roslyn is the right tool for the job, but I thought I would give it a shot so I have put together some proof of concept code which attempts to get all methods and their invocations and then outputs these invocations in a form that can be interpreted by js-sequence-diagrams.
The code generates some output, but it does not capture everything. I cannot seem to capture invocations via extension methods, invocations of static methods in static classes.
I do see invocations of methods with out parameters, but not in any form that extends the BaseMethodDeclarationSyntax
Here is the code (keep in mind this is proof of concept code and so I did not entirely follow best-practices, but I am not requesting a code review here ... also, I am used to using Tasks so I am messing around with await, but am not entirely sure I am using it properly yet)
https://gist.github.com/SoundLogic/11193841
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Emit;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.FindSymbols;
using System.Collections.Immutable;
namespace Diagrams
{
class Program
{
static void Main(string[] args)
{
string solutionName = "Diagrams";
string solutionExtension = ".sln";
string solutionFileName = solutionName + solutionExtension;
string rootPath = #"C:\Workspace\";
string solutionPath = rootPath + solutionName + #"\" + solutionFileName;
MSBuildWorkspace workspace = MSBuildWorkspace.Create();
DiagramGenerator diagramGenerator = new DiagramGenerator( solutionPath, workspace );
diagramGenerator.ProcessSolution();
#region reference
//TODO: would ReferencedSymbol.Locations be a better way of accessing MethodDeclarationSyntaxes?
//INamedTypeSymbol programClass = compilation.GetTypeByMetadataName("DotNetDiagrams.Program");
//IMethodSymbol barMethod = programClass.GetMembers("Bar").First(s => s.Kind == SymbolKind.Method) as IMethodSymbol;
//IMethodSymbol fooMethod = programClass.GetMembers("Foo").First(s => s.Kind == SymbolKind.Method) as IMethodSymbol;
//ITypeSymbol fooSymbol = fooMethod.ContainingType;
//ITypeSymbol barSymbol = barMethod.ContainingType;
//Debug.Assert(barMethod != null);
//Debug.Assert(fooMethod != null);
//List<ReferencedSymbol> barReferencedSymbols = SymbolFinder.FindReferencesAsync(barMethod, solution).Result.ToList();
//List<ReferencedSymbol> fooReferencedSymbols = SymbolFinder.FindReferencesAsync(fooMethod, solution).Result.ToList();
//Debug.Assert(barReferencedSymbols.First().Locations.Count() == 1);
//Debug.Assert(fooReferencedSymbols.First().Locations.Count() == 0);
#endregion
Console.ReadKey();
}
}
class DiagramGenerator
{
private Solution _solution;
public DiagramGenerator( string solutionPath, MSBuildWorkspace workspace )
{
_solution = workspace.OpenSolutionAsync(solutionPath).Result;
}
public async void ProcessSolution()
{
foreach (Project project in _solution.Projects)
{
Compilation compilation = await project.GetCompilationAsync();
ProcessCompilation(compilation);
}
}
private async void ProcessCompilation(Compilation compilation)
{
var trees = compilation.SyntaxTrees;
foreach (var tree in trees)
{
var root = await tree.GetRootAsync();
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var #class in classes)
{
ProcessClass( #class, compilation, tree, root );
}
}
}
private void ProcessClass(
ClassDeclarationSyntax #class
, Compilation compilation
, SyntaxTree tree
, SyntaxNode root)
{
var methods = #class.DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var method in methods)
{
var model = compilation.GetSemanticModel(tree);
// Get MethodSymbol corresponding to method
var methodSymbol = model.GetDeclaredSymbol(method);
// Get all InvocationExpressionSyntax in the above code.
var allInvocations = root.DescendantNodes().OfType<InvocationExpressionSyntax>();
// Use GetSymbolInfo() to find invocations of target method
var matchingInvocations =
allInvocations.Where(i => model.GetSymbolInfo(i).Symbol.Equals(methodSymbol));
ProcessMethod( matchingInvocations, method, #class);
}
var delegates = #class.DescendantNodes().OfType<DelegateDeclarationSyntax>();
foreach (var #delegate in delegates)
{
var model = compilation.GetSemanticModel(tree);
// Get MethodSymbol corresponding to method
var methodSymbol = model.GetDeclaredSymbol(#delegate);
// Get all InvocationExpressionSyntax in the above code.
var allInvocations = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
// Use GetSymbolInfo() to find invocations of target method
var matchingInvocations =
allInvocations.Where(i => model.GetSymbolInfo(i).Symbol.Equals(methodSymbol));
ProcessDelegates(matchingInvocations, #delegate, #class);
}
}
private void ProcessMethod(
IEnumerable<InvocationExpressionSyntax> matchingInvocations
, MethodDeclarationSyntax methodDeclarationSyntax
, ClassDeclarationSyntax classDeclarationSyntax )
{
foreach (var invocation in matchingInvocations)
{
MethodDeclarationSyntax actingMethodDeclarationSyntax = null;
if (SyntaxNodeHelper.TryGetParentSyntax(invocation, out actingMethodDeclarationSyntax))
{
var r = methodDeclarationSyntax;
var m = actingMethodDeclarationSyntax;
PrintCallerInfo(
invocation
, classDeclarationSyntax
, m.Identifier.ToFullString()
, r.ReturnType.ToFullString()
, r.Identifier.ToFullString()
, r.ParameterList.ToFullString()
, r.TypeParameterList != null ? r.TypeParameterList.ToFullString() : String.Empty
);
}
}
}
private void ProcessDelegates(
IEnumerable<InvocationExpressionSyntax> matchingInvocations
, DelegateDeclarationSyntax delegateDeclarationSyntax
, ClassDeclarationSyntax classDeclarationSyntax )
{
foreach (var invocation in matchingInvocations)
{
DelegateDeclarationSyntax actingMethodDeclarationSyntax = null;
if (SyntaxNodeHelper.TryGetParentSyntax(invocation, out actingMethodDeclarationSyntax))
{
var r = delegateDeclarationSyntax;
var m = actingMethodDeclarationSyntax;
PrintCallerInfo(
invocation
, classDeclarationSyntax
, m.Identifier.ToFullString()
, r.ReturnType.ToFullString()
, r.Identifier.ToFullString()
, r.ParameterList.ToFullString()
, r.TypeParameterList != null ? r.TypeParameterList.ToFullString() : String.Empty
);
}
}
}
private void PrintCallerInfo(
InvocationExpressionSyntax invocation
, ClassDeclarationSyntax classBeingCalled
, string callingMethodName
, string returnType
, string calledMethodName
, string calledMethodArguments
, string calledMethodTypeParameters = null )
{
ClassDeclarationSyntax parentClassDeclarationSyntax = null;
if (!SyntaxNodeHelper.TryGetParentSyntax(invocation, out parentClassDeclarationSyntax))
{
throw new Exception();
}
calledMethodTypeParameters = calledMethodTypeParameters ?? String.Empty;
var actedUpon = classBeingCalled.Identifier.ValueText;
var actor = parentClassDeclarationSyntax.Identifier.ValueText;
var callInfo = callingMethodName + "=>" + calledMethodName + calledMethodTypeParameters + calledMethodArguments;
var returnCallInfo = returnType;
string info = BuildCallInfo(
actor
, actedUpon
, callInfo
, returnCallInfo);
Console.Write(info);
}
private string BuildCallInfo(string actor, string actedUpon, string callInfo, string returnInfo)
{
const string calls = "->";
const string returns = "-->";
const string descriptionSeparator = ": ";
string callingInfo = actor + calls + actedUpon + descriptionSeparator + callInfo;
string returningInfo = actedUpon + returns + actor + descriptionSeparator + "returns " + returnInfo;
callingInfo = callingInfo.RemoveNewLines(true);
returningInfo = returningInfo.RemoveNewLines(true);
string result = callingInfo + Environment.NewLine;
result += returningInfo + Environment.NewLine;
return result;
}
}
static class SyntaxNodeHelper
{
public static bool TryGetParentSyntax<T>(SyntaxNode syntaxNode, out T result)
where T : SyntaxNode
{
// set defaults
result = null;
if (syntaxNode == null)
{
return false;
}
try
{
syntaxNode = syntaxNode.Parent;
if (syntaxNode == null)
{
return false;
}
if (syntaxNode.GetType() == typeof (T))
{
result = syntaxNode as T;
return true;
}
return TryGetParentSyntax<T>(syntaxNode, out result);
}
catch
{
return false;
}
}
}
public static class StringEx
{
public static string RemoveNewLines(this string stringWithNewLines, bool cleanWhitespace = false)
{
string stringWithoutNewLines = null;
List<char> splitElementList = Environment.NewLine.ToCharArray().ToList();
if (cleanWhitespace)
{
splitElementList.AddRange(" ".ToCharArray().ToList());
}
char[] splitElements = splitElementList.ToArray();
var stringElements = stringWithNewLines.Split(splitElements, StringSplitOptions.RemoveEmptyEntries);
if (stringElements.Any())
{
stringWithoutNewLines = stringElements.Aggregate(stringWithoutNewLines, (current, element) => current + (current == null ? element : " " + element));
}
return stringWithoutNewLines ?? stringWithNewLines;
}
}
}
Any guidance here would be much appreciated!
Using the methodSymbol in the ProcessClass method I took Andy's suggestion and came up with the below (although I imagine there may be an easier way to go about this):
private async Task<List<MethodDeclarationSyntax>> GetMethodSymbolReferences( IMethodSymbol methodSymbol )
{
var references = new List<MethodDeclarationSyntax>();
var referencingSymbols = await SymbolFinder.FindCallersAsync(methodSymbol, _solution);
var referencingSymbolsList = referencingSymbols as IList<SymbolCallerInfo> ?? referencingSymbols.ToList();
if (!referencingSymbolsList.Any(s => s.Locations.Any()))
{
return references;
}
foreach (var referenceSymbol in referencingSymbolsList)
{
foreach (var location in referenceSymbol.Locations)
{
var position = location.SourceSpan.Start;
var root = await location.SourceTree.GetRootAsync();
var nodes = root.FindToken(position).Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>();
references.AddRange(nodes);
}
}
return references;
}
and the resulting image generated by plugging the output text into js-sequence-diagrams (I have updated the github gist with the full source for this should anyone find it useful - I excluded method parameters so the diagram was easy digest, but these can optionally be turned back on):
Edit:
I've updated the code (see the github gist) so now calls are shown in the order they were made (based on the span start location of a called method from within the calling method via results from FindCallersAsync):
Here's what I'm doing:
Selector selector = new Selector();
selector.fields = new string[] {"CampaignId", "AdGroupId", "Id", "CriteriaType", "Criteria", "CriteriaDestinationUrl", "Clicks", "Impressions", "Cost"};
Predicate predicate = new Predicate();
predicate.field = "Status";
predicate.#operator = PredicateOperator.IN;
predicate.values = new string[] { "ACTIVE", "PAUSED" };
selector.predicates = new Predicate[] { predicate };
ReportDefinition definition = new ReportDefinition();
definition.reportName = "criteria report";
definition.reportType = ReportDefinitionReportType.CRITERIA_PERFORMANCE_REPORT;
definition.downloadFormat = DownloadFormat.XML;
definition.dateRangeType = ReportDefinitionDateRangeType.YESTERDAY;
definition.selector = selector;
definition.includeZeroImpressions = true;
_handler.RunReport(new AdWordsUser(), definition);
And here's my handler method:
public void RunReport(AdWordsUser user, ReportDefinition definition)
{
if (definition != null)
{
string reportContents = string.Empty;
try
{
ReportUtilities utilities = new ReportUtilities(user);
utilities.ReportVersion = "v201206";
ClientReport report = utilities.GetClientReport(definition);
reportContents = report.Contents.ToString();
}
catch (Exception ex)
{
_errorHandler.AddError(ex);
}
}
}
Where I step through I get this error:
Report contents are invalid. - !!!2|||-1|||[ReportDefinitionError.CUSTOMER_SERVING_TYPE_REPORT_MISMATCH # selector]???
Been searching for hours trying to find a solution. Any hints?
It appears that the issue is indeed needing to pass in the customerClientID.
This document helped me picture what was going on.
I ended up modifying my code like this:
var configOptions = new Dictionary<string,string>();
configOptions.Add("clientCustomerID", customerID.ToString());
_handler.RunReport(new AdWordsUser(configOptions), definition);