itextsharp c# need a perfect method for page break - c#

I am using itextsharp library.I design an HTML page and convert to PDF .in that case some table are not split perfectly and row also not split correctly
table.keepTogether;
table.splitRows;
table.splitLate
I try this extension but it does not work correctly it mess my CSS and data in PDF. if you have method..answer me:)

finally i got it
public class TableProcessor : Table
{
const string NO_ROW_SPLIT = "no-row-split";
public override IList<IElement> End(IWorkerContext ctx, Tag tag, IList<IElement> currentContent)
{
IList<IElement> result = base.End(ctx, tag, currentContent);
var table = (PdfPTable)result[0];
if (tag.Attributes.ContainsKey(NO_ROW_SPLIT))
{
// if not set, table **may** be forwarded to next page
table.KeepTogether = false;
// next two properties keep <tr> together if possible
table.SplitRows = true;
table.SplitLate = true;
}
return new List<IElement>() { table };
}
}
use this class and
var tagfac = Tags.GetHtmlTagProcessorFactory();
tagfac.AddProcessor(new TableProcessor(), new string[] { HTML.Tag.TABLE });
htmlContext.SetTagFactory(tagfac);
integrate this method with htmlpipeline context
this method every time run when the tag hasno-row-split.if it contain key it will make kept by keep together keyword to make page breaking
html
<table no-row-split style="width:100%">
<tr>
<td>
</td>
</tr>
</table>

Related

Populate table column from code foreach row

I recently started the transition from asp.net webforms to blazor server side.
I am still reading and learning about it, and as a side project in order to learn, i started to rewrite an application i made in webforms.
So, in the webforms, when i had to present very big amounts of data coming from ms sql server, i used asp:repeater to populate the main values, and then with nested update panels i managed to have the basic results almost instantly, and after 1-2 seconds, the other columns were populated.
I try to do something similar with Blazor, but with no luck at all.
My code for the main values is
<tbody>
#if(antists==null)
{
}
else
{
#foreach (var antist in antists)
{
string? trdr = "405";
string? mtrl = antist.Mtrl;
string? trdbusiness = "2001";
string? mtrpcategory = antist.Mtrpcategory;
string? mtrmanfctr = antist.Mtrmanfctr;
string image = "https://zerog01.b-cdn.net/" + antist.Image_guid + ".jpg";
//getfldasync(trdr, mtrl, trdbusiness, mtrpcategory, mtrmanfctr);
<tr>
<td style="text-align: center">
<a data-fancybox href=#image>
<img id="img_eikona_in" runat="server" class="img-fluid" style="max-height:100px;"
src=#image onerror="this.onerror=null; this.src='/webimages/no-image.jpg'" /></a>
</td>
<td>#antist.Code.ToString() <br>
#antist.Name</td>
<td>#fld</td>
</tr>
}
}
</tbody>
My goal is to populate #fld from a stored procedure. If i use code to do this on the fly as the rows are being created, it is very slow. (same problem as webforms)
I tried to use async method, but no luck. The closer i got was to have System.Threading.Tasks.Task`1[System.String] shown in the column of #fld. I didn't keep the exact code, but it was something like
public async Task<string> getfldasync(string trdr, string mtrl, string trdbusiness, string mtrpcategory, string mtrmanfctr )
SqlCommand cmd = new SqlCommand(str, con_digi);
cmd.Parameters.AddWithValue("#trdr", trdr);
cmd.Parameters.AddWithValue("#mtrl", mtrl);
cmd.Parameters.AddWithValue("#trdbusiness", trdbusiness);
cmd.Parameters.AddWithValue("#mtrpcategory", mtrpcategory);
cmd.Parameters.AddWithValue("#mtrmanfctr", mtrmanfctr);
await con_digi.OpenAsync();
var scalar = cmd.ExecuteScalarAsync();
fld = Convert.ToString(scalar);
await con_digi.CloseAsync();
return fld;
>not a sp, but i wanted to make changes in order to test the code.
Is there another method i miss for presenting that kind of data? Or should i keep on trying to make async call?
Edit: I uploaded the code where i used datatable instead of list for the initial databind of the table.

Umbraco 7 load content media picker in custom section

I've created a custom section in Umbraco 7 that references external urls, but have a requirement to extend it to use exactly the same functionality as the media picker from the 'Content' rich text editor. I don't need any other rich text functionality other than to load the media picker overlay from an icon, and select either an internal or external url.
I've tried to distil the umbraco source code, as well as trying various adaptations of online tutorials, but as yet I can't get the media picker to load.
I know that fundamentally I need:
Another angular controller to return the data from the content
'getall' method
An html section that contains the media picker overlay
A reference in the edit.html in my custom section to launch the overlay.
However, as yet I haven't been able to wire it all together, so any help much appreciated.
So, this is how I came up with the solution.....
The first win was that I discovered 2 excellent tutorial blog posts, upon the shoulders of which this solution stands, so much respect to the following code cats:
Tim Geyssons - Nibble postings:
http://www.nibble.be/?p=440
Markus Johansson - Enkelmedia
http://www.enkelmedia.se/blogg/2013/11/22/creating-custom-sections-in-umbraco-7-part-1.aspx
Create a model object to represent a keyphrase, which will be associated to a new, simple, ORM table.
The ToString() method allows a friendly name to be output on the front-end.
[TableName("Keyphrase")]
public class Keyphrase
{
[PrimaryKeyColumn(AutoIncrement = true)]
public int Id { get; set; }
public string Name { get; set; }
public string Phrase { get; set; }
public string Link { get; set; }
public override string ToString()
{
return Name;
}
}
Create an Umbraco 'application' that will register the new custom section by implementing the IApplication interface. I've called mine 'Utilities' and associated it to the utilities icon.
[Application("Utilities", "Utilities", "icon-utilities", 8)]
public class UtilitiesApplication : IApplication { }
The decorator allows us to supply a name, alias, icon and sort-order of the new custom section.
Create an Umbraco tree web controller that will allow us to create the desired menu behaviour for our keyphrases, and display the keyphrase collection from our database keyphrase table.
[PluginController("Utilities")]
[Umbraco.Web.Trees.Tree("Utilities", "KeyphraseTree", "Keyphrase", iconClosed: "icon-doc", sortOrder: 1)]
public class KeyphraseTreeController : TreeController
{
private KeyphraseApiController _keyphraseApiController;
public KeyphraseTreeController()
{
_keyphraseApiController = new KeyphraseApiController();
}
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
{
var nodes = new TreeNodeCollection();
var keyphrases = _keyphraseApiController.GetAll();
if (id == Constants.System.Root.ToInvariantString())
{
foreach (var keyphrase in keyphrases)
{
var node = CreateTreeNode(
keyphrase.Id.ToString(),
"-1",
queryStrings,
keyphrase.ToString(),
"icon-book-alt",
false);
nodes.Add(node);
}
}
return nodes;
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
// root actions
menu.Items.Add<CreateChildEntity, ActionNew>(ui.Text("actions", ActionNew.Instance.Alias));
menu.Items.Add<RefreshNode, ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
return menu;
}
else
{
menu.Items.Add<ActionDelete>(ui.Text("actions", ActionDelete.Instance.Alias));
}
return menu;
}
}
The class decorators and TreeController extension allow us to declare the web controller for our keyphrase tree, associate it to our Utilities custom section, as well as choose an icon and sort order.
We also declare an api controller (we'll get to that!), which will allow us access to our Keyphrase data object.
The GetTreeNodes method allows us to iterate the keyphrase data collection and return the resultant nodes to the view.
The GetMenuNode method allows us to create the menu options we require for our custom section.
We state that if the node is the root (Utilities), then allow us to add child nodes and refresh the node collection.
However, if we are lower in the node tree (Keyphrase) then we only want users to be able to delete the node (ie the user shouldn't be allowed to create another level of nodes deeper than Keyphrase)
Create an api controller for our Keyphrase CRUD requests
public class KeyphraseApiController : UmbracoAuthorizedJsonController
{
public IEnumerable<Keyphrase> GetAll()
{
var query = new Sql().Select("*").From("keyphrase");
return DatabaseContext.Database.Fetch<Keyphrase>(query);
}
public Keyphrase GetById(int id)
{
var query = new Sql().Select("*").From("keyphrase").Where<Keyphrase>(x => x.Id == id);
return DatabaseContext.Database.Fetch<Keyphrase>(query).FirstOrDefault();
}
public Keyphrase PostSave(Keyphrase keyphrase)
{
if (keyphrase.Id > 0)
DatabaseContext.Database.Update(keyphrase);
else
DatabaseContext.Database.Save(keyphrase);
return keyphrase;
}
public int DeleteById(int id)
{
return DatabaseContext.Database.Delete<Keyphrase>(id);
}
}
Create the custom section views with angular controllers, which is the current architectual style in Umbraco 7.
It should be noted that Umbraco expects that your custom section components are put into the following structure App_Plugins//BackOffice/
We need a view to display and edit our keyphrase name, target phrase and url
<form name="keyphraseForm"
ng-controller="Keyphrase.KeyphraseEditController"
ng-show="loaded"
ng-submit="save(keyphrase)"
val-form-manager>
<umb-panel>
<umb-header>
<div class="span7">
<umb-content-name placeholder=""
ng-model="keyphrase.Name" />
</div>
<div class="span5">
<div class="btn-toolbar pull-right umb-btn-toolbar">
<umb-options-menu ng-show="currentNode"
current-node="currentNode"
current-section="{{currentSection}}">
</umb-options-menu>
</div>
</div>
</umb-header>
<div class="umb-panel-body umb-scrollable row-fluid">
<div class="tab-content form-horizontal" style="padding-bottom: 90px">
<div class="umb-pane">
<umb-control-group label="Target keyphrase" description="Keyphrase to be linked'">
<input type="text" class="umb-editor umb-textstring" ng-model="keyphrase.Phrase" required />
</umb-control-group>
<umb-control-group label="Keyphrase link" description="Internal or external url">
<p>{{keyphrase.Link}}</p>
<umb-link-picker ng-model="keyphrase.Link" required/>
</umb-control-group>
<div class="umb-tab-buttons" detect-fold>
<div class="btn-group">
<button type="submit" data-hotkey="ctrl+s" class="btn btn-success">
<localize key="buttons_save">Save</localize>
</button>
</div>
</div>
</div>
</div>
</div>
</umb-panel>
</form>
This utilises umbraco and angular markup to display data input fields dynamically and associate our view to an angular controller that interacts with our data layer
angular.module("umbraco").controller("Keyphrase.KeyphraseEditController",
function ($scope, $routeParams, keyphraseResource, notificationsService, navigationService) {
$scope.loaded = false;
if ($routeParams.id == -1) {
$scope.keyphrase = {};
$scope.loaded = true;
}
else {
//get a keyphrase id -> service
keyphraseResource.getById($routeParams.id).then(function (response) {
$scope.keyphrase = response.data;
$scope.loaded = true;
});
}
$scope.save = function (keyphrase) {
keyphraseResource.save(keyphrase).then(function (response) {
$scope.keyphrase = response.data;
$scope.keyphraseForm.$dirty = false;
navigationService.syncTree({ tree: 'KeyphraseTree', path: [-1, -1], forceReload: true });
notificationsService.success("Success", keyphrase.Name + " has been saved");
});
};
});
Then we need html and corresponding angular controller for the keyphrase delete behaviour
<div class="umb-pane" ng-controller="Keyphrase.KeyphraseDeleteController">
<p>
Are you sure you want to delete {{currentNode.name}} ?
</p>
<div>
<div class="umb-pane btn-toolbar umb-btn-toolbar">
<div class="control-group umb-control-group">
<a href="" class="btn btn-link" ng-click="cancelDelete()"
<localize key="general_cancel">Cancel</localize>
</a>
<a href="" class="btn btn-primary" ng-click="delete(currentNode.id)">
<localize key="general_ok">OK</localize>
</a>
</div>
</div>
</div>
</div>
Utilise Umbraco's linkpicker to allow a user to select an internal or external url.
We need html markup to launch the LinkPicker
<div>
<ul class="unstyled list-icons">
<li>
<i class="icon icon-add blue"></i>
<a href ng-click="openLinkPicker()" prevent-default>Select</a>
</li>
</ul>
</div>
And an associated directive js file that launches the link picker and posts the selected url back to the html view
angular.module("umbraco.directives")
.directive('umbLinkPicker', function (dialogService, entityResource) {
return {
restrict: 'E',
replace: true,
templateUrl: '/App_Plugins/Utilities/umb-link-picker.html',
require: "ngModel",
link: function (scope, element, attr, ctrl) {
ctrl.$render = function () {
var val = parseInt(ctrl.$viewValue);
if (!isNaN(val) && angular.isNumber(val) && val > 0) {
entityResource.getById(val, "Content").then(function (item) {
scope.node = item;
});
}
};
scope.openLinkPicker = function () {
dialogService.linkPicker({ callback: populateLink });
}
scope.removeLink = function () {
scope.node = undefined;
updateModel(0);
}
function populateLink(item) {
scope.node = item;
updateModel(item.url);
}
function updateModel(id) {
ctrl.$setViewValue(id);
}
}
};
});
There is one final js file that allows us to send data across the wire, with everyone's favourite http verbs GET, POST(handles put too here too) and DELETE
angular.module("umbraco.resources")
.factory("keyphraseResource", function ($http) {
return {
getById: function (id) {
return $http.get("BackOffice/Api/KeyphraseApi/GetById?id=" + id);
},
save: function (keyphrase) {
return $http.post("BackOffice/Api/KeyphraseApi/PostSave", angular.toJson(keyphrase));
},
deleteById: function (id) {
return $http.delete("BackOffice/Api/KeyphraseApi/DeleteById?id=" + id);
}
};
});
In addition, we will need a package manifest to register our javascript behaviour
{
javascript: [
'~/App_Plugins/Utilities/BackOffice/KeyphraseTree/edit.controller.js',
'~/App_Plugins/Utilities/BackOffice/KeyphraseTree/delete.controller.js',
'~/App_Plugins/Utilities/keyphrase.resource.js',
'~/App_Plugins/Utilities/umbLinkPicker.directive.js'
]
}
Implement tweaks to allow the CMS portion of the solution to work correctly.
At this point we've almost got our custom section singing, but we just need to jump a couple more Umbraco hoops, namely
a) add a keyphrase event class that creates our keyphrase db table if it doesn't exist (see point 8)
b) fire up Umbraco and associate the new custom section to the target user (from the User menu)
c) alter the placeholder text for the custom section by searching for it in umbraco-->config-->en.xml and swapping out the placeholder text for 'Utilities'
Intercept target content fields of target datatypes when content is saved or published
The requirement I was given was to intercept the body content of a news article, so you'll need to create a document type in Umbraco that has, for example, a title field of type 'Textstring', and bodyContent field of type 'Richtext editor'.
You'll also want a, or many, keyphrase(s) to target, which should now be in a new Umbraco custom section, 'Utilities'
Here I've targeted the keyphrase 'technology news' to link to the bbc technology news site so that any time I write the phrase 'technology news' the href link will be inserted automatically.
This is obviously quite a simple example, but would be quite powerful if a user needed to link to certain repetitive legal documents, for example tax, property, due dilligence, for example, which could be hosted either externally or within the CMS itself. The href link will open an external resource in a new tab, and internal resource in the same window (we'll get to that in Point 9)
So, the principle of what we're trying to achieve is to intercept the Umbraco save event for a document and manipulate our rich text to insert our link. This is done as follows:
a) Establish a method (ContentServiceOnSaving) that will fire when a user clicks 'save', or 'publish and save'.
b) Target our desired content field to find our keyphrases.
c) Parse the target content html against our keyphrase collection to create our internal/external links.
NB: If you just want to get the custom section up and running, you only need the ApplicationStarted method to create the KeyPhrase table.
public class KeyphraseEvents : ApplicationEventHandler
{
private KeyphraseApiController _keyphraseApiController;
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication,
ApplicationContext applicationContext)
{
_keyphraseApiController = new KeyphraseApiController();
ContentService.Saving += ContentServiceOnSaving;
var db = applicationContext.DatabaseContext.Database;
if (!db.TableExist("keyphrase"))
{
db.CreateTable<Keyphrase>(false);
}
}
private void ContentServiceOnSaving(IContentService sender, SaveEventArgs<IContent> saveEventArgs)
{
var keyphrases = _keyphraseApiController.GetAll();
var keyphraseContentParser = new KeyphraseContentParser();
foreach (IContent content in saveEventArgs.SavedEntities)
{
if (content.ContentType.Alias.Equals("NewsArticle"))
{
var blogContent = content.GetValue<string>("bodyContent");
var parsedBodyText = keyphraseContentParser.ReplaceKeyphrasesWithLinks(blogContent, keyphrases);
content.SetValue("bodyContent", parsedBodyText);
}
}
}
}
The ContentServiceOnSaving method allows us to intercept any save event in Umbraco. Afterwhich we check our incoming content to see if it's of the type we're expecting - in this example 'NewsArticle' - and if it is, then target the 'bodyContent' section, parse this with our 'KeyphraseContentParser', and swap the current 'bodyContent' with the parsed 'bodyContent'.
Create a Keyphrase parser to swap keyphrases for internal/external links
public class KeyphraseContentParser
{
public string ReplaceKeyphrasesWithLinks(string htmlContent, IEnumerable<Keyphrase> keyphrases)
{
var parsedHtmlStringBuilder = new StringBuilder(htmlContent);
foreach (var keyphrase in keyphrases)
{
if (htmlContent.CaseContains(keyphrase.Phrase, StringComparison.OrdinalIgnoreCase))
{
var index = 0;
do
{
index = parsedHtmlStringBuilder.ToString()
.IndexOf(keyphrase.Phrase, index, StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
var keyphraseSuffix = parsedHtmlStringBuilder.ToString(index, keyphrase.Phrase.Length + 4);
var keyPhraseFromContent = parsedHtmlStringBuilder.ToString(index, keyphrase.Phrase.Length);
var keyphraseTarget = "_blank";
if (keyphrase.Link.StartsWith("/"))
{
keyphraseTarget = "_self";
}
var keyphraseLinkReplacement = String.Format("<a href='{0}' target='{1}'>{2}</a>",
keyphrase.Link, keyphraseTarget, keyPhraseFromContent);
if (!keyphraseSuffix.Equals(String.Format("{0}</a>", keyPhraseFromContent)))
{
parsedHtmlStringBuilder.Remove(index, keyPhraseFromContent.Length);
parsedHtmlStringBuilder.Insert(index, keyphraseLinkReplacement);
index += keyphraseLinkReplacement.Length;
}
else
{
var previousStartBracket = parsedHtmlStringBuilder.ToString().LastIndexOf("<a", index);
var nextEndBracket = parsedHtmlStringBuilder.ToString().IndexOf("a>", index);
parsedHtmlStringBuilder.Remove(previousStartBracket, (nextEndBracket - (previousStartBracket - 2)));
parsedHtmlStringBuilder.Insert(previousStartBracket, keyphraseLinkReplacement);
index = previousStartBracket + keyphraseLinkReplacement.Length;
}
}
} while (index != -1);
}
}
return parsedHtmlStringBuilder.ToString();
}
}
It's probably easiest to step through the above code, but fundamentally the parser has to:
a) find and wrap all keyphrases, ignoring case, with a link to an internal CMS, or external web resource.
b) handle an already parsed html string to both leave links in place and not create nested links.
c) allow CMS keyphrase changes to be updated in the parsed html string.
The blog of this, as well as the github code can be found from the links in the previous post.
Ok, so after finding some excellent helper posts and digging around I came up with the solution, which I've written about here:
http://frazzledcircuits.blogspot.co.uk/2015/03/umbraco-7-automatic-keyphrase.html
And the source code is here:
https://github.com/AdTarling/UmbracoSandbox

Rendering a control to HTML without __VIEWSTATE

I'm using a web service to get and render a GridView to HTML. The idea is to generate it asynchronously (to generate the data in a hidden row for an expandable grid, for example).
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public static string ExpandRowService(string contextKey)
{
try
{
long recId = long.Parse(contextKey);
return RenderControlToHtml("/Controls/SubGrid.ascx", recId);
}
catch (Exception)
{
return "";
}
}
public static string RenderControlToHtml(string controlPath, params object[] constructorParams)
{
var page = new Page
{
EnableEventValidation = false,
EnableViewState = false,
};
var form = new HtmlForm
{
EnableViewState = false
};
page.Controls.Add(form);
var control = page.LoadControl(controlPath, constructorParams);
form.Controls.Add(control);
return page.RenderControl();
}
The problem is that rendering to HTML like that needs a dummy form to render into, and that dummy form has a __VIEWSTATE hidden input:
<form method="post" action="ExpandRowService" id="ctl00">
<div class="aspNetHidden">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="" />
</div>
On postback after any other action, there are more than one __VIEWSTATE variables and that's obviously a no-no.
How can I force rendering of a control to HTML without the accursed __VIEWSTATE hiden input?
EDIT:
I'm using a GridView and not making a table from scratch because I need its data binding capabilities to set various styles (for example: mark negative amounts in red).
You can render the control itself, without the surrounding form. All you need is to setup a HtmlTextWriter instance backed by a reasonable storage, such as a StringBuilder:
// create control and make it part of a form and page
…
// render just the control
var c = new StringBuilder();
using (var sw = new StringWriter(c))
{
using (var writer = new HtmlTextWriter(sw))
{
control.RenderControl(writer);
}
}
return c.ToString();
It appears that it is not possible to generate the control with LoadControl into a form (required) and not have a __VIEWSTATE.
I made my own TableWriter, and did some rowbinding to it. Best I could do.

HTML is being rendered as literal string using RazorEngine. How can I prevent this?

I'm trying to generate a HTML document with RazorEngine (http://razorengine.codeplex.com/). Everything is mostly working, but the problem I have now is some of the HTML is being rendered correctly, and HTML that I have nested in that is being rendered as literal HTML, so rather than the browser displaying the tables & divs as expected, it's displaying e.g.
"<table></table><div></div>"
I kick off this process by calling the following:
string completeHTML = RazorEngine.Razor.Parse("InstallationTemplate.cshtml", new { Data = viewModels });
The completeHTML is then written to a file.
"InstallationTemplate.cshtml" is defined as:
#{
var installationReport = new InstallationReport(Model.Data);
}
<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
<!-- I would expect this to write the rendered HTML
in place of "#installationReport.DigiChannels()" -->
#installationReport.DigiChannels()
</div>
</body>
</html>
Where InstallationReport and DigiChannels are defined as follows:
public static class InstallationReportExtensions
{
public static string DigiChannels(this InstallationReport installationReport)
{
return installationReport.GetDigiChannelsHtml();
}
}
public class InstallationReport
{
public string GetDigiChannelsHtml()
{
// the following renders the template correctly
string renderedHtml = RazorReport.GetHtml("DigiChannels.cshtml", GetDigiChannelData());
return renderedHtml;
}
}
public static string GetHtml(string templateName, object data)
{
var templateString = GetTemplateString(templateName);
return RazorEngine.Razor.Parse(templateString, data);
}
After GetDigiChannelsHtml() runs and returns renderedHtml, the line of execution returns to TemplateBase.cs into the method ITemplate.Run(ExecuteContext context), which is defined as:
string ITemplate.Run(ExecuteContext context)
{
_context = context;
var builder = new StringBuilder();
using (var writer = new StringWriter(builder))
{
_context.CurrentWriter = writer;
Execute(); // this is where my stuff gets called
_context.CurrentWriter = null;
}
if (Layout != null)
{
// Get the layout template.
var layout = ResolveLayout(Layout);
// Push the current body instance onto the stack for later execution.
var body = new TemplateWriter(tw => tw.Write(builder.ToString()));
context.PushBody(body);
return layout.Run(context);
}
return builder.ToString();
}
When I inspect builder.ToString(), I can see it contains proper HTML for the InstallationTemplate.cshtml stuff, and escaped HTML for the DigiChannels.cshtml stuff. For example:
How can I get #installationReport.DigiChannels() to include the correct HTML instead of the escaped HTML that it's currently doing?
Have you tried:
#Raw(installationReport.DigiChannels())
Edit : I could use it in following way (MVC3)
#Html.Raw(installationReport.DigiChannels())
The alternative to #Raw is to change your API to return HtmlStrings in appropriate places
Represents an HTML-encoded string that should not be encoded again.
The default razor behaviour is to encode strings.

C# - Creating HTML reports

I have in my winform application 4 datasets that gets values from my database. Values like like how many products I have in my product table and information about categories. I was wondering how I can save the dataset informaiton in a html page. I want to create a html template so that I can present the information in nice way. How can I do that? Eny good guide that explains how to do that?
This seems like a great job for something like RazorEngine. You can define a template using Razor syntax, and then render out the content using the RazorEngine template service, e.g.:
#helper RenderItem(Item item) {
<tr>
<td>item.Name</td>
<td>item.Price</td>
</tr>
}
<html>
<head></head>
<body>
<table>
#foreach (Item item in Model.Items) {
#RenderItem(item)
}
</table>
</body>
</html>
You can save DataSet as XML and then transform it by using XSLT.
You can take a look at this samples:
XSLT - Transformation
Creating and Populating an HTML Template
Using XSLT and .NET to Manipulate XML Files
I personally recommend generating HTML with Linq to Xml (using System.Xml.Linq)
You don't even have to use the strict XHTML schema's, but you'll get heap s of mileage out of Xml.Linq. Here is a snippet from my own code bases:
#region Table Dump Implementation
private static XNode Dump<T>(IEnumerable<T> items, IEnumerable<string> header, params Func<T, string>[] columns)
{
if (!items.Any())
return null;
var html = items.Aggregate(new XElement("table", new XAttribute("border", 1)),
(table, item) => {
table.Add(columns.Aggregate(new XElement("tr"),
(row, cell) => {
row.Add(new XElement("td", EvalColumn(cell, item)));
return row;
} ));
return table;
});
html.AddFirst(header.Aggregate(new XElement("tr"),
(row, caption) => { row.Add(new XElement("th", caption)); return row; }));
return html;
}
private static XNode EvalColumn<T>(Func<T, string> cell, T item)
{
var raw = cell(item);
try
{
var xml = XElement.Parse(raw);
return xml;
}
catch (XmlException)
{
return new XText(raw);
}
}
#endregion
#region Dot Diagrams
public void LinkDiagram(Digraph graph, string id)
{
if (!graph.AllNodes.Any())
return;
var img = Path.GetFileName(GenDiagramFile(graph, _directory, id));
_body.Add(
new XElement("a",
new XAttribute("href", img),
new XElement("h4", "Link naar: " + graph.name),
new XElement("img",
new XAttribute("border", 1),
new XAttribute("src", img),
new XAttribute("width", "40%"))));
}
Note that is it exceedingly easy to use inline HTML text as well (as long as it is valid XML), using a helper like so:
public void GenericAppend(string content)
{
if (!string.IsNullOrEmpty(content))
_body.Add(XElement.Parse(content));
}
What you want is pretty easy so you may just want to generate html directly and use some pre-created CSS for the styling. However if you want something more sophisticated, please take a look at Windward Reports.

Categories