I'm new to .NET and I'm making a web api with asp.net. I'm trying to post a file to AWS S3 using the AWS .Net SDK.
It works but not if I try to read the fileName and send that as the key. It only works if I hardcode the key to a string (whereas I always want the key to be the same as the filename of the uploaded file).
When I try to read the filename from the file the error I'm getting is "Your socket connection to the server was not read from or written to within the timeout period. Idle connections will be closed."
So far I'm just using Postman to POST the files. I set the "Content-Type" header to multipart/form-data but exactly the same thing happens if I set it to application/x-www-form-urlencoded.
This is my UploadController:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using BlogApi.Models;
using System.Web;
using awsTestUpload;
using Amazon.S3.Model;
using Microsoft.AspNetCore.Http;
namespace BlogApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class S3UploadController : ControllerBase
{
public S3UploadController()
{
}
[HttpGet]
public ListObjectsResponse GetAll()
{
var uploader = new AmazonUploader();
return uploader.ListingObjectsAsync().Result;
}
[HttpPost]
public PutObjectResponse MyFileUpload()
{
var request = HttpContext.Request;
var fileStream = request.Body;
var contentLength = request.ContentLength;
string filePath = request.Form.Files.First().FileName;
var length = contentLength.HasValue ? (long)contentLength : 0;
var uploader = new AmazonUploader();
return uploader.sendMyFileToS3(fileStream, filePath, length).Result;
}
}
}
and the Uploader class looks like this:
using System;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using System.IO;
using Amazon;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Microsoft.Extensions.Configuration;
namespace awsTestUpload
{
public class AmazonUploader
{
public AmazonUploader() {
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
client = new AmazonS3Client(Configuration["aws:AWS_KEY"],
Configuration["aws:AWS_SECRET"], bucketRegion);
}
private IAmazonS3 client;
public static IConfiguration Configuration { get; set; }
private const string bucketName = "my-bucket-name";
private static readonly RegionEndpoint bucketRegion = RegionEndpoint.EUWest2;
public async Task<ListObjectsResponse> ListingObjectsAsync()
{
ListObjectsRequest request = new ListObjectsRequest
{
BucketName = bucketName
};
return await client.ListObjectsAsync(request);
}
public async Task<PutObjectResponse> sendMyFileToS3(System.IO.Stream inputStream, string fileNameInS3, long contentLength = 0)
{
PutObjectRequest request = new PutObjectRequest
{
BucketName = bucketName,
Key = fileNameInS3,
InputStream = inputStream
};
request.Headers.ContentLength = contentLength;
return await client.PutObjectAsync(request);
}
}
}
When I debug and set a breakpoint, the value of filePath is a string matching the file name ( as I expect) but the upload is timing out. if I just set filePath to be a hardcoded string (ie replace string filePath = request.Form.Files.First().FileName; with string filePath = "foo.png";) it works fine.
Can anyone see why there's a difference?
Related
I am currently stuck with reading form data. Below is my controller code.
using System.Net;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using Rehub.Models;
using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
namespace Rehub_v1._0.Areas.Admin.Controllers
{
public class TestController : Controller
{
private IHostingEnvironment Environment;
public TestController()
{
}
[HttpPost]
public async Task<HttpResponseMessage> Post()
{
string path = Path.Combine(this.Environment.WebRootPath, "~/App_Data");
var provider = new MultipartFormDataStreamProvider(path);
await Request.Content.ReadAsMultipartAsync(provider);
var email = new SendGridEmail
{
Dkim = provider.FormData.GetValues("dkim").FirstOrDefault(),
To = provider.FormData.GetValues("to").FirstOrDefault(),
Html = provider.FormData.GetValues("html").FirstOrDefault(),
From = provider.FormData.GetValues("from").FirstOrDefault(),
Text = provider.FormData.GetValues("text").FirstOrDefault(),
SenderIp = provider.FormData.GetValues("sender_ip").FirstOrDefault(),
Envelope = provider.FormData.GetValues("envelope").FirstOrDefault(),
Attachments = int.Parse(provider.FormData.GetValues("attachments").FirstOrDefault()),
Subject = provider.FormData.GetValues("subject").FirstOrDefault(),
Charsets = provider.FormData.GetValues("charsets").FirstOrDefault(),
Spf = provider.FormData.GetValues("spf").FirstOrDefault()
};
// The email is now stored in the email variable
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
}
I get the error on await Request.Content.ReadAsMultipartAsync(provider):
"CS1061 C# ‘HttpRequest’ does not contain a definition for ‘Content’ and no accessible extension method ‘Content’ accepting a first argument of type ‘HttpRequest’ could be found"
In a simple controller action in ASP.NET Core 3.1 that accepts multipart/form-data parameters:
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace WebApplication
{
public class Controller : ControllerBase
{
[HttpPost("length")]
public IActionResult Post([FromForm] Input input)
{
if (!ModelState.IsValid)
return new BadRequestObjectResult(ModelState);
return Ok(input.File.Length);
}
public class Input
{
[Required]
public IFormFile File { get; set; }
}
}
}
When I send a multipart/form-data with a File with no filename (which is acceptable under RFC 7578) it is not recognized as an IFormFile. For example:
using System;
using System.IO;
using System.Threading.Tasks;
using System.Net.Http;
namespace ConsoleApp4
{
class Program
{
static async Task Main(string[] args)
{
await Test(withFilename: true);
await Test(withFilename: false);
}
static async Task Test(bool withFilename)
{
HttpClient client = new HttpClient();
var fileContent = new StreamContent(new FileStream("/Users/cucho/Desktop/36KB.pdf", FileMode.Open, FileAccess.Read));
var message = new HttpRequestMessage(
HttpMethod.Post,
"http://localhost:5000/length"
);
var content = new MultipartFormDataContent();
if (withFilename)
{
content.Add(fileContent, "File", "36KB.pdf");
}
else
{
content.Add(fileContent, "File");
}
message.Content = content;
var response1 = await client.SendAsync(message);
var withString = withFilename ? "With" : "Without";
Console.WriteLine($"{withString} filename: {(int)response1.StatusCode}");
}
}
}
results in:
With filename: 200
Without filename: 400
How can I bind that File without filename to the IFormFile object?
Edit:
I found in the ContentDispositionHeaderValueIdentityExtensions the following method:
public static class ContentDispositionHeaderValueIdentityExtensions
{
/// <summary>
/// Checks if the content disposition header is a file disposition
/// </summary>
/// <param name="header">The header to check</param>
/// <returns>True if the header is file disposition, false otherwise</returns>
public static bool IsFileDisposition(this ContentDispositionHeaderValue header)
{
if (header == null)
{
throw new ArgumentNullException(nameof(header));
}
return header.DispositionType.Equals("form-data")
&& (!StringSegment.IsNullOrEmpty(header.FileName) || !StringSegment.IsNullOrEmpty(header.FileNameStar));
}
And a similar code in FormFeature, which I think is how it decides whether the part is a file or not.
Just iterating what #Tieson T. mentioned in the comments, model binder requires files to have filenames regardless of their value.
I am sending a request using HttpClient, and this is working. If I omit filename, it won't work.
var formContent = new MultipartFormDataContent();
var streamContent = new StreamContent(request.Stream);
formContent.Add(streamContent, "Photo", "any filename is accepted");
https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ModelBinding/Binders/FormFileModelBinder.cs#L142
I have a Middleware that logs the request information in my ASP.Net core API project, but the proble is when I read the request in the middleware, I can't read the same request in my Controller.
Who can I read the Json request in my Middleware and in my Controller?
Middleware Comtroller
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using srpago_oxxo.Library.Logger;
using srpago_oxxo.Library.Logger.Handlers;
using srpago_oxxo.Library.Logger.Formatters;
using Microsoft.AspNetCore.Http.Internal;
namespace srpago_oxxo.Middlewares
{
public class IOMiddleware
{
private readonly RequestDelegate _next;
private Logger logger;
public IOMiddleware(RequestDelegate next)
{
_next = next;
logger = new Logger("SRPAGO-OXXO");
var formatter = new WebFormatter();
var handler = new StdoutHandler();
handler.SetFormatter(formatter);
logger.AddHandler(handler);
}
public async Task Invoke(HttpContext context)
{
var contentLenth = context.Request.ContentLength.ToString();
contentLenth = contentLenth == "" ? "-" : contentLenth;
var info = new Dictionary<string, string>()
{
{"{code}", "200"},
{"{clientIp}", context.User.ToString()},
{"{method}", context.Request.Method},
{"{path}", context.Request.Path.Value},
{"{bodyLength}", contentLenth},
{"{message}", await GetJsonRequest(context)},
};
logger.Info(info);
await _next.Invoke(context);
}
private async Task<string> GetJsonRequest(HttpContext context)
{
var body = context.Request.Body;
context.Request.EnableRewind();
var request = context.Request;
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body = body;
if (bodyAsText == "")
{
bodyAsText = "{}";
}
return bodyAsText;
}
}
}
This is my Base Controller
using System.IO;
using Microsoft.AspNetCore.Mvc;
namespace srpago_oxxo.Controllers
{
public class BaseController : Controller
{
protected string GetJsonRequest()
{
var request = this.HttpContext.Request;
var stream = new StreamReader(request.Body);
var body = stream.ReadToEnd()
.Replace("\n", "")
.Replace("\t", "");
return body;
}
}
}
When I use the function GetJsonRequest after the IOMiddleware logs the request data, it does'nt return any data at all.
Can you help me whit this problem?
I'm pretty new to rest API and restsharp so I need some help. I need to get a magento version 2.2.3 admin token but I keep getting a bad request. I've followed this tutorial: https://www.youtube.com/watch?v=2sdGuC7IUAI&t=343s. But I'm ending up with a bad request. When I check the statuscode using a the breakpoints from the tutorial I get: NotFound.
My main goal is to get the categories I have in Magento. But to get that I need an admin token. I already have a bearer acces code etc.
I would really appreciate your help.
my code so far:
magento.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RestSharp;
using Newtonsoft.Json;
namespace MagentoTest
{
public class magento
{
private RestClient Client { get; set; }
private string Token { get; set; }
public magento(string magentoUrl)
{
Client = new RestClient(magentoUrl);
}
public magento(string magentoUrl,string token)
{
Client = new RestClient(magentoUrl);
Token = token;
}
public string GetAdminToken(string userName, string passWord)
{
var request = CreateRequest("/rest/V1/integration/admin/token", Method.POST);
var user = new Credentials();
user.username = userName;
user.password = passWord;
string Json = JsonConvert.SerializeObject(user, Formatting.Indented);
request.AddParameter("aplication/json", Json, ParameterType.RequestBody);
var response = Client.Execute(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
return response.Content;
}
else
{
return "";
}
}
private RestRequest CreateRequest(string endPoint, Method method)
{
var request = new RestRequest(endPoint, method);
request.RequestFormat = DataFormat.Json;
return request;
}
}
}
Credentials:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MagentoTest
{
public class Credentials
{
public string username { get; set; }
public string password { get; set; }
}
}
(Client)
Program.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MagentoTest;
namespace Client
{
class Program
{
static void Main(string[] args)
{
GetToken("blabla", "blabla");
}
static void GetToken(string userName, string passWord)
{
var m2 = new magento("http://beta.topprice24.com");
string token = m2.GetAdminToken(userName, passWord);
}
}
}
It looks, relative URL needs to be changed as "/rest/default/V1/integration/admin/token"
(https://devdocs.magento.com/guides/v2.1/get-started/order-tutorial/order-admin-token.html).
I have simplified the above code and you can easily get the token.
Keep your Credentials class as it is and change your main program as below
Modified Code:(Program.cs)
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Client
{
class Program
{
static void Main(string[] args)
{
//Base URL needs to be Specified
String host = "http://beta.topprice24.com";
//Relative URL needs to be Specified
String endpoint = "/rest/default/V1/integration/admin/token";
RestClient _restClient = new RestClient(host);
var request = new RestRequest(endpoint, Method.POST);
//Initialize Credentials Property
var userRequest = new Credentials{username="blabla",password="blabla"};
var inputJson = JsonConvert.SerializeObject(userRequest);
//Request Header
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
//Request Body
request.AddParameter("application/json", inputJson, ParameterType.RequestBody);
var response = _restClient.Execute(request);
var token=response.Content;
}
}
}
I got the files to send by Retrofit with Android using Multiparti however on my server I work with .Net C# to build the Restful Service, then How can i create the restful to receive the files multiparti from Retrofit / Android?
sample:
[RoutePrefix("rest/files")]
public class ReceiveImagesController : ApiController
{
[AcceptVerbs("POST")]
[Route("SendFiles")]
public string sendFiles()
{
string retorno = "";
string path = "C:/temp";
// byte[] Bytes = new byte[files.Inpu]
return retorno;
}
}
My sample code and I use file upload in webapi 2. I think your problem will solve below codes.
sing System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
namespace WebMvcTest.Controllers
{
[System.Web.Http.RoutePrefix("api/test")]
public class FileUploadController : ApiController
{
[System.Web.Http.Route("files")]
[System.Web.Http.HttpPost]
[ValidateMimeMultipartContentFilter]
public async Task<FileResult> UploadSingleFile()
{
var streamProvider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(streamProvider);
string descriptionResult = string.Empty;
var description =
streamProvider.Contents.AsEnumerable()
.FirstOrDefault(T => T.Headers.ContentDisposition.Name == "\"description\"");
if (description != null)
{
descriptionResult = await description.ReadAsStringAsync();
}
return new FileResult
{
FileNames = streamProvider.Contents.AsEnumerable().Select(T => T.Headers.ContentDisposition.FileName).ToArray(),
Names = streamProvider.Contents.AsEnumerable().Select(T => T.Headers.ContentDisposition.FileName).ToArray(),
ContentTypes = streamProvider.Contents.AsEnumerable().Where(T => T.Headers.ContentType != null).Select(T => T.Headers.ContentType.MediaType).ToArray(),
Description = descriptionResult,
CreatedTimestamp = DateTime.UtcNow,
UpdatedTimestamp = DateTime.UtcNow,
};
}
}
}