I’m trying to develop an activity using the Activity Creator for Visual Studio, i’m using Visual Studio 17.3.7 which is the suggested version from the forums, and it works, but it freezes my studio for about 30-60 seconds whenever i load it and drag it into my workflow file, and then it works fine, and debug works fine. I’m developing these activities for SharePoint due to the standard UiPath 365 activities doesn’t seem to work in local sharepoint tenants with more than 200+ files/folders, even when setting the “first” to 9999 to ignore the 200 limit.
I have followed all the standard practices and created it as suggested, building/publishing it and it works fine but it just doesn’t really add a good experience for other people who might need to use it since it freezes the ui for the first 60 seconds since dragging the activities to the workflow file. I have since added icons, but it froze even when just using the standard setup. I suspect the Scope might be the culprit.
Scope code:
using System;
using System.Activities;
using System.Threading;
using System.Threading.Tasks;
using System.Security;
using System.Activities.Statements;
using System.ComponentModel;
using COMPANY.SharePointActivities.Activities.Properties;
using UiPath.Shared.Activities;
using UiPath.Shared.Activities.Localization;
using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;
using System.Collections.Generic;
namespace COMPANY.SharePointActivities.Activities
{
[LocalizedDisplayName(nameof(Resources.SharePointScope_DisplayName))]
[LocalizedDescription(nameof(Resources.SharePointScope_Description))]
public class SharePointScope : ContinuableAsyncNativeActivity
{
#region Properties
[Browsable(false)]
public ActivityAction<IObjectContainer> Body { get; set; }
/// <summary>
/// If set, continue executing the remaining activities even if the current activity has failed.
/// </summary>
[LocalizedCategory(nameof(Resources.Common_Category))]
[LocalizedDisplayName(nameof(Resources.ContinueOnError_DisplayName))]
[LocalizedDescription(nameof(Resources.ContinueOnError_Description))]
public override InArgument<bool> ContinueOnError { get; set; }
[LocalizedDisplayName(nameof(Resources.SharePointScope_ClientId_DisplayName))]
[LocalizedDescription(nameof(Resources.SharePointScope_ClientId_Description))]
[LocalizedCategory(nameof(Resources.Input_Category))]
public InArgument<string> ClientId { get; set; }
[LocalizedDisplayName(nameof(Resources.SharePointScope_TenantId_DisplayName))]
[LocalizedDescription(nameof(Resources.SharePointScope_TenantId_Description))]
[LocalizedCategory(nameof(Resources.Input_Category))]
public InArgument<string> TenantId { get; set; }
[LocalizedDisplayName(nameof(Resources.SharePointScope_Username_DisplayName))]
[LocalizedDescription(nameof(Resources.SharePointScope_Username_Description))]
[LocalizedCategory(nameof(Resources.Input_Category))]
public InArgument<string> Username { get; set; }
[LocalizedDisplayName(nameof(Resources.SharePointScope_SecurePassword_DisplayName))]
[LocalizedDescription(nameof(Resources.SharePointScope_SecurePassword_Description))]
[LocalizedCategory(nameof(Resources.Input_Category))]
public InArgument<SecureString> SecurePassword { get; set; }
[LocalizedDisplayName(nameof(Resources.SharePointScope_SharePointUrl_DisplayName))]
[LocalizedDescription(nameof(Resources.SharePointScope_SharePointUrl_Description))]
[LocalizedCategory(nameof(Resources.Input_Category))]
public InArgument<string> SharePointUrl { get; set; }
// A tag used to identify the scope in the activity context
internal static string ParentContainerPropertyTag => "ScopeActivity";
// Object Container: Add strongly-typed objects here and they will be available in the scope's child activities.
private readonly IObjectContainer _objectContainer;
#endregion
#region Constructors
public SharePointScope(IObjectContainer objectContainer)
{
_objectContainer = objectContainer;
Body = new ActivityAction<IObjectContainer>
{
Argument = new DelegateInArgument<IObjectContainer> (ParentContainerPropertyTag),
Handler = new Sequence { DisplayName = Resources.Do }
};
}
public SharePointScope() : this(new ObjectContainer())
{
}
#endregion
#region Protected Methods
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
if (ClientId == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(ClientId)));
if (TenantId == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(TenantId)));
if (Username == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(Username)));
if (SecurePassword == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(SecurePassword)));
if (SharePointUrl == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(SharePointUrl)));
base.CacheMetadata(metadata);
}
protected override async Task<Action<NativeActivityContext>> ExecuteAsync(NativeActivityContext context, CancellationToken cancellationToken)
{
// Inputs
var clientid = ClientId.Get(context);
var tenantid = TenantId.Get(context);
var username = Username.Get(context);
var securepassword = SecurePassword.Get(context);
var sharepointurl = SharePointUrl.Get(context);
var scopes = new[] { "https://graph.microsoft.com/.default" };
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
string SiteURL = sharepointurl;
// Check if the URL starts with "https://"
if (SiteURL.StartsWith("https://"))
{
SiteURL = SiteURL.Substring(8); // Remove "https://"
}
// Replace ".sharepoint.com" with ".sharepoint.com:"
SiteURL = SiteURL.Replace(".sharepoint.com", ".sharepoint.com:");
var password = new System.Net.NetworkCredential(string.Empty, securepassword).Password;
// https://learn.microsoft.com/dotnet/api/azure.identity.usernamepasswordcredential
var userNamePasswordCredential = new UsernamePasswordCredential(
username, password, tenantid, clientid, options);
var accessToken = await userNamePasswordCredential.GetTokenAsync(new TokenRequestContext(scopes) { });
var graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
var dictionaryContainer = new Dictionary<string, object>();
dictionaryContainer["clientid"] = clientid;
dictionaryContainer["tenantid"] = tenantid;
dictionaryContainer["username"] = username;
dictionaryContainer["securepassword"] = securepassword;
dictionaryContainer["sharepointurl"] = SiteURL;
dictionaryContainer["graphclient"] = graphClient;
_objectContainer.Add<Dictionary<string, object>>(dictionaryContainer);
return (ctx) => {
// Schedule child activities
if (Body != null)
ctx.ScheduleAction<IObjectContainer>(Body, _objectContainer, OnCompleted, OnFaulted);
// Outputs
};
}
#endregion
#region Events
private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
{
faultContext.CancelChildren();
Cleanup();
}
private void OnCompleted(NativeActivityContext context, ActivityInstance completedInstance)
{
Cleanup();
}
#endregion
#region Helpers
private void Cleanup()
{
var disposableObjects = _objectContainer.Where(o => o is IDisposable);
foreach (var obj in disposableObjects)
{
if (obj is IDisposable dispObject)
dispObject.Dispose();
}
_objectContainer.Clear();
}
#endregion
}
}
Scope designer:
<sap:ActivityDesigner x:Class="COMPANY.SharePointActivities.Activities.Design.Designers.SharePointScopeDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sa="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:p="clr-namespace:COMPANY.SharePointActivities.Activities.Design.Properties"
xmlns:sharedres="clr-namespace:UiPath.Shared.Localization"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:converters="clr-namespace:UiPath.Shared.Activities.Design.Converters"
xmlns:uip="clr-namespace:UiPath.Shared.Activities.Design.Controls" >
<sap:ActivityDesigner.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..\Themes\Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:ActivityIconConverter x:Key="ActivityIconConverter" />
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<sap:ActivityDesigner.Icon>
<DrawingBrush Stretch="Uniform" Drawing="{Binding Path=ModelItem, Converter={StaticResource ActivityIconConverter}, ConverterParameter=pack://application:\,\,\,/COMMPANY.SharePointActivities.Activities.Design;component/themes/icons.xaml}" />
</sap:ActivityDesigner.Icon>
<uip:ActivityDecoratorControl Style="{StaticResource ActivityDecoratorStyle}">
<DockPanel LastChildFill="True">
<sap:WorkflowItemPresenter x:Uid="sad:WorkflowItemPresenter_1"
AutomationProperties.AutomationId="Activity"
DockPanel.Dock="Bottom"
MinWidth="400"
Margin="0,10,0,0"
Item="{Binding Path=ModelItem.Body.Handler, Mode=TwoWay}"
AllowedItemType="{x:Type sa:Activity}"
HintText="{x:Static p:Resources.DropActivityHere}" />
</DockPanel>
</uip:ActivityDecoratorControl>
</sap:ActivityDesigner>
