Rotativa Download with SaveAs dialog - asp.net-mvc

I am using Rotativa tool to display pdf. It works fine with the following code:
public ActionResult PreviewDocument()
{
var htmlContent = Session["html"].ToString();
var model = new PdfInfo { Content = htmlContent, Name = "PDF Doc" };
return new ViewAsPdf(model);
}
I wanted to know the way to download the pdf via browser's "save as" dialog on clicking on a button and not to display in some iframe. "new ViewAsPdf(model)" just returns the pdf data.
Thanks in advance.

You can add additional attributes to the Rotativa call like this:
return new PartialViewAsPdf("PreviewDocument", pdfModel)
{
PageSize = Size.A4,
FileName = "PDF Doc.pdf"
};
And it'll create the file for you. :)

I finally got a way to do this.
Actually rotativa's method "return new ViewAsPdf(model)" returns HttpResponseStream. Where we can hardly do something. But we can modify/alter the response once the action get executed by using custom attribute. We can override OnResultExecuted() method of action filter.
Controller's action
[HttpGet]
[ActionDownload] //here a custom action filter added
public ActionResult DownloadDocument()
{
var htmlContent = "<h1>sachin Kumar</hi>";
var model = new PdfInfo {FtContent = htmlContent, FtName = "Populate Form"};
return new ViewAsPdf(model);
}
Custom Action filter:
public class ActionDownloadAttribute : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
//Add content-disposition header to response so that browser understand to download as an attachment.
filterContext.HttpContext.Response.AddHeader("content-disposition", "attachment; filename=" + "Report.pdf");
base.OnResultExecuted(filterContext);
}
}

You can use return new ActionAsPdf. No custom attributes or anything else required.
Example: https://github.com/webgio/Rotativa/
public ActionResult PrintPreviewDocument()
{
return new ActionAsPdf("PreviewDocument") { FileName = "PDF Doc.pdf" };
}
public ActionResult PreviewDocument()
{
var htmlContent = Session["html"].ToString();
var model = new PdfInfo { Content = htmlContent, Name = "PDF Doc" };
return View(model);
}

Related

Redirect to specified action if requested action was not found

How can I redirect Action which is not found in controller into another action within the same controller? Let's say that file abc.txt is requested via http://localhost:5000/Link/GetFile/abc.txt. My controller correctly serving that file. But now, i need to handle request such as http://localhost:5000/Link/Document/abc. Of course there is no any action matched to Document so I need to invoke function Error within the same controller (including id from original request).
I tried to solve this with StatusCodePagesWithReExecute function but then my File action is not working (each request goes directly to Error function).
I have following controller:
public class LinkController : ControllerBase
{
public IActionResult GetFile(string id)
{
return DownloadFile(id);
}
public IActionResult Error(string id)
{
return File("~/index.html", "text/html");
}
private FileResult DownloadFile(string fileName)
{
IFileProvider provider = new PhysicalFileProvider(#mypath);
IFileInfo fileInfo = provider.GetFileInfo(fileName);
var readStream = fileInfo.CreateReadStream();
return File(readStream, "text/plain");
}
}
and startup configuration:
app.UseDefaultFiles();
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "application/octet-stream",
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}"
);
});
Any clues how to solve this problem?
Regards
You can use UseStatusCodePages to achieve a simple redirection whenever there's a 404. Here's what it looks like:
app.UseStatusCodePages(ctx =>
{
if (ctx.HttpContext.Response.StatusCode == 404)
ctx.HttpContext.Response.Redirect("/Path/To/Your/Action");
return Task.CompletedTask;
});
Just add this somewhere above UseMvc.
EDIT:
I´m sorry, my first answer was not correct.
IRouteCollection router = RouteData.Routers.OfType<IRouteCollection>().First();
with this, you can match an url to controller action
Create HttpContext for testing (example with injection)
private readonly IHttpContextFactory _httpContextFactory;
public HomeController(
IHttpContextFactory httpContextFactory)
{
_httpContextFactory = httpContextFactory;
}
Create the context with values
HttpContext context = _httpContextFactory.Create(HttpContext.Features);
context.Request.Path = "/Home/Index";
context.Request.Method = "GET";
Check route
var routeContext = new RouteContext(context);
await router.RouteAsync(routeContext);
bool exists = routeContext.Handler != null;
Further reading: https://joonasw.net/view/find-out-if-url-matches-action

MVC Image - Dispaly in a Tab instead of downloading to a file

I have the following controller method which gets a PNG from a web api.
public async Task<ActionResult> RealTimeUpdate(string fundName)
{
string docPath = ConfigurationManager.AppSettings["RealTimeUpdate"].Replace("{fundname}",fundName).ToString();
docPath = docPath.Replace("\\\\", "\\");
docPath = docPath.Replace("\"", "");
string url = ServiceUrl + "api/RealTime/" + fundName;
HttpResponseMessage response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var dataStream = response.Content.ReadAsStringAsync().Result;
if (dataStream == null)
return HttpNotFound();
var _buffer = JsonConvert.DeserializeAnonymousType(dataStream, new { _buffer = (byte[])null })._buffer;
// If user decides to save the file, this will help...
//Response.AddHeader("content-disposition", "filename=" + Path.GetFileName(path));
return File(_buffer, "application/png");
}
return View("Error");
}
I call it like this:
Real Time Update
As you can see, I have target="_blank", however, instead of displaying the image in a new tab, it downloads it to my documents folder. How can I get it to display in a tab?
You need a ImageController to render that.
once you have a controller you can render as follows:
public class ImageController{
public ActionResult ShowImage(string path)
{
return File(path);
}
}
in your views:
<img src="#Url.Action("Render","Image", new {id =1 // or path })" />
this answer was taken from https://stackoverflow.com/a/16142574/5586581

MVC, cannot return to the previous URL

I am working on some legacy code.
I have a form which I edit some data and when I click save, if successful I want to return to the previous form.
So my controller methods are;
public ActionResult Edit(int callDiaryId)
{
ViewBag.PreviousUrl = System.Web.HttpContext.Current.Request.UrlReferrer;
var callDiary = this.SCDCallDiaryRepository.Get(callDiaryId);
return this.View("Edit", new DiaryItemViewModel(callDiary));
}
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateOnlyIncomingValuesAttribute]
public ActionResult Edit(DiaryItemViewModel item, string previousUrl)
{
var callDiary = this.SCDCallDiaryRepository.Get(item.SCD_CallDiaryId);
callDiary.Comments = item.Comments;
callDiary.ContractId = item.ContractId;
var opStatus = this.SCDCallDiaryRepository.Update(callDiary);
if (opStatus.Status)
{
this.TempData["SuccessMessage"] = "Details updated successfully.".MessageTime();
return RedirectToAction(previousUrl);
}
this.TempData["ErrorMessage"] = "Details NOT updated.".MessageTime();
ViewBag.PreviousUrl = previousUrl;
return this.View(new DiaryItemViewModel(callDiary));
}
and the incoming value of previousUrl is
http://localhost:58384/LogDiary/Comments?companyId=11033
This works perfectly for my back button.
But after my RedirectToAction command is performed, the Bad Request error that I get is because the Url it is showing is;
http://localhost:58384/LogDiary/http%3a/localhost%3a58384/LogDiary/Comments%3fcompanyId%3d11033
How do I fix this?
I was able to do this in the Controller of my App to return the user to the previous page
public ActionResult ChangePassword()
{
var userId = this.User.Identity.GetUserId();
var viewModel = this._userService.ChangePasswordViewModelForUser(userId);
viewModel.PreviousPage = this.Request.UrlReferrer.AbsoluteUri;
return this.View(viewModel);
}
If I need to use it in a button
#Resource.Button_Cancel
I think I found the answer.
Instead of using RedirectToAction, I am now using Redirect.
You can return to the previous form by doing the below code
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateOnlyIncomingValuesAttribute]
public ActionResult Edit(DiaryItemViewModel item)
{
..your other code..
return View(item);
}
it will return to the Edit page along with the entered data
hope this helps

Returning single item in model - mvc razor

I have a finalist page that displays a list of finalists, that are clickable into a single-view page. I have the XML document being passed into a model and am spilling that data onto the main finalists page, but I can't seem to figure out how to grab the clicked finalist's id and display only that finalist on the single-view page.
Any help is appreciated, here is my controller right now:
I am trying to pass the newly created model in to the singleView class, but I'm not sure how to filter it to know which finalist was clicked on, and which finalist to display on the single-view page.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Xml.Linq;
using ProjectX_Awards.Models;
namespace ProjectX_Awards.Controllers
{
public class FinalistsController : Controller
{
//
// GET: /Finalists/
public ActionResult Index()
{
var doc = XElement.Load(HttpContext.Server.MapPath("~/finalist.xml"));
var finalists = doc.Descendants("finalist").Select(f => new Models.Finalist()
{
Id = (int)f.Attribute("id"),
Name = f.Descendants("name").First().Value,
Description = f.Descendants("description").First().Value,
Link = f.Descendants("webLink").First().Value,
Photo = f.Descendants("photoUrl").First().Value
});
return View(finalists);
}
public ActionResult SingleView(Finalist model)
{
var singleFinalist = model;
return View(singleFinalist);
}
}
}
If you want to pass a complete model, you need to do a POST to that action-method. The easiest way is to make sure you post all the values in a form-element to the specified action. However, the best thing would be to pass an Id to your SingleView-method. This allows you to do a get to that page, instead of having to post a complete object:
public ActionResult SingleView(int id)
{
var singleFinalist = model;
var doc = XElement.Load(HttpContext.Server.MapPath("~/finalist.xml"));
var finalist = doc.Descendants("finalist").Where(f => (int)f.Attribute("id") == id)
.Select(f => new Models.Finalist()
{
Id = (int)f.Attribute("id"),
Name = f.Descendants("name").First().Value,
Description = f.Descendants("description").First().Value,
Link = f.Descendants("webLink").First().Value,
Photo = f.Descendants("photoUrl").First().Value
})
.FirstOrDefault;
return View(finalist);
}
Then in your finalists-page you can just emit an a-tag like this:
#foreach(var finalist in Model)
{
Detail
// or
#Html.ActionLink("SingleView", "YourController", new { id = finalist.id })
}
EDIT Adding a simple caching method, so the XML is not reloaded everytime:
public ActionResult Index()
{
return View(GetAllFinalists());
}
public ActionResult SingleView(int id)
{
var doc = XElement.Load(HttpContext.Server.MapPath("~/finalist.xml"));
var finalist = GetAllFinalists().Where(f => f.Id == id)
.FirstOrDefault;
return View(finalist);
}
private IEnumerable<Models.Finalist> GetAllFinalists()
{
if (HttpContext.Current.Application["finalists"] == null)
{
var doc = XElement.Load(HttpContext.Server.MapPath("~/finalist.xml"));
HttpContext.Current.Application["finalists"] = doc.Descendants("finalist")
.Select(f => new Models.Finalist()
{
Id = (int)f.Attribute("id"),
Name = f.Descendants("name").First().Value,
Description = f.Descendants("description").First().Value,
Link = f.Descendants("webLink").First().Value,
Photo = f.Descendants("photoUrl").First().Value
});
}
return (IEnumerable<Models.Finalist>)HttpContext.Current.Application["finalists"];
}

View doesn't refresh after RedirectToAction is done

Here is my problem:
[HttpPost]
public ActionResult AddData(CandidateViewModel viewModel)
{
var newCandidateId = 0;
newCandidateId = this._serviceClient.AddCandidate(viewModel);
return RedirectToAction("DisplayCandidate",new {id=newCandidateId});
}
public ActionResult DisplayCandidate(int id)
{
var candidateViewModel= this._serviceClient.GetCandidate(id);
return View(candidateViewModel);
}
After filling the form viwemodel sends to server. After data were stored, flow is redirected to DisplayCandidate action and it goes there but page didn't refresh. I don't understand why! Help, please.
Because you are using Ajax Post
public ActionResult AddData(CandidateViewModel viewModel)
{
var newCandidateId = 0;
newCandidateId = this._serviceClient.AddCandidate(viewModel);
string ReturnURL = "/DisplayCandidate/"+newCandidateId;
return JSON(ReturnURL);
}
and in your Ajax Post Method:
Onsuccess(function(retURL){ window.location(retURL); })
This will take to the new Action and that Action will return View.
If you're using Ajax, return a script results to execute the navigation
instead of
return RedirectToAction("DisplayCandidate",new {id=newCandidateId});
try
var viewName = "/Path/ViewName";
var id = 1;
var urlNavigate = string.Format("location.href='{0}?id={1}'", viewName, id);
return new JavaScriptResult() { Script = urlNavigate };

Resources