iTextSharp .Net Action Result to download as a pdf through ajax - javascript

I am using iTextSharp to convert a razor view to a pdf that can be downloaded by way of a C# controller. This current implementation works perfectly, however I would like to pass a model from a view to the pdf controller and have the action result download within the browser
Here is my current controller:
public ActionResult Report(MyModel model)
{
// The below method is a custom implementation using iTextSharp
return new FoilPdfActionResult(model, (writer, document) =>
{
document.SetPageSize(PageSize.LETTER.Rotate());
document.NewPage();
})
{
FileDownloadName = "testing.pdf",
};
}
Here is my AJAX call:
function sendForm(model) {
$.ajax({
type: 'POST',
url: '#Url.Action("Report")',
data: model,
success: function (data) {
console.log(data);
},
error: function (xhr, textStatus, errorThrown) {
}
});
}
If I just visit the path of the controller directly at "/Report" it downloads the file correctly within the browser.
If I use the ajax call above to call the controller, it passes the model correctly and returns the pdf result through the data variable on success but does not actually download the pdf. Is is possible to have the pdf downloaded instead of being passed through the data variable?

Recently has a (2) similar situation and found that there are no real baked in ways to do it via ajax. GOOD NEWS: It's actually really easy to do and you do not need an ajax call.
What you can do is submit a form post request and in your controller post action you can generate the file in memory and use "return File()" to pump the file back to the view without the view reloading.
Here is an example using iTextSharp:
Example Model:
public class TestM
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Number { get; set; }
}
View (basic auto generated create)
#model DeleteMeWeb45.Models.TestM
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TestM</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.LastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Number, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Number, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Number, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Controller:
public ActionResult Index()
{
TestM t = new TestM();
return View(t);
}
[HttpPost]
public ActionResult Index(TestM t)
{
if(ModelState.IsValid)
{
Document doc = new Document(iTextSharp.text.PageSize.LETTER, 10, 10, 42, 30);
byte[] pdfBytes;
BaseFont bfTimes = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
Font timesBold = new Font(bfTimes, 12, Font.BOLD);
using (var mem = new MemoryStream())
{
PdfWriter wri = PdfWriter.GetInstance(doc, mem);
doc.SetMargins(20, 20, 20, 60);
doc.Open();
var orderInfoTable = new PdfPTable(2);
orderInfoTable.AddCell(new Phrase("First Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.FirstName, timesBold));
orderInfoTable.AddCell(new Phrase("Last Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.LastName, timesBold));
orderInfoTable.AddCell(new Phrase("Number:", timesBold));
orderInfoTable.AddCell(new Phrase(t.Number.ToString(), timesBold));
doc.Add(orderInfoTable);
doc.Close();
pdfBytes = mem.ToArray();
}
return File(pdfBytes, "application/pdf", "Weeeeeee_" + DateTime.Now.ToString("_MM-dd-yyyy-mm-ss-tt") + ".pdf");
}
else
{
return View(t);
}
}
In the event that you want to keep your form submit button as is (so it saves or does whatever) and want an extra button that just downloads then you would modify the above example as such:
View:
Change 1. Give the form an ID:
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "transferForm" }))
Change 2. Add a button and JS to submit the form at an alternative location:
<div class="row">
<div class="btn btn-primaryDark btn-sm" id="btnRunReport" style="min-width:120px;">Run Report</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script>
$(document).ready(function () {
$("#btnRunReport").click(function () {
$('#transferForm').attr('action', "/Home/JSFUN").submit();
});
});
</script>
}
Controller Changes:
1. Create a new controller action to handle the post request:
[HttpPost]
public ActionResult JSFUN(TestM t)
{
Document doc = new Document(iTextSharp.text.PageSize.LETTER, 10, 10, 42, 30);
byte[] pdfBytes;
BaseFont bfTimes = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
Font timesBold = new Font(bfTimes, 12, Font.BOLD);
using (var mem = new MemoryStream())
{
PdfWriter wri = PdfWriter.GetInstance(doc, mem);
doc.SetMargins(20, 20, 20, 60);
doc.Open();
var orderInfoTable = new PdfPTable(2);
orderInfoTable.AddCell(new Phrase("First Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.FirstName, timesBold));
orderInfoTable.AddCell(new Phrase("Last Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.LastName, timesBold));
orderInfoTable.AddCell(new Phrase("Number:", timesBold));
orderInfoTable.AddCell(new Phrase(t.Number.ToString(), timesBold));
doc.Add(orderInfoTable);
doc.Close();
pdfBytes = mem.ToArray();
}
return File(pdfBytes, "application/pdf", "Weeeeeee_" + DateTime.Now.ToString("_MM-dd-yyyy-mm-ss-tt") + ".pdf");
}
This way your user can use the form submit button and a export button for different purposes.

You can create a Iframe element on page and make it hidden and then bind "src" property with complete path of action, it will download your pdf for sure. I have done this many time. You can take help from this link https://www.codeproject.com/Questions/470316/how-to-download-attachment-using-Iframe

Related

Submitting dynamically added forms (MVC app)

I have a page that contains a partial view, which has dynamically added form fields (name and date-range for each guest). How do I submit all these fields to a post method, so that I get a list of all the guests (name AND date-range)?
The partial view:
#model ProjectName.Models.ViewModels.GuestCreatorViewModel
#using Res = ProjectName.Resources.Resources
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#for (int i = 1; i <= Model.NumOfGuests; i++)
{
<div class="row">
<div class="form-group">
#Html.Label(Res.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label(Res.Period, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.DateRange, new { id = "dateRangePicker", #class = "form-control custom-date-picker", #readonly = true })
#Html.ValidationMessageFor(model => model.DateRange, "", new { #class = "text-danger" })
</div>
</div>
</div>
}
<div class="form-group">
<div>
<input type="submit" value="#Res.CreateGuests" class="customBtnXSmall" />
</div>
</div>
</div>
}
<script type="text/javascript">
$('input.custom-date-picker').daterangepicker({
"showWeekNumbers": true
}, function (start, end, label) {
console.log('New date range selected: ' + start.format('YYYY-MM-DD') + ' to ' + end.format('YYYY-MM-DD') + ' (predefined range: ' + label + ')');
});
</script>
I'm guessing that I can write some javascript or an AJAX function to post the information, since it's just strings, but I'm not sure how and I'm not that skilled with javascript.
Can anyone help me make a simple post action for these dynamic form fields?
The solution was actually really simple, and already implemented. I already used model binding to bind the data to a model, so instead of the model having "name" and "daterange" attributes as string, I just changed their type to List<string> and that way it fills all the values into those lists.
public ActionResult CreateGuests([Bind(Include = "Name,DateRange,NumOfGuests")] GuestCreatorViewModel viewModel)
public List<string> Name { get; set; }
public List<string> DateRange { get; set; }

Upload Form Data And File With Jquery in ASP.Net MVC [duplicate]

This question already has answers here:
How to append whole set of model to formdata and obtain it in MVC
(4 answers)
Closed 4 years ago.
I use a code like this to receive customer information.
Along with my customer information, I must upload a file as well.
CSHTML File
#using (Html.BeginForm("AddPersonInfo", "Management", FormMethod.Post, new { enctype = "multipart/form-data", id = "frmAddPerson" })) { #Html.AntiForgeryToken()
<div id="divControls" class="form inline">
<div class="form-group">
#Html.LabelFor(model => model.FName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-12">
#Html.EditorFor(model => model.FName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LName, htmlAttributes: new { #class = "control-label col-md-2"})
<div class="col-md-12">
#Html.EditorFor(model => model.LName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FileName, htmlAttributes: new { #class = "control-label col-md-2", style = "float: right" })
<div class="col-md-12">
<input id="postedFile" type="file" data-buttonText="Select File">
#Html.ValidationMessageFor(model => model.FileName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-12">
<input style="float: right" id="btnSave" type="button" value="Save" class="btn btn-info" />
</div>
</div>
</div>
}
Java Script File
<script>
$(document).ready(function () {
$("#btnSave").click(function (e) {
e.preventDefault();
if ($("#frmAddPerson").valid()) {
var formData = new FormData();
var file = document.getElementById("postedFile").files[0];
formData.append("postedFile", file);
$.ajax({
url: "/Management/AddPersonInfo",
data: formData,
type: "Post",
dataType: "Json",
contentType: false,
processData: false,
success: function (result) {
alert("success")
},
error: function (result) {
alert("error");
}
});
}
});
});
</script>
Controller File
public ActionResult AddPersonInfo(PersonViewModel model, HttpPostedFileBase postedFile)
{
try
{
//
}
catch
{
//
}
}
In the AddPersonInfo in Controller, the file information (postedFile) will be received correctly But the form information (model) will be received null
In the ajax request, you are only adding the postedFile to your FormData()
var formData = new FormData();
var file = document.getElementById("postedFile").files[0];
formData.append("postedFile", file);
Try adding the model by hand,
var formData = new FormData();
var file = document.getElementById("postedFile").files[0];
formData.append("postedFile", file);
var model: {
'FName' : 'John',
'LName' : 'Smith'
}
formData.append('model', model);

How to pass data from a form to my controller, and then back to my client-side javascript?

I have a form in my webpage that asks for first and last name. When the user submits this form I would like the data to be passed to a function in my controller, where it is used to find the corresponding user in our Active Directory. I want to then return the matching Email address to a Javascript function, when then displays that information on my page. Is this the "correct" way to go about doing this, and if yes, how would I structure a function in my controller to accept form input, and return data to my client side javascript?
What I have so far in my controller:
public SearchResult[] searchAD(String fname, String lname)
{
Func<System.DirectoryServices.ActiveDirectory.Domain> domain = System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain;
System.DirectoryServices.DirectoryEntry root = new DirectoryEntry("LDAP://"+domain);
DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchRoot = root;
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = string.Format("(&(objectCategory=person)(objectClass=user)(givenName={0})(sn={1}))", fname, lname);
SearchResult[] results = new SearchResult['a'];
searcher.FindAll().CopyTo(results, 0);
return results;
}
And my form:
#using (Html.BeginForm("searchAD", "AD", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.firstName, "First Name", htmlAttributes: new { #class = "control-label" })
<div>
#Html.EditorFor(model => model.firstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.firstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.lastName, "Last Name", htmlAttributes: new { #class = "control-label" })
<div>
#Html.EditorFor(model => model.lastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.lastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div>
<input type="submit" value="Search" class="btn" />
</div>
</div>
}
You can Store value in TempData and get it on view
public ActionResult searchAD(String fname, String lname)
{
Func<System.DirectoryServices.ActiveDirectory.Domain> domain = System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain;
System.DirectoryServices.DirectoryEntry root = new DirectoryEntry("LDAP://"+domain);
DirectorySearcher searcher = new DirectorySearcher();
searcher.SearchRoot = root;
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = string.Format("(&(objectCategory=person)(objectClass=user)(givenName={0})(sn={1}))", fname, lname);
SearchResult[] results = new SearchResult['a'];
searcher.FindAll().CopyTo(results, 0);
TempData["Result"]=results;
return View("ViewName");
}
In View (Jquery)
$(function(){
if ('#TempData["Result"]' != '')
{
// call your function here and pass TempData Value
}
});
Above is not perfect solution for your posted issue because tempdata or viewbag are not meant for that.If you need to post data again back from control to view then its always make use of #Ajax.BeginForm
#using (Ajax.BeginForm("ActionName", "Controller", new AjaxOptions { OnComplete = "Sucess" }))
in jquery you have to write Success function like below :
function Sucess(arg) {
//arg.data
}
in arg.data you will get that object what you are passing from controller.More ever #Html.BeginForm refreshing your whole page not specific content but with With Ajax.begin form you can manage to reload only those content which is under ajax.begin form.

MVC - jQuery validation does not work on dynamically added elements

I know this kind of issue has been solved many times already, however I am unable to get mine fixed based on solutions provided.
I am building a simple library application. There is a feature to add a copy of a book, which uses jQuery to invoke controller actions and return partial views which are then added dynamically to the DOM.
The last dynamically element added is a form with additional details of a created copy. The ajax call is being triggered when a value of a DropDownList (#AuthorBooksDropDown) (also added dynamically) changes.
$('#authorBooksPlaceHolder').on('change', '#AuthorBooksDropDown', function () {
var bookId = $(this).val();
$.get('/Books/AddCopy_RenderDetails/' + bookId, function (data) {
$('#bookDetailsPlaceHolder').html(data);
$('#bookDetailsPlaceHolder').slideDown();
});
$.validator.unobtrusive.parse('#addCopyForm');
});
The call invoked the AddCopy_RenderDetails action get an entity from a DB based on book id, and creates a new copy with certain fields populated.
Controller action:
public PartialViewResult AddCopy_RenderDetails(int id)
{
var book = db.LibraryBooks.Find(id);
var newCopy = new Book()
{
Author = book.Author,
Title = book.Title,
Publisher = book.Publisher,
CollectionId = book.CollectionId,
Collection = book.Collection
};
return PartialView("_AddCopy_Details", newCopy);
}
The view displays remaining fields which need to be populated.
#model CityLibrary.Models.Library.Book
<div class="vertical-separator"></div>
<hr />
#using (Ajax.BeginForm("AddCopy", "Books", new AjaxOptions
{
UpdateTargetId = "bookDetailsPlaceHolder"
}, new { #id = "addCopyForm" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Author)
#Html.HiddenFor(model => model.Title)
#Html.HiddenFor(model => model.CollectionId)
#Html.HiddenFor(model => model.Collection.Name)
#Html.HiddenFor(model => model.Publisher)
<div class="form-group">
#Html.LabelFor(model => model.Collection.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Collection.Name, new { htmlAttributes = new { #class = "form-control", #disabled = "" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ISBN, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ISBN, new { htmlAttributes = new { #class = "form-control", } })
#Html.ValidationMessageFor(model => model.ISBN, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Publisher, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Publisher, new { htmlAttributes = new { #class = "form-control", #disabled = "disabled" } })
#Html.ValidationMessageFor(model => model.Publisher, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.YearPrinted, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.YearPrinted, new { htmlAttributes = new { #class = "form-control", #Value = "" } })
#Html.ValidationMessageFor(model => model.YearPrinted, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-success btn-block" />
</div>
</div>
}
Even though I have $.validator.unobtrusive.parse('#addCopyForm'); invoked when the form is rendered (checked in chrome dev tools), validation still happens on the server side upon pressing a submit button as a POST action is being triggered every time. Not to mention that validation errors do not display upon TABing to next field.
Validation attributes are there in form's inputs:
I also have remote validation which checks whether a entered ISBN is already in the database. Obviously this works on the client side, which in my case simply does not.
Thank you for your time and help.
EDIT:
Well, I've added the following to the end of the view:
<script>
$.validator.unobtrusive.parse('#addCopyForm');
</script>
And it works. I have no idea why triggering it on a function does nothing.
Ajax is async, and your $.validator.unobtrusive.parse('#addCopyForm'); line of code is being called before the html has been added to the DOM. Move it to inside the success callback
$.get('/Books/AddCopy_RenderDetails/' + bookId, function (data) {
$('#bookDetailsPlaceHolder').html(data);
$('#bookDetailsPlaceHolder').slideDown();
$.validator.unobtrusive.parse('#addCopyForm');
});
Try this peace of code
$("form").on("submit", function (e) {
e.preventDefault();
$.validator.unobtrusive.parse($('#addCopyForm')); // here you need define your form id
if ($(this).valid()) // use to validate the form
{
//do ajax call
$.ajax({
type: "Post",
url: "/Books/AddCopy_RenderDetails/" + bookId,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
}
});
}
});

ASP.NET MVC jQuery JSON result redirecting URL

Using MVC/Json/Jquery.
Using form to create a new "group".
Form is on ~Group/Manage, posting form to ~Group/Create
Whilst working on this, returning Json result was working fine, handling in Jquery, no URL redirection.
Now, everytime I run it, it redirects me to ~Group/Create and displays the Json result.
Controller Group/Create
[HttpPost]
public ActionResult Create([Bind(Include="name,description")] GroupModel groupmodel)
{
...
return Json(new { success = true, message = groupmodel.name }, JsonRequestBehavior.AllowGet);
}
Form
<form id="frm_createGroup" action="/Groups/Create" method="post">
<h2>Create Group</h2>
<div class="form-group">
#Html.LabelFor(model => model.name, new { #for = "name" })
#Html.TextBoxFor(model => model.name, new { #class = "form-control", #placeholder = "Group Name" })
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="form-group">
#Html.LabelFor(model => model.description, new { #for = "description" })
#Html.TextBoxFor(model => model.description, new { #class = "form-control", #placeholder = "Group Description" })
#Html.ValidationMessageFor(model => model.description)
</div>
<span id="createGroupMessage"></span>
<button type="submit" class="btn btn-primary pull-right">Create</button>
</form>
Jquery to handle form
$(document).ready(function (){
$('#navGroups').makeActiveMenuItem();
var options = {
success: groupCreateSubmitted
,error: groupCreateError
}
$('#frm_createGroup').ajaxForm(options);
});
function groupCreateSubmitted(responseText, statusText, xhr, $form) {
if (responseText.success)
{
$('#createGroupMessage').html = "Group Created";
}
else
{
$('#createGroupMessage').html = responseText.message;
}
}
To be clear, I don't want URL redirection, I just want the Jquery to catch the return (it was before, have no idea why its changed...)
Thanks!
removed
,error: groupCreateError
working now...form bind was failing.

Categories