How to return an image from wwwroot? - c#

I'm trying to return an image stored inside wwwwroot/images folder, this is the structure:
Inside the View I have the following tag:
<img src="#Url.Action("GetAvatar", "User", new { username = User.Identity.Name })" />
as you can see for display the image it simply call GetAvatar method from User controller passing the username as parameter.
The method have the following configuration:
[HttpGet]
public FileResult GetAvatar(string username)
{
User user = _repo.GetUser(username);
if(user.UserDetail != null)
return File(user.UserDetail?.UserPhoto, "image/png");
//The user has no custom image, will displayed the default.
string root = Path.Combine(_env.WebRootPath, "images");
return File(Path.Combine(root, "avatar_default.png"), "image/png");
}
the firtst part of the method that retrieve the image from the database works, but the last part which try to get the image from the wwwroot folder doesn't work. Infact when I load the View I get the broked thumbnail which mean not found.
I'm also injected the IHostingEnvironment for access to wwwroot folder.
Any idea?

The File method you're using has the following signature:
public VirtualFileResult File (string virtualPath, string contentType);
As its name suggests, the first parameter here represents the virtual path of the file you want to serve; not the physical path. By default, this means you need to provide a path that is essentially just relative to the wwwroot folder. In your example, the path would be images/avatar_default.png. With this, there's no need for Path.Combine or IHostingEnvironment in your example. Here's the updated version:
[HttpGet]
public FileResult GetAvatar(string username)
{
User user = _repo.GetUser(username);
if(user.UserDetail != null)
return File(user.UserDetail?.UserPhoto, "image/png");
return File("images/avatar_default.png", "image/png");
}

Related

File download not opening in new tab when setting the file name in .net core api

I need to download files and open them in a new tab instead of download. The below action works fine when downloading a PDF or image. The file opens in a new tab.
[Route("image/{id}")]
[Route("view/{id}")]
[HttpGet]
[AllowAnonymous]
public ActionResult GetImage(string id)
{
var file = _fileService.Get(id, User);
return File(file.ByteArray, file.ContentType);
}
However the file name comes down as the database ID and I want it to come down as the file name. When I set the file name in the file, the file is named correctly but it no longer opens in a new tab. Now it downloads. How can I fix this? Why is it doing this?
[Route("image/{id}")]
[Route("view/{id}")]
[HttpGet]
[AllowAnonymous]
public ActionResult GetImage(string id)
{
var file = _fileService.Get(id, User);
return File(file.ByteArray, file.ContentType, file.FileName);
}

Serve static pdf files in solution folder - in MVC pipeline , with different route

One of my old project we do have a static content file folder named XYZ which we are keeping in the same location fo the root.
Currently we are directly passing the url like 'siteaddress/XYZ/test.pdf' or siteaddress/XYZ/2020/Test1.pdf to get the pdf files.
Now we have a requirement to store some of the confidential files also in the path. So we are planning to restrict the direct access to the path and serve via MVC pipeline
we have added a handlers to enable the requests from the folder, to go through mvc pipeline
<add
name="ManagedPdfExtension"
path="XYZ/*/*.pdf"
verb="GET"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0"
/>
<add
name="ManagedPdfInnerFolderExtension"
path="CommonFiles/*/*.pdf"
verb="GET"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0"
/>
Also created a method to return the file in controller
[HttpGet]
[Route("XYZ/{attachmentName}")]
public ActionResult CommonFiles(string attachmentName)
{
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/XYZ/"+ attachmentName);
byte[] fileBytes = System.IO.File.ReadAllBytes(path);
string fileName = "Test.pdf";
var cd = new ContentDisposition
{
Inline = true,
FileName = fileName
};
Response.Clear();
Response.AddHeader(CoreConstants.ContentDisposition, cd.ToString());
Response.AddHeader(CoreConstants.WindowTarget, CoreConstants.WindowTargetBlank);
Response.BufferOutput = false;
return File(fileBytes, "application/pdf");
}
This code works ok with files which are directly under folder XYZ
That means if I try a url like
siteaddress/XYZ/test.pdf which is working.
But for the pdf that are inside another folder, I am not able to get with the existing approach.
Since we have only single param attachmentName defined in the method i couldn't get the files under subfolders.
Is there any way to do the same in MVC ??
Because of some reasons, I cannot move all these items to database , change the folder structure . Also i cannot create a mapping table like
url : key and use the key instead.
Th urls are coming from a common table which is used in many applications. So changing that is bit difficult.
If the folder and subfolders are limited then may be with multiple route i could handle this. But here the subfolder number can be a variable too.
In fact from the following urls
siteadress/XYZ/abc/bn/test.pdf
siteadress/XYZ/abc/cf/bn/test.pdf
siteadress/XYZ/abc/bn/test.pdf
is there any way to make it hit a single controller method with a string params like
abc/bn/test.pdf
abc/cf/bn/test.pdf
abc/bn/test.pdf
??
Added a route with * in the property part.
[HttpGet]
[Route("CommonFiles/{*any}")]
public ActionResult CommonFiles(string attachmentName)
{
var filePathWithName = this.RouteData.Values["any"];
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/CommonFiles/"+ filePathWithName);
path = path.Replace("//", "/").Replace("/","//");
byte[] fileBytes = System.IO.File.ReadAllBytes(path);
string fileName = "Test.pdf";
var cd = new ContentDisposition
{
Inline = true,
FileName = fileName
};
Response.Clear();
Response.AddHeader(CoreConstants.ContentDisposition, cd.ToString());
Response.AddHeader(CoreConstants.WindowTarget, CoreConstants.WindowTargetBlank);
Response.BufferOutput = false;
return File(fileBytes, "application/pdf");
}

Serve static folder with authentication in the asp net core 2.0

I am using asp net core 2.0.5 with MVC and want to serve a static folder outside of wwwroot.
My input files would look like below.
wwwroot
staticfolder
10
index.html
image1.png
image2.png
sub-folder
image3.jpg
The index.html is generated by other program. All images are used in the index.html.
We want to server in after checking the authentication, i.e. users can view index.html through a path api/flight/10/index.html. But I need to check the user permission to access resource flight 10.
I understand Physicalfile can be used to serve a static file (e.g. *.png) outside wwwroot, and also configure app.UseStaticFiles to serve folders and files in the startup.cs.
Some pseudo codes for a minimum example.
[Route("api/flight/{flight}/index.html")]
[HttpGet]
public IActionResult GetImageThumbnail(
[FromRoute] int flight)
{
// Check permission
List<Flight> data = _context
.Flight(id: flight)
.ToList();
if (data.Count() == 0)
{
return NotFound("No permission.");
}
// data[0].BaseFolder is a folder outside wwwroot
string filename = data[0].BaseFolder + "/index.html";
// Return the content for index.html
...
}
So my question is how should I server a folder mixed with html and images using the similar method above.
I found a simple solution to achieve my target. Post here in case others have the same problem.
The key thing is to set Route using {*file} which will match any string at the end.
[Route("api/flight/{flight}/{*file}")]
[HttpGet]
public async Task<IActionResult> GetImageThumbnail([FromRoute]int flight,
[FromRoute] string file)
{
// Check permission
List<Flight> data = _context
.Flight(id: flight)
.ToList();
if (data.Count() == 0)
{
return NotFound("No permission.");
}
string filename = Path.Combine(data[0].BaseFolder, file);
var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider();
string minetype = "";
provider.TryGetContentType(filename, out minetype);
return PhysicalFile(filename, minetype);
}

Download File through headers from different server

I have PDF file placed on different (FILE-Server) server machine, and the IIS machine on which my MVC application is hosted have rights to that File-Server. From IIS machine i can access the file through following URI:
file://file-server/data-folder/pdf/19450205.pdf
I want to enable my MVC app's users to download their respective files by clicking on download link or button. So probably i would have to write some Action for that link/button.
I tried to use File return type for my Action method in following way:
public ActionResult FileDownload()
{
string filePth = #"file://file-server/data-folder/pdf/19450205.pdf";
return File(filePth , "application/pdf");
}
but the above code gives exception of URI not supported.
I also tried to use FileStream to read bytes inside array return that bytes towards download, but FileStream also gives error of not proper "Virtual Path" as the file is not placed inside virtual path, its on separate server.
public ActionResult Download()
{
var document = = #"file://file-server/data-folder/pdf/19450205.pdf";
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = document.FileName,
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(document.Data, document.ContentType);
}
Thanks for the replies, but both suggestion did not work.
as file needs to be accessed over URI, using FileInfo gives error: URI formats are not supported.
I managed to get this done through following mechanism:
public ActionResult FaxFileDownload()
{
string filePth = #"file://file-server/data-folder/pdf/19450205.pdf";
WebClient wc = new WebClient();
Stream s = wc.OpenRead(filePth);
return File(s, "application/pdf");
}
Thanks to All.

How to modify ASP.NET MVC static file root

I want to be able to reorganize my ASP.NET MVC site structure to more closely match the way Rails does it (We do both rails and ASP.net at my company).
In Rails, there is a "public" folder that behaves as the root of the site. For example, I could drop in a "test.html" and access the file with the url http://domain.com/test.html which would serve up the static html file.
In asp.net MVC there is a "Content" folder that I want to behave as the root. So instead of accessing http://domain.com/content/myfile.html, i want to be able to do http://domain.com/myfile.html.
I know I can just drop the file in the root of the project, but i need to do this with many files including css, js, html, images, etc and want to share some standardized assets across rails and aspnetmvc.
Is there a way to do this?
There is another possible solution. Instead of using code, you can use a rewrite rule to handle this for you. If you are using IIS 7 or above, you can use Microsoft's URL Rewrite Module.
A rule like the following would probably do it:
<rule name="Rewrite static files to content" stopProcessing="true">
<match url="^([^/]+(?:\.css|\.js))$" />
<conditions>
<add input="{APPL_PHYSICAL_PATH}content{SCRIPT_NAME}" matchType="IsFile" />
</conditions>
<action type="Rewrite" url="/content/{R:1}" />
</rule>
The rule checks for a request to a css or js file off the root of the site. Then it checks to see if the file exists in the content folder. If it exists, then the rewrite will return the file in the content folder. I've only tested this a little bit, but it seems to work. It certainly needs more testing, and possible refinement.
The only solution I can think of, is to use a custom controller and route to do this for you. But it isn't a clean solution.
First you need a PublicController class with a GetFile action method. This assumes that all files are in the public/content folder directly. Handling folders makes things more complicated.
public class PublicController : Controller
{
private IDictionary<String, String> mimeTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{{"css", "text/css"}, {"jpg", "image/jpg"}};
public ActionResult GetFile(string file)
{
var path = Path.Combine(Server.MapPath("~/Content"), file);
if (!System.IO.File.Exists(path)) throw new HttpException(404, "File Not Found");
var extension = GetExtension(file); // psuedocode
var mimetype = mimeTypes.ContainsKey(extension) ? mimeTypes[extension] : "text/plain";
return File(path, mimetype);
}
}
Now, you just need a route near the bottom of your list of routes that looks like this:
routes.MapRoute("PublicContent", "{file}", new {controller = "Public", action = "GetFile"});
The problem is, now when you just put in a controller name like 'Home' instead of defaulting to the Index action method on the HomeController, it assumes you want to download a file called "Home" from the content directory. So, above the file route, you would need to add a route for each controller so it knows to get the Index action instead.
routes.MapRoute("HomeIndex", "Home", new { controller = "Home", action = "Index" });
So, one way around that is to change the route to this:
routes.MapRoute("PublicContent", "{file}.{extension}", new {controller = "Public", action = "GetFile"});
And the action method to this:
public ActionResult GetFile(string file, string extension)
{
var path = Path.Combine(Server.MapPath("~/Content"), file + "." + extension);
if (!System.IO.File.Exists(path)) throw new HttpException(404, "File Not Found");
var mimetype = mimeTypes.ContainsKey(extension) ? mimeTypes[extension] : "text/plain";
return File(path, mimetype);
}
Like I said, this assumes that all files are in the content directory, and not in subfolders. But if you wanted to do subfolders like Content/css/site.css you could add your routes like this:
routes.MapRoute("PublicContent_sub", "{subfolder}/{file}.{extension}", new { controller = "Public", action = "GetFileInFolder" });
routes.MapRoute("PublicContent", "{file}.{extension}", new { controller = "Public", action = "GetFile"});
Now the action method has to change too.
public ActionResult GetFile(string file, string extension)
{
return GetFileInFolder("", file, extension);
}
public ActionResult GetFileInFolder(string subfolder, string file, string extension)
{
var path = Path.Combine(Server.MapPath("~/Content"), subfolder, file + "." + extension);
if (!System.IO.File.Exists(path)) throw new HttpException(404, "File Not Found");
var mimetype = mimeTypes.ContainsKey(extension) ? mimeTypes[extension] : "text/plain";
return File(path, mimetype);
}
If you start getting multiple levels deep in the folder structure, this gets uglier and uglier. But maybe this will work for you. I'm sure you were hoping for a checkbox in the project properties, but if there is one, I don't know about it.

Categories

Resources