Microsoft Teams Notificaitons CSS Styling - c#

Is there a way to style Teams Notifications?
Posting to a WebHook the following works:
string _html = #"<table width='100%'>
<tr>
<th>Service</th>
<th>2 hours</th>
<th>4 hours</th>
<th>8 hours</th>
<th>24 hours</th>
<th>48 hours</th>
</tr>
<tr style='background-color: #1a1a1a;'>
<td>2343</td>
<td>132</td>
<td>43</td>
<td>12</td>
<td>132</td>
<td>532</td>
</tr>
</table>";
var values = new Dictionary<string, string>{
{"themeColor", "ffd11a"},
{ "title", "My Title" },
{ "textFormat", "markdown" },
{ "text", _html }
};
var json = JsonSerializer.Serialize(values);
var stringContent = new StringContent(json);
var response = await client.PostAsync(webhookUrl, stringContent);
however when I try to add <style> tag it no longer works, and it seems that only 'inline' styles work. Is there a way to add CSS to Teams Notifications Webook?

Related

How to find last column of a table using Html Agility Pack

I have a table like this:
<table border="0" cellpadding="0" cellspacing="0" id="table2">
<tr>
<th>Name
</th>
<th>Age
</th>
</tr>
<tr>
<td>Mario
</td>
<th>Age: 78
</td>
</tr>
<tr>
<td>Jane
</td>
<td>Age: 67
</td>
</tr>
<tr>
<td>James
</td>
<th>Age: 92
</td>
</tr>
</table>
I want to get the last td from all rows using Html Agility Pack.
Here is my C# code so far:
await page.GoToAsync(NumOfSaleItems, new NavigationOptions
{
WaitUntil = new WaitUntilNavigation[] { WaitUntilNavigation.DOMContentLoaded }
});
var html4 = page.GetContentAsync().GetAwaiter().GetResult();
var htmlDoc4 = new HtmlDocument();
htmlDoc4.LoadHtml(html4);
var SelectTable = htmlDoc4.DocumentNode.SelectNodes("/html/body/div[2]/div/div/div/table[2]/tbody/tr/td[1]/div[3]/div[2]/div/table[2]/tbody/tr/td[4]");
if (SelectTable.Count == 0)
{
continue;
}
else
{
foreach (HtmlNode row in SelectTable)//
{
string value = row.InnerText;
value = value.ToString();
var firstSpaceIndex = value.IndexOf(" ");
var firstString = value.Substring(0, firstSpaceIndex);
LastSellingDates.Add(firstString);
}
}
How can I get only the last column of the table?
I think the XPath you want is: //table[#id='table2']//tr/td[last()].
//table[#id='table2'] finds the table by ID anywhere in the document. This is preferable to a long brittle path from the root, since a table ID is less likely to change than the rest of the HTML structure.
//tr gets the descendent rows in the table. I'm using two slashes in case there might be an intervening <tbody> element in the actual HTML.
/td[last()] gets the last <td> in each row.
From there you just need to select the InnerText of each <td>.
var tds = htmlDoc.DocumentNode.SelectNodes("//table[#id='table2']//tr/td[last()]");
var values = tds?.Select(td => td.InnerText).ToList() ?? new List<string>();
Working demo here: https://dotnetfiddle.net/7I8yk1

Calling ajax request from asp.net razor view

How do I initiate an ajax request (calling controller action) from razor view which returns the data in JSON format?
At the moment after clicking the action link in my razor view the page does the post request which redirects the page to /actionName which of course does not exist.
I am also using jQuery but not sure how do I fetch the data from razor view which needs to be passed if I use jQuery ajax method.
ShowEventLogs.cshtml
#{ ViewBag.Title = "Event Logs"; }
#model IEnumerable
<Application.Models.EventLogs>
<table id="data-table" class="table display responsive" style="width:100%">
<thead class="thead-colored thead-light">
<tr>
<th>Time</th>
<th>Scheme</th>
<th>Serial Number</th>
<th>Batch</th>
<th>Exp Date</th>
<th>Product Code</th>
<th>Http Code</th>
<th>Is Confirmed?</th>
<th>Confirmation Date</th>
<th>Verify Pack</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>#item.Timestamp</td>
<td>#item.ProductCodeScheme</td>
<td>#item.SerialNumber</td>
<td>#item.Batch</td>
<td>#item.ExpirationDate</td>
<td>#item.ProductCode</td>
<td>#item.HttpResponseCode</td>
<td>#item.ConfirmedParsed</td>
<td>#item.ConfirmedDate</td>
if (#item.HttpResponseCode == "202")
{
<td class="text-secondary">#Html.ActionLink("Verify Pack", "VerifyPack", "Home", new { ProductCodeScheme = #item.ProductCodeScheme, ProductCode = #item.ProductCode, SerialNumber = #item.SerialNumber, Batch = #item.Batch, ExpirationDate = #item.ExpirationDate, CommandStatusCode = 0 }, new { #class = "text-info" })</td>
}
else
{
<td class="text-secondary">Not Available</td>
}
</tr>
}
</tbody>
</table>
}
Controller action
[HttpPost]
public ActionResult VerifyPack(string productCodeScheme, string productCode, string serialNumber, string batch, string expirationDate, int commandStatusCode, string orderTrackingNo = null) {
string TextAreaResult = string.Empty;
string TextAreaResultException = string.Empty;
string TextAreaResultHttpOperationCode = string.Empty;
string TextAreaResultHttpResponseCode = string.Empty;
string TextAreaResultHttpInformation = string.Empty;
string TextAreaResultHttpWarning = string.Empty;
string TextAreaResultState = string.Empty;
string RemoteIpAddress = string.Format("{0}", Request.UserHostAddress);
try {
using(SecureMediDatabase database = new SecureMediDatabase(this)) {
DatabaseFactory.setDatabase(database);
Request baseRequest = (Request) database.createRequest(Country);
ServiceThread serviceThread = new ServiceThread(0, null, Country);
serviceThread.attach(this);
baseRequest.setId(0);
baseRequest.setProductCodeScheme(productCodeScheme);
baseRequest.setRequestType(1); //single pack
baseRequest.setProductCode(productCode);
baseRequest.setSerialNumber(serialNumber);
baseRequest.setBatch(batch);
baseRequest.setExpirationDate(expirationDate);
baseRequest.setWorkstation(RemoteIpAddress);
baseRequest.setManualEntry(string.IsNullOrEmpty(expirationDate) || string.IsNullOrEmpty(batch));
if (baseRequest.isManualEntry()) {
switch (commandStatusCode) {
case 2:
case 3:
break;
default:
throw new NotSupportedException("This operation does not support manual entries!");
}
}
switch (Country) {
case "SE":
SecureMediRequestSE requestSE = (SecureMediRequestSE) baseRequest;
requestSE.setUserId(#User.Identity.Name);
requestSE.setCommandStatusCode(commandStatusCode);
requestSE.OrderTrackingNumber = orderTrackingNo;
break;
case "FI":
SecureMediRequestFI requestFI = (SecureMediRequestFI) baseRequest;
requestFI.setSubUserId(#User.Identity.Name);
break;
}
serviceThread.RunRequest(control, baseRequest, apteekki);
TextAreaResult = string.Format("{0} {1} {2} {3} {4}", baseRequest.getResponseOperationCode(), baseRequest.getHttpResponseCode(), baseRequest.getHttpInformation(), baseRequest.getHttpWarning(), baseRequest.getResponseStatusCode());
TextAreaResultHttpOperationCode = string.Format("{0}", baseRequest.getResponseOperationCode());
TextAreaResultHttpResponseCode = string.Format("{0}", baseRequest.getHttpResponseCode());
TextAreaResultHttpInformation = string.Format("{0}", baseRequest.getHttpInformation());
TextAreaResultHttpWarning = string.Format("{0}", baseRequest.getHttpWarning());
TextAreaResultState = string.Format("{0}", baseRequest.getResponseStatusCode());
}
} catch (Exception exc) {
TextAreaResultException = "Exception: " + exc.Message;
}
return Json(new {
result = TextAreaResult,
httpOperationCode = TextAreaResultHttpOperationCode,
httpResponseCode = TextAreaResultHttpResponseCode,
httpInformation = TextAreaResultHttpInformation,
httpWarning = TextAreaResultHttpWarning,
state = TextAreaResultState,
exception = TextAreaResultException,
isSuccess = TextAreaResultHttpResponseCode == "200" || TextAreaResultHttpResponseCode == "202"
});
}
Error based on the answer:
Basically #Html.ActionLink() helper renders anchor tag (<a>) with attributes and defaulted to use GET request by refreshing whole page, hence you need to add preventDefault() in order to use AJAX callback from that element. If the action method uses HTTP GET method, you can perform simple AJAX call against common class of the anchor link like this:
$('.text-info').on('click', function (e) {
e.preventDefault();
var url = $(this).attr('href');
$.get(url, function (response) {
// do something with AJAX response
});
});
However since target controller action marked as [HttpPost], you need to extract query string parameters from href attribute with additional function and use them in the AJAX call with type: 'POST' setting, or use $.post():
$('.text-info').on('click', function (e) {
e.preventDefault(); // mandatory to prevent GET request
var url = $(this).attr('href');
var pcs = getQueryStringParams(url, 'ProductCodeScheme');
var pc = getQueryStringParams(url, 'ProductCode');
var sn = getQueryStringParams(url, 'SerialNumber');
var batch = getQueryStringParams(url, 'Batch');
var expDate = getQueryStringParams(url, 'ExpirationDate');
var csc = getQueryStringParams(url, 'CommandStatusCode');
// create key-value pair for action method parameters
var obj = { ProductCodeScheme: pcs, ProductCode: pc, SerialNumber: sn, ... }
$.ajax({
type: 'POST',
url: url.split('?')[0], // URL without query string, or use '#Url.Action("VerifyPack", "Home")'
data: obj,
dataType: 'json', // expects response as JSON
success: function (response) {
// do something with AJAX response
},
error: function (xhr, status, err) {
// error handling
}
});
// just make sure that the link is not redirecting
return false;
});
function getQueryStringParams(url, name) {
return (RegExp(name + '=' + '(.+?)(&|$)').exec(url)||[,null])[1];
}
Actually there exists another way to call AJAX from anchor tag like #Ajax.ActionLink(), depending on your choice:
#Ajax.ActionLink("Verify Pack", "VerifyPack", "Home", new { ProductCodeScheme = #item.ProductCodeScheme, ProductCode = #item.ProductCode, SerialNumber = #item.SerialNumber, Batch = #item.Batch, ExpirationDate = #item.ExpirationDate, CommandStatusCode = 0 },
new AjaxOptions { HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "targetElementId",
OnComplete = "onComplete();"
},
new { #class = "text-info" })
Note:
If you need to handle AJAX request and normal request from the same controller, you can differentiate them using Request.IsAjaxRequest() (or Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest" in Core MVC).
Something like this should get you started. Add a class to the items you need to pull information from. Then instead of using an actionlink just create a normal a element with a unique class as well. Have JQuery handle click events on those links and pass the other TD items of the same row to the controller via an AJAX call.
$(".button").click( function() {
var tr = $(this).closest("tr");
var ProductCodeScheme = tr.find(".ProductCodeScheme").html();
var SerialNumber = tr.find(".SerialNumber").html();
var Batch = tr.find(".Batch").html();
var ExpirationDate = tr.find(".ExpirationDate").html();
var ProductCode = tr.find(".ProductCode").html();
$.ajax({
url: "/Verify Pack/VerifyPack",
type: "POST",
data: ({
ProductCodeScheme: ProductCodeScheme,
SerialNumber: SerialNumber,
Batch: Batch,
ExpirationDate: ExpirationDate,
ProductCode: ProductCode
}),
cache: false,
success: function(data){
//Do something here for a successful POST
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="data-table" class="table display responsive" style="width:100%">
<thead class="thead-colored thead-light">
<tr>
<th>Time</th>
<th>Scheme</th>
<th>Serial Number</th>
<th>Batch</th>
<th>Exp Date</th>
<th>Product Code</th>
<th>Http Code</th>
<th>Is Confirmed?</th>
<th>Confirmation Date</th>
<th>Verify Pack</th>
</tr>
</thead>
<tbody>
<tr>
<td>Timestamp 1</td>
<td class="ProductCodeScheme">ProductCodeScheme 1</td>
<td class="SerialNumber">SerialNumber 1</td>
<td class="Batch">Batch 1</td>
<td class="ExpirationDate">ExpirationDate 1</td>
<td class="ProductCode">ProductCode 1</td>
<td>HttpResponseCode 1</td>
<td>ConfirmedParsed 1</td>
<td>ConfirmedDate 1</td>
<td class="text-secondary">Item 1</td>
</tr>
<tr>
<td>Timestamp 2</td>
<td class="ProductCodeScheme">ProductCodeScheme 2</td>
<td class="SerialNumber">SerialNumber 2</td>
<td class="Batch">Batch 2</td>
<td class="ExpirationDate">ExpirationDate2</td>
<td class="ProductCode">ProductCode 2</td>
<td>HttpResponseCode 2</td>
<td>ConfirmedParsed 2</td>
<td>ConfirmedDate 2</td>
<td class="text-secondary">Item 2</td>
</tr>
</tbody>
</table>

How can I get rid of an error that says "ByteArray Objects Cannot be Converted to Strings" in my ASP.NET MVC 5 application?

I am using the following code to POST XML data to a remote server.
public async Task<ActionResult> Index(Transmission t)
{
ViewBag.ErrorMessage = "";
ViewBag.OtherMessage = "";
ViewBag.ResponseHtml = "";
try
{
var xmlSerializer = new XmlSerializer(typeof(Transmission));
XmlWriterSettings xws = new XmlWriterSettings();
XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", "");
xws.OmitXmlDeclaration = true; //omit the xml declaration line
using (StringWriter sw = new StringWriter())
{
XmlWriter writer = XmlWriter.Create(sw, xws);
xmlSerializer.Serialize(writer, t, ns);
var contentData = sw.ToString();
var httpContent = new StringContent(contentData, Encoding.UTF8, "application/xml");
var httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(0, 1, 0);
var response = await httpClient.PostAsync("theUrl", httpContent);
ViewBag.ResponseHtml = await response.Content.ReadAsStringAsync();
return View("Error"); //TODO: change this to success when I get the 500 error fixed
}
}
catch (Exception ex)
{
string message = ex.Message;
ViewBag.ErrorMessage = ex.Message;
return View("Error");
}
}
The response that I am getting is saying "ByteArray objects cannot be converted to strings." I haven't been given many details about the system to which I am posting this data, only that it using Adobe ColdFusion (which I know next to nothing about). Is there something that I can do on my side to fix this problem?
Edit
The response HTML I am getting is this:
<head>
<title>Error Occurred While Processing Request</title>
<script language="JavaScript">
function showHide(targetName) {
if( document.getElementById ) {
// NS6+
target = document.getElementById(targetName);
} else if( document.all ) {
// IE4+
target = document.all[targetName];
}
if( target ) {
if( target.style.display == "none" ) {
target.style.display = "inline";
} else {
target.style.display = "none";
}
}
}
</script>
</head>
<body>
<font style="COLOR: black; FONT: 16pt/18pt verdana">
The web site you are accessing has experienced an unexpected error.
<br>
Please contact the website administrator.
</font>
<br><br>
<table border="1" cellpadding="3" bordercolor="#000808" bgcolor="#e7e7e7">
<tr>
<td bgcolor="#000066">
<font style="COLOR: white; FONT: 11pt/13pt verdana" color="white">
The following information is meant for the website developer
for debugging purposes.
</font>
</td>
<tr>
<tr>
<td bgcolor="#4646EE">
<font style="COLOR: white; FONT: 11pt/13pt verdana" color="white">
Error Occurred While Processing Request
</font>
</td>
</tr>
<tr>
<td>
<font style="COLOR: black; FONT: 8pt/11pt verdana">
<table width="500" cellpadding="0" cellspacing="0" border="0">
<tr>
<td id="tableProps2" align="left" valign="middle" width="500">
<h1 id="textSection1" style="COLOR: black; FONT: 13pt/15pt verdana">
ByteArray objects cannot be converted to strings.
</h1>
</td>
</tr>
.... A BUNCH OF OTHER CF DEBUG TEXT ....

Unexpected token in JSON

I have checked JSON string response of at
https://jsonformatter.curiousconcept.com/ and it says that JSON string is valid.
Following is the function which I have used to serialize data to JSON string:
private string getJSONData()
{
obj_userSession = new UserSession();
DataTable dtRender = null;
DataView dvRender = null;
obj_userSession = new UserSession();
if (obj_userSession.LoginData != null && obj_userSession.EmailsDetails != null)
{
dvRender = new DataView(obj_userSession.EmailsDetails);
dtRender = dvRender.ToTable("EmailsDetails", false, "MessageDate", "SentFrom", "MessageBody", "SentTo", "MLSNumber");
return JsonConvert.SerializeObject(dtRender);
}
return "";
}
And here is the response from above function as JSON string:
[
{
"MessageDate": "2016-04-04T05:42:38.273",
"SentFrom": "Site Team",
"MessageBody": "<html>\r\n<head>\r\n\t<style type=\"text/css\">\r\n\t\t.c0 { font-family:'Arial';font-size:10.5pt; }\r\n\t\t.c1 { margin-left:0pt;margin-top:0pt;margin-right:0pt;margin-bottom:7.5pt; }\r\n\t</style>\r\n</head>\r\n<body class=\"c0\">\r\n<p class=\"c1\">Hi Joe, </p>\r\n<p class=\"c1\">Testing Site</p>\r\n<p class=\"c1\">--James</p>\r\n<p class=\"c1\"></p>\r\n</body>\r\n</html>\r\n",
"SentTo": "James",
"Number": ""
}
]
We does not getting any error in code but result not displayed in browser. And gets above mention error in developer tools in browser.
If I removed MessageBody from function getJSONData to avoid it from serializing and remove binding code from design page for MessageBody then it works fine.
What character I have to escape from MessageBody and how to do it?
Edit
This is AngularJS controller function which I have used to get data from:
$scope.browseListing = function (strURL) {
$scope.getURL(strURL);
$http.post($scope.URL)
.then(function (response) {
$scope.Data = response.data;
if ($scope.IsMap)
$scope.LoadMapData();
if ($scope.IsDetails)
$scope.buildReportURL($scope.Data[0].ListingID);
}, function (response) {
$log.info(response);
});
};
And this is the html binding:
<tr ng-repeat="listing in Data">
<td colspan="6">
<table class="tblListingOuter">
<tr>
<th style="width:20%;">
</th>
<th style="width:80%;">
</th>
</tr>
<tr>
<td><b>Date: </b>{{ listing.MessageDate }}</td>
<td><b>Message: </b></td>
</tr>
<tr>
<td><b>Sent By: </b>{{ listing.SentFrom }}</td>
<td rowspan="3">
<pre contenteditable="true" ng-bind-html="listing.MessageBody | unsafe"></pre>
</td>
</tr>
<tr>
<td><b>Sent To: </b>{{ listing.SentTo }}</td>
</tr>
<tr>
<td><b>MLSNO: </b>{{ listing.MLSNumber }}</td>
</tr>
</table>
</td>
</tr>
In Newtonsoft JsonSerializerSettings you have property StringEscapeHandling which specifies how strings are escaped when writing json. code below may work for you
var settings = new JsonSerializerSettings();
settings.StringEscapeHandling = StringEscapeHandling.EscapeHtml;
return JsonConvert.SerializeObject(dtRender, settings)
acceptable values for this property listed in documentation here: http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_StringEscapeHandling.htm
you may try other flags to get it working
This is just a guess, but you may try double-escaping the escaped characters in the MessageBody, like this:
[
{
"MessageDate": "2016-04-04T05:42:38.273",
"SentFrom": "Site Team",
"MessageBody": "<html>\\r\\n<head>\\r\\n\\t<style type=\\"text/css\\">\\r\\n\\t\\t.c0 { font-family:'Arial';font-size:10.5pt; }\\r\\n\\t\\t.c1 { margin-left:0pt;margin-top:0pt;margin-right:0pt;margin-bottom:7.5pt; }\\r\\n\\t</style>\\r\\n</head>\\r\\n<body class=\\"c0\\">\\r\\n<p class=\\"c1\"\>Hi Joe, </p>\\r\\n<p class=\\"c1\\">Testing Site</p>\\r\\n<p class=\\"c1\\">--James</p>\\r\\n<p class=\\"c1\\"></p>\\r\\n</body>\\r\\n</html>\\r\\n",
"SentTo": "James",
"Number": ""
}
]

Unable to Export HTML Markup to PDF using iTextSharp API in asp.net using C#?

I have an HTML markup which has HTML Table and Images in it. I am using iTextSharp API to convert the HTML markup to PDF. But, unfortunately the iTextSharp fails to export the HTML markup containing the Images & Table to PDF.
Error:The network path was not found.
Result must be:
Code:
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.html;
using System.Data.SqlClient;
using System.Text;
using iTextSharp.text.html.simpleparser;
public string strSelectUserListBuilder = #" <div style='width: 50%; border: 1 solid #000000; padding-bottom: 100; padding-left: 100;
padding-right: 100; text-align: justify; text-justify: inter-word;'>
<br />
<table>
<tr>
<td>
<div id='divLeft'>
<p>
<img src='/images/log_out.png' width='200' height='100' /></p>
<h2>
Packing slip</h2>
<h3>
Place this slip inside the box with your device.</h3>
<div id='divDeviceList' style='width: 600; text-align: left;' border='0' cellpadding='3'
cellspacing='1' rules='BOTH' frame='BOX'>
<table style='width: 600;'>
<tr>
<td>
<strong> ITEM</strong>
</td>
<td>
<strong> OFFER</strong>
</td>
</tr>
<tr>
<td>
iPhone 5 32GB (AT&T)
</td>
<td>
$205.00
</td>
</tr>
<tr>
<td align='right'>
<hr />
<strong><h3>Total Offer: </h3></strong>
</td>
<td>
<hr />
<strong> <h3> $215.00</h3></strong>
</td>
</tr>
</table>
</div>
<h3>
You have until 01/29/2014 to ship your device.</h3>
<p style='padding:10;'>
<i>If you send your device after the expiration date we cannot honor your initial offer.
We will not accept devices that have been reported lost or stolen.</i></p>
<br />
</div>
</td>
<td>
<div id='divRight'>
<div style='text-align:right;padding:15;'> <img src='/images/google-login.png' alt='barcode' /></div>
<table cellpadding='3' style='border: 1 solid orange;padding:20;'>
</tr>
<tr align='center'>
<td>
<img src='/images/google-login.png' />
</td>
</tr>
<tr>
<td>
<h3>
'Find my iPhone' must be turned off</h3>
</td>
</tr>
<tr>
<td>
This feature locks your device and will delay or reduce payment.
</td>
</tr>
<tr>
<td>
<strong>How to deactivate:</strong></td>
</tr>
<tr>
<td>
1. Tap the “settings” icon on your homescreen.</td>
</tr>
<tr>
<td>
2. Tap iCloud from the settings menu. </td>
</tr>
<tr>
<td>
3. If 'Find My iPhone' is on, tap the slider to turn it off.</td>
</tr>
</table>
</div>";
protected void HTMLToPDF()
{
String htmlText = strSelectUserListBuilder.ToString();
Document document = new Document();
PdfWriter.GetInstance(document, new FileStream(Request.PhysicalApplicationPath + "MySamplePDF.pdf", FileMode.Create));
document.Open();
iTextSharp.text.html.simpleparser.HTMLWorker hw =
new iTextSharp.text.html.simpleparser.HTMLWorker(document);
hw.Parse(new StringReader(htmlText));
document.Close();
}
protected void Page_Load(object sender, EventArgs e)
{
HTMLToPDF();
}
I know this error is due the Image Path. But, not able to resolve.
Any Solution?
Help Appreciated!
Take a look at: creating pdf with itextsharp with images from database
You need to either provide the full physical path in the src-attribute of every img-elements (e.g. <img src="c:\myproject\images\image.jpg"/>) or there is a possibility to tell iTextSharp the physical base folder for all your images.
I was able to render my html correctly by useiung special providers
var baseUrl = string.Format("{0}://{1}", Request.Url.Scheme, Request.Url.Authority);
var html = ...;
Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.ContentEncoding = Encoding.UTF8;
Response.AddHeader("content-disposition", string.Format("attachment; filename={0}.pdf", fileName));
Response.ContentType = "application/pdf";
using (var ms = new MemoryStream())
{
using (var doc = new Document())
{
using (var writer = PdfWriter.GetInstance(doc, ms))
{
doc.Open();
var rootProvider = new CustomRootProvider(baseUrl, Request.PhysicalApplicationPath); //Server.MapPath(Request.ApplicationPath)
FontFactory.RegisterDirectories();
var htmlContext = new HtmlPipelineContext(null);
htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
htmlContext.SetImageProvider(rootProvider);
htmlContext.SetLinkProvider(rootProvider);
htmlContext.CharSet(Encoding.UTF8);
var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
var pipeline = new CssResolverPipeline(cssResolver,
new HtmlPipeline(htmlContext, new PdfWriterPipeline(doc, writer)));
var worker = new XMLWorker(pipeline, true);
var p = new XMLParser(worker);
using(var htmlStream = new MemoryStream(Encoding.UTF8.GetBytes(html)))
{
p.Parse(htmlStream, Encoding.UTF8);
}
writer.CloseStream = false;
}
doc.Close();
}
ms.Position = 0;
Response.BinaryWrite(ms.ToArray());
}
Response.Flush();
Response.End();
...
public class CustomRootProvider : AbstractImageProvider, ILinkProvider
{
private string _baseUrl;
private string _basePath;
public CustomRootProvider(string baseUrl, string basePath)
{
_baseUrl = baseUrl;
}
public override Image Retrieve(string src)
{
var siteUrl = string.IsNullOrEmpty(_baseUrl) ? HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, string.Empty) : _baseUrl;
siteUrl = VirtualPathUtility.RemoveTrailingSlash(siteUrl);
if (Uri.IsWellFormedUriString(src, UriKind.Relative))
{
src = new Uri(new Uri(siteUrl), src).ToString();
}
try
{
return Image.GetInstance(src);
}
catch (Exception)
{
return Image.GetInstance(new Uri(new Uri(siteUrl), "/Content/Images/noimage.png").ToString());
}
}
public override string GetImageRootPath()
{
return string.IsNullOrEmpty(_basePath) ? HttpContext.Current.Request.PhysicalApplicationPath : _basePath;
//HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath);
}
public string GetLinkRoot()
{
return string.IsNullOrEmpty(_baseUrl)
? HttpContext.Current.Request.Url.AbsoluteUri.Replace(HttpContext.Current.Request.Url.PathAndQuery, string.Empty)
: _baseUrl;
}
}
But was having strange problems with html unicode symbols on some hosting machines (ok in german machine but incorrect rendering on russian). I was trying to register differern fonts with iTextSharp fontfactory, to play with different markup (css rules, font-faces etc). Nothing helped me. This was ended with using wkhtmltopdf with wrapper Pechkin (different wrappers available on the internet). wkhtmltopdf works just great out of the box!:
var config = new GlobalConfig().SetMargins(0, 0, 0, 0).SetPaperSize(PaperKind.A4);
var pdfWriter = new SynchronizedPechkin(config);
var bytes = pdfWriter.Convert(new Uri(dataUrl));
Response.BinaryWrite(bytes);
Highly recommend!

Categories