We are currently trying to create an action that returns a JsonResult and at certain times that action should also return some HTML inside it along with the other data. Is it possible to generate the HTML from another action that returns a PartialViewResult?
I think this is what you want to achieve.
Generate HTML from another action sounds strange.
You may get the same model from your repository and then render the same PartialView. You will need a method like the following.
// in a RenderingHelper class
public static string RenderViewToString(ControllerContext context, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = context.RouteData.GetRequiredString("action");
ViewDataDictionary viewData = new ViewDataDictionary(model);
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
ViewContext viewContext = new ViewContext(context, viewResult.View, viewData, new TempDataDictionary(), sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
And then you will render the partial view accordingly:
string renderedHtml = RenderingHelper.RenderViewToString(this.ControllerContext, "~/Views/MyController/MyPartial", viewModel);
Where viewModel is the model used by "other action" too and "~/Views/MyController/MyPartial"is the partial view used by "other action" too.
Related
As the title suggests, I am looking for a way to export a .NET MVC View to a PDF.
My program works like this:
Page 1
Takes in information
Page 2
Takes this information and heavily styles it with CSS etc
So basically I need to save page 2 after it has been processed and used the information from Page 1's model.
Thanks in advance!
To render a non-static page to a pdf, you need to render the page to a string, using a ViewModel, and then convert to a pdf:
Firstly, create a method RenderViewToString in a static class, that can be referenced in a Controller:
public static class StringUtilities
{
public static string RenderViewToString(ControllerContext context, string viewPath, object model = null, bool partial = false)
{
// first find the ViewEngine for this view
ViewEngineResult viewEngineResult = null;
if (partial)
{
viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
}
else
{
viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
}
if (viewEngineResult == null)
{
throw new FileNotFoundException("View cannot be found.");
}
// get the view and attach the model to view data
var view = viewEngineResult.View;
context.Controller.ViewData.Model = model;
string result = null;
using (var sw = new StringWriter())
{
var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
view.Render(ctx, sw);
result = sw.ToString();
}
return result.Trim();
}
}
Then, in your Controller:
var viewModel = new YourViewModelName
{
// Assign ViewModel values
}
// Render the View to a string using the Method defined above
var viewToString = StringUtilities.RenderViewToString(ControllerContext, "~/Views/PathToView/ViewToRender.cshtml", viewModel, true);
You then have the view, generated by a ViewModel, as a string that can be converted to a pdf, using one of the libraries out there.
Hope it helps, or at least sets you on the way.
I have the following view model
public class ProjectVM
{
....
[Display(Name = "Category")]
[Required(ErrorMessage = "Please select a category")]
public int CategoryID { get; set; }
public IEnumerable<SelectListItem> CategoryList { get; set; }
....
}
and the following controller method to create a new Project and assign a Category
public ActionResult Create()
{
ProjectVM model = new ProjectVM
{
CategoryList = new SelectList(db.Categories, "ID", "Name")
}
return View(model);
}
public ActionResult Create(ProjectVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Save and redirect
}
and in the view
#model ProjectVM
....
#using (Html.BeginForm())
{
....
#Html.LabelFor(m => m.CategoryID)
#Html.DropDownListFor(m => m.CategoryID, Model.CategoryList, "-Please select-")
#Html.ValidationMessageFor(m => m.CategoryID)
....
<input type="submit" value="Create" />
}
The view displays correctly but when submitting the form, I get the following error message
InvalidOperationException: The ViewData item that has the key 'CategoryID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
The same error occurs using the #Html.DropDownList() method, and if I pass the SelectList using a ViewBag or ViewData.
The error means that the value of CategoryList is null (and as a result the DropDownListFor() method expects that the first parameter is of type IEnumerable<SelectListItem>).
You are not generating an input for each property of each SelectListItem in CategoryList (and nor should you) so no values for the SelectList are posted to the controller method, and therefore the value of model.CategoryList in the POST method is null. If you return the view, you must first reassign the value of CategoryList, just as you did in the GET method.
public ActionResult Create(ProjectVM model)
{
if (!ModelState.IsValid)
{
model.CategoryList = new SelectList(db.Categories, "ID", "Name"); // add this
return View(model);
}
// Save and redirect
}
To explain the inner workings (the source code can be seen here)
Each overload of DropDownList() and DropDownListFor() eventually calls the following method
private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
IDictionary<string, object> htmlAttributes)
which checks if the selectList (the second parameter of #Html.DropDownListFor()) is null
// If we got a null selectList, try to use ViewData to get the list of items.
if (selectList == null)
{
selectList = htmlHelper.GetSelectData(name);
usedViewData = true;
}
which in turn calls
private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
which evaluates the the first parameter of #Html.DropDownListFor() (in this case CategoryID)
....
o = htmlHelper.ViewData.Eval(name);
....
IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
if (selectList == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
MvcResources.HtmlHelper_WrongSelectDataType,
name, o.GetType().FullName, "IEnumerable<SelectListItem>"));
}
Because property CategoryID is typeof int, it cannot be cast to IEnumerable<SelectListItem> and the exception is thrown (which is defined in the MvcResources.resx file as)
<data name="HtmlHelper_WrongSelectDataType" xml:space="preserve">
<value>The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.</value>
</data>
according to stephens (user3559349) answer, this can be useful:
#Html.DropDownListFor(m => m.CategoryID, Model.CategoryList ?? new List<SelectListItem>(), "-Please select-")
or in ProjectVM:
public class ProjectVM
{
public ProjectVM()
{
CategoryList = new List<SelectListItem>();
}
...
}
Most Likely Caused some sort of error redirecting to your page and you not initializing your model's drop down lists again.
Make sure that you initialize your drop downs in either the model's constructor or every time before you send said model to the page.
Otherwise you will need to maintain the state of the drop down lists either through the view bag or through the hidden value helpers.
OK, the poster's canned answer neatly explained why the error occurred, but not how to get it to work. I'm not sure that's really an answer, but it did point me in the right direction.
I ran into the same issue and found a slick way to resolve it. I'll try to capture that here. Disclaimer - I work on web pages once a year or so and really don't know what I'm doing most of the time. This answer should in no way be considered an "expert" answer, but it does the job with little work...
Given that I have some data object (most likely a Data Transfer Object) that I want to use a drop-down list to supply valid values for a field, like so:
public class MyDataObject
{
public int id;
public string StrValue;
}
Then the ViewModel looks like this:
public class MyDataObjectVM
{
public int id;
public string StrValue;
public List<SectListItem> strValues;
}
The real problem here, as #Stephen so eloquently described above, is the select list isn't populated on the POST method in the controller. So your controller methods would look like this:
// GET
public ActionResult Create()
{
var dataObjectVM = GetNewMyDataObjectVM();
return View(dataObjectVM); // I use T4MVC, don't you?
}
private MyDataObjectVM GetNewMyDataObjectVM(MyDataObjectVM model = null)
{
return new MyDataObjectVM
{
int id = model?.Id ?? 0,
string StrValue = model?.StrValue ?? "",
var strValues = new List<SelectListItem>
{
new SelectListItem {Text = "Select", Value = ""},
new SelectListITem {Text = "Item1", Value = "Item1"},
new SelectListItem {Text = "Item2", Value = "Item2"}
};
};
}
// POST
public ActionResult Create(FormCollection formValues)
{
var dataObject = new MyDataObject();
try
{
UpdateModel(dataObject, formValues);
AddObjectToObjectStore(dataObject);
return RedirectToAction(Actions.Index);
}
catch (Exception ex)
{
// fill in the drop-down list for the view model
var dataObjectVM = GetNewMyDataObjectVM();
ModelState.AddModelError("", ex.Message);
return View(dataObjectVM);
)
}
There you have it. This is NOT working code, I copy/pasted and edited to make it simple, but you get the idea. If the data members in both the original data model and the derived view model have the same name, UpdateModel() does an awesome job of filling in just the right data for you from the FormCollection values.
I'm posting this here so I can find the answer when I inevitably run into this issue again -- hopefully it will help someone else out as well.
I had the same problem, I was getting an invalid ModelState when I tried to post the form. For me, this was caused by setting CategoryId to int, when I changed it to string the ModelState was valid and the Create method worked as expected.
In my case the first ID in my list was zero, once I changed the ID to start from 1, it worked.
My purpose of this is to render string into view. This is what i have done:
public string RenderRazorViewToString(string viewName, object model, string controllerName)
{
ViewData.Model = model;
Type controllerType = Type.GetType(controllerName + "Controller");
object controller = Activator.CreateInstance(controllerType);
RouteData rd = new System.Web.Routing.RouteData();
rd.Values.Add("controller", "Account");
((ControllerBase)(controller)).ControllerContext = new ControllerContext(HttpContext, rd, (ControllerBase)controller);
ControllerContext cc = ((ControllerBase)(controller)).ControllerContext;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindView(cc, viewName, null);
var viewContext = new ViewContext(cc, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(cc, viewResult.View);
return sw.GetStringBuilder().ToString();
}
}
Basically what this code does is to render a view file that is found in :
var viewResult = ViewEngines.Engines.FindView(cc, viewName, null);
And renders it in :
viewResult.View.Render(viewContext, sw);
What i want is not finding view file, but i want to render view that is stored in my database as string. So what i need is:
Getting database view string
Render the database view string
Output it as string after render
How can i achieve this?
You can simply use
#Html.Raw(mystring)
mystring will be the view content stored in database
What is the best way to generate html page from data in view? I have a html template with all tables and etc. Don't want to use any templating like JqueryTemplate.
Try this approach using the hiqpdf html to pdf converter, a commercial product:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
Session["MySessionVariable"] = "My Session Variable Value assigned in Index";
return View();
}
public ActionResult About()
{
return View();
}
public string RenderViewAsString(string viewName, object model)
{
// create a string writer to receive the HTML code
StringWriter stringWriter = new StringWriter();
// get the view to render
ViewEngineResult viewResult = ViewEngines.Engines.FindView(ControllerContext, viewName, null);
// create a context to render a view based on a model
ViewContext viewContext = new ViewContext(
ControllerContext,
viewResult.View,
new ViewDataDictionary(model),
new TempDataDictionary(),
stringWriter
);
// render the view to a HTML code
viewResult.View.Render(viewContext, stringWriter);
// return the HTML code
return stringWriter.ToString();
}
[HttpPost]
public ActionResult ConvertThisPageToPdf()
{
// get the HTML code of this view
string htmlToConvert = RenderViewAsString("Index", null);
// the base URL to resolve relative images and css
String thisPageUrl = this.ControllerContext.HttpContext.Request.Url.AbsoluteUri;
String baseUrl = thisPageUrl.Substring(0, thisPageUrl.Length - "Home/ConvertThisPageToPdf".Length);
// instantiate the HiQPdf HTML to PDF converter
HtmlToPdf htmlToPdfConverter = new HtmlToPdf();
// hide the button in the created PDF
htmlToPdfConverter.HiddenHtmlElements = new string[] { "#convertThisPageButtonDiv" };
// render the HTML code as PDF in memory
byte[] pdfBuffer = htmlToPdfConverter.ConvertHtmlToMemory(htmlToConvert, baseUrl);
// send the PDF file to browser
FileResult fileResult = new FileContentResult(pdfBuffer, "application/pdf");
fileResult.FileDownloadName = "ThisMvcViewToPdf.pdf";
return fileResult;
}
[HttpPost]
public ActionResult ConvertAboutPageToPdf()
{
// get the About view HTML code
string htmlToConvert = RenderViewAsString("About", null);
// the base URL to resolve relative images and css
String thisPageUrl = this.ControllerContext.HttpContext.Request.Url.AbsoluteUri;
String baseUrl = thisPageUrl.Substring(0, thisPageUrl.Length - "Home/ConvertAboutPageToPdf".Length);
// instantiate the HiQPdf HTML to PDF converter
HtmlToPdf htmlToPdfConverter = new HtmlToPdf();
// render the HTML code as PDF in memory
byte[] pdfBuffer = htmlToPdfConverter.ConvertHtmlToMemory(htmlToConvert, baseUrl);
// send the PDF file to browser
FileResult fileResult = new FileContentResult(pdfBuffer, "application/pdf");
fileResult.FileDownloadName = "AboutMvcViewToPdf.pdf";
return fileResult;
}
}
Source of this sample code: How to convert HTML to PDF using HiQPDF
Just create pdf server side and return File instead of html view.
I don't what kind of pdf provider do you use but this a solution for iTextSharp:
How to return PDF to browser in MVC?
I have an ASP.NET MVC 3 Controller Action that accepts an object as follows:
//// model.ReferenceNumber equals "-1"
public ActionResult EditApplication(EditApplicationViewModel model)
{
if (!ModelState.IsValid)
{
...
}
// Existing Applicant
model.ReferenceNumber = "";
var msg = this.RenderPartialViewToString("_ExistingApplication", model);
}
When the request is received, the "model.ReferenceNumber" equals "-1". Even after setting the property on the model to empty string "", still the results of "RenderPartialViewToString" shows the value of "-1" in the "Value" property of the "input" HTML field.
Any idea why this is happening?
This is the code used to render the partial view:
http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/
public static string RenderPartialViewToString(this Controller controller, string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
controller.ViewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Thanks
When you modify the value of some model property in your controller qction make sure you remove it from the modelstate as well:
ModelState.Remove("ReferenceNumber");
model.ReferenceNumber = "new value";
var msg = this.RenderPartialViewToString("_ExistingApplication", model);