Flurl Extension for multi-part Put - flurl

I have a Flurl implementation already working for multi-part Posts (json plus file attachment). I need to add an extension for Put that works the same way.
So far I've failed to replicate one.
My Post code...
resp = await url
.PostMultipartAsync(mp => mp
.AddString("manifest", ManifestJSON)
.AddFile("attachment", new MemoryStream(sendFile.ToArray()), sendFilename));
How would I build a comparable ".PutMultipartAsync" method to take its place?

Got it.
var content = new Flurl.Http.Content.CapturedMultipartContent();
content.AddString("manifest", ManifestJSON);
content.AddFile("attachment", new MemoryStream(sendFile.ToArray()), sendFilename, "application/zip");
resp = await url
.SendAsync(System.Net.Http.HttpMethod.Put, content);
Easy.

What you did should work great for a one-off. If you wanted to be really robust about it so it feels like first-class Flurl functionality, you could follow the Flurl.Http extensibility pattern and add these extension methods:
public static class MultipartPutExtensions
{
public static Task<HttpResponseMessage> PutMultipartAsync(this IFlurlRequest request, Action<CapturedMultipartContent> buildContent, CancellationToken cancellationToken = default(CancellationToken))
{
var cmc = new CapturedMultipartContent(request.Settings);
buildContent(cmc);
return request.SendAsync(HttpMethod.Put, cmc, cancellationToken);
}
public static Task<HttpResponseMessage> PutMultipartAsync(this Url url, Action<CapturedMultipartContent> buildContent, CancellationToken cancellationToken = default(CancellationToken))
{
return new FlurlRequest(url).PutMultipartAsync(buildContent, cancellationToken);
}
public static Task<HttpResponseMessage> PutMultipartAsync(this string url, Action<CapturedMultipartContent> buildContent, CancellationToken cancellationToken = default(CancellationToken))
{
return new FlurlRequest(url).PutMultipartAsync(buildContent, cancellationToken);
}
}
These are modeled directly after the PostMultipartAsync implementations.

Related

How do I add a sharepoint listitem with a content type using the GraphClient in an azure function

I'm trying to add a sharepoint list item with a content type using the graphclient.
I using this code:
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
ContentTypeInfo ct = new ContentTypeInfo()
{
ODataType = "microsoft.graph.contentTypeInfo",
Id = config["AuditItemContentTypeId"]
};
var listItem = new Microsoft.Graph.ListItem
{
ContentType = ct,
Fields = new FieldValueSet
{
AdditionalData = new Dictionary<string, object>()
{
{"Title", "xxx"},
{"CreationTime", auditItem.CreationTime},
{"AuditItemId", auditItem.Id},// ID is used in sp
}
}
,
};
var addedItem = await graphClient.Sites[stc.CaptureToSiteId].Lists[stc.CaptureToListId].Items
.Request()
.AddAsync(listItem);
The addadsync fails with the message
2021-05-27T21:37:04.897 [Error] Executed 'ProcessAuditItem' (Failed, Id=d846ddc1-bb9d-4082-88b6-b6b3fa26afc8, Duration=507ms)Object reference not set to an instance of an object.
Anyone have an ide what i've done wrong? Any docs on doing this with the graph client in c# (not sending raw JSON... im trying to use the classes porovided). Is ther a way to turn on some verbose logging in the graphclient?
Russell
It is difficult to tell what is wrong with your code based on the information you have provided. Assuming you have checked that stc is not null: What kind of SharePoint field is AuditItemId? Also the exact ContentTypeId could be relevant. As far as I know, there is no additional logging that you can turn on. However, in the latest versions of the Graph SDK you can customize the GraphServiceClient to implement your own logging. First, create a class based on System.Net.Http.DelegatingHandler:
public class ExtensiveLoggingDelegatingHandler : DelegatingHandler
{
private readonly IExtensiveLoggingService _extensiveLoggingService;
public ExtensiveLoggingDelegatingHandler(
IExtensiveLoggingService extensiveLoggingService)
: base()
{
_extensiveLoggingService = extensiveLoggingService;
}
public ExtensiveLoggingDelegatingHandler(
HttpMessageHandler innerHandler,
IExtensiveLoggingService extensiveLoggingService)
: base(innerHandler)
{
_extensiveLoggingService = extensiveLoggingService;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
//add custom logging logic here
await _extensiveLoggingService.DocumentMSGraphApiCall(request, response);
return response;
}
}
I am using Dependency Injection to inject custom logging into the DelegatingHandler. The logger just writes all info (request url, request header, request body, response header, ...) into a log file, if a certain log level is configured. The handler needs to be injected into the GraphServiceClient in order to be called upon request execution:
var handlers = GraphClientFactory.CreateDefaultHandlers(
someMicrosoftGraphIAuthenticationProvider);
handlers.Add(new ExtensiveLoggingDelegatingHandler( _extensiveLoggingService));
var httpClient = GraphClientFactory.Create(handlers);
var graphServiceClient = new GraphServiceClient(httpClient);

Mocking HttpMessageHandler with moq - How do I get the contents of the request?

Is there a way to get the contents of the http request before deciding what kind of response I want to send back for the test? Multiple tests will use this class and each test will have multiple http requests.
This code does not compile because the lambda is not async and there is an await in it. I'm new to async-await, so I'm not sure how to resolve this. I briefly considered having multiple TestHttpClientFactories, but that would mean duplicated code, so decided against it, if possible.
Any help is appreciated.
public class TestHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
messageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
HttpResponseMessage response = new HttpResponseMessage();
var requestMessageContent = await request.Content.ReadAsStringAsync();
// decide what to put in the response after looking at the contents of the request
return response;
})
.Verifiable();
var httpClient = new HttpClient(messageHandlerMock.Object);
return httpClient;
}
}
To take advantage of the async delegate use the Returns method instead
public class TestHttpClientFactory : IHttpClientFactory {
public HttpClient CreateClient(string name) {
var messageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
messageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns(async (HttpRequestMessage request, CancellationToken token) => {
string requestMessageContent = await request.Content.ReadAsStringAsync();
HttpResponseMessage response = new HttpResponseMessage();
//...decide what to put in the response after looking at the contents of the request
return response;
})
.Verifiable();
var httpClient = new HttpClient(messageHandlerMock.Object);
return httpClient;
}
}
Or consider creating your own handler that exposes a delegate to handle the desired behavior.
For example
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
And used in the factory like this
public class TestHttpClientFactory : IHttpClientFactory {
public HttpClient CreateClient(string name) {
var messageHandlerMock = new DelegatingHandlerStub(async (HttpRequestMessage request, CancellationToken token) => {
string requestMessageContent = await request.Content.ReadAsStringAsync();
HttpResponseMessage response = new HttpResponseMessage();
//...decide what to put in the response after looking at the contents of the request
return response;
});
var httpClient = new HttpClient(messageHandlerMock);
return httpClient;
}
}

Reading HttpRequestMessage content. DelegatingHandler vs ActionFilter

I've read in a few places that it's impossible to read the contents of the request stream in WebApi more than once, as it's forward only. This presents a problem if you're trying to implement an AOP style request logger and you also want to dump the contents of a request.
I've been playing around with it, and I've noticed that if you try to read the stream in an ActionFilter.OnActionExecutingAsync, as below, you get problems with null content in the controllers action.
public override async System.Threading.Tasks.Task OnActionExecutingAsync(System.Web.Http.Controllers.HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
var content = await actionContext.Request.Content.ReadAsStringAsync();
traceManager.TraceEvent(TraceEventType.Information, (int)EventId.RequestReceived, "{0}", content);
}
Having said that, if you implement almost the same code in an DelegatingHandler:
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var content = await request.Content.ReadAsStringAsync();
traceManager.TraceEvent(TraceEventType.Information, (int)EventId.RequestReceived, "{0}", content);
return await base.SendAsync(request, cancellationToken);
}
It works flawlessly and doesn't cause any problems with everything further down the pipeline. I understand that the handler runs much sooner than the filter, but why is it possible to do this at one stage of the pipeline but not another?

How to remove charset=utf8 from Content-Type header generated by HttpClient.PostAsJsonAsync()?

I have an issue with
HttpClient.PostAsJsonAsync()
In addition to "application/json" in the "Content-Type" header the method also adds "charset=utf-8"
so the header looks like this:
Content-Type: application/json; charset=utf-8
While ASP.NET WebAPI doesn't have any issue with this header, i've found that other WebAPIs I work against as a client don't accept request with this header, unless it's only application/json.
Is there anyway to remove the "charset=utf-8" from Content-Type when using PostAsJsonAsync(), or should I use another method?
SOLUTION:
Credits to Yishai!
using System.Net.Http.Headers;
public class NoCharSetJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
headers.ContentType.CharSet = "";
}
}
public static class HttpClientExtensions
{
public static async Task<HttpResponseMessage> PostAsJsonWithNoCharSetAsync<T>(this HttpClient client, string requestUri, T value, CancellationToken cancellationToken)
{
return await client.PostAsync(requestUri, value, new NoCharSetJsonMediaTypeFormatter(), cancellationToken);
}
public static async Task<HttpResponseMessage> PostAsJsonWithNoCharSetAsync<T>(this HttpClient client, string requestUri, T value)
{
return await client.PostAsync(requestUri, value, new NoCharSetJsonMediaTypeFormatter());
}
}
You can derive from JsonMediaTypeFormatter and override SetDefaultContentHeaders.
Call base.SetDefaultContentHeaders() and then clear headers.ContentType.CharSet
then write your own extension method based on the following code:
public static Task<HttpResponseMessage> PostAsJsonAsync<T>(this HttpClient client, string requestUri, T value, CancellationToken cancellationToken)
{
return client.PostAsync(requestUri, value,
new JsonMediaTypeFormatter(), cancellationToken);
}
In essence something like:
public static Task<HttpResponseMessage> PostAsJsonWithNoCharSetAsync<T>(this HttpClient client, string requestUri, T value, CancellatioNToken cancellationToken)
{
return client.PostAsync(requestUri, value,
new NoCharSetJsonMediaTypeFormatter(), cancellationToken);
}
For more direct control over the payload you send, you can create derived HttpContent classes instead of letting your object be passed to an ObjectContent class which then delegates streaming to a Formatter class.
A JsonContent class that supports both reading and writing looks like this,
public class JsonContent : HttpContent
{
private readonly Stream _inboundStream;
private readonly JToken _value;
public JsonContent(JToken value)
{
_value = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
public JsonContent(Stream inboundStream)
{
_inboundStream = inboundStream;
}
public async Task<JToken> ReadAsJTokenAsync()
{
return _value ?? JToken.Parse(await ReadAsStringAsync());
}
protected async override Task<Stream> CreateContentReadStreamAsync()
{
return _inboundStream;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
if (_value != null)
{
var jw = new JsonTextWriter(new StreamWriter(stream)) {Formatting = Formatting.Indented};
_value.WriteTo(jw);
jw.Flush();
} else if (_inboundStream != null)
{
return _inboundStream.CopyToAsync(stream);
}
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_inboundStream.Dispose();
}
base.Dispose(disposing);
}
}
Once you have this class you can then do,
var content = new JsonContent(myObject);
_httpClient.PostAsync(uri,content);
If you need to change any of the content Headers you can do that manually before sending the request. And if you need to mess with any of the request headers then you use the SendAsync overload,
var content = new JsonContent(myObject);
// Update Content headers here
var request = new HttpRequestMessage {RequestUri = uri, Content = content };
// Update request headers here
_httpClient.SendAsync(request);
Derived content classes are easy to create for pretty much any media type or any source of data. I've created all kinds of classes derived from HttpContent. e.g. FileContent, EmbeddedResourceContent, CSVContent, XmlContent, ImageContent, HalContent, CollectionJsonContent, HomeContent, ProblemContent.
Personally, I've found it gives me much better control over my payloads.
The easiest approach that is working for me is passing new MediaTypeHeaderValue as parameter:
using var client = new HttpClient();
var data = JsonContent.Create(new
{
data = "local"
}, new MediaTypeHeaderValue("application/json"));
var response = await client.PutAsync("api/test", data);
I like Darrel's answer more than the accepted one, but it was still too complex for me. I used this:
public class ContentTypeSpecificStringContent : StringContent
{
/// <summary>
/// Ensure content type is reset after base class mucks it up.
/// </summary>
/// <param name="content">Content to send</param>
/// <param name="encoding">Encoding to use</param>
/// <param name="contentType">Content type to use</param>
public ContentTypeSpecificStringContent(string content, Encoding encoding, string contentType)
: base(content, encoding, contentType)
{
Headers.ContentType = new MediaTypeHeaderValue(contentType);
}
}
Needless to say you could adapt it for whichever base class suits your needs. Hope that helps somebody.

Why is the body of a Web API request read once?

My goal is to authenticate Web API requests using a AuthorizationFilter or DelegatingHandler. I want to look for the client id and authentication token in a few places, including the request body. At first it seemed like this would be easy, I could do something like this
var task = _message.Content.ReadAsAsync<Credentials>();
task.Wait();
if (task.Result != null)
{
// check if credentials are valid
}
The problem is that the HttpContent can only be read once. If I do this in a Handler or a Filter then the content isn't available for me in my action method. I found a few answers here on StackOverflow, like this one: Read HttpContent in WebApi controller that explain that it is intentionally this way, but they don't say WHY. This seems like a pretty severe limitation that blocks me from using any of the cool Web API content parsing code in Filters or Handlers.
Is it a technical limitation? Is it trying to keep me from doing a VERY BAD THING(tm) that I'm not seeing?
POSTMORTEM:
I took a look at the source like Filip suggested. ReadAsStreamAsync returns the internal stream and there's nothing stopping you from calling Seek if the stream supports it. In my tests if I called ReadAsAsync then did this:
message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait();
The automatic model binding process would work fine when it hit my action method. I didn't use this though, I opted for something more direct:
var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor());
var formatters = _message.GetConfiguration().Formatters;
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType);
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials;
With an extension method (I'm in .NET 4.0 with no await keyword)
public static class TaskExtensions
{
public static T WaitFor<T>(this Task<T> task)
{
task.Wait();
if (task.IsCanceled) { throw new ApplicationException(); }
if (task.IsFaulted) { throw task.Exception; }
return task.Result;
}
}
One last catch, HttpContent has a hard-coded max buffer size:
internal const int DefaultMaxBufferSize = 65536;
So if your content is going to be bigger than that you'll need to manually call LoadIntoBufferAsync with a larger size before you try to call ReadAsByteArrayAsync.
The answer you pointed to is not entirely accurate.
You can always read as string (ReadAsStringAsync)or as byte[] (ReadAsByteArrayAsync) as they buffer the request internally.
For example the dummy handler below:
public class MyHandler : DelegatingHandler
{
protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var body = await request.Content.ReadAsStringAsync();
//deserialize from string i.e. using JSON.NET
return base.SendAsync(request, cancellationToken);
}
}
Same applies to byte[]:
public class MessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var requestMessage = await request.Content.ReadAsByteArrayAsync();
//do something with requestMessage - but you will have to deserialize from byte[]
return base.SendAsync(request, cancellationToken);
}
}
Each will not cause the posted content to be null when it reaches the controller.
I'd put the clientId and the authentication key in the header rather than content.
In which way, you can read them as many times as you like!

Resources