Blog posts by Janaka Fernando2021-03-05T15:02:06.0000000Z/blogs/Janaka-Fernando/Optimizely WorldWhen NOT to use Episerver Search & Navigation (Find)/blogs/Janaka-Fernando/Dates/2021/3/when-not-to-use-episerver-search--navigation-find/2021-03-05T15:02:06.0000000Z<p>I was having a conversation recently with a Technical Architect around Product recommendations and website performance. There was some inefficient code retrieving the product display based on the results from the Optimizely Product Recommendations tracking response. The first thought was to utilise Episerver Search & Navigation (formerly Find) for this feature. On the face of it, Search provides a very fast and effective means of retrieving catalog items. In some situations that would be correct, however this is a bad use case for Episerver Search. </p>
<p>This got me thinking about why some scenarios are not suited for Episerver Search & Navigation.</p>
<h3>Unable to implement a Caching strategy</h3>
<p>Anything that could result in hundreds or thousands of customers triggering Search queries around the same time can quickly max out the queries per second threshold on the Search service. This is the type of bug that could pass QA and not be detected until the real-world load is on the application.</p>
<p>Recommendations are by design unique for each customer and could change between subsequent requests. This makes them difficult if not outright impossible to cache.</p>
<h3>There is another efficient API available</h3>
<p>In the case of the code needed for Product recommendations, the response from Episerver provides a Content Reference for each recommendation. The Episerver API provides an easy way to retrieve all of the items by calling<span style="font-family: helvetica, arial, sans-serif;"> </span><em><span style="font-family: 'courier new', courier, monospace;"><span style="font-family: helvetica, arial, sans-serif;">IContentLoader.GetItems()</span> </span></em>directly. Even if the Content Reference was not supplied, most systems would provide the unique refCode/SKU code, which again can be converted to a Content Reference easily.</p>
<p>This same rule applies when a developer simply needs to retrieve content in a hierarchical nature such as Pages from the site tree, even when filters need to be applied. This only becomes ineffective when many hundreds or thousands of pages are in a single node, but I would say that is a fault of bad information architecture!</p>
<h3>Paging through large datasets</h3>
<p>This last one might not seem as obvious, because you want to use Episerver Search for searching across very large amounts of data right? For the most part yes. When a customer searches by a keyword and goes through a couple of pages of results, that’s normal behaviour.</p>
<p>What I’m thinking of here is code that runs in custom jobs that use Episerver Search, for example, a Google shopping feed scheduled job. The problem here is it normally traverses every product in the catalog and the Skip and Take methods internally are not efficient for this purpose. If your batch size is 1000 products and you are on step 50, that Skip(50000) still needs to load through all those products. This puts quite an intensive load on the Search cluster and can affect overall performance.</p>
<p>If it's possible to avoid Search and work directly against the Commerce API, I would advocate that approach first, provided it's performant enough for your needs.</p>
<p>The recommended approach instead with Search is to use a <em>GreaterThan()</em> filter alongside <em>Take() </em>on your data. For example,</p>
<pre class="language-csharp"><code>searchQuery.Filter(x => x.PublishDate.GreaterThan(lastProcessedPublishDate));</code></pre>
<p> </p>
<p>I hope you found this informative and helpful. Episerver Search is a very powerful and flexible product, but with great power comes… well you know the rest.</p>The Goldilocks Rule/blogs/Janaka-Fernando/Dates/2020/2/the-goldilocks-rule/2020-02-21T11:17:10.0000000Z<p>When I hear of a customer who is unhappy with Episerver what they are usually unhappy with is a badly implemented Episerver solution and the number one reason I find is that the development team didn’t follow the Goldilocks rule.</p>
<h3>What is the Goldilocks rule?</h3>
<p>The story of Goldilocks and the Three Bears is a classic tale. In it, a young girl stumbles into a house in a forest that, unbeknownst to her, is owned by three bears.</p>
<p>She helps herself to the bears' porridge only to find the porridge either <em>too hot</em> or <em>too cold </em>before settling on one that is <em>just right</em>. </p>
<p>Goldilocks' experience is not unlike that of organisations looking to find the right content strategy for their <span>Episerver digital</span> experience.<span> </span></p>
<p><span>To follow the Goldilocks rule, you must find a balance between the number of Page types and Block types in your solution that is just right. This will be an ideal number that won't confuse editors, will provide them with the right amount of governance yet give them enough flexibility to get the most out of Episerver. </span></p>
<p><em>Not enough Blocks </em>and you could be in danger of creating too many rigid Page types.<br /><em>Too many Blocks</em> and you could be in danger of increasing the amount of maintenance required. Neither are good experiences for editors.</p>
<h4>Not enough Blocks</h4>
<p>On the extreme end of the spectrum, there are absolutely no Blocks at all. While I very rarely come across this anymore, I have found CMS 7.5+ sites that still operated with no concept of Content areas and Blocks. These were legacy and have been upgraded but never made to fully utilise content reusability.</p>
<p>As you move up the spectrum you may find solutions where Blocks do exist, but only a couple. Here most of the Pages are strongly typed and because of the lack of Blocks; Pages must be more prescriptive – e.g. because there are no Listing blocks there is a different listing for multiple Page types.</p>
<p>Layout differences may also create specific Page types. If you ever come across a solution with types like Standard Page, Standard Page 2 Column, Standard Page No Sidebar, etc. then you know the reason is that layouts are rigid, and one solution is to have dozens of Page types. Editors will likely complain they have too many page types to choose from and if they don’t pick the correct one they have to redo their work again in another page.</p>
<p><img src="/link/4ca398ffa72f4ce5a359bdbe4e2f3947.aspx" width="506" alt="Not enough blocks" height="386" /></p>
<h6>Figure 1: Too many layout specific Page types</h6>
<p><br />Being prescriptive has its place with Pages (more on that shortly) but putting too much rigidity in place doesn't make for a compelling Editor experience. This is simply missing out on everything modern Episerver can offer in content reuse and flexibility.</p>
<h3>Too many Blocks</h3>
<p>This brings me to the other end of the spectrum where you have hardly any Page types and nearly everything is built up entirely of Blocks. Customers might see a demo and think they can just drag and drop their way to building the entire website… the Alloy website Landing page type has a lot to answer for!</p>
<p>To do that right would put a lot of cost on the development of completely flexible Blocks for any situation. While modern frameworks like Bootstrap can make this simpler, it is not feasible to say that every single block could be used with any other block side-by-side.</p>
<p>What I have seen in implementations are page designs that have been sliced up and respective Blocks created for every conceivable piece of content on the screen.</p>
<p>Going overboard with this approach winds up making the CMS virtually unusable for the Editor. To make a single change when, say, an Event page's (assume it's a generic Page type also) location details need to be updated, involves the following steps:</p>
<ol>
<li>Find the Page in the site tree</li>
<li>Go to All Properties View</li>
<li>Find the correct Content Area</li>
<li>Click to edit a Container Block</li>
<li>Click to edit an Editorial Block</li>
<li>Update <em>Text</em> WYSIWYG property and Publish</li>
</ol>
<p>Now compare the same change on an Event Page type with specific properties for events:</p>
<ol>
<li>Find the Page in the site tree</li>
<li>Update <em>Location</em> WYSIWYG property and Publish</li>
</ol>
<p><img src="/link/aa797b2b80604fb2a81a6a3375209eb4.aspx" width="473" alt="A page made up entirely of Blocks" height="500" /></p>
<h6>Figure 2: A page made up entirely of Blocks</h6>
<p><br />Updating Blocks becomes a very time-consuming process. Also, it's missing out on the On-Page edit features you can get by making your Page types more strongly typed when content needs to be consistently in the same place. Additionally, it makes more sense to Editors having more prescriptive, strongly typed pages. For instance, a News Article Page has to have a headline, byline, author, introduction and main body. That's easier to understand and validate for than trying to build up a page using different block types.</p>
<h4>Just Right</h4>
<p>Finding what is Just Right for your project will be part of the planning work done before development begins. Once you have a good sense of the wireframes and design mock-ups you can conduct a CMS planning workshop. In general, there are just two main things to draw out of this process.</p>
<p><strong>1 – Start with detecting consistency across different screens</strong></p>
<p>For example, several pages that start with a banner, page heading, intro text and main body can all be the same Page type. This provides more generic Page types and the use of Blocks to provide flexibility in specific regions of the page.</p>
<p> </p>
<p><strong>2 - Introduce specialisation for specific pages</strong><br />When you have an idea of the template for the generic Page types you can start to specialise specific pages that need some custom properties or differences in their design. These can, of course, inherit from our generic Page type where they do share similar properties. For example, an Event Page type shares all the same properties as a Standard Page but it has specific properties for Location, Event start, Event end, etc.</p>
<p> </p>
<p><strong>The most common reasons for specialised page types</strong></p>
<ul>
<li>Governance requires specific properties above the standard ones</li>
<li>A different set of default values is required</li>
<li>Design is different – e.g. Case study and Article Page may contain the same properties but display differently</li>
<li>Custom business logic/controller code is required to handle this type of Page</li>
</ul>
<p> </p>
<h3>Conclusion</h3>
<p>Getting the balance right is not as easy as you think. Most of the projects we do at Made to Engage will have a small number of Page types (usually between 6 - 12) which is just enough to specialise and a slightly larger list of Block types (typically up to 16). This is because there are more variations of what you can do with Blocks on a page then types of Pages. However, we try to keep this to a manageable amount for our customer to be able to quickly comprehend what they need. We also find a key benefit of this approach is that customers will intuitively know how to work with Episerver and enjoy using the product.</p>Is your organisation still developing Episerver solutions in isolation to your Content team?/blogs/Janaka-Fernando/Dates/2020/1/is-your-organisation-still-developing-episerver-solutions-in-isolation-to-your-content-team/2020-01-29T08:50:45.0000000Z<h3>Your projects may be experiencing any of these symptoms.</h3>
<p>The team feels they're making good progress on all their sprints. Then they hit the dreaded UAT phase of the project and suddenly loads of bugs and problems have been found!</p>
<p>You are forced to make assumptions based on designs (or lack of them) - "Does anybody know if a Banner block should have a call to action button? It was included in one of the mockups, but no one discussed it as a requirement."</p>
<p>The project finishes, you hand over the keys, but nothing happens till months later when the customer finally gets around to understanding how to manage content.</p>
<p>Everyone says we are doing Agile, but it feels a lot like Scrum-Waterfall.</p>
<p><img src="/link/3df91f9f26094c87bb91548c36f34d23.aspx" width="800" alt="Feeling unwell?" height="607" /></p>
<h6><span>Photo by </span><a href="https://unsplash.com/@kellysikkema?utm_source=unsplash&amp;amp;amp;amp;amp;amp;amp;utm_medium=referral&amp;amp;amp;amp;amp;amp;amp;utm_content=creditCopyText">Kelly Sikkema</a><span> on </span><a href="https://unsplash.com/s/photos/ill?utm_source=unsplash&amp;amp;amp;amp;amp;amp;amp;utm_medium=referral&amp;amp;amp;amp;amp;amp;amp;utm_content=creditCopyText">Unsplash</a></h6>
<p>One explanation for why these problems are occurring is real world content is not being included in the delivery at various stages. Yes content, that thing the solutions we are building is meant for. You probably have people who are paid just to create this stuff. Are you utilising your Content team effectively?</p>
<h3>Here's a couple places to be thinking about content.</h3>
<p><em>Customer Experience (CX)</em> <br />This is the place where content is considered and played around with in order to determine the best way for the site's customers to experience different journeys. Domain experts are needed here to provide better context and examples.</p>
<p><em>Wireframes and Designs</em><br />While prototyping can be a quick process, ensure we have captured good samples of content to include in our wireframes and hi-fidelity designs. "Lorem Ipsum" and repeated filler text can come back to haunt the team later when it is unclear what production content looks like.</p>
<p><em>Content Strategy</em><br />This is where you will need to ask your customer questions on what content and message they want to present. What will be the information architecture for your solution? Will content be migrated, will we be producing new content, or both? Without this going live will become slowed down by inertia when content creation becomes a bottleneck. </p>
<p><em>Development</em> <br />Ensure your team is thinking about production content during development with good, clear user acceptance tests. Build out real, production content when you do QA.</p>
<p><em>Agile Delivery</em><br />Rather than just give a sprint demo every couple of weeks, how about delivering the actual product? Have your Content team build out production ready content each sprint.</p>
<h3>Better content, Better projects.</h3>
<p>By the end of your project a good chunk of the site should be already in place. At the very least all of the key pages are in the CMS and every single one of your Page and Block types has been built with production content. This has been a collaborative project for everyone involved.</p>
<p>While this isn't specifically an Episerver development problem, ignoring it makes problems grow exponentially when developing for Episerver. For anyone interested you can read my article about <a href="https://medium.com/serious-scrum/add-content-into-your-definition-of-done-e788b428c1e4">including content in your development team's definition of done</a> for a more general view of how to apply this thinking.</p>
<p>A customer who has been involved early on seeing the project come to life, will have a richer understanding of using the completed Episerver solution - and the project will be more likely a success. When it is difficult to tie down a customer to commit time to the project, promote a Digital Marketer from your Content team to take the lead. They can fill in the gaps and provide useful feedback to developers. Having a Content Specialist as part of your delivery team is a must have!</p>
<p>Finally, don't we all want to create something amazing and engaging? The reason we work with Episerver is because it gives us a platform to create an unlimited variety of customer solutions with different kinds of content. Try introducing more collaboration with your Content team into your product development and see the difference it makes.</p>Five things developers need to start doing now to make editors happy!/blogs/Janaka-Fernando/Dates/2019/3/five-things-developers-need-to-start-doing-now-to-make-editors-happy/2019-03-21T21:41:34.0000000Z<p>Over the years I've seen many implementations of Episerver and one thing that has been consistent is developers tending to forget about the Editor. You know, that person who will wind up using the product. </p>
<p>Sometimes it's an inherited project from an inexperienced partner or customer that decided they could do it all themselves… after all Epi is just .net isn't it? I’ve heard from customers that the product is terrible and difficult to use. What? Those are exactly the opposite points of why anyone would buy Epi. When we look under the hood we can find bad examples of content types, poor UI and rushed decisions.</p>
<p>At Made to Engage, when we train backend & frontend developers on Episerver we always include a module on designing for the Editor. Bad habits can stick though, so we regularly need to review and check best practices are followed. </p>
<p>Here's a checklist I made to make sure what we are building is a good product for Editors.</p>
<h3>1. Use clear names and descriptions for all your content types</h3>
<p>This one should be fairly easy to do, yet I’ve seen plenty of projects that have screens like this! </p>
<p> <img src="/link/e3b441a376e14f528404b9e4fa126d40.aspx" /></p>
<p>
<p>While ArticlePage is just a bit lazy, what the heck is StandardLeftContentPage?</p>
<p>Episerver has the Display Name and Description properties for your content type attribute to make this editor friendly. These can also be localized when you have editors in multiple languages. The same rules apply for Blocks too. </p>
<p>The Description should be in line with the intended usage of the page. </p>
<p>For example:</p>
<p>Article Page<br />This page is for Press releases and corporate information.</p>
<p> </p>
<h3>2. Have relevant iconography</h3>
<p>And while we are here did you notice the use of Alloy icons? Is it just me or do these seem to be the same icons we see on every project? They make sense on Alloy because that is the brand of the company! At the very least you should come up with a thumbnail icon for the brand of the customer site. </p>
<p>The icons are meant to indicate the usage of the Page or Block, so more specific icons are helpful for the editor to spot the right content type quickly.</p>
<p>And never ever release a type without a thumbnail icon. You could spend weeks delivering the functionality for that specific content type and yet it will look unprofessional and rushed if you skip this detail.</p>
<p> </p>
<h3>3. Fully utilise on page editing</h3>
<p>Sometimes there are settings for a Page or Block that you need to go into the All Properties view to properly edit, like the number of items to display on a Page Listing block for instance. </p>
<p>However, you should try to make the On-Page Edit view the easiest way for editors to make changes to the majority of properties because this is the most natural way of understanding what those changes will look like.<img src="/link/93de90a3a1e44956939d35146f2c535c.aspx" width="825" height="569" /></p>
<p>As a rule of thumb anything in the Content tab should be available in the On-Page Edit view. If it doesn’t fit in there it’s likely to go in Settings or a custom tab name.</p>
<p> </p>
<h3>4. Avoid nesting of blocks where possible</h3>
<p>Here’s one that frustrates editors to no end. They want to do something simple but in order to do so they have to click through several layers of blocks.</p>
<p>Adding one layer is generally okay, for example a block that contains a set of items that might be blocks themselves.</p>
<p>The problem lies when those blocks contain other blocks and your poor Editor has to take 4 or more steps to finally edit the content. </p>
<p> </p>
<h3>5. Provide human error messages</h3>
<p>Error messages provide a better experience for Editors, guiding them on how to correct invalid input. However sometimes these don’t provide much help if they just show the default message.</p>
<p> <img src="/link/d17d9603445b47ad9714053d9a3d5cc2.aspx" /></p>
<p>Try to help them along with examples of the correct input or a clearer error message.</p>
<p><img src="/link/b0ff902ebee846f29cdbff1283636c0a.aspx" /></p>
<p>Again use localization here if you need to accomodate Editors in multiple languages.</p>
<p>
<h3>Conclusion</h3>
<p>I hope you found this useful and that importantly it makes you consider the experience of the Editors who use the site. If I missed something out that you find a niggling issue, drop a comment below. </p></p></p>Let's get leveraging social engagement for personalisation/blogs/Janaka-Fernando/Dates/2019/2/leveraging-social-engagement-and-personalisation/2019-02-05T19:11:44.0000000Z<p>Before Christmas I wrote an article on the Made to Engage blog highlighting four things retailers should ask for from Santa Claus to help them succeed in 2019. </p>
<p>It came down to providing a more seamless customer experience that gives shoppers exactly what they want with the flexibility of in-store convenience. We know customers are very picky and fussy these days. While I confess to being one of those people, the only alternative for retailers is to try to compete with Amazon or Walmart on price – not an easy thing to do. Why not read the full article <a href="https://www.madetoengage.com/blog/unwrap-our-4-stocking-fillers-for-ecommerce-success-in-2019/"><strong>here</strong></a>?<br /><br /></p>
<p>Now that we’re in 2019 and January is over, retailers will know how well they’ve done during the busiest period of the year. And the news isn't looking good for department stores, with headlines dominated by news of traditional brands like Sears and Marks & Spencer closing stores across the US and the UK. This suggests consumers are moving away from shopping at big catch-all stores towards searching out more tailored information or specific items that fit with their interests and hobbies. </p>
<p> </p>
<p>Niche retailers and brands are now better-placed to serve the right information and content to customers. Social influencers can be more valuable than traditional advertising and PPC at directing consumers to purchase the latest season’s products. The question is, what does this mean for developers and agencies? Epi's strengths lie in the collaboration of content and commerce to build rich, integrated commerce experiences that enable businesses to compete in new ways. Combining this capability with user-generated content and social influence via Stackla and Episerver Social can improve customer experiences even further.</p>
<p>In addition, combining Episerver Perform recommendations with social engagement metrics is a great way to support changing trends. Let's say you're a retailer of home furnishings and from March of this year you’re bringing out five new ranges with 20 products each. Which ones should you recommend? Those fancy new floor lights with Edison bulbs seem to be getting lots of likes from hipsters on Instagram. By monitoring consumer demand and updating a "social score" for your daily Perform data feed you can boost the products your customers are talking about to increase purchasing.</p>
<p>In 2019, retail businesses would do well to focus on building more engaging digital experiences that enable customers to interact, digest and promote their brands. Either they maximise value through immersive experiences that are tailored to them or customers will go elsewhere. It’s that simple.</p>Introducing Alloy Demo Kit/blogs/Janaka-Fernando/Dates/2015/8/introducing-alloy-demo-kit/2015-08-12T11:16:23.6670000Z<h2>Why another starter kit?</h2>
<p>Hey folks, we have recently released a new Github repository called <em>Alloy Demo Kit! </em></p>
<p>This is something we Solution Architects at EPiServer actually use for demos and POC purposes. It's based on the Alloy MVC Starter Site and you will find the familiar Alloy products here. However you will find this has its own extended content types and a whole host of additional features. Because everything is pre-set a demo can be set up within minutes. Alternatively, this can be the base site for a customised demo. </p>
<h3>More than Alloy Tech</h3>
<p>While the name implies the standard Alloy demo site, it goes beyond that with added functionality. There is also a new set of content, page and block types and a smorgasbord of Add-Ons! Oh and it works with EPiServer Find as its default search.</p>
<p>We've added certain things that we commonly get asked to demo, for instance there is a Carousel block item.</p>
<p><img src="/link/1ffa396ad55b47e2b973477f2ec00c61.aspx" alt="Image ADK-Carousel.jpg" /></p>
<p>Inside you will also find the Blog types that <a href="/link/d4905cd7bfa14054a14864f657337f8d.aspx">Jacob Khan contributed a little while back</a>.</p>
<p><img src="/link/47500e2627d8464eb79e1b01b2ab518b.aspx" width="693" alt="Image ADK-Blog.jpg" height="343" /></p>
<p>More code goodies are available including a RSS reader, custom CSS styling functionality, a Social feed block and even an Apple Watch display channel.</p>
<p><img src="/link/7e7273c2ce3f4a32b0185ab266fa9979.aspx" width="421" alt="Image ADK-AppleWatch.jpg" height="521" /></p>
<h3>Pre-installed Add-Ons</h3>
<p>The majority of Add-Ons you might demo are already included:</p>
<ul>
<li>Google Analytics</li>
<li>Social Reach</li>
<li>Content Collaboration</li>
<li>Language Manager</li>
<li>Live Monitor</li>
<li>Tiny MCE Spellchecker</li>
<li>Visitor Group Criteria Pack</li>
<li>Menu Pin</li>
<li>Marketing Automation Connector</li>
<li>Site Attention</li>
<li>Powerslice</li>
</ul>
<p>All Add-Ons are installed and ready to go. Some will need configuring to your own account. Please refer to the release notes for instructions on setting these up.</p>
<h3>Real world scenarios</h3>
<p>There is already sample data ready in this solution so you can hit the ground running. Here's some of the things you will find.</p>
<ul>
<li>Appropriate security groups and permissions are set up and 3 user accounts added with different permissions</li>
<li>Workflows (for Windows deployment) are pre-set for Press Releases and Products </li>
<li>Predefined Visitor Groups</li>
<li>Several additional languages and fallbacks</li>
<li>Pre-set Content Collaboration conversations</li>
<li>Mobile campaign in Projects</li>
<li>Additional Forms</li>
<li>Second site with site templates</li>
</ul>
<p>For example, included is a Twitter Visitor Group, which gets activated from incoming Twitter traffic. The home page has a personalised social block showing a Twitter feed if you are in this group.</p>
<p><img src="/link/87e096d5bdc44ad5beb912d4c24c7ee1.aspx" width="674" alt="Image ADK-Tweet.jpg" height="515" /></p>
<h3>Now get your hands on it</h3>
<p>The intention is you can clone the repository, update it to your settings, modify some content to tailor to your client's demo and then you can showcase the latest functionality. When you download the source you will find a Release notes for the latest version which guides you through configuration. </p>
<p>We would advise making sure any code or features you intend to re-use have appropriate security, logging, caching, scaling and performance testing and improvements made. As the name mentions this is a <em>Demo Kit </em>and while you may find some of the code useful this isn't a production ready system.</p>
<p>We use this on an on-going basis so will keep it updated regularly with the latest updates to the CMS.</p>
<p>Of course we are happy for developers to Fork this and make their own changes. We'd be even happier if the developer community decided to contribute some cool features back.</p>
<p><a href="https://github.com/episerver/AlloyDemoKit">View on GitHub</a></p>Implementing Pay by Credit payments/blogs/Janaka-Fernando/Dates/2015/4/implementing-pay-by-credit-payments/2015-05-05T21:03:21.3170000Z<h3>The Scenario</h3>
<p>One common scenario for B2B Commerce is allowing organizations to pay using credit for purchases which they will have to settle by invoice at a later date. Normally an organization would have to go through an approval and receive a credit limit from the retailer.</p>
<p>In this example I will show how we implemented a basic payment gateway for setting up Pay by Credit payment methods.</p>
<p> </p>
<h3>Credit fields for Organization</h3>
<p>To start with we will add two new fields to the Organization entity through Business Foundation. Both fields will be a Currency data type. </p>
<p>First add <em>CreditLimit</em> which will be used to set the limit for the Organization. By default this will not be null and be 0 as not every organization should have a credit limit. </p>
<p><img src="/link/d8e07a94482b483f98c1d4e7f6d031eb.aspx?id=120796" width="930" alt="Image PaybyCredit-Org1.PNG" height="251" /></p>
<p>Next add another Currency field called <em>CreditBalance</em>, this again will have 0 as the default value and allow Nulls. It will also allow negative values. Make the changes and Save. Now from the organization screen click on the <em>Forms </em>tab and edit the <em>Edit Form</em>.</p>
<p><img src="/link/df79bfc37c154f0cb3ebcec727be401f.aspx?id=120798" width="985" alt="Image PaybyCredit-Org3.PNG" height="235" /></p>
<p>Select the <em>Credit Balance</em> field and click on the <em>Remove</em> button. </p>
<p><img src="/link/cc548607ca6d4eb6aee55f65a847a66a.aspx?id=120799" alt="Image PaybyCredit-Org4.PNG" /></p>
<p>Now that these two fields are available you can open an existing organization or create a new one. Set the Credit Limit value to some value like 5000 and save. Note that we don't want to set the Credit Balance as we will be updating this through the payment provider. This is why we removed it from edit view.</p>
<h3>Add a Payment Gateway</h3>
<p>Now we are ready to add some code to the solution. We will utilize the exisitng <em>OtherPayment</em> payment type for our example. If we had some additional payment properties we wanted to set we could create our own derived payment type, for example CreditPayment. The payment gateway code is best if its in a seperate code library as it needs to be referenced by <strong>both</strong> the front end and Commerce Manager sites. We will create a new gateway called <em>CreditLimitPaymentGateway </em>which implements <em>IPaymentGateway. </em></p>
<p>The main method you need to implement is <em>ProcessPayment</em>. This takes the incoming payment and allows you to pass back a message from the payment gateway. Because the credit limit is on the organization we retrieve this through the current contact. The full source below shows a number of validations against whether the credit payment is valid. If valid we update the credit on the account to the organization. </p>
<pre class="language-csharp"><code>public class CreditLimitPaymentGateway : IPaymentGateway
{
public const string CreditLimitKey = "CreditLimit";
public const string CreditBalanceKey = "CreditBalance";
public bool ProcessPayment(Payment payment, ref string message)
{
bool hasPaid = false;
if (payment is OtherPayment)
{
// Get the customer's organisation
Organization organization = CustomerContext.Current.CurrentContact.ContactOrganization;
if (organization != null && !string.IsNullOrWhiteSpace(organization.Name))
{
decimal limit = Convert.ToDecimal(organization[CreditLimitKey]);
decimal balance = Convert.ToDecimal(organization[CreditBalanceKey]);
decimal totalWithPurchase = balance + payment.Amount;
if (limit > 0)
{
if (totalWithPurchase <= limit)
{
// Take payment
if (UpdateCreditToAccount(organization, totalWithPurchase))
{
hasPaid = true;
}
else
{
message = "Unable to add credit to your account.";
}
}
else
{
message = "You have insufficient credit for this purchase.";
}
}
else
{
message = "You do not have a credit limit.";
}
}
else
{
message = "You can only pay by credit on business accounts.";
}
}
else
{
message = "Only Credit Payments can be accepted.";
}
return hasPaid;
}
private bool UpdateCreditToAccount(Organization organization, decimal totalWithPurchase)
{
try
{
organization[CreditBalanceKey] = totalWithPurchase;
organization.SaveChanges();
return true;
}
catch (Exception ex)
{
//Log exception
return false;
}
}
public IDictionary<string, string> Settings
{
get;
set;
}
}</code></pre>
<h3>Configure the Payment Method</h3>
<p>Once this project has been compiled and referenced in both the web and commerce manager project you can start utilising the new Payment gateway. Go into the Administration in Commerce Manager and open up the Order System -> Payments section. Here normally you will choose the language that the payment type is applicable to (usually defaulted to 'English' or your default language) and then click on <em>New</em>. In the next screen you will configure your new Pay by Credit payment method to use the new CreditLimitPaymentGateway and appropriate OtherPayment class. See example below</p>
<p><img src="/link/34790fd47b344912b50d3a7e921b1eba.aspx" width="758" alt="Image Blog-PaymentMethod.JPG" height="377" /></p>
<h3>Wiring it up</h3>
<p>I'm not going to go into detail here too much as how the payment option is displayed on the front end of your checkout page is up to you. If you use the EPiServer Starter Kit or Github OXXAS Starter Kit then these work by retrieving all payment methods for the market and displaying them automatically. You will need to update a bit of code to ensure that an OtherPayment is sent with the appropriate amount, and some handling if the workflow throws and error on the payment method, for example if the credit limit has been reached.</p>
<p>Now the rest is up to you to decide how you could extend this further.</p>
<p>Happy coding!</p>
<pre class="language-csharp"><code></code></pre>Adding custom logic to your publishing step/blogs/Janaka-Fernando/Dates/2015/4/adding-custom-logic-to-your-publishing-step/2015-04-02T10:33:24.0000000Z<div align="center"> <div align="left"> <div align="left"> <p>With EPiServer there are great, easy ways to add validation to your content.  You can easily add DataAnnotations like [Required] to your properties to individually validate them.</p> There is also the <em>IValidator</em>  interface you can implement to handle validation of the Save event.  See the documentation for more info - <a title="http://world.episerver.com/Documentation/Items/Developers-Guide/EPiServer-CMS/7/Validation/Validation/" href="/link/96f6f886c6b84146b1323ad9aa98619b.aspx">http://world.episerver.com/Documentation/Items/Developers-Guide/EPiServer-CMS/7/Validation/Validation/</a></div> <div align="left"> </div> <div align="left">While both these are a simple and effective, one question I am hearing from developers and customers is how can I set up custom business rules for validating who can publish a content item?</div> <div align="left"> </div> <div align="left"> </div> <h3 align="left">Content Events to the Rescue!</h3> <div align="left"> </div> <div align="left">The easiest way here is to use the built in <em>IContentEvents</em> to determine when an event like Content Publishing is triggered.  This applies globally to all sites so I would consider writing this code into an InitializationModule like in the example below.  IContentEvents exposes a number of events, because here we’re interested in <strong>before </strong>content is published, we will use PublishingContent.  All the events have a pre / post event so there is also a <em>PublishedContent </em>event.</div> <div align="left"> </div> <div id="codeSnippetWrapper" style="overflow: auto; cursor: text; font-size: 8pt; border-top: silver 1px solid; font-family: 'Courier New', courier, monospace; border-right: silver 1px solid; width: 97.5%; border-bottom: silver 1px solid; padding-bottom: 4px; direction: ltr; text-align: left; padding-top: 4px; padding-left: 4px; margin: 20px 0px 10px; border-left: silver 1px solid; line-height: 12pt; padding-right: 4px; max-height: 400px; background-color: #f4f4f4"> <div id="codeSnippet" style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white">[InitializableModule]</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> [ModuleDependency(<span style="color: #0000ff">typeof</span>(EPiServer.Web.InitializationModule))]</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> PublishEventInitializationModule : IInitializableModule</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Initialize(InitializationEngine context)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #008000">//Add initialization logic, this method is called once after CMS has been initialized</span></pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>();</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> contentEvents.PublishingContent += contentEvents_PublishingContent;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #0000ff">void</span> contentEvents_PublishingContent(<span style="color: #0000ff">object</span> sender, EPiServer.ContentEventArgs e)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #008000">// Your event code here</span></pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Preload(<span style="color: #0000ff">string</span>[] parameters) { }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Uninitialize(InitializationEngine context)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #008000">//Add uninitialization logic</span></pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> var contentEvents = ServiceLocator.Current.GetInstance<IContentEvents>();</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> contentEvents.PublishingContent -= contentEvents_PublishingContent;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> }</pre>
<!--CRLF--></div>
</div>
</div>
</div>
<p> </p>
<h3>Now the interesting bit</h3>
<p> </p>
<p>Let’s say the customer use case is they wanted someone other than the content <em>Creator</em> to be able to publish.  For example, I can publish your work but not my own.</p>
<p>This isn’t standard functionality and fits well into our scenario.  </p>
<p> </p>
<p>Here’s an example solution to solving this.  Here I am  testing whether the content to be published implements <em>IChangeTrackable </em>which gives us access to the <em>CreatedBy </em>property.  If its the same person as the current user then we want to cancel publishing.</p>
<div id="codeSnippetWrapper" style="overflow: auto; cursor: text; font-size: 8pt; border-top: silver 1px solid; font-family: 'Courier New', courier, monospace; border-right: silver 1px solid; width: 97.5%; border-bottom: silver 1px solid; padding-bottom: 4px; direction: ltr; text-align: left; padding-top: 4px; padding-left: 4px; margin: 20px 0px 10px; border-left: silver 1px solid; line-height: 12pt; padding-right: 4px; max-height: 400px; background-color: #f4f4f4">
<div id="codeSnippet" style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"><span style="color: #0000ff">void</span> contentEvents_PublishingContent(<span style="color: #0000ff">object</span> sender, EPiServer.ContentEventArgs e)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">{</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #008000">// Applies to Pages, Blocks, Media</span></pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #0000ff">if</span> (e.Content <span style="color: #0000ff">is</span> IChangeTrackable)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #0000ff">string</span> author = <span style="color: #0000ff">string</span>.Empty;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> var content = e.Content <span style="color: #0000ff">as</span> IChangeTrackable;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> author = content.CreatedBy;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #0000ff">bool</span> isSameUser = <span style="color: #0000ff">string</span>.Compare(PrincipalInfo.CurrentPrincipal.Identity.Name, author, <span style="color: #0000ff">true</span>) == 0;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #0000ff">if</span> (isSameUser)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> e.CancelAction = <span style="color: #0000ff">true</span>;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> e.CancelReason = <span style="color: #006080">"Unable to publish content created by the same user. Please mark as 'Ready to Publish' and have another user publish"</span>;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">}</pre>
<!--CRLF--></div>
</div>
<p> </p>
<p>The <em>ContentEventArgs</em> <em>e </em>gives us the content item which we can test against.  It also provides us with properties to cancel the action and provide a message to the editor.  Like the validation options mentioned at the start, this hooks in nicely to the editor so they get error messages like this.</p>
<p><a href="/link/eced5f7934804cdf9ba70487b2f636cd.aspx"><img title="ErrorMessage" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="ErrorMessage" src="/link/07f3aff35aa9455cb6ca6b503dcf0c59.aspx" width="881" height="472" /></a></p>
<p> </p>
<h3>Polishing it up</h3>
<p> </p>
<p>So now that the validation code is in place I’d tidy a few things up to make this production ready.  </p>
<p> </p>
<p>To start off I would separate out the concern of the validation into a validator class.  In this example there is only 1 validation but potentially there could be several.  Also importantly this means we can unit test the validator class functionality.  My completed <em>PublishingValidator</em> looks like this.</p>
<div id="codeSnippetWrapper" style="overflow: auto; cursor: text; font-size: 8pt; border-top: silver 1px solid; font-family: 'Courier New', courier, monospace; border-right: silver 1px solid; width: 97.5%; border-bottom: silver 1px solid; padding-bottom: 4px; direction: ltr; text-align: left; padding-top: 4px; padding-left: 4px; margin: 20px 0px 10px; border-left: silver 1px solid; line-height: 12pt; padding-right: 4px; max-height: 450px; background-color: #f4f4f4">
<div id="codeSnippet" style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> PublishingValidator : IPublishingValidator </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">{</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span> IsTheSameCreator(IContent content, <span style="color: #0000ff">string</span> authorName)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">bool</span> isSameUser = <span style="color: #0000ff">false</span>;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">if</span> (content <span style="color: #0000ff">is</span> IChangeTrackable)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">string</span> author = <span style="color: #0000ff">string</span>.Empty;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> var tracked = content <span style="color: #0000ff">as</span> IChangeTrackable;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> author = tracked.CreatedBy;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> isSameUser = <span style="color: #0000ff">string</span>.Compare(authorName, author, <span style="color: #0000ff">true</span>) == 0;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #0000ff">return</span> isSameUser;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">}</pre>
<!--CRLF--></div>
</div>
<p> </p>
<p>My InitializationModule will now use this class through the <em>ServiceLocator</em>.  I registered <em>IPublishingValidator</em> in my dependency resolver initialization.  Finally I will add the <em>LocalizationService </em>to make the error message localized to the editor’s language.  Here is the final code for the PublishingContent event handler.</p>
<div id="codeSnippetWrapper" style="overflow: auto; cursor: text; font-size: 8pt; border-top: silver 1px solid; font-family: 'Courier New', courier, monospace; border-right: silver 1px solid; width: 97.5%; border-bottom: silver 1px solid; padding-bottom: 4px; direction: ltr; text-align: left; padding-top: 4px; padding-left: 4px; margin: 20px 0px 10px; border-left: silver 1px solid; line-height: 12pt; padding-right: 4px; max-height: 350px; background-color: #f4f4f4">
<div id="codeSnippet" style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"><span style="color: #0000ff">void</span> contentEvents_PublishingContent(<span style="color: #0000ff">object</span> sender, EPiServer.ContentEventArgs e)</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">{</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> var localizationService = ServiceLocator.Current.GetInstance<LocalizationService>();</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> var validator = ServiceLocator.Current.GetInstance<IPublishingValidator>();</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> </pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> <span style="color: #008000">// Applies to Pages, Blocks, Media</span></pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> <span style="color: #0000ff">if</span> (validator.IsTheSameCreator(e.Content, PrincipalInfo.CurrentPrincipal.Identity.Name))</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> {</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> e.CancelAction = <span style="color: #0000ff">true</span>;</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4"> e.CancelReason = localizationService.GetString(<span style="color: #006080">"/ValidationErrors/SameCreator"</span>);</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: white"> }</pre>
<!--CRLF-->
<pre style="border-top-style: none; overflow: visible; font-size: 8pt; font-family: 'Courier New', courier, monospace; width: 100%; border-bottom-style: none; color: black; padding-bottom: 0px; direction: ltr; text-align: left; padding-top: 0px; border-right-style: none; padding-left: 0px; margin: 0em; border-left-style: none; line-height: 12pt; padding-right: 0px; background-color: #f4f4f4">}</pre>
<!--CRLF--></div>
</div>Adding Canonical support to EPiServer sites/blogs/Janaka-Fernando/Dates/2013/3/Adding-Canonical-support-to-EPiServer-sites/2013-03-13T11:19:44.0000000Z<h2>Avoiding Duplicate Content</h2> <p>I think everyone loves the Simple Address feature in EPiServer CMS to easily set up friendly URLs.  If you haven't come across it, Simple Address lets you set a simple URL that will come after the site domain.  This is useful if you have a long URL to the page and want to print or send a short URL  - for example a landing page that you want to direct traffic to.</p> <p>So for the example here I would have two URLs</p> <p><a href="http://janaka.uk.episerver.com/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/">http://janaka.uk.episerver.com/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/</a></p> <p>simply becomes</p> <p><a href="http://janaka.uk.episerver.com/Forward/">http://janaka.uk.episerver.com/Forward/</a> <br /></p> <p>Now while this is great, it produces two unique URLs with identical content... a big no-no with Google page ranking. To avoid getting penalized for this, you need to set the canonical link to let the search engine know which URL is the primary source of this content to index. </p> <p>Setting a canonical URL is pretty simple, its a single <link> tag that needs to go into your <head> tag.  So for the above example we'd simply need either of the following:</p> <p>Absolute <link rel="canonical" href="<a href="http://janaka.uk.episerver.com/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/"">http://janaka.uk.episerver.com/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/"</a> /> <br />or <br />Relative <link rel="canonical" href="/About-us/News-Events/Press-Releases/Alloy-pays-it-forward/" /> </p> <p> </p> <h2>Adding Canonicalization Support to EPiServer</h2> <p>So we need to add a canonical link whenever a page has a Simple Address.  I'm using the new Alloy sample code base for my examples here.  I want to modify the <head> section so looking at the existing code this is best achieved through the master page base class - SiteMasterPage</p> <p>Below is an excerpt from the SiteMasterPage.cs file with my modifications</p> <p> </p> <div class="csharpcode"> <pre class="language-csharp"><code><span class="kwrd">public</span> <span class="kwrd">abstract</span> <span class="kwrd">class</span> SiteMasterPage : System.Web.UI.MasterPage</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnLoad(System.EventArgs e)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">base</span>.OnLoad(e);</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (Master != <span class="kwrd">null</span>)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">return</span>;</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> SetupMetaTags();</code></pre>
<pre class="language-csharp"><code> SetupCanonicalURL();</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">private</span> <span class="kwrd">void</span> SetupCanonicalURL()</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="rem">// Add canonical url meta tag for simple address</span></code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (!<span class="kwrd">string</span>.IsNullOrEmpty(CurrentPage.ExternalURL) &&</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">string</span>.Compare(CurrentPage.ExternalURL, </code></pre>
<pre class="language-csharp"><code> Request.Url.Segments.Last().Replace(<span class="str">"/"</span>, <span class="str">""</span>), <span class="kwrd">true</span>) == 0)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> var urlBuilder = <span class="kwrd">new</span> UrlBuilder(CurrentPage.LinkURL);</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (EPiServer.Global.UrlRewriteProvider.ConvertToExternal(urlBuilder, </code></pre>
<pre class="language-csharp"><code> CurrentPage.ContentLink,</code></pre>
<pre class="language-csharp"><code> System.Text.Encoding.Default))</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">string</span> sourceUrl = urlBuilder.Uri.ToString();</code></pre>
<pre class="language-csharp"><code> var canonicalLink = <span class="kwrd">new</span> HtmlLink();</code></pre>
<pre class="language-csharp"><code> canonicalLink.Attributes.Add(<span class="str">"rel"</span>, <span class="str">"canonical"</span>);</code></pre>
<pre class="language-csharp"><code> canonicalLink.Href = sourceUrl;</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> Page.Header.Controls.Add(canonicalLink);</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code>}</code></pre>
</div>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p>Some points to note here.  The API provides a property <em>CurrentPage.ExternalURL</em> which will have the simple address.  If this is present I then check if this is the current URL ( as opposed to the long name URL ).  Finally the rest if fairly straightforward, using the UrlBuilder to construct the external link to the page, then writing this to the Link.  Note that in this example I am using relative links, because <em>CurrentPage.LinkURL</em> is relative, but you could build up the full path and use absolute links using Uri.AbsoluteUri.
<br /></p>
<h2>Language Fallback & Replacement</h2>
<p>In addition to the Simple Address, another place to provide automatic support for canonical URLs is when using language replacement or fallback.  In this case the URL will be different depending on the language, but the site will contain duplicated content.  </p>
<p>I've refactored the previous code sample and added a second condition to the SiteMasterPage <em>SetupCanonicalLink</em> method, checking if the current page's language is different from what is expected.  If it's different I load the source content and update the <em>LinkURL</em> to indicate the source language.  </p>
<p> </p>
<div class="csharpcode">
<pre class="language-csharp"><code><span class="kwrd">private</span> <span class="kwrd">void</span> SetupCanonicalURL()</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="rem">// Add canonical url meta tag for simple address</span></code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (!<span class="kwrd">string</span>.IsNullOrEmpty(CurrentPage.ExternalURL) &&</code></pre>
<pre class="language-csharp"><code><p> <span class="kwrd">string</span>.Compare(CurrentPage.ExternalURL, </p><p> Request.Url.Segments.Last().Replace(<span class="str">"/"</span>, <span class="str">""</span>), <span class="kwrd">true</span>) == 0)</p></code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> var urlBuilder = <span class="kwrd">new</span> UrlBuilder(CurrentPage.LinkURL);</code></pre>
<pre class="language-csharp"><code> WriteCanonicalLink(urlBuilder);</code></pre>
<pre class="language-csharp"><code> } </code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="rem">// Add canonical url meta tag for language fallback/replacement </span></code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (CurrentPage.Language != ContentLanguage.PreferredCulture)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> var sourcePage = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<IContentRepository>()</code></pre>
<pre class="language-csharp"><code> .Get<PageData>(CurrentPage.ContentLink, </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">new</span> LanguageSelector(CurrentPage.Language.Name));</code></pre>
<pre class="language-csharp"><code> var sourceUrl = UriSupport.AddLanguageSelection(sourcePage.LinkURL, CurrentPage.Language.Name);</code></pre>
<pre class="language-csharp"><code> var sourceUrlBuilder = <span class="kwrd">new</span> UrlBuilder(sourceUrl);</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> WriteCanonicalLink(sourceUrlBuilder);</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">private</span> <span class="kwrd">void</span> WriteCanonicalLink(UrlBuilder url)</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> <span class="kwrd">if</span> (EPiServer.Global.UrlRewriteProvider.ConvertToExternal(url, CurrentPage.ContentLink, System.Text.Encoding.Default))</code></pre>
<pre class="language-csharp"><code> {</code></pre>
<pre class="language-csharp"><code> <span class="kwrd">string</span> sourceUrl = url.Uri.ToString();</code></pre>
<pre class="language-csharp"><code> var canonicalLink = <span class="kwrd">new</span> HtmlLink();</code></pre>
<pre class="language-csharp"><code> canonicalLink.Attributes.Add(<span class="str">"rel"</span>, <span class="str">"canonical"</span>);</code></pre>
<pre class="language-csharp"><code> canonicalLink.Href = sourceUrl;</code></pre>
<pre class="language-csharp"><code> </code></pre>
<pre class="language-csharp"><code> Page.Header.Controls.Add(canonicalLink);</code></pre>
<pre class="language-csharp"><code> }</code></pre>
<pre class="language-csharp"><code> }</code></pre>
</div>
<style type="text/css">
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }</style>
<p> </p>
<h3>Further reading:</h3>
<p><a href="http://www.seomoz.org/blog/canonical-url-tag-the-most-important-advancement-in-seo-practices-since-sitemaps">http://www.seomoz.org/blog/canonical-url-tag-the-most-important-advancement-in-seo-practices-since-sitemaps</a></p>
<p><a href="http://www.mattcutts.com/blog/seo-advice-url-canonicalization/">http://www.mattcutts.com/blog/seo-advice-url-canonicalization/</a></p>
<p><a href="http://support.google.com/webmasters/bin/answer.py?hl=en&answer=139394">http://support.google.com/webmasters/bin/answer.py?hl=en&answer=139394</a></p>