Blog posts by Karoline Klever2015-02-25T09:00:00.0000000Z/blogs/Karoline-Klever/Optimizely WorldThe PayEx payment provider for EPiServer Commerce is now public! http://www.karolikl.com/2015/02/the-payex-payment-provider-for.html2015-02-25T09:00:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">I'm glad to announce that the new PayEx payment provider for EPiServer Commerce is now available in the <a href="http://nuget.episerver.com/en/" target="_blank">EPiServer NuGet feed</a> and and the source code is available on <a href="https://github.com/PayEx/PayEx.EPi.Commerce.Payment" target="_blank">GitHub</a>. The payment provider is called PayEx.EPi.Commerce.Payment and the full documentation is available on <a href="https://github.com/PayEx/PayEx.EPi.Commerce.Payment#payex-payment-provider" target="_blank">GitHub</a>.<br /><br /><div><b>Supported payment methods</b></div>The provider supports several of the PayEx payment methods:<br /><ul><li><a href="http://www.payexpim.com/payment-methods/credit-cards/" target="_blank">Credit Cards</a></li><li><a href="http://www.payexpim.com/payment-methods/direct-bank-debit/" target="_blank">Direct Bank Debit</a></li><li><a href="http://www.payexpim.com/payment-methods/paypal/" target="_blank">PayPal</a></li><li><a href="http://www.payexpim.com/payment-methods/invoice/" target="_blank">PayEx Invoice</a></li><li><a href="http://www.payexpim.com/payment-methods/payex-faktura-2-0/" target="_blank">PayEx Invoice 2.0</a></li><li><a href="http://www.payexpim.com/payment-methods/gift-cards-generic-cards/" target="_blank">Gift Cards</a></li><li><a href="http://www.payexpim.com/payment-methods/payex-part-payment/" target="_blank">PayEx Part Payment</a></li></ul><div><b>Prerequisites</b></div><div>The prerequisites for the provider are the following: </div><div><ul><li>EPiServer.CMS version 7.6.3 or higher</li><li>EPiServer.Commerce version 7.6.1 or higher</li><li>.NET Framework 4.5 or higher</li></ul><div>Enjoy!</div></div></div>Coming soon: A PayEx payment provider for EPiServer Commercehttp://www.karolikl.com/2015/01/coming-soon-payex-payment-provider-for.html2015-01-12T10:13:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">One of the projects I've been working on lately is a new PayEx payment provider for EPiServer Commerce and I'm very excited to announce that the provider will be available very soon!<br /><br /><div><b>Supported payment methods</b></div>In its first version, the provider will support several of the PayEx payment methods:<br /><ul style="text-align: left;"><li><a href="http://www.payexpim.com/payment-methods/credit-cards/" target="_blank">Credit Cards</a></li><li><a href="http://www.payexpim.com/payment-methods/direct-bank-debit/" target="_blank">Direct Bank Debit</a></li><li><a href="http://www.payexpim.com/payment-methods/paypal/" target="_blank">PayPal</a></li><li><a href="http://www.payexpim.com/payment-methods/invoice/" target="_blank">PayEx Invoice</a></li><li><a href="http://www.payexpim.com/payment-methods/payex-faktura-2-0/" target="_blank">PayEx Invoice 2.0</a></li><li><a href="http://www.payexpim.com/payment-methods/gift-cards-generic-cards/" target="_blank">Gift Cards</a></li><li><a href="http://www.payexpim.com/payment-methods/payex-part-payment/" target="_blank">PayEx Part Payment</a></li></ul><div><b>Prerequisites</b></div><div>The prerequisites for the provider are the following: </div><div><ul style="text-align: left;"><li>EPiServer.CMS version 7.6.3 or higher</li><li>EPiServer.Commerce version 7.6.1 or higher</li><li>.NET Framework 4.5 or higher</li></ul><div><b>How does the payment provider work? </b></div></div><div>When developing this payment provider, I've focused on simple installation and extensibility. Looking at the install instructions of the other payment providers available for EPiServer Commerce, I found that they all require lots of copying files from one folder to another, manual labor that will result in the provider not working as expected if you make a mistake. To avoid this issue in the PayEx payment provider, anything that could be automated has been automated. And needless to say, new payment methods should be easy to add in the future. </div><div>The PayEx payment provider consists of two NuGet packages which will be available from the <a href="https://nuget.episerver.com/en/Feed/" target="_blank">EPiServer NuGet feed</a>, one will be added to your web project and one will be added to your Commerce Manager. </div><div>During initialization, the PayEx payment provider will check whether the required MetaClasses and MetaFields exist. If they don't exist, they will be created for you automatically. An instance of all the available payment methods will also be created for you, so you simply need to activate the ones you want to use. </div><div>After including your PayEx account information in the module settings for the payment provider, you will be all set. You will need to write some code to handle transaction callbacks etc., but this should be fairly straight forward as example code will be included. </div><div>So stay posted, I'll let you know as soon as the payment provider is made public! </div></div>Buttons missing from Commerce Manager in Internet Explorerhttp://www.karolikl.com/2014/12/buttons-missing-from-commerce-manager.html2014-12-08T12:37:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">I recently encountered an issue with all buttons missing from the Commerce Manager when running it locally in Internet Explorer:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-k-MI9xqn6S4/VIWMEz6EUII/AAAAAAAAAks/OfM_c_FdU5A/s1600/NoButtonsInManagr.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-k-MI9xqn6S4/VIWMEz6EUII/AAAAAAAAAks/OfM_c_FdU5A/s1600/NoButtonsInManagr.PNG" height="323" width="640" /></a></div><br />Deploying the Manager to any other environment such as test or production, the buttons appeared as they should. I also had this issue for several different Commerce Managers, so it was quite clear that the problem had something to do with my machine, specifically Internet Explorer as it worked fine in all other browsers.<br /><br /><b>Problem:</b><br /><br />Internet Explorer had been set to display all intranet sites in Compatibility View, and apparently the Commerce Manager was viewed by IE as an intranet.<br /><br /><b>Solution</b>:<br /><br />Tools -> Compatibility View Settings -> Uncheck "Display intranet sites in Compatibility View" and all your buttons will appear.<br /><br /><br /></div>Generate an order number for your cart in EPiServer Commercehttp://www.karolikl.com/2014/10/generate-order-number-for-your-cart-in.html2014-10-29T09:36:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">Many payment services, such as PayEx, require that you pass in an order number when processing payments. This makes it very easy to track payments as you instantly know which order the payment belongs to. At the same time, if you don't want to generate an order before the payment has been processed, where does this order number come from? In this blog post, I'll show you how to can generate an order number for your Cart and then later generate a purchase order with the same order number.<br /><br />The Cart in EPiServer Commerce has a CreateOrderNumber delegate you can use to define how the order number for your PurchaseOrder will be generated. EPiServer has a great overview of how this works in their <a href="/link/cabda692b23b4c9180ea195156b9f540.aspx" target="_blank">Changing order number sequence article</a>. As the article states, the method you define for the CreateOrderNumber delegate is run in the SaveAsPurchaseOrder method, but we don't want to run this method until after the payment is already processed. So what do we do?<br /><br />We'll still use the CreateOrderNumber delegate, but we've added a lambda expression:<br /><br /><pre class="language-csharp"><code>public void BeforeProcessingPayment(Cart cart)<br />{<br /> var orderNumber = GenerateOrderNumber(cart.OrderGroupId);<br /> cart.OrderNumberMethod = c => orderNumber;<br /><br /> // Save cart and process payment, passing in the required orderNumber<br />}<br /><br />// This is the default order number generator in EPiServer Commerce, <br />// you can change this around if you want a different format<br />private string GenerateOrderNumber(int orderGroupId)<br />{<br /> string str = new Random().Next(1000, 9999).ToString();<br /> return string.Format("PO{0}{1}", orderGroupId, str);<br />}<br /></code></pre><br />In the BeforeProcessingPayment method, we're generating an order number and using a lambda expression to set the OrderNumberMethod delegate to this value. This means that you can process the payment and call cart.SaveAsPurchaseOrder() after the payment has successfully been processed. The PurchaseOrder will then receive the same order number as you generated for your Cart. <br /><br />Now, that wasn't too hard, was it?</div>How to hide SeoInformation in EPiServer Commercehttp://www.karolikl.com/2014/10/how-to-hide-seoinformation-in-episerver.html2014-10-08T08:31:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">When working with EPiServer Commerce you will see that all products, variants and categories automatically will receive a block in the Information tab called "SeoInformation". In several of our projects we don't want to display this block, we'd rather create our own SEO properties.<br /><br /><div>The SeoInformation block comes from the ISearchEngineInformation interface that all the following classes implement: NodeContent, BundleContent, PackageContent, ProductContent and VarianContent. So if you don't want the SeoInformation property to be displayed on any of your products, variants and categories, you will need to override the property on all your classes inheriting from ProductContent, VariationContent and NodeContent. </div><div>For example: </div><div><pre class="language-csharp"><code>public abstract class BaseProductData : ProductContent<br />{<br /> [ScaffoldColumn(false)]<br /> public override SeoInformation SeoInformation { get; set; }<br />}<br /></code></pre></div><br /><div>Now, you might be tempted to add an Ignore attribute to the property as well, but if you do that EPiServer will give the the following error when trying to publish: "Property with name 'SeoInformation' is not part of the ContentType definition"<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-VRlszJ44u48/VDOm4cnwD5I/AAAAAAAAAiI/vaqlo7XREx8/s1600/SeoInformation.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-VRlszJ44u48/VDOm4cnwD5I/AAAAAAAAAiI/vaqlo7XREx8/s1600/SeoInformation.PNG" height="154" width="640" /></a></div><br />Which of course, is logical as EPiServer won't find the SeoInformation property on the ContentType model. So if you find yourself receiving this error message, make sure you're not using the Ignore attribute on the property in question.<br /><br /><br /></div></div>Find purchase orders by an OrderForm meta field in EPiServer Commercehttp://www.karolikl.com/2014/09/find-purchase-orders-by-orderform-meta.html2014-09-24T11:26:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">If you've added a meta field to an OrderForm, you can use the <span style="font-family: Courier New, Courier, monospace;">OrderContext.Current.FindPurchaseOrders</span> method to find all purchase orders with a given value in that meta field. However, figuring out what the <span style="font-family: Courier New, Courier, monospace;">SqlWhereClause</span> should be might not be completely straight forward, so I thought I'd share a solution.<br /><br />I've added a meta field to the OrderForm called OrderStatus. This meta field will then show up as a new column in the OrderFormEx table in the Commerce database:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-ufOaupp7A40/VCKMx_cGyiI/AAAAAAAAAhs/ZKtf2aLZKnk/s1600/sql.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-ufOaupp7A40/VCKMx_cGyiI/AAAAAAAAAhs/ZKtf2aLZKnk/s1600/sql.PNG" height="320" width="287" /></a></div><br /><br />What if I want to find all purchase orders where OrderStatus is NULL? The following code would do the trick:<br /><br /><pre class="language-csharp"><code>var searchOptions = new OrderSearchOptions<br />{<br /> CacheResults = false,<br /> Classes = new StringCollection { "PurchaseOrder" },<br /> Namespace = "Mediachase.Commerce.Orders"<br />};<br /><br />OrderSearchParameters parameters = new OrderSearchParameters()<br />{<br /> SqlWhereClause = "OrderGroupId IN (SELECT OrderGroupId FROM OrderForm WHERE OrderFormId IN (SELECT ObjectId FROM OrderFormEx WHERE OrderStatus IS NULL))"<br />};<br /><br />PurchaseOrder[] purchaseOrderCollection = OrderContext.Current.FindPurchaseOrders(parameters, searchOptions);<br /></code></pre><br />If you want to know more about how this works, you should check out <a href="http://world.episerver.com/Blogs/Shannon-Gray/Dates/2012/12/EPiServer-Commerce-Order-Search-Made-Easy/" target="_blank">EPiServer Commerce Order Search Made Easy</a> (not sure I agree on the "made easy" part, but that's a different story).<br /><br />Hopefully this will save you some time! </div>Using StructureMap with named instances in EPiServerhttp://www.karolikl.com/2014/09/using-structuremap-with-named-instances.html2014-09-18T12:10:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">If you have several classes inheriting from the same interface, and you want your business logic to choose the correct class based on a criteria, you should check out named instances in StructureMap.<br /><br />For example, let's say you have the following classes:<br /><br /><span style="font-family: Courier New, Courier, monospace;">public class Facebook : IAuthenticationStrategy</span><br /><span style="font-family: Courier New, Courier, monospace;">public class Google : IAuthenticationStrategy</span><br /><span style="font-family: Courier New, Courier, monospace;">public class Forms : IAuthenticationStrategy</span><br /><br />We have three implementations of IAuthenticationStrategy and the role of these classes is to validate the authentication credentials for Facebook, Google and Forms authentication.<br /><br />Now, let's define the criteria as an enum:<br /><pre class="language-csharp"><code>public enum AuthenticationType<br />{<br /> Facebook,<br /> Forms,<br /> Google<br />}<br /></code></pre><br />If the user selects Facbook during login, the business logic validating the authentication will receive <span style="font-family: Courier New, Courier, monospace;">AuthenticationType.Facebook. </span>If Google is selected, <span style="font-family: Courier New, Courier, monospace;">AuthenticationType.Google</span> is used, and so on.<br /><br />How can we use named instances in StructureMap to wire all this up? Let's define all our instances of <span style="font-family: Courier New, Courier, monospace;">IAuthenticationStrategy </span>in our container:<br /><br /><pre class="language-csharp"><code>container.Configure(<br /> x =><br /> {<br /> x.For<IAuthenticationStrategy>().AddInstances(i =><br /> {<br /> i.Type<Facebook>().Named(AuthenticationType.Facebook.ToString());<br /> i.Type<Google>().Named(AuthenticationType.Google.ToString());<br /> i.Type<Forms>().Named(AuthenticationType.Forms.ToString());<br /> });<br />});<br /></code></pre><br />Now all we have to do is create a Factory class passing in the <span style="font-family: Courier New, Courier, monospace;">AuthenticationType </span>the user has selected:<br /><br /><pre class="language-csharp"><code>public class AuthenticationFactory : IAuthenticationFactory<br />{<br /> public IAuthenticationStrategy Create(AuthenticationType type)<br /> {<br /> return ServiceLocator.Current.GetInstance(typeof(IAuthenticationStrategy), type.ToString()) as IAuthenticationStrategy;<br /> }<br />}<br /></code></pre><br />Last, but not least: Here's an example of how you could use the <span style="font-family: Courier New, Courier, monospace;">AuthenticationFactory</span> to receive the correct instance:<br /><br /><pre class="language-csharp"><code>public UserAuthentication ServerValidate(UserAuthentication userAuthentication)<br />{<br /> IAuthenticationStrategy authentication = _authenticationFactory.Create(userAuthentication.Type);<br /> return authentication.ServerValidate(userAuthentication);<br />}<br /></code></pre><br />Simple, right?<br /><br /></div>Multiple CustomerContacts created in EPiServer Commercehttp://www.karolikl.com/2014/08/multiple-customercontacts-created-in.html2014-08-29T06:39:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">Creating a CustomerContact from a MembershipUser is something you'll run into quite quick when developing a Commerce site. Now, what's wrong with this piece of code?<br /><br /><pre class="language-csharp"><code>public CustomerContact CreateUser(MembershipUser user, string firstname, string lastname)<br />{<br /> CustomerContact customerContact = CustomerContact.CreateInstance(user);<br /><br /> SecurityContext.Current.AssignUserToGlobalRole(user, AppRoles.EveryoneRole);<br /> SecurityContext.Current.AssignUserToGlobalRole(user, AppRoles.RegisteredRole);<br /><br /> customerContact.FullName = string.Join(" ", firstname, lastname);<br /> customerContact.FirstName = firstname;<br /> customerContact.LastName = lastname;<br /> customerContact.Email = user.Email;<br /> return customerContact;<br />}<br /></code></pre><br />To me, this piece of code looked fine until I ran it. What happened was that I received two CustomerContacts, one containing the correct information such as firstname, lastname and fullname. The other CustomerContact only contained the email address, nothing else. Both of them were linked to the correct account.<br /><br />So what happened? Well, according to the <a href="http://world.episerver.com/Documentation/Class-library/?documentId=commerce/7.5/97EBD3DD" target="_blank">EPiServer Commerce SDK</a> we need to save the CustomerContact before calling <span style="font-family: Courier New, Courier, monospace;">AssignUserToGlobalRole</span>: <i>"The CustomerContact associated with the user must be saved before calling this method. Otherwise contact information might be lost." </i><br /><i><br /></i>In this case, contact information was not lost, it was duplicated. So what's the correct way of doing it then? We have to call <span style="font-family: Courier New, Courier, monospace;">SaveChanges </span>on the CustomerContact before assigning the roles:<br /><br /><pre class="language-csharp"><code>public CustomerContact CreateUser(MembershipUser user, string firstname, string lastname)<br />{<br /> CustomerContact customerContact = CustomerContact.CreateInstance(user);<br /> customerContact.SaveChanges();<br /> <br /> SecurityContext.Current.AssignUserToGlobalRole(user, AppRoles.EveryoneRole);<br /> SecurityContext.Current.AssignUserToGlobalRole(user, AppRoles.RegisteredRole);<br /><br /> customerContact.FullName = string.Join(" ", firstname, lastname);<br /> customerContact.FirstName = firstname;<br /> customerContact.LastName = lastname;<br /> customerContact.Email = user.Email;<br /> return customerContact;<br />}<br /></code></pre><br /></div>When you're not allowed to translate blocks in EPiServer 7http://www.karolikl.com/2014/06/when-youre-not-allowed-to-translate.html2014-06-30T13:58:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on"><div class="separator" style="clear: both; text-align: left;">If you find yourself not able to translate blocks in EPiServer 7, you might not have marked the language you're working with as available. </div><div class="separator" style="clear: both; text-align: left;">The block you want to translate will look like this, as you can see the translate button is disabled. </div><div class="separator" style="clear: both; text-align: left;"><a href="http://4.bp.blogspot.com/-Tw-0CXoqzvQ/U7FOcbLO1GI/AAAAAAAAAcI/-euywC3WH_Q/s1600/Capture.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-Tw-0CXoqzvQ/U7FOcbLO1GI/AAAAAAAAAcI/-euywC3WH_Q/s1600/Capture.PNG" height="31" width="640" /></a></div><br /><b><br /></b><b><br /></b><b>How to set the language as available: </b><br /><br />1) Go to the startpage<br /><br />2) Click on "Tools" -> "Language settings":<br /><br /><div class="separator" style="clear: both; text-align: left;"><a href="http://1.bp.blogspot.com/-vbNaYUzOzso/U7FPyV8kA5I/AAAAAAAAAcU/JkgTZCjdHbs/s1600/Capture.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-vbNaYUzOzso/U7FPyV8kA5I/AAAAAAAAAcU/JkgTZCjdHbs/s1600/Capture.PNG" height="192" width="640" /></a></div><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />3) Select the language:<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-T9L3wcnn8Aw/U7FQBrC9YeI/AAAAAAAAAcc/eVUXosq16Jo/s1600/Capture.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-T9L3wcnn8Aw/U7FQBrC9YeI/AAAAAAAAAcc/eVUXosq16Jo/s1600/Capture.PNG" /></a></div><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />Now, if you return to your block, you'll see that the translate button is enabled and you can translate the block:<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-Nvti9Jeef8Q/U7FQcVJGFrI/AAAAAAAAAck/pnlbn-RAeHg/s1600/Capture.PNG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-Nvti9Jeef8Q/U7FQcVJGFrI/AAAAAAAAAck/pnlbn-RAeHg/s1600/Capture.PNG" height="48" width="640" /></a></div><br /></div>Behind the scenes of "Release shipment" in EPiServer Commercehttp://www.karolikl.com/2014/06/behing-scenes-of-release-shipment-in.html2014-06-22T21:00:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">Working with EPiServer Commerce, one thing you quickly realize is that you spend more time debugging than you do developing. And some of the bugs can be a real pain to figure out.<br /><br />I ran into a bug last week where nothing happened when the customer clicked on "Release Shipment" on the order. They clicked the button, and not a thing happened.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-grn5Vw6DgPQ/U6cgJW71REI/AAAAAAAAAb4/0Xmw4lHi_3c/s1600/ReleaseShipment.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-grn5Vw6DgPQ/U6cgJW71REI/AAAAAAAAAb4/0Xmw4lHi_3c/s1600/ReleaseShipment.PNG" height="300" width="640" /></a></div><br />The only error i found was a javascript error <i>"Workflow generated an exception, please look at the previous error for more details". </i>However, there was no "previous error", so I had no way of knowing what was failing and there was nothing in the logs...<br /><br />Decompiling the Mediachase.Commerce assemblies, I found that the method called when you click "Release Shipment" is <span style="font-family: Courier New, Courier, monospace;">DoCommand(...) </span>in <span style="font-family: Courier New, Courier, monospace;">Mediachase.Commerce.Manager.Order.CommandHandlers.PurchaseOrderHandlers.ReleaseShipmentHandler.</span><span style="font-family: inherit;"> This method validates a couple of things such as the authorized payment amount, that the item is in stock etc. The most important part, however, is that the method changes the status of the order to be "Released" by calling the </span><span style="font-family: Courier New, Courier, monospace;">ReleaseOrderShipment(...) </span>method in <span style="font-family: Courier New, Courier, monospace;">Mediachase.Commerce.Orders.Managers</span>. After the status is changed, the RecalculatePurchaseOrderWorkflow is called (Note: I've also seen this workflow being called by the name PurchaseOrderRecalculateWorkflow, just to make the confusion complete).<br /><br />So what does the RecalculatePurchaseOrderWorkflow / PurchaseOrderRecalculateWorkflow do? If you're using the standard workflows, it contains the following activities:<br /><ul style="text-align: left;"><li>ValidateLineItemsActivity</li><li>GetFulfillmentWarehouseActivity</li><li>CheckInventoryActivity</li><li>ProcessShipmentsActivity</li><li>RemoveDiscountsActivity</li><li>CalculateTotalsActivity</li><li>CalculateDiscountsActivity</li><li>CalculateTaxActivity</li><li>CalculateTotalsActivity</li><li>CalculatePurchaseOrderStatusActivity</li></ul>In my case, the CalculateDiscountsActivity was missing a null check, and that was what caused all the trouble. How did I find out which activity was the troublemaker? I added logging to the catch block of the try/catch of all the Execute methods in the activites, and retrieved the error message from the log.<br /><div><br /></div><div>I cannot believe the amount of exceptions being swallowed by the Commerce workflows. Any developer working with Commerce projects should make sure they go through all the activities and add some logging. It will make your life a lot easier in the future! </div></div>Debugging workflows in EPiServer Commerce 7.6http://www.karolikl.com/2014/04/debugging-workflows-in-episerver.html2014-04-02T19:49:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on"><div dir="ltr" style="text-align: left;" trbidi="on">Today I've had the pleasure of debugging workflows in EPiServer Commerce. In the project I'm working on, we've downloaded the <a href="http://world.episerver.com/Download/Items/EPiServer-Commerce/EPiServer-75-Commerce-tools-and-code-samples/">Workflow activities samles</a> and customized them to meet our customers need. This worked fine until we upgraded our project to EPiServer 7.6, but after the upgrade strange things started happening.<br /><br />The first error that occured was a compile error in the Mediachase.Commerce.Workflow project:<br /><br /><i><b>Could not load file or assembly 'EPiServer.Framework, Version=7.5.394.2</b></i><br /><i><br /></i>Now, that's strange, seeing as I just upgraded the project to EPiServer 7.6 and there exists no references to EPiServer 7.5 anywhere. Trying to find any clues, I ran across the following error when trying to open a couple of the workflows:<br /><br /><i><b>Error loading workflow. Could not deserialize object. The type 'http://schemas.mediachase.com/ecf/50/activities.ProcessPaymentActivity' could not be resolv</b></i><b>ed</b>. <br /><br />Now, this error was a lot easier to debug than the first one! Clearly, there's something wrong with the namespace in the workflow as the ProcessPaymentActivity is one of the activities in the Mediachase.Commerce.Workflow.Activities project. Taking a look at the .xoml of the workflow this is what I found:<br /><br /><pre class="language-html"><code><SequentialWorkflowActivity x:Class="Mediachase.Commerce.Workflow.CartCheckoutWorkflow" x:Name="CartCheckoutWorkflow" xmlns:ns0="clr-namespace:Mediachase.Commerce.Workflow.Activities;Assembly=Mediachase.Commerce.Workflow.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:ns1="http://schemas.mediachase.com/ecf/50/activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"><br /> <IfElseActivity x:Name="ifElseActivity1"><br /> <IfElseBranchActivity x:Name="ifElseBranchActivity1"><br /> <IfElseBranchActivity.Condition><br /> <CodeCondition Condition="RunProcessPayment" /><br /> </IfElseBranchActivity.Condition><br /> <ns1:ProcessPaymentActivity Warnings="{x:Null}" OrderGroup="{ActivityBind CartCheckoutWorkflow,Path=OrderGroup}" Payment="{x:Null}" x:Name="processPaymentActivity1" /><br /> </IfElseBranchActivity><br /> </IfElseActivity><br /> <ns0:CalculateTotalsActivity x:Name="calculateTotalsActivity1" OrderGroup="{ActivityBind CartCheckoutWorkflow,Path=OrderGroup}" Warnings="{x:Null}" /><br /><ns1:RecordPromotionUsageActivity UsageStatus="Used" Warnings="{x:Null}" OrderGroup="{ActivityBind CartCheckoutWorkflow,Path=OrderGroup}" x:Name="recordPromotionUsageActivity1" /><br /></code></pre><br /><br />If you take a look at the reference to ProcessPaymentActivity on line 7, you can see that it's prefixed with ns1, meaning it's in the namespace declared by xmls:ns1 attribute on line 1. If you've been paying attention, you will also see that the namespace "http://schemas.mediachase.com/ecf/50/activities" is, in fact, the namespace in the error message we received. Change the namespace, and voilá! The compile error complaining about EPiServer.Framework 7.5 not being referenced disappears and you'll be able to open your workflows again:<br /><br /><b>Old namespace:</b><br /><pre class="language-csharp"><code>xmlns:ns1="http://schemas.mediachase.com/ecf/50/activities"</code></pre><br /><b>New namespace (this might be different in your project)</b>:<br /><pre class="language-csharp"><code>xmlns:ns1="clr-namespace:Mediachase.Commerce.Workflow.Activities;Assembly=Mediachase.Commerce.Workflow.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:ns1="clr-namespace:Mediachase.Commerce.Workflow.Activities.Cart;Assembly=Mediachase.Commerce.Workflow.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"</code></pre><br />While debugging the workflows, I also ran across another bug worth mentioning. Somewhere along the way I received the following error when trying to open a workflow:<br /><br /><i><b>Error loading workflow. Could not deserialize object. The type 'Mediachase.Commerce.WorkflowActivities.Cart.GetFulfillmentWarehouseActivity' could not be resolved. </b></i><br /><i><br /></i>First thing I checked was of course if the namespace was correct, as that had been my previous issue. The namespace was indeed correct, so I had to keep on looking. The problem turned out to be that the namespace declaration was missing the assembly version number.<br /><br /><b>Incorrect declaration without assembly version:</b><br /><pre class="language-csharp"><code>xmlns:ns1="clr-namespace:Mediachase.Commerce.Workflow.Activities.Cart;Assembly=Mediachase.Commerce.Workflow.Activities, Culture=neutral, PublicKeyToken=null"</code></pre>sdf</div><br /><b>Correct declaration containing assembly version:</b><br /><pre class="language-csharp"><code>xmlns:ns1="clr-namespace:Mediachase.Commerce.Workflow.Activities.Cart;Assembly=Mediachase.Commerce.Workflow.Activities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"</code></pre><br />Hopefully this will save you some time debugging your workflows! </div>Receiving 404 errors from the FileManager in EPiServerhttp://www.karolikl.com/2014/03/receiving-404-errors-from-filemanager.html2014-03-26T09:13:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">Last week I had a customer contact me as they were not able to upload new files in the filemanager. Taking a closer look I saw that uploading new files or searching for files in the filemanager resulted in HTTP 404 not found errors.<br /><br />The problem? It turns out the FileSummary.config did not exist in the location specified in episerver.config:<br /><pre class="language-html"><code><virtualPath customFileSummary="~/Config/FileSummary.config"></code></pre>So if you find yourself scratching your head, wondering why the filemanager is giving you HTTP 404 errors, check the location of your FileSummary.config file!</div>Deploying a site that has been upgraded to EPiServer 7.6http://www.karolikl.com/2014/03/deploying-site-that-has-been-upgraded.html2014-03-18T07:54:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on"><div dir="ltr" style="text-align: left;" trbidi="on"><div dir="ltr" style="text-align: left;" trbidi="on">When you upgrade a site to <a href="http://world.episerver.com/Articles/Items/EPiServer-75--update-6/">EPiServer 7.6</a> it might be a good idea to keep a close eye on what is happening during the upgrade.<br /><br /><b>Why?</b><br />Well, the upgrade makes some changes to your AppData folder, so your next one-click deployment will not work as expected (unless you've included the AppData folder in your deployment process, in which case: shame on you). EPiServer has described the <a href="http://world.episerver.com/Documentation/Items/Installation-Instructions/Installing-EPiServer-updates/Technical-details-for-UI-NuGet-packages/">"Changes to location of package contents when upgrading"</a> and blogged about the <a href="http://world.episerver.com/Blogs/Khurram-Hanif/Dates/2014/3/EPiServer-76-Protected-Site-Add-Ons-location/">new add-ons location</a>, and it's a good idea to read these if you run into trouble.<br /><br />I experienced this yesterday, I deployed a new version of the site I'm working on to test and the website crashed.<br /><br />The errors I received were along the lines of:<br /><br /></div><pre class="language-csharp"><code>System.ArgumentException: An item with the same key has already been added.<br /> at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)<br /> at System.Collections.Generic.Dictionary`2.System.Collections.Generic.ICollection<system .collections.generic.keyvaluepair="" alue="" ey="">>.Add(KeyValuePair`2 keyValuePair)<br /> at EPiServer.Web.Mvc.Html.QuickNavigatorHtmlHelperExtensions.RenderEPiServerQuickNavigator(HtmlHelper htmlHelper, String partialViewName)</system></code></pre></div><br />This is another error I received:<br /><br /><pre class="language-csharp"><code>System.Web.HttpException (0x80004005): Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerWrapper'. ---> System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.ArgumentException: An item with the same key has already been added.<br /> at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)<br /> at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)<br /> at EPiServer.Cms.Shell.CmsModuleViewModel..ctor(ShellModule module, IClientResourceService clientResourceService, IEnumerable`1 contentRepositoryDescriptors)<br /> at EPiServer.Cms.Shell.CmsModule.CreateViewModel(ModuleTable moduleTable, IClientResourceService clientResourceService)</code></pre><br />So what was the problem? Well, the problem was that the code that was deployed had been upgraded to 7.6, but the neccessary changes needed to make 7.6 work had not been done on the test server.<br /><br /><b>What I did to fix this was:</b><br /><ol style="text-align: left;"><li>Delete the Modules and ModulesRepository folders in AppData on the test server</li><li>Copy the Modules and ModulesRepository folders from the dev environment to the test server (the upgrade had been done on the dev environment, so we knew these folders were correct)</li><li>Delete the following assemblies from the modulesbin folder on your site root: </li><ul><li><span style="white-space: pre-wrap;">EPiServer.Shell.UI.dll</span></li><li><span style="white-space: pre-wrap;">EPiServer.Cms.Shell.UI.dll</span></li><li><span style="white-space: pre-wrap;">EPiServer.Cms.Shell.UI.Sources.dll. </span><span style="white-space: pre-wrap;">If your site uses Commerce, you'll also have to delete these: </span><span style="white-space: pre-wrap;">EPiServer.Commerce.AddOns.UI.dll and </span><span style="white-space: pre-wrap;">EPiServer.Commerce.AddOns.Manager.dll </span></li></ul><li><span style="white-space: pre-wrap;">Make sure the modules/_protected folder exists on your site root. </span></li></ol><div><span style="white-space: pre-wrap;"><b>For more details, I recommend reading the readme.txt files for the upgrade:</b></span></div><div><a href="/link/be73ad714ecc4b0795ebb282f8080e82.txt" style="white-space: pre-wrap;">EPiServer.CMS.UI.Core</a></div><div><a href="/link/c7f07019580145c7833d96ce6b076ecd.txt" style="white-space: pre-wrap;">EPiServer.Commerce.UI</a> (<span style="white-space: pre-wrap;">for commerce sites) </span></div><div><span style="white-space: pre-wrap;"><a href="/link/f5d421a66f4444b282317a5032f9460b.txt">EPiServer.Commerce.UI.ManagerIntegration</a> </span>(<span style="white-space: pre-wrap;">for commerce sites) </span></div></div>Dynamic error handling in EPiServer 7 with static fallbackhttp://www.karolikl.com/2013/09/dynamic-error-handling-in-episerver-7.html2013-09-08T19:47:00.0000000Z<p>A lot of EPiServer editors want to be able to edit their 404 and 500 error pages in edit mode, and telling them that they have to poke around in static HTML files is usually not very popular. </p> <p>But it’s important to remember that dynamic error pages cause some additional challenges that has to be considered. Examples are avoiding infinite loops, globalization and being able to add error pages for any HTTP status code that you’d like. The concept behind this solution is the same as the <a href="http://karolikl.blogspot.no/2011/01/dynamic-error-pages-in-episerver-with.html">solution for EPiServer CMS 6</a>, but it has been rewritten to MVC 4 and it now supports globalization. </p> <p>I prefer to create a new pagetype for the error pages, in that way I can restrict where the pagetype can be used and who has access to creating and editing error pages. So let’s create our new pagetype called ErrorPage:</p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span>[ContentType(Order = 7000)]</p> <p><span class="lnum">2: </span>[AvailablePageTypes(Availability = Availability.None)]</p> <p class="alt"><span class="lnum">3: </span><span class="kwrd">public</span> <span class="kwrd">class</span> ErrorPage : SitePageData</p> <p><span class="lnum">4: </span>{</p> <p class="alt"><span class="lnum">5:   </span>[Display(Order = 100)]</p> <p><span class="lnum">6:   </span>[CultureSpecific]</p> <p class="alt"><span class="lnum">7:   </span><span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">string</span> HttpStatusCode { get; set; }</p> <p><span class="lnum">8: </span> </p> <p class="alt"><span class="lnum">9:   </span>[Display(Order = 1000, GroupName = SystemTabNames.Content)]</p> <p><span class="lnum">10:  </span>[CultureSpecific]</p> <p class="alt"><span class="lnum">11:  </span><span class="kwrd">public</span> <span class="kwrd">virtual</span> XhtmlString MainBody { get; set; }</p> <p><span class="lnum">12: </span> </p> <p class="alt"><span class="lnum">13:   </span><span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">void</span> SetDefaultValues(ContentType contentType)</p> <p><span class="lnum">14:   </span>{</p> <p class="alt"><span class="lnum">15:     </span><span class="kwrd">base</span>.SetDefaultValues(contentType);</p> <p><span class="lnum">16: </span> </p> <p class="alt"><span class="lnum">17:     </span>VisibleInMenu = <span class="kwrd">false</span>;</p> <p><span class="lnum">18:     </span>DisableIndexing = <span class="kwrd">true</span>;</p> <p class="alt"><span class="lnum">19:   </span>}</p> <p><span class="lnum">20: </span>}</p> </div> <p>There is nothing special with this page type. It has a string property called HttpStatusCode where the editor specifies which status code this error page should be used for, and a MainBody property where they can specify a friendly message. Now let’s create a controller for this pagetype:</p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span>[ContentOutputCache]</p> <p><span class="lnum">2: </span><span class="kwrd">public</span> <span class="kwrd">class</span> ErrorPageController : PageControllerBase<ErrorPage></p> <p class="alt"><span class="lnum">3: </span>{</p> <p><span class="lnum">4:   </span><span class="kwrd">public</span> ActionResult Index(ErrorPage currentPage)</p> <p class="alt"><span class="lnum">5:   </span>{</p> <p><span class="lnum">6:     </span><span class="kwrd">return</span> View(PageViewModel.Create(currentPage));</p> <p class="alt"><span class="lnum">7:   </span>}</p> <p><span class="lnum">8: </span>}</p> </div> <p>The only thing this controller does is to return a view, and that view can look like this: </p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span>@model PageViewModel<span class="kwrd"><</span><span class="html">ErrorPage</span><span class="kwrd">></span></p> <p><span class="lnum">2: </span> </p> <p class="alt"><span class="lnum">3: </span>@{ Layout = "~/Views/Shared/Layouts/_Root.cshtml"; }</p> <p><span class="lnum">4: </span></p> <p class="alt"><span class="lnum">5: </span><span class="kwrd"><</span><span class="html">h1</span><span class="kwrd">></span>@Model.CurrentPage.PageName<span class="kwrd"></</span><span class="html">h1</span><span class="kwrd">></span></p> <p><span class="lnum">6: </span>@Html.DisplayFor(m =<span class="kwrd">></span> Model.CurrentPage.MainBody)</p> </div> <p>So far, so good! Right? Now comes the challenging part! The view we just created will never actually be seen by a visitor, the only time this view is displayed is in edit mode. Instead of the visitor seeing this view, an ErrorHandler will be in charge of finding the correct ErrorPage and displaying its content. Let’s take a look at our ErrorHandlerController: </p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> ErrorHandlerController : Controller</p> <p><span class="lnum">2: </span>{</p> <p class="alt"><span class="lnum">3:   </span><span class="kwrd">private</span> List<ErrorPage> _errorPages;</p> <p><span class="lnum">4:   </span><span class="kwrd">private</span> <span class="kwrd">string</span> _currentLanguage;</p> <p class="alt"><span class="lnum">5: </span> </p> <p><span class="lnum">6:   </span><span class="kwrd">private</span> List<ErrorPage> ErrorPages</p> <p class="alt"><span class="lnum">7:   </span>{</p> <p><span class="lnum">8:     </span>get</p> <p class="alt"><span class="lnum">9:     </span>{</p> <p><span class="lnum">10:       </span><span class="kwrd">if</span> (_errorPages == <span class="kwrd">null</span> || _errorPages.Count == 0)</p> <p class="alt"><span class="lnum">11:       </span>{</p> <p><span class="lnum">12:         </span><span class="kwrd">if</span> (<span class="kwrd">string</span>.IsNullOrWhiteSpace(_currentLanguage))</p> <p class="alt"><span class="lnum">13:           </span>_currentLanguage = ContentLanguage.PreferredCulture.Name;</p> <p><span class="lnum">14: </span> </p> <p class="alt"><span class="lnum">15:         </span>IContentTypeRepository contentTypeRepository = ServiceLocator.Current.GetInstance<IContentTypeRepository>();</p> <p><span class="lnum">16:         </span>ContentType errorPage = contentTypeRepository.Load(<span class="kwrd">typeof</span>(ErrorPage));</p> <p class="alt"><span class="lnum">17: </span> </p> <p><span class="lnum">18:         </span>_errorPages = DataFactory.Instance.FindPagesWithCriteria(ContentReference.RootPage, <span class="kwrd">new</span> PropertyCriteriaCollection</p> <p class="alt"><span class="lnum">19:         </span>{</p> <p><span class="lnum">20:           </span><span class="kwrd">new</span> PropertyCriteria</p> <p class="alt"><span class="lnum">21:           </span>{</p> <p><span class="lnum">22:           </span>Condition = CompareCondition.Equal,</p> <p class="alt"><span class="lnum">23:           </span>Name = <span class="str">"PageTypeID"</span>,</p> <p><span class="lnum">24:           </span>Type = PropertyDataType.PageType,</p> <p class="alt"><span class="lnum">25:           </span>Value = errorPage.ID.ToString()</p> <p><span class="lnum">26:           </span>}</p> <p class="alt"><span class="lnum">27:         </span>}, _currentLanguage).FilterForDisplay().Cast<ErrorPage>().ToList();</p> <p><span class="lnum">28:       </span>}</p> <p class="alt"><span class="lnum">29:       </span><span class="kwrd">return</span> _errorPages;</p> <p><span class="lnum">30:     </span>}</p> <p class="alt"><span class="lnum">31: </span>}</p> <p><span class="lnum">32: </span> </p> <p class="alt"><span class="lnum">33: </span><span class="kwrd">public</span> ActionResult Index()</p> <p><span class="lnum">34: </span>{</p> <p class="alt"><span class="lnum">35: </span><span class="rem">// Get status code</span></p> <p><span class="lnum">36:     </span><span class="kwrd">int</span> statusCode = GetStatusCode();</p> <p class="alt"><span class="lnum">37: </span> </p> <p><span class="lnum">38:     </span><span class="kwrd">if</span> (statusCode > 0)</p> <p class="alt"><span class="lnum">39:     </span>{</p> <p><span class="lnum">40:       </span>Response.StatusCode = statusCode;</p> <p class="alt"><span class="lnum">41: </span> </p> <p><span class="lnum">42:       </span><span class="rem">// Check if page with this status code is defined in EPiServer</span></p> <p class="alt"><span class="lnum">43:       </span>ErrorPage errorPage = ErrorPages.Find(page => page.HttpStatusCode.Equals(statusCode.ToString()));</p> <p><span class="lnum">44:       </span><span class="kwrd">if</span> (errorPage == <span class="kwrd">null</span>)</p> <p class="alt"><span class="lnum">45:       </span>{</p> <p><span class="lnum">46:         </span><span class="kwrd">string</span> staticHtmlPage = GetStaticHtmlPage(statusCode);</p> <p class="alt"><span class="lnum">47:         </span><span class="kwrd">if</span> (String.IsNullOrEmpty(staticHtmlPage))</p> <p><span class="lnum">48:         </span>{</p> <p class="alt"><span class="lnum">49:           </span><span class="kwrd">throw</span> <span class="kwrd">new</span> HttpException(</p> <p><span class="lnum">50:           </span>String.Format(<span class="str">"Could not get static html page for statuscode {0} in errorhandler"</span>, statusCode));</p> <p class="alt"><span class="lnum">51:         </span>}</p> <p><span class="lnum">52:         </span><span class="kwrd">return</span> <span class="kwrd">new</span> ExtendedContentResult</p> <p class="alt"><span class="lnum">53:         </span>{</p> <p><span class="lnum">54:           </span>StatusCode = Response.StatusCode,</p> <p class="alt"><span class="lnum">55:           </span>Content = MvcHtmlString.Create(staticHtmlPage).ToString(),</p> <p><span class="lnum">56:           </span>ContentEncoding = Encoding.UTF8,</p> <p class="alt"><span class="lnum">57:           </span>ContentType = <span class="str">"text/html"</span>,</p> <p><span class="lnum">58:           </span>Headers = Response.Headers</p> <p class="alt"><span class="lnum">59:         </span>};</p> <p><span class="lnum">60:       </span>}</p> <p class="alt"><span class="lnum">61:       </span> </p> <p><span class="lnum">62:       </span><span class="rem">// Set RoutingConstants.NodeKey so EPi's extension method RequestContext.GetContentLink() will work</span></p> <p class="alt"><span class="lnum">63:       </span>ControllerContext.RouteData.DataTokens[RoutingConstants.NodeKey] = errorPage.ContentLink;</p> <p><span class="lnum">64:       </span><span class="kwrd">return</span> View(<span class="str">"~/Views/ErrorHandling/ErrorHandler.cshtml"</span>, PageViewModel.Create(errorPage));</p> <p class="alt"><span class="lnum">65:     </span>}</p> <p><span class="lnum">66: </span> </p> <p class="alt"><span class="lnum">67:     </span><span class="kwrd">throw</span> <span class="kwrd">new</span> HttpException(<span class="str">"No status code sent to errorhandler"</span>);</p> <p><span class="lnum">68:   </span>}</p> <p class="alt"><span class="lnum">69: </span> </p> <p><span class="lnum">70:   </span><span class="rem">/// <summary></span></p> <p class="alt"><span class="lnum">71:   </span><span class="rem">/// Returns the static html file for the given status code as a string</span></p> <p><span class="lnum">72:   </span><span class="rem">/// </summary></span></p> <p class="alt"><span class="lnum">73:   </span><span class="rem">/// <param name="statusCode">HTTP Error code</param></span></p> <p><span class="lnum">74:   </span><span class="rem">/// <returns>The content of the static html file as a string</returns></span></p> <p class="alt"><span class="lnum">75:   </span><span class="kwrd">private</span> <span class="kwrd">string</span> GetStaticHtmlPage(<span class="kwrd">int</span> statusCode)</p> <p><span class="lnum">76:   </span>{</p> <p class="alt"><span class="lnum">77:     </span><span class="kwrd">string</span> filePath = String.Format(<span class="str">"/StatusCodes/{0}.html"</span>, statusCode);</p> <p><span class="lnum">78:     </span><span class="kwrd">string</span> mapPath = Server.MapPath(filePath);</p> <p class="alt"><span class="lnum">79:     </span><span class="kwrd">if</span> (System.IO.File.Exists(mapPath))</p> <p><span class="lnum">80:     </span>{</p> <p class="alt"><span class="lnum">81:       </span><span class="rem">// Read the file as one string.</span></p> <p><span class="lnum">82:       </span>StreamReader myFile = <span class="kwrd">new</span> StreamReader(mapPath);</p> <p class="alt"><span class="lnum">83:       </span><span class="kwrd">string</span> myString = myFile.ReadToEnd();</p> <p><span class="lnum">84:       </span>myFile.Close();</p> <p class="alt"><span class="lnum">85:       </span><span class="kwrd">return</span> myString;</p> <p><span class="lnum">86:     </span>}</p> <p class="alt"><span class="lnum">87:     </span><span class="kwrd">return</span> String.Empty;</p> <p><span class="lnum">88:   </span>}</p> <p class="alt"><span class="lnum">89: </span> </p> <p><span class="lnum">90:   </span><span class="rem">/// <summary></span></p> <p class="alt"><span class="lnum">91:   </span><span class="rem">/// Get the HTTP error status code from the querystring and sets current language</span></p> <p><span class="lnum">92:   </span><span class="rem">/// </summary></span></p> <p class="alt"><span class="lnum">93:   </span><span class="kwrd">private</span> <span class="kwrd">int</span> GetStatusCode()</p> <p><span class="lnum">94:   </span>{</p> <p class="alt"><span class="lnum">95:     </span><span class="kwrd">int</span> code = 0;</p> <p><span class="lnum">96:     </span><span class="kwrd">string</span> request = Server.UrlDecode(ControllerContext.HttpContext.Request.QueryString.ToString());</p> <p class="alt"><span class="lnum">97:     </span><span class="kwrd">if</span> (!String.IsNullOrEmpty(request))</p> <p><span class="lnum">98:     </span>{</p> <p class="alt"><span class="lnum">99:        </span>Regex regex = <span class="kwrd">new</span> Regex(<span class="str">@"(?:[0-9]{3}\;)"</span>);</p> <p><span class="lnum">100:       </span>Match match = regex.Match(request);</p> <p class="alt"><span class="lnum">101: </span> </p> <p><span class="lnum">102:       </span><span class="kwrd">if</span> (match.Success)</p> <p class="alt"><span class="lnum">103:       </span>{</p> <p><span class="lnum">104:         </span><span class="kwrd">string</span>[] requestQueryStrings = request.Split(<span class="str">';'</span>);</p> <p class="alt"><span class="lnum">105:         </span><span class="kwrd">if</span> (requestQueryStrings.Length > 0)</p> <p><span class="lnum">106:         </span>{</p> <p class="alt"><span class="lnum">107:           </span><span class="kwrd">int</span>.TryParse(requestQueryStrings[0], <span class="kwrd">out</span> code);</p> <p><span class="lnum">108: </span> </p> <p class="alt"><span class="lnum">109:           </span><span class="kwrd">if</span> (requestQueryStrings.Length > 1)</p> <p><span class="lnum">110:             </span>SetCurrentLanguage(requestQueryStrings[1]);</p> <p class="alt"><span class="lnum">111:           </span>}</p> <p><span class="lnum">112:         </span>}</p> <p class="alt"><span class="lnum">113:       </span>}</p> <p><span class="lnum">114: </span> </p> <p class="alt"><span class="lnum">115:       </span><span class="kwrd">return</span> code;</p> <p><span class="lnum">116:     </span>}</p> <p class="alt"><span class="lnum">117: </span> </p> <p><span class="lnum">118:   </span><span class="rem">/// <summary></span></p> <p class="alt"><span class="lnum">119:   </span><span class="rem">/// Gets the current page language based on the url</span></p> <p><span class="lnum">120:   </span><span class="rem">/// </summary></span></p> <p class="alt"><span class="lnum">121:   </span><span class="rem">/// <param name="url">Url of current page</param></span></p> <p><span class="lnum">122:   </span><span class="kwrd">private</span> <span class="kwrd">void</span> SetCurrentLanguage(<span class="kwrd">string</span> url)</p> <p class="alt"><span class="lnum">123:   </span>{</p> <p><span class="lnum">124:     </span><span class="kwrd">string</span> absoluteUrl = <span class="kwrd">new</span> Uri(url).AbsolutePath;</p> <p class="alt"><span class="lnum">125:     </span><span class="kwrd">if</span> (<span class="kwrd">string</span>.IsNullOrWhiteSpace(absoluteUrl))</p> <p><span class="lnum">126:       </span><span class="kwrd">return</span>;</p> <p class="alt"><span class="lnum">127: </span> </p> <p><span class="lnum">128:     </span><span class="kwrd">string</span>[] urlSegments = absoluteUrl.Split(<span class="kwrd">new</span>[] { <span class="str">'/'</span> }, StringSplitOptions.RemoveEmptyEntries);</p> <p class="alt"><span class="lnum">129:     </span><span class="kwrd">if</span> (urlSegments.Length == 0)</p> <p><span class="lnum">130:       </span><span class="kwrd">return</span>;</p> <p class="alt"><span class="lnum">131: </span> </p> <p><span class="lnum">132:     </span>var regex2 = <span class="kwrd">new</span> Regex(<span class="str">"([^\\s]+(?=\\.([a-zA-Z]+))\\.\\2)"</span>);</p> <p class="alt"><span class="lnum">133:     </span>Match match2 = regex2.Match(urlSegments[urlSegments.Length - 1]);</p> <p><span class="lnum">134:     </span><span class="kwrd">if</span> (match2.Success && !urlSegments[urlSegments.Length - 1].EndsWith(Settings.Instance.UrlRewriteExtension))</p> <p class="alt"><span class="lnum">135:       </span><span class="kwrd">return</span>;</p> <p><span class="lnum">136: </span> </p> <p class="alt"><span class="lnum">137:     </span>_currentLanguage = GetLanguageFromDomainSegment(urlSegments[0]);</p> <p><span class="lnum">138:   </span>}</p> <p class="alt"><span class="lnum">139: </span> </p> <p><span class="lnum">140:   </span><span class="rem">/// <summary></span></p> <p class="alt"><span class="lnum">141:   </span><span class="rem">/// Returns the language to be used for the given domainSegment from EPiServerFramework.config</span></p> <p><span class="lnum">142:   </span><span class="rem">/// </summary></span></p> <p class="alt"><span class="lnum">143:   </span><span class="rem">/// <param name="domainSegment">Domain of the requested page</param></span></p> <p><span class="lnum">144:   </span><span class="rem">/// <returns></returns></span></p> <p class="alt"><span class="lnum">145:   </span><span class="kwrd">private</span> <span class="kwrd">string</span> GetLanguageFromDomainSegment(<span class="kwrd">string</span> domainSegment)</p> <p><span class="lnum">146:   </span>{</p> <p class="alt"><span class="lnum">147:     </span><span class="kwrd">foreach</span> (HostNameCollection siteHosts <span class="kwrd">in</span> EPiServerFrameworkSection.Instance.SiteHostMapping)</p> <p><span class="lnum">148:     </span>{</p> <p class="alt"><span class="lnum">149:       </span><span class="kwrd">foreach</span> (HostNameElement hostNameElement <span class="kwrd">in</span> siteHosts)</p> <p><span class="lnum">150:       </span>{</p> <p class="alt"><span class="lnum">151:         </span><span class="kwrd">if</span> (hostNameElement.Name.EndsWith(domainSegment, StringComparison.InvariantCultureIgnoreCase) &&</p> <p><span class="lnum">152:           </span>hostNameElement.Name != <span class="str">"*"</span>)</p> <p class="alt"><span class="lnum">153:           </span><span class="kwrd">return</span> hostNameElement.Language;</p> <p><span class="lnum">154:         </span>}</p> <p class="alt"><span class="lnum">155:       </span>}</p> <p><span class="lnum">156:       </span><span class="kwrd">return</span> <span class="kwrd">null</span>;</p> <p class="alt"><span class="lnum">157:     </span>}</p> <p><span class="lnum">158: </span>}</p> </div> <p>This code requires some explanation. When the Index() method of the ErrorHandlerController is called, the HTTP Status Code is retrieved from the request and if there exists an ErrorPage containing this status code, a Index view is returned for the ErrorHandler creating the model based on the ErrorPage. If there is no ErrorPage with the HTTP Status Code set, a static HTML file is used as fallback. This static HTML file should be located in the StatusCodes folder of your solution. So what does the ErrorPage Index view look like? It looks exactly like the View that was used for the ErrorPage that we saw previously: </p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span>@model PageViewModel<ErrorPage></p> <p><span class="lnum">2: </span></p> <p class="alt"><span class="lnum">3: </span>@{ Layout = <span class="str">"~/Views/Shared/Layouts/_Root.cshtml"</span>; }</p> <p><span class="lnum">4: </span> </p> <p class="alt"><span class="lnum">5: </span><h1>@Model.CurrentPage.PageName</h1></p> <p><span class="lnum">6: </span>@Html.DisplayFor(m => Model.CurrentPage.MainBody)</p> </div> <p>So what’s the difference between the ErrorPage Index view and the ErrorHandler Index view? As I’ve mentioned before, the ErrorPage Index view is only seen by the editor. The ErrorHandler Index view however, is the one that will be seen by the visitor. So how does this work? How is the ErrorHandler Index method invoked? It all comes down to the <httpErrors> settings under <system.webserver> in the web.config file: </p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span><httpErrors errorMode=<span class="str">"Custom"</span> existingResponse=<span class="str">"Replace"</span>></p> <p><span class="lnum">2: </span><remove statusCode=<span class="str">"404"</span> /></p> <p class="alt"><span class="lnum">3: </span><error statusCode=<span class="str">"404"</span> path=<span class="str">"/Views/ErrorHandler"</span> responseMode=<span class="str">"ExecuteURL"</span> /></p> <p><span class="lnum">4: </span></httpErrors></p> </div> <p>Here we register a 404 status code, setting its path to the ErrorHandler View folder. In this way, the Index method of the ErrorHandlerController will be invoked for every 404 status code. So for every status code you want to catch, you have to add it to the web.config file as we’ve done above. Last, but not least, we need to add a new Route in Global.asax.cs:</p> <div class="csharpcode"> <p class="alt"><span class="lnum"> 1: </span> <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> RegisterRoutes(RouteCollection routes)</p> <p><span class="lnum"> 2: </span> {</p> <p class="alt"><span class="lnum"> 3:   </span> <span class="rem">// Route to handle error pages</span></p> <p><span class="lnum"> 4:    </span> routes.MapRoute(</p> <p class="alt"><span class="lnum"> 5:      </span> <span class="str">"ErrorHandler"</span>,</p> <p><span class="lnum"> 6:      </span> <span class="str">"Views/ErrorHandling/ErrorHandler"</span>,</p> <p class="alt"><span class="lnum"> 7:     </span> <span class="kwrd">new</span> { controller = <span class="str">"ErrorHandler"</span>, action = <span class="str">"Index"</span> });</p> <p><span class="lnum"> 8: </span> </p> <p class="alt"><span class="lnum"> 9:   </span> <span class="kwrd">base</span>.RegisterRoutes(routes);</p> <p><span class="lnum"> 10: </span> }</p> </div> <p>And that’s it! You now have dynamic error pages with a static fallback. AND, they support globalization, so you can have separate error pages for all your languages. As a last note, in the ErrorHandlerController, when retrieving a static error page a ExtendedContentResult is returned based on a solution by <a href="http://weblogs.asp.net/andrewrea/archive/2009/12/14/creating-an-extended-content-result-for-asp-net-mvc.aspx">Andrew Rea</a>. ExtendedContentResult looks like this: </p> <div class="csharpcode"> <p class="alt"><span class="lnum">1: </span><span class="kwrd">public</span> <span class="kwrd">class</span> ExtendedContentResult : ContentResult</p> <p><span class="lnum">2: </span>{</p> <p class="alt"><span class="lnum">3:   </span><span class="kwrd">public</span> <span class="kwrd">int</span> StatusCode { get; set; }</p> <p><span class="lnum">4:   </span><span class="kwrd">public</span> NameValueCollection Headers { get; set; }</p> <p class="alt"><span class="lnum">5: </span> </p> <p><span class="lnum">6:   </span><span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">void</span> ExecuteResult(ControllerContext context)</p> <p class="alt"><span class="lnum">7:   </span>{</p> <p><span class="lnum">8:     </span>context.HttpContext.Response.StatusCode = StatusCode;</p> <p class="alt"><span class="lnum">9:     </span><span class="kwrd">foreach</span> (<span class="kwrd">string</span> s <span class="kwrd">in</span> Headers.Keys)</p> <p><span class="lnum">10:     </span>{</p> <p class="alt"><span class="lnum">11:       </span>context.HttpContext.Response.AddHeader(s, Headers[s]);</p> <p><span class="lnum">12:     </span>}</p> <p class="alt"><span class="lnum">13:     </span><span class="kwrd">base</span>.ExecuteResult(context);</p> <p><span class="lnum">14:   </span>}</p> <p class="alt"><span class="lnum">15: </span>}</p> </div> <p>Enjoy!</p> EPiServer 7 RequestError: Unable to load /EPiServer/shell/Stores/metadata/?...http://www.karolikl.com/2013/08/episerver-7-requesterror-unable-to-load.html2013-08-19T09:27:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">Working in the new EPiServer 7 edit mode, I received the following javascript error:<br /><br /><span style="color: red;">RequestError: Unable to load /EPiServer/shell/Stores/metadata/?type=EPiServer.Core.ContentData&modelAccessor=%7B%22contentLink%22%3A%2212_24%22%7D&dojo.preventCache=1376896223044 status: 500 </span><br /><span style="color: red;"><br /></span>This error prevented me from editing pages and even worst, creating new pages. Browsing to the url in the error message I saw that EPiServer was unable to locate my Editors.css file.<br /><br />The location of the Editors.css file is configured in the uiEditorCssPaths attribute in episerver.config. Turns out I had moved the Editor.css file to a different folder and forgotten to update the uiEditorCssPaths attribute. So, update the Editors.css path, and the javascript error disappears!<br /><div><br /></div></div>Epinova.CRMFramework 2.0 Beta version has been releasedhttp://www.karolikl.com/2012/12/epinovacrmframework-20-beta-version-has.html2012-12-05T13:58:00.0000000Z<p>A couple of days ago, I was finally ready to release the Beta version of Epinova.CRMFramework 2.0 on <a href="http://crmframework.codeplex.com/" target="_blank">CodePlex</a>. Unfortunately, the release came with a couple of broken promises, but I believe my excuses are indisputable (at least for now). </p> <p>When I released the Alpha version I wrote a <a href="http://karolikl.blogspot.no/2012/03/epinovacrmframework-20-alpha-version.html" target="_blank">blog post</a> similar to this, listing the new requirements and changes made to the framework. The requirements have not changed for the Beta version, but a couple of my goals and plans for the framework have. </p> <p>My initial plans were to replace the CrmQuery system with LINQ support in order to do strongly typed queries in a syntax well known to all developers. Unfortunately, this has not been done, and therefore I’ve had to reinstate CrmQuery. This means that the querying in v2.0 of Epinova.CRMFramework works exactly the same way as it did in version 1.0. If you are completely lost right now, take a look at the <a href="http://crmframework.codeplex.com/wikipage?title=Using%20the%20CrmQuery%20system&referringTitle=Documentation" target="_blank">documentation</a>. </p> <p>The good news is that the CrmEntityController<T>.Find() method is back and so is the CrmManyToManyRelationshipController<T, V>. This means that Epinova.CRMFramework 2.0 is complete with the features found in v1.0.</p> <p>So why did<em> </em>I not rewrite the querying as I said I would? Short answer: Lack of time. This framework is not something I get paid for, it’s a project I play around with on my spare time. As I have a full-time job, it’s only natural that I prioritize my employer and my customers rather than my toys. The good thing is, it’s free and it’s open source, which means that you’re free to add the LINQ support yourself if you want to!</p> <p>Feel free to contact me if you have any comments or questions <img style="border-bottom-style: none; border-left-style: none; border-top-style: none; border-right-style: none" class="wlEmoticon wlEmoticon-smile" alt="Smile" src="http://lh5.ggpht.com/-W9TghY6B5gA/UL9FCibbwOI/AAAAAAAAAWY/wvh1Pg3o2CQ/wlEmoticon-smile%25255B2%25255D.png?imgmax=800" /></p> My experiences from migrating EPiServer Relate+ 1 to EPiServer Relate+ 2http://www.karolikl.com/2012/10/my-experiences-from-migrating-episerver.html2012-10-15T20:33:00.0000000Z<p>Recently I’ve been migrating an EPiServer Relate+ 1 site to EPiServer Relate+ 2, and I think it might be useful for others contemplating doing the same to get an overview of the troubles I had on the way. </p> <p>First of all, some background information. The website I migrated was based on the Relate+ 1 template package, with some modifications and additional integrations. I decided to go through with the migration in the following steps: </p> <p>1) Migrate both database and code in the development environment. I then did some brief testing to ensure that all data had been migrated. </p> <p>2) Migrate the database in the test environment, and then deploy the code from step 1. I then let the customer test the site thoroughly while I did some bugfixing. </p> <p>3) Migrate the database in the production environment, and then deploy the code from step 1. </p> <p><strong><font size="3">Useful resources:</font> </strong></p> <p>My main resource for migrating the site was the <strong><a href="http://world.episerver.com/Articles/Items/Migrate-EPiServer-Relate-1-to-EPiServer-Relate-2/" target="_blank">”Migrate EPiServer Relate+ to EPiServer Relate+ 2”</a> </strong>article written by Klas Wikström. What I especially appreciated about his way of doing it was that you end up with two databases, one for CMS content and one for Community content. </p> <p>When it came to the code migrations needed in order to make the site run on Community 4, I was in for quite a surprise as there were a lot of them! Some I could fix with a simple “Find and Replace”, but unfortunately many of them involve changes in method definitions, for example where the parameters have switched places. Either way, the <strong>“EPiServerCommunity code migration” </strong>section of the <strong><a href="http://world.episerver.com/Documentation/Items/Tech-Notes/EPiServer-Community/EPiServer-Community-32/Migrating-EPiServer-Community-32-to-40-and-EPiServer-Mail-44-to-50/" target="_blank">“Migrating EPiServer Community 3.2 to 4.0 and EPiServer Mail 4.4 to 5.0”</a> </strong>tech note will tell you everything you need. </p> <p><strong><font size="3">Bumps in the road:</font></strong></p> <p><strong>1) SQL Error when running the “migration_tool.sql” script:</strong></p> <p><em><font size="3">“</font>The INSERT statement conflicted with the FOREIGN KEY constraint "FK_tblEPiServerCommunityClubMember_tblClub". The conflict occurred in database "dbFKWorldCommunity", table "dbo.tblEPiServerCommunityClub", column 'intID'. <br />The statement has been terminated.”</em></p> <p>I got about 5 of these errors the first time I ran the migration script. After a couple of rollbacks and some debugging, I figured I could change the failing script from “SELECT intID FROM” to “SELECT TOP 1 intID FROM”. Turns out the script expects one record, but in my case several were found. So I rewrote the script a bit, if you want to take a look at it you can find it <a href="https://dl.dropbox.com/u/39136043/migration_tool.sql" target="_blank">here</a> (note: I cannot guarantee that this will work for you, but for me it did the trick). </p> <p><strong>2) Error when installing a new Relate+ 2 site: </strong></p> <p>“<em>Error - Exception calling ‘WorkerProcessAccounts’ with ‘1’ argument(s): ‘Invalid application pool name’</em>”</p> <p>I found the solution for this error on the world.episerver.com <a href="/link/ed18a725d2804575bbc9cf8b4d754494.aspx" target="_blank">forum</a> (see Tommy’s solution). </p> <p><font size="3"><strong>Final words of advice: </strong></font></p> <p>1) Document every single step on the way! As I, in reality, did the migration three times (once in each environment) it was very useful to keep a journal of every single step on the way to make sure I didn’t forget a small detail.</p> <p>2) Backup your database before you start a new step! There are quite a lot of things that can go wrong, and I had to roll back three or four times during the first migration. If I’d forgotten a database backup, I would have been in a lot of trouble. </p> <p>3) Take your time! A migration is not something you do in a couple of hours. This migration included moving the site into a new environment which took some extra time, but still you should set aside at least a day per migration (and that’s not including code migrations). </p> <p>4) Breath. The migration script can run for ages while you bite your nails wondering if you’re about to mess everything up. Go get a coffee, don’t sit there staring at the “Executing query” message waiting for it to explode. Most likely, everything will be fine.</p> How to crash the page structure in edit modehttp://www.karolikl.com/2012/08/how-to-crash-page-structure-in-edit-mode.html2012-08-03T12:15:00.0000000Z<p>A quite interesting bug was reported recently on one of the CMS 6 R2 websites I’ve been working on. When expanding a certain node in the page structure in edit mode, the following error message appeared: </p> <p><font color="#ff0000">System.UriFormatException: Invalid URI: A port was expected because of there is a colon (':') present but the port could not be parsed.</font></p> <p><a href="http://lh5.ggpht.com/-XoZXz2LBBWU/UBukwqabsoI/AAAAAAAAAVs/MaD_aAZNXtY/s1600-h/image%25255B3%25255D.png"><img style="background-image: none; border-right-width: 0px; margin: 0px 8px 0px 0px; padding-left: 0px; padding-right: 0px; display: inline; float: left; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" align="left" src="http://lh4.ggpht.com/-_WMjHFGpU-w/UBukxYJ39RI/AAAAAAAAAVw/ORTW_rZL26k/image_thumb%25255B1%25255D.png?imgmax=800" width="92" height="279" /></a>As annoying as that error message is, the most annoying part was that the message appeared in the frame where the page structure usually is.</p> <p>This resulted in us being unable to navigate the page structure and therefore not being able to find the page responsible for the error message. I tried finding the page by clicking around in View mode, but that’s just a waste of time fumbling in the dark. I also tried adding the page ID directly to the URL, but the error message appeared on all pages in this part of the structure (and that’s a lot of pages) so it was impossible to figure out exactly which page was responsible. </p> <p>This leaves debugging the database as the last option, hooray! From the error message and poking around in Resharper I found that the LinkURL property of one of the pages probably was in the wrong format (containing a ‘:’ from the looks of the message). So I did a database query in order to find all pages with LinkURLs containing a ‘:’</p> <p class="csharpcode"><span class="kwrd">SELECT</span> * <span class="kwrd">FROM</span> [dbo].[tblWorkPage] <span class="kwrd">WHERE</span> [LinkURL] <span class="kwrd">like</span> <span class="str">'%:%'</span></p> <p>This resulted in 200 results, too many for my taste so I wanted to narrow down the result. As I knew this problem only occurred in a certain part of the page structure I wanted to find out how many pages existed in that part. By looking at the pageID’s I could figure out the PagePath in the database and query all pages beginning with this PagePath:</p> <p class="csharpcode"><span class="kwrd">SELECT</span> * <span class="kwrd">FROM</span> [dbo].[tblPage] <span class="kwrd">WHERE</span> [PagePath] <span class="kwrd">like</span> <span class="str">'.1.91927.91932.43806.%'</span></p> <p>This resulted in 188 results, still too many. But now I knew two things, I knew all the pages in the part on the structure that was causing a problem, and I knew all the pages containing a ‘:’ in their LinkURL property. By joining these two queries, I could now find all pages in the problematic part of the structure containing a ‘:’ in their LinkURLs: </p> <p class="csharpcode"><span class="kwrd">SELECT</span> W.pkID, W.fkPageID, W.Name, W.URLSegment, W.LinkURL, W.ExternalURL, P.PagePath <span class="kwrd">FROM</span> [dbo].[tblWorkPage] <span class="kwrd">AS</span> W <span class="kwrd">INNER</span> <span class="kwrd">JOIN</span> [dbo].[tblPage] <span class="kwrd">AS</span> P <span class="kwrd">ON</span> W.fkPageID = P.pkID <span class="kwrd">WHERE</span> P.PagePath <span class="kwrd">like</span> <span class="str">'.1.91927.91932.43806.%'</span> <span class="kwrd">AND</span> W.LinkURL <span class="kwrd">like</span> <span class="str">'%:%'</span></p> <p>Finally, this gave me 10 results and I could go through these results manually. Looking through all the LinkURL values I found the problem: </p> <p>http:www.mydomain.com</p> <p>I’m stunned that one simple typo like this can take down a part of the site structure! And guess what, the only way of fixing it is directly in the database, how’s that for a friday? First of all, I updated the LinkURL value in tblWorkPage:</p> <p class="csharpcode"><span class="kwrd">UPDATE</span> [dbo].[tblWorkPage] <span class="kwrd">SET</span> [LinkURL] = <span class="str">'http://www.mydomain.com'</span> <span class="kwrd">WHERE</span> [LinkURL] <span class="kwrd">like</span> <span class="str">'http:www.mydomain.com'</span> GO</p> <p><font color="#000000">Then, as globalization was enabled on my website, I needed to do the same thing in tblPageLanguage (Thanks to Tore Gjerdrum for pointing this out for me):</font></p> <p class="csharpcode"><span class="kwrd">UPDATE</span> [dbo].[tblPageLanguage] <span class="kwrd">SET</span> [LinkURL] = <span class="str">'http://www.mydomain.com'</span> <span class="kwrd">WHERE</span> [LinkURL] <span class="kwrd">like</span> <span class="str">'http:www.mydomain.com'</span> GO</p> <p><font color="#000000"><strong>Would you like to try this at home? <br /></strong><em>Note: This will crash a part of your website (or the whole thing if you’re really destructive and pick the start page in step #1). I take no responsibility for any of your actions or your angry boss/customer/colleagues! <br /></em></font><font color="#000000"><em> <br /></em></font><font color="#000000"><em>Note 2: I’ve only tried this in CMS 6 R2 <br /></em> <br />1) Choose a page you don’t like on your website <br />2) Set a shortcut on the page to for example: http:www.google.com <br />3) Watch as you look forward to some SQL fun!</font></p> <p><strong><font color="#000000">Update!</font></strong></p> <p><font color="#000000">1) Åge Reinås just gave me a hot tip! In order to find the page responsible for the problems, you can check out the page structure under Access Rights in Admin mode. If you hover over the page names in the structure, the page responsible will show a faulty url. For example: “http://www.mywebsite.com/EPiServer/CMS/Admin/www.mydomain.com”</font></p> <p><font color="#000000">2) Thanks to <a href="https://twitter.com/stianvhagen"><s>@</s><b>stianvhagen</b></a> for the hillarious meme he created after I published this post: </font></p> <p><img src="http://cdn.memegenerator.net/instances/400x/24376906.jpg" /></p> Playing around with the EPiServer.Util.SimpleEncryption classhttp://www.karolikl.com/2012/03/playing-around-with-episerverutilsimple.html2012-03-21T21:22:00.0000000Z<div dir="ltr" style="text-align: left;" trbidi="on">The other day I was playing around with Reflector and I ran into the <a href="http://sdk.episerver.com/library/cms6/html/AllMembers_T_EPiServer_Util_SimpleEncryption.htm" target="_blank">EPiServer.Util.SimpleEncryption</a> class, which I haven’t really noticed before. The class includes the usual self-explaining cryptography methods like Encrypt and Decrypt, but it also contains some not so self-explaining methods like ClearText and EncryptedText. <br />So I had to play around with this a little bit, hoping I’d understand why EPiServer has included this class. I browsed for usages of the class and the only usages I could find was in the ExceptionManager and in the EPiServer.Cmo.Cms assembly. I found <a href="/link/a0d57b930a2e4d88955764898013a784.aspx?epslanguage=en" target="_blank">one comment</a> from 2005 stating that EPiServer.Util originally wasn’t planned on being made public. If this it the case it might explain why there’s so little information about it. <br /><br /><strong>The SimpleEncryption constructor <br /></strong>The SimpleEncryption constructor takes one parameter, an initializer used for generating the cryptography key: <br /><br /><div class="csharpcode"><div class="alt">SimpleEncryption simpleEncryption = <span class="kwrd">new</span> SimpleEncryption(<span class="str">"myInitializer"</span>);</div></div><br /><strong>Encrypting text <br /></strong>There are two encryption methods you can use, Encrypt or EncryptedText. They will return the same encrypted string, but the string returned from EncryptedText will be prefixed with ENCRYPTED:<br /><br /><div class="csharpcode"><div class="alt"><span class="kwrd">string</span> original = <span class="str">"Testing encryption with SimpleEncryption"</span>;</div><br /><div class="alt"><span class="kwrd">string</span> encrypt = simpleEncryption.Encrypt(<span class="str">"myKey"</span>, original);</div><span class="rem">// encrypt == AvVayN0k1jSXjUVHzRmtq9rl9yCtmNLq+sBvz53vr0A6CIbzMaASE2LZ1LHR7hPT</span><br /><div class="alt"><br /></div><span class="kwrd">string</span> encryptedText = simpleEncryption.EncryptedText(<span class="str">"myKey"</span>, original);<br /><div class="alt"><span class="rem">// encryptedText == ENCRYPTED:AvVayN0k1jSXjUVHzRmtq9rl9yCtmNLq+sBvz53vr0A6CIbzMaASE2LZ1LHR7hPT</span></div></div>It took me a while to understand why the EncryptedText method is included at all, I found it a bit pointless in the beginning. <br /><br /><strong>Checking if a string is encrypted <br /></strong>Let’s assume you have a piece of text, and you don’t know whether this text is encrypted or not. If you’ve made a habit of using the EncryptedText method, you can use the IsEncrypted method to check if the text is encrypted: <br /><br /><div class="csharpcode"><div class="alt"><span class="kwrd">bool</span> isEncrypted = simpleEncryption.IsEncrypted(<span class="str">"AvVayN0k1jSXjUVHzRmtq9rl9yCtmNLq+sBvz53vr0A6CIbzMaASE2LZ1LHR7hPT"</span>);</div><span class="rem">// isEncrypted == false </span><br /><div class="alt"><br /></div><span class="kwrd">bool</span> isEncryptedText = simpleEncryption.IsEncrypted(<span class="str">"ENCRYPTED:AvVayN0k1jSXjUVHzRmtq9rl9yCtmNLq+sBvz53vr0A6CIbzMaASE2LZ1LHR7hPT"</span>);<br /><div class="alt"><span class="rem">// isEncrypted == true</span></div></div>Here I’m calling the IsEncrypted method with the two encrypted strings from the previous example. Both these texts are encrypted, so you’d assume that both isEncrypted and isEncryptedText would be true. That’s not the case, the IsEncrypted method only checks if the value specified is prefixed with “ENCRYPTED:”. This means that the following would return true even though the text is not encrypted: <br /><div class="csharpcode"><div class="alt"><span class="kwrd">bool</span> isEncrypted = simpleEncryption.IsEncrypted(<span class="str">"ENCRYPTED:This text is not encrypted"</span>);</div><span class="rem">// isEncrypted == true</span></div><br /><strong>Decrypting text <br /></strong>If you encrypted the text using the Encrypt method, you can decrypt the text by using the Decrypt method. If you encrypted the text using the EncryptedText method, you need to use the ClearText method in order to decrypt it. <br /><br /><strong>UPDATE!</strong><br />This class is old, outdated and not safe. It will be phased out, so don't use it. See comments for more information. Conclusion: you've just wasted your time reading this blog post! Sorry...</div>Epinova.CRMFramework 2.0 Alpha version has been releasedhttp://karolikl.blogspot.com/2012/03/epinovacrmframework-20-alpha-version.html2012-03-13T12:43:00.0000000Z<p>Finally, Epinova.CRMFramework now supports Microsoft Dynamics CRM 2011! The alpha version has just been released on <a href="http://crmframework.codeplex.com/" target="_blank">CodePlex</a>, check it out!</p> <p>Due to a lot of changes in Microsoft Dynamics CRM 2011, the framework has been completely rewritten with a few consequences: <br /> <br /><strong>New requirements: <br /></strong>.NET Framework 4 <br />Microsoft Dynamics CRM 2011</p> <p><strong>CrmControllerFactory.Instance returns an interface <br /></strong>In previous versions CrmControllerFactory.Instance returned an object of type CrmControllerFactory. This now returns an interface: ICrmControllerFactory.</p> <p><strong>Permanent removal of CrmQuery class <br /></strong>The CrmQuery system, while functioning in the previous version, was not well enough equipped for complex queries. I’ve removed the CrmQuery class with plans of replacing it with LINQ support in the Beta version. </p> <p><strong>Temporary removal of CrmEntityController<T>.Find() <br /></strong>As the CrmEntityController<T>.Find() method requires the CrmQuery class, this method has been removed. It will be reinserted in the Beta version of the framework. </p> <p><strong><strong>Temporary removal </strong>of CrmManyToManyRelationshipController<T, V> <br /></strong>As the CrmManyToManyRelationshipController<T, V> class requires the CrmQuery class it has been removed. It will be reinserted in the Beta version of the framework.</p> <h4>Questions</h4> <p><strong>Can I upgrade from v1.0 to <strong>v2.0-alpha</strong>? <br /></strong>If you are integrating with Microsoft Dynamics CRM 2011 and you are not affected by the removals listed above, then yes. I would recommend testing well though as I can not guarantee a bug free alpha version (surprise surprise). Replace the dll’s from v1.0 with the dll’s from v2.0-alpha and you’re good to go. <br /> <br /><strong>When will the beta version be released? <br /></strong>I can not give you an accurate answer to this question. The more feedback I get from the alpha version, the faster will the beta version be released!</p> <p>Do you have any other questions? Feel free to contact me or post a comment :) </p> <div class="blogger-post-footer"><img width="1" height="1" src="https://blogger.googleusercontent.com/tracker/1175973087761281127-4225510822582481791?l=karolikl.blogspot.com" alt="" /></div>