There's a big difference between Umbraco 7 and 8 in how to make a surface controller that is called via Ajax return data in the current culture.
In both Umbraco 7 and 8, if you don't pass the culture information to the controller then it doesn't know what culture to use, and ends up always returning data in the default culture. This problem could be solved in Umbraco 7 by just passing the culture name to the controller and re-applying the culture to the current thread inside the controller, making sure the controller is using the same culture as the calling thread and returns data in that culture.
In Umbraco 8, though, this is not the case since even if you do set the culture inside the controller, it is ignored and the default culture is used instead.
(For the TL;DR among you, the answer is "use the IVariationContextAccessor").
So let's see how a controller in Umbraco 8 behaves in each of the following cases:
- Direct call, with or without culture passed (just for completeness - no difference between Umbraco versions)
- Ajax call, with or without culture passed (essentially passing the culture was how we used to do it in v7)
- Ajax call with the user of IVariationContextAccessor (the new way in v8)
To create a test environment, we need a site with at least two languages and some minimal content in each, so here's what I did:
- I set up a default Umbraco 8.5.4 installation (the latest one at the time of writing this post).
- Changed models builder mode to "AppData" so I could have strongly-typed models with Intellisense.
- Added a second language (Greek, since I'm Greek, but could be any language).
- Changed the "Products" and "Product" document types so that they could vary per language.
- Added the second language in "Culture and Hostnames"
- Published items in the second language with changed titles (only 3 items) and unpublished other items from both languages.
- Created a controller (actually, two) and a view model to use in the products list view.
- Created a partial view to consume the view model data.
What I will do is create a series of calls to the controller in all possible ways (direct/ajax, with/without setting culture), where it will be very easy to see whether correct data is returned just by looking at the product names (Product names in Greek will end in "-EL").
So here's our (first) controller that gets product data in two ways, just like we did back in v7: With or without the culture passed. You'll notice that we've offloaded the code to get the VM data to another function - this is just for simplicity.
The two methods here - GetProductsOldWayNoCultureSet and GetProductsOldWayWithCulture get the view model data and feed it to our partial view exactly like we would do in Umbraco 7, either by passing the culture (if it was an Ajax call) or not (if it was a direct call). Nothing wrong with passing the culture in both types of calls though. Notice that passing the culture goes hand-in-hand with setting the culture again inside the controller.
And here's our view model. Nothing fancy here:
Our partial view is actually a copy of the existing code found in the Products.cshtml page, adapted for our view model
There are a couple of quick-and-dirty things here, like the default currency being created for every item of our view model. I hope you forgive me about this - those were a bit out of the scope of this post, so I just chose the quickest way possible to deal with them.
For using the "new" Umbraco 8 way of calling a controller using IVariationContextAccessor, I created a second controller. Could have one serving both purposes, but I wanted to make what changes very clear. The GetProductVm function is unfortunately duplicated here (as I said, quick-and-dirty). You'll notice that DI is used on this one, along with a constructor for the controller:
Finally, here's our altered Products.cshtml page, containing five calls to our controller(s): Two direct calls with and without culture, two ajax calls with and without culture, and one ajax call that passes culture but uses IVariationContextAccessor in the second controller.
In order to have the scripts run after jQuery is loaded, I used a section here which gets called by the master template.
Still with me? :) Okay, let's go for the final spin.
So if we used this code for direct and Ajax calls in both languages for products, this is the result we would get:
For English (the default language):
As you can probably see, any call will give the same results since we are on the default language (culture).
Things get a little different for a non-default language, though (remember, names in the non-default language will be ending in "-EL"). Let's see how it goes with Greek:
Our first two direct calls to the controller worked as expected - since we're on the same thread, the controller picked up the culture and got us our Greek products.
The third and fourth call, though, are a different story. These are both Ajax calls.
On the third call, we don't pass the culture to the controller and thus we don't recreate the culture when running our code to get the view model and return the view. The controller, not knowing which culture we are currently using, uses the default (English) culture and returns English data. This is exactly as it would do on Umbraco 7 as well.
The fourth call, though, is where Umbraco 8 behaves differently from Umbraco 7. On Umbraco 7, passing the culture (and re-setting the culture back in the controller) would get us data in the correct language. But here culture is ignored (even if set) and this just doesn't work! That's where our "new" way of calling a controller via Ajax comes into play.
The fifth way, which uses the IVariationContextAccessor, is again an Ajax call that passes the culture to the controller. But as you can see, this time it brings data in the correct language.
With the new call, we didn't even have to set the culture again in the controller. Using DI to inject a VariationContextAccessor does all the work for us.
You can find a full installation of the test Umbraco environment along with the code for the controllers / viewmodel (in the App_Code folder) here: https://github.com/sotirisf/UmbracoMLCultureTest (username: email@example.com, password: 1234567890)
Many thanks and kudos to Ross Ance (rossance.com) who has provided invaluable help with this one!