aws s3 api dotnet core - 02
인증
aws iam에서 계정을 하나 만든다. program only로 만들었다. key/secret둘다 복사해둔다.
맥에서 ~/.aws/credentials를 만들어서 넣는다.
[profileName]
aws_access_key_id = XXXX
aws_secret_access_key = TTTT
이제 개발 프로젝트에 다음처럼 설정한다.
nuget package 설치
dotnet add package AWSSDK.Extensions.NETCore.Setup
dotnet add package AWSSDK.S3
appsettings.Developement.json
"AWS": {
"Profile": "profileName",
"Region": "us-west-2"
},
"AwsS3BucketOptions": {
"BucketName": "ur-bucketname"
}
이제 startup.cs에서 다음 추가
services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService<IAmazonS3>();
services.AddTransient<IStorageService, AmazonS3Service>();
services.Configure<AwsS3BucketOptions>(Configuration.GetSection(nameof(AwsS3BucketOptions)))
.AddSingleton(x => x.GetRequiredService<IOptions<AwsS3BucketOptions>>().Value);
service폴더를 만들고 거기에 다음 두개 추가
Interface/class
public interface IStorageService
{
Task<HttpStatusCode> CreateDirectoryAsync(string path);
Task<HashSet<FileViewModel>> GetFilesAsync(string path);
Task<bool> UploadFileAsync(Stream fileStream, string fileName, string directory);
Task<(Stream FileStream, string ContentType)> ReadFileAsync(string directory, string fileName);
Task<bool> RemoveFileAsync(string path, string fileName);
Task<bool> RemoveFolderAsync(string path);
}
public class AmazonS3Service : IStorageService
{
private readonly ILogger<AmazonS3Service> _logger;
private readonly IAmazonS3 _client;
private readonly AwsS3BucketOptions _s3BucketOptions;
public AmazonS3Service(ILogger<AmazonS3Service> logger, IAmazonS3 client, AwsS3BucketOptions s3BucketOptions)
{
_logger = logger;
_client = client;
_s3BucketOptions = s3BucketOptions;
}
public async Task<HttpStatusCode> CreatePathAsync(string path)
{
PutObjectRequest request = new PutObjectRequest()
{
BucketName = _s3BucketOptions.BucketName,
Key = path.Trim('/') + @"/"
};
var response = await _client.PutObjectAsync(request);
return response.HttpStatusCode;
}
public async Task<HashSet<FileViewModel>> GetFilesAsync(string directory)
{
var fileTransferUtility = new TransferUtility(_client);
var request = new ListObjectsV2Request()
{
BucketName = _s3BucketOptions.BucketName,
Prefix = directory,
Delimiter = "/"
};
var data = new HashSet<FileViewModel>();
var response = await fileTransferUtility.S3Client.ListObjectsV2Async(request);
foreach (S3Object entry in response.S3Objects)
{
var filename = entry.Key;
if (directory != null)
{
filename = filename.Substring(directory.Length);
}
if (filename.Trim() != string.Empty)
{
data.Add(new FileViewModel(filename.Trim(), "file", entry.Size.ToString()));
}
}
foreach (var name in response.CommonPrefixes)
{
if (directory != null)
{
data.Add(new FileViewModel(name.Substring(directory.Length).TrimEnd('/'), "directory", ""));
}
else
{
data.Add(new FileViewModel(name.TrimEnd('/'), "directory", ""));
}
}
return data;
}
public async Task<bool> UploadFileAsync(Stream fileStream, string fileName, string path = null)
{
var fileTransferUtility = new TransferUtility(_client);
var bucketPath = !string.IsNullOrWhiteSpace(path)
? _s3BucketOptions.BucketName + @"/" + path
: _s3BucketOptions.BucketName;
var fileUploadRequest = new TransferUtilityUploadRequest()
{
BucketName = bucketPath,
Key = fileName,
InputStream = fileStream
};
fileUploadRequest.UploadProgressEvent += (sender, args) => _logger.LogInformation($"{args.FilePath} upload complete : {args.PercentDone}%");
await fileTransferUtility.UploadAsync(fileUploadRequest);
_logger.LogInformation($"successfully uploaded {fileName} to {bucketPath} on {DateTime.UtcNow:O}");
return true;
}
public async Task<(Stream FileStream, string ContentType)> ReadFileAsync(string path, string fileName)
{
var fileTransferUtility = new TransferUtility(_client);
var bucketPath = !string.IsNullOrWhiteSpace(path)
? _s3BucketOptions.BucketName + @"/" + path.Trim('/')
: _s3BucketOptions.BucketName
;
var request = new GetObjectRequest()
{
BucketName = bucketPath,
Key = fileName
};
var objectResponse = await fileTransferUtility.S3Client.GetObjectAsync(request);
return (objectResponse.ResponseStream, objectResponse.Headers.ContentType);
}
}
public async Task<bool> RemoveFileAsync(string path, string fileName)
{
var fileTransferUtility = new TransferUtility(_client);
var bucketPath = !string.IsNullOrWhiteSpace(path)
? _s3BucketOptions.BucketName + @"/" + path.TrimStart('/').TrimEnd('/')
: _s3BucketOptions.BucketName
;
// 1. deletes files
var result = await fileTransferUtility.S3Client.DeleteObjectAsync(new DeleteObjectRequest()
{
BucketName = bucketPath,
Key = fileName,
});
_logger.LogInformation($"successfully deleted {fileName} from {bucketPath}");
return true;
}
public async Task<bool> RemoveFolderAsync(string path)
{
var fileTransferUtility = new TransferUtility(_client);
var deleteObjectsRequest = new DeleteObjectsRequest()
{
BucketName = _s3BucketOptions.BucketName
};
var request = new ListObjectsV2Request()
{
BucketName = _s3BucketOptions.BucketName,
Prefix = path.TrimStart('/').TrimEnd('/') + @"/",
};
var response = await fileTransferUtility.S3Client.ListObjectsV2Async(request);
foreach (S3Object entry in response.S3Objects)
{
deleteObjectsRequest.AddKey(entry.Key);
}
var result = await fileTransferUtility.S3Client.DeleteObjectsAsync(deleteObjectsRequest);
return true;
}
}
public class StorageObject
{
public string path { get; set; }
}
public class AwsS3BucketOptions
{
public string BucketName { get; set; }
}
public class FileViewModel
{
public FileViewModel(string name, string type, string size)
{
this.Name = name;
this.Type = type;
this.Size = size;
}
public string Name { get; set; }
public string Type { get; set; }
public string Size { get; set; }
}
지저분한 코드 지우려고 try catch등을 다 지운코드다 보니 대충 내용만 파악한다고생각하고 보기 바람.
이제 컨트롤러를 만들면된다.
controller
[Route("files")]
// [Authorize]
public class FilesController : ControllerBase
{
private readonly IStorageService _service;
public FilesController( IStorageService service)
{
_service = service;
}
[HttpOptions]
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
public IActionResult Options()
{
Response.Headers.Add("Allow", "OPTIONS,GET,POST");
return Ok();
}
[HttpGet]
public async Task<IActionResult> GetFolder(string path)
{
if (string.IsNullOrEmpty(path))
{
return BadRequest("please provide valid file and/or valid path name");
}
var result = await _service.GetFilesAsync(path);
return Ok(result);
}
[HttpGet("download")]
public async Task<IActionResult> Get(string path, string fileName)
{
if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(path))
{
return BadRequest("please provide valid file or valid path name");
}
var response = await _service.ReadFileAsync(path, fileName);
if (response.FileStream == null)
{
return NotFound();
}
return File(response.FileStream, response.ContentType);
}
[HttpPost("CreateDirectory")]
public async Task<IActionResult> CreateDirectory([FromBody] StorageObject storageObject)
{
if (string.IsNullOrEmpty(storageObject.path))
{
return BadRequest("please provide valid file and/or valid path name");
}
var result = await _service.CreatePathAsync(storageObject.path);
return StatusCode((int)result);
}
[HttpPost]
public async Task<IActionResult> Upload(List<IFormFile> files)
{
if (files == null || files.Count == 0)
{
return BadRequest("please provide valid file");
}
bool status = false;
foreach (var item in files)
{
var fileName = item.FileName.Trim('"');
var path = Request.Form.ContainsKey("path") ? Request.Form["path"].ToString().Trim('/') : null;
using (var fileStream = item.OpenReadStream())
using (var ms = new MemoryStream())
{
await fileStream.CopyToAsync(ms);
status = await _service.UploadFileAsync(ms, fileName, path);
}
}
return status ? Ok("success")
: StatusCode((int)HttpStatusCode.InternalServerError, $"error uploading");
}
[HttpDelete]
public async Task<IActionResult> RemoveFile(string path, string name)
{
if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(name))
{
return BadRequest("please provide valid file and/or valid path name");
}
if (await _service.RemoveFileAsync(path, name))
{
return NoContent();
}
return StatusCode((int)HttpStatusCode.InternalServerError, $"error removing {path}");
}
[HttpDelete("folder")]
public async Task<IActionResult> RemoveFolder(string path)
{
if (string.IsNullOrEmpty(path))
{
return BadRequest("please provide valid file and/or valid path name");
}
if (await _service.RemoveFolderAsync(path))
{
return NoContent();
}
return StatusCode((int)HttpStatusCode.InternalServerError, $"error removing {path}");
}
}
이러면 된다.
참고
- folder를 지울때는 폴더로 모든 객체를 가져와서 그걸 하나씩 지워준다.
Postman으로 테스트
create folder
file list
download
delete file
delete folder
upload
Postman file upload
포스트맨으로 업로드를 하면 위처럼 할수 있다. files (변수명)을 써주고 (api에서 사용한다.) file을 선택하고 select files를 누르면 여러개의 파일을 선택할수 있다.
이제 api를 다 만들었다. 프론트에서 api에 보내주기만 하면된다.
배포
스테이지에 배포를 하려고 하니 로컬에 만들어 두었던 credential 처리 방법이 이상햇다.
여러가지를 고민하다 그냥 쿠베 환경변수로 넣어버리는걸 선택햇다.
참조
여기저기 코드를 참조하면서 내 상황에 맞춰서 바꾸다보니 어디어디를 참조햇는데 확인이 안된다.
혹시 알려주시면 추가하겠다.