Output current html view to string when button clicked - c#

I have a print-ready view that renders nicely in the browser. It would be nice to offer users the option to click a link or button located on that view that would call an action to create a PDF from the raw HTML in the view. I have the PDF processing part figured out, I just need help figuring out:
When link or button is clicked, put the rendered HTML from that view into a string variable to be sent to my PDF processing code.
Here is my Controller method that renders the page:
public ActionResult ViewReport(int? id, string memberID, int month, int year)
{
var task = new ViewReportTask();
return View(task.BuildViewModel(id, memberID, month, year));
}
The view is just a lot of html and razor code blocks so I did not include it here.
Thanks

I'm using following approach:
You have Action method which generates your View:
public ActionResult ViewReport(int? id, string memberID, int month, int year)
{
var task = new ViewReportTask();
return View(task.BuildViewModel(id, memberID, month, year));
}
Create one more ActionResult:
public ActionResult PrintMyView(int? id, string memberID, int month, int year)
{
return new ActionAsPdf( "ViewReport", new { id= id; memberID=memberID; month=month; year=year})
{ FileName = "ViewReport.pdf"};
}
To have ActionAsPdf method you need to install Rotativa Nuget Package:
Install-Package Rotativa
Now to save your page as pdf user must click on following link on your view:
#Html.ActionLink("Save as PDF, "PrintMyView", "Home", new{id= id, memberID=memberID, month=month, year=year}, null)
//** I can't see where you takes parameters in your view, so I just list them.
It works fine for me.

Related

ActionResult Create with conditions

im doing an MVC with CRUDS.
this is my code.
[HttpPost]
public ActionResult Create([Bind(Include = "FileStatusID, Name, MinValue, MaxValue")] fileStatusModel FILeStatusModel, TBL_FileStatus tBL_FileStatus) //include tem os valores que vamos inserir na view
{
var userID = ((SessionModel)Session["SessionModel"]).UserID; // get current user id
if (ModelState.IsValid)
{
TBL_FileStatus item = new TBL_FileStatus()
{
Name = FILeStatusModel.Name,
MinValue = FILeStatusModel.MinValue,
MaxValue = FILeStatusModel.MaxValue,
Ative = true,
CreateDate = DateTime.Now,
CreateBy = userID
};
db.TBL_FileStatus.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
I want to create a status with special conditions.
I have a table with ID, status, minvalue and maxvalue and have an interval of numbers in those values.
I want to create another status out of the range of those numbers.
ex: minvalue: 20 maxvalue: 40
So.. When i create a new status, if i put numbers inside that range, its say a message like "already exist in that range", if not, it creats de status.
thanks
1. Rendering layout page from ActionResult (using Controller. View extension method)
The Controller. View method has two extension methods, using these
extension methods we can pass a master page (layout page) name and
render a layout page based on a condition.
Example Code
public ActionResult About()
{
return View("About","_otherLayout");
}
public ActionResult OtherAbout()
{
string myName = "Jignesh Trivedi";
return View("About", "_otherLayout", myName);
}
2. Using _ViewStart.cshtml Page
The Controller. View method has two extension methods, using these
extension methods we can pass a master page (layout page) name and
render a layout page based on a condition.
Using the _ViewStart.cshtml page, we can change the layout page based
on a condition.
Example Code
#{
var controller = HttpContext.Current.Request.RequestContext.RouteData.Values["Controller"].ToString();
string layout = "";
if (controller != "Home")
{
layout = "~/Views/Shared/_otherLayout.cshtml";
}
else
{
layout = "~/Views/Shared/_Layout.cshtml";
}
Layout = layout;
}
We can also create multiple _ViewStart.cshtml pages. The file
execution is dependent upon the location of the file within the folder
hierarchy and the view being rendered. The MVC Runtime will first
execute the code of the _ViewStart.cshtml file located in the root of
the Views folder.
3. Define the Layout page in each view
We can override the default layout rendering by setting the Layout
property of the View using the following code.
#{
Layout = "~/Views/Shared/_otherLayout.cshtml";
ViewBag.Title = "About Us";
}
As presented in the article by : Jignesh Trivedi at
https://www.c-sharpcorner.com/UploadFile/ff2f08/rendering-layouts-base-on-condition-in-Asp-Net-mvc/

Navigation buton ASP.NET

I have multiple static htmls stored in the /Content/ file. I have a method in the controller which displays these static html files.
public ActionResult GetHTML(int sectionId)
{
var result = new FilePathResult($"~/Content/files/{sectionId}.html", "text/html");
return result;
}
I have no view for this method, but I'd like to create one and to include a button which allows me to navigate to the next/previous static html.
Any help is highly appreciated.
You can create a regular view with a FilePathResult as its model.
If the model is not null in the view then you can create a hyper link in the view from result:
Next Page

How to get byte[] to display as a background image for a div on a view (C#, ASP.NET, MVC)

I am building a web app in C# and ASP.Net with an MVC framework. I have the app running on my local desktop (for now). The app has a SQL backend that stores all my data. I am able to pull the data from the SQL db successfully through a number of stored procedures. The data is able to successfully be transferred from the stored procedures all the way up to my view from the controller.
Part of the data being transferred to the view is a byte[] from an image stored in the db (datatype is VARBINARY(MAX)). In short, I am trying to get the data from this byte[] to display as a background image in a div. This div acts as a single image in a Bootstrap carousel.
Initially, I had the following as my controller:
public ActionResult Dashboard()
{
DashboardViewModelHolder holder = new DashboardViewModelHolder();
DiscoveryService discoveryService = new DiscoveryService();
holder.national_Elected_Officials = new List<National_Elected_Officials_Model>();
National_Elected_Officials_Model n = new National_Elected_Officials_Model();
foreach (List<object> official in discoveryService.retrieve_National_Elected_Officials())
{
for(int i = 0; i <= official.Count; i++)
{
int id = int.Parse(official.ElementAt(0).ToString());
string fname = official.ElementAt(1).ToString();
string lname = official.ElementAt(2).ToString();
byte[] pictureByteArray = (byte[])official.ElementAt(3);
string position = official.ElementAt(4).ToString();
string party = official.ElementAt(5).ToString();
string bio = official.ElementAt(6).ToString();
int yearsOfService = int.Parse(official.ElementAt(7).ToString());
int terms = int.Parse(official.ElementAt(8).ToString());
string branch = official.ElementAt(9).ToString();
Image picture = image_Adapter.byteArrayToImage(pictureByteArray);
n.ElectedOfficialID = id;
n.FirstName = fname;
n.LastName = lname;
n.Picture = picture;
n.Position = position;
n.Party = party;
n.Bio = bio;
n.YearsOfService = yearsOfService;
n.Terms = terms;
n.Branch = branch;
}
holder.national_Elected_Officials.Add(n);
}
return View(holder);
}
My thought process was that I would just call n.Picture in my view and it would render the picture. After several tries and tutorials later, I left n.Picture as a byte[] and processed it in its own ActionResult method as seen below:
public FileContentResult Image(byte[] pictureByteArray)
{
return new FileContentResult(pictureByteArray, "image/jpeg");
}
I call this in my view as the following:
<div class="fill" style="background-image:src(#Url.Action("Image", electedOfficial.Picture))"></div>
electedOfficial is a reference to the model being set in the controller (n.Picture).
Is there something that I am missing?
EDIT 1
I forgot to add that the div returns null when I debug and step through the code. This is because the line with the div never gets called on when debugging. If I have it set as Url.Action, the program will actually go to the controller before hitting the line. If I do Html.Action, the program will skip the line and go to the controller after. Both will return null as a result which returns an error on the controller side since nulls arent allowed.
Edit 2
I tried changing the div tag to the following:
<div class="fill" style="background-image:src(#{Html.Action("Image", electedOfficial.Picture);})"></div>
By putting the {} in the debugger actually parses the line as I step through. Now, the problem is that the controller is not receiving the value being passed to it from electedOfficial.Picture. Just to confirm, this variable does hold the correct value in the view.
If you have the full byte[] in your model, then you can put the data directly into the view:
<div style="background:url( data:image/jpeg;base64,#Convert.ToBase64String(electedOfficial.Picture) )"></div>
This will work without the need for a separate controller that returns a FileContentResult, but will be a longer initial page load since the user will download all of the images along with the page HTML.
If you want to use a Controller endpoint so the images can be referenced as a URL in the src attribute and downloaded after the HTML has rendered then you are not too far off. It would work better to have the controller accept ElectedOfficialID and return the FileContentResult from that.
public FileContentResult Image(int electedOfficialId)
{
byte[] picture = GetPicture(electedOfficialId);
return new FileContentResult(picture, "image/jpeg");
}
Simples way of doing that would be encoding image as base64 string and add new string property eg PictureAsString to model instead having Picture
controller
n.PictureAsString = Convert.ToBase64String(pictureByteArray)
view
<div style="background:url(data:image/jpeg;base64,#electedOfficial.PictureAsString )" ></div>
use handler(ASHX). and call handler url in src.
public class MyHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
//Get Id from somewhere
//Get binary data
context.Response.ContentType = "application/octet-stream";
context.Response.BinaryWrite(bytes);
}
}
You can convert your byte array into a picture using this way:
Convert your byte array into a base64 string.
Display it in <img> tag.
Here is the code:
#{
var base64 = Convert.ToBase64String(Model.ByteArray);
var imgSrc = String.Format("data:image/gif;base64,{0}", base64);
}
<img src="#imgSrc" />

How to return multiple images from controller method?

I have an ASP.NET MVC controller that generates images (they are stored in memory, I don't want to store them on the hard drive) and should return them to my view.
The problem is: I don't how know how to return multiple images from one controller method (I want to show the images in 1 view). I know that I can return a single image with the FileResult for example, but I can't find out (not on google/stackoverflow) how to return multiple images from the same method. Splitting the method up in multiple methods can't be done. Oh, all of the images are converted to a byte[], but that can be reversed if necessary.
This should work. Note I am reading my images from disk for my example but they can come from memory or anywhere. Then on the client side use java-script to display them.
[HttpGet]
public JsonResult Images()
{
var image1Base64 = Convert.ToBase64String(System.IO.File.ReadAllBytes(Server.MapPath("~/Images/1.jpg")));
var image2Base64 = Convert.ToBase64String(System.IO.File.ReadAllBytes(Server.MapPath("~/Images/2.jpg")));
var jsonResult = Json(new { image1 = image1Base64, image2 = image2Base64 }, JsonRequestBehavior.AllowGet);
jsonResult.MaxJsonLength = int.MaxValue;
return jsonResult;
}
If you have access to the MIME type of the image, you could always render them as Base64-encoded images, instead of making another request to a different controller method. This is a view model I use:
public class ImageViewModel
{
public string FileName { get; set; }
public string MIME { get; set; }
public byte[] Data { get; set; }
public override string ToString()
{
return string.Format(#"data:{0};base64,{1}", MIME.ToLower(), Convert.ToBase64String(Data));
}
}
You can use the Filename property in the alt attribute of the <img /> tag, so the markup & model-binding in your view would look something like this (assuming Razor syntax):
<img src="#model.ToString()" alt="#model.FileName" />
You do lose image-caching, AFAIK - that hasn't been an issue for me, but is understandably a deal-breaker for some.
I think that you can solve it in another way, instead of returning multiple images, you can create a utility method that loads the image in the controller
public FileContentResult GetImage(int imageId)
{
var image = GetImageById(imageId); // get from a list for example
return File(image, "image/jpeg"); // your image Mime type
}
and in the View you can do the following, iterate over the images
#foreach (var image in Model)
{
<img alt="" src="#Url.Action("GetImage", "ControllerName", new {imageId =image.Id})"/>
}
You wish to show multiple images based on input of the user, but don't want to save to the hard disk (chat). Therefor I recommand you using a session variable to save the users input in. And the use a simple FileResult to return multiple images based on that session variable.
http://blog.theobjectguy.com/2009/12/session-with-style.html
If your requirement is to show multiple images in one view it would be easier to use 2 action methods. One that returns a image with FileResult and another where you just use standard html img tags pointing the first action method

Redirecting to MVC ActionResult from FileResult

This might be a simple one but here goes:
I'm implementing an excel downloadable report in my MVC3 application. I've used this method in the past and it's worked perfectly, however in this case, there is a chance that sales data may not exist for the report. Here is my code:
I have a FileResult action within a Reports controller:
[HttpPost]
public FileResult ExcelReportDownload(ReportExcelDownloadRequest reportRequest)
{
ReportEngine re = new ReportEngine();
Stream report = re.GetReport(reportRequest);
return new FileStreamResult(report, "application/ms-excel")
{
FileDownloadName = "SalesReport.xls"
};
}
My issue is that sometimes the report stream may be null meaning that there's no sales info available, in which case I would rather redirect to a View that displays a message to say there is no sales information available, however I am not sure how to achieve this.
Is there a way to do this?
Well, FileResult inherits from ActionResult :
If you result can be either a RedirectToRouteResult (inheriting from ActionResult) or a FileResult, then... your action must be of type ActionResult, which can manage both.
something like that :
[HttpPost]
public ActionResult ExcelReportDownload(ReportExcelDownloadRequest reportRequest)
{
ReportEngine re = new ReportEngine();
Stream report = re.GetReport(reportRequest);
if (report == null)
return RedirectToAction(<action Name>);
else
return new FileStreamResult(report, "application/ms-excel")
{
FileDownloadName = "SalesReport.xls"
};
}

Categories