Adrian Hara

Working through the .NET maze

  Home  |   Contact  |   Syndication    |   Login
  40 Posts | 0 Stories | 71 Comments | 10 Trackbacks

News

Archives

Post Categories

Thursday, July 02, 2009 #

In case you missed it, a really good whitepaper by Rick Strahl and Michele Leroux Bustamante was published a few days ago on dealing with localization in WPF. At 66 pages long, I found the whitepaper really good, as it takes a hands-on, no-bull approach (in the familiar style of the authors ;) ) on the subject. There is also sample code for download containing examples and some goodies.

For those interested, here's a quick summary of the paper (which is NOT supposed to replace reading it):
  • you can use LocBAML for localization, which is an unsupported tool from Microsoft. Basically it sucks, but if you have a finished WPF application which you wrote without localization in mind, this tool might help you somewhat.
  • you can use the plain ol' ResX resources, together with the {x:Static} markup extension to access them. This works loads better than the LocBAML approach, but has the major disadvantage that you're limited to localizing only simple string properties, because the {x:Static} markup extension doesn't use type converters (so, for example, localizing a Thickness might be tricky if not impossible).
  • you can use the plain ol' ResX resources, but this time together with a custom markup extension which knows how to use type converters. The authors of the whitepaper kindly supply for download such an extension, which does quite a lot of other nice stuff besides the type converter business. I'd say this is the way to go :)
  • you can use a mechanism similar to the one used in Winforms and Asp.net applications where you could have a label, say "lblTitle" and then resource keys with sub-properties, like "lblTitle.Text", "lblTitle.Width" and so on. To do this in WPF the authors again supply some attached properties which do some magic to get this working. While this looks pretty cool, unfortunately they also don't support type converters, so you're stuck to localizing "simple" stuff.
Again, if you're interested in the subject, please read the whitepaper as it provides a lot more subject matter than the actual possibilities of localization and I'd go as far as to say it's a mandatory read for anyone who has to localize WPF applications.

Codeplex link:

http://wpflocalization.codeplex.com/


Thursday, May 21, 2009 #

If you ever need to deploy a WCF service to SharePoint 2007, there's plenty of information about how to do it on the web. Including how to make it work with Integrated Authentication or Forms Authentication. And it's all great and works.

However, I had to make the service "run" under the context of a particular web (which was actually a subweb). By this I mean that if I have a root site and a subsite and my WCF service calls SPContext.Current.Web I'd want it to return the subsite, not the root site.

Well, I deployed the service in a subfolder of the "_layouts" folder and it worked fine. Except that SPContext.Current.Web always returned the root site, not the subsite. Surprisingly (or maybe due to my lack of google search skills  ), there was little to no information available on the net on how to do this. All I found was something along the lines of "if deployed in layouts or vti_bin folder, a service runs under the context of the specific web". Pretty generic for me. So after some headbanging and some more searching, I finally found out the problem. And it's so obvious and simple that I'm really surprised there's very little information around about this, so I thought I'd note it here... for posterity like

In the service client, instead of having the service reference like so:

http://domain/_layouts/somefolder/MyService.svc

have it like so

http://domain/SUBSITENAME/_layouts/somefolder/MyService.svc

That's all there is to it, just include the subsite (or subweb) name in the url, as the services in the _layouts folder are available from every subweb, and then the SPContext.Current.Web will return the correct web.

Thanks to Serge van den Oever, on whose blog I found out about this!


Wednesday, April 29, 2009 #

This has probably been covered in some tutorial videos or "silverlight 3 what's new"s, but due to the fact that I spent the better half of an hour googling about ways to do it, I guess also posting it here for reference won't hurt.

Problem: when calling a WCF service from Silverlight 2, if the service throws an exception, in Silverlight you get a CommunicationException with some mumbo-jumbo in it, not the actual exception or Fault declared on the service.

Fix: in Silverlight 3 there is built-in support on the client-side for FaultException and FaultException<T>. All you need to do it tweak the WCF service a bit so it returns a 200 response code, instead of the default 500 response code, so you actually get the FaultException, as explained here: http://msdn.microsoft.com/en-us/library/dd470096(VS.96).aspx

Also, some more info on this and other new stuff in SL3: http://blogs.msdn.com/silverlightws/archive/2009/03/20/what-s-new-with-web-services-in-silverlight-3-beta.aspx

Friday, April 17, 2009 #

If you have a Silverlight 3 Beta 1 DataGrid that, for some reason, is not refreshing when its bound ItemsSource changes, like so...

 <data:DataGrid ItemsSource="{Binding Path=Parameters}" AutoGenerateColumns="false" IsReadOnly="True" >
                     
...you might want to know that it's a bug in the beta. I actually spent about 2 days on and off on this issue, researching a workaround and googling for it with no results. Only today, more on a guess than anything else, I did this:

 <data:DataGrid ItemsSource="{Binding Path=Parameters, Mode=TwoWay}" AutoGenerateColumns="false" IsReadOnly="True" >

It's obviously stupid to have a readonly grid with TwoWay binding but, to my surprise, it started working ;)

Armed with this knowledge, I refined my google searches and voila, the following thread magically appeared. The link to the repro in the thread isn't working, but I assume it's about the problem at hand. So, anyway, just use TwoWay binding in the meantime ;)

update: I also noticed the same behavior for binding paths that go "deeper" in the bound object (e.g. "SelectedParameter.Value"), even if they are NOT used for the DataGrid (i.e. I have a case that's not working for a Button). Luckily, the same workaround works there too. The downside is that you have to be careful to not really use the TwoWay binding, thus inadvertently updating your ViewModel.

 


Wednesday, April 15, 2009 #

Small gotcha today after installing the Silverlight 3 beta "suite" - runtime, visual studio 2008 tools and blend 3 preview.
(side note: if you install the Silverlight 3 runtime BEFORE the visual studio 2008 tools, then the tools installation fails because it tries to reinstall the runtime... so either uninstall the runtime or don't install it at all standalone, the tools installation will take care of it)

Opening an existing Silverlight 2 project triggered the conversion to SL3, which worked fine. Suprisingly though, the service reference that the project had to a WCF service didn't work anymore. Looking at the generated reference file I found it to be empty (with just some comment at the top). Doing an "updated service reference" from VS2008 didn't do anything much so I was stuck.

Fortunately, an entry on Jimmy Lewis' blog about a similar situation led me on the right way to the fix: uninstall the Silverlight 2 SDK. After I did this and went back to "update service reference" the code was generated correctly and worked fine.

Hope this helps someone.

Tuesday, March 03, 2009 #

Today I experienced what appears to be a small WSSF gotcha :)

If you have a Data Contract which has a Data Member called Value (for example contract is ParameterInfo, so having a member called Value might make sense), the code that gets generated by WSSF looks like so:

private string value;

[WcfSerialization::DataMember(Name = "Value", IsRequired = true, Order = 1)]

public string Value

{

  get { return value; }

  set { value = value; }

}    


Obviously, what happens is that in the property setter...well...nothing happens ;). The code generator doesn't prefix the assignment with "this", so we'd have "this.value = value", meaning that the data member will always have the default value for its type (in this case, because it's string, that'd be null).

Now I admit that having a property named "Value" is questionable, but I might argue that, using WSSF, the whole point is to model the contracts in an implementation-oblivious manner, so it should be perfectly fine to have such a data member name.

Anyway, you probably can poke into the code generation and fix this, and probably I would've tried it, but it turns out that my "Extending the WSSF Hands-On-Labs.chm" is not working ("page cannot be displayed"). So if anyone has a fix for this (other than renaming the data member), please shout ;)

Friday, February 20, 2009 #

It's been there for a while, but I just found out about it yesterday. There's a free ebook from InfoQ called "Domain Driven Design Quickly", which I think is a great introduction to DDD. It talks about the main concepts of DDD and presents them in a very focused yet easy and enjoyable-to-read way, with little or no actual code in one particular technology.

The book is a light read and, at 106 pages, it's packed with great info and insight about modelling, communication within teams, organizing your business logic etc. Even if you won't do DDD, I think the book is still well worth reading.

http://www.infoq.com/minibooks/domain-driven-design-quickly

Sunday, January 25, 2009 #

Last year I had the pleasure of receiving a copy of the book Entity Framework Tutorial by Joydip Kanjilal from Packt Publishing and I recently got the time to read it so I thought I'd share my impressions on it.

I have to confess to having great expectations from the book: it was from Packt, the publishers of the excellent Programming Windows Workflow Foundation by K Scott Allen and it was on Entity Framework, a topic which I had been meaning to read up on for a while.

Like the WF book, EF Tutorial takes a very hands-on approach to the problem. In fact, since the book itself is quite small (some 200 pages), there's clearly no space to waste with marketing talk. Also like the WF book, EF Tutorial comes loaded with a lot of screenshots and code examples and is nicely structured so it progressively  but steadily goes from light topics to some more advanced ones. Unfortunately, the similarities with Programming WWF stop here.

In all honesty, I was quite dissapointed with the book. While the WWF book (no hidden advertising here, honest! :)) was also short, it was incredibly focused and, to me, made it seem like every word counted: there were no rants, no superfluous information, just facts, hints, tips, code (and some insights a la K. Scott Allen ;)). Straight up quality! In EF Tutorial, on the other hand, a lot of the time I got the feeling that either a) stuff was written just to fill the chapter or b) the author had no knowledge of the "inner workings" of something so instead he just wrote some generic stuff, without really explaining the "how"s and "why"s and moved on.

Examples:
  • the edmx schema is glossed over very briefly - there are no explanations as to what each element means or why it was designed this way. Instead, we get the whole triplet thing (CSDL, SSDL, MSL) several times over, presented as a big thing. I'm sure it really is, but I'd like to know why ;)
  • there's lots and lots and *lots* of xml mapping code - I'm not quite sure if anyone wanting to try to practice what's presented in the book will write all that code by hand in visual studio... reading it from the book. Also, as far as I'm concerned, presenting xml mapping code over several pages doesn't exactly help improve its readability. I would've certainly liked some highlights and some words instead of the same complete mapping code over and over.
  • there's lots of screenshots - that could actually be a good thing, and in several places it is. But having screenshots of how to install the Entity Framework or .NET Framework 3.5 SP1 (yea, I mean screenshots of the actual install wizard!) is a bit overdoing it. Still, although annoying and pointless at times, there are quite a few helpful ones, like the EF designer and mapping details.
  • there was more than one place in the book where the topics presented ended quite abruptly, without any real explanation - chapter 7, attaching entities or implementing complex types in the edm (where I found out that "a complex type is actually a structures property" and that's it) come off the top of my head.
Another negative point of the book is that, at least to me, it seems it never got a serious review (or reviews) before getting published: there are a lot of English language mistakes, which I can live with, but also a lot of code (or xml) mistakes. The latter part is especially bad as the book's target is someone who's completely new to EF and he or she might be put off or sent on the wrong track by these mistakes. It's too bad, because more often than not, they are not reasoning mistakes but probably just copy/pasting or late night working and, in my opinion, could have easily been corrected. Also, there are some rather dubious claims here and there, like mapping of stored procedures which return a scalar value (and thus don't have an entity set), for which the author's solution is (warning! wtf moment coming up!) to create a dummy table in the db and an entity type for it ;)

So, after all this, you might be wondering if the book's ANY good: it is. It's just that you have to know how to take it: for me it was reading through it and whenever I felt that a topic was not explained enough or contained dubious information I read about it on the net. So the book was more like an overview, a pointer towards the right directions to read and help to structure learning EF. And maybe this was its purpose. BUT, if you plan on reading this on a plane trip of a few hours, with no connection to the internet to look stuff up (and are the type of person who doesn't take things for granted but wants to knows the reasoning behind and inner workings of them), maybe you should look elsewhere.

Friday, November 07, 2008 #

I just installed .net 3.5 sp1, vs 2008 sp1 and thus got access to the latest version of the entity framework. Since I hadn't played with it before, I decided to try. My scenario was more than simple, bordering on lame: save some data in one table in a sql compact database.

So I quickly generated the .edmx using the wizard (which is pretty cool btw) and just ran the app. Unfortunately, when the ObjectContext derived class would be instantiated, i got the following FileNotFoundException:

"Could not load file or assembly 'System.Drawing, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' "

This was pretty strange, since I was targeting the 3.5 version of the framework and I didn't see any explicit need for the 1.0 version of System.Drawing.

After some digging, I came across this MSDN forums thread: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3926655&SiteID=1 . Craig Lee's post is particularly enlightening: "There is a note in the above MSDN page that outlines the effect of keeping the default "res://*" value that the designer writes out.  In your case, you should change this so that it uses explicit paths.  I suspect that because you have "res://*", the EF runtime is scanning your DLL and all referenced DLLs for any EF runtime artifacts".

As it turns out, I was actually using a 3rd party component which had a reference to System.Drawing 1.0 and, because of my default generated connectionstring by the entity model wizard (which should just work, right? :)) it looks like the EF was crawling all referenced assemblies to look for its definition files, ending up by not finding the 1.0 version of System.Drawing because...well... I haven't the 1.0 version of the framework installed :)

Fix was easy: change the connectionstring to use the assembly name, instead of the wildcard search, like so "res://SomeAssembly/<whatever else was generated>".

Hope this helps.

Thursday, September 11, 2008 #

...is not straightforward. The problem is that many (all?) of log4net's classes are not serializable. This includes the implementations of the ILog interface, which means that if you plan on using log4net loggers in your SharePoint workflow code like so...

private ILog logger = LogManager.GetLogger(typeof(Foo));

...you're out of luck. As soon as the workflow is dehydrated, you'll get a nice exception stating that type X (probably LogImpl) is not serializable.

 

One solution to this problem is to recreate the logger each time the workflow is rehydrated or, more simply put, lazy-initialize the value of the logger: if it's null, create it, if not, return it. Although this could be done separately wherever you need it in the project (via perhaps a private property of each class), I thought it would be nicer to abstract this away in a little library. I called it SPLog4net (how imaginative!) and you can find it on CodePlex.

 

The library has two classes (ok, three, including the msdn copy/pasted TraceProvider):

1. LoggerWrapper  - this class implements the ILog log4net interface and acts as a thin wrapper over a real log4net logger. Its main purpose is to lazy-initialize the log4net logger if it's not yet initialized. The usage pattern is very similar to the usual log4net usage pattern:

log4net standard -             private ILog logger = LogManager.GetLogger(typeof(Foo));

LoggerWrapper -                private ILog logger = new LoggerWrapper(typeof(Foo));

    The other little thing that the logger wrapper does is it prefixes each log message with a...umm... prefix. The idea is that, being in SharePoint, you might want to log messages to the ULS log (more on that in a minute). But, as you probably know, the ULS log files are not the most user-friendly things around. So, if you use a utility to view them, such as the excellent SPTraceView, you might want to take advantage of its message filtering feature and have it only display messages starting with a certain.... ahem... prefix :)

2. UlsLogAppender - to be honest I think it's quite strange such a thing doesn't yet exist (I couldn't find it on google anyway ;)), so I wrote it. This class represents a log4net appender that writes to the SharePoint ULS log. You might want to take a look at the source and tweak it, because it has a method which converts the log4net severity levels to SharePoint severity levels and the said method is... well... not totally complete. (which means I only mapped severals level which I thought of use, but you might need more). To use it just make sure you have it in the GAC (or application bin) and added to the log4net config file (or section in the web.config file of your web application).

 

Naturally, you can use the two independently and not only from a workflow.

 

Hope this helps someone and please let me know if I'm either reinventing the wheel or inventing a square one ;)


Wednesday, August 13, 2008 #

Searching for a way to check if a workflow is running on a list item (useful, for example, in an item event handler), I found some code like this:

        public static bool IsWorkflowRunning(SPListItem listItem, Guid workflowId)
        {
            foreach (SPWorkflow workflow in listItem.Workflows)
            {
                if (workflow.ParentAssociation.BaseTemplate.Id == workflowId &&
                    workflow.InternalState == SPWorkflowState.Running)
                {
                    return true;
                }
            }
            return false;
        }

What this code does is iterate the workflows list for the item (point to remember: listItem.Workflows contains not only running workflows, but also cancelled or otherwise) and checks the InternalState for the given workflow id. While it might seem ok, as I found out the hard way, it's not. Correct way to check is like so:

        public static bool IsWorkflowRunning(SPListItem listItem, Guid workflowId)
        {
            foreach (SPWorkflow workflow in listItem.Workflows)
            {
                if (workflow.ParentAssociation.BaseTemplate.Id == workflowId &&
                    (workflow.InternalState & SPWorkflowState.Running) == SPWorkflowState.Running)
                {
                    return true;
                }
            }
            return false;
        }

The somewhat subtle thing to notice is the check: it denotes that the SPWorkflowState is an enum decorated with the [Flags] attribute, which means that the check has to be changed like above.


Friday, August 08, 2008 #

If you're developing workflows for SharePoint 2007, you are probably familiar with the Item property of the SPWorkflowActivationProperties class. Usually your workflow gets an instance of SPWorkflowActivationProperties when the OnWorkflowActivated activity executes, which is bound to a public field on the workflow class so you can hang on to it for the lifetime of the workflow. Perhaps one of the more useful properties of this class is Item, which returns an instance of SPListItem, representing the list item on which the workflow runs.

This is all fine, but maybe for more than two step workflows or for a better design/maintainability of the code, it makes sense (and I try to do it) to encapsulate the properties (access to) and whatever manipulating logic of the list item in a business class, representing the business concept you're dealing with (for example, I find that using classes like Contract or Customer to work with the item's metadata is nicer than working with SPListItem). A small problem with this approach is that you can't simply wrap the SPListItem instance in your business class, because it's not [Serializable], which means that you'll get some nasty error when SharePoint tries to dehydrate your workflow. In light of this, what I do is wrap a reference to the SPWorkflowActivationProperties instance and have a private property called ListItem which just calls the Item property, like so:

        private SPListItem ListItem
        {
              get { return workflowProperties.Item; }
        }

This worked very good for me until a few days ago, when I had a workflow that runs over items in a document library and had to replace the actual document file at some point (like replace a .doc with a generated .pdf from it). The replacement worked fine (I won't go into details, google has answers ;)), but after the replacement, if, during the same call (meaning before the workflow gets dehydrated again), I wanted to do something else with the file, or even call Update() on the SPListItem, exceptions got thrown, with messages like "the file has been modified by blah blah". After some investigations I realized that that although I was modifying the SPFile (which i got from SPListItem.File) and calling Update() on it, this didn't do anything (for example, looking at the SPListItem.File.Name, I'd still get "somepath/somename.doc" instead of "somepath/somename.pdf"). Also, the next logical solution, calling Update() or SystemUpdate() on the SPListItem itself would throw, as stated above. However, if, after modifying and/or replacing the file, I wouldn't do anything else and just let the workflow dehydrate, whenever it would get hydrated again, everything would work: this makes sense, since the SPListItem is not serialized and would be created again after rehydration.

So, the next step would be looking when is the SPListItem of SPWorkflowActivationProperties created and how can we refresh it "on the fly". Reflector shows the following:

ItemProperty

As you can see, the item is actually cached after the first call. This means that when updating the SPFile associated with the item and calling Update() on it, since this doesn't cause the item itself to update (or at least update its SPFile), we have to somehow refresh the whole SPListItem. While the SPWorkflowActivationProperties class doesn't expose some method to do this, it's easy to do with reflection: just set the m_item to null and on the next call to the property, it will be recreated. One thing to note here is that, as seen in the picture above, there are two cases where the item can be refreshed:

  • for non super user workflows, a call is made to the list directly for the item
  • for super user workflow, a call is made to SPWorkflow.ParentItem

Looking at the SPWorkflow.ParentItem with reflector, it turns out that the value backing the property is also cached:

ParentItemProperty

So, in order to be fully covered for all situations, the method to invalidate the list item and make sure it's refreshed should look like so:

        private void InvalidateWorkflowPropertiesItem()
        {
            FieldInfo parentItemField = workflowProperties.Workflow.GetType().GetField("m_createdParentItem", BindingFlags.NonPublic | BindingFlags.Instance);
            parentItemField.SetValue(workflowProperties.Workflow, null);

            FieldInfo itemField = workflowProperties.GetType().GetField("m_item", BindingFlags.NonPublic | BindingFlags.Instance);
            itemField.SetValue(workflowProperties, null);
        }

 

Normally, you should just call this method after doing something that causes the SPFile for an item to change, but I guess there are probably other situations where refreshing of the list item would come in handy.

ps: another approach to the basic problem that the SPListItem isn't serializable would be to keep it's unique id and retrieve it like that every time you need it, but this could mean implementing the caching and getting mechanisms yourself if you want to have some business class wrapper over it (meaning that you can't just GetByUniqueId() it every time you need to change a property, because you'd end up with a different instance every time and would have to be careful about updating it)


Wednesday, July 16, 2008 #

The following gotcha was a pretty nasty one for me (I'd say about 2 hours of lost time worth). Supposing you have some content types deployed at the site collection level and you want to add them to a document library of a sub-site (web) plus configure them a little bit, like so:

            foreach (string contentTypeName in ToAttachToListContentTypeNames)
            {
                // Get site collection content type
                SPContentType siteContentType = web.AvailableContentTypes[contentTypeName];

                // Add to library and configure
                SPContentType libraryContentType = library.ContentTypes.Add(siteContentType);
                libraryContentType.DocumentTemplate = string.Empty;
                libraryContentType.Update(false);

               // Maybe some more work, like adding some linked fields here?...
            }

This turned out to result in some weird behavior: supposing I had 3 content types to add to the document library, the first one of them (which would also be the first one added) always got some of its properties wrong. For example, even though the code sets the DocumentTemplate explicitly to string.Empty, the resulting document library content type (the first one added) would always get the "template.doc" template instead. Weird stuff happened also to some linked fields I was adding, like the first content type would always get one linked field less than the others

Now, unfortunately (or fortunately?) I didn't have the time to Reflect over the sharepoint code to see why this happens, but after some trial-and-errors I found that it seems the call to "library.ContentTypes" somehow merges the "web.AvailableContentTypes" with the library's content types. Since this call is done in the foreach loop, it means that, for each subsequent loop step, some settings for the content types added in prior steps are "merged" (read overwritten) with the ones of the site collection content types. What's weird is that:
1. It only seemed to happen for the first added content type (so the first step in the loop). The other two were fine.
2. It only "merged" some of its properties, like DocumentTemplate and *some* field links, not all

Anyway, the fix is to take the call to "library.ContentTypes" out of the loop and assign to a variable. Then you can just iterate over that "local" collection and do stuff:

            SPContentTypeCollection availableContentTypes = web.AvailableContentTypes;
            foreach (string contentTypeName in ToAttachContentTypeNames)
            {
                // Get site collection content type
                SPContentType siteContentType = availableContentTypes[contentTypeName];

                // Add to library and configure
                SPContentType libraryContentType = library.ContentTypes.Add(siteContentType);
                libraryContentType.DocumentTemplate = string.Empty;
                libraryContentType.Update(false);
            }
            library.Update();

            // Add some fieldlinks to the library content types
            SPContentTypeCollection libraryContentTypes = library.ContentTypes;
            foreach (string contentTypeName in ToExtendWithFieldLinksContentTypes)
            {
                SPContentType libraryContentType = libraryContentTypes[contentTypeName];

                SPFieldLink someFieldLink = new SPFieldLink(web.Fields["some field"]);
                libraryContentType.FieldLinks.Add(someFieldLink );

                // Maybe add some more...
              
                libraryContentType.Update(false);
            }

This seems to work, but granted, is strange enough to do...

Sunday, July 13, 2008 #

To paraphrase a heavyweight blogger, this is the first post in (probably) a infinite number of posts about Sharepoint Gotchas.

Today's gotcha: why doesn't my custom content type have any columns?

It took me quite some time (including a little foaming-at-the-mouth time) to figure this out. Suppose you declare a custom content type in CAML that you'd like to just inherit all its columns from its base content type, without defining any of its own, like so:

<ContentType
      ID="0x010100D4C041607B6A400dB211CC424B2D153BAB"
      Name="My Content Type"
      Description="Foobar content type"
      Version="0"
      Group="Foobar Group" >
  </ContentType>

All is well, it deploys fine, just that when you want to use it it turns out it didn't actually inherit any of its parent type's columns, but rather has an empty Columns list. The problem is pretty subtle, if you ask me, and stems from the fact that you should've declared the content type like so:

<ContentType
      ID="0x010100D4C041607B6A400dB211CC424B2D153BAB"
      Name="My Content Type"
      Description="Foobar content type"
      Version="0"
      Group="Foobar Group" >
    <FieldRefs/>
  </ContentType>

Did you spot the difference yet? It's the <FieldRefs/> element. So even if you don't declare any columns, you still have to include the empty FieldRefs element. What's even nastier is that the schema validates without it :(...

Tuesday, July 08, 2008 #

Although this might be present in other versions of DNN too, I've found that if I add the attribute "theme=something" to the <pages> element in web.config, I get a compilation error in FCKImageGallery.aspx:

"Error    129    Error parsing attribute 'theme': The 'theme' property is read-only and cannot be set.    "

That is because the page's codebehind class derives from the FCKGallery class, which already has a Theme property. This is unfortunate, because, although DNN has its own skinning mechanisms, you might also want to use the standard asp.net themes (like for example for CSS Friendly Control Adapters).

The only workaround I've found so far for this is to add a web.config file to the Fck folder that looks like so:

<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
      <pages theme=""> </pages>
    </system.web>
</configuration>

This "local" config file basically overwrites the application web.config file and tells the compiler that themes are not needed for the local Fck folder.

As I'm new to DNN, maybe I'm missing something, so if you know anything about this, please let me know ;)