Lukasvan3L

A pragmatic programmer

Posts tagged orchard

32 notes &

Orchard import & export 1-n relations

While developing in Orchard with a team, recipes are very useful. Everyone has their own database (so we don’t break each others environment), and sometimes you want to share your content with a colleague. Or your database got corrupted and you need to re-install. Not to mention backupping. That’s when Recipes come in.

For each of my orchard projects I created extensive recipes with custom Commands and importable ContentParts. I want to share a little code snippet for importing and exporting complicated ContentParts. It’s not as easy as it seems because it works with Linq.XElement properties.

First, the exporting
Inside the ContentPartDriver, here’s my code for the override function Exporting(ContentPart part, ExportContentContext context), which exports a richtext string into an attribute and related Offer elements into child-elements:

// first, get the ContentPart element
var parent = context.Element(part.PartDefinition.Name);

// a simple one: a string
if (!string.IsNullOrEmpty(part.RichTextBlock1))
  parent.SetAttributeValue("RichTextBlock1", part.RichTextBlock1);

// a 1-n relationship
// create the container element
parent.SetElementValue("offers", "");
var offers = parent.Element("offers");
foreach (var offer in part.Offers)
{
  // every node must have a unique name, else SetElementValue won't work
  var elid = "offer" + offer.Id;
  // again, first create the container element
  offers.SetElementValue(elid, "");
  var el = offers.Element(elid);
  // then import the values (without Id, it will be autogenerated)
  //el.SetAttributeValue("Id", offer.Id);
  el.SetAttributeValue("Image", offer.Image);
  el.SetAttributeValue("Price", offer.Price);
  el.SetAttributeValue("Url", offer.Url);
}

Expand the recipe
So now, when I execute the export function (don’t forget to check the Data checkbox!), I get the entire contentitem with my custom contentpart embedded into it. The export.xml file that’s generated can be used as recipe, or parts of it can be used.

And then, the importing
So now I override the Importing(ContentPart part, ImportContentContext context) function as follows:

// this gets injected into the constructor by Autofac
private readonly IRepository<OfferRecord> _offerRepository;


// first import the simple richtext field
var
RichTextBlock1 = context.Attribute(part.PartDefinition.Name, "RichTextBlock1"); if (RichTextBlock1 != null) part.RichTextBlock1 = RichTextBlock1;
// then get the "offers" element
var offers = context.Data.Element(part.PartDefinition.Name).Element("offers"); if (offers != null) { var offerList = new List<OfferRecord>();
// and import each of the offer elements. I'm not checking on nodenames because they're unique!
foreach (var offer in offers.Elements()) { var n = new OfferRecord(); if (offer.Attribute("Image") != null) n.Image = offer.Attribute("Image").Value; if (offer.Attribute("Price") != null) n.Price = offer.Attribute("Price").Value; if (offer.Attribute("Url") != null) n.Url = offer.Attribute("Url").Value;

// the new offer should first be saved to database, then attached to the ContentPart _offerRepository.Create(n); offerList.Add(n); } part.Offers = offerList; }

That’s it! Importing and exporting works like a charme, and my colleague’s have the same pages as I have when they re-install orchard with my recipe!

4 notes &

Orchard Multitenancy module

I ran into a few issues working with the Multitenancy Module in Orchard CMS:

Setting up a development environment using IIS
First of all there’s just one site running on my server, but it needs to be reached by multiple domains. For instance, http://localhost should go to my default site, and http://tenant should go to the tenant site. To get this working in IIS, edit the Hosts file (c:\Windows\System32\drivers\etc\hosts) and add:

127.0.0.1		localhost
127.0.0.1 tenant

When you surf to http://tenant, this file tells your machine to go to 127.0.0.1 instead of look the domain up on the internet.

Deleting tenants
When you create a tenant, a new folder gets created in the AppData/Sites directory. Next to the existing Default site.

The tenants are retrieved by the function ShellSettingsManager.LoadSettings(). This function checks the AppData/Sites folder for folders containing a Settings.txt file. So if you’re running into problems with your tenant, it’s easy to remove it again by removing the directory.

Custom named Routes
When it was working, I immediately got this error message:

A route named 'LocationDetails' is already in the route collection. Route names must be unique.
Parameter name: name

And more disturbingly, the error message was also shown on my default site! First thing to do is remove the tenant directory from AppData/Sites, so the default site builds again. Then find out wtf is happening!

Apparantly there’s one MVC RouteTable in memory. So when a module registers a route, it also gets registered for each tenant that has the module enabled. The problem here is that I was adding named routes to the RouteTable. By simple removing the “Name = “LocationDetails”“, both sites were working again, both enabling my LocationDetailsModule.

Wrap-up
I’m going to use the multitenancy module to host 7 different languages of the same site, each on its own domain name. With this module I can have 1 codebase and configure each site to behave slightly different. If I run into any other problems, you’ll be the first to hear :)

16 notes &

My f1rst Orchard site

I’ve been working on a project based on the Orchard CMS for a travel agency. We chose Orchard because of the modularity; four different travel sites will be based on this same architecture. Different themes, different modules, but one codebase.

Last week the first site went live: check it out at http://www.charmequality.nl!

We faced some interesting problems and came up with creative solutions. A few key learnings:

  • The modularity is indeed amazing: on a live site en- or disable a feature and the site can get changed in many different ways
  • The learning curve for developing Orchard is quite steep, not easy to step in. The cms is using some complicated techniques that take some time to fully understand. But once you get to know them, you’ll also have improved your c# knowledge!
  • Orchard’s performance is far from optimal. Outputcaching is needed to make it work and we still have CPU spikes when visitor counters rise. When you need live data on your outputcached page, consider using Ajax requests to load the uncachable data and scatter it on the page
  • The gallery has loads of good stuff! Some modules don’t work perfectly out-of-the-box, but can usually be fixed without much hassle. Don’t give up too easily! Fixing the issue also helps you better understand the inner workings of Orchard.

All in all I’m glad to have had this experience. And it’s not over yet, there’s a 1.1 version in the making and after that we’re starting on the second travel agency site.

3 notes &

Don’t trust Modules!

One of the powers of Orchard is a gallery filled with useful modules. Just install them and you’re good to go. But if you want to build a high-performance website with Orchard CMS, you’re gonna have to do more than that. The modules are really generic and that could cost you in performance. Also, they’re contributed by the community, and not everyone has the performance focus you might need.

Output Caching module
The output caching module for instance, does a terrific job at outputcaching. If I had built it myself it wouldn’t be as flexible with a cms module where editors can change the cachetime per route and even invalide specific cache entries. But for my application it can be modified to greatly increase the response times of a cached page.
For each page that is outputcached, the OutputCacheFilter computes a CacheKey that’s unique for this specific url and serversettings by joining the url, tenant, culture and themeid.

private string ComputeCacheKey(ActionExecutingContext filterContext)
{
  var keyBuilder = new StringBuilder();
  keyBuilder.Append("tenant=").Append(_shellSettings.Name).Append(";");
  keyBuilder.Append("url=").Append(filterContext.HttpContext.Request.RawUrl.ToLowerInvariant()).Append(";");
  foreach (var pair in filterContext.ActionParameters)
    keyBuilder.AppendFormat("{0}={1};", pair.Key, pair.Value);
  keyBuilder.Append("culture=").Append(_workContext.CurrentCulture).Append(";");
  keyBuilder.Append("theme=").Append(_themeManager.GetRequestTheme(filterContext.RequestContext).Id).Append(";");
  return keyBuilder.ToString();
}

In my application each tenant has its own culture. So I don’t need both of them in my cachekey. When I take out the line that includes the CurrentCulture, the page response time drops from 45ms to 5ms!

SecureSocketsLayer module
The SecureSocketsLayer also gets executed for each page request (even the outputcached ones!). In it’s filter is the following code:

var settings = _services.WorkContext.CurrentSite.As<SslSettingsPart>();

This does multiple database queries and costs about 40ms. On an outputcached page that’s a lot of unneccesary overhead!. In my specific application I only needed to do one area over SSL. So I put the setting (EnableSSL) in the AppSettings, and created my own filter with a tiny piece of code.

var wishedPort = 80;
if (UseSsl)
{
  var area = filterContext.Controller.ControllerContext.RouteData.Values["area"];
  if (area != null && area.ToString() == "Viaselect.Checkout")
    wishedPort = 443;
}

Conclusion
When you install a module, take a good look at the code! The developer might have made choices differently than you would have made them. And sometimes it’s a really quick fix to tune a module and benefit from it’s extra’s!

2 notes &

Speeding up Orchard: Packaging and Minification

Orchard is really flexible and modular. This has a big drawback while performance tuning: it’s really hard to package the static resources. Each module has its own javascript and css files, and one module doesn’t know about the files of the other. Time to hack performance into it!

The solution I implemented was a plain batch file. It gets fed a list of css and js files and minifies them to one output file. It does take a few steps to include a new js or css file, but it performs a lot better!

Here’s the bat file, using the Microsoft Ajax Minifier and DotLess:

for /R %%i in (*.less) do (
  tools\dotless.compiler.exe "%%i" "%%i.css"
)

tools\AjaxMin-4.37.exe -js -clobber -xml "minify.xml" -out "Orchard.Web\min"

So first of all I take each *.less file and run it through the dotless compiler. The output is a *.less.css file next to each original, which I’ve included in the minify.xml file that AjaxMin parses. Here’s a portion of it:

<root>
  <output path="js.js">
    <input path="c:\inetpub/Modules/Viaselect/Scripts/plugins/jquery.ba-hashchange.min.js" />
    <input path="c:\inetpub/Modules/Viaselect/Scripts/menu.js" />
  </output>
  <output path="css.css">
    <input path="c:\inetpub/Themes/CharmeQuality/styles/base.less.css" />
    <input path="c:\inetpub/modules/viaselect/styles/datepicker.less.css" />
  </output>
</root>

Unfortunately I didn’t get it to work with relative paths, so I had to make them absolute. We’ll cross that bridge when we get there :)

So now I have a js.js and a css.css that I include in the layout.cshtml:

  Script.Require("jQuery").AtHead();
  Script.Include("~/min/js.debug.js""~/min/js.js");
  Style.Include("~/min/css.debug.css""~/min/css.css");

And of course when you’re in debug mode, you want firebug to give you the right codelines! Here’s how to tackle that one; the debug files contain the following:

@import url("/Themes/CharmeQuality/styles/base.less");
@import url("/modules/viaselect/styles/datepicker.less");
document.write("<script src='/Modules/Viaselect/Scripts/plugins/jquery.ba-hashchange.min.js'><\/script>");
document.write("<script src='/Modules/Viaselect/Scripts/menu.js'><\/script>");

It’s not the best solution, but for now it speeds up the site considerably! I wouldn’t know how to create a module that takes care of this. The ResourceManager is way too complicated to just include the package if the file you’re requesting is inside the xml file. If any of you have good ideas, let me know! I’d love to facilitate this module :)

5 notes &

Profiling orchard with Eqatec Profiler

I can’t seem to get Red-Gate Ants Performance Profiler to attach to the right website. If I let it attach to IIS directly, it restarts IIS in .net2.0 mode instead of .net4.0. When I let it create its own webserver, I do get .net4.0, but am welcomed with the message “Operation could destabilize the runtime” in the WarmupHttpModule constructor. Not very comforting.

Luckily a colleague showed me Eqatec Profiler. It works really ingenious: it re-compiles selected DLL’s, injecting profiling logic into them. This should work nicely with Orchard CMS.

So I want Eqatec to handle the Orchard Core, and installed modules. First problem occurs: the modules are dynamically compiled. As Eqatec can only handle DLL files that are in the same folder, I made references from Orchard.Web to the modules I wanted to investigate. This is my config for Eqatec:

Eqatec Orchard configuration

All I have to do now is click “Build”, the selected dll’s are re-compiled and when I surf to my website (http://localhost) I see logs coming in in the “Run” tab. I don’t want to profile the start-up costs of Orchard, so I’ll refresh my browser a couple of times, then hit “reset counters”, refresh twice, and click “take snapshot”.

Eqatec Orchard profiling output

Perfect! This overview tells us the BuildDisplay was called 4 times, resulting in a total time of nearly 600ms that it took. And The repository did 126 database GetById() calls. And a whopping 316.100 time IsCurrent() on an IVolatileToken

So we’ve got some pointers on where to look for performance fixes ;)

ps. Eqatec has a time limited campaign where you can get a Corporate license worth $999 for free!

0 notes &

External data in Orchard

I have a webservice that gives me information to display on a detail page. This needs to be output in an orchard page with its masterpage and widgets surrounding it.

Colleagues of mine are trying the approach where they import the webservice data into Orchard, and let Orchard figure out the routing and displaying of the content. They have a fixed set of detail pages, none ever disappear and sometimes pages get added. (They are running into performance issues because they need an initial import of about 500.000 items)

My case is a little bit different: the data changes constantly and there can’t be a delay. The data has to come directly from the webservices, I can’t even cache it for a few minutes. So I fiddled around a bit and came up with this solution. In orchard I create a LocationDetails contenttype that’s not creatable and automatically gets the LocationDetailsPart:

ContentDefinitionManager.AlterPartDefinition(typeof(LocationDetailsPart).Name, part => part
.Attachable(false)
);
ContentDefinitionManager.AlterTypeDefinition("LocationDetails", cfg => cfg
  .WithPart(typeof(LocationDetailsPart).Name)
  .Creatable(false)
);

I then implemented a custom Controller that matches a url. If the appropriate content is found on the webservice, a temporary LocationDetails contentitem gets created (but doesn’t get inserted into the database!) and gets passed the details from the webservice.

var details = getStuffFromWebservice();
if (details == null) return new HttpNotFoundResult();

var
 contentItem = _contentManager.New("LocationDetails");
contentItem.As<LocationDetailsPart>().Details = details;

return new ShapeResult(this, _contentManager.BuildDisplay(contentItem));

After that it’s just using the default display logica of Orchard!

11 notes &

Multiple features in one orchard module

When you’re creating multiple features in an Orchard module, it’s important to specify of each class for which feature it’s needed. The Q42.DbTranslations module for instance, contains two features: Q42.DbTranslations and Q42.AdminCultureSelector.

First attempt I only decorated the Handler to be specific to this feature:

[OrchardFeature("Q42.AdminCultureSelector")]
public class AdminCultureSettingsPartHandler : ContentHandler
{
  public AdminCultureSettingsPartHandler(IRepository<AdminCultureSettingsPartRecord> repository)
  {
    Filters.Add(new ActivatingFilter<AdminCultureSettingsPart>("Site"));
    Filters.Add(StorageFilter.For(repository));
  }
}

I expected the AdminCultureSettingsPart not to be attached to Site, and therefore the whole feature would be disabled. But alas, it didn’t work. So I decorated the Part, the PartRecord and the Driver with OrchardFeature, and that fixed the problems.

But then you’re not there yet. Because when you have multiple features in a module, everything that isn’t specifically decorated with an OrchardFeature, is automatically allways enabled. So everything specific for DbTranslations also needed to be decorated.

Actually, every class except Migrations needs to be decorated, when you have multiple features in one module. In the end I decided to split the two and create separate modules for them :)