I know this has been asked before, and I have read a lot of posts about it, but I can't seem to find anything to help.
I have about 15 controllers that(among many other things) output different documents as ASP.NET MVC views. I am trying to create a method that will combine a variety of these different documents as a PDF using SyncFusion by generating individual PDFs and then merging them (the merge routine is not included in the code below).
Here is a portion of my main method that creates the individual PDF files:
foreach (FormsSubmitted fs in formObjects)
{
filename = "";
switch (fs.FormName.ToUpper())
{
case "OT EVAL":
OTEvalController cOTEVAL = new OTEvalController();
filename = cOTEVAL.CreateOTEvalPrintPDF(fs.VisitId.ToString());
cOTEVAL = null;
break;
case "OT POC":
OTPOCController cOTPOC = new OTPOCController();
filename = cOTPOC.CreatePrintOTPlanOfCarePDF(fs.VisitId.ToString());
cOTPOC = null;
break;
case "OT PROGRESS NOTE":
ProgressNoteController cOTPN = new ProgressNoteController();
filename = cOTPN.CreateOTProgressNotePrintPDF(fs.VisitId.ToString());
cOTPN = null;
break;
case "PT EVAL":
PTEvalController cPTEVAL = new PTEvalController();
filename = cPTEVAL.CreatePTEvalPrintPDF(fs.VisitId.ToString());
cOTEVAL = null;
break;
case "PT POC":
PTPOCController cPTPOC = new PTPOCController();
filename = cPTPOC.CreatePTPOCPrintPDF(fs.VisitId.ToString());
cPTPOC = null;
break;
case "PT PROGRESS NOTE":
PTProgressNoteController cPTPN = new PTProgressNoteController();
filename = cPTPN.CreatePrintPTProgressNotePDF(fs.VisitId.ToString());
cPTPN = null;
break;
case "SP EVAL":
SPEvalController cSPEVAL = new SPEvalController();
filename = cSPEVAL.CreatePrintSPEvalPDF(fs.VisitId.ToString());
cSPEVAL = null;
break;
case "SP POC":
SPPOCController cSPPOC = new SPPOCController();
filename = cSPPOC.CreatePrintSPPOCPDF(fs.VisitId.ToString());
cSPPOC = null;
break;
case "SP PROGRESS NOTE":
SPProgressNoteController cSPPN = new SPProgressNoteController();
filename = cSPPN.CreatePrintSPProgressNotePDF(fs.VisitId.ToString());
cSPPN = null;
break;
case "MSW EVAL":
MSWEvalController cMSWEVAL = new MSWEvalController();
filename = cMSWEVAL.CreateMSWEvalPrintPDF(fs.VisitId.ToString());
cMSWEVAL = null;
break;
case "MSW POC":
MSWPOCController cMSWPOC = new MSWPOCController();
filename = cMSWPOC.CreateMSWPOCPrintPDF(fs.VisitId.ToString());
cMSWPOC = null;
break;
case "COMMUNICATION/COORDINATION OF CARE":
CommController cCOMM = new CommController();
filename = cCOMM.CreatePrintCommunicationFormPDF(fs.VisitId.ToString());
cCOMM = null;
break;
}
if (filename != "")
filenames.Add(filename);
}
This is an example of one of the methods that creates a single PDF:
public string CreatePrintPTProgressNotePDF(string visitid)
{
string filename = string.Format("PTProgressNote{0}.pdf", visitid);
string DestPath = Path.Combine(HttpRuntime.AppDomainAppPath, "UploadedDocuments", "InvoiceForms", filename);
if (!System.IO.File.Exists(DestPath))
{
// Getting Index view page as HTML
ViewEngineResult viewResult = ViewEngines.Engines.FindView(ControllerContext, "PrintPTProgressNotePDF", "");
PrintPTProgressNoteViewModel ptModel = PrintPTProgressNotePDF(visitid);
string html = Utility.GetHtmlFromView(ControllerContext, viewResult, "PrintPTProgressNotePDF", ptModel, HttpContext.Request.Url.Scheme, HttpContext.Request.Url.Authority);
string baseUrl = string.Empty;
// Convert the HTML string to PDF using WebKit
Syncfusion.HtmlConverter.HtmlToPdfConverter htmlConverter = new Syncfusion.HtmlConverter.HtmlToPdfConverter(Syncfusion.HtmlConverter.HtmlRenderingEngine.WebKit);
Syncfusion.HtmlConverter.WebKitConverterSettings settings = new Syncfusion.HtmlConverter.WebKitConverterSettings();
// Assign WebKit settings to HTML converter
htmlConverter.ConverterSettings = settings;
// Convert HTML string to PDF
Syncfusion.Pdf.PdfDocument document = htmlConverter.Convert(html, baseUrl);
document.Save(DestPath);
document.Close(true);
}
return DestPath;
}
This is the GetHtmlFromView
method that is being used above that also uses the ControllerContext
:
public static string GetHtmlFromView(ControllerContext context, ViewEngineResult viewResult, string viewName, object model,
string Scheme, string Authority)
{
context.Controller.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
// view not found, throw an exception with searched locations
if (viewResult.View == null)
{
var locations = new StringBuilder();
locations.AppendLine();
foreach (string location in viewResult.SearchedLocations)
{
locations.AppendLine(location);
}
throw new InvalidOperationException(
string.Format(
"The view '{0}' or its master was not found, searched locations: {1}", viewName, locations));
}
ViewContext viewContext = new ViewContext(context, viewResult.View, context.Controller.ViewData, context.Controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
string html = sw.GetStringBuilder().ToString();
string baseUrl = string.Format("{0}://{1}", Scheme, Authority);
html = Regex.Replace(html, "<head>", string.Format("<head><base href=\"{0}\" />", baseUrl), RegexOptions.IgnoreCase);
return html;
}
}
The Create PDF methods work from their parent controllers, but when I call them from the other controller, I get ControllerContext
as null.
Any ideas?
The help page for the ControllerBase.ControllerContext states:
IControllerActivator activates this property while activating controllers. If user code directly instantiates a controller, the getter returns an empty ControllerContext.
I tried to if I could resolve IControllerActivator and have that one active the controller, but the IControllerActivator also requires a controller context, so that comes with the same issue...
I assume the foreach (FormsSubmitted fs in formObjects)
part of your code is in the parent controller you mentioned?
Just pass in the parent context into the child:
OTEvalController cOTEVAL = new OTEvalController() { ControllerContext = this.ControllerContext };
Use the IControllerActivator (or IControllerFactory) as the help suggests, you'd do that like this:
var controllerFactory = this.HttpContext.RequestServices.GetRequiredService<IControllerFactory>();
var newContext = new ControllerContext(this.ControllerContext)
{
ActionDescriptor =
{
ControllerName = nameof(OTEvalController),
ControllerTypeInfo = new TypeDelegator(typeof(OTEvalController)),
MethodInfo = typeof(OTEvalController).GetMethod("CreateOTEvalPrintPDF")
}
};
var resultController = controllerFactory.CreateController(newContext);
Create the controller with manually with the manually created context:
var resultController = new OTEvalController(_mediator) { ControllerContext = newContext }; // newContext from Option 2
The downside of this option is though that if your controller takes any constructor parameters, you also have to provide them manually. With option 2 it resolves the controller and all it's dependencies
This kind of a setup seems pretty weird by itself anyways. I'd suggest to refactor your controllers/logic so it's not depended on the ControllerContext. like move the logic from the controller to a separate "manager" class that's not dependent on Controller properties. You controllers should then extract the data that the "manager class", and pass it on to them.
Try making a unittest in which you can call the entire flow without being depended on Controller stuff