Remove StringValuesModel

Deserialization causes problems when a value is a single string
This commit is contained in:
Sipke Schoorstra 2021-04-02 16:52:31 +02:00
parent c6ad488a0b
commit a6d05cfce0
11 changed files with 16 additions and 178 deletions

View file

@ -70,8 +70,8 @@ namespace Elsa.Activities.Http
var model = new HttpRequestModel
{
Path = new Uri(request.Path.ToString(), UriKind.Relative),
QueryString = request.Query.ToDictionary(x => x.Key, x => new StringValuesModel(x.Value)),
Headers = request.Headers.ToDictionary(x => x.Key, x => new StringValuesModel(x.Value)),
QueryString = request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()),
Headers = request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString()),
Method = request.Method
};

View file

@ -13,6 +13,7 @@ using Elsa.Design;
using Elsa.Services;
using Elsa.Services.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using HttpRequestHeaders = Elsa.Activities.Http.Models.HttpRequestHeaders;
// ReSharper disable once CheckNamespace
@ -139,7 +140,7 @@ namespace Elsa.Activities.Http
var url = Url;
var request = new HttpRequestMessage(new HttpMethod(Method), url);
var authorizationHeaderValue = Authorization;
var requestHeaders = new HeaderDictionary(RequestHeaders);
var requestHeaders = new HeaderDictionary(RequestHeaders.ToDictionary(x => x.Key, x => new StringValues(x.Value.Split(','))));
if (methodSupportsBody)
{

View file

@ -3,7 +3,7 @@ using Microsoft.Extensions.Primitives;
namespace Elsa.Activities.Http.Models
{
public class HttpRequestHeaders : Dictionary<string, StringValues>
public class HttpRequestHeaders : Dictionary<string, string>
{
public string ContentType => this["content-type"];
}

View file

@ -7,8 +7,8 @@ namespace Elsa.Activities.Http.Models
{
public Uri Path { get; set; } = default!;
public string Method { get; set; } = default!;
public IDictionary<string, StringValuesModel> QueryString { get; set; } = default!;
public IDictionary<string, StringValuesModel> Headers { get; set; } = default!;
public IDictionary<string, string> QueryString { get; set; } = default!;
public IDictionary<string, string> Headers { get; set; } = default!;
public object? Body { get; set; }
public T GetBody<T>() => (T)Body!;
}

View file

@ -3,7 +3,7 @@ using Microsoft.Extensions.Primitives;
namespace Elsa.Activities.Http.Models
{
public class HttpResponseHeaders : Dictionary<string, StringValues>
public class HttpResponseHeaders : Dictionary<string, string>
{
}
}

View file

@ -6,7 +6,7 @@ namespace Elsa.Activities.Http.Models
public class HttpResponseModel
{
public HttpStatusCode StatusCode { get; set; }
public Dictionary<string, string[]> Headers { get; set; } = new Dictionary<string, string[]>();
public Dictionary<string, string[]> Headers { get; set; } = new();
public object? Content { get; set; }
}
}

View file

@ -1,68 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Primitives;
namespace Elsa.Activities.Http.Models
{
/// <summary>
/// Represents a collection of zero or more string values. It is used for values such as
/// HTTP header/query string values. These are often thought-of as key/value pairs but might
/// have more than one value.
/// </summary>
public class StringValuesModel : IConvertible
{
/// <summary>
/// Gets either the first value of <see cref="Values"/> or a <see langword="null"/> reference
/// if the values are null or empty.
/// </summary>
/// <returns>The first value</returns>
public string? Value => Values?.FirstOrDefault();
/// <summary>
/// Gets a collection of the string values for the current instance.
/// </summary>
/// <value>The values</value>
public string[] Values { get; set; }
/// <summary>
/// Initializes an instance of <see cref="StringValuesModel"/> with an empty collection of values.
/// </summary>
public StringValuesModel() => Values = new string[0];
/// <summary>
/// Initializes an instance of <see cref="StringValuesModel"/> from a <see cref="StringValues"/>.
/// </summary>
/// <param name="value">A model of zero or more string values.</param>
public StringValuesModel(StringValues value) => Values = value.ToArray();
/// <summary>
/// Gets a string representation of the current instance. Either null (for no, or an empty-collection of <see cref="Values"/>),
/// a single string (for a collection of one <see cref="Values"/>) or a comma-separated string of many values.
/// </summary>
/// <returns>The string representation of the <see cref="Values"/>.</returns>
public override string? ToString() => (Values?.Any() == true) ? string.Join(",", Values) : null;
#region IConvertible implementation
TypeCode IConvertible.GetTypeCode() => TypeCode.Object;
bool IConvertible.ToBoolean(IFormatProvider? provider) => Convert.ToBoolean(ToString(), provider);
byte IConvertible.ToByte(IFormatProvider? provider) => Convert.ToByte(ToString(), provider);
char IConvertible.ToChar(IFormatProvider? provider) => Convert.ToChar(ToString()!, provider);
DateTime IConvertible.ToDateTime(IFormatProvider? provider) => Convert.ToDateTime(ToString(), provider);
decimal IConvertible.ToDecimal(IFormatProvider? provider) => Convert.ToDecimal(ToString(), provider);
double IConvertible.ToDouble(IFormatProvider? provider) => Convert.ToDouble(ToString(), provider);
short IConvertible.ToInt16(IFormatProvider? provider) => Convert.ToInt16(ToString(), provider);
int IConvertible.ToInt32(IFormatProvider? provider) => Convert.ToInt32(ToString(), provider);
long IConvertible.ToInt64(IFormatProvider? provider) => Convert.ToInt64(ToString(), provider);
sbyte IConvertible.ToSByte(IFormatProvider? provider) => Convert.ToSByte(ToString()!, provider);
float IConvertible.ToSingle(IFormatProvider? provider) => Convert.ToSingle(ToString(), provider);
string IConvertible.ToString(IFormatProvider? provider) => ToString()!;
object IConvertible.ToType(Type conversionType, IFormatProvider? provider) => Convert.ChangeType(ToString()!, conversionType, provider);
ushort IConvertible.ToUInt16(IFormatProvider? provider) => Convert.ToUInt16(ToString(), provider);
uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(ToString(), provider);
ulong IConvertible.ToUInt64(IFormatProvider? provider) => Convert.ToUInt64(ToString(), provider);
#endregion
}
}

View file

@ -12,14 +12,15 @@ namespace Elsa.Activities.Http.Parsers
{
public int Priority => 0;
public string?[] SupportedContentTypes => new[] { "application/x-www-form-urlencoded" };
public async Task<object?> ParseAsync(HttpRequest request, Type? targetType = default, CancellationToken cancellationToken = default)
{
var form = await request.ReadFormAsync(cancellationToken);
return form.ToDictionary(
x => x.Key,
x => new StringValuesModel(x.Value)
);
x => x.Key,
x => x.Value.ToString())
;
}
}
}

View file

@ -31,7 +31,7 @@ namespace Elsa.Samples.Server.Host.Workflows
private void MaybeThrow(ActivityExecutionContext context)
{
var model = context.GetInput<HttpRequestModel>()!;
var fault = model.QueryString.GetItem("fault")?.Value == "true";
var fault = model.QueryString.GetItem("fault") == "true";
if (fault)
throw new Exception("This is quite a serious fault!");

View file

@ -19,7 +19,7 @@ namespace Elsa.Samples.Server.Host.Workflows
.Correlate(() => Guid.NewGuid().ToString("N"))
.WriteHttpResponse(x => x.WithStatusCode(HttpStatusCode.OK).WithContent(context => $"Tell me your name please. Use correlation ID {context.WorkflowExecutionContext.CorrelationId}").WithContentType("text/plain"))
.HttpEndpoint(x => x.WithPath("/signup").WithMethod(HttpMethod.Post.ToString()).WithReadContent())
.SetVariable("Name", context => (string) context.GetInput<HttpRequestModel>().Body)
.SetVariable("Name", context => (string) context.GetInput<HttpRequestModel>()!.Body!)
.SetName(context => context.GetVariable<string>("Name"))
.WriteHttpResponse(x => x

View file

@ -1,96 +0,0 @@
using System;
using Elsa.Activities.Http.Models;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace Elsa.Activities.Http
{
public class StringValuesModelTests
{
[Theory(DisplayName = "It should be possible to use Convert.ChangeType to convert an instance to string. This is primarily to support usage via Jint."), AutoMoqData]
public void ItShouldBePossibleToConvertAStringValuesModelToString(string[] values)
{
var sut = new StringValuesModel(new StringValues(values));
var result = Convert.ChangeType(sut, typeof(string));
Assert.Equal(sut.ToString(), result);
}
[Fact(DisplayName = "It should be possible to use Convert.ToInt32 to convert an instance to a number if the value is a valid string representation of a number. This is primarily to support usage via Jint.")]
public void ItShouldBePossibleToConvertAStringValuesModelToInt32IfValid()
{
var sut = new StringValuesModel(new StringValues("3"));
var result = Convert.ChangeType(sut, typeof(int));
Assert.Equal(3, result);
}
[Theory(DisplayName = "The Value property should return the first value if there is more than one."), AutoMoqData]
public void ValueShouldReturnFirstItemIfThereAreMoreThanOne(string firstValue, string secondValue)
{
var sut = new StringValuesModel(new StringValues(new [] { firstValue, secondValue }));
Assert.Equal(firstValue, sut.Value);
}
[Theory(DisplayName = "The Value property should always return the first value if there is only one."), AutoMoqData]
public void ValueShouldReturnFirstItemIfThereIsOne(string firstValue)
{
var sut = new StringValuesModel(new StringValues(new [] { firstValue }));
Assert.Equal(firstValue, sut.Value);
}
[Fact(DisplayName = "The Value property should return null if there are no values.")]
public void ValueShouldReturnNullIfThereAreNone()
{
var sut = new StringValuesModel(new StringValues(new string[0]));
Assert.Null(sut.Value);
}
[Theory(DisplayName = "The Values property should return a length-one collection if there is a single value."), AutoMoqData]
public void ValuesShouldReturnLengthOneCollectionIfThereIsOneValue(string firstValue)
{
var sut = new StringValuesModel(new StringValues(new [] { firstValue }));
Assert.Equal(new [] {firstValue}, sut.Values);
}
[Theory(DisplayName = "The Values property should return a length-two collection if there are two values."), AutoMoqData]
public void ValuesShouldReturnLengthTwoCollectionIfThereAreTwoValues(string firstValue, string secondValue)
{
var sut = new StringValuesModel(new StringValues(new [] { firstValue, secondValue }));
Assert.Equal(new [] {firstValue, secondValue}, sut.Values);
}
[Fact(DisplayName = "The Values property should return an empty collection if there are no values.")]
public void ValuesShouldReturnEmptyCollectionIfThereAreNone()
{
var sut = new StringValuesModel(new StringValues(new string[0]));
Assert.Empty(sut.Values);
}
[Fact(DisplayName = "The ToString method should return null if there are no values")]
public void ToStringShouldReturnNullIfNotValues()
{
var sut = new StringValuesModel(new StringValues(new string[0]));
Assert.Null(sut.ToString());
}
[Theory(DisplayName = "The ToString method should return the first value if there is only one"), AutoMoqData]
public void ToStringShouldReturnFirstValueIfThereIsOnlyOne(string firstValue)
{
var sut = new StringValuesModel(new StringValues(new [] { firstValue }));
Assert.Equal(firstValue, sut.ToString());
}
[Theory(DisplayName = "The ToString method should return comma-separated values if there are more than one"), AutoMoqData]
public void ToStringShouldReturnCommaSeparatedValuesIfThereIsMoreThanOne(string firstValue, string secondValue)
{
var sut = new StringValuesModel(new StringValues(new [] { firstValue, secondValue }));
Assert.Equal($"{firstValue},{secondValue}", sut.ToString());
}
[Fact(DisplayName = "The parameterless constructor should initialize Values to an empty collection")]
public void ParameterlessCtorShouldInitializeWithEmptyCollection()
{
var sut = new StringValuesModel();
Assert.Empty(sut.Values);
}
}
}