Thursday, May 27, 2021

MVC download pdf generated from a view iText

 This is a little tricky as it involves converting html to pdf by using a tool such as iText. 

To generate the html I used a normal view like:

public ActionResult PdfView(int id)

{

    var entity = db.Customers.Find(id);

    return View(entity);   

}

Then I added a utility class which converts html to pdf using iText library

public class PdfCreator

    {

        public static byte[] ConvertToPdf(string html, string baseUri)

        {

 LicenseKey.LoadLicenseFile(HttpContext.Current.Server.MapPath(@"~/iText7/itextkey_0.xml"));

            ConverterProperties properties = new ConverterProperties();

            // Add solely one Typeface, Helevetica.

            FontProvider fp;

            properties = new ConverterProperties();

            var Helvetica = FontProgramFactory.CreateFont(StandardFonts.HELVETICA);

            var HelveticaBold = FontProgramFactory.CreateFont(StandardFonts.HELVETICA_BOLD);

            var HelveticaOblique = FontProgramFactory.CreateFont(StandardFonts.HELVETICA_OBLIQUE);

            var HelveticaBoldItalic = FontProgramFactory.CreateFont(StandardFonts.HELVETICA_BOLDOBLIQUE);

            fp = new FontProvider();

            fp.AddFont(Helvetica);

            fp.AddFont(HelveticaBold);

            fp.AddFont(HelveticaOblique);

            fp.AddFont(HelveticaBoldItalic);

            properties.SetFontProvider(fp);

            properties.SetBaseUri(baseUri);

            using (MemoryStream memoryStream = new MemoryStream())

            {

                PdfWriter writer = new PdfWriter(memoryStream);

                writer.SetCompressionLevel(CompressionConstants.BEST_COMPRESSION);

 

                // This is a crucial call. Otherwise, when we close the document, as we must do, the PDF created contains errors.

                writer.SetCloseStream(false);

 

                PdfDocument pdf = new PdfDocument(writer);

                pdf.SetDefaultPageSize(PageSize.A4);

               

                using (Document document = HtmlConverter.ConvertToDocument(html, writer, properties))

                {

                    document.SetMargins(100,100,100,100);

                }

                memoryStream.Seek(0, SeekOrigin.Begin);

                return memoryStream.ToArray();

            }

        }

    }

Next, I added another method with return type FileResult which converts this view's html to pdf and writes it to the response stream.


public FileResult DownloadPdf(int id)

        {

            var controllerContext = this.ControllerContext;

            var result = ViewEngines.Engines.FindView(controllerContext, "PdfView", null);

            ViewDataDictionary viewData = new ViewDataDictionary();

            viewData.Model = db.Entities.Find(id);

            StringWriter output;

            using (output = new StringWriter())

            {

                var viewContext = new ViewContext(controllerContext, result.View, viewData, controllerContext.Controller.TempData, output);

                result.View.Render(viewContext, output);

                result.ViewEngine.ReleaseView(controllerContext, result.View);

            }

 

            string baseUrl = Request.Url.Scheme + "://" + Request.Url.Authority + Request.ApplicationPath.TrimEnd('/') + "/Content";

            byte[] fileBytes = PdfCreator.ConvertToPdf(output.ToString(), baseUrl);

            string fileName = string.Format("Entity_{0}", id.ToString());

            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);

        }


References:

  1. https://stackoverflow.com/questions/30121340/how-to-return-a-pdf-in-an-action-result-in-mvc
  2. https://stackoverflow.com/questions/21994933/open-pdf-in-new-tab-c-sharp
  3. https://itextpdf.com/en/products/itext-7/convert-html-css-to-pdf-pdfhtml
  4. https://stackoverflow.com/questions/18442343/get-html-from-mvc-4-view-into-a-string


Friday, May 21, 2021

MVC checkbox list

 Although it appears to be rather simple taks to add a checkbox list for multiselect items in MVC, in reality it takes a bit of time to get around the concepts and limitations of the framework. So here is a step-by-step list to save the exploration time.

Disclaimer: This article uses techniques mentioned in the last answer at https://stackoverflow.com/questions/40160998/checkboxlist-using-asp-net-mvc-5

 

  1. I had two lists in the controller, one list of all possible values and another of the selected items. To be able to display all values from one list and pre-select only  those which were present in the second list, it is very convenient to use a CheckboxViewModel class as explained in the last answer of above post.
  2. I also added one more property to the CheckboxViewModel class which stores id of the object to which it belongs, thus this simplifies the process of generating entities for database update.
  3. I found out that IEnumerable<CheckboxViewModel> for checkbox binding in view fails to bind values properly in postback methods, so I changed the property type to CheckboxViewModel[].
  4. Need to add hidden fields for each of the property of  CheckboxViewModel class.
 
 
Thus CheckboxViewModel class becomes 
      

public class CheckboxViewModel

    {

        public int Id { get; set; }

 

        public string Name { get; set; }

 

        public bool Checked { get; set; }

 

        public int ParentId { get; set; }

    }

 

 And final extension method looks like

 

public static CheckboxViewModel[] CreateCheckboxList<T>(this IEnumerable<T> entities, Func<T, int> value, Func<T, object> text, Func<T, bool> isChecked, Func<T, int> parentId)

        {

            return entities.Select(x => new CheckboxViewModel

            {

                Id = (int)value(x),

                Name = text(x).ToString(),

                Checked = isChecked(x),

                ParentId = parentId(x)

            }).ToArray();

        }

 
   View looks like

<div class="form-group">

    <div class="checkbox checkbox-primary">

        @Html.HiddenFor(model => model.MyCheckboxList[i].Id)

        @Html.CheckBoxFor(model => model.MyCheckboxList[i].Checked)

        @Html.HiddenFor(model => model.MyCheckboxList[i].Checked)

        @Html.LabelFor(model => model.MyCheckboxList[i].Checked, Model.MyCheckboxList[i].Name)

        @Html.HiddenFor(model => model.MyCheckboxList[i].Name)

        @Html.HiddenFor(model => model.MyCheckboxList[i].ParentId)

    </div>

</div>

c# httpclient The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch

 If we get this error while trying to get http reponse using HttpClient object, it could mean that certificate validation fails for the remo...