How to generate a single PDF using Rotativa from a View which has different dynamic headers in ASP.Net MVC? - asp.net-mvc-4

I am using Rotativa to generate a single PDF file for single invoice, in Asp.Net MVC application. I am using the command --header-html with CustomSwitches of ViewAsPdf to include header for each page of an Invoice as follows,
public ActionResult GenerateSingleInvoicePDF(string invoiceId)
{
var invoiceViewModel = new InvoicePDFModel();
... // get content from database;
...
...
ViewBag.InvoiceDetail = invoiceViewModel;
string customSwitches = string.Format("--print-media-type --allow {0} --header-html {0} --page-offset \"0\" --header-spacing \"1\" ", Url.Action("InvoiceHeader", "Order", new { invNumber =
invoiceViewModel.invNo, invDate = invoiceViewModel.InvoiceDateString, shippAddr = invoiceViewModel.DeliveryAddr, billingAddress = invoiceViewModel.BillingAddr }, "http"));
return new ViewAsPdf("~/SingleInvoiceView.cshtml")
{
FileName = "SingleInvoice.pdf",
PageSize = Size.A4,
CustomSwitches = customSwitches
};
}
This works perfectly without any issue. Now my need is how to generate a single PDF for multiple invoice. I tried the above code for multiple invoice as follows,
public ActionResult GenerateMultipleInvoicePDF(string invoiceIds)
{
var invoiceList = new List<InvoicePDFModel>
... // get list of content from database;
...
...
ViewBag.InvoiceList = invoiceList;
string customSwitches = // don't know how to define header view for multiple invoice.
return new ViewAsPdf("~/MultiInvoiceView.cshtml")
{
FileName = "MultiInvoice.pdf",
PageSize = Size.A4,
CustomSwitches = customSwitches
};
}
But I am stuck at the header part. Because, header content varies for each invoice. Any suggestions how to do this?

For your specific case, taking a look at your individual invoice code
Url.Action("InvoiceHeader", "Order", new { invNumber =
invoiceViewModel.invNo, invDate = invoiceViewModel.InvoiceDateString, shippAddr = invoiceViewModel.DeliveryAddr, billingAddress = invoiceViewModel.BillingAddr }, "http")
Maybe it would be a good idea to take all the parameters you are sending to the view and store them inside a model. This model could keep a list or collection of classes or models, each of them with the same pack of parameters that you send for an individual invoice. Then, on the view, you might be able to determine, depending on your requirements, which parameters you must specify for each page.
Bearing in mind that Rotativa uses WkHtmlToPdf, and as you can chek in this, an other posts, the oficial documentation sets that the page number, among other parameters, is send to the header/footer.
Could be tricky, but this means you might be able to achieve what you are looking for, following these steps:
Generate MVC action and asociated view, to generate the header, accepting the model you wish.
Inside the view, access QueryString parameter for page number, so this way you can identify which invoice you are printing (supossing that each invoice prints no more and no less than one page).
Using the collection of classes from your model, access by index the particular invoice that matches the page number from the previous step, and write the view according to the parameters that apply for the current invoice.

Related

ASP.net - Uploading Files Associated with a Database Record?

I know that there are tons of examples of multi-part form data uploading in ASP.net. However, all of them just upload files to the server, and use System.IO to write it to server disk space. Also, the client side implementations seem to handle files only in uploading, so I can't really use existing upload plugins.
What if I have an existing record and I want to upload images and associate them with the record? Would I need to write database access code in the upload (Api) function, and if so, how do I pass that record's PK with the upload request? Do I instead upload the files in that one request, obtain the file names generated by the server, and then make separate API calls to associate the files with the record?
While at it, does anyone know how YouTube uploading works? From a user's perspective, it seems like we can upload a video, and while uploading, we can set title, description, tags, etc, and even save the record. Is a record for the video immediately created before the API request to upload, which is why we can save info even before upload completes?
Again, I'm not asking HOW to upload files. I'm asking how to associate uploaded files with an existing record and the API calls involved in it. Also, I am asking for what API calls to make WHEN in the user experience when they also input information about what they're uploading.
I'm assuming you're using an api call to get the initial data for displaying a list of files or an individual file. You would have to do this in order to pass the id back to the PUT method to update the file.
Here's a sample of the GET method:
[HttpGet]
public IEnumerable<FileMetaData> Get()
{
var allFiles = MyEntities.Files.Select(f => new FileMetaData()
{
Name = f.Name,
FileName = f.FileName,
Description = f.Description,
FileId = f.Id,
ContentType = f.ContentType,
Tags = f.Tags,
NumberOfKB = f.NumberOfKB
});
return allFiles;
}
Here's a sample of the POST method, which you can adapt to be a PUT (update) instead:
[HttpPost]
[ValidateMimeMultipartContentFilter]
public async Task<IHttpActionResult> PutFile()
{
try
{
var streamProvider =
await Request.Content.ReadAsMultipartAsync(new InMemoryMultipartFormDataStreamProvider());
//We only allow one file
var thisFile = files[0];
//For a PUT version, you would grab the file from the database based on the id included in the form data, instead of creating a new file
var file = new File()
{
FileName = thisFile.FileName,
ContentType = thisFile.ContentType,
NumberOfKB = thisFile.ContentLength
};
//This is the file metadata that your client would pass in as formData on the PUT / POST.
var formData = streamProvider.FormData;
if (formData != null && formData.Count > 0)
{
file.Id = formData["id"];
file.Description = formData["description"];
file.Name = formData["name"] ?? string.Empty;
file.Tags = formData["tags"];
}
file.Resource = thisFile.Data;
//For your PUT, change this to an update.
MyEntities.Entry(file).State = EntityState.Detached;
MyEntities.Files.Add(file);
await MyEntities.SaveChangesAsync();
//return the ID
return Ok(file.Id.ToString());
}
I got the InMemoryMultipartFormDataStreamProvider from this article:
https://conficient.wordpress.com/2013/07/22/async-file-uploads-with-mvc-webapi-and-bootstrap/
And adapted it to fit my needs for the form data I was returning.

Ektron: How to change the Framework API's default page size?

I've noticed that when pulling content form the framework API that there is a default page size of 50. I've tried adjusting the "ek_PageSize" AppSetting, but that doesn't seem to affect the API.
Basically in all my code I need to create a new PaginInfo object to update the number of items being returned.
var criteria = new ContentTaxonomyCriteria(ContentProperty.Id, EkEnumeration.OrderByDirection.Descending);
criteria.PagingInfo = new PagingInfo(100);
Does anyone know if there's a way to change that default value (for the entire site) without having to modify the PagingInfo object on the criteria on each call?
You could create a factory method that creates your criteria object. Then instead of instantiating the criteria object, you would call this factory method. From here, you can define an AppSetting that is unique to your code. There are several types of criteria objects used by the ContentManager, so you could even make the factory method generic.
private T GetContentCriteria<T>() where T : ContentCriteria, new()
{
// Sorting by Id descending will ensure newer content blocks are favored over older content.
var criteria = new T
{
OrderByField = ContentProperty.Id,
OrderByDirection = EkEnumeration.OrderByDirection.Descending
};
int maxRecords;
int.TryParse(ConfigurationManager.AppSettings["CmsContentService_PageSize"], out maxRecords);
// Only set the PagingInfo if a valid value exists in AppSettings.
// The Framework API's default page size of 50 will be used otherwise.
if (maxRecords > 0)
{
criteria.PagingInfo = new PagingInfo(maxRecords);
}
return criteria;
}
There's a page size App Setting in the web.config that I believe controls this default page size as well. But be warned as this will also change page sizes within the Workarea.
So if you set it to 100, then you'll see 100 users, content items, aliases, etc. per page instead of the default 50.

convert string to array in mvc 4

In my recent mvc 4 project, I store multiple image and other files (such as doc, pdf, ppt etc.) as string in my database. Now I want to show this multiple image and want to show link of other files.
For example, I store data as string in my db as like as given below:
1980082_10201802177236118_516383197_o.jpg, ASP.NET MVC Interview Questions &amp; Answers.pdf, Sample-1.jpg,
Now I want to fetch this string and show image and give the link of the other files.
I hope it will help.First when you save in your database you have to make sure you save it with all the paths, not only the filenames. In simple In your Action:
public FilePathResult GetDoc(int yourParam)
{
var path= (from r in yourContext.yourTable where id == yourParam
select r.path).FirstOrDefault()
//Considering that your path contains also the folder
return new FilePathResult(path);
}
In your View:
<img src="#Url.Action("GetDoc", "YourController", new { yourParam=Model.yourParam}) " />
#*For a link*#
<a href="#Url.Action("GetDoc", "YourController", new { yourParam=Model.yourParam}) ">#Model.TheNameOfYourFile <a/>
Firstly note that, it is not good idea to store that file names in one row in db. If I understood you right, to extract images and files from that rows, create a model that contains list of different file list:
public class RowResult
{
public RowResult()
{
Images = new List<string>();
Pdfs = new List<string>();
}
public List<string> Images { get; set; }
public List<string> Pdfs { get; set; }
//you can add other file types here
}
Create a method that return result for every row:
public RowResult ExtractFilesFromRow(string row)
{
RowResult result = new RowResult();
string[] parts = row.Split(' ');
foreach (string part in parts)
{
if (part.TrimEnd(',').EndsWith(".jpeg")) result.Images.Add(part);
if (part.TrimEnd(',').EndsWith(".pdf")) result.Pdfs.Add(part);
//add others here..
}
return result;
}
And finally, to show them in view for each row you can get RowResult in db and fill List<RowResult>. In Action:
....
List<RowResult> list = new List<RowResult>();
foreach (string row in rows)
{
list.Add(ExtractFilesFromRow(row));
}
...
return list;
In view:
#foreach (string result in Model)
{
//here depends on you want
<img src="#Url.Content("~/Uploads/Images/" + result.Images[0])" />
....
<img src="#Url.Content("~/Uploads/Images/" + result.Images[1])" />
}
I store multiple image and other files (such as doc, pdf, ppt etc.) as string in my database
I assume you mean you save the file names as string in your database.
Now let's start with the db data you shared
For example, I store data as string in my db as like as given below:
1980082_10201802177236118_516383197_o.jpg, ASP.NET MVC Interview Questions & Answers.pdf, Sample-1.jpg,
when comma seperated becomes this
1980082_10201802177236118_516383197_o.jpg
ASP.NET MVC Interview Questions & Answers.pdf
Sample-1.jpg
Now what we have here is only filenames and to "physical path" to where these files are stored.
So for this to work you WILL have to save the file path in the database too. Unless you have defined a path say "C:/SomeApp/MyDump/" and you are simply dumping all the files here.
In case you are then you can use that path append it with the file name and show on the UI. The file extension could help you decide what HTML to use (<img> or <a> to display image or show a download link)

HTTP GET to return custom model with data from external database with Umbraco MVC Surface Controller

I am currently working on an Umbraco MVC 4 project version 6.0.5. The project currently uses Vega.USiteBuilder to build the appropriate document types in the backoffice based on strongly typed classes with mapping attributes. Consequently, all my razor files inherit from UmbracoTemplatePageBase
I am coming across a road block trying to invoke a HTTP GET from a razor file. For example a search form with multiple fields to submit to a controller action method, using a SurfaceController using Html.BeginUmbracoForm.
My Html.BeginUmbracoForm looks like this
#using (Html.BeginUmbracoForm("FindTyres", "TyreSearch"))
{
// Couple of filter fields
}
I basically have a scenario where I will like to retrieve some records from an external database outside of Umbraco (external to Umbraco Database) and return the results in a custom view model back to my Umbraco front end view. Once my controller and action method is setup to inherit from SurfaceController and thereafter compiling it and submitting the search, I get a 404 resource cannot be found where the requested url specified: /umbraco.RenderMVC.
Here is my code snippet:
public ActionResult FindTyres(string maker, string years, string models, string vehicles)
{
var tyreBdl = new Wheels.BDL.TyreBDL();
List<Tyre> tyres = tyreBdl.GetAllTyres();
tyres = tyres.Where(t => string.Equals(t.Maker, maker, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Year, years, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Model, models, StringComparison.OrdinalIgnoreCase)
&& string.Equals(t.Version, vehicles, StringComparison.OrdinalIgnoreCase)).ToList();
var tyreSearchViewModel = new TyreSearchViewModel
{
Tyres = tyres
};
ViewBag.TyreSearchViewModel = tyreSearchViewModel;
return CurrentUmbracoPage();
}
I then resort to using standard MVC, Html.BeginForm (the only difference). Repeating the steps above and submitting the search, I get the following YSOD error.
Can only use UmbracoPageResult in the context of an Http POST when
using a SurfaceController form
Below is a snippet of the HTML BeginForm
#using (Html.BeginForm("FindTyres", "TyreSearch"))
{
// Couple of filter fields
}
I feel like I am fighting the Umbraco routes to get my controller to return a custom model back to the razor file. I have googled alot trying to figure out how to do a basic search to return a custom model back to my Umbraco front end view till the extent that I tried to create a custom route but that too did not work for me.
Does my controller need to inherit from a special umbraco controller class to return the custom model back? I will basically like to invoke a HTTP GET request (which is a must) so that my criteria search fields are reflected properly in the query strings of the url. For example upon hitting the search button, I must see the example url in my address browser bar
http://[domainname]/selecttyres.aspx/TyresSearch/FindTyresMake=ASIA&Years=1994&Models=ROCSTA&Vehicles=261
Therefore, I cannot use Surface Controller as that will operate in the context of a HTTP Post.
Are there good resource materials that I can read up more on umbraco controllers, routes and pipeline.
I hope this scenario makes sense to you. If you have any questions, please let me know. I will need to understand this concept to continue on from here with my project and I do have a deadline.
There are a lot of questions about this and the best place to look for an authoritative approach is the Umbraco MVC documentation.
However, yes you will find, if you use Html.BeginUmbracoForm(...) you will be forced into a HttpPost action. With this kind of functionality (a search form), I usually build the form manually with a GET method and have it submit a querystring to a specific node URL.
<form action="#Model.Content.Url"> ... </form>
On that page I include an #Html.Action("SearchResults", "TyresSearch") which itself has a model that maps to the keys in the querystring:
[ChildAction]
public ActionResult(TyreSearchModel model){
// Find results
TyreSearchResultModel results = new Wheels.BDL.TyreBDL().GetAllTyres();
// Filter results based on submitted model
...
// Return results
return results;
}
The results view just need to have a model of TyreSearchResultModel (or whatever you choose).
This approach bypasses the need for Umbraco's Controller implementation and very straightforward.
I have managed to find my solution through route hijacking which enabled me to return a custom view model back to my view and work with HTTP GET. It worked well for me.
Digby, your solution looks plausible but I have not attempted at it. If I do have a widget sitting on my page, I will definitely attempt to use your approach.
Here are the details. I basically override the Umbraco default MVC routing by creating a controller that derived from RenderMvcController. In a nutshell, you implement route hijacking by implementing a controller that derives from RenderMvcController and renaming your controllername after your given documenttype name. Recommend the read right out of the Umbraco reference (http://our.umbraco.org/documentation/Reference/Mvc/custom-controllers) This is also a great article (http://www.ben-morris.com/using-umbraco-6-to-create-an-asp-net-mvc-4-web-applicatio)
Here is my snippet of my code:
public class ProductTyreSelectorController : Umbraco.Web.Mvc.RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
var productTyreSelectorViewModel = new ProductTyreSelectorViewModel(model);
var maker = Request.QueryString["Make"];
var years = Request.QueryString["Years"];
var models = Request.QueryString["Models"];
var autoIdStr = Request.QueryString["Vehicles"];
var width = Request.QueryString["Widths"];
var aspectRatio = Request.QueryString["AspectRatio"];
var rims = Request.QueryString["Rims"];
var tyrePlusBdl = new TPWheelBDL.TyrePlusBDL();
List<Tyre> tyres = tyrePlusBdl.GetAllTyres();
if (Request.QueryString.Count == 0)
{
return CurrentTemplate(productTyreSelectorViewModel);
}
if (!string.IsNullOrEmpty(maker) && !string.IsNullOrEmpty(years) && !string.IsNullOrEmpty(models) &&
!string.IsNullOrEmpty(autoIdStr))
{
int autoId;
int.TryParse(autoIdStr, out autoId);
tyres = tyres.Where(t => string.Equals(t.Maker, maker, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Year, years, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Model, models, StringComparison.OrdinalIgnoreCase) &&
t.AutoID == autoId)
.ToList();
productTyreSelectorViewModel.Tyres = tyres;
}
else if (!string.IsNullOrEmpty(width) && !string.IsNullOrEmpty(aspectRatio) && !string.IsNullOrEmpty(rims))
{
tyres = tyres.Where(t => string.Equals(t.Aspect, aspectRatio, StringComparison.OrdinalIgnoreCase) &&
string.Equals(t.Rim, rims, StringComparison.OrdinalIgnoreCase)).ToList();
productTyreSelectorViewModel.Tyres = tyres;
}
var template = ControllerContext.RouteData.Values["action"].ToString();
//return an empty content result if the template doesn't physically
//exist on the file system
if (!EnsurePhsyicalViewExists(template))
{
return Content("Could not find physical view template.");
}
return CurrentTemplate(productTyreSelectorViewModel);
}
}
Note my ProductTyreSelectorViewModel must inherit from RenderModel for this to work and my document type is called ProductTyreSelector. This way when my model is returned with the action result CurrentTemplate, the Umbraco context of the page is retained and my page is rendered appropriately again. This way, all my query strings will show all my search/filter fields which is what I want.
Here is my snippet of the ProductTyreSelectorViewModel class:
public class ProductTyreSelectorViewModel : RenderModel
{
public ProductTyreSelectorViewModel(RenderModel model)
: base(model.Content, model.CurrentCulture)
{
Tyres = new List<Tyre>();
}
public ProductTyreSelectorViewModel(IPublishedContent content, CultureInfo culture)
: base(content, culture)
{
}
public ProductTyreSelectorViewModel(IPublishedContent content)
: base(content)
{
}
public IList<Tyre> Tyres { get; set; }
}
This approach will work well perhaps with one to two HTTP GET forms on a given page. If there are multiple forms within in a page, then a good solution will may be to use ChildAction approach. Something I will experiment with further.
Hope this helps!

Magento API V2 - add an additional attribute to API response

I'm using the Magento API V2.
When I call salesOrderCreditmemoInfo, I get a response with the credit memo details and a list of the products associated with the order.
But in the list of product items there is no product_type attribute.
I want to manually edit the response to add this attribute.
I tried editing:
app\code\core\Mage\Sales\Model\Order\Creditmemo\Api.php
And replaced:
public function info($creditmemoIncrementId)
{
...
$result['items'] = array();
foreach ($creditmemo->getAllItems() as $item) {
$result['items'][] = $this->_getAttributes($item, 'creditmemo_item');
}
With the following - (basically appending an extra attribute to the array):
public function info($creditmemoIncrementId)
{
...
$result['items'] = array();
foreach ($creditmemo->getAllItems() as $item) {
$product_type = '1'; //test value to check if works
$attribs = $this->_getAttributes($item, 'creditmemo_item');
$attribs['product_type'] = $product_type;
$result['items'][] = $attribs;
}
When I do mage::log($result), the extra attribute seems to be added correctly to the array.
(also indicating that this function is the one getting called)
But it has no impact on the actual API response.
Am I totally looking in the wrong place or is there something else I need to update?
Since You were using SOAP V2, you should update the wsdl.xml to get the output.
For your case it is product_type and refresh cache on server. /tmp to load the new wsdl.xml that already updated. don't forget to go to System -> Cache Management clear all cache.