Setting up Habitat without having Visual Studio installed on server

Have you started checking Habitat architecture? If no, this is the best time to get started as Sitecore is evolving faster than it used to and customers are also asking competitive and best enterprise solutions. In this case sometime it becomes difficult to manage your project architecture specially in a big project/team. So, why not look at Habitat? an example provided by Sitecore as a best practice for Sitecore projects based on Helix architecture.

There are many resources available on how to setup Habitat for sitecore correctly like
https://github.com/Sitecore/Habitat (source code)
https://github.com/Sitecore/Habitat/wiki/01-Getting-Started (Geting started guide)
https://www.youtube.com/watch?v=FNqKZN9DH5I (step by step video tutorial on how to setup Habitat for Sitecore)

I ran into a situation where i needed to setup a Habitat on a plain server where i won’t find IIS, Visual Studio etc. installed.

So, lets get on a tour on setting up Habitat without VS installed, Issues which we might face and solutions.

  1. Install plain Sitecore: first step first. Install a plain Sitecore 8.2 Update 1 on which we will setup Habitat. We will use this later
  2. Clone/Download Habitat project: Clone the Habitat repository or download zip from https://github.com/Sitecore/Habitat.
  3. Node.Js: Install latest Node.Js from https://nodejs.org/en/
  4. Perform all the steps specified in https://github.com/Sitecore/Habitat/wiki/01-Getting-Started until step 5. Or you can follow the video tutorial https://www.youtube.com/watch?v=FNqKZN9DH5I
  5. Now, we have arrived at the step to build and publish the solution to the Habitat Sitecore website we installed in the beginning.
  6. Without having Visual Studio installed, we cannot use Task Runner Explorer of VS. So, we have to use command window to run gulp command.

Now, you will start encountering the issues when trying to run gulp commands using command prompt. Some of issues and solutions are mentioned below:

  1. Go to the project directory where you have cloned the habitat project or downloaded and unzipped a project.
  2. Run npm gulp command in that directory on to command window.
  3. Now, if you have not installed all the required modules properly from npm and try to execute gulp task like gulp default, then it will start giving error. In my case it was cannot find module gulp-msbuild. so rather running npm gulp command in project directory, run npm install command so that it will install all the required plugins along with gulp.
  4. I tried to run the default task specified in gulpfile.js using gulp default command. And i got the error saying gulp is not recognized as internal or external command. You required gulp-cli package to be installed to run gulp command from command window. So, if it is missing and you are facing this issue than run npm install -g gulp-cli command. Now once you have installed this package you can run the gulp command as gulp default from the command window.
  5. Running gulp default command again, You may face error saying Nuget package installation failed. As VS is not installed, Nuget will not be installed by default.So, you have to install Nuget command line from https://dist.nuget.org/win-x86-commandline/latest/nuget.exe or https://docs.nuget.org/consume/command-line-reference/.
  6. Running gulp default command again, Nuget package installation will get complete but you may face error saying Build Failed. As VS is not installed on server. So, you will required to install MSBuild tool. You can find it here: https://www.microsoft.com/en-in/download/details.aspx?id=48159
  7. Running gulp default command again, i got error saying MSBuild – microsoft.webapplication.targets was not found issue even though we have installed MSBuild tool on the server. To resolve this, i just copied C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications folder from local to the server and tried run command again.
  8. Encountered error stdout maxbuffer exceeded, error in build gulp task.
    So, i come to know that there must be something tricky with MSBuild. It is missing something that generally shifts with VS in our local machine. I found a solution as per: http://nickberardi.com/a-net-build-server-without-visual-studio. We need to perform third step from the link, which says copy both the below folders from your local machine to server:

    • C:\Program Files (x86)\MSBuild
    • C:\Program Files (x86)\Reference Assemblies

    Between those two directories MSBuild and Reference Assemblies you have most everything you need to complete any build that Visual Studio can do by it self. And the above steps while simple, but not obvious, provide a nice clean build server that doesn’t require Visual Studio to operate.

Keep reading Helixian.

 

 

 

Disappearing cores in Solr after restart

You are setting up Solr cores, and it is getting disappeared on restart. Is this happening with you? Yes, It did happen to me as well when i got a chance to setup Solr for one of the Sitecore project.

I installed Solr 6.2 and completed all the installation and configuring steps. Than i started to create multiple cores for multiple indexes (Sitecore indexex like master/web/core/analytics etc.).

I created first core by copying the folder “configsets\basic_configs\conf” as per instructions and renamed it to relevant index say “sitecore_master_index”. I created a core for it in Solr using Solr admin UI. Everything works fine (even after restart 🙂). Than i started to create multiple cores for other indexes (core/web/analytics) by copying “sitecore_master_index” folder. I was able to create cores and it was working fine. When i came to office next day than i realized that cores are not showing in Solr admin UI.

What is the solution?

I tried many options to persist my cores, yes i also tried to add property persistent=”true” to <solr> node in Solr.xml, but no luck there too.

Than after fighting a lot, in logging tab i saw an error mentioning that Multiple cores found with the name “sitecore_master_index” while discovering cores. Here is an interesting fact about how Solr finds the core: “In older versions of Solr, cores had to be predefined as <core> tags in solr.xml in order for Solr to know about them. Now, however, Solr supports automatic discovery of cores and they no longer need to be explicitly defined. The recommended way is to dynamically create cores/collections using the APIs.”

As i got to know that issue is there with the name of cores, i checked the “core.properties” file inside other core folders like sitecore_core_index, sitecore_web_index, sitecore_analytics_index etc. and i found that name property having same value as sitecore_master_index as shown in below image:

solr1

Which was causing the issue while discovering cores. Giving unique core names solved the all issues and cores getting persisted after restart without specifying persistent=”true” property in Solr.config.

Please also specify if you faced similar situation and solved the issue in a different way, will be interesting to hear that.

Preview Publishing Target – Enable Normal View For Non-Sitecore Users

Enabling normal view to non Sitecore users to review changes before publishing the actual changes to production site – preview publishing target

Sometimes, we got requirements from client that some of their executives will also required to review the site changes before publishing it to live site, and upon that those executives will be non Sitecore users. In this case we need to enable normal view mode for non Sitecore users so that they can review changes before reaching to final state of workflow to publish to public site.

Sitecore has introduced a feature called Preview Publishing Target which allows us to publish the items to that specific target ignoring in which state of workflow an item is.

Let’s create a Preview Publishing Target and enable normal view for the non Sitecore reviewers step by step:

  • Create a copy database of web. We have named it here preview_web.
  • Add connectionstring in App_Config/Connectionstrings.config with above specified name preview_web.
  • Add database node in App_Config/Sitecore.Config <databases> pointing to above database preview_web.
  • Add new publishing target to /sitecore/system/Publishing targets/
  • 1
  • Check Preview publishing target check-box as shown in above image
  • Publish this item to both the target web and preview_web.
  • Go to workflow used in your site -> select state -> select the preview publishing targets from the list as shown below:
  • 2
  • Do the same steps for all the necessary workflow states.
  • Publish the workflow with subitems.
  • Now accessing item which is using above workflow, you can see the note as shown below:
  • 3
  • Now add a new site in site definitions in ~/App_Config/Sitecore.Config
  • <site name="previewsite" hostName="scpreview" enableAnalytics="true" enableTracking="true" virtualFolder="/" physicalFolder="/" rootPath="/sitecore/content" startItem="/homepage" database="preview_web" domain="extranet" allowDebug="true" cacheHtml="true" htmlCacheSize="50MB" registryCacheSize="0" viewStateCacheSize="0" xslCacheSize="25MB" filteredItemsCacheSize="10MB" enablePreview="true" enableWebEdit="true" enableDebugger="true" disableClientData="false" cacheRenderingParameters="true" renderingParametersCacheSize="10MB" />
    
  • Add bindings and host entry for above demo host
  • Now, make changes, publish changes to both web and preview_web instances before submitting or approving in final workflow state, changes will be visible in the above preview site. While live site will remain unchanged.

Note:

Hope this will help you in implementing similar scenario.

Using Display Name and Generating links for multilingual sites – Sitecore

About Display Name:

As many of us already aware, Display Name is used

In case of multilingual sites, where we required to generate links for pages specific to the language it is serving. For ex: there is a page in website named about-us and website is available in two languages English, and German. Now URL should be formed according to language as shown below:

This can be achieved by the feature referred as Display Name in Sitecore.

How to Assign Display Name

  • Create an item in Sitecore with the preferred name, in our example it is about-us.
  • For English language version display name will be same as item name so we will skip this part for English language.
  • For German language, Select the German language version of an item, click on Home (tab) -> Rename (chunk) -> Display Name command from the ribbon bar in content editor (You can also assign Display Name from Experience Editor).
  • A popup will appear expecting a text, enter the display name specific to the language which will then be used to form language specific URL. In our example equivalent to contact-us will be kontaktiere-uns, so enter this in textbox and save.
  • That’s it from the configuration part. You will see entered display name in content tree now instead of item name.

Generating Links While Using Display Name Feature

To generate URL specific to language using Display Name, You need to add useDisplayName=”true” into linkmanager in the sitecore.config. See below:


<linkManager defaultProvider="sitecore">
    <providers>
      <clear/>
      <add name="sitecore" type="Sitecore.Links.LinkProvider, Sitecore.Kernel" addAspxExtension="false" alwaysIncludeServerUrl="false" encodeNames="true" languageEmbedding="always" languageLocation="querystring" lowercaseUrls="true" shortenUrls="true" useDisplayName="true"/>
    </providers>
</linkManager>

After enabling Display Name in link manager, you can now generate link using link manager as follow:

Consider context language as English, and we need to generate link for German language version of item using display name. We can do it simply using link manager in view as:


@{

Sitecore.Links.UrlOptions options = new Sitecore.Links.UrlOptions();
options.Language = Sitecore.Data.Managers.LanguageManager.GetLanguage("de-DE");
options.LanguageEmbedding = Sitecore.Links.LanguageEmbedding.Always;
options.LanguageLocation = Sitecore.Links.LanguageLocation.QueryString;
options.UseDisplayName = true;

string URL = Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Context.Item, options);

}

Will this generate the link we desire for German (de-DE) language? No.

The link generated using the above code will look something like http://[hostname]/about-us?sc_lang=de-DE

Why we are showing about-us in the URL instead of the kontaktiere-uns even though we have supplied language de-DE in UrlOptions.

After drilling down to Linkmanager code, come to know that language passed into UrlOptions not getting used while getting Display Name, It is getting Display Name of the item provided as first parameter of GetItemUrl()

In our case we are passing context item, and context language is English. As we do not have provided any Display Name for this item in English language, it will use Item Name itself to generate the URL.

So, we need to get the item in specific language before passing to GetItemUrl() function to form valid URL specific to language with help of Display Name. See below:


@{

string URL = Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Context.Item, options);

}

In place of above line use below code:


@{

string URL = @Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Context.Database.GetItem(Sitecore.Context.Item.ID, Sitecore.Data.Managers.LanguageManager.GetLanguage("de-DE")), options);

}

So, above code will first get German version of the about-us item, and thus will form a valid URL using Display Name provided for German language.

Unit Testing in Sitecore using Nunit with Sitecore Context information

Few months back, we were doing R&D to implement Unit Testing for our Sitecore project. We tried different kind of solutions like Miscosoft’s Unit Testing Template which we can create from Visual Studio directly and Nunit for which you can install Nuget Package from Visual Studio.

Both has all the functionality that we require in Unit Testing with some different gesture. Microsoft’s Unit Testing project can easily be run from Visual Studio itself and there are couple of ways to run Nunit either by command line or using GUI. I would not go into detail of implementing it.

What is the challenge?

Everything works well in either of two solutions until Sitecore Context comes into the picture. In our case, all the module were tightly coupled so it was not properly unit testable without Sitecore Context information. And we were also needed to perform testing on live items so Fake DB couldn’t help. So, we created Unit Test project using NUnit and executed these unit tests using a tool page from inside Sitecore website with Sitecore Context information. Please see implementation below:

Step 1: Creating Unit Test peoject using NUnit

  • Create a Class Library project, we will call it “MyProject.Tests” here.
  • Add reference of “Sitecore.Kernal.dll” to the project.
  • Install NUnit from Nuget package manager console or download latest NUnit asemplys from download page.
  • Add references of “nunit.core.dll”, “nunit.core.interfaces.dll”, and “nunit.framework.dll” to the project.
  • Create a class and tests as shown in below code:
using NUnit.Framework;
namespace MyProject.Tests
{
    [TestFixture, Category("TestGroup1")]
    public class TestMethods
    {
        [Test]
        public void Test1()
        {
            //Use Sitecore Context information here. It really works!
            String ContextSite = Sitecore.Context.Site.Name;
            String ContextItemName = Sitecore.Context.Item.Name;
            Assert.IsNotNullOrEmpty(ContextItemName, "Cannot access Sitecore Context information.");
        }
    }
}

Step 2: Create aspx page into Sitecore website to execute Tests manually

After created Unit Test project, It’s turn to execute these tests. In place of traditional way of executing either from command line or from GUI of NUnit we will execute it using code from “.aspx” page within Sitecore website.

  • Create an “.aspx” page in your sitecore website, we will call it “ExecuteUnitTest.aspx” here.
  • Add references of “nunit.core.dll”, “nunit.core.interfaces.dll”, and “nunit.framework.dll” to the website as well.
using NUnit.Core;
using NUnit.Core.Filters;
Inherit NUnit.Core.EventListner to the page.
public partial class ExecuteUnitTest : System.Web.UI.Page, EventListener
namespace website.tools
{
    public partial class ExecuteUnitTest : System.Web.UI.Page, EventListener
    {
        #region Member Variables
        DataTable m_results = new DataTable();
        private int m_executedCount = 0;
        private int m_failedCount = 0;
        private TestSuite m_testSuite = null;
        #endregion

        protected void Page_Load(object sender, EventArgs e)
        {
            // Initialise data table to hold test results
            m_results.Columns.Add("test");
            m_results.Columns.Add("result");
            m_results.Columns.Add("message");
            m_results.Columns.Add("class");

            // Initialise controls
            lblResult.Text = "";
            ltlStats.Text = "";

            // Initialise NUnit
            CoreExtensions.Host.InitializeService();

            // Find tests in current assembly
            TestPackage package = new TestPackage(Server.MapPath("~/bin/MyProject.Tests.dll")); // PATH TO THE DLL OF THE UNIT TEST PROJECT
            m_testSuite = new TestSuiteBuilder().Build(package);

            if (!IsPostBack)
            {
                // Display category filters
                StringCollection coll = new StringCollection();
                GetCategories((TestSuite)m_testSuite, coll);
                string[] cats = new string[coll.Count];
                coll.CopyTo(cats, 0);
                Array.Sort(cats);
                cblCategories.DataSource = cats;
                cblCategories.DataBind();
            }
        }

        protected void RunClick(object sender, EventArgs args)
        {
            // Determine if any category filters have been selected
            StringCollection categories = new StringCollection();
            for (int i = 0; i < cblCategories.Items.Count; i++)             {                 if (cblCategories.Items[i].Selected)                     categories.Add(cblCategories.Items[i].Value);             }             string[] arCats = new string[categories.Count];             categories.CopyTo(arCats, 0);             // Create a category filter             ITestFilter filter = new CategoryFilter(arCats);             //ITestFilter filter = new NUnit.Core.Filters.SimpleNameFilter("GetExternalContentAndTransform");             TestResult result = null;             // Run test suite with appropriate filter             if (arCats.Length >= 1)
                result = m_testSuite.Run(this, filter);
            else
                result = m_testSuite.RunSuite(this, NUnit.Core.Filters.SimpleNameFilter.Empty);
            //result = m_testSuite.Run(this, NUnit.Core.Filters.SimpleNameFilter.Empty);

            // Bind results to presentation
            DataView m_resultsview = new DataView(m_results);
            DataTable dt = m_resultsview.ToTable(true, "test");
            gvResults.DataSource = m_results;
            gvResults.DataBind();

            // Display statistics
            ltlStats.Text = string.Format("{0} out of {1} tests run in {2} seconds.", m_executedCount, result.Test.TestCount, result.Time);

            if (m_failedCount > 0)
                ltlStats.Text += string.Format("
{0} {1} failed", m_failedCount, m_failedCount == 1 ? "test" : "tests");

            int skipped = result.Test.TestCount - m_executedCount;
            if (skipped > 0)
                ltlStats.Text += string.Format("
{0} {1} skipped", skipped, skipped == 1 ? "test" : "tests");

            lblResult.Text = "Suite " + (result.IsSuccess ? "Passed" : "Failed");
            if (result.IsSuccess)
                lblResult.CssClass = "passLabel";
            else
                lblResult.CssClass = "failLabel";
        }

        ///
<summary>
        /// Find all available categories in the test suite
        /// </summary>

        /// <param name="suite">The test suite to get categories from</param>
        /// <param name="cats">Output collection containing categories found</param>
        private void GetCategories(TestSuite suite, StringCollection cats)
        {
            if (suite.Categories != null)
            {
                for (int i = 0; i < suite.Categories.Count; i++)
                    if (!cats.Contains((string)suite.Categories[i]))
                        cats.Add((string)suite.Categories[i]);
            }

            for (int i = 0; i < suite.Tests.Count; i++)
                if (((ITest)suite.Tests[i]).IsSuite)
                    GetCategories((TestSuite)suite.Tests[i], cats);
        }

        #region EventListener Members

        public void TestFinished(TestResult result)
        {
            // Put results into data table
            DataRow dr = m_results.NewRow();
            dr["test"] = result.Test.TestName;
            dr["message"] = result.Message;
            if (result.IsFailure)
                dr["message"] += result.StackTrace;
            dr["class"] = "notRun";

            if (result.IsSuccess && result.Executed)
            {
                dr["result"] = "Pass";
                dr["class"] = "pass";
            }

            if (result.IsFailure && result.Executed)
            {
                dr["result"] = "Fail";
                dr["class"] = "fail";
                m_failedCount++;
            }

            if (result.Executed)
                m_executedCount++;

            m_results.Rows.Add(dr);
        }

        public void RunFinished(Exception exception)
        {
        }

        public void RunFinished(TestResult result)
        {
        }

        public void RunStarted(string name, int testCount)
        {
        }

        public void SuiteFinished(TestResult result)
        {
        }

        public void SuiteStarted(TestName testName)
        {
        }

        public void TestOutput(TestOutput testOutput)
        {
        }

        public void TestStarted(TestName testName)
        {
        }

        public void UnhandledException(Exception exception)
        {
        }

        #endregion
    }
}

When you view above page in the browser using link http://#MySitecoreDomain#/tools/#ExecuteUnitTest.aspx#,
You will see all the categories specified in the Unit Testing project. Upon selecting one or more
selecting when you click submit, It will run all the test suites available within that category
and displays the result of the tests as shown in below image.

UnitTest1
After executing suite, it looks like below:

UnitTest2
And thus way you can write the tests which is highly dependent on Sitecore Context information.

Enjoy Unit Testing with Sitecore Context. No Fake 🙂

Useful Sitecore Recourses

Long way back when i started working with Sitecore, I have faced many difficulties to get the useful resources. Even though there are lots of great writers available in the Sitecore world, It’s difficult to find what each said on a same topic.

To overcome this i am trying to make a single repository, where we can have most useful links of the content written by great Sitecore people on a specific topic. so that for specific topic we only need to look at one place.

If you wish to contribute to the repository, please get in touch with me at psatikunvar7@gmail.com 

Topics:

Sitecore Publishing

Sitecore Caching

Sitecore Search –  Lucene/Solr

Workflows – developing and customizing

Globalization and Language Fallback Support

Custom Field Types

Custom Pipelines

Sitecore Modules on Marketplace

DMS – Implementing and customising Engagement Analytics

Note that i have just started collecting useful resources for different topics (Please forgive me if i have missed other useful links), and it will last for as long as Sitecore 🙂

Please share the topics and useful links for that topic to get added to this repository.

EvoPDF/Winnovative – Adding Header, Footer, and Page Number in Existing PDF without using HtmlToPdfConverter

Adding to my previous blog post on using EvoPDF/Winnovative to convert Html to PDF, How we can add Header, Footer, and Page Number in existing PDFs.

When you look examples given by EvoPDF/Winnovative for adding Header/Footer/Page Number in existing PDF, Most of examples are with HtmlToPdfConverter. Which means example of existing PDFs and also converting URL/Html to PDF at a time.

But if we do not want to do any conversion, Just add Header/Footer/Page Number in existing PDF document without using HtmlToPdfConverter than there isn’t any solution documented. So, here we will see how we can achieve this functionality:

Merge Existing PDFs & Add Header/Footer/Page Numbers

 //Create a document to merge all selected PDFs
 Document MergedDocument = new Document();

 //Get individual existing PDFs to merge and add header/footer/page number
 Document PDFDoc1 = new Document(Server.MapPath("~/PDF1.pdf"));
 Document PDFDoc2 = new Document(Server.MapPath("~/PDF2.pdf"));

 //Merge existing PDFs into New Merge Document
 MergedDocument.AppendDocument(PDFDoc1, true, true, true);
 MergedDocument.AppendDocument(PDFDoc2, true, true, true);
 //Appending document with this syntax MergedDocument.AppendDocument(PDFDoc1);
 //will not add the header/footer/page number on the PDF. To add it properly,
 //you need to append document in above syntax Doc.AppendDocument(PDFDoc,
 //enableHeaderAndFooter, drawHeaderOnFirstPage, drawFooterOnFirstPage)

 //Append Header, Footer and Page Number to the merged document so that it will get
 //applied to all existing PDFs appended to the document           
 CreateHeader(MergedDocument, 100, "<h3>Header</h3>", False, "Page &p; of &P;", 10, 60, "Verdana", 10, "Silver", True, "Silver");
 CreateFooter(MergedDocument, 100, "<h3>Footer</h3>", True, "Page &p; of &P;", 10, 60, "Verdana", 10, "Silver", True, "Silver");
            
 byte[] outPdfBuffer = MergedDocument.Save();
 Response.AddHeader("Content-Type", "application/pdf");
 Response.AddHeader("Content-Disposition", String.Format("attachment;  filename=Header_Footer_in_External_PDF.pdf; size={0}", outPdfBuffer.Length.ToString()));
 Response.BinaryWrite(outPdfBuffer);
 Response.End();

/// <summary>
/// Creates the document header
/// </summary>
 private void CreateHeader(Document pdfDocument, int headerHeight, string   headerHtml, bool addPageNumbers, string pageNumberText, int pageNumberLocationX,
int pageNumberLocationY, string pageNumberFontFamily, int pageNumberFontSize, string pageNumberFontColor, bool drawHeaderLine, string headerLineColor)
        {
            pdfDocument.AddHeaderTemplate(headerHeight);
            HtmlToPdfElement HtmlElement = new HtmlToPdfElement(headerHtml, "");
            HtmlElement.FitHeight = true;
            pdfDocument.Header.AddElement(HtmlElement);
            if (addPageNumbers)
            {
                TextElement pageNumberElement = new TextElement(pageNumberLocationX, pageNumberLocationY, pageNumberText,
                    new System.Drawing.Font(new System.Drawing.FontFamily(pageNumberFontFamily), pageNumberFontSize, System.Drawing.GraphicsUnit.Point));
                pageNumberElement.ForeColor = Color.FromName(pageNumberFontColor);
                pageNumberElement.EmbedSysFont = true;
                pdfDocument.Header.AddElement(pageNumberElement);
            }
            if (drawHeaderLine)
            {
                float Width = pdfDocument.Header.Width;
                float Height = pdfDocument.Header.Height;
                LineElement headerLine = new LineElement(0, Height - 1, Width, Height - 1);
                headerLine.ForeColor = Color.FromName(headerLineColor);
                pdfDocument.Header.AddElement(headerLine);
            }
        }
/// <summary>
/// Creates the document footer
/// </summary>
 private void CreateFooter(Document pdfDocument, int footerHeight, string footerHtml, bool addPageNumbers, string pageNumberText, int pageNumberLocationX,
int pageNumberLocationY, string pageNumberFontFamily, int pageNumberFontSize, string pageNumberFontColor, bool drawFooterLine, string footerLineColor)
        {
            pdfDocument.AddFooterTemplate(footerHeight);
            HtmlToPdfElement HtmlElement = new HtmlToPdfElement(footerHtml, "");
            HtmlElement.FitHeight = true;
            pdfDocument.Footer.AddElement(HtmlElement);
            if (addPageNumbers)
            {
                TextElement pageNumberElement = new TextElement(pageNumberLocationX, pageNumberLocationY, pageNumberText,
                    new System.Drawing.Font(new System.Drawing.FontFamily(pageNumberFontFamily), pageNumberFontSize, System.Drawing.GraphicsUnit.Point));
                pageNumberElement.ForeColor = Color.FromName(pageNumberFontColor);
                pageNumberElement.EmbedSysFont = true;
                pdfDocument.Footer.AddElement(pageNumberElement);
            }
            if (drawFooterLine)
            {
                float Width = pdfDocument.Footer.Width;
                LineElement footerLine = new LineElement(0, 0, Width, 0);
                footerLine.ForeColor = Color.FromName(footerLineColor);
                pdfDocument.Footer.AddElement(footerLine);
            }
        }

 

This might help you if you are stuck in same situation, Please share if any other way with which we can implement this functionality.

Thanks for reading 🙂