Skip to content

Instantly share code, notes, and snippets.

@hetfirma
Forked from alirobe/PostToUrlAsJson.cs
Last active October 21, 2024 09:58
Show Gist options
  • Save hetfirma/b0da14502f0cbf1e3d4f99707a45da9d to your computer and use it in GitHub Desktop.
Save hetfirma/b0da14502f0cbf1e3d4f99707a45da9d to your computer and use it in GitHub Desktop.
Umbraco Forms Workflow - POST to URL as JSON (with optional Bearer Access Token). Just place this file anywhere in your Umbraco+Forms project, and the dependency injection will pick it up. This will allow you to connect to Microsoft Flow or Zapier or any integration web service, from which you can send to Salesforce/Dynamics/etc.
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Dynamic;
using System.Net;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Web.Common;
using Umbraco.Forms.Core;
using Umbraco.Forms.Core.Attributes;
using Umbraco.Forms.Core.Enums;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Persistence.Dtos;
using Umbraco.Forms.Core.Providers.Models;
using static Umbraco.Cms.Core.Constants;
using System.Text.Json;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Umbraco.Site.Forms.Workflow
{
public class PostToUrlAsJson : Umbraco.Forms.Core.WorkflowType
{
private readonly ILogger<PostToUrlAsJson> _logger;
private readonly IUmbracoHelperAccessor _umbracoHelperAccessor;
private readonly IPublishedUrlProvider _publishedUrlProvider;
[Setting("Url", Description = "Enter the url to post to", View = "TextField")]
public virtual string Url { get; set; }
[Setting("Bearer Token", Description = "Optional: enter a bearer token for API access (don't include the word 'Bearer')", View = "TextField")]
public virtual string? BearerToken { get; set; }
[Setting("Method", Description = "POST or GET", PreValues = "POST,PUT", View = "Dropdownlist", DisplayOrder = 20)]
public string Method { get; set; } = "POST";
[Setting("Standard Fields", Description = "Map any standard form information to send.", View = "StandardFieldMapper", SupportsPlaceholders = true, DisplayOrder = 30)]
public string StandardFields { get; set; } = string.Empty;
[Setting("Fields", Description = "Map the needed fields", View = "FieldMapper", SupportsPlaceholders = true, DisplayOrder = 40)]
public string Fields { get; set; } = string.Empty;
[Setting("DoNotAwait", Description = "Run the task in the background, without waiting for the result", View = "Checkbox", DisplayOrder = 6)]
public virtual string? DoNotAwait { get; set; } = "False";
public PostToUrlAsJson(ILogger<PostToUrlAsJson> logger, IUmbracoHelperAccessor umbracoHelperAccessor, IPublishedUrlProvider publishedUrlProvider)
{
_logger = logger;
Url = string.Empty;
Id = new Guid("eb5d0597-1111-43c4-9437-407cba35dfa6");
Name = "Post as json";
Description = "Sends the form as json";
Icon = "icon-terminal";
Group = "Services";
_umbracoHelperAccessor = umbracoHelperAccessor;
_publishedUrlProvider = publishedUrlProvider;
}
public override List<Exception> ValidateSettings()
{
List<Exception> exceptions = new List<Exception>();
if (string.IsNullOrEmpty(Url))
{
exceptions.Add(new Exception("'Url' setting has not been set"));
}
return exceptions;
}
/// <summary>
/// ExecuteAsync
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<WorkflowExecutionStatus> ExecuteAsync(WorkflowExecutionContext context)
{
try
{
// Determine if the task should be awaited or run as fire-and-forget
if (DoNotAwait == "True")
{
// Run task asynchronously in the background, handle exceptions inside the task
_ = Task.Run(async () =>
{
try
{
await PostFormAsJson(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending Record ID (Fire-and-forget): Record ID '{RecordId}', Form '{FormName}', Form ID '{FormId}', URL '{Url}'",
context.Record.UniqueId, context.Form.Name, context.Form.Id, Url);
}
});
}
else
{
await PostFormAsJson(context);
}
return WorkflowExecutionStatus.Completed;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending Record ID '{RecordId}' from Form '{FormName}' (ID: '{FormId}') to URL '{Url}' via POST",
context.Record.UniqueId, context.Form.Name, context.Form.Id, Url);
return WorkflowExecutionStatus.Failed;
}
}
// Method to post the form data to the specified URL
public async Task<WorkflowExecutionStatus> PostFormAsJson(WorkflowExecutionContext context)
{
try
{
var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var client = new HttpClient(handler)
{
BaseAddress = new Uri(Url)
};
if (!string.IsNullOrWhiteSpace(BearerToken))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", BearerToken);
}
var options = new JsonSerializerOptions
{
// needed to convert to bool
PropertyNameCaseInsensitive = true
};
NameValueCollection nameValueCollection = new NameValueCollection();
var mappedFields = JsonSerializer.Deserialize<List<FieldMapping>>(this.Fields, options);
List<StandardFieldMapping> standardFieldMappings = new();
if (!string.IsNullOrEmpty(this.StandardFields))
{
IEnumerable<StandardFieldMapping> source = JsonSerializer.Deserialize<List<StandardFieldMapping>>(this.StandardFields, options);
if (source != null)
standardFieldMappings = source.Where(x => x.Include).ToList<StandardFieldMapping>();
}
this.AddDefaultFieldMappings(nameValueCollection, standardFieldMappings, context);
this.AddFieldMappings(nameValueCollection, mappedFields, context.Record);
if (nameValueCollection == null)
{
return WorkflowExecutionStatus.Completed;
}
var values = new Dictionary<string, string>();
foreach (string allKey in nameValueCollection.AllKeys)
{
values.Add(allKey, nameValueCollection[allKey]);
}
using var httpClient = new HttpClient(handler);
var jsonContent = new StringContent(JsonSerializer.Serialize(values), Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(Url, jsonContent);
if (response.IsSuccessStatusCode)
{
_logger.LogInformation(response.ToString());
}
else
{
_logger.LogWarning(response.ToString());
}
}
catch (Exception ex)
{
string err = String.Format("There was a problem sending the Record with unique id '{0}' from the Form '{1}' with id '{2}' to the URL Endpoint '{3}' with the method 'POST'", context.Record.UniqueId, context.Form.Name, context.Form.Id, Url);
_logger.LogError(err, ex);
}
return WorkflowExecutionStatus.Completed;
}
// Method to add field mappings to the name value collection
private void AddFieldMappings(NameValueCollection values, List<FieldMapping> mappings, Record record)
{
if (mappings.Any<FieldMapping>())
{
foreach (FieldMapping mapping in mappings)
{
string alias = mapping.Alias;
if (!string.IsNullOrEmpty(mapping.StaticValue))
{
string staticValue = mapping.StaticValue;
values.Add(alias, staticValue);
}
else if (!string.IsNullOrEmpty(mapping.Value))
{
RecordField recordField = record.GetRecordField(new Guid(mapping.Value));
if (recordField != null)
{
string str = recordField.ValuesAsString(false);
values.Add(alias, str);
}
else
this._logger.LogWarning("Workflow {WorkflowName}: The field mapping with alias, {FieldMappingAlias}, did not match any record fields. This is probably caused by the record field being marked as sensitive and the workflow has been set not to include sensitive data", (object)this.Workflow?.Name, (object)mapping.Alias);
}
else
values.Add(alias, string.Empty);
}
}
else
{
foreach (RecordField recordField in record.RecordFields.Values)
{
if (recordField.Field != null)
values.Add(recordField.Field.Caption.Replace(" ", string.Empty), recordField.ValuesAsString());
}
}
}
// Method to add default field mappings to the name value collection
private void AddDefaultFieldMappings(NameValueCollection values, List<StandardFieldMapping> standardFieldMappings, WorkflowExecutionContext context)
{
foreach (StandardFieldMapping defaultMapping in standardFieldMappings)
{
switch (defaultMapping.Field)
{
case StandardField.FormId:
values.Add(defaultMapping.KeyName, context.Form.Id.ToString());
continue;
case StandardField.FormName:
values.Add(defaultMapping.KeyName, context.Form.Name);
continue;
case StandardField.PageUrl:
if (_umbracoHelperAccessor.TryGetUmbracoHelper(out var umbracoHelper))
{
// stupid code
IPublishedContent ipublishedContent = umbracoHelper.Content(context.Record.UmbracoPageId);
if (ipublishedContent != null)
{
string str = _publishedUrlProvider.GetUrl(context.Record.UmbracoPageId, UrlMode.Absolute);
values.Add(defaultMapping.KeyName, str);
continue;
}
continue;
}
continue;
case StandardField.SubmissionDate:
values.Add(defaultMapping.KeyName, context.Record.Created.ToString());
continue;
default:
DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(62, 1);
interpolatedStringHandler.AppendLiteral("The field '");
interpolatedStringHandler.AppendFormatted<StandardField>(defaultMapping.Field);
interpolatedStringHandler.AppendLiteral("' is not supported for including in the collection.");
throw new InvalidOperationException(interpolatedStringHandler.ToStringAndClear());
}
}
}
[DataContract(Name = "standardFieldMapping")]
private class StandardFieldMapping
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public StandardField Field { get; set; }
[JsonPropertyName("include")]
public bool Include { get; set; }
[JsonPropertyName("keyName")]
public string KeyName { get; set; } = string.Empty;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment