how to pass a folder path to mvc controller - asp.net-mvc

I am trying to pass a folder path to a download controller using #Html.ActionLink, but I am getting could not find the location error like
Could not find file 'C:\Teerth
Content\Project\Colege\WebApp\Media\#item.Content'
However when I give the hard coded value it does work. May I have suggestions what is wrong with that.
Here is my code:
Action method:
public FileResult Download(string fileName, string filePath)
{
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
string documentName = fileName;
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, documentName);
}
view
#Html.ActionLink("Download", "Download", "Marketing", routeValues: new
{
fileName = #item.Content,
filePath = Server.MapPath("~/Media/#item.Content"),
area = "AffiliateAdmin"
}, htmlAttributes: null)

Like mentioned in comments, you've got an error in your view:
The code ("~/Media/#item.Content") renders as C:\Teerth Content\Project\Colege\WebApp\Media\#item.Content, where you actually want Server.MapPath("~/Media/" + #item.Content) to find the actual filename.
But you need to reconsider this design, as it opens up your entire machine to the web. Someone is bound to try Download("C:\Teerth Content\Project\Colege\WebApp\web.config", "web.config"), exposing your connection strings and other application settings, not to mention other files on your server you really don't want clients to download.

Related

Display image from byte array in ASP.NET MVC Core

I have a byte[] stored in a VARBINARY(MAX) column in a table in my database.
I want to show this image on my index.cshtml page - but I'm stuck.
My CSHTML looks like this:
#using Microsoft.AspNetCore.Hosting.Internal
#{
ViewData["Title"] = "Title";
}
<h2>#ViewData["Title"]</h2>
<h3>#ViewData["Message"]</h3>
#if (!Context.User.Identity.IsAuthenticated)
{
<p>blah blah.</p>
<p>blah blah</p>
}
#if (Context.User.Identity.IsAuthenticated)
{
<p>Hi #(Context.User.Identity.Name)<br/></p>
<p>Where we off to today?</p>
}
I want to add
<img src="...." />
obviously I don't know what to do here.
My model has the byte array data:
public byte[] UserImage { get; set; }
My controller assigned that the value:
var model = new IndexViewModel
{
Username = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
IsEmailConfirmed = user.EmailConfirmed,
StatusMessage = StatusMessage,
UserImage = user.UserImage
};
but I am using .net core in VS2017 and the answers I have found don't seem to work for me. Any help would be really appreciated.
Thanks
Johan
You have two options:
Base64 encode the byte[] and use a Data URI:
<img src="data:image/png;base64,[base64-encoded byte array here]">
However, bear in mind two things. 1) Data URIs are supported in every modern browser, but notoriously do not work in IE 10 and under. That may not be an issue, but if you need to have legacy IE support, this is a non-starter. 2) Since you're Base64-encoding, the size of the "image" will balloon roughly 50%. As such, Data URIs are best used with small and simple images. If you've got large images or simply a lot of images, your HTML document can become a very large download. Since Data URIs are actually embedded in the HTML code, that means the browser cannot actually begin to render the page at all until the entire HTML document has loaded, which then also means that if it's megabytes in size, your users will be waiting a while.
Create an action that pulls the image from the database and returns it as a FileResult. This is the most optimal path. Essentially, you just need an action that accepts some sort of identifier for the image, which can be used to pull it from the database. You then return the byte[] like:
return File(myByteArray, "image/png");
In your view, you simply make the image source the route to this action:
<img src="#Url.Action("GetImage", "Foo", new { id = myImageIdentifier }">
Ok so I managed to work it out with the help above. I created a method on the controller that looks like this:
public FileResult GetFileFromBytes(byte[] bytesIn)
{
return File(bytesIn, "image/png");
}
[HttpGet]
public async Task<IActionResult> GetUserImageFile()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return null;
}
FileResult imageUserFile = GetFileFromBytes(user.UserImage);
return imageUserFile;
}
in my cshtml I then added this:
<img src= '#Url.Action("GetUserImageFile", "Manage")'/>
"Manage" was the start of the controller name. I didnt need to pass in an ID as my image bytes are stored on the aspuser so the code knows which user it is using the GetUserAsync
Can anyone see problems with this? Also, it doesnt seem to care that the origional image is a jpeg but in the code I am using "image/png", am I risking losing something?
Many thanks for the comments and help! this is such an amazing forum!

Can i download a pdf file saved in media in Umbraco

I need to set create a page on which i have button and on clicking, it should redirect to a registration page and then download a pdf file. so i created a document type in Umbraco which have a file Upload field and i uploaded one file through it. On its template i have added a macro which have a partial view for the registration page. After completing registration, this pdf file should download automatically.
My problem is, the file i uploaded is not showing in the Media library. but the Url is as follows: /media/1051/filname.pdf .
am getting this url in controller. but couldn't get the file usinng its id.
[HttpPost]
public HttpResponseMessage DownloadFile([FromBody] DownloadEBookViewModel model)
{
int id = Convert.ToInt32(model.Url.Split('/')[2]);
var media = Umbraco.Media(id).Url;
if (!File.Exists(media))
throw new HttpResponseException(HttpStatusCode.NotFound);
HttpResponseMessage Response = new HttpResponseMessage(HttpStatusCode.OK);
byte[] fileData = File.ReadAllBytes(media);
if (fileData == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
Response.Content = new ByteArrayContent(fileData);
Response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return Response;
}
Someone please help. thank you
When working with the Umbraco helper in code behind, I would advise to use the typed variants for getting items
var media = Umbraco.TypedMedia(id).Url;
This will give you a strongly typed model with intellisense
To get the physical file from the media object you'll probably want to call
byte[] fileData = File.ReadAllBytes(media.getPropertyValue("umbracoFile"));
instead of:
byte[] fileData = File.ReadAllBytes(media);
(code is untested)

Performance ramifications of serving FilePath rather than View

What are the performance ramifications if any of serving a FilePathResult rather than a view (If we have created server cached copies of a website using a headless browser)
public class HomeController : Controller
{
public ActionResult Index()
{
var url = Request.RawUrl.Replace("/", "_");
var path = System.Configuration.ConfigurationManager.AppSettings["PreloadPath"] + "\\" + url + ".html";
if (System.IO.File.Exists(path))
{
return new FilePathResult(path, "text/html");
}
else
{
return View("Index");
}
}
}
We are having to access the AppSettings every request now, use the File System to check if a file exists, and then serve that html file.
What costs are there compared with just
return View("Index");
Will the file access have any cost on the server? Or am I talking nonsense, and IIS would have to perform some similar action?
Note: Please suggest any other tags if I should add them
Looking at the FilePathResult's source code you can see that in the end it goes down to WriteStreamAsText of HttpResponse. It's obvious that there is no magic call to IIS for example to handle the file directly without any .Net code taking place.
Having said that I still expect this to be somewhat faster than running a view, which possibly needs interpretation and execution.

Map routes with combined URL parameter

User can download price information PDFs located in a folder PriceInformations with subfolders specifying the document type, e.g.:
/PriceInformations/Clothes/Shoes.pdf
/PriceInformations/Clothes/Shirts.pdf
/PriceInformations/Toys/Games.pdf
/PriceInformations/Toys/Balls.pdf
Consider following action in Controller Document to download those PDFs:
// Filepath must be like 'Clothes\Shoes.pdf'
public ActionResult DownloadPDF(string filepath)
{
string fullPath = Path.Combine(MyApplicationPath, filepath);
FileStream fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read);
return base.File(fileStream, "application/pdf");
}
To get a PDF document, my client wants URLs to be like:
/PriceInformations/Clothes/Shoes.pdf
I could easily create an overload function for this case:
public ActionResult DownloadPDF(string folder, string filename)
{
return this.DownloadPDF(Path.Combine(folder, filename);
}
And map it like
routes.MapRoute(
"DownloadPriceInformations",
"DownloadPriceInformations/{folder}/{filename}",
new
{
controller = "Document",
action = "DownloadPDF"
});
But I'm curious if it would be possible to work without an overload function and to map this case in RegisterRoutes in Global.asax, so to be able to create one single parameter out of of multiple parameters:
routes.MapRoute(
"DownloadPriceInformations",
"DownloadPriceInformations/{folder}/{filename}",
new
{
controller = "Document",
action = "DownloadPDF",
// How to procede here to have a parameter like 'folder\filename'
filepath = "{folder}\\{filename}"
});
Question became a bit longer but I wanted to make sure, you get my desired result.
Sorry, this is not supported in ASP.NET routing. If you want multiple parameters in the route definition you'll have to add some code to the controller action to combine the folder and path name.
An alternative is to use a catch-all route:
routes.MapRoute(
"DownloadPriceInformations",
"DownloadPriceInformations/{*folderAndFile}",
new
{
controller = "Document",
action = "DownloadPDF"
});
And the special {*folderAndFile} parameter will contain everything after the initial static text, including all the "/" characters (if any). You can then take in that parameter in your action method and it'll be a path like "clothes/shirts.pdf".
I should also note that from a security perspective you need to be absolutely certain that only allowed paths will be processed. If I pass in /web.config as the parameter, you must make sure that I can't download all your passwords and connection strings that are stored in your web.config file.

How to get an instance of the current HttpRequestBase outside of a Controller or View's context

What I'm doing:
I am creating an email mailing engine that takes html templates, replaces tokens with data and then sends off the mail to an SMTP server. Eventually we'll want a CMS UI on top of this to allow them to add/remove file templates or update the content in them via a CMS UI. Anyway I am putting the .htm files for the email templates inside my MVC web project and need to read their contents and get the string of HTML back to work with. If I move all this code out of my MVC project into like a utility C# project layer, the problem is then I have to worry about maintaining physical paths ("C:...\theMVCWebSiteFolder...\Email\Templates\SomeTemplate.htm") and that to me would have to be hard coded I think to keep track if you were to move the site to a different server, different hard drive partition, etc. So I'd like to be able to work with the files using the current application's context unless there's a way to do this agnostic to my MVC app and still not have to worry about having to ever change the location of the physical root every time we move folders.
I've got the following utility method I created in a utility class that sits in a folder somewhere in my ASP.NET MVC web project in just a folder (a folder outside of any view folders:
public static string GetFileData(string filePath)
{
if (!File.Exists(HttpContext.Current.Request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
I'm trying to figure out why the context is turning up null here. I originally tried HttpContext.Current but current is coming up null so it can't find the current context in my Utility.cs method that sits in my MVC web project.
UPDATE:
Ok so the consensus is to use HttpRequestBase object and not the HttpContext.Current object. I still find it weird that the HttpContext.Current is null. But moving on, if my Utility.cs is outside any context of a controller or view, then how the heck do I get an instance of the current request (HttpRequestBase) to work with and send an instance that implements HttpRequestBase (I do not know what object that would be if I want to do this in the "MVC way" outside a controller or view) to this utility method?
No idea why this returns null but since it is a bad idea to tie your business layers with ASP.NET specifics I'd recommend you the following change to your method:
public static string GetFileData(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
return File.ReadAllText(filePath);
}
and then when you need to consume it from a web application:
public ActionResult Foo()
{
var result = MyClass.GetFileData(Server.MapPath("~/foo/bar.txt"));
...
}
and when you need to consume it in a WinForms application:
protected void Button1_Click(object sender, EventArgs e)
{
var filePath = Path.Combine(Environment.CurrentDirectory, "bar.txt");
var result = MyClass.GetFileData(filePath);
...
}
In a utility class I would remove the dependency on any web-related stuff.
For a path relative to the application root I would use:
Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, fileName)
which will give you what you probably want (a path relative to the web root directory in a web application; relative to the path containing the executable in a WinForms app, ...)
If want to rewrite that method in asp.net mvc way, you could rewrite it this way and remove coupling with HttpContext class
public static string GetFileData(HttpRequestBase request, string filePath)
{
if (!File.Exists(request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
And generally in MVC HttpContext, HttpRequest and HttpResponse are abstracted into HttpContextBase, HttpRequestBase and HttpResponseBase accordingly

Resources