Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
integrated C# Roslyn Expression Editor
  • Loading branch information
orosandrei committed Nov 26, 2016
1 parent 33c56d0 commit cb6b5bb
Show file tree
Hide file tree
Showing 63 changed files with 2,736 additions and 83 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -13,6 +13,7 @@
*_p.c
*.ncb
*.suo
*.conflict
*.tlb
*.tlh
*.bak
Expand Down
File renamed without changes.
File renamed without changes.
Binary file removed Meetup WF Intro.suo
Binary file not shown.
Binary file removed MeetupWfIntro.suo
Binary file not shown.
Binary file removed MeetupWfIntro.v12.suo
Binary file not shown.
Binary file removed MeetupWfIntro.v12.suo.conflict
Binary file not shown.
6 changes: 0 additions & 6 deletions MeetupWfIntro/App.config

This file was deleted.

6 changes: 0 additions & 6 deletions MeetupWfIntro/packages.config

This file was deleted.

File renamed without changes.
38 changes: 38 additions & 0 deletions RehostedDesigner/App.config
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.4.1.0" newVersion="1.4.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Composition.AttributedModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.30.0" newVersion="1.0.30.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Composition.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.30.0" newVersion="1.0.30.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Composition.TypedParts" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.30.0" newVersion="1.0.30.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Composition.Hosting" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.30.0" newVersion="1.0.30.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
3 changes: 1 addition & 2 deletions MeetupWfIntro/App.xaml → RehostedDesigner/App.xaml
@@ -1,8 +1,7 @@
<Application x:Class="MeetupWfIntro.App"
<Application x:Class="RehostedWorkflowDesigner.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>

</Application.Resources>
</Application>
2 changes: 1 addition & 1 deletion MeetupWfIntro/App.xaml.cs → RehostedDesigner/App.xaml.cs
Expand Up @@ -6,7 +6,7 @@
using System.Threading.Tasks;
using System.Windows;

namespace MeetupWfIntro
namespace RehostedWorkflowDesigner
{
/// <summary>
/// Interaction logic for App.xaml
Expand Down
211 changes: 211 additions & 0 deletions RehostedDesigner/CSharpExpressionEditor/CSharpExpressionHelper.cs
@@ -0,0 +1,211 @@
using Microsoft.CSharp.Activities;
using System;
using System.Activities;
using System.Activities.Expressions;
using System.Activities.Presentation.Model;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;

namespace RehostedWorkflowDesigner.CSharpExpressionEditor
{
public static class CSharpExpressionHelper
{
private static readonly Type CSharpValueType = typeof(CSharpValue<>);
private static readonly Type CSharpReferenceType = typeof(CSharpReference<>);
private static readonly Type CodeActivityType = typeof(CodeActivity);
private static readonly Type AsyncCodeActivityType = typeof(AsyncCodeActivity);
private static readonly Type GenericAsyncCodeActivityType = typeof(AsyncCodeActivity<>);
private static readonly Type VariablesCollectionType = typeof(Collection<Variable>);

internal static ActivityWithResult CreateCSharpExpression(string expressionText, bool isLocationExpression, Type returnType)
{
Type expressionType;
if (isLocationExpression)
{
expressionType = CSharpReferenceType.MakeGenericType(returnType);
}
else
{
expressionType = CSharpValueType.MakeGenericType(returnType);
}

return Activator.CreateInstance(expressionType, expressionText) as ActivityWithResult;
}

public static ActivityWithResult CreateExpressionFromString(string expressionText, bool useLocationExpression, Type resultType)
{
ActivityWithResult expression;
if (!useLocationExpression)
{
if (!CSharpExpressionHelper.TryCreateLiteral(resultType, expressionText, out expression))
{
expression = CSharpExpressionHelper.CreateCSharpExpression(expressionText, useLocationExpression, resultType);
}
}
else
{
expression = CSharpExpressionHelper.CreateCSharpExpression(expressionText, useLocationExpression, resultType);
}

return expression;
}

internal static bool TryCreateLiteral(Type type, string expressionText, out ActivityWithResult literalExpression)
{
literalExpression = null;

// try easy way first - look if there is a type conversion which supports conversion between expression type and string
TypeConverter literalValueConverter = null;
bool isQuotedString = false;
if (IsLiteralExpressionSupported(type))
{
bool shouldBeQuoted;
if (typeof(char) == type)
{
shouldBeQuoted = true;
isQuotedString = expressionText.StartsWith("'", StringComparison.CurrentCulture) &&
expressionText.EndsWith("'", StringComparison.CurrentCulture) &&
expressionText.IndexOf("'", 1, StringComparison.CurrentCulture) == expressionText.Length - 1;
}
else
{
shouldBeQuoted = typeof(string) == type;

// whether string begins and ends with quotes '"'. also, if there are
// more quotes within than those begining and ending ones, do not bother with literal - assume this is an expression.
isQuotedString = shouldBeQuoted &&
expressionText.StartsWith("\"", StringComparison.CurrentCulture) &&
expressionText.EndsWith("\"", StringComparison.CurrentCulture) &&
expressionText.IndexOf("\"", 1, StringComparison.CurrentCulture) == expressionText.Length - 1 &&
expressionText.IndexOf("\\", StringComparison.CurrentCulture) == -1;
}

// if expression is a string, we must ensure it is quoted, in case of other types - just get the converter
if ((shouldBeQuoted && isQuotedString) || !shouldBeQuoted)
{
literalValueConverter = TypeDescriptor.GetConverter(type);
}
}

// if there is converter - try to convert
if (null != literalValueConverter && literalValueConverter.CanConvertFrom(null, typeof(string)))
{
try
{
var valueToConvert = isQuotedString ? expressionText.Substring(1, expressionText.Length - 2) : expressionText;
var convertedValue = literalValueConverter.ConvertFrom(null, CultureInfo.CurrentCulture, valueToConvert);

// ok, succeeded - create literal of given type
Type concreteExpType = typeof(Literal<>).MakeGenericType(type);
literalExpression = (ActivityWithResult)Activator.CreateInstance(concreteExpType, convertedValue);

// C# expression is case sensitive, if it's not exactly "true"/"false" with case matching, we don't generate Literal<bool>
if (type == typeof(bool) && (valueToConvert != "true") && (valueToConvert != "false"))
{
literalExpression = null;
}
}
catch
{
// conversion failed - do nothing, let it continue to generate C# expression instead
}
}

return literalExpression != null;
}

internal static bool IsLiteralExpressionSupported(Type type)
{
// type must be set and cannot be object
if (null == type || typeof(object) == type)
{
return false;
}

return type.IsPrimitive || type == typeof(string) || type == typeof(TimeSpan) || type == typeof(DateTime);
}

internal static List<ModelItem> GetVariablesInScope(ModelItem ownerActivity)
{
List<ModelItem> variablesInScope = new List<ModelItem>();
if (ownerActivity != null)
{
HashSet<string> variableNames = new HashSet<string>();
ModelItem currentItem = ownerActivity;
Func<ModelItem, bool> filterDelegate = new Func<ModelItem, bool>((variable) =>
{
string variableName = (string)variable.Properties["Name"].ComputedValue;
if (!string.IsNullOrWhiteSpace(variableName))
{
return !variableNames.Contains(variableName);
}
else
{
return false;
}
});

while (currentItem != null)
{
List<ModelItem> variables = new List<ModelItem>();
ModelItemCollection variablesCollection = GetVariableCollection(currentItem);
if (variablesCollection != null)
{
variables.AddRange(variablesCollection);
}

variables.AddRange(FindActivityDelegateArguments(currentItem));

// For the variables defined at the same level, shadowing doesn't apply. If there're multiple variables defined at the same level
// have duplicate names when case is ignored, all of these variables should bee added as variables in scope and let validation reports
// ambiguous reference error. So that we need to scan all variables defined at the same level first and then add names to the HashSet.
IEnumerable<ModelItem> filteredVariables = variables.Where<ModelItem>(filterDelegate);
variablesInScope.AddRange(filteredVariables);
foreach (ModelItem variable in filteredVariables)
{
variableNames.Add(((string)variable.Properties["Name"].ComputedValue));
}

currentItem = currentItem.Parent;
}
}

return variablesInScope;
}

private static ModelItemCollection GetVariableCollection(ModelItem currentItem)
{
if (null != currentItem)
{
Type elementType = currentItem.ItemType;
if (!(CodeActivityType.IsAssignableFrom(elementType) || GenericAsyncCodeActivityType.IsAssignableFrom(elementType) ||
AsyncCodeActivityType.IsAssignableFrom(elementType) || GenericAsyncCodeActivityType.IsAssignableFrom(elementType)))
{
ModelProperty variablesProperty = currentItem.Properties["Variables"];
if ((variablesProperty != null) && (variablesProperty.PropertyType == VariablesCollectionType))
{
return variablesProperty.Collection;
}
}
}

return null;
}

private static IEnumerable<ModelItem> FindActivityDelegateArguments(ModelItem currentItem)
{
List<ModelItem> delegateArguments = new List<ModelItem>();
if (currentItem.GetCurrentValue() is ActivityDelegate)
{
delegateArguments.AddRange(currentItem.Properties
.Where<ModelProperty>(p => (typeof(DelegateArgument).IsAssignableFrom(p.PropertyType) && null != p.Value))
.Select<ModelProperty, ModelItem>(p => p.Value));
}

return delegateArguments;
}
}
}

0 comments on commit cb6b5bb

Please sign in to comment.