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?
CodePudding user response:
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?
So option 1:
Just pass in the parent context into the child:
OTEvalController cOTEVAL = new OTEvalController() { ControllerContext = this.ControllerContext };
Option 2:
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);
Option 3:
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
Sidenote:
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
CodePudding user response:
You can use DI, inject your controllers into the controller and use them instead of creating a new instance of each controller and losing the ControllerContext. Like that the lifecycle of your controllers is managed by the application in the right way.
If you need an example you can check => https://stackoverflow.com/a/35056047/8404545