Serving a view as a file in asp.net mvc - asp.net-mvc

I'm trying to send a view that contains a html table as a downloadable file to the user, as an excel file.
I keep getting the error "Server cannot set content type after HTTP headers have been sent.". I can't figure out what's going wrong...
Here's some code:
Excel.aspx:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<html>
<head runat="server">
<title>Excel</title>
</head>
<body>
....
</body>
</html>
ControllerAction:
public FileResult Excel()
{
string view = RenderViewToString(this.ControllerContext, "~/Views/Shared/Excel.aspx", null, this.ViewData, this.TempData);
MemoryStream stream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(view));
string mimetype = RainbowsDotNet.FileHandling.MimeType.GetMimetypeFromExtension(".xls");
FileStreamResult filestreamresult = new FileStreamResult(stream, mimetype);
filestreamresult.FileDownloadName = "Employees_{0}.xls".FormatWith(DateTime.Now.ToString("ddMMyyyy_HHmmss"));
return filestreamresult;
}
While debugging, string "view" contains:
"\r\n<html>\r\n<body>............................"
Any idea?
I do about the exact same thing with a blob and that nicely returns a document to download.

You could get around this issue by putting this in the controller. It allows you to put HTML in a view, and then send it to the browser.
public ActionResult Excel()
{
this.Response.AddHeader("Content-Disposition", "Employees_{0}.xls".FormatWith(DateTime.Now.ToString("ddMMyyyy_HHmmss")));
this.Response.ContentType = "application/vnd.ms-excel";
//Do model stuff
Model model = new Model();
return View(model);
}
Sounds hacky? It is a little. I had the same problem you mentioned, and the question Jeff points to is also one of mine. :)
As mentioned in my comment, you should ensure that your view does not have the following:
<html>
<head>
...
</head>
<body>
</body>
</html>
None of this is needed, and may result in your page being rendered as HTML, rather than the Excel document being returned. So all you'll have rendered is the actual table tags and everything inside.

Did you use the RenderViewToString method from this post: Render a view as a string?
If you did then there is a Response.Flush in that code which is sending the headers. Buffering is enabled by default but if you call Response.Flush then everything is sent down to the client. And then why you try to send the file with the updated headers, you get that error.

Related

Changes to VM after POST not reflected in the Page [duplicate]

I want to send a message to userID=3 by going to /MyController/Message/3
This executes Message() [get] action, I enter some text in the text area and click on Save to post the form
Message() [post] action saves the changes, resets the value of SomeText to empty string and returns to the view.
At this point I expect the text area to be empty because I have set ViewData["SomeText"] to string.Empty.
Why is text area value not updated to empty string after post action?
Here are the actions:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Message(int ID)
{
ViewData["ID"] = ID;
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Message(int ID, string SomeText)
{
// save Text to database
SaveToDB(ID, SomeText);
// set the value of SomeText to empty and return to view
ViewData["SomeText"] = string.Empty;
return View();
}
And the corresponding view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm())
{ %>
<%= Html.Hidden("ID", ViewData["ID"])%>
<label for="SomeText">SomeText:</label>
<%= Html.TextArea("SomeText", ViewData["SomeText"]) %>
<input type="submit" value="Save" />
<% } %>
</asp:Content>
The problem is that your ModelState is re-filled with the posted values.
What you can do is clear it on the Action that has the Post attribute :
ModelState.Clear();
The problem is the HtmlHelper is retrieving the ModelState value, which is filled with the posted data. Rather than hacking round this by resetting the ModelState, why not redirect back to the [get] action. The [post] action could also set a temporary status message like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Message(int ID, string SomeText)
{
// save Text to database
SaveToDB(ID, SomeText);
TempData["message"] = "Message sent";
return RedirectToAction("Message");
}
This seems to me like more correct behaviour.
The html helpers read the value from the ModelState. And there's no elegant way to override this behaviour.
But if you add this line after SaveToDB(ID, SomeText), it should work :
ModelState["SomeText"].Value =
new ValueProviderResult("", "", CultureInfo.CurrentCulture);
I tried everything, but only worked when I did something like this:
ModelState.Clear();
//This will clear the address that was submited
viewModel.Address = new Address();
viewModel.Message = "Dados salvos com sucesso!";
return View("Addresses", ReturnViewModel(viewModel));
Hope this helps.
Instead of using ModelState.Clear() which clears the whole modelstate, you can do ModelState.Remove("SomeText"), if you want to. Or render the Input without the htmlhelper-extensions.
They are designed to take the Value from ModelState instead of the Model (or viewdata).
That is a clientside behavior. I would recommend using javascript. If you use JQuery, you can do it like this:
<script type="text/javascript">
$(function(){ $("#SomeText").val("");});
</script>
I don't use Javascript anymore, but I believe in regular JS that it is like:
document.getElementById("SomeText").value = "";
(You would do this on one of the load events.
<body onload="...">
Hope this helps.
I am fairly certain the textarea is grabbing the value from the Request.Form under the hood since ViewData["SomeText"] is empty.
Is it possible that the model state has been updated with an error? I believe that it will pull the attempted value from the model state rather than from view data or the model if the model state isn't valid.
EDIT:
I'm including the relevant section of the source code from the TextArea HtmlHelper extension below. It appears to me that it does exactly what I expected -- if there has been a model error, it pulls the value from the model state, otherwise it uses it from ViewData. Note that in your Post method the "SomeText" key shouldn't even exist until you set it, i.e., it won't be carried forward from the version of the code that responds to the GET.
Since you explicitly supply a value to the ViewData, useViewData should be false, attemptedValue should be false unless an error has been set in the model state.
// If there are any errors for a named field, we add the css attribute.
ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(name, out modelState)) {
if (modelState.Errors.Count > 0) {
tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
}
}
// The first newline is always trimmed when a TextArea is rendered, so we add an extra one
// in case the value being rendered is something like "\r\nHello".
// The attempted value receives precedence over the explicitly supplied value parameter.
string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.SetInnerText(Environment.NewLine + (attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : value)));
return tagBuilder.ToString(TagRenderMode.Normal);
Do s.th. like this:
add:
ModelState.Clear();
before the return statement of the submit buttons action method. Works for me. It could work for you.

MVC - FileContentResult sends corrupted pdf

I have a simple action method that returns a PDF document, that gets shown in an <iframe> with an <embed> tag, and every few calls to this method will return a corrupted PDF. (I've determined its corrupted by using dev tools to save the response from the server)
Action Method:
public FileContentResult GetPdfReport(string Id)
{
Response.AppendHeader("Content-Disposition", "inline; filename=report.pdf");
var content = System.IO.File.ReadAllBytes(Server.MapPath("~/Reports/Testfile.pdf"));
System.IO.File.WriteAllBytes(Server.MapPath("~/Reports/debugReport.pdf"), content);
return File(content, "application/pdf");
}
View Content:
<embed id="widgetResponsePdf" src="#Url.Action("GetPdfReport", "WidgetResponse", new { Id = "123" })" type="application/pdf" onmouseout="mouseOutHandler();" />
The files TestFile.pdf and debugReport.pdf open just fine when I get a corrupted PDF, and there is no difference in the request and response header between the normal request/response and the corrupted request/response.
Is there some setting in IIS that I am missing that could be causing the inconsistent behavior between requests, or could this be caused solely by a network issue?
In our case, the IFrame has a src attribute that points to a partial view that loads the <embed> content, which then has a src attribute that points to the actual PDF file instead of a PartialView that returns a FileContentResult. The example below has been simplified from our actual implementation
Partial View
<iframe> <!-- iframe is actually loaded from another partial view, it is show here to simplify things -->
<embed
src='#Url.Content(Model.ReportFileName)'
type="application/pdf">
</embed>
</iframe>
Controller
public PartialView GetPdfReport(string Id)
{
var model = PDFResponseModel
{
ReportFileName = Server.MapPath("~/Reports/Testfile.pdf")
};
return PartialView(model);
}
Due to a site restriction for IE support, our users (intranet site) are required to have AdobeAcrobat installed to view PDF's. While this solution works for us, this may not be desirable for internet facing websites.

Asp.Net MVC ActionResult with File

In Asp.Net MVC, we need to show a Html page, but also when that page shows, download a file too as the result of a form post.
Is there a kind of ActionResult that both renders HTML, but also cause the browser to download a file? Think of a page that shows "Here's your requested file" and the file starts to download.
Basically a combination of ActionResult and FileResult in one.
Here's a controller example that returns a file. I added an Iframe to the view that targets the controller method. I set the hidden attribute so the iframe doesn't show anywhere in the page. Hope you can use the solution. It seems to work very smoothly.
[HttpGet]
public FileResult GetPDF()
{
string fileName = "test.pdf";
string filePath = HttpContext.Server.MapPath(string.Format("~/Content/{0}", fileName));
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
Add the following code to your view.
<iframe hidden="hidden" src="#Url.Content("~/Home/Home/GetPDF")"></iframe>

Render string as MVC View

I have a simple Email Composer class where I get all Application Emails content from.
In this example, it sends the products to the email.
Now, I want to print them as well, and I'm trying to re-use the same method to grab the full html from the Email Composer and output it to a View.
the controller action
public ActionResult PrintRules()
{
var products = rep.ListAllProductsByCompanyId(currentCompany.company_id);
string body = mail.GetProductRules(products);
ViewBag.email = HttpUtility.HtmlEncode(body);
return View();
}
the view is:
#{
Layout = null;
string email = HttpUtility.HtmlDecode(ViewBag.email);
}
#Html.Raw(email)
<script>
window.print();
</script>
If I pass the body as a Model I do get errors on the parser, so I'm using the ViewBag instead.
as outputs:
#Html.Raw(email) will output nothing at all
#Html.Raw(email.Length) will output 17463
#email will output the code but the browser outputs it, does not parse it (image below)
What am I missing? I know it must be a really simple thing, but I'm totally blank...
browser output from using #email
Try this
#(new HtmlString(mystring))
or
#MvcHtmlString.Create(ViewBag.Stuff)

How do I use a Button(Non Submit) Click Event to call a method on my Controller?

I feel ridiculous asking this question but here goes, I am trying to make a very simple ASP.NET MVC 5 app. My first of it's kind. I want to have a button that when clicked does something but doesn't change the user's view or at most returns a "Email has been submitted" message.
My problem is I can't figure out how to wire a button to an "event" or "action" that doesn't change the view(i.e. using #Html.ActionLink()) or is a Submit button. Every example I find is also of a Submit button.
Thanks for any help.
EDIT
This is still not working for me. I'll post my code below. My effort is based on what was said here and on the linked post. Also, FYI, I can make it work with `#Html.ActionLink("link text", "actionname") but that appears as a link and I am trying to use a "Button".
Index.cshtml
#{
ViewBag.Title = "Home Page";
}
<div class="hero-unit">
<h3>Marketing & Communications Project Request Form</h3>
<p>User: <strong>#Context.User.Identity.Name</strong></p>
</div>
<div class="row">
<div class="span4">
#Html.ActionLink("Send Email", "SendEmail") #*this line works*#
<input id="SendEmail" class="btn" type="button" value="SendEmail" /> #*this line does not
</div>
</div>
<script type="text\javascript">
$(function(){
var url = '#Url.Action("SendEmail", "HomeController")';
$("input#SendEmail").on('click', function() {
$.ajax({
url: url
})
});
});
</script>
HomeController
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
public ActionResult SendEmail()
{
//Code to create and send email here
return View("Index");
}
}
OK, say you have the following button, written in HTML:
<input type="button" id="MyButton" value="Click Me />
You can use jQuery to hook up to the click event:
$(function(){
$("input#MyButton").on('click', function(){
// Do what you want.
});
});
If you use a HTML helper, such as HTML.TextBoxFor(), as long as you know the id of the input that is generated, you can use the same jQuery code to hook up the click event.
EDIT:
You can place that jQuery code either in the view e.g.
<script type="text/javascript">
$(function(){
$("input#MyButton").on('click', function(){
// Do what you want.
});
});
</script>
Usually you find script code placed near the bottom of the view, but you can place it anywhere you like really.
Or you could place that jQuery in a separate JavaScript (*.js) file. Just make sure you add the following to the <head> section in _Layout.cshtml:
<script type='text/javascript' src='#Url.Content("~Scripts/YourFile.js")' ></script>
_Layout.cshtml is a master layout file that is used across your project (assuming you have picked one of the usual ASP.NET MVC project templates in Visual Studio).
EDIT:
Regards the jQuery reference, in _Layout.cshtml you can add this, if jQuery is not already referenced:
<script type='text/javascript' src='#Url.Content("~Scripts/jquery.version.js")' ></script>
Just replace the filename for the correct one you have. Now, things get interesting if your MVC app uses bundling. That's a bit out of my knowledge bank, so might be better to look at other SO questions that talk about CSS/JavaScript bundling in MVC apps.
You could simply achieve with this below sample code using jQuery ajax(post) call
<a id="btn-send-mail">SendEmail</a>
JS
$(document).ready(function(){
$('#btn-send-mail').click(function(e){
e.preventDefault();
var emailData={};
emailData.toMail='sample#testmail.com';
$.post('/Mail/Send/',{data:emailData}, function(result){
if(result.status==true){
alert('Email submitted successfully');
}});//end of post
});//end of click
});//end of ready
Controller code
public class MailController
{
[HttpPost]
public JsonResult Send(Email obj)
{
//Code for sending email
return Json(new {status=true});
}
}
If you don't want to use normal jquery call you can take advantage of Razor #Ajax.ActionLink.
You just set up the link like a normal Html.ActionLink and then create a controller action that sends your email, since this call is asynchronous it wont refresh the page.
#Ajax.ActionLink("Send Email", // <-- Text to display
"SendEmail", // <-- Action Method Name
new AjaxOptions
{
UpdateTargetId="", // <-- Used if you want to update up, DOM element ID to update
HttpMethod = "GET" // <-- HTTP method
})
I would recomend doing the jquery way but here is an example using Ajax.ActionLink

Resources