Mvc Render Action/Partial to Response Output - asp.net-mvc

Although #Html.RenderPartial calls write and returns void, it is still writing to a StringWriter/StringBuilder. Is there a way to render directly to the ResponseStream?
Can this be done with a custom IViewEngine that implements render like PdfView to directly output to the ResponseStream?
ADDITION
ViewResultBase.ExecuteResult shows the ViewContext being built with Response.Output, but debugger shows ViewContext.Writer as a StringWriter
Both of these approaches results in a StringWriter
return PartialView("view", Model)
// or
PartialView("view", Model).ExecuteResult(ControllerContext)
EDIT
It appears that System.Web.WebPages.WebPageBase ExecutePageHeirarchy pushes a temp StringWriter onto the context stack, so I'm not sure if this can be bypassed
IN SUMMARY
RenderPartial, RenderAction do not directly output to the Response.Stream, none of Razor Views will
SOLUTION
It was the new WebPages/Razor rendering engine that wraps everything with a StringWriter to a StringBuilder. The solution was to change my page to use the WebFormViewEngine which does not apply this wrapping.

This below method illustrates one way achieving the outcome you are looking for:
// <summary>
// An extension methods for rendering a model/view into a stream
// </summary>
// <param name="myModel">The model you are trying render to a stream</param>
// <param name="controllerBase">This will come from your executing action</param>
// <returns></returns>
public static Stream GetStream(CustomModel myModel, ControllerBase controllerBase)
{
//we will return this stream
MemoryStream stream = new MemoryStream();
//you can add variables to the view data
controllerBase.ViewData["ViewDataVariable1"] = true;
//set your model
controllerBase.ViewData.Model = myModel;
//The example uses the UTF-8 encoding, you should change that if you are using some other encoding.
//write to a stream
using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
{
using (var sw = new StringWriter())
{
//render the view ~/Views/Shared/_FeedbackMessage.cshtml (can be passed in as a parameter if you want to make it super generic)
var viewResult = ViewEngines.Engines.FindPartialView(controllerBase.ControllerContext, "_FeedbackMessage");
//create a new view context
var viewContext = new ViewContext(controllerBase.ControllerContext, viewResult.View, controllerBase.ViewData, controllerBase.TempData, sw);
//Render the viewengine and let razor do its magic
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(controllerBase.ControllerContext, viewResult.View);
//get StringBuilder from StringWriter sw and write into the stream writer
//you could simply return the StringWriter here if that is what you were interested in doing
writer.Write(sw.GetStringBuilder().ToString());
writer.Flush();
stream.Position = 0;
}
}
//return the stream from the above process
return stream;
}

Related

Rendering a Razor view as a string within a Task

The following code works fine for rendering a Razor View to a string:
///
/// url: /api/createHtml
///
public ActionResult CreateHtml()
{
// Heavy calculations
MyModel myModel = new MyModel();
myModel.Feature1 = ...;
myModel.Feature2 = ...;
myModel.Feature3 = ...;
ViewData.Model = myModel;
using (var stringWriter = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, "MyView");
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, stringWriter);
viewResult.View.Render(viewContext, stringWriter);
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
string html = stringWriter.GetStringBuilder().ToString();
byte[] htmlBytes = Encoding.ASCII.GetBytes(html);
System.IO.File.WriteAllBytes(Server.MapPath("~/temp/foo.html"), htmlBytes);
}
return JSON(new
{
error = false,
message = "Your view is available at temp/foo.html"
});
}
The above code runs synchronously, meaning that an AJAX request to /api/createHtml/ will finish with the temp/foo.html file created.
I want to do this asynchronously: meaning that the AJAX request returns fast to the user with a message like: "Your view WILL BE available at temp/foo.html". And then the user must go check if the file is ready (by simply polling to the temp directory [or using other method, not important in this question])
So, when I try the same code within a Task, it doesn't work:
///
/// url: /api/createHtml
///
public ActionResult CreateHtml()
{
new Task(() =>
{
// Heavy calculations
MyModel myModel = new MyModel();
myModel.Feature1 = ...;
myModel.Feature2 = ...;
myModel.Feature3 = ...;
ViewData.Model = myModel;
using (var stringWriter = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, "MyView");
var viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, stringWriter);
viewResult.View.Render(viewContext, stringWriter); // <--- Problem
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
string html = stringWriter.GetStringBuilder().ToString();
byte[] htmlBytes = Encoding.ASCII.GetBytes(html);
System.IO.File.WriteAllBytes(Server.MapPath("~/temp/foo.html"), htmlBytes);
}
}).Start();
return JSON(new
{
error = false,
message = "Your view _WILL BE_ available at temp/foo.html"
});
}
It doesn't work because it throws an Exception at viewResult.View.Render(...)
Value does not fall within the expected range.
It seems that the viewContext passed to viewResult.View.Render(...) is no longer valid in the new thread, as shown here: ASP.NET MVC: Exception rendering view in new thread
Is there a workaround for rendering a view within a Task ?
I know I could use "RazorEngine", a free library that renders razor views without all the controller mumbo jumbo, but I'd prefer to use native code, for reutilization of the code.
POST EDITED:
The few answers thought that I wanted to use "await async". I don't. I don't want to wait for the task to finish.
Well your tasks is probably doing what it suppose to do, but the main threat is not waiting for him.
Try using:
var myTask=new Task(() =>
{
// all of your code
}).Start();
myTask.Wait();
check more info
The best way is using async and await, because Tasks should be used for asynchronous operations, good luck.
Asynchronous action methods, works async only inside the web server. The advantage of async methods is that they release the precious thread back to the thread pool, when they don't need it (as opposed to blocking it)... But the HttpRequest is still synchronous... your browser would synchronously wait for a response from an async action method.
What you need is to spawn a new thread or task to do your long running task and return from the action method.
Take a look at: Long Running Background Tasks in Asp.Net MVC3
Also this question might help: Do asynchronous operations in ASP.NET MVC use a thread from ThreadPool on .NET 4

How to save any file/data in application folder in MVC?

I'm using a library for converting HTML to PDF. After converting to PDF how can I save this converted PDF file in application folder in a controller?
Here is the code:
public ActionResult ABC(ResearchProposal model)
{
ViewDataDictionary viewData = new ViewDataDictionary(model);
// transmit the posted data to view
viewData["MyModel"] = model;
StringWriter stringWriter = new StringWriter();
// Render the Index view in a HTML string
ViewEngineResult viewResult = ViewEngines.Engines.FindView(ControllerContext, "ABC", null);
ViewContext viewContext = new ViewContext(
ControllerContext,
viewResult.View,
viewData,
new TempDataDictionary(),
stringWriter
);
viewResult.View.Render(viewContext, stringWriter);
// Get the view HTML string
string htmlToConvert = stringWriter.ToString();
// Get the base URL
String currentPageUrl = this.ControllerContext.HttpContext.Request.Url.AbsoluteUri;
String baseUrl = currentPageUrl.Substring(0, currentPageUrl.Length - "Reports/ABC".Length);
// Create a HTML to PDF converter object with default settings
HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();
// Set license key received after purchase to use the converter in licensed mode
// Leave it not set to use the converter in demo mode
htmlToPdfConverter.LicenseKey = "fvDh8eDx4fHg4P/h8eLg/+Dj/+jo6Og=";
// Set an adddional delay in seconds to wait for JavaScript or AJAX calls after page load completed
// Set this property to 0 if you don't need to wait for such asynchcronous operations to finish
htmlToPdfConverter.ConversionDelay = 2;
// Convert the HTML string to a PDF document in a memory buffer
byte[] outPdfBuffer = htmlToPdfConverter.ConvertHtml(htmlToConvert, baseUrl);
// Send the PDF file to browser
FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
fileResult.FileDownloadName = "Convert_Current_Page.pdf";
return fileresult;
}
You can use File.WriteAllBytes to save the bytes of the FileContentResult that you got.
You will need to map the relative path of the server for this to work, as relative path only works in the context of ASP.NET.
string filename = "Convert_Current_Page.pdf";
string path = Server.MapPath("~G/Initial try/Content/data/");
path = Path.Combine(path, filename);
File.WriteAllBytes(path, fileResult.FilecContents);

How to convert my html Razor view to pdf document without any third-party tool

I am just new in MVC 4. I have a html Razor view which contain all some table related data.
I just want to convert that view in to pdf document without third-party tool.
In case you are using ASP.NET Core here is my solution: http://nikolay.it/Blog/2018/03/Generate-PDF-file-from-Razor-view-using-ASP-NET-Core-and-PhantomJS/37
Get HTML string from a Razor view
This step is pretty straight-forward. There is a service called IRazorViewEngine in ASP.NET Core which can be injected and then used to get the view. After providing the view with default ViewDataDictionary and ActionContext we can request the view to be rendered into StringWriter which can be easily converted to string. Here is ready-to-use code for getting a string from given Razor view file:
public interface IViewRenderService
{
Task<string> RenderToStringAsync(string viewName, object model);
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine razorViewEngine;
private readonly ITempDataProvider tempDataProvider;
private readonly IServiceProvider serviceProvider;
public ViewRenderService(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
this.razorViewEngine = razorViewEngine;
this.tempDataProvider = tempDataProvider;
this.serviceProvider = serviceProvider;
}
public async Task<string> RenderToStringAsync(string viewName, object model)
{
var httpContext = new DefaultHttpContext { RequestServices = this.serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using (var sw = new StringWriter())
{
var viewResult = this.razorViewEngine.GetView(null, viewName, false);
if (viewResult.View == null)
{
throw new ArgumentNullException($"{viewName} does not match any available view");
}
var viewDictionary =
new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary()) { Model = model };
var viewContext = new ViewContext(
actionContext,
viewResult.View,
viewDictionary,
new TempDataDictionary(actionContext.HttpContext, this.tempDataProvider),
sw,
new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
return sw.ToString();
}
}
}
One important think here: if you use view compilation (pre-compiling views to YourProject.Web.PrecompiledViews.dll) then it is important to get the view using the GetView method instead of FindView. More information here.
Generate the PDF file from HTML using PhantomJS
For this task we are going to use a headless browser which will render the HTML (with all CSS and JS included in it). There are many such tools but I will use PhantomJS (headless WebKit scriptable with a JavaScript API). PhantomJS can save the rendered page to small-sized PDF pretty fast. For the PDF export to work we are going to need a .js file which will use the PhantomJS API to tell the tool that we want to export the file:
"use strict";
var page = require('webpage').create(),
system = require('system'),
address,
output;
console.log('Usage: rasterize.js [URL] [filename] [paperformat]');
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
page.paperSize = { format: system.args[3], orientation: 'portrait', margin: '0.5cm' };
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
The next thing is to run the phantomjs.exe process and pass the rasterize.js file along with paths for the HTML file and the output file name for the PDF result. This is done in HtmlToPdfConverter.cs:
public interface IHtmlToPdfConverter
{
byte[] Convert(string htmlCode);
}
public class HtmlToPdfConverter : IHtmlToPdfConverter
{
public byte[] Convert(string htmlCode)
{
var inputFileName = "input.html";
var outputFileName = "output.pdf";
File.WriteAllText(inputFileName, htmlCode);
var startInfo = new ProcessStartInfo("phantomjs.exe")
{
WorkingDirectory = Environment.CurrentDirectory,
Arguments = string.Format(
"rasterize.js \"{0}\" {1} \"A4\"",
inputFileName,
outputFileName),
UseShellExecute = true,
};
var process = new Process { StartInfo = startInfo };
process.Start();
process.WaitForExit();
var bytes = File.ReadAllBytes(outputFileName);
File.Delete(inputFileName);
File.Delete(outputFileName);
return bytes;
}
}
If you are going to deploy your application in Azure it is important to have UseShellExecute set to true.
Use the code together
Since we now have implemented both IViewRenderService and IHtmlToPdfConverter we can start using them by first register them in the Startup.cs file where your ConfigureServices method should be located (services.AddScoped<IViewRenderService, ViewRenderService>() and services.AddScoped<IHtmlToPdfConverter, HtmlToPdfConverter>()). Now lets see the code wrapped up together:
private readonly IViewRenderService viewRenderService;
private readonly IHtmlToPdfConverter htmlToPdfConverter;
public DashboardController(
IViewRenderService viewRenderService,
IHtmlToPdfConverter htmlToPdfConverter)
{
this.viewRenderService = viewRenderService;
this.htmlToPdfConverter = htmlToPdfConverter;
}
[HttpGet]
public async Task<IActionResult> GetPdf(SomeInputModel input)
{
var model = this.GetViewModel(input);
var htmlData = await this.viewRenderService.RenderToStringAsync("~/Views/Dashboard/GetPdf.cshtml", model);
var fileContents = this.htmlToPdfConverter.Convert(htmlData);
return this.File(fileContents, "application/pdf");
}
Since ASP.NET MVC has no built-in PDF functionality, the only way to convert a Razor view to PDF without a third party tool would be to write the conversion code yourself. This would be a massive undertaking and almost certainly not worth the effort. I would start by purchasing the ISO 32000-1 reference document and learning everything I could about how the PDF format works. At the time of this answer, the cost is around 200 Swiss francs.
MVC4 - even if it is the name of your Frameworks should be understood as the pattern called Model-View-Controller (MVC). The Idea of this pattern is to separate the these three components in order to replace them with different channels whenever reqeuired.
In your case, the Razor Template is a tool to generate a VIEW in HTML. Razor is limited to HTML - your pattern isn't. By design of MVC you can use any other tool to replace the VIEW from a HTML to a PDF, XML or whatever you desire.
what you should be looking for is a way, to use your existing MODEL (not VIEW) and generate a PDF output, using any library needed.

Render partial view as string

I know this looks like a duplicate question, but please read the whole question before marking it as duplicate.
First of all, I'm simulating the windows service in my ASP web application to send weekly emails, so in Global.asax I'm running my function that will send the emails.
Now the emails content is in HTML and I want to render the views to get the content. The problem is that in my function, I don't have any of the following :
Controller
ControllerContext
HttpContext
RoutData
... & much more. Which makes sense, because the function was invoked as a callback not as a HTTP request action.
I tried to use the RazorEngine to use the partial as a template by reading the file then using Razor.Parse() method. But I faced a lot of problems from this approach, because nothing is included in the template. What I mean is: it keeps telling me that The name "Html" does not exist in the current context OR 'CompiledRazorTemplates.Dynamic.becdccabecff' does not contain a definition for 'Html' even if I include the System.Web.Mvc.Html.
how can I solve this issue?.
I think the best approach is assuming you developed a real NT service and use HttpClient to send a http request to your partial view and receive the response as string and use it to make up your email. However, you can have HttpContext in RunScheduledTasks method by making some changes in Scheduler class.
public delegate void Callback();
to
public delegate void Callback(HttpContext httpContext);
add cache.Current_HttpContext = HttpContext.Current; to the Run method
public static void Run(string name, int minutes, Callback callbackMethod)
{
_numberOfMinutes = minutes;
CacheItem cache = new CacheItem();
cache.Name = name;
cache.Callback = callbackMethod;
cache.Cache = HttpRuntime.Cache;
cache.LastRun = DateTime.Now;
cache.Current_HttpContext = HttpContext.Current;
AddCacheObject(cache);
}
change CacheCallback to
private static void CacheCallback(string key, object value, CacheItemRemovedReason reason)
{
CacheItem obj_cache = (CacheItem)value;
if (obj_cache.LastRun < DateTime.Now)
{
if (obj_cache.Callback != null)
{
obj_cache.Callback.Invoke(obj_cache.Current_HttpContext);
}
obj_cache.LastRun = DateTime.Now;
}
AddCacheObject(obj_cache);
}
Edited:
How to use HttpClient
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://localhost/controller/action/");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Without using 3rd party library, one can use this method to generate string of view in Global.asax.cs file
public class EmptyController : Controller { }
public string GetView(string viewName)
{
//Create an instance of empty controller
Controller controller = new EmptyController();
//Create an instance of Controller Context
var ControllerContext = new ControllerContext(Request.RequestContext, controller);
//Create a string writer
using (var sw = new StringWriter())
{
//get the master layout
var master = Request.IsAuthenticated ? "_InternalLayout" : "_ExternalLayout";
//Get the view result using context controller, partial view and the master layout
var viewResult = ViewEngines.Engines.FindView(ControllerContext, viewName, master);
//Crete the view context using the controller context, viewResult's view, string writer and ViewData and TempData
var viewContext = new ViewContext(ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
//Render the view in the string writer
viewResult.View.Render(viewContext, sw);
//release the view
viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
//return the view stored in string writer as string
return sw.GetStringBuilder().ToString();
}
}

Returning a view as part of a JSON object

I have an app that only ever loads a full view one time. My reason for doing this is not important. What is important is that the rest of the content is only ever going to come back in partial views. In addition to some content I have some JSON objects I'd like to pass back and forth to and from the server with each AJAX request.
Is there a way to return a JSON object with a view as one of its properties? This would be extremely useful and would save on bandwidth as my current workaround is to make two ajax calls, one for the JSON and one for the partial view which not only takes more time and more bandwidth, but it also requires two separate action methods and some fancy tricks on the server side. Serializing a view into a JSON object would solve all my problems.
What is the best way to accomplish this and what downsides (if any) would there be to doing this?
You can render the view from the controller and return it with the JSON object back to the client.
If you will use my simple helper to render ActionResult to a string then your code will look like:
public JsonResult DoSomething() {
var viewString = View().Capture(ControllerContext);
return new JsonResult {
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new {
time = DateTime.Now,
html = viewString
}
};
}
Here's an interesting tidbit of code that seems to do what I want and preserves model binding from what I can tell.
protected string RenderPartialViewToString(string viewName, object model)
{
controller.ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
ViewContext viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Works like a charm. I just use this and pass the string as a JSON parameter and then on the client I read the parameter and drop it in it's appropriate container. I'm very excited to have this working.

Resources