multipart/form-data support
This commit is contained in:
parent
e49ee1551d
commit
b501dc5e76
59
Network/MultipartFormData.cs
Normal file
59
Network/MultipartFormData.cs
Normal file
@ -0,0 +1,59 @@
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
public abstract class MultipartFormData
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public MultipartFormData(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public static FieldFormData From(string name, string value)
|
||||
{
|
||||
return new(name, value);
|
||||
}
|
||||
|
||||
public static ObjectFormData<T> From<T>(string name, T value)
|
||||
{
|
||||
return new(name, value);
|
||||
}
|
||||
|
||||
public static FileFormData From(string name, Stream stream, string fileName, string mimeType = NSMultipartFormData.DefaultMimeType)
|
||||
{
|
||||
return new(name, stream, fileName, mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
public class FieldFormData : MultipartFormData
|
||||
{
|
||||
public string? Value { get; }
|
||||
|
||||
public FieldFormData(string name, string? value) : base(name)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class ObjectFormData<T> : FieldFormData
|
||||
{
|
||||
public ObjectFormData(string name, T value) : base(name, value?.ToString())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class FileFormData : MultipartFormData
|
||||
{
|
||||
public Stream Stream { get; }
|
||||
|
||||
public string FileName { get; }
|
||||
|
||||
public string MimeType { get; }
|
||||
|
||||
public FileFormData(string name, Stream stream, string fileName, string mimeType = NSMultipartFormData.DefaultMimeType) : base(name)
|
||||
{
|
||||
Stream = stream;
|
||||
FileName = fileName;
|
||||
MimeType = mimeType;
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0-ios</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0-ios</TargetFrameworks>
|
||||
|
||||
<RootNamespace>Blahblah.Library.Network</RootNamespace>
|
||||
<UseMaui>true</UseMaui>
|
||||
<Nullable>enable</Nullable>
|
||||
@ -12,4 +13,9 @@
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.0-preview.6.8686" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.0-preview.6.8686" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -8,8 +8,12 @@ public partial class NetworkHelper
|
||||
public const string AcceptJpegImage = "image/jpeg,image/*,*/*;q=0.8";
|
||||
public const string AcceptJson = "application/json";
|
||||
|
||||
public const string ContentJson = "application/json";
|
||||
public const string ContentFormData = "multipart/form-data";
|
||||
|
||||
const string AcceptHeader = "Accept";
|
||||
const string ReferrerHeader = "Referer";
|
||||
const string ContentTypeHeader = "Content-Type";
|
||||
|
||||
const int Timeout = 30;
|
||||
const int ImageTimeout = 60;
|
||||
|
@ -10,6 +10,20 @@ partial class NetworkHelper : NSObject, INSUrlSessionDataDelegate
|
||||
return (int)response.StatusCode is >= 200 and < 300;
|
||||
}
|
||||
|
||||
static NSMutableUrlRequest CreateUrlRequest(string url, string accept, string? referrer = null)
|
||||
{
|
||||
var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url));
|
||||
var request = new NSMutableUrlRequest(uri)
|
||||
{
|
||||
[AcceptHeader] = accept
|
||||
};
|
||||
if (!string.IsNullOrEmpty(referrer))
|
||||
{
|
||||
request[ReferrerHeader] = referrer;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
NSUrlSession? urlSession;
|
||||
|
||||
readonly Dictionary<NSUrlSessionTask, NetworkTask> tasks = new();
|
||||
@ -71,12 +85,7 @@ partial class NetworkHelper : NSObject, INSUrlSessionDataDelegate
|
||||
|
||||
public partial Task<NetworkResult<string>> GetContentAsync(string url, StringHandler? process, CancellationToken token)
|
||||
{
|
||||
var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url));
|
||||
var request = new NSMutableUrlRequest(uri)
|
||||
{
|
||||
[AcceptHeader] = Accept ?? AcceptHttp,
|
||||
[ReferrerHeader] = Referrer
|
||||
};
|
||||
var request = CreateUrlRequest(url, Accept ?? AcceptHttp, Referrer);
|
||||
|
||||
var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null");
|
||||
var taskSource = new TaskCompletionSource<NetworkResult<string>>();
|
||||
@ -94,12 +103,7 @@ partial class NetworkHelper : NSObject, INSUrlSessionDataDelegate
|
||||
|
||||
public Task<NetworkResult<bool>> GetImageAsync(string url, string filePath, StepHandler<CGImage>? step = null, CancellationToken token = default)
|
||||
{
|
||||
var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url));
|
||||
var request = new NSMutableUrlRequest(uri)
|
||||
{
|
||||
[AcceptHeader] = Accept ?? AcceptImage,
|
||||
[ReferrerHeader] = Referrer
|
||||
};
|
||||
var request = CreateUrlRequest(url, Accept ?? AcceptImage, Referrer);
|
||||
|
||||
var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null");
|
||||
var taskSource = new TaskCompletionSource<NetworkResult<bool>>();
|
||||
@ -115,15 +119,44 @@ partial class NetworkHelper : NSObject, INSUrlSessionDataDelegate
|
||||
return taskSource.Task;
|
||||
}
|
||||
|
||||
public Task<NetworkResult<string>> PostAsync(string url, Action<NSMutableUrlRequest>? prepare = null, CancellationToken token = default)
|
||||
public Task<NetworkResult<string>> PostAsync(string url, string data, string contentType = ContentJson, CancellationToken token = default)
|
||||
{
|
||||
return RequestAsync(url, request =>
|
||||
{
|
||||
request[ContentTypeHeader] = contentType;
|
||||
request.Body = NSData.FromString(data, NSStringEncoding.UTF8);
|
||||
}, token);
|
||||
}
|
||||
|
||||
public Task<NetworkResult<string>> PostAsync(string url, MultipartFormData[] forms, CancellationToken token = default)
|
||||
{
|
||||
return RequestAsync(url, request =>
|
||||
{
|
||||
var body = new NSMultipartFormData();
|
||||
request[ContentTypeHeader] = body.ContentType;
|
||||
foreach (var form in forms)
|
||||
{
|
||||
if (form is FieldFormData field)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(field.Value))
|
||||
{
|
||||
body.AddTextField(field.Name, field.Value);
|
||||
}
|
||||
}
|
||||
else if (form is FileFormData file)
|
||||
{
|
||||
body.AddDataField(file.Name, file.Stream, file.FileName, file.MimeType);
|
||||
}
|
||||
}
|
||||
body.AddEnd();
|
||||
request.Body = body.Data;
|
||||
}, token);
|
||||
}
|
||||
|
||||
public Task<NetworkResult<string>> RequestAsync(string url, Action<NSMutableUrlRequest>? prepare = null, CancellationToken token = default)
|
||||
{
|
||||
var uri = NSUrl.FromString(url) ?? throw new ArgumentNullException(nameof(url));
|
||||
var request = new NSMutableUrlRequest(uri)
|
||||
{
|
||||
HttpMethod = "POST",
|
||||
[AcceptHeader] = Accept ?? AcceptImage,
|
||||
[ReferrerHeader] = Referrer
|
||||
};
|
||||
var request = CreateUrlRequest(url, Accept ?? AcceptJson, Referrer);
|
||||
request.HttpMethod = "POST";
|
||||
prepare?.Invoke(request);
|
||||
|
||||
var task = (urlSession?.CreateDataTask(request)) ?? throw new NullReferenceException("Url request is null");
|
||||
|
66
Network/Platforms/iOS/Structs/NSMultipartFormData.cs
Normal file
66
Network/Platforms/iOS/Structs/NSMultipartFormData.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Foundation;
|
||||
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
class NSMultipartFormData
|
||||
{
|
||||
public const string DefaultMimeType = "application/octet-stream";
|
||||
|
||||
public NSMutableData Data => boundaryData;
|
||||
|
||||
readonly string boundary;
|
||||
readonly NSMutableData boundaryData;
|
||||
|
||||
public string ContentType => $"{NetworkHelper.ContentFormData}; boundary={boundary}";
|
||||
|
||||
public NSMultipartFormData()
|
||||
{
|
||||
var data = new byte[12];
|
||||
for (var i = 0; i < 12; i++)
|
||||
{
|
||||
data[i] = (byte)(Random.Shared.Next(0x30, 0x3a));
|
||||
}
|
||||
var id = Convert.ToBase64String(data);
|
||||
boundary = $"----WebKitFormBoundary{id}";
|
||||
boundaryData = new NSMutableData();
|
||||
}
|
||||
|
||||
public void AddTextField(string name, string value)
|
||||
{
|
||||
// Content-Type: text/plain; charset=ISO-8859-1
|
||||
// Content-Transfer-Encoding: 8bit
|
||||
|
||||
var field = $"--{boundary}\r\n" +
|
||||
$"Content-Disposition: form-data; name=\"{name}\"\r\n\r\n" +
|
||||
$"{value}\r\n";
|
||||
boundaryData.AppendData(NSData.FromString(field, NSStringEncoding.UTF8));
|
||||
}
|
||||
|
||||
public void AddDataField(string name, Stream data, string? fileName = null, string mimeType = DefaultMimeType)
|
||||
{
|
||||
if (fileName == null)
|
||||
{
|
||||
fileName = "";
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(fileName))
|
||||
{
|
||||
fileName = $"; filename=\"{fileName}\"";
|
||||
}
|
||||
var field = $"--{boundary}\r\n" +
|
||||
$"Content-Disposition: form-data; name=\"{name}\"{fileName}\r\n" +
|
||||
$"Content-Type: {mimeType}\r\n\r\n";
|
||||
boundaryData.AppendData(NSData.FromString(field, NSStringEncoding.UTF8));
|
||||
|
||||
var stream = NSData.FromStream(data);
|
||||
if (stream != null)
|
||||
{
|
||||
boundaryData.AppendData(stream);
|
||||
}
|
||||
boundaryData.AppendData(NSData.FromString("\r\n"));
|
||||
}
|
||||
|
||||
public void AddEnd()
|
||||
{
|
||||
boundaryData.AppendData(NSData.FromString($"--{boundary}--"));
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
public abstract class ContentTask<T, TResult> : NetworkTask
|
||||
abstract class ContentTask<T, TResult> : NetworkTask
|
||||
{
|
||||
public StepHandler<T>? Step { get; set; }
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
public class DownloadTask<T> : ContentTask<T, bool>
|
||||
class DownloadTask<T> : ContentTask<T, bool>
|
||||
{
|
||||
public string FilePath { get; }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
public class FileTask : DownloadTask<float>
|
||||
class FileTask : DownloadTask<float>
|
||||
{
|
||||
public FileTask(string url, string filePath, TaskCompletionSource<NetworkResult<bool>> source, CancellationToken token) : base(url, filePath, source, token)
|
||||
{
|
||||
|
@ -3,7 +3,7 @@ using ImageIO;
|
||||
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
public class ImageTask : DownloadTask<CGImage>
|
||||
class ImageTask : DownloadTask<CGImage>
|
||||
{
|
||||
CGImageSource? imageSource;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Blahblah.Library.Network;
|
||||
|
||||
public class StringTask : ContentTask<string, string>
|
||||
class StringTask : ContentTask<string, string>
|
||||
{
|
||||
public StringHandler? Process { get; set; }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user