ASP.Net MVC FilePathResult Failing in IE - asp.net-mvc

I have a controller action that returns a pdf document. Depending on a site switch variable it will choose one of a number of source files, rename it and return it to the browser. This works perfectly on Chrome and Firefox but, on IE8, the download dialog appears and then the following error messagebox...
"Internet Explorer cannot download
FullTermsAndConditions from
www.mydomain.com.
Internet Explorer was not able to open
this Internet site. The requested site
is either unavailable or cannot be
found. Please try again later."
This is my code...
public ActionResult FullTermsAndConditions()
{
var targetFileName = LookupTargetFileName();
var fullPath = System.IO.Path.Combine(DownloadsPath, LookupSourceFileName());
var result = File(fullPath, "application/pdf");
result.FileDownloadName = targetFileName;
return result;
}
Can anyone see what I've done wrong?
Further information...
I added the following code to my controller to view the headers...
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
WriteResponseHeadersToFile(#"C:\temp\ResponseHeaders.txt", filterContext.HttpContext.Response);
}
private static void WriteResponseHeadersToFile(string fileName, System.Web.HttpResponseBase response)
{
using (StreamWriter writer = new StreamWriter(fileName, true))
{
writer.Write("Reponse started # ");
writer.WriteLine(DateTime.Now.ToShortTimeString());
var allHeaders = response.Headers;
for (int i = 0; i < allHeaders.Count; i++)
writer.WriteLine("{0} = {1}", allHeaders.Keys[i], allHeaders[i]);
writer.Close();
}
}
This was the result...
Reponse started # 09:02
Server = Microsoft-IIS/7.0
X-AspNetMvc-Version = 1.0
Content-Disposition = attachment; filename="Full Terms and Conditions.pdf"

Success!
The problem is with client-side caching. One of my parent controller classes had the following code (in an attempt to stop a user going 'back' once logged out)...
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
Response.Buffer = true;
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ExpiresAbsolute = DateTime.Now.AddDays(-1d);
Response.Expires = -1500;
Response.Cache.SetETag(DateTime.Now.Ticks.ToString());
}
If, in IE8, the file you're downloading cannot be opened in the browser it will attempt to save a temporary copy. This is in violation of the no-cache header. Removing this header solves the problem.

Related

MvcRazorToPdf save as MemoryStream or Byte[]

I'm using MvcRazorToPdf in a Azure website and create my PDF's and output them in the browser.
Now i'm creating a new function to directly email the PDF as attachment (without output them in the browser).
Does anybody know if it is possible to save the PDF (with MvcRazorToPdf) as a MemoryStream or Byte[]?
I think you can handle this in ResultFilter, I used below code to allow user to download file and prompt for download popup, in this way you can grab all your memory stream and store somewhere to send email afterwords.
public class ActionDownloadAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.AddHeader("content-disposition", "attachment; filename=" + "Report.pdf");
base.OnResultExecuted(filterContext);
}
}
[ActionDownload]
public ActionResult GeneratePdf()
{
List<Comment> comments = null;
using (var db = new CandidateEntities())
{
comments = db.Comments.ToList();
}
return new PdfActionResult("GeneratePdf", comments);
}
I have implemented something like that. So basically I have not been changing my method to output PDF. What I have done is used restsharp to make request at URL where I get PDF then what you have is in lines of (this is partial code only so you can get idea )
var client = new RestClient(IAPIurl);
var request = new RestRequest(String.Format(IAPIurl_generatePDF, targetID), Method.GET);
RestResponse response = (RestResponse) client.Execute(request);
// Here is your byte array
response.RawBytes
Otherwise you can use my answer from here where I discussed directly returning a file.
Hope this helps!

MVC3 Redirect Loop

I have been searching for a solution to this issue for over 10 hours with no answer. In my application I am using the [requirehttps] attribute. When clicking an action method decorated with this attribute I get “cannot display the webpage" in IE. After digging into that issue I saw that I was receiving infinite 302 calls in Fiddler, which would eventually timeout and cause that error. So I decided to create a custom attribute and physically create the https call. I am using IIS Express and successfully created a certificate and binded the port. If I call this URL directly through the browser everything works fine. This is the code I have been using to redirect the request.
public class HttpsAttribute : System.Web.Mvc.RequireHttpsAttribute
{
public bool RequireSecure = false;
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext){
var builder = new UriBuilder(HttpContext.Current.Request.Url);
if (RequireSecure){
// redirect to HTTP version of page
builder.Scheme = Uri.UriSchemeHttps;
builder.Port = 44300;
filterContext.Result = new RedirectResult(builder.Uri.ToString());
}
else{
// non secure requested
if (filterContext.HttpContext.Request.IsSecureConnection){
HandleNonHttpRequest(filterContext);
}
}
}
protected virtual void HandleNonHttpRequest(AuthorizationContext filterContext){
if (String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)){
// redirect to HTTP version of page
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
}
When I set a breakpoint to find out what the builder value is, it is correct. And it is from here I receive an infinite redirect loop. The strange thing is that the first request is never correct when I look at the URL within the browser. It is as if UriBuilder is not sending the correct URL. Any ideas? This is driving me nuts.
The redirect loop is occurring because the code is only checking if the HTTPS redirect is required, not if the current request is already HTTPS (i.e. the redirect has already happened).
if (RequireSecure && !filterContext.HttpContext.Request.IsSecureConnection){
// redirect to HTTP version of page
builder.Scheme = Uri.UriSchemeHttps;
builder.Port = 44300;
filterContext.Result = new RedirectResult(builder.Uri.ToString());
}
else{
// non secure requested
if (filterContext.HttpContext.Request.IsSecureConnection){
HandleNonHttpRequest(filterContext);
}
}
Although the RequireHttps should work correctly for this, unless you need to redirect to the port specified.
EDIT:
Refactored attribute
public class HttpsAttribute : System.Web.Mvc.RequireHttpsAttribute
{
public bool RequireSecure = false;
public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
{
var requestUri = HttpContext.Current.Request.Url;
var requestIsSecure = HttpContext.Current.Request.IsSecureConnection;
if (RequireSecure && !requestIsSecure)
filterContext.Result = Redirect(requestUri, Uri.UriSchemeHttps, 44300);
else if (!RequireSecure && requestIsSecure)
filterContext.Result = Redirect(requestUri, Uri.UriSchemeHttp, 80);
}
private RedirectResult Redirect(Uri uri, string scheme, int port)
{
return new RedirectResult(new UriBuilder(uri) { Scheme = scheme, Port = port }.Uri.ToString());
}
}

ASP.NET MVC 3 File download: Not Working in IE8

I have the Download that simply serves a static zip file from the local file system that works in Chrome and Firefox, but not in IE8.
The website is running on localhost with SSL, but I am getting the following error message in IE.
Unable to download Download/ from localhost.
Unable to open this Internet site. The requested site is either
unavailable or cannot be found. Please try again later.
public ActionResult Download(long batchID)
{
var batchFilePath = string.Format(BatchOrderReportsFolder + "\\Batch-{0}\\Batch-{0}.zip", batchID);
if (!System.IO.File.Exists(batchFilePath)) {
return RedirectToAction("Index", "Error");
}
return File(batchFilePath, "application/zip", Path.GetFileName(batchFilePath));
}
Here's what ultimately worked for me. In my case, there was a global ActionFilter on OnActionExecuted that was setting cache-control to "no-cache".
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var browserInfo = Request.Browser.Browser;
if (filterContext.Result is FileResult) {
filterContext.HttpContext.Response.CacheControl = browserInfo == "IE" ? "private" : "no-cache";
}
}
The information in the following question should help you...
Struts application - unable to download file through https on IE

Create Custom 301 Redirect Page

I am attempting to write a 301 redirect scheme using a custom route (class that derives from RouteBase) similar to Handling legacy url's for any level in MVC. I was peeking into the HttpResponse class at the RedirectPermanent() method using reflector and noticed that the code both sets the status and outputs a simple HTML page.
this.StatusCode = permanent ? 0x12d : 0x12e;
this.RedirectLocation = url;
if (UriUtil.IsSafeScheme(url))
{
url = HttpUtility.HtmlAttributeEncode(url);
}
else
{
url = HttpUtility.HtmlAttributeEncode(HttpUtility.UrlEncode(url));
}
this.Write("<html><head><title>Object moved</title></head><body>\r\n");
this.Write("<h2>Object moved to here.</h2>\r\n");
this.Write("</body></html>\r\n");
This is desirable, as in my experience not all browsers are configured to follow 301 redirects (although the search engines do). So it makes sense to also give the user a link to the page in case the browser doesn't go there automatically.
What I would like to do is take this to the next level - I want to output the result of an MVC view (along with its themed layout page) instead of having hard coded ugly generic HTML in the response. Something like:
private void RedirectPermanent(string destinationUrl, HttpContextBase httpContext)
{
var response = httpContext.Response;
response.Clear();
response.StatusCode = 301;
response.RedirectLocation = destinationUrl;
// Output a Custom View Here
response.Write(The View)
response.End();
}
How can I write the output of a view to the response stream?
Additional Information
In the past, we have had problems with 301 redirects from mydomain.com to www.mydomain.com, and subsequently got lots of reports from users that the SSL certificate was invalid. The search engines did their job, but the users had problems until I switched to a 302 redirect. I was actually unable to reproduce it, but we got a significant number of reports so something had to be done.
I plan to make the view do a meta redirect as well as a javascript redirect to help improve reliability, but for those users who still end up at the 301 page I want them to feel at home. We already have custom 404 and 500 pages, why not a custom themed 301 page as well?
It turns out I was just over-thinking the problem and the solution was much simpler than I had envisioned. All I really needed to do was use the routeData to push the request to another controller action. What threw me off was the extra 301 status information that needed to be attached to the request.
private RouteData RedirectPermanent(string destinationUrl, HttpContextBase httpContext)
{
var response = httpContext.Response;
response.CacheControl = "no-cache";
response.StatusCode = 301;
response.RedirectLocation = destinationUrl;
var routeData = new RouteData(this, new MvcRouteHandler());
routeData.Values["controller"] = "Home";
routeData.Values["action"] = "Redirect301";
routeData.Values["url"] = destinationUrl;
return routeData;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
// Do stuff here...
if (this.IsDefaultUICulture(cultureName))
{
var urlWithoutCulture = url.ToString().ToLowerInvariant().Replace("/" + cultureName.ToLowerInvariant(), "");
return this.RedirectPermanent(urlWithoutCulture, httpContext);
}
// Get the page info here...
if (page != null)
{
result = new RouteData(this, new MvcRouteHandler());
result.Values["controller"] = page.ContentType.ToString();
result.Values["action"] = "Index";
result.Values["id"] = page.ContentId;
}
return result;
}
I simply needed to return the RouteData so it could be processed by the router!
Note also I added a CacheControl header to prevent IE9 and Firefox from caching the redirect in a way that cannot be cleared.
Now I have a nice page that displays a link and message to the user when the browser is unable to follow the 301, and I will add a meta redirect and javascript redirect to the view to boot - odds are the browser will follow one of them.
Also see this answer for a more comprehensive solution.
Assuming you have access to the HttpContextBase here's how you could render the contents of a controller action to the response:
private void RedirectPermanent(string destinationUrl, HttpContextBase httpContext)
{
var response = httpContext.Response;
response.Clear();
response.StatusCode = 301;
response.RedirectLocation = destinationUrl;
// Output a Custom View Here (HomeController/SomeAction in this example)
var routeData = new RouteData();
routeData.Values["controller"] = "Home";
routeData.Values["action"] = "SomeAction";
IController homeController = new HomeController();
var rc = new RequestContext(new HttpContextWrapper(context), routeData);
homeController.Execute(rc);
response.End();
}
This will render SomeAction on HomeController to the response.

Mini profiler not displaying ajax request information?

I'm using the mini-profiler on my asp.net MVC 3 application. I've implemented the profiler using the mvc nuget package. Everything works ok for standard page requests i get profile information sql everything.
However ajax requests don't seem to display initially. Just to let me confirm the request are completed without error. I've debugged this and they complete they also return http 200 responses in fiddler.
There is no request by the mini profiler that accompanies the ajax request. When i then navigate to another page i.e. a standard page request all the ajax request that were made on the last page are now displayed.
This is my mini profiler configuration page in App_Start
public static class MiniProfilerPackage
{
public static void PreStart()
{
//Setup sql formatter
MiniProfiler.Settings.SqlFormatter = new OracleFormatter();
//Make sure the MiniProfiler handles BeginRequest and EndRequest
DynamicModuleUtility.RegisterModule(typeof(MiniProfilerStartupModule));
//Setup profiler for Controllers via a Global ActionFilter
GlobalFilters.Filters.Add(new ProfilingActionFilter());
//Settings
MiniProfiler.Settings.PopupShowTimeWithChildren = true;
MiniProfiler.Settings.PopupShowTrivial = false;
//Ignore glimpse details in miniprofiler
var ignored = MiniProfiler.Settings.IgnoredPaths.ToList();
ignored.Add("Glimpse.axd");
MiniProfiler.Settings.IgnoredPaths = ignored.ToArray();
}
public static void PostStart()
{
// Intercept ViewEngines to profile all partial views and regular views.
// If you prefer to insert your profiling blocks manually you can comment this out
var copy = ViewEngines.Engines.ToList();
ViewEngines.Engines.Clear();
foreach (var item in copy)
{
ViewEngines.Engines.Add(new ProfilingViewEngine(item));
}
}
}
public class MiniProfilerStartupModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += (sender, e) =>
{
var request = ((HttpApplication)sender).Request;
MiniProfiler.Start();
};
//Profiling abadened if user is not in the admin role
context.PostAuthorizeRequest += (sender, e) =>
{
if (!context.User.IsInRole("Admin"))
MiniProfiler.Stop(discardResults: true);
};
context.EndRequest += (sender, e) =>
{
MiniProfiler.Stop();
};
}
public void Dispose() { }
}
Is there some configuration that is required or potential issues I should be aware of?
In the end i figured out that the Miniprofiler JavaScript block that is added to the page must be below the pages jquery reference.
My JavaScript reference are all added at the end of the page for best practice performance reasons.
But I'd left the #MvcMiniProfiler.MiniProfiler.RenderIncludes() in the page header. Moving it below the jquery script ref at the bottom of the page fixed my problem.
Just found out that the same situation repeat if
$.ajaxSetup({
global: false
});

Resources