Orchard CMS: module settings not visible in Admin area? placement.info is the key!

by Oliver Fri, September 09 2011 22:29

Today I went off to create a custom search module for our new Orchard based web application. I simply copied the module Orchard.Search, renamed all namespaces and such to Teamaton.Search, replaced some strings and prepended a prefix of “Teamaton” to a bunch of them. I wanted the new module to be able to run side-by-side with the original Orchard.Search module to make sure I had an independent implementation and there wouldn’t be any conflicts in case the Orchard.Search module is already installed somewhere.

A problem I ran into quite quickly was that I wouldn’t see any of the search index’s fields on the Admin page for the new module (to the right you see the settings in the original Search module which is what I expect to see on the left as well):

image            image

Searching the discussions on Orchard’s codeplex site, I found this very helpful post on Module site settings not showing up which promised to be the key to my problem. During the initial string replacement orgy, in SearchSettingsPartDriver I had changed

return ContentShape(…).OnGroup(“search”) to 
return ContentShape(…).OnGroup(“TeamatonSearch”).

Now, this wasn’t in sync anymore with the Group Id specified inside the SearchSettingsPartHandler, but it should be (as the mentioned post suggests). So I changed that to match the Group “TeamatonSearch” like so:

   1: protected override void GetItemMetadata(GetContentItemMetadataContext context) {
   2:     if (context.ContentItem.ContentType != "Site") {
   3:         return;
   4:     }
   5:     base.GetItemMetadata(context);
   6:     context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("TeamatonSearch")));
   7: }

Unfortunately, it still did not work.

Frustration.

Another post on the discussion list led me to believe it was a problem with my Copy’n’Paste. There, piedone states:

One remarkable point is a name clashing with content parts: there can't be content parts with the same name in different modules as this leads to problems with schema creation and also when selecting them in them admin menus (because then their displayed name is also the same).

This somehow made sense to me, so I went and prefixed the part type name with Teamaton. When that didn’t work I also prefixed all properties of all ContentParts and Records because I remembered that I’d encountered a similar problem before when building our Google Maps module which, contained the same Latitude and Longitude property names that another ContentPart had already defined. But still: to no avail.

Desperation.

In the first post mentioned above, Bertrand Le Roy also states:

I have been in that exact situation. This is probably an error getting swallowed somewhere. […] In my case, it was the feature not enabling all the classes I needed, in particular the record, and the system couldn't find how to map database records.

So I went to the Orchard console … feature disable Teamaton.Searchfeature enable Teamaton.Search … still nothing.

Next it hit me like a sledge hammer – Migrations.cs! I hadn’t taken a single look at the persistence mapping for the SettingPartRecord! Well, turns out there was something wrong in there, but only since after I prefixed both parts and properties because now they wouldn’t match the strings in Migrations.cs anymore (a good example where magic strings break stuff).

Kind of helpless, I launched NHProf to look at the database calls that were being made and saw that there weren’t any for the Teamaton.Search module – but for the Orchard.Search module there were … hm. I scanned through the project files again – and stared at placement.info, another file I hadn’t touched since copying the original Search module. Bingo! This was the root of all evil or rather of the search fields not displaying. It looked like this:

   1: <Placement>
   2:     <Place Parts_Search_SiteSettings="Content:1"/>
   3:     <Place Parts_Search_SearchForm="Content:1"/>
   4: </Placement>

The problem with it was that I had renamed all the view files and prepended a prefix to them! So the correct version should look like this:

   1: <Placement>
   2:     <Place Parts_TeamatonSearch_SiteSettings="Content:1"/>
   3:     <Place Parts_TeamatonSearch_SearchForm="Content:1"/>
   4: </Placement>

That’s because the views are named TeamatonSearch.SiteSettings.cshtml and TeamatonSearch.SearchForm.cshtml and are both placed inside a Parts folder:

image

And now I saw the search fields even for our own new search module.

Lesson learned

If you can’t see something check the  placement.info  if you (accidentally) haven’t forgot to make that something visible Winking smile And if you still can’t see it – double check!

Happy coding!

Tags:

ASP.NET | Web Applikationen | Orchard

Running a multi-lingual web application: System.IO.PathTooLongException

by Oliver Fri, March 04 2011 15:18

We definitely have long paths on our client’s platform www.camping.info, for example for a concrete rating on the detail page of a campsite with a long name in a state with a long name - http://www.camping.info/deutschland/schleswig-holstein-und-hamburg/campingplatz-ferien-und-campinganlage-schuldt-19573/bewertung/r23989 - but the path (everything after the .info including the ‘/’) is still only 112 characters long which is still a long way from the 260 character barrier that’s the default in ASP.NET (see the MSDN).

The problem

Well, the same page in Greek for example has the following URL: http://el.camping.info/γερμανία/σλέσβιχ-χολστάιν-αμβούργο/campingplatz-ferien-und-campinganlage-schuldt-19573/αξιολόγηση/r23989, at least that is what we see in the browser bar. Essentially, this URL will be encoded when requesting the page from the server so it becomes a gigantic http://el.camping.info/%CE%B3%CE%B5%CF%81%CE%BC%CE%B1%CE%BD%CE%AF%CE%B1/%CF%83%CE%BB%CE%AD%CF%83%CE%B2%CE%B9%CF%87-%CF%87%CE%BF%CE%BB%CF%83%CF%84%CE%AC%CE%B9%CE%BD-%CE%B1%CE%BC%CE%B2%CE%BF%CF%8D%CF%81%CE%B3%CE%BF/campingplatz-ferien-und-campinganlage-schuldt-19573/%CE%B1%CE%BE%CE%B9%CE%BF%CE%BB%CF%8C%CE%B3%CE%B7%CF%83%CE%B7/r23989! Now the path of the URL is a whopping 310 characters long! That’s quite a bit over the limit - but even for shorter URLs the cyrillic or greek equivalents surpass the limit not as rarely as one would think once they are URL encoded.

The exception you get when exceeding the default of 260 chars is:

System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be less

than 260 characters, and the directory name must be less than 248 characters.

And this is what the error looks like in Elmah:

PathTooLong

The solution

You don’t have to search long to find a solution to this problem on the web: http://forums.asp.net/t/1553460.aspx/2/10. Just make sure the httpRuntime node contains the properties maxUrlLength AND relaxedUrlToFileSystemMapping like so:

<httpRuntime maxUrlLength="500" relaxedUrlToFileSystemMapping="true" />

You might wonder what the relaxedUrlToFileSystemMapping property really does: you can read more on MSDN. In short, if set to true “the URL does not have to comply with Windows path rules” (MSDN).

Happy coding,

Oliver

Tags: ,

ASP.NET | Camping.Info | Architecture | Web Applikationen | IIS

Automatic deployment of an ASP.NET Web Application Project with TeamCity and MSBuild

by Oliver Fri, January 21 2011 20:19

We recently updated one our largest project to use ASP.NET 4.0, and for this matter the new Package/Publish feature including sub-web.configs which is meant to supersede the Web Deployment Project. For a manual deployment there’s a good write-up on the msdn library titled ASP.NET Web Application Project Deployment Overview which shows how and where to set this up.

In our case this was not satisfactory because our deployment process is a bit more complicated. We push our changes to a central repository and use JetBrains’ continuous integration server (CIS) TeamCity Professional, which is totally free for our project size, for a continuous integration process. Once TeamCity has pulled and tested the current version, it is supposed to deploy this version to our staging server where we further test the complete site.

The key point in an automatic deployment was the management of the different web.config files for the different environments our project is running on. Unfortunately, until yesterday every deployment that included changes to the web.config file – even to the staging server - required a manual step of editing the web.config that live on our staging system (outside of source control!). What we used to do: after a successful build on our CIS we simply copied the web application (files) to our staging server! But as Scott Hanselman wrote: If You're Using XCopy, You're Doing It Wrong! This post inspired us to move along and take advantage of the new possibilities that we were given.

In the meanwhile, before switching to .NET 4.0 actually, we also took a shot at the Web Deployment Project way of doing things but never actually got that far as to fully automate the deployment – somehow the setup was not as easy as we hoped. Anyway, we wanted web.config Transforms!

So what does our setup look like and what did we want to do?

setup 

During local development and testing I use a web.config file that talks to a local DB instance and has some more specific settings. To run the web application on our staging server we need to replace certain values or whole sections in the web.config. For this transformation we use the sub-web.config files, one for each build configuration:

web-configs

Now, with all of these web.config files the simple XCOPY deployment we used to use does not work any longer. We need to trigger the web.config transformation on the build server and then deploy the whole application. As easy as this looks using the built-in menus and dialogs in Visual Studio – it took me quite a while to find how to do this in an automated build, more concretely from the command line.

After unsuccessfulle skimming stackoverflow.com for a solution I finally tripped over this very informative blog post on publishing a VS2010 ASP.NET web application using MSBuild. Admittedly, the author focuses on how to publish on the local machine as it’s yet a different process but towards the end he posts the solution I was looking for:

   1: msbuild Website.csproj "/p:Platform=AnyCPU;Configuration=Release;DesktopBuildPackageLocation=c:\_Publish\stage\Website.zip" /t:Package

This was it! After running this on my machine with my own settings I looked into the folder with the zip file and found the following 5 files:

package-folder

At first I just wanted to take the zip file, copy it to the staging server, unpack it – done! But then I peaked into it… and deeper… and deeper… and… still deeper… until I finally saw our application files underneath this directory:

zip-path

This has got to be one of the longest paths I’ve ever seen and used! How would I automate the extraction of web application files from the zip with such a path? I was already seeing myself hacking away on the command line…

But wait: what about those files that appeared next to the zip file? A ci-stage.deploy.cmd and a readme.txt caught my attention – of course, I opened the cmd file first :-D

deploy-cmd

Well… maybe the readme file gives me a shortcut to understanding this and the rest of the 190 lines:

readme

Looks promising! I convinced myself to give it a shot. So we set up a new configuration in TeamCity with the following settings:

tc-config

These settings reflect the command line from above with a few minor changes (removed the DesktopBuildPackageLocation and set the /v[erbose] switch to m[inimal]):

msbuild Website.csproj "/p:Platform=AnyCPU;Configuration=Release" /t:Package /v:m

The second step is to use the generated script, ci-stage.deploy.cmd. I recommend to run the script by hand once using the /T switch just to make sure everything looks alright. In our case we found out that the deployment of the package would have deleted a lot of files, most of all images, from our website. This was not what we wanted! After a quick search I found this question on stackoverflow.com: MSDeploy: “Leave extra files on destination” from command line? So I added this switch to the parameters list in the build step configuration as follows:

web-deploy

That’s it! This is all we need on the command line to generate a package that is ready for deployment on the staging server. There are a few more necessary settings, including the DesktopBuildPackageLocation, that can be found in the Package settings window inside the project properties of the web application project:

  • the DesktopBuildPackageLocation can be set here instead of on the command line
  • the website and application name on the destination IIS server that this package should be installed to
  • some more options like excluding debug symbols etc.

package-settings

These settings are saved in the project file and will be used during generation and deployment of the package.

That’s all I have to say right now.

Happy Coding, Oliver

Tags:

ASP.NET | Automation | Productivity | Software development | Web Applikationen

Vorsicht bei String als Lock-Objekt

by Oliver Sat, March 20 2010 14:59

Wenn man denn unbedingt einen String als Lock-Objekt nutzen will und man sich davon überzeugt hat, nicht den Type eines gerade durch die Methode schwirrenden Objektes zu nutzen (das könnte ja jeder machen ;-)), dann sollte man auf jeden Fall an String.Intern(myLockString) denken!

Wo man kann, sollte man also weiterhin einen einfachen object-Lock nutzen:

   1: private static readonly object _lock = new object();

Aber man darf dann nicht vergessen, dass alle Threads, die auf den Code im Lock zugreifen von einem einzigen Lock bedient werden. Wenn man die Threads aber in Gruppen aufteilen kann, die unabhängig voneinander arbeiten können, ist diese Beschränkung zu scharf. Ich suchte also einen Weg, einen eindeutigen Lock für jede dieser Gruppen (hier: TranslationGroup) zu generieren, der von allen Instanzen geteilt wird. Der Gruppenidentifikator heißt hier _pageNameKey.

Beispiel:

   1: private const string _migrateLock = "9B90360AAF4342798A739DA2D85AF5AF"; // guid
   2:  
   3: private string _pageNameKey = ...; // can be the same in multiple parallel threads
   4:  
   5: public void Migrate(string oldGroupName, Dictionary<string, string> controlIdsOldToNew)
   6: {
   7:     lock (string.Intern(_migrateLock + _pageNameKey))
   8:     {
   9:         // do something not here that should not be done in parallel
  10:     }
  11: }

Ließe man in Zeile 7 das String.Intern(…) weg, würde bei jedem Aufruf der Methode ein neues String-Objekt generiert – was für dem Lock seine Existenzberechtigung raubt.

Gruß, Oliver

Technorati-Tags: ,,,

Tags:

.NET | ASP.NET | Software development

“NEUE” ASP.NET AJAX Version (seit September)

by robert Wed, December 02 2009 03:16

Hatte das gar nicht mitbekommen, seit September gibt es eine neue ASP.NET AJAX Version. Änderungen lassen sich hier nachlesen: http://www.asp.net/AJAX/AjaxControlToolkit/Samples/Default.aspx. Auch interessant, das ASP.NET AJAX Toolkit ist jetzt Teil der ASP.NET AJAX Library.

Es wurden 20 Bugs gefixed und es gibt ein neues VS Projekt Template für Visual Studio. @Stefan, hätte Dir das geholfen?

Neu: Seadragon ist wirklich spannend. Wir könnten das sowohl für Produkt-Bilder als auch für Stellplatzkarten einsetzen.

Seadragon-Ajax

Neu: AsynFileUpload, vielleicht lohnt es bei Gelegenheit mal mit unserer Lösung zu vergleichen. Wobei in Opera der Upload-Button schon nicht funktionierte – gleich ein schlechtes Zeichen. Insgesamt würde ich mal vermuten, dass unsere Lösung in Speak-Lib deutlich besser ist :-)

Tags:

ASP.NET

Javascript in UserControl in UpdatePanel

by Oliver Sat, November 28 2009 15:17

Wenn man weiß, wie es geht, ist alles einfach. Aber JavaScript in UserControls innerhalb eines UpdatePanels funktionstüchtig zu bekommen hatte für mich - bis heute! - immer etwas mit Magie zu tun... Hinzu kam heute, dass ich noch Sys.UI.DomElement-Methoden nutzen wollte (aus der AJAX-Bibliothek). Folgende Versuche habe ich gemacht:

1. Page.ClientScript.RegisterClientScriptBlock(GetType(), "helpful_css_" + ClientID, script, true);
2. ScriptManager.RegisterClientScriptBlock(this, GetType(), "helpful_" + ClientID, script, true);
3. Page.ClientScript.RegisterStartupScript(GetType(), "helpful_css_" + ClientID, script, true);
4. ScriptManager.RegisterStartupScript(this, GetType(), "helpful_css_" + ClientID, script, true); // <-- this is it!

 

Zu 1. und 3. ist zu sagen, dass sie innerhalb einen UpdatePanels nur nach dem ursprünglichen PageLoad funktionieren, nicht nach einem asynchronen Postback. Also fallen sie raus. Außerdem fallen 1. und 2. weg, weil an der Stelle, an der der hier übergebene JavaScript-Code in die Seite generiert wird, der $Sys-Namespace noch nicht definiert ist (ähnlich wie hier: http://encosia.com/2007/08/16/updated-your-webconfig-but-sys-is-still-undefined/).

Bleibt nur 4. Und 4. funktioniert sowohl nach dem initialen PageLoad als auch nach einem async Postback und man kann $Sys verwenden. Wunderbar!

1. oder 2. bleiben allerdings nützlich, wenn man Custom Javascript in die Seite einfügen will, um es weiter unten auf der Seite zu nutzen. Wahlweise kann man natürlich auch die Methode RegisterClientScriptInclude nutzen, um das Javascript in eine eigene Datei auszulagern. Soviel dazu.

Oliver

Tags: , ,

ASP.NET | Tips & Tricks | WEB 2.0 | Web Applikationen

CPE (CollapsiblePanelExtender) per JavaScript öffnen

by Oliver Thu, November 12 2009 20:53

Bevor ich noch x Mal durchs Web klicke, hier der Einzeiler:

hplCatBox.Attributes["onclick"] = "javascript:$find('" + cpeInqCat.BehaviorID + "').expandPanel(new Sys.EventArgs)";

Tags: , ,

ASP.NET | Tips & Tricks | WEB 2.0

32-bit DLL in 64-bit WebApp: An attempt was made to load a program with an incorrect format

by Oliver Fri, October 30 2009 16:36

Die folgende Fehlermeldung erhielt ich heute von unserem IIS7, als ich Camping.Info starten wollte:

Server Error in '/' Application.

Could not load file or assembly 'Microsoft.Cci' or one of its dependencies. An attempt was made to load a program with an incorrect format.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.BadImageFormatException: Could not load file or assembly 'Microsoft.Cci' or one of its dependencies. An attempt was made to load a program with an incorrect format.

Also das Orakel gefragt und u.a. das hier gefunden: http://forums.asp.net/t/1358032.aspx

Standardmäßig unterstützt ein 64-bittiger IIS 7 keine 32-bit-Module (u.a. DLLs). Man kann es ihm aber einfach beibringen :-)

Im IIS-Manager den gewünschten ApplicationPool auswählen und in den Advanced Settings die folgende Einstellung vornehmen:



Der Vollständigkeit halber hier noch ein Link zur Anleitung für den IIS6 auf Windows 2003 Server: http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/405f5bb5-87a3-43d2-8138-54b75db73aa1.mspx?mfr=true

Happy Coding!

Oliver

Tags:

.NET | ASP.NET | Software development | Web Applikationen

Cache + NHibernate.HibernateException: Illegal attempt to associate a collection with two open sessions + NUnit

by Oliver Tue, April 07 2009 18:55

In unserem Projekt verwenden wir einen Cache zum Vorhalten von oft benutzten Daten, u.a. für Länder und Bundesländer. Für die Übersetzungen derselben in viele Sprachen habe ich einfach je eine einfache Tabelle angelegt mit einer Referenz auf das jeweilige (Bundes-)Land, dem Sprachen-Isocode und dem Namen in der entsprechenden Sprache. Per NHibernate-Mapping dann die Übersetzungen als <bag> an das (Bundes-)Land angeheftet - fertig.

Beim NUnit-Testen bekam ich nach dem Einbau der Übersetzungen beim Update von (Bundes-)Ländern allerdings diesen Fehler:

NHibernate.HibernateException: Illegal attempt to associate a collection with two open sessions.

Offensichtlich waren die neuen Collections an den Domainobjekten Schuld. Was war passiert? Beim Aufrufen der Frontend-Seiten kam dieser Fehler nicht...

Nun, die Exception gibt uns einen guten Tipp: Wir haben zwei offene Session, mit denen die Collection von Übersetzungen assoziiert werden soll. Aber warum nur in den Tests und warum dort überhaupt?

Eine erste Erklärung: Im Frontend benutzen wir für die NHibernate-Sessions das One-Session-Per-Request-Pattern, so dass alle Manipulationen an den Session-Objekten (inkl. Update) innerhalb derselben Session passieren. Außerdem funktionierte dort das Dispose der Session zuverlässig. In unseren Tests war das ja leider nicht der Fall, so dass wir also irgendwie eine geöffnete Session nicht schließen. Durch die Benutzung eines Caches kamen wir in die Verlegenheit, Länderobjekte in einem Testcase in den Cache zu schreiben und diese in einem folgenden Testcase wiederzuverwenden. Als an dieser zweiten Stelle ein Update passieren sollte, knallte es, denn das Objekt im Cache hielt eine Referenz auf NHibernate-Collection, die noch mit einer vergangenen Session assoziiert war. Beim Update versucht NHibernate aber, dieselbe Collection an die aktuelle Session zu binden, was eine Exception wirft.

Der Cache war also Schuld. Oder zumindest war das mein erster Gedanke. Und tatsächlich: Wenn ich im [SetUp] der NUnit-TestFixture ein Cache.Clear() aufrufe, verschwindet der Fehler! Keine Überraschung, denn jetzt benutzen wir ja keine Objekte aus "alten" Sessions mehr. Aber Moment mal, im Frontend benutzen wir doch denselben Cache?! Und da gibt es diesen Fehler nicht.

Nach ein wenig Haareraufen, kam ich dann zurück zu der Feststellung, dass es ja ein Problem mit einer offenen Session gab. Also setzte ich da nochmal an.

Für das Lifecycle-Management unserer Serviceklassen benutzen einen Autofac-IoC-Container. Dieser stellt auch die NHibernat-Session bereit. Zum Benutzen der Services haben wir eine ServiceLocator-Klasse, die uns die vom Container generierten Serviceinstanzen auf Anfrage zurückgibt. Um diese Architektur in den NUnit-Tests zu nutzen, schrieben wir flugs eine Basisklasse BaseTest, von der alle Testklassen ableiten und fortan Zugriff auf alle Services haben. Das Lifecycle-Management des Containers wird dort ebenfalls verwaltet.

        public BaseTest()
        {
            InitializeContainer();
        }

        ~BaseTest()
        {
            DisposeContainer();
        }

Wie sich nun herausstellt, eine dumme Idee! Beim Debuggen stellte ich fest, dass der Konstruktor zwar von jeder TestFixture (also jeder Testklasse, die selbst mehrere Tests enthält) in Reihenfolge aufgerufen wird, dass aber der Destruktor einer Testklasse keineswegs zuverlässig vor dem Konstruktor der nächsten Testklasse ausgeführt wird! Damit wird der Container der ersten Testklasse nicht Dispose'd und in Folge die NHibernate-Session nicht geschlossen, so dass wir den o.g. Fehler bekommen.

Wahrscheinlich ist das Ausführen von Businesslogik in einem NUnit-TestFixture-Konstruktor bzw. viel schlimmer im Destruktor ein absolutes NoNo. Dafür gibt es schließlich die Attribute [TestFixtureSetUp] und [TestFixtureTearDown]. Mit dem folgenden Code funktioniert denn auch alles wie gewollt:

        [TestFixtureSetUp]
        public virtual void TestFixtureSetUp()
        {
            InitializeContainer();
        }

        [TestFixtureTearDown]
        public virtual void TestFixtureTearDown()
        {
            DisposeContainer();
        }

Ein kleiner Wehrmutstropfen bei dieser Lösung ist, dass man beim Testklassen-Schreiben jetzt daran denken muss, diese Methoden zu überschreiben, wenn man weitere Funktionalität in [TestFixtureSetUp] und/oder [TestFixtureTearDown] benötigt...

Alles Gute, Oliver

Tags:

ASP.NET | NHibernate

TranslatableUserControl in Repeater + TranslationPanel

by Oliver Tue, March 24 2009 14:30

Nach zwei unabhängigen Fällen sieht es nun deutlicher danach aus, dass o.g. Kombination Probleme aufwirft und zwar mit dem EventHandling auf Serverseite.

Genauer: Es werden keine Events für DropDownListen, LinkButtons u.ä. gefeuert, obwohl sie sollten. Das macht die Funktionalität im TranslationPanel kaputt.

 

Spannend ist folgendes Phänomen: Auf einer Seite mit der genannten Kombination und geöffnetem TranslationPanel geschieht folgendes:

  1. Erstes Mal die DDL benutzen -> nicht (optisch; im Fiddler sieht man aber einen Request)
  2. Zweites Mal die DDL benutzen -> die Seite wird neu geladen (komplett!)
  3. Drittes Mal die DDL benutzen -> funktioniert so, wie es soll
  4. Ab jetzt funzt es so wie es soll. Warum?

Das also als Notiz an mich selbst. Mal schauen, was draus wird.

Oli

Tags:

ASP.NET | Web Applikationen