WebApi File Read Access is Denied - c#

I have a WebApi application whose intent is to serve up audio files, located within the application. They are stored in the app_data/audio folder.
Here is my method of retrieval:
public HttpResponseMessage Get(string file)
{
var path = String.Format("{0}{1}", HttpContext.Current.Server.MapPath(#"~/App_Data/Audio/"), file);
try
{
var responseStream = new MemoryStream();
using (Stream fileStream = File.Open(path, FileMode.Open))
{
fileStream.CopyTo(responseStream);
fileStream.Close();
responseStream.Position = 0;
}
var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StreamContent(responseStream)
};
response.Content.Headers.Add("content-type", "audio/basic");
response.Headers.CacheControl = new CacheControlHeaderValue()
{
Private = true
};
response.Content.Headers.Expires = null;
response.Headers.Pragma.Clear();
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = file
};
return response;
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound, e.Message);
}
}
Unfortunately, I get this error:
"Access to the path 'F:\Apps\AudioServeup\App_Data\Audio\test.pcm' is denied."
I can resolve this by setting the AppPool identity to NetworkService, then giving NetworkService Write Access. Huh? Not sure why I would require that, because NetworkService has read access by default.
Any ideas?
Thanks!

Try File.OpenRead( instead of File.Open( as that will ensure it only requests read access when opening the file.

Related

How to resolve 400 error with bad request in update api when it is deployed

I am getting 400 error code with bad request while request to file upload API.
I built the back-end and front-end for file uploading in asp.net core and it works in localhost when I run it with IIS in my PC (using visual studio 2017).
Both of saving and updating API are working in my local but update API is not working if I deploy the code
front-end code like below:
public static async Task<HttpResponseMessage> UploadFile(string uploadUrl, string filePath, FFFileInfo fileInfo)
{
string fileName = fileInfo.Name + "." + fileInfo.Extension;
string contentType = MimeTypes.GetMimeType(filePath);
using (var hc = new HttpClient())
{
hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TokenType, AccessToken);
hc.DefaultRequestHeaders.Accept.Clear();
hc.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
StreamContent streamContent = CreateFileContent(fileStream, fileName, contentType);
// StreamContent streamContent = CreateFileContent(fileStream, "image.jpg", "image/jpeg"); // Multiple file upload
var requestContent = new MultipartFormDataContent("Upload Id" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
requestContent.Add(streamContent, fileInfo.Name, fileName);
var progressContent = new ProgressableStreamContent(
requestContent,
4096,
(sent, total) =>
{
//Console.WriteLine("Uploading {0}/{1}", sent, total);
int percentage = (int) Math.Round((double)(100 * sent) / total);
Console.Write("\r{0}\t{1}%", fileInfo.Path, percentage);
if (sent == total)
{
Console.WriteLine();
}
});
var response = await hc.PostAsync(new Uri(uploadUrl), progressContent);
return response;
}
}
backend code like below:
[HttpPost]
[DisableFormValueModelBinding]
public async Task<IActionResult> UploadFiles([FromQuery] FFFileInfo fileinfo)
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
}
authUser = User.ToAuthUser();
userId = authUser.UserId();
customerId = authUser.CustomerId();
Server.Model.File new_file = new Server.Model.File();
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
MemoryStream writeStream = new MemoryStream();
byte[] content = null;
while (section != null)
{
ContentDispositionHeaderValue contentDisposition;
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
int chunkSize = 1024;
byte[] byte_file = new byte[chunkSize];
int bytesRead = 0;
new_file.File_Content = byte_file;
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
//await section.Body.CopyToAsync(targetStream);
using (var byte_reader = new BinaryReader(section.Body))
{
do
{
bytesRead = byte_reader.Read(byte_file, 0, byte_file.Length);
if(bytesRead <= 0)
{
content = writeStream.ToArray();
}
writeStream.Write(byte_file, 0, bytesRead);
} while (bytesRead > 0);
content = writeStream.ToArray();
}
}
}
// Drains any remaining section body that has not been consumed and
// reads the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
try
{
new_file = new Server.Model.File
{
File_Name = fileinfo.Name,
File_Path = fileinfo.Path,
File_Ext = fileinfo.Extension,
Check_Sum = fileinfo.Checksum,
ToolSerialNumber = fileinfo.ToolSerialNumber,
FileSize = fileinfo.Length,
File_Content = content,
UserId = userId,
CustomerId = customerId
};
}
catch (Exception ex)
{
return BadRequest(ex);
}
try
{
if (!fileService.isExist(new_file.File_Path, userId))
{
fileService.SaveFile(new_file);
}
else
{
Server.Model.File existing = fileService.GetFileByPath(new_file.File_Path, userId);
fileService.UpdateFile(existing, new_file);
}
//set file content to null to response with small data
new_file.File_Content = null;
return Ok(new_file);
}
catch (Exception ex)
{
logger.LogError("DB action error {0}", ex.ToString());
return BadRequest(ex);
}
}
As you can see the above code, saving and updating are using same code but only updating is not working when it is deployed.
It is very strange for me.
I found the solution.
This code was deployed by my client I couldn't check the database that he deployed.
Based on researching and testing, I got an idea that might be related with permission issue.
So, we check it for db.
At the end, we found that current user has insert, delete, select permission but have not update permission.
After granting the update permission, it is working perfectly

How to Get File using Console application in C#?

I am building an C# Console Application for GET file which will automatically download the file when I run the console application.
These are my codes:
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace WebAPIConsoleNEW
{
class Program
{
static void Main(string[] args)
{
RunAsync().Wait();
}
static async Task RunAsync()
{
string bookPath_Pdf = #"D:\VisualStudio\randomfile.pdf";
string bookPath_xls = #"D:\VisualStudio\randomfile.xls";
string bookPath_doc = #"D:\VisualStudio\randomfile.docx";
string bookPath_zip = #"D:\VisualStudio\randomfile.zip";
string format = "pdf";
string reqBook = format.ToLower() == "pdf" ? bookPath_Pdf : (format.ToLower() == "xls" ? bookPath_xls : (format.ToLower() == "doc" ? bookPath_doc : bookPath_zip));
string fileName = "sample." + format.ToLower();
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49209/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("applicaiton/json"));
Console.WriteLine("GET");
//converting Pdf file into bytes array
var dataBytes = File.ReadAllBytes(reqBook);
//adding bytes to memory stream
var dataStream = new MemoryStream(dataBytes);
//send request asynchronously
HttpResponseMessage response = await client.GetAsync("api/person");
response.Content = new StreamContent(dataStream);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = fileName;
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
//Check that response was successful or throw exception
//response.EnsureSuccessStatusCode();
//Read response asynchronously and save asynchronously to file
if (response.IsSuccessStatusCode)
{
using (var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:49209/api"))
{
using (
Stream contentStream = await (await client.SendAsync(request)).Content.ReadAsStreamAsync(),
fileStream = new FileStream("D:\\VisualStudio\\randomfile.pdf", FileMode.Create, FileAccess.Write, FileShare.None))
{
//copy the content from response to filestream
await response.Content.CopyToAsync(fileStream);
//Console.WriteLine();
}
}
}
}
catch (HttpRequestException rex)
{
Console.WriteLine(rex.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
When I run another ASP.NET application which is my localhost, it only return the default which is value1 and value2 in the Controller. However, I do not have Controller in C# Console Application. I think I just one step away, I had successfully obtain the file and CopyToAsync the file I wanted to download.
Conclusion:
I want when user runs the application it would straight download the file in a place (or can I use SaveFileDialog to let user decide where to save the file).
Please help thanks
Update:
At first, I created a ASP.NET Web Application and Create a PersonController and I run the Project. After that I created a console C# Application and then I want to achieve the result of when user runs the console C# Application it would straight download the file to a specific place.
In the first get I uses api/person, and I convert the file int o bytes array and add the bytes array to memory stream. After that, I don't really know what I'm doing is right or wrong. I saw something like CopyToAsync is working then I tried it and implement it but it won't works. My goal is simple I just want to acheive once I run the C# Console application it would straight download the file from a specific localhost address
Well I think your problem is that you are sending two GET requests, in case you just want to call api/student then save the response into a file then no need for the second request
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:49209/api")//no need for it
So your code should be like this:
static async Task RunAsync()
{
string bookPath_Pdf = #"D:\VisualStudio\randomfile.pdf";
string bookPath_xls = #"D:\VisualStudio\randomfile.xls";
string bookPath_doc = #"D:\VisualStudio\randomfile.docx";
string bookPath_zip = #"D:\VisualStudio\randomfile.zip";
string format = "pdf";
string reqBook = format.ToLower() == "pdf" ? bookPath_Pdf : (format.ToLower() == "xls" ? bookPath_xls : (format.ToLower() == "doc" ? bookPath_doc : bookPath_zip));
string fileName = "sample." + format.ToLower();
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49209/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("applicaiton/json"));
Console.WriteLine("GET");
//converting Pdf file into bytes array
var dataBytes = File.ReadAllBytes(reqBook);
//adding bytes to memory stream
var dataStream = new MemoryStream(dataBytes);
//send request asynchronously
HttpResponseMessage response = await client.GetAsync("api/person");
response.Content = new StreamContent(dataStream);
response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = fileName;
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
//Check that response was successful or throw exception
//response.EnsureSuccessStatusCode();
//Read response asynchronously and save asynchronously to file
if (response.IsSuccessStatusCode)
{
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
{
using (fileStream = new FileStream("D:\\VisualStudio\\randomfile.pdf", FileMode.Create, FileAccess.Write, FileShare.None))
{
//copy the content from response to filestream
await response.Content.CopyToAsync(fileStream);
//Console.WriteLine();
}
}
}
}
}
catch (HttpRequestException rex)
{
Console.WriteLine(rex.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Also it would be good to print a message for the user telling him that logging data from server into file(File path) is in progress:
static void Main(string[] args)
{
Console.WriteLine("Logging data from server into file (D:\\VisualStudio\\randomfile.pdf");
RunAsync().Wait();
}

C# MVC/API - Return an image from Amazon S3 for my API Call

I'm using .net core to upload and retrieve an image from a private Amazon S3 bucket.
I'm able to upload it successfully, and even view it after I download it from S3, however when I'm a bit unsure about how to return the stream/response back to the client for the actual API call (for example right now I'm just trying to use Postman/Fiddler proxy tools to get back the image from my API)
My code for S3 to retrieve the stream:
///Retrieve my image from my bucket
public async Task<string> ReadObjectData(MediaFolder key, String fileName)
{
string responseBody = "";
IAmazonS3 client;
using (client = new AmazonS3Client(accessKey, accessSecret, endpoint))
{
Amazon.S3.Model.GetObjectRequest request = new Amazon.S3.Model.GetObjectRequest
{
BucketName = bucket,
Key = key + "/" + fileName,
};
using (GetObjectResponse response = await client.GetObjectAsync(request))
using (Stream responseStream = response.ResponseStream)
using (StreamReader reader = new StreamReader(responseStream))
{
string title = response.Metadata["x-amz-meta-title"];
responseBody = reader.ReadToEnd();
}
}
return responseBody;
}
So now in my controller, I have the following action:
[HttpGet("ProfilePic")]
public async Task<IActionResult> GetProfilePicture()
{
var user = await GetUserFromBearerToken();
//Retrieve
var utf8ImageResponse = await _fileService.ReadObjectData(MediaFolder.Profiles, user.ProfileImageFileName);
//To return a file as a stream
var imageBytes = System.Text.Encoding.UTF8.GetBytes(utf8ImageResponse);
//Return the image, which I'll hardcode as jpeg for a test
return File(imageBytes, "image/jpeg");
}
When I make the call using Postman, it returns a little blank box (the box you'd see if you tried to return an image, but it wasn't a valid image or null in some way).
Right now I'm using Postman but ideally I'd want an app to present this image.
Any ideas what I'm doing wrong? I tried messing around with base64 encoding and other things but nothing seems to work.
Thanks!
This way you can retrieve the file as stream from S3 storage
public async Task<Stream> ReadObjectData(MediaFolder key, String fileName)
{
try
{
using (var client = new AmazonS3Client(accessKey, accessSecret, endpoint))
{
var request = new GetObjectRequest
{
BucketName = bucket,
Key = key + "/" + fileName
};
using (var getObjectResponse = await client.GetObjectAsync(request))
{
using (var responseStream = getObjectResponse.ResponseStream)
{
var stream = new MemoryStream();
await responseStream.CopyToAsync(stream);
stream.Position = 0;
return stream;
}
}
}
}
catch (Exception exception)
{
throw new Exception("Read object operation failed.", exception);
}
}
And then - return this stream as FileStreamResult:
[HttpGet("ProfilePic")]
public async Task<IActionResult> GetProfilePicture()
{
var user = await GetUserFromBearerToken();
Stream imageStream = await _fileService.ReadObjectData(MediaFolder.Profiles, user.ProfileImageFileName);
Response.Headers.Add("Content-Disposition", new ContentDisposition
{
FileName = "Image.jpg",
Inline = true // false = prompt the user for downloading; true = browser to try to show the file inline
}.ToString());
return File(imageStream, "image/jpeg");
}

How to stream zip file from TFS api

I am using TFS2018 api and I am trying to to retrieve the zip file of a solution but I always get an internal server error.
internal async Task<bool> GetSourceZipFile(string sourceVersionId)
{
using (var handler = new HttpClientHandler { Credentials = new NetworkCredential(tfsUser, tfsPass) })
using (var client = new HttpClient(handler))
{
try
{
client.BaseAddress = new Uri(tfsServer);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
var tempFolder = "c:\\temp\\test";
tempFolder = HttpUtility.UrlEncode(tempFolder);
var url = $"DefaultCollection/_api/_versioncontrol/itemContentZipped?path={tempFolder}&version={sourceVersionId}";
using (var file = await client.GetStreamAsync(url).ConfigureAwait(false))
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
var s = memoryStream.ToArray();
var f = s;
};
}
catch (Exception ex)
{
// LOGGING
}
return true;
}
}
I am not sure if the zip file is generated by the TFS server. Do I need to set it specifically? Any idea why this is not working?
You're passing in a local folder to the path parameter in the REST API. The path should be to the item in source control (ex: $/MyTeamProject/DEV/SomeCode), not to the local file system.

WebAPI returns corrupted, incomplete file

I want to return an image from WebApi endpoint. This is my method:
[System.Web.Http.HttpGet]
public HttpResponseMessage GetAttachment(string id)
{
string dirPath = HttpContext.Current.Server.MapPath(Constants.ATTACHMENT_FOLDER);
string path = string.Format($"{dirPath}\\{id}.jpg");
try
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
var content = new StreamContent(stream);
result.Content = content;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = Path.GetFileName(path) };
return result;
}
catch (FileNotFoundException ex)
{
_log.Warn($"Image {path} was not found on the server.");
return Request.CreateResponse(HttpStatusCode.NotFound, "Invalid image ID");
}
}
Unfortunately, the file that is downloaded is incomplete. The message in consuming Android app is:
java.io.EOFException: source exhausted prematurely
The problem is most likely that your Android client thinks the download is over before it actually is.
To easily fix this, you can use this method instead which will return the entire file at once (rather than streaming it):
result.Content = new ByteArrayContent(File.ReadAllBytes(path));
Turns out that this was caused by compression, that was set for all responses in this controller. There is GZip encoding set up in controller's constructor:
HttpContext.Current.Response.AppendHeader("Content-Encoding", "gzip");
HttpContext.Current.Response.Filter = new GZipStream(HttpContext.Current.Response.Filter, CompressionMode.Compress);
To solve this, I added these lines to my method
(just after beginning of try block):
// reset encoding and GZip filter
HttpContext.Current.Response.Headers["Content-Encoding"] = "";
HttpContext.Current.Response.Headers["Content-Type"] = "";
// later content type is set to image/jpeg, and default is application/json
HttpContext.Current.Response.Filter = null;
Also, I'm setting content type and length like this:
result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
result.Content.Headers.ContentLength = stream.Length;

Categories

Resources