ASP.NET MVC and linked photos on shared folder - asp.net-mvc

I'm writing asp.net mvc web application that uses a large number of .jpg files that are on a share folder outside the web server and not accessible via http protocol. How can I place image path inside img tag?

Do not put the image path inside your image tag as a parameter to a script. This is called a direct object reference and is a bad thing. Consider a naive implementation of such a script/page/controller which serves up as /image/?resource=\server\path\img.jpg.
Now what happens if someone loads /image/resource/?resource=c:\windows\system32\config\SAM? Your password database gets sent.
You do not want to use fully qualified paths at all, ideally you want to either serve all images from that directory and just accept a file name, stripping any path information from it by doing something like
string filename = Path.GetFileName(userInputtedFilename);
FileInfo file = new FileInfo(Server.Path.Combine("\\Server\share\", filename)));
That at least is safe, but of course users could browse through all the images if they're suitably named. Safer yet is to have an indirect object reference, a mapping table somewhere which maps something like a GUID to the actual filename and use that as the parameter to your serving script.
To serve a file you return a FileContentResult from your controller,
public FileContentResult GetFile(Guid indirectReference)
{
byte[] fileContents = // Resolve the file and read it from the indirect reference
mimeType = // Suitable MIME type
return File(fileContent, mimeType, fileName);
}

If the web server has regular network access to that share and can read the image files, you can create a new MVC controller Image with default action Index that takes one string parameter called fn. The action would create a new FileResult from the parameter. Then, when you generate the <img> tag, set the url to something like /image/?fn=\\share\image.png. (Don't forget to add the proper route for it, of course)
If the web server has no access to that share, but the page user has access, you can try setting the <img> tag to the file:// URL for the image. However, this is going to be fragile and might or might not work, depending on the user's OS and browser configuration.
Update: Do read the security implications #blowdart mentioned in his answer.

Related

In an asp.net-mvc site, is there a better way to get the full base URL?

I have an asp.net-mvc site and I send out a lot of email from my site. Before I used to send email from my controller and I used this code to get the base url:
protected string GetBaseUrl()
{
return string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, Url.Content("~"));
}
that is because i obviously need fully formed URL. so in a normal link on a page that i have href="/GoHere", i would want that to translate to:
"http://www.mysite.com/GoHere"
not just relative URLs given that they are going in emails like
"/GoHere"
I am now refactoring my controller to move all of this code outside of it, but i find myself passing this baseURL string around (because the function above relies on Request which is in namespace:
System.Web
and I can't seem to access this request object outside the controller class. Right now I am passing a string BaseURL all over the place so when i need to generate the emails, i can append the relative URL after the base URL but that feels very hacky
Is there a better way to get the baseURL (either through the Request object or not) outside of a controller class in an asp.net-mvc website?
UrlHelper.Action has an overload that accepts "protocol". Use this to generate full urls that conform to your routing table.
var baseUrl = Url.Action("Index", "Home", null, Request.Url.Scheme);
MSDN Source
In a default project MVC with default routing, this would return something like:
http://mydomain.com
Note that if you can't use/access the HttpRequest object, consider passing in the scheme or if you know it's always "https" (for example) then you can use a magic (hard coded) string or read it from a settings/config file.
You can access the HttpRequest object outside of the controller by using:
var request = System.Web.HttpContext.Current.Request;
On a similar note, you will have the same problem accessing the UrlHelper instance of the controller. You can create an instance like so:
var url = new UrlHelper(System.Web.HttpContext.Current.Request.RequestContext);
HttpRequest.RequestContext is new to .NET 4.0. I thought I'd not that seeing as though you haven't specified a working version. MSDN source.
The problem is determining the baseURL programmatically as it is highly relative and a website is reachable at many URLs. For example:
* http://localhost/GoHere locally
* http://localhost:8080/GoHere locally on another port
* http://127.0.0.1/GoHere locally by the loop back address
* http://10.0.10.5/GoHere the internal IP of the machine
* http://72.34.56.78/GoHere the public IP of the machine
* http://www.mysite.com/GoHere preferred public URL
* http://www.bestsiteever.com/GoHere SEO URL
It would be easier to set the baseURL in a config file, a database by customer, or whatever way you need to correlate the baseURL to the email.

Handling WebDAV requests on MVC action

I have an existing MVC3 application which allows users to upload files and share them with others. The current model is that if a user wants to change a file, they have to delete the one there and re-upload the new version. To improve this, we are looking into integrating WebDAV to allow the online editing of things like Word documents.
So far, I have been using the .Net server and client libraries from http://www.webdavsystem.com/ to set the website up as a WebDAV server and to talk with it.
However, we don't want users to interact with the WebDAV server directly (we have some complicated rules on which users can do what in certain situations based on domain logic) but go through the previous controller actions we had for accessing files.
So far it is working up to the point where we can return the file and it gives the WebDAV-y type prompt for opening the file.
The problem is that it is always stuck in read-only mode. I have confirmed that it works and is editable if I use the direct WebDAV URL but not through my controller action.
Using Fiddler I think I have found the problem is that Word is trying to talk negotiate with the server about the locking with a location that isn't returning the right details. The controller action for downloading the file is "/Files/Download?filePath=bla" and so Word is trying to talk to "/Files" when it sends the OPTIONS request.
Do I simply need to have an action at that location that would know how to respond to the OPTIONS request and if so, how would I do that response? Alternatively, is there another way to do it, perhaps by adding some property to the response that could inform Word where it should be looking instead?
Here is my controller action:
public virtual FileResult Download(string filePath)
{
FileDetails file = _fileService.GetFile(filePath);
return File(file.Stream, file.ContentType);
}
And here is the file service method:
public FileDetails GetFile(string location)
{
var fileName = Path.GetFileName(location);
var contentType = ContentType.Get(Path.GetExtension(location));
string license ="license";
var session = new WebDavSession(license) {Credentials = CredentialCache.DefaultCredentials};
IResource resource = session.OpenResource(string.Format("{0}{1}", ConfigurationManager.AppSettings["WebDAVRoot"], location));
resource.TimeOut = 600000;
var input = resource.GetReadStream();
return new FileDetails { Filename = fileName, ContentType = contentType, Stream = input };
}
It is still very early days on this so I appreciate I could be doing this in entirely the wrong way and so any form of help is welcome.
In the end it seems that the better option was to allow users to directly talk to the WebDAV server and implement the authentication logic to control it.
The IT Hit server has extensions that allow you to authenticate against the forms authentication for the rest of the site using basic or digest authentication from Office. Using that along with some other customisations to the item request logic gave us what we needed.
This is exactly what i did for a MVC 4 project.
https://mvc4webdav.codeplex.com/

How do I code PDF download from external site on Image click?

I have one "Download PDF" Image link, I am calling an action of a controller in order to allow users to download specific file from external site (so has given complete URL of PDF file link)
I have written following code, but its not working.
public virtual ActionLink OpenPDF()
{
string fileName = "http://mysite/filetodownload.pdf";
return File(fileName, "application/pdf", Server.UrlEncode(fileName);
}
This controller action gets called from an Image link.. and I can see this action gets called..
When I click on image, the code gets executed, and asks to Open/Save file, but when I say Save it says "This file cannot be downloaded"
what do you think can be wrong here.
Why don't you just point your link directly to the site
Download File
You don't need to go through a controller for this
As a side not, if you are returning a FileResult you need to pass it a stream, a byte array, or a path to a file on disk. You can't pass it a third party URL. It doesn't work like that. It is meant to work like this:
public virtual ActionLink OpenPDF()
{
string fileName = Server.MapPath("~/Download/filetodownload.pdf");
return File(fileName, "application/pdf");
}
I think
Show images in table from database in Asp.net-mvc3
Azure blobs and thumbnails
ASP.NET MVC - user managment of folder with pictures (FTP?)
links as you meet the answer.

How do you get the full URL of a file uploaded using an Upload object in Java Play?

I'm writing an image uploader in Java Play! in the style of
https://gist.github.com/1256286#file-picture-java-L3
This is great. The image is rendered using this function in the Controller object (here, Picture is a Model implementation that has metadata about an image file:
public static void show(Long id) {
Logger.info("loading id=%s", id);
Picture picture = Picture.findByKey(id);
response.setContentTypeIfNotSet(picture.contentType);
renderBinary(new ByteArrayInputStream(picture.file), picture.file.length);
}
I want to add a url member to Picture that contains the absolute URL to this function for a given picture. How can I populate this member in a way that works irrespective of where the Java Play! application is hosted?
Although I didn't try it in version 1.x I suggest to check the Router API, there are some methods which indicates, that they can optionally create an absolute route (I'm Play 2 user and have no experience with them).
There are also methods like ie. getBaseUrl() or getFullUrl(String action, Map<String,Object> args) so I just guess that they probably will help you to resolve the problem.

routing image paths through mvc

I am currently creating an MVC4 web application. This application shows products including images. I have got a company to review the SEO aspects of the site, and they have come back with a recommendation regarding product images.
Currently my image path is: folder/images/productimage/PROD_98713204_LARGE.gif
They have recommended the following: /folder/images/productimage/98713204-160x260-0-0_My+Product+Name.gif
The problem I have is that I have a large number of images on the site so it is difficult to go rename all to include product names etc. So I have thought about using the routing features within MVC, outputting the recommended in the html markup but picking the current image path shown above from the filesystem.
2 questions I have are:
Is there performance implications of using such routing to manage image paths? My site will have large traffic loads and a number of images so it is a concern.
Could someone give me an example of a route I would need to configure to achieve the above?
In order to do the routing option, you'll have to come up with some specification for how the urls map to the actual images. How many images are we talking about? 1000? 10,000? A million? If you have less than 100,000 then I'd probably go ahead and use the specification you already wrote to just go ahead and rename all the files and then use the specification to name files on the way in to the file system from now on.
The advantages of this system are that it limits the scope of the changes to the data, and you only have to affect one point of the system (when the files are on the way in). When it comes to performance, the overhead of mapping a string to another string is probably negligible, even for a large number of requests. String manipulation for short strings is very fast, but in any case you should profile the entire request if requests start taking too long and focus on the major pain points. Of course if you just rename the files, you can be sure you won't have to worry about any of this profiling.
As for creating a route to do the mapping of urls, you first have to get ASP.NET to hand the request to your code. By default ASP.NET first checks if the file exists at the location specified by the url, and if it does, it just processes the file based on the registered handler in IIS. My suggestion is to leave these settings as they are because they make very large changes to the system when you change them. For images, the handler just attempts to send the file to the client.
If the file does not exist on the disk, it then looks for a handler for the request by iterating through the route collection, which is the thing you register your routes into typically in Global.asax. Since you didn't tell us what you tried, I'm going to assume that you know how routes work. You can get pretty crafty with routes, but I'll stick to something simple:
routes.MapRoute(
name: "images",
url: "{folder}/images/productimage/{unmappedFileName}",
defaults: new { controller = "Home", action = "Images" }
);
This route will match the example url you gave. In the case that they use the actual file name however, this route will never be hit, as I have explained above. Since the SEOed file name does NOT exist however, this route will be hit and it will try to run the Images action on the Home controller (I list my entire Home controller here to remove any confusion about where these parts go):
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Images(string unmappedFileName)
{
if (String.IsNullOrWhiteSpace(unmappedFileName))
{
return HttpNotFound();
}
var fileName = MapFileName(unmappedFileName);
var diskLocation = GetDiskLocation(fileName);
return File(diskLocation, "image/png");
}
private string MapFileName(string unmappedFileName)
{
return unmappedFileName + ".png";
}
private string GetDiskLocation(string fileName)
{
var fullPath = String.Format("~/Content/themes/base/images/{0}", fileName);
var diskLocation = Server.MapPath(fullPath);
return diskLocation;
}
}
Obviously you'll need to update the file name mapping to whatever spec you decided upon. I am using what's here because there are a bunch of example files in that folder when you create a new MVC4 project.
A simple way to show that it works is to implement your Index view in the Home folder like this:
{
ViewBag.Title = "Index";
var imagesDirectory = Server.MapPath("~/Content/themes/base/images/");
var imageFileNames = Directory.GetFiles(imagesDirectory).Select(m => m.Replace(imagesDirectory, "").Replace(".png", ""));
}
<h2>Index</h2>
#foreach (var imageFileName in imageFileNames)
{
<div>#Html.ActionLink(imageFileName, "Images", new { unmappedFileName = imageFileName })</div>
}
In the future when you run into a problem like this, you should just try to figure it out first. When you ask your question, be sure to tell us what you have tried so we can get you over the next hump and point you in the right direction instead of just asking us for the code to solve your problem. In the case where you don't know where to get started, try searching for or asking a more abstract question. Who knows, they might even answer your other questions at the same time. :)
And lastly, this solution is really complicated. I don't even know how your mapping function is going to work, but I know this is complicated. It also adds a layer of complexity when debugging because now the urls you have don't directly relate to the file name on disk, and that time will add up later on. Of course there are reasons why I might favor this mapping, most notably if you intend to change the url structure in the future for further SEO changes, but then you're breaking urls on the internet and damn you for that. So really, I suggest just changing all of your file names if that is feasible.

Resources