Как преобразовать вид бритвы со значениями формы и модели (включая URL-адрес изображения) в PDF? - PullRequest
0 голосов
/ 09 июля 2019

У меня есть простое приложение ASP.Net Core MVC, которое представляет собой базовую форму ввода с HTML-канвой на нем (для подписи).Когда форма заполнена, мне нужно преобразовать ее в PDF и прикрепить к электронному письму.Я нашел SelectPDF , который имеет бесплатную версию для сообщества, поддерживающую .Net Core, и решил, что мне это удастся.

Я получил свое заявление в месте, где я могу отправитьзаполнить форму и просмотреть заполненную форму в отдельном виде (в комплекте с изображением для представления того, что пользователь ввел на холст).Электронные письма отправляются просто отлично, но я не могу на всю жизнь сгенерировать PDF из своего отрисованного представления.

Чего я не знал, пока не потратил несколько дней на попытки решить, было ли это решение сSelectPdf выполняет GET для URL-адреса в новом сеансе. Это означает, что мне нужно было бы выполнить массивный запрос, поскольку моя форма содержит ~ 20 полей, включая преобразованное изображение, которое превышает лимит размера запроса.

I 'Я пытаюсь сделать это без использования базы данных или службы, но изображение доказывает, что это гораздо более сложная задача, чем я ожидал.

Я видел и попробовал многие из предложенных решений на SOи другие сайты.Они либо несколько лет (в некоторых случаях десятилетие или более) и устарели, либо пытаются сделать проблему намного сложнее, чем нужно, используя несколько других инструментов или расширений (большинство из которых платные или устарели).

Есть ли способ для меня:

  • Как-то обрезать URL изображения?Или
  • Разрешить GET-запросу принимать такой длинный запрос?
  • Каким-то образом преобразовать отображаемое представление со значениями, заполненными в строку HTML, и отправить его вместо URL?
  • Использовать Tempdata для перестроения моей модели в представлении «Отображение», чтобы обойти ограничение длины запроса?

Любые советы или предложения о том, как выполнить то, что я пытаюсь сделать,быть великолепным.

Редактировать: больше кода и того, что я пробовал до сих пор (расширенный)

Модель:

namespace Website.Models
{
    //[Serializable]
    public class ComputerRepairModel
    {
        [Required(AllowEmptyStrings = false)]
        [Display(Name="Customer Name")]
        public string CustomerName { get; set; }

        [Display(Name = "Email")]
        public string CustomerEmail { get; set; }

        [Display(Name = "Home")]
        public string ContactHomeNumber { get; set; }

        [Display(Name = "Work")]
        public string ContactWorkNumber { get; set; }

        [Required(AllowEmptyStrings = false)]
        [Display(Name = "Cell")]
        public string ContactCellNumber { get; set; }

        [Display(Name = "Signed")]
        public string Signature { get; set; }
        ....
    }

Контроллер:

namespace Website.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public IActionResult RepairAgreement()
        {                          
            ComputerRepairModel model = new ComputerRepairModel();
            return View(model);
        }

        [HttpPost]
        public IActionResult RepairAgreement(ComputerRepairModel Model)
        {
            if (!ModelState.IsValid)
            {
                Model.Signature = "";
                return View("RepairAgreement", Model);
            }

            return View(Model);
        }

        [HttpGet]
        public IActionResult DisplayRepairAgreement()
        {
            //ComputerRepairModel model = (ComputerRepairModel)TempData["model"];
            return View();
        }

        [HttpPost]
        public IActionResult SubmitRepairAgreement(ComputerRepairModel Model)
        {
            if (!ModelState.IsValid)
            {
                Model.Signature = null;
                return View("RepairAgreement", Model);
            }


            //TempData["model"] = Model;
            return RedirectToAction("DisplayRepairAgreement");
        }

Вид:

@model ComputerRepairModel

@section Scripts{
    <script src="https://cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js"></script>

    <script>
        $(function () {
            var canvas = document.querySelector('#signatureCanvas');
            var pad = new SignaturePad(canvas);
        });
    </script>

    <script>
        $("#submit").click(function () {
            //alert("button"); // Remove this line if it worked
            var dataURL = document.getElementById('signatureCanvas').toDataURL();
            document.getElementById('signature').value = dataURL;
            $("#submitbutton").hide();
        });
    </script>
}

<head>

</head>

<body>
    <h2 style="margin-top:20px;">Computer Repair Form</h2>

    <hr />

    <form method="post" asp-action="SubmitRepairAgreement">
        <div class="form-group">
            <div class="form-row">
                <div class="form-group col-sm-3">
                    <label asp-for="CustomerName"></label>
                    <input type="text" asp-for="CustomerName" class="form-control" />
                    <span asp-validation-for="CustomerName" class="text-danger"></span>
                </div>

                <div class="form-group col-sm-3">
                    <label asp-for="CustomerEmail"></label>
                    <input type="text" asp-for="CustomerEmail" class="form-control" placeholder="example@domain.com" />
                    <span asp-validation-for="CustomerEmail" class="text-danger"></span>
                </div>
            </div>
        </div>

        <div class="form-group">
            <label><b>Contact Number(s)</b></label>
            <div class="form-row">
                <div class="form-group col-sm-3">
                    <label asp-for="ContactHomeNumber"></label>
                    @*<input type="text" asp-for="ContactHomeNumber" class="phone form-control" maxlength="14" />*@
                    <input id="homePhone" class="form-control" type="text" asp-for="ContactHomeNumber" />
                    <span asp-validation-for="ContactHomeNumber" class="text-danger"></span>
                </div>
                <div class="form-group col-sm-3">
                    <label asp-for="ContactWorkNumber"></label>
                    <input id="workPhone" class="form-control" type="text" asp-for="ContactWorkNumber" />
                    <span asp-validation-for="ContactWorkNumber" class="text-danger"></span>
                </div>
                <div class="form-group col-sm-3">
                    <label asp-for="ContactCellNumber"></label>
                    <input id="cellPhone" class="form-control" type="text" asp-for="ContactCellNumber" />
                    <span asp-validation-for="ContactCellNumber" class="text-danger"></span>
                </div>
            </div>
        </div>

        <div class="form-group">
            <label><b>Billing Address</b></label>
            <div class="form-row">
                <div class="form-group col-sm-5">
                    <label asp-for="BillingStreetAddress"></label>
                    <input class="form-control" type="text" asp-for="BillingStreetAddress" />
                    <span asp-validation-for="BillingStreetAddress" class="text-danger"></span>
                </div>
                <div class="form-group col-sm-2">
                    <label asp-for="BillingCity"></label>
                    <input class="form-control" type="text" asp-for="BillingCity" />
                    <span asp-validation-for="BillingCity" class="text-danger"></span>
                </div>
                <div class="form-group col-sm-2">
                    <label asp-for="BillingState"></label>
                    <input class="form-control" type="text" asp-for="BillingState" />
                    <span asp-validation-for="BillingState" class="text-danger"></span>
                </div>
                <div class="form-group col-sm-2">
                    <label asp-for="BillingZip"></label>
                    <input class="form-control" type="text" asp-for="BillingZip" />
                    <span asp-validation-for="BillingZip" class="text-danger"></span>
                </div>
            </div>
        </div>

        <div class="form-group">
            <label><b>Computer Access</b></label>
            <div class="form-row">
                <div class="form-group col-sm-3">
                    <label asp-for="CustomerComputerUsername"></label>
                    <input class="form-control" type="text" asp-for="CustomerComputerUsername" />
                    <span asp-validation-for="CustomerComputerUsername" class="text-danger"></span>
                </div>
                <div class="form-group col-sm-3">
                    <label asp-for="CustomerComputerPassword"></label>
                    <input class="form-control" type="text" asp-for="CustomerComputerPassword" />
                    <span asp-validation-for="CustomerComputerPassword" class="text-danger"></span>
                </div>
            </div>
        </div>

        <div class="form-group">
            <div class="form-row">
                <div class="form-group col-sm-12">
                    <label asp-for="DescriptionOfProblem"></label>
                    <textarea class="form-control" asp-for="DescriptionOfProblem"></textarea>
                    <span asp-validation-for="DescriptionOfProblem" class="text-danger"></span>
                </div>
            </div>
        </div>

        <div class="form-group">
            <div class="form-row">
                <div class="form-group col-sm-12">
                    <label asp-for="ItemsReceived"></label>
                    <textarea class="form-control" asp-for="ItemsReceived"></textarea>
                    <span asp-validation-for="ItemsReceived" class="text-danger"></span>
                </div>
            </div>
        </div>

        <hr />    

        <div class="form-group">
            <div class="form-row">
                <div class="form-group col-sm-12">
                    <label asp-for="Comments"></label>
                    <textarea class="form-control" asp-for="Comments"></textarea>
                    <span asp-validation-for="Comments" class="text-danger"></span>
                </div>
            </div>
        </div>
        <div>
            I hereby agree to the above terms and authorize AMTI to perform services/repairs as stated in the service order.<br />
            I also agree to the terms and conditions within this Agreement.
        </div>

        <div class="form-group" style="margin-top:20px;">
            <div class="form-row justify-content-between">
                <div class="col-sm-6">
                    <label asp-for="Signature"></label>
                    @if (String.IsNullOrEmpty(Model.Signature))
                    {
                        <input type="hidden" id="signature" asp-for="Signature" />
                        <canvas width="500" height="100" id="signatureCanvas" style="border:1px solid black"></canvas>
                    }
                    else
                    {
                        <img src="@Url.Content(Model.Signature)" alt="Image" />
                    }
                </div>
                <div class="form-group col-sm-3">
                    <label asp-for="DateSigned"></label>
                    <input class="form-control" type="date" asp-for="DateSigned"/>
                </div>
            </div>
        </div>

        <div>
            <hr />
            <center><b>For Office Use Only</b></center>
            <div class="form-group">
                <div class="form-row">
                    <div class="form-group col-sm-4">
                        <label asp-for="ComputerMfg"></label>
                        <input class="form-control" readonly asp-for="ComputerMfg" />
                    </div>
                    <div class="form-group col-sm-4">
                        <label asp-for="ComputerModel"></label>
                        <input class="form-control" readonly asp-for="ComputerModel" />
                    </div>
                    <div class="form-group col-sm-4">
                        <label asp-for="ComputerOS"></label>
                        <input class="form-control" readonly asp-for="ComputerOS" />
                    </div>
                </div>
            </div>
        </div>

        <div id="submitbutton">
            <input id="submit" class="form-control button" style="background-color: #4CAF50; color:white;" type="submit"/>
        </div>
    </form>

</body>

Показанный выше, по сути, мой внешний вид модели, контроллера и вида.

Закомментированный код в моей модели и контроллере представляет мою последнюю попытку решения проблемы из этого ответа .Очевидно, у меня еще есть работа, если я хочу попробовать и заставить этот метод работать, так как, несмотря на пометку моей модели как Serializable, я получаю следующую ошибку.Serialize Error

Я попытался это сделать, потому что, если бы я просто сделал обычный RedirectToAction("DisplayRepairAgreement", Model);, запрос был бы слишком длинным (так как я преобразую холст HTML в строку URL через Javascript), как показано,Request Too Long

Еще одна вещь, которую я попробовал, состояла в том, чтобы просто использовать то же представление и использовать действие POST, которое использовалось для отправки в преобразование PDF (именно поэтому у меня есть условие ifрядом с входом для подписи внизу), но это будет когда-либо только GET, когда я передам URL-адрес метода и будет иметь форму в формате PDF, но без заполненных значений.

Ниже приведеныдополнительные действия, которые я выполнял в своем контроллере перед моей последней попыткой (показанной выше):

    [HttpPost]
    public IActionResult RepairAgreement(ComputerRepairModel Model)
    {
        if (!ModelState.IsValid)
        {
            Model.Signature = "";
            return View("RepairAgreement", Model);
        }
        string url = Url.Action(nameof(DisplayRepairAgreement), 
            new { Model.CustomerName, Model.CustomerEmail, Model.ContactHomeNumber, Model.ContactWorkNumber, Model.ContactCellNumber,
                Model.BillingStreetAddress, Model.BillingCity, Model.BillingState, Model.BillingZip, Model.CustomerComputerUsername, Model.CustomerComputerPassword, Model.DescriptionOfProblem,
                Model.ItemsReceived, Model.Comments, Model.Signature, Model.DateSigned});

        // instantiate a html to pdf converter object
        HtmlToPdf converter = new HtmlToPdf();

        // create a new pdf document converting an url
        PdfDocument doc = converter.ConvertUrl(url);

        // save pdf document
        doc.Save("Sample.pdf");

        // close pdf document
        doc.Close();
        return View(Model);
    }

В своем отчаянии я также пытался жестко закодировать мой HTML-вид непосредственно в моей модели, поскольку один из методов объекта SelectPDFможет принимать строку HTML, а не URL.Я заполнил форму и был переведен в представление «Дисплей», где я использовал инспектор, чтобы просто захватить весь объект HTML и вставить его. Это почти сработало.По сути, в своем действии я бы просто вызвал следующий метод, и передаваемый HTML-файл был сохранен в модели, как описано ранее в этом параграфе.

        public PdfDocument CreatePdfFromHTML(string Html)
        {
            HtmlToPdf converter = new HtmlToPdf();
            PdfDocument pdfDoc = converter.ConvertHtmlString(Html);

            return pdfDoc;
        }

Вот как выглядит форма в браузере, икак бы я хотел, чтобы PDF выглядел так же

What I want

и вот как это выглядит, когда я попробовал подход stringbuilder и написал свою собственную строку HTMLна базе инспектора в Chrome.

What I get

1 Ответ

1 голос
/ 10 июля 2019

Хорошо, теперь это имеет смысл. Похоже, вы не правильно отображаете свое мнение. Я пробовал нечто подобное ранее, и вы можете использовать этот метод для рендеринга представления в строку:

public class MyController : Controller
{
    private readonly ICompositeViewEngine _viewEngine;

    public MyController(ICompositeViewEngine viewEngine)
    {
        _viewEngine = viewEngine;
    }

    [HttpPost]
    public async Task<IActionResult> RepairAgreement(ComputerRepairModel Model)
    {
        if (!ModelState.IsValid)
        {
            Model.Signature = "";
            return View("RepairAgreement", Model);
        }

        string url = await RenderPartialViewToString("DisplayRepairAgreement", new { Model.CustomerName, Model.CustomerEmail, Model.ContactHomeNumber, Model.ContactWorkNumber, Model.ContactCellNumber,
            Model.BillingStreetAddress, Model.BillingCity, Model.BillingState, Model.BillingZip, Model.CustomerComputerUsername, Model.CustomerComputerPassword, Model.DescriptionOfProblem,
            Model.ItemsReceived, Model.Comments, Model.Signature, Model.DateSigned});

        // instantiate a html to pdf converter object
        HtmlToPdf converter = new HtmlToPdf();

        // create a new pdf document converting an url
        PdfDocument doc = converter.ConvertHtmlString(url);

        // save pdf document
        doc.Save("Sample.pdf");

        // close pdf document
        doc.Close();
        return View(Model);
    }

    [HttpPost]
    public IActionResult DisplayRepairAgreement()
    {
        return Ok();
    }

    private async Task<string> RenderPartialViewToString(string viewName, object model)
    {
        if (string.IsNullOrEmpty(viewName))
            viewName = ControllerContext.ActionDescriptor.ActionName;

        ViewData.Model = model;

        using (var writer = new StringWriter())
        {
            ViewEngineResult viewResult = 
                _viewEngine.FindView(ControllerContext, viewName, false);

            ViewContext viewContext = new ViewContext(
                ControllerContext, 
                viewResult.View, 
                ViewData, 
                TempData, 
                writer, 
                new HtmlHelperOptions()
            );

            await viewResult.View.RenderAsync(viewContext);

            return writer.GetStringBuilder().ToString();
        }
    }
}

Вот, пожалуйста. Я должен был заполнить некоторые пробелы, но я надеюсь, что это имеет смысл. Я добавил некоторый код, который может отображать Razor. Таким образом, он должен использовать движок Razor для рендеринга точно так же, как это делал ваш браузер. Еще одним преимуществом вышеизложенного является то, что вы не делаете никаких других http-запросов. Вы просто используете механизм рендеринга напрямую и генерируете нужную http-страницу в контроллере.

Код был взят из этого ответа stackoverflow answer

Я никогда не пробовал SelectPdf самостоятельно, но если он все еще выходит без какого-либо стиля, вам, возможно, придется взглянуть на какой-то движок рендеринга, например, Chromium. Надеюсь, это приблизит вас к достижению того, чего вы хотите.

...