1

I'm working on a site that I've inherited that's built with ASP.Net, which I'm only slightly familiar with. One of the pages allows for a link to a document (word or pdf) that, when clicked on, prompts the user to save or open the file, but does not reveal the true path of the file. This way it prevents users from pasting a url to a file - the link is to an aspx file that checks for a valid session, and then retrieves the file.

Anyway, because there's a lot of legacy code, I need to do this with a bunch of static htm files as well, however these files need to be displayed rather than prompting the user to save them, which is what happens now. I tried changing the content type to application/text, application/html, text/html, etc. and that didn't work, then tried adding a response header of content-disposition inline. When I do that, build, and try linking to the file, I get a couple of runtime exceptions:

[FormatException: Input string was not in a correct format.] Microsoft.VisualBasic.CompilerServices.Conversions.ParseDecimal(String Value, NumberFormatInfo NumberFormat) +206 Microsoft.VisualBasic.CompilerServices.Conversions.ToLong(String Value) +110 [InvalidCastException: Conversion from string "inline; filename=\" + myFile + " to type 'Long' is not valid.] Microsoft.VisualBasic.CompilerServices.Conversions.ToLong(String Value) +428 cmswebasp.CMSModdoclinks.DownloadFile(String file) +1704 cmswebasp.CMSModdoclinks.Page_Load(Object sender, EventArgs e) +625 System.Web.UI.Control.OnLoad(EventArgs e) +99 System.Web.UI.Control.LoadRecursive() +50 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627 

Here's a snippet of code from the page:

Response.AddHeader("Content-Disposition", "inline; filename=" & file) Dim fi As New FileInfo(myFile) Response.AddHeader("Content-Length", fi.Length) Dim contentType As String = "" Dim fileExt As String = file.Split(".")(1) Select Case fileExt Case "htm" contentType = "application/text" Case Else contentType = "application/octet-stream" End Select Response.ContentType = contentType Response.WriteFile(myFile) 

Do I have to do something with an htmlwriter object or something? Can't I just have it open a new browser window with the file displaying or does it have to prompt the user if used in this way??

3 Answers 3

2

Scrap the full page (.aspx) approach in place of using a generic handler (.ashx). An .aspx page is going to do a lot of built-in loading to initialize all the state that would normally be used in an ASP.NET web page, while the generic handler only initializes the bare minimum to send output back out to the client.

You will need to also implement System.Web.SessionState.IRequiresSessionState to get your session state when using a generic handler, as it does not load the session state by default.

An example:

Public Class FileWrapper Implements System.Web.IHttpHandler, System.Web.SessionState.IRequiresSessionState Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest ' Validate session... ' Set output content type based on extension of requested file Dim fileName As String = context.Request.QueryString("file") Dim fileExt As String = fileName.Split("."c)(1).ToLowerInvariant() Select Case fileExt Case "htm", "html" context.Response.ContentType = System.Net.Mime.MediaTypeNames.Text.Html Case Else context.Response.AddHeader("Content-Disposition", "attachment; filename=" & fileName) context.Response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet End Select context.Response.WriteFile(fileName) End Sub ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable Get Return False End Get End Property End Class 

Note: If hosting on IIS 7, you will need to remove any <httpHandler> registrations from <system.web> and register them instead as <handler> registrations in <system.webServer>.

Such as (correct as necessary to use your namespace):

<system.webServer> <!-- For IIS 7 --> <handlers> <add name="FileWrapperHandler" path="FileWrapper.ashx" verb="*" type="MyNamespace.FileWrapper"/> </handlers> </system.webServer> 

Another Note: If you are developing for a IIS 7 host using Integrated pipeline (default for IIS 7), then I would suggest using (and installing if necessary) the IIS Express option for your web project. This would be found going into Properties for your web project, the Web tab from left, then in the Servers section, select Use Local IIS Web Server radio button, and check Use IIS Express below.

Use IIS Express

This will put your development environment more in sync with how your production environment will behave.

Sign up to request clarification or add additional context in comments.

14 Comments

Sorry, finally got around to implementing this and it works like a charm - I used code from dotnetperls.com/ashx in order to feed the correct filename into the handler.
My only problem, however, is that even I'm implementing IRequiresSessionState as you show above, I'm able to access the url and thus the file without being logged in... Any idea why that might be?
Never mind, I guess I just needed to write the conditional code that would prevent the user from accessing the page if their session variable wasn't set.
So this was working fine on my local instance of VS with the temp web server, but when transferring it up to our server and having them update the dll, it doesn't seem to work there. Initially it was giving parsing errors (unable create type X where X is the class name I gave my handler). Then after the admin did some more updating, it started to work, but only prompts to save and not open doc files. None of this happens on my local instance. Are their web.config settings to look out for in relation to this?
@LeviWallach What version of IIS is on the host? I didn't have to add anything locally, either, but I seem to recall there being a web.config entry for IIS 6. Looking into that, and will update answer based on what I find.
|
0

After some discussion, it appears that you may be best served using a lobotomized Page after all, instead of a generic handler, due to the possibility of users coming to the site via links within Word documents or other sources outside the browser.

We've found that the aspx page is able to recover session in this instance, whereas the ashx does not. Thus, I am providing code for an aspx solution:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load ' Validate session... ' Set output content type based on extension of requested file Dim fileName As String = Request.QueryString("file") Dim fileExt As String = fileName.Split("."c)(1).ToLowerInvariant() Select Case fileExt Case "htm", "html" Response.ContentType = System.Net.Mime.MediaTypeNames.Text.Html Case Else Response.ClearHeaders() Response.AppendHeader("Content-Disposition", "attachment; filename=" & fileName) Response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet End Select Response.WriteFile(fileName) Response.Flush() Response.Close() End Sub 

It doesn't matter whatever is placed in the aspx markup itself, since none of that will get rendered anyway.

Just a note that this may cause some log entries on the web host about trying to "access a closed stream" since we have closed the response stream, but the page will continue processing as usual. That's one of the prices to pay for essentially hijacking the normal page flow.

1 Comment

I got this working so that it will serve html inline vs. other files as attachments. However, the behavior I was experiencing before (session stickiness), I'm not seeing today. Hopefully I wasn't just imagining it! At this point it doesn't look possible, but if there is some workaround, I'd love to know!
0

The server might be throwing out your manually-set content-type header when you call Response.WriteFile(). Find out what the actual content-type header is when the client receives it. Firebug will tell you if you look under its Net tab.

If this is the case, try setting Response.ContentType after calling Response.WriteFile(). Alternatively, you could try reading the file into a string and use Response.Write() instead of WriteFile.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.