 |
| Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | FileType | xsn | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.2 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.3 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.4 | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsx | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsb | 255 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsx | 256 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsb | 256 |
|
|
| Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | FileType | xsn | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.2 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.3 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.4 | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsx | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsb | 255 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsx | 256 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsb | 256 |
|
|
| Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | FileType | xsn | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.2 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.3 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.4 | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsx | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsb | 255 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsx | 256 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsb | 256 |
|
|
|
 |
|
|
|
|
|
ThomasCarpe.com > Categories
|
10/6/2008
Just wanted to let the world know that my attempts to rennovate this blog are nearly finished. The new version is in the staging server now and sports the new CKS EBE 2.0 with Windows Live Authentication, my own theme/master page, and several home grown bug fixes. I should have it moved to the production server in a day or two.
I also have several articles I had been keeping in Word, including a guide for implementing WLA in SharePoint and a techniocal discussion about overcoming some of the nastier limitations of custom fields and CAML. Look for those to be published in peices over the next two weeks. 1/30/2008
Well now in addition to WSPBuilder and the VSeWSS, you now have yet another method you can use to build out your SharePoint projects.
I have not yet downloaded and tested this tool, but they seem to have some pretty good tutorials for it and have made the source available, which is a HUGE plus over VSeWSS.
I'm going to give using it a try some time this week and maybe do a brief review of it. But for now why not give it a try yourself? 1/24/2008Congratulations, Alara!
So, congratulations to Alara for landing a pretty sweet gig today. She starts on Wednesday, doing business analysis type work for a healthcare company in Elk Ridge - in addition to whatever else it was she's been doing with the city to earn money through her business.
The extra money she'll be bringing in from this new contract will really help a lot. Even if it doesn't go past the 2-3 months that it's commited to, it'll teally put a dent in what has become our too-damn-big pile of debts.
A lot of people don't like to talk about money, but for me it is not something I am coy about, and I'm generally very up front about it. The truth is that I make better money now than most people out there - more than I ever expected that I could. I'm not ashamed of that, because I work very, very hard and have cultivated some pretty unique skills. (Acutally, I'm not full of myself, so I know it's a lot of luck plus a little talent.)
So, when I say that this past year has been very difficult for us financially, please understand that if it was rough for me, then my heart truly goes out to the 90%+ of Americans who bring home less than I do. I grew up in near poverty; my mom took help from her parents, food stamps, and college aid as she raised me by herself. I know how it feels.
Lately, raising four kids, it's hard to figure out where the money goes and why we don't keep more of it. Maybe that is just a part of having kids. Or maybe we should both work a little less hard and instead devote that time into managing our money better. Less time spent in setting up the TVPC and a bit more in balancing the household budget might help. I think a lot of it has to do with the simple fact that the dollar just is not worth what it once was. If I had to guess, I would say it has had about the same effect as a 25-33% pay cut.
Oddly enough that's not what I wanted to blog about. I just wanted to thank Alara for her hard work, and for taking the stress (and the daycare bills) off of my shoulders a bit.
So, What Kind of Job Would I Love To Have?
So, her getting this gig made me start thinking about my job, about raises I did not get, about what I enjoy about my job, what I'd rather take a pass on, and how I really want to be spending my time.
So here goes, my wishlist for a dream job:
- I want to work with SharePoint most or all the time, because it's really cool!
- But, I don't want to work for Microsoft.
- I like working on lots of little short-term projects. Veriety in work and in solving different problems for different types of users is exciting. For that reason, I could probably spend my whole life building nifty web parts and showing people how to use them.
- But, I hate having to think about my commute [or the people I have to work with] changing whenever the project ends.
- I truly enjoy using technology to help transform a business. Getting only little wins is really frustrating, so buy-in from the top is very important to me. If that means part of my job is to fight for that support, then so be it. Unlike many people, I find debate invigorating.
- I like doing work on proof of concept and design. Finding out what can be done is fun. Finding out that you *could*, except that you *can't* because there either a) aren't the skills, b) aren't the resources, or c) isn't the time is no fun at all. So, a place where there is a real investment in technology (as opposed to band aid solutions) is a real plus. (Update: Add "lack of political will" to that above list of frustrations.)
- I would like to either have a very short commute to downtown Baltimore, or else a reasonable train ride to Washington DC that I can do myself. I am tired of relying on my wife for transportation. I wouldn't mind working in my boxers either, but I need an excuse to get out of the house once in a while, and some things are better done face to face.
- I'd like a job title where the median base salary is in the neighborhood of $150,000/yr. Software Engineering Director looks nice, though frankly I have never seen anybody hiring for that one. They always use terms like Developer, Analyst, or Architect. Sorry, but the pennies don't spend like they used to, and wages don't seem to be going up to meet inflation. I guess I could accept a lower salary for the right perks, but the money is pretty important.
- Funnily enough, I like managing technical people, and I am good at it too. That's an aspect of work that I miss when being stood up as a lone gunman in consutling gigs. You rarely if ever have the authority to direct a team. Well, at least sometimes you can act as the trusted advisor; that can be nice.
Too bad that nobody will probably ever ask me what kind of job I want. Wouldn't it be great if we interviewed companies instead of companies interviewing us?
For your pleasure, here's my resume. If you feel so inclined, tell me if you think I am qualified for the job I am describing, or for that matter if it even exists.
So after waiting a few weeks to receive my e-mail for the MCP web site, I finally had to break down and call them on the phone to get my MCP ID and activation code.
To their credit, they were very helpful and got my issues resolved quickly. I guess the system just never sent me my automatic invitation, and to some extent it's really my own fault for not visiting the MCP web site since 2000.
So here it is, my pretty logo, plus prominently displayed contact info (so I am compliant with the usage guidelines).
Thomas Carpe
SharePoint Architect
Baltimore, MD
I guess I'll just have to change the link before I get too much spam at that address. :-) Maybe this will get me off my lazy rear end to create that "contact me" page I've been thinking about.
And, my pretty certificate, which I will apparently have to print out myself. (Back in the day, they used to send you a nice one in the *mail*. Ahh, technology!) You can view it in PDF format or as a Microsoft XPS Document. 1/5/2008
Today I finally managed to get my sorry rear end into a Prometric testing center to take the 70-630 exam for MOSS Technology Specialist.
My reasons for waiting so long are complex, and in some ways they are kind of pathetic.
At a basic level, I've just had too much on my plate over the past year. My 2 older kids were getting involved in extracurricular education that required a lot of my time and money. My two youngest are still in diapers, My house is half gutted with renovations. Then I broke my rib and caught some kind of weird bronchitis. On top of just generally being overextended at work, preparing for an exam seems like it would probably have been biting off more than I could chew.
Under the surface, my reasons for procrastinating were more complex, but I think they say volumes about the kind of person that I am. In school, I never needed to study. I was always ahead of everyone else, I always read ahead, and I always tested at the top end of the scale. I was always eager to prove how good I was. So, you might think that in my 30s I would approach test taking in a very similar way.
That's not the case, though. What I realized about myself this week was that I have, over the years, developed a lot of performance anxiety around the concept of taking tests - in spite of the fact that I've never taken any that I did not easily pass. So, I wondered, why might that be?
At some level I think that historically doing well on tests has actually been bad for me. Over my years in high school I was tested early and often for many things. Please don't take what I am about to say as bragging, because I only want to talk about it to highlight the contrast between my actual performance and my feelings.
I took the SATs in seventh grade and scored higher than the average for high school seniors. Years later, my score for the PSAT was the highest in my school - I tied with my friend who died later that year - and qualified for the NMSQT scholarship program. I retook the SAT when I was a senior myself, and with scores alone I could have gotten into many colleges regardless of my GPA. I took the ASVAB test and was literally hounded by military recruiters for months, even though I was considered legally blind. I was given psychological tests, and identified as a high formal thinker. I blew through several AP tests, and got the highest scores possible for both English and history exams.
However, none of these achievements got me much in the way of a tangible reward. Over time, I began to feel that no good deed goes unpunished.
As a seventh grader taking and passing the SAT, I qualified for the Hopkins CTY program, and I even got a nice certificate. But, I never got to participate in CTY, because my family didn't have the money to send me to their summer program. My mom, adding insult to perceived injury, was so ashamed of this fact that she wouldn’t talk to me about it, and later insisted that I'd never actually wanted to go to the program, even though I wanted it more than anything I had ever done or wanted to do up until that point.
As a parent blessed enough to have bright kid, I was overjoyed that my daughter qualified for the same program and that I will have a chance to do for her what my parents' couldn't do for me. But, even making a good living it is expensive. Sarah qualifies for many such expensive opportunities for the gifted, and I was starting to feel strained even before her brother was also recently invited to enter the talent search for CTY. I am starting to understand the dilemma my mother faced, and I think I would've handled it differently and communicated about the problem. I thought she understood me, but looking back I guess she never fully could. I guess I can forgive her for it - finally. But, she passed away about two and a half years ago, so I'll never get a chance to tell her any of this.
Maybe that was what started it all. I had worked hard to pass that exam. I studied all summer for it in my windowless bedroom, learning things about algebra, trig, and English that we had never covered in school. All that work, and what I got amounted to a small pat on the back. Today, CTY has an award cerimony for the kids who take part in the test. I don't know if it existed when I was a kid, but we were less than an hour from Baltimore back then and I think it would've prevented a lot of emotional damage if I'd been able to attend such a thing.
Anyway, that was never really the end of it. As I got older, my grades in school started to slip. My life at home was an emotional mess. I had a difficult relationship with my dad, and he was pretty rough with me. The bigger I got, the more we fought, and the more I did things deliberately to punish my family. Then later I would punish myself for not doing well in school and not doing my homework. To this day, the worst thing that negatively affects my work performance is having to deal with people who have confrontational or combative personalities. A negative encounter with such a person, even for a few short minutes, can turn my entire day into a disaster and leave me falling flat on my face.
I qualified for NMSQT, and I probably could've gotten a scholarship. I can't quite remember it well enough to state it as fact, but I believe that Rob might've actually gotten the Merit scholarship. If so, they sure didn't reallocate it when he was killed in a car accident, and I know that doesn't make any sense, so maybe it went to a person in a different school. I don't know - maybe I just don't want to think anything bad about my friend. He was really one of the few people alive that would understand the importance of what I am writing here today, so it really hurts to be writing this. We were equals in every way, but I wasn't even allowed to say a few kind words about him at his funeral.
My parents were actively discouraged by the school administration, because they said that my grade performance was too much of a factor against me. I guess I wasn't a model student, or the kind of person they thought would be worth investing a little time and energy in helping me prepare for college or get my grades together. Maybe they resented the fact that I could get an A one quarter and then fail the next, and then start working again and get an A, and then fail again - then ace the final and pass the class with a C.
Later I learned that my scores were high enough that there were colleges willing to completely ignore my GPA. Can you believe that? IGNORE my grades. By the time I found this out though it was too late and I would have to pay for my own schooling. The long story short with the SATs was that in the end another five years of school only raised my score about 300 points. I got no help finding a school. Hell, the majority of senior year I didn't even *have* parents and I had to pay for my own food, clothing, and shelter. All I learned is that a smart person can get the same score on the test as they would've if they'd studied by reading a book about quasi-cheating style test taking practices that defeat the way the test is designed, and that you can do this and even go without food and sleep.
All the military tests ever got me was recruiters willing to make false promises to me and tell me that there was some hope that they could use me - at least in the Navy. What a pile of bullshit that turned out to be!
All the psychological tests ever earned me, frankly, was more psychological tests. I started to feel like a pincushion, and I chafed at taking tests. My train was beginning to come off the rails.
AP exams were the only place where I still have an overall good feeling about the tests. They were easy. The essays felt especially easy, where other people seem to hate them. My mom used to say I have "the gift of gab". Just look at my blog; is it any wonder? But even with these, I have the dubious distinction of being the only kid in my school's history to have ever successfully gotten a perfect score on the AP English exam - and an A on the final - and yet still manage to fail senior English. I was in danger of failing history too, and I owe it to my teacher Mr. MacAvoy that I passed. He was the best teacher I ever knew and I owe him a lot that he was willing to spend the time with me to make sure that I could get exposure to the test material even though I read far to slowly from the textbook to ever keep up.
Fast forward past college, where I would routinely CLEP out of classes.
When I started my IT career, tests were also very important in moving ahead. Early Novell and Microsoft certification programs were not what they are today. There was a lot more ink and paper in the process during the nineties. And, they were experimenting with something called adaptive test administration software. In a nutshell the test was designed to sense your weak points and probe at them, almost as if it were trying to make you fail. To the best of my knowledge this testing technique has been either abandoned or very much watered down since then. If so, then I am not surprised, because I found the whole idea to be emotionally traumatic. For me it took my feelings that the world was full of people who would punish me for my talents and secretly wanted me to fail, and it transferred them from people to the test itself. I never forgot that.
After the divorce with Karen, I never updated my contact information with MS or with Novell. Novell certifications became worthless anyway. After she left, I let the rent go on the last two months of our lease, and even let them evict me, steal half my stuff, and throw the rest out on the side of the road. I let this happen even though I took the time to go back and rescue my pet cats. I think that I really did not want any non-living thing to be left that would remind me of that time in my life.
It has been eight years since then.
My attitude about tests is that I expect myself to do very well on them. In fact, I would consider anything less than a 95% to be a personal failure, though I can learn to live with myself for getting above 90%. The extent of my shame is such that I would retake such a test and pay the extra money to do so in order to hide such a score. I will delay taking any test if I do not believe that I can easily achieve a near perfect score. This is who I am - I am such a perfectionist that I consider this to be a great weakness and I am not proud of it.
People who believe that I have a big ego, just because I know my skills and demand fair compensation for them do not have a clue. I push myself harder and demand more from myself than most people alive. It is a blessing and a curse. I am stuck with it, so I might as well make the best of it.
When I started preparing a week ago, I found immediately that there were several gaps in my SharePoint knowledge. In the past week, I invested probably about 8-10 hours making sure I knew what those holes were. Most of my energy was spent in shoring up my biggest weakness - convincing myself that I was ready to take the test.
I guess if you've gotten this far, you are wondering how I did on the test this morning.
I completed the test in under half an hour. I missed two questions out of 51. I'm certain that I know which one at least one of them was, because I had it narrowed down to two possible choices and decided to take a 50/50 guess instead of sitting there for an extra minute or two to think about it. I believe that I probably missed the second one due to not reading the test questions carefully enough; this happens to me on about 1 in 50 questions historically because I have bad eyesight and I tear through the test like a ferret snorting sugar out of a Pixie Stick.
So, now if you are good at math and have taken this test before, you know that my score was 964 out of a thousand. And now the rest of the readers know it too. ;-)
I was disappointed to have not gotten a perfect score. I say this because I thought the test was very easy. I believe that other people - especially young people with no health problems, lots of time to prepare, and no outside responsibilities or commitments to their families - should be able to get a perfect score if they study hard. I also thought that in many, many cases the test would give five choices and three or four of them could be eliminated right off the bat as being patently ridiculous, which was not my experience with practice tests in which the distinctions between choices are much more vague. So, if you have good test taking skills and over prepare for the test, I think you almost can't avoid doing well on it.
And, if me saying this makes you either feel a little frightened to take the test, or more than a little angry at me because you didn't do as well, then I would like to apologize. The truth is that I often feel guilty about that too. I took this test basically because I was forced into it, not because I wanted to compare myself to anyone else. In fact, the last thing I want is to be measured against others, because no matter where I fit into the spectrum I will be miserable both for not being better than I am and for being so much better than so many other people.
And, if you were one of the very fortunate ones who got a perfect score, then I would like to congratulate you for escaping at least one side of that double edged sword.
If you missed one question on the test, I bet your are kicking yourself. I bet you either know exactly which question it was and are really beating yourself up, because you knew it was either C or D and you picked the wrong one. Or, you have no idea which one it was, in which case I am sorry that you'll probably have trouble sleeping tonight. There are few things worse than letting test anxiety creep into your dreams. Either way, let me know who you are, because I owe you a beer.
I know my ten year old daughter has similar feelings of perfectionism, so I think that in the near future I will have to learn how to deal with these. This is a start.
So all said and done and havign gotten all this out of my system, I did about as well as I thought I would. I didn't do as badly as I feared, and I didn't do as well as I hoped I would. I hit it right down the middle, line drive right of center field. Not a home run, but a two-base double. Not half bad! 11/15/2007
This week, I stumbled across a very interesting problem in SharePoint that could’ve easily put my entire design into jeopardy. Lucky for me, I can post now that I escaped this beast handily after only a few hours of absolute terror. For the benefit of anyone else who walks down this path, I’ll try to recount my experiences.
I have three different SharePoint projects going on right now. Among other concerns, I’ve recently been focused on our deployment strategy. It’s a good time for this, because I spent a few weeks mocking up a couple sites and developing some custom features. Now, we need to move those sites into our UAT environment from the virtual server on my laptop.
There are a few tools for doing this, namely the VSeWSS, Solution Generator, and WspBuilder. Until recently, I mostly used the WspBuilder and post build actions to deploy some of my simpler projects in Visual Studio. This strategy works pretty well for situations where you need to test an assembly over and over again. It was also the only option (until recently) if you had assets that needed to be copied outside of the FEATURES folder. On top of that, the 1.0 version of VSeWSS had some serious issues, so there wasn’t much good reason to switch strategies.
To deploy my mockup sites, I wanted to just basically create site and list definitions and move them over to the new server. I decided to give VSeWSS 1.1 CTP a try and see if I could use it to do this. While the CTP has some issues and challenges, I feel comfortable now saying that once you have worked with it for a while and understand how it behaves, it is overall a pretty good tool. The solution generator is ideal for replicating a site on the site server or packing up a site created entirely by a user. But, if you have a desire to leverage functionality like content types or list receivers, you’re going to end up to pushing its limits – often.
Today, I’m going to describe one of those limits, how it scared the living crap out of me for about two hours, and how you can rise to this challenge and overcome it.
If you are like most developers, you probably want to get traction in SharePoint as quickly as possible. This helps engage the customer and get the feedback process started so our designs are better. And, if you know SharePoint, you know it’s probably a pretty darn good idea to create Site Columns and Content Types instead of just slapping fields into a list; it’s the same amount of work, more reusable, and even required for certain functionality (like search) to work. It was with this idea in mind that I had created several lists with content types.
Of course, I want to re-use everything I created in the GUI, so I was now in the position of needing to create content type definitions based on what I’d already done during our discovery period. The Solution Generator that ships with VSeWSS does half this job for you, because content types are embedded with the list definition. They aren’t perfect of course, but I needed to clean up some field names anyhow. (IMHO, if you’re writing the definition file anyway, there’s just no good reason to have “_0x0020_” peppered throughout all your field names.)
So, I would use VSeWSS to create new (empty) content types, then paste the XML from the list definition, and massage it a bit to get the Fields and FieldRef elements in the right format. This isn’t a bad approach especially if you’re still learning your way around the wss.xsd schema, and it was a good opportunity to refine my approach and refresh my memory. (It had been over a year since my last official SharePoint gig, after all.)
In fact, this technique works fine, but there are a couple of things that aren’t entirely obvious at first glance that can trip you up when it comes to content types and feature definitions.
Firstly, if you’re content types are using the same GUIDs as the ones you created in SharePoint, then you are probably going to notice that your changes don’t always appear on the web site after you deploy. There are two reasons for this that I’ll explain later. The other thing you’ll run up against is that it doesn’t seem possible to use the ContentType element to define a type that inherits from another content type. There doesn’t appear to be any [documented] way to do it.
These two problems are actually related. To understand what is going on here requires a little background on how SharePoint manages Content Types.
SharePoint keeps track of Content Types in many places. They can have Site or Web scope, meaning that they are available in either the root site (top level web) or an individual sub web and its sub webs. Within each of these scopes, it is possible to derive a new type based on a parent type, even when that parent type is defined in a parent web site.
Actually, that’s a pretty nifty thing if you think about it, and as developers we want to be able to take advantage of that inheritance as often as possible to save us some work in both development and maintenance. Up until now, developers have tried to do this in their projects, but found that the only apparent ways to do so were either in the web UI or via the object model. This introduces another issue, in that once a content type is modified in the UI or API, it only gets its information from the content database SQL Server, and never from an XML element manifest. This would seem to rule out using features as a way to deploy new content types, unless of course you are writing a ton of code to create the content types programmatically, and we all know how much fun that can be!
Let me take a step back for a minute. Once you have content types defined in your site or web, you may have noticed that if you use them in a list, a mirror image of your content type will appear in each list. Actually, that’s kind of a cool feature too, because it may be that in one particular case you need to change the name of a field or some other property. Having the list based content type allows us to do that without affecting the parent and all the other lists that use our content types.
So, we have content types stored in the root site (both independent and inherited from other CTs), in the sub-webs (again, either independent or inherited), and in the lists themselves (always inherited). Let that last one linger in your mind for a bit, because that’s how I was able to figure this whole mess out.
I had noticed a while ago that when I use the Solution Generator to create a list definition, the content types always had IDs that were significantly longer that the ones generated if I created a new Content Type using VSeWSS 1.1 CTP. From the beginning, it had become pretty obvious to me that the latter of these was probably a GUID with some salt (“0x0100”) sprinkled in front of it. But, the GUIDs from the list definition were much longer – which was weird. I wondered a bit about this, and decided to use the GUIDs created by Visual Studio instead of the ones in the list definition, but I didn’t dig into it any deeper until today.
I’ve seen some folks have called these “magic GUIDs”, and some hacks have been documented where you can create a content type in the GUI and then steal these magic IDs to use in your Content Type manifest files, and the inheritance will follow them. Of course, any technology – sufficiently advanced or undocumented – will seem like magic. Obviously there isn’t some spirit that is following these funny ID attributes around and thus magically bestowing a parent-child relationship upon them. Or, is there? Could there be a Ghost in the Machine – literally?
Or, should that be "Re-ghost in the Machine"? It seems like all this talk of physical vs virtual content type definitions seems somehow eerily familliar. :-)
Anyway, I had an intuition that these mystery strings had something to do with the missing ability to define parent-child relationships, so I decided to do some digging and a few controlled experiments to see if I could unravel this inscrutable conundrum. Of course I’ve already spoiled this great detective story, because I already told you that I saved the day, disaster was averted, and I avoided being in a terrible mood for the rest of the week.
Firstly, these strange strings are made entirely of hex digits. This fact is made obvious by the leading “0x” at the start of the ID. Also, they are almost universally (but not always) some length that is close to a multiple of 32, which happens to be the length of a GUID with the dashes taken out. But, some are not. Well, if you keep in mind that 0x0100 and 0x000100 mean exactly the same thing, then things start to line up a little better. Allow me to demonstrate using the IDs of several ContentType elements that I generated in VS.net. I have inserted underscores to pad them out (not a legal substitution of course, but good for visualization purposes.)
So from this:
<ContentType Name="Item" ID="0x01006964b2a9ff5c4a299a55daf0ca370a79" Group="Development" Version="0"><FieldRefs /> </ContentType>
We get:
Item: 0x____0100 6964b2a9ff5c4a299a55daf0ca370a79
And so on…
Document: 0x__010100 28ec491792cd40469e5023279d495a74 Event: 0x__010200 0d11c8f44fee4d299fa7412976ced2f3 Issue: 0x__010300 d0ff4d76b975418f92f99b6380525b51 Announcement: 0x__010400 ca7fe46207ee4f678658688328478ac9 Link: 0x__010500 94746e4cec53476faf5c25b625375005 Contact: 0x__010600 d74d210576bb41d1ad7c2129f6b2f146 Task: 0x__010800 39f9376dd88d446fa540ae6b0aab80b2 Folder: 0x__012000 667c88b5ec704512acf75070fd6a9cb8 Discussion: 0x01200200 08993122a65146068f4acfc1f7be60f5 XMLDocument: 0x01010100 afce5e1e9dd84a7abc719987a7bd1627 Picture: 0x01010200 bc7bc4a9bb6c4dbe924eccebcf2e260a WikiDocument: 0x01010800 8898737798e045a6bcbf8ae3617da0ac
So now, at least for the basic types, it seems pretty obvious that the first eight digits contain the base type that the content types is derives from. This makes me ask myself all sorts of strange questions, like “What happened to 0x0107?” and “What other types that I don’t see here (Project Task List anyone?) could I exploit if I knew their codes?” But, my mission is clear, so moving on.
I also exported some Content Types from my own list definitions, and I observed some meaningful things about them. I will spare you having to stare at another list of random GUIDs, and simply explain what I found.
· Once you line up the strings so that the first GUID always starts in the same positions, as above, the remainder of the string always follows a consistent format. Additional characters on the string consist of a “00” pad (0-FF being a single byte), followed by 32 hex characters (a GUID). This pattern will repeat for the remainder of the string.
· I have seen strings with two or three GUIDs stored in this way. I assume that since the ContentTypeID field (in the content database in SQL) is a 512 byte binary block, that the developers anticipated needing to store up to 17 GUIDs in this way.
· Assuming that your site is working, the “last” (low order) GUID of any ID attributes generated by exporting from it will be unique. No other content type will (or should) ever have that GUID.
· The high order GUIDs will not be unique. Somewhere in SharePoint there will be another content type that has that GUID as a segment of its ID.
· If you want to create an inherited content type as part of your feature, create its ID in the format “0x” + Base ID hex + Parent GUID + “00” Unique GUID. I have tried this and it works. If believe that if you want to create multiple inheritance, you do it like “0x” + Base ID hex + Grandparent GUID + “00” + Parent GUID + “00” Unique GUID, but I haven’t confirmed this. Also, don’t forget to take the dashes out.
· List content types are simply inherited types that have the web/site content type as a parent. However, they do not appear to be stored in the ContentTypes table in SQL. Maybe they are embedded within the list itself?
So why would the SharePoint development team implement inheritance in this way and not as a property of the ContentType element (BaseType for example)? I don’t know the answer, but I suspect that it had something to do with a freeze on code near the end of development, or perhaps there were different teams relying on the schema and it couldn’t be easily changed. That much I can forgive, but this methodology was so unintuitive that it makes me laugh just thinking about it.
Here’s what Microsoft has to say about the ID attribute:
|
ID |
Required Text. Specifies the content type ID of the content type. |
While I can’t find anything factually wrong about that statement, I still have to say that it seems like something important is missing. ^_^
I found this article Content Type IDs after the fact, and indeed is it a very helpful elaboration on what I've explained above. Also, it seems to indicate that in addition to mulriple "00"+Guid combinations at the end of the first GUID, you can append any hex string. "You can simply append two hexadecimal digits to the myDocument content type ID." In fact they say this is a good way to stay below the 512 character limit. I wonder however, if it actually works and how inheritance is implemented in this case. (Since they don't do this themselves when you create content types OoTB with the SharePoint GUI, I have to question the accuracy on this point.)
So how are the two issues that started this conversation related? Well, it turns out that what the Solution Generator is extracting along with lists are the List Content Types. It looks like I will have to build a tool to export the actual Content Type from the web itself, after which the List Definitions should work without any issues, but all the related gripes about content type disconnection will still apply.
But at least for now, they'll be a bit less frustrating knowing that at least I can do what I want without writing let another pile of code to make it happen. Thank fate for small victories.
Related posts/articles:
-
-
Propagate Site Content Types to List Content Types [aka Make Content Type Inheritance Work for XML Based Content types] This is actually the article that started my little crusade. Thanks to Søren Nielsen for asking the tough questions in a way that got me headed in the right direction.
-
Content Type IDs After the fact, but detailed enough that it's worth looking into for its supplimental information.
-
Creating Content Types Based on Other Content Types Not the discussion of defining inherited content types that I was hoping for, but it has a nifty diagram (and that's about all) if you're still a bit fuzzy on the sholw dsitinction between site, web, and lsit content types.
-
Creating Site Columns and Content Types I also found this one from way back pre-RTM. I thought at first it might've said the same thing I just did, but turns out it didn't. Still, not a bad primer.
-
11/6/2007
So, I was packaging up a custom solution today using VSeWSS 1.1, and I realized something important.
Apparently, I had configured Visual Studo to understand the schema for wss.xsd, but not any of the other files from the <12hive>\TEMPLATES\XML folder.
As it turns out, if you don't provide all the deployment files, then your element manifests and feature files will not have intellisense. So, I just expanded my catalog file a little bit.
To save you a lot of cut-and-paste, here's the wss_catalog.xml file. Just put it in your C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas folder and VS should pick up on it right away, though you might need to exit and re-enter Visual Studio.
11/5/2007
If you've used the VSeWSS to create a SharePoint solution and deploy it to your development server, then you probably know about the infamous "Object Reference Not Set" bug in it that has had many a hearty developer run back to the comforting arms of WspBuilder. Of course, this issue was not fixed in the CTP version of the VSeWSS v1.1 released in August.
I wait with bated breath for the day (real soon now?) when the final release of 1.1 comes out. However, I simply couldn't wait to get around this problem. Unfortunately, Paul's EXE did not work for me, becaue it refused to connect to the instance of SQL Server that my content databases are stored on.
So, I wrote my own, independently. I'm glad to say that some neat innovations came out of it. Firstly, mine takes a URL instead of a connection string. You can get the connection string from SPSite.ContentDatabase.ConnectionString instead. Second, I learned that the reason for this error is generally always that an attempt to read SPFeature.Definition returns null if there is no feature.xml file in the FEATURES folder with the required matching GUID.
Sadly, I could not create a solution MS would suppport, because trying to call SPFeature.Remove() also causes the error. I suppose you could create a feature.xml file first, and then try this, but I have no idea if that would even work without resetting the application pool and re-retreiving the SPSIte/SPWeb objects or what-have-you.
Knowing this, it's hard for me to imagine that this is going to just stop being an issue when the new VSeWSS comes out. THere are just too many other ways you can accidentally orphan a feature in SharePoint. I suspect we'll be hearing from this one for a long time.
So, without further adieu, here's the source code/ Enjoy!
Actually I had a different pet peeve today, but I'll blog that later. There'll be some code - and ice cream for those of you who get here while there's some left.
Okay, SharePoint can awesome, but sometimes it can also be pretty damned stupid. Here is a classic example of that dichotomy.
This is the documentation for a CHOICES / CHOICE element collection in CAML, from the MSDN version of the WSS 3.0 SDK.
CHOICES Element (List)
Used to define several choices within a field for a drop-down list.
...
Used to define a choice within a Choice field.
<CHOICES>
<CHOICE
Value = "Text">
</CHOICE>
<CHOICE
Value = "Text">
</CHOICE>
...
</CHOICES>
Now, reading that, you might be led to believe that you could create a CHOICES collection like this to have SharePoint use value codes on the back-end of its DropDownList's options tag in HTML.
<CHOICES> <CHOICE Value="MD">Maryland</CHOICE> <CHOICE Value="DE">Delaware</CHOICE> <CHOICE Value="VA">Virginia</CHOICE> <CHOICE Value="PA">Pennsylvania</CHOICE> </CHOICES>
Well, you would be WRONG! Wrong, as in "You get NOTHING! Good day sir!" wrong. In fact, as nearly as I can tell, the Value attrbiute of this element is less than useless. It's not invalid if you use it - sometimes. (Actually, sometimes it causes validation errors and sometimes it doesn't) It [usually] doesn't break anything; it just doesn't do anything, and the documentation leads you to believe that it should, which I think has the potential to cause a huge waste of time.
So, a big WoTF (wag of the finger) to Microsoft, for fragging this one up royally. And, I guess with that comment, I shouldn't expect to be getting that MVP nomination I've always wanted any time soon, huh? Oh well!
Update I dug into this problem a little deeper. At first, I believed that you could provide a multi-column style value-text pair in the CHOICE element text, similar to what you'd find in a ModStat field type. However, this turned out to be a pipe dream.
<CHOICE>MD;#Maryland</CHOICE>
As far as I can tell so far, what is actually going on is that the XSD schema for WSS is actually not consistent with the online documentation. See the following from wss.xsd:
<xs:complexType name="CHOICEDEFINITIONS" mixed="true"> <xs:sequence> <xs:element name="CHOICE" type="xs:string" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType>
Well, it's no wonder when I try to use the Value attribute in my custom content types and other features, I get nasty XML validation messages from Visual Studio telling me that the Value attribute is no valid in the CHOICE element, because it's not.
I don't recommend you try this on a production box, but what I did was to patch wss.xsd as follows.
<!-- BEGIN CHANGE BY TCARPE 11/1/2007 --> <xs:complexType name="CHOICEDEFINITIONS" mixed="true"> <xs:sequence> <xs:element name="CHOICE" type="CHOICEDEFINITION" minOccurs="0" maxOccurs="unbounded" /> </xs:sequence> </xs:complexType> <xs:complexType name="CHOICEDEFINITION"> <xs:simpleContent> <xs:extension base="xs:string"> <xs:attribute name="Value" type="xs:string" use="optional" /> </xs:extension> </xs:simpleContent> </xs:complexType> <!-- END CHANGE BY TCARPE 11/1/2007 -->
That made Visual Studio and the VSeWSS behave themselves, but it's not clear to me yet if it will actually have any effect on the behavior of the Choice field and its DropDownList. In theory it should, since I see CAML in the RenderTemplate for CHOICE that retreives the Value attribute - if it exists.
From FLDTYPES.xml:
<HTML><![CDATA[<SCRIPT>fld = new ChoiceField(frm,]]></HTML> <ScriptQuote><Property Select="Name"/></ScriptQuote><HTML>,</HTML> <ScriptQuote><Property Select="DisplayName"/></ScriptQuote> <HTML>,</HTML> <ScriptQuote><Column/></ScriptQuote> <HTML>); fld.format = "</HTML> <Property Select="Format"/> <HTML>"; </HTML> <Switch> <Expr><Property Select="FillInChoice"/></Expr> <Case Value="TRUE">fld.fFillInChoice = true;</Case> </Switch> <ForEach Select="CHOICES/CHOICE"> <HTML>fld.AddChoice(</HTML> <ScriptQuote><Property Select="."/></ScriptQuote> <HTML>, </HTML> <ScriptQuote><Property Select="Value"/></ScriptQuote> <HTML>);</HTML> </ForEach> <Switch> <Expr><Property Select="Required"/></Expr> <Case Value="TRUE">fld.fRequired = true;</Case> </Switch> <HTML><![CDATA[fld.IMEMode="]]></HTML> <Switch> <Expr><Property Select="Type"/></Expr> <Case Value="Lookup"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="DateTime"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="GridChoice"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="Calculated"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="Currency"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="Number"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="User"><HTML><![CDATA[inactive]]></HTML></Case> <Case Value="Boolean"><HTML><![CDATA[inactive]]></HTML></Case> <Default><Property Select="IMEMode" HTMLEncode="TRUE"/></Default> </Switch> <HTML><![CDATA[";]]></HTML> <HTML><![CDATA[fld.BuildUI();</SCRIPT>]]></HTML>
Clearly, somebody over there at MS was expecting this attribute to be declared - at least sometimes. I really have to wonder how this fell through the cracks, and if I'll be doing more harm than good with this particular tweak.
So, I just gave it a try to see what happens. Sadly, there is no joy in mudville today. Mighty SharePoint has struck out. Using this:
<CHOICES> <CHOICE Value="C">Client</CHOICE> <CHOICE Value="P">Partner</CHOICE> <CHOICE Value="S">Staff / Professional</CHOICE> <CHOICE Value="V">Vendor</CHOICE> <CHOICE Value="O">Other</CHOICE> </CHOICES>
SharePoint gives me:
<option selected="selected" value="Client">Client</option> <option value="Partner">Partner</option> <option value="Staff / Professional">Staff / Professional</option> <option value="Vendor">Vendor</option> <option value="Other">Other</option>
I give up! I don't think anything short of creating a custom field type is going to solve this problem.
The remifications of this "bug" are actually far reaching and profound. For example, let's suppose you have a custom field (or site column) with custom properties defined, and you want to assign a value from a CHOICES collection to a property of an enumerable type in your own class derived from SPFIeld. If the Value attribute were working as claimed, you could assign it the string equivalent of each value of the enumeration, then use a simple call to Enum.Parse() to do the conversion. Instead, you are left with the ugly choice of writing custom parsers. (Choose your posion: property attributes in the enum, or a hard coded switch-case statement.) And, you'll have to write one for each choice property you want to create in this way. This increases the overall amount of time spent doing code and maintainance, which is the opposite of why we use SharePoint in the first place.
For now, I guess the only thing I can really take away from this experience, other than frustration, is an affirmation of one of the great truisms of software engineering as coined by the irrerent Fred Brooks, "Documentation lies." 10/31/2007
Recently, I was working on developing custom field types and controls for SharePoint. There is some really amazing customization capability here that I am very excited to talk about in a future article, but today I want to talk about something I know many people have seen, but (so far as I know) nobody has been able to solve without a lot of code.
I am talking about a bug that occurs when you add properties to the PropertySchema element in your custom field type definition file. The way these are supposed to work is that adding these properties will give you additional settings for your field that you can access from the code of your custom field class. This is supposed to save you a lot of work by providing a UI for properties that are based on fields that already exist in SharePoint, like text or choice.
However, the properties don't work as expected. After creating a new field (either in a list or as a site column) and saving it, the data entered is indeed stored and available to you via the GetCustomProperty function of the SPField class, but when you return to make changes to the field, it's properties are not properly set and there is garbage text in the form instead.

Now, I am not really sure what is going wrong here under the hood in SharePoint. I have read some things that speculated there was a hotfix to this problem, and at least one person has speculated that such a hotfix will be rolled into Service Pack 1, whenever that is. I would've thought that the HUGE security updates from September and October that required schema updates would have rolled this fix in, but they didn't solve this issue.
Some folks have solved this issue by writing custom property type rendering controls for their fields. There is a lot of chatter on the forums about this solution and the various shortcomings with it. However, I am not a big fan of writing a bunch of code where it should not be needed, just to address some broken functionality. I wanted to be able to use the custom property schema as intended out-of-the-box to make my custom field controls configurable without a ton of extra work.
Well, I did some digging, and though it was very difficult and took me a little over a day, I found the answer to my prayers. I know Microsoft will eventually fix this issue, but in the mean time, I'm providing source code for you to download and use in your own custom field controls so that you can get around this issue. See the link at below for the code, or keep reading to find out how it works.
Note that I haven't been able to reproduce the issue reliably on every SharePoint machine. I'll leave it to Microsoft to determine the root cause and get to the bottom of it and fix it. Basically, I think you should try every other option at your disposal first, and then try this if there is nothing left that works for you
My Approach
If you attach to the debugger in Visual Studio while you are on the FldEditEx.aspx page, you will find that your own custom field type is indeed being constructed (several times in fact) as that page goes through its own lifecycle.
So, I got to thinking, "If the data in SharePoint is OK, but the data shown on the form is bad, then it must be just a rendering issue in the page and we can probably write over it as long as we do so before the page posts the 'corrupted' data back into SharePoint." I did a little digging in the debugger and I could see that at certain points, the field did indeed have the "correct" custom properties.
So far, so good. But how to get the correct data back into the web form where it belongs, and not too soon so that SharePoint doesn't then just copy over my changes? Well, you can get the page context from HttpContext.Current and actually everything you need from there is at your disposal.
I dug into the HTML source and found the text input element for my first custom property. This was easy enough since I knew the field title that appears in its label. It had an ID property like this:
ctl00_PlaceHolderMain_OptionalSettings_ctl00_ctl00_ctl01_ctl00_ctl00_TextField
After redeploying my feature, the ID actually changed slightly. It seems some of the ID properties for placeholders, templates, and such are set dynamically.
In the debugger, if you look through the Controls collections of the page object, you can see that there is a complex hierarchy of templated controls being used to render the form. Knowing that, I was able to browse the control hierarchy for the page, looking for any control that ended in "ctl00_TextField". Then I walked up the Parent properties of all the controls and was able to determine this hierarchy.
|
ID |
Type |
|
ctl00 |
ASP._layouts_application_master |
|
ctl00 |
System.Web.UI.HtmlControls.HtmlGenericControl |
|
ctl13 |
System.Web.UI.HtmlControls.HtmlForm
We can get this deep with page.Form. |
|
PlaceHolderMain |
System.Web.UI.WebControls.ContentPlaceHolder |
|
OptionalSettings ASP |
Control of dynamic type |
|
PlaceHolderControls |
System.Web.UI.WebControls.PlaceHolder |
|
Customization |
System.Web.UI.WebControls.PlaceHolder
We can get to here using a recursive FindControl command. |
|
ctl08 |
Microsoft.SharePoint.WebControls.ListFieldIterator
We can loop through the Controls for a control of this type. |
|
ctl00 |
Microsoft.SharePoint.WebControls.TemplateContainer |
|
ctl01 |
Microsoft.SharePoint.WebControls.FormField |
|
ctl00 |
Microsoft.SharePoint.WebControls.TextField |
|
ctl00 |
Microsoft.SharePoint.WebControls.TemplateContainer |
|
TextField |
System.Web.UI.WebControls.TextBox |
What a pain! I hate trying to access properties in templates! Half the time they don't even have static ID properties, so FindControl doesn't work. That is true in the case as well, but fortunately there is a predictable pattern that we can use to find what we need. I wrote a couple simple functions to help us do recursive searches for controls and a search by type. You can find examples of these in just about a million places on the web.
public class WebControlTools {
/// <summary>
/// Used to scan for controls within a hierarchy of control containers. The
/// </summary>
/// <param name="root">The top levle control at which to begin the search</param>
/// <param name="id">The ID or ClientOD property to search for</param>
/// <param name="resolveByClientId">
/// Set to true if you want to find a control whose CLientID property
/// ends with a specific string. Useful for finding a specific control
/// buried deep inside nested templates, mostly for research.</param>
/// <returns></returns>
public static Control RecurseFindControl( Control root, string id, bool resolveByClientId ) {
if (resolveByClientId) {
if (root.ClientID.EndsWith( id ))
return root;
if (root.Controls == null)
return null;
foreach (Control subCtl in root.Controls) {
Control found = RecurseFindControl( subCtl, id, resolveByClientId );
if (found != null)
return found;
}
} else {
Control ctl = root.FindControl( id );
if (ctl != null)
return ctl;
if (root.Controls == null)
return null;
foreach (Control subCtl in root.Controls) {
Control found = RecurseFindControl( subCtl, id, resolveByClientId );
if (found != null)
return found;
}
}
return null;
}
public static Control RecurseFindControl( Control root, string id ) {
return RecurseFindControl( root, id, false );
}
/// <summary>
/// Gets the first child control that matches the specified type.
/// This is useful for finding tempalted controls when you know
/// the type but the ID is determined dynamically at run time.
/// </summary>
/// <param name="parent">The parent control to search</param>
/// <param name="controlType">The type of control to search for</param>
/// <param name="skipHowMany">An optional parameter to tell the search to skip the first x numbe rof controlls of this type that it finds, default is 0</param>
/// <returns>A control of the specified type, or null if none found</returns>
public static Control GetChildControlByType( Control parent, Type controlType, int skipHowMany ) {
int i = 0;
foreach (Control ctl in parent.Controls) {
if (ctl.GetType() == controlType) {
if (i++ >= skipHowMany)
return ctl;
}
}
return null;
}
public static Control GetChildControlByType( Control parent, Type controlType ) {
return GetChildControlByType( parent, controlType, 0 );
}
}
It might've also been possible to get the field at a point where its FieldRenderingControl was already created and set up, and then use that to bootstrap our way up the stack of parent controls to get where we needed to be, but I haven't tried that yet.
So anyway, the FldEditEx.aspx page has a Placeholder control, called Customization, that holds the dynamically rendered controls for - of all things - our custom properties. Once we can get that, we can grab the ListIterator control beneath it. Within that, the FormField controls represent the editor controls for each custom property.
/// <summary>
/// Search a Page object for the ListIterator control that acts
/// as a container for all the custom property field controls for
/// that page. This will only work on SharePoint's FldEditEx.aspx page.
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
private Control GetListIteratorControl( Page page ) {
/*
Control phMain = page.Form.FindControl( "PlaceHolderMain" ); if (phMain == null) return;
Control oSettings = phMain.FindControl( "OptionalSettings" ); if (oSettings == null) return;
Control phControls = oSettings.FindControl( "PlaceHolderControls" ); if (phControls == null) return;
Control custom = oSettings.FindControl( "Customization" ); if (custom == null) return null;
*/
Control custom = WebControlTools.RecurseFindControl( page.Form, "Customization" );
if (custom == null)
return null;
ListFieldIterator listIterator = WebControlTools.GetChildControlByType( custom, typeof( ListFieldIterator ) ) as ListFieldIterator;
return listIterator;
}
We can fix them all in one fell swoop by looping through all the controls in the ListIterator. For each FormField control, we do a GetCustomProperty and then just stuff that value into the form field's Value property. Since the design of FormField (and all its derived classes) is that Value is used to set the value of the underlying primitive web controls - Voilla!
private void ReplaceCorruptedFieldValues( ListFieldIterator listIterator, SPField field ) {
if (listIterator == null)
return;
if (listIterator.ControlMode == SPControlMode.New)
return;
foreach (Control propertyTemplate in listIterator.Controls) {
// there is generally always a template for the individual field property
foreach (Control lic in propertyTemplate.Controls) {
FormField ff = lic as FormField; // this is the form field for the custom property, not the actual list item field
if (ff != null) { // filter out any LiteralControls that are floating around the form field
// get the field name and do the substitution
string fieldName = ff.FieldName;
object prop = field.GetCustomProperty( fieldName );
ff.ItemFieldValue = prop; // this may not be necessary but it hasn't bitten me yet
ff.Value = prop;
}
}
}
}
As you can see from this screen, the problem is fixed!

One last caveat, since the field gets instantiated several times during the page lifecycle, we need a way to make sure our code runs at the correct time, and that it only runs once. I do this by creating a static method called DoHookup which takes the custom field as an argument. It does all the work of getting the page object and managing the event. If the passed field has not been sent to DoHookup before, it will bind a PreRender event to the page to do the rest of the work. To make sure we track the fields we have called, I just store a generic string list in the page's Items collection and check it every time the static method is called. This works much better than using a static property, since ASP.net will run multiple pages in a single thread and you don't want field names to cross over the pages in this case.
/// <summary>
/// This class contains code to workaround an issue that prevents the properties
/// of a custom field from being properly displayed in the FldEditEx.aspx page.
/// </summary>
public class CustomFieldPropertyDisplayWorkaround {
private SPField _field = null;
private Page _page = null;
/// <summary>
/// You can call this method as many times as you want. It will only
/// create an event one time per unique field that is passed to it.
/// </summary>
/// <param name="field"></param>
/// <returns></returns>
public static object DoHookup( SPField field) {
if (HttpContext.Current == null || field == null)
return null;
Page page = HttpContext.Current.Handler as Page;
if (page == null || page.IsPostBack)
return null;
// ensure that we only attach a single pre render event for each unique field name
// TODO do something to ensure that fields trigger by other parts of the form or custom
// properties do not get added here
List<string> fieldList = GetFieldList(page);
if (!fieldList.Contains( field.InternalName )) {
object work = new CustomFieldPropertyDisplayWorkaround( page, field );
fieldList.Add( field.InternalName );
return work;
}
return null;
}
private static List<string> GetFieldList(Page page) {
List<string> fieldList = page.Items["FieldList"] as List<string>;
if (fieldList == null) {
fieldList = new List<string>();
page.Items.Add("FieldList", fieldList);
}
return fieldList;
}
internal CustomFieldPropertyDisplayWorkaround( Page page, SPField field ) {
this._page = page;
this._field = field;
_page.PreRender += new EventHandler( page_PreRender );
} // SPField field
protected void page_PreRender( object sender, EventArgs e ) {
if (_field == null) return;
if (_page == null || _page.IsPostBack) return;
ListFieldIterator listIterator = GetListIteratorControl( _page ) as ListFieldIterator;
ReplaceCorruptedFieldValues( listIterator, this._field );
}
// ... other methods shown above
}
To incorporate this workaround into your own custom field classes, simply call CustomFieldPropertyDisplayWorkaround.DoHookup( this ); from the constructors of your custom field. (Note, not the field value, and not the field control - the field itself). That's all you should need to do.
Keep in mind that this solution only does the workaround on fields you created (or at least have source code for), since the out-of-the-box SharePoint fields all work as designed - it's only custom properties that are hosed. If you have custom fields with custom properties in use where the author has not provided you the source, you need to tell them about this workaround. (If you are very brave, you might try to figure out how to wire up your own code on the page to an instance of their field object using events, but I don't need to do it, so I'm not gonna.)
In case you're wondering how this will affect custom properties when they're accessed outside of a web context, it won't! If any of the controls don't line up as expected, or the HttpContext is null, the code doesn't do anything at all. Since our issue is a malfunctioning web page, we don't need the workaround anyplace but here. :-)
Download the Code
You can download the needed source code here.
References and More Information
More chit-chat about this issue is in various places in the SharePoint community:
| Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XsnLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | FileType | xsn | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.2 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.3 | 255 | | Edit in Browser | /_layouts/images/icxddoc.gif | /_layouts/formserver.aspx?XmlLocation={ItemUrl}&OpenIn=Browser | 0x0 | 0x1 | ProgId | InfoPath.Document.4 | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsx | 255 | | View in Web Browser | /_layouts/images/ichtmxls.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&DefaultItemOpen=1 | 0x0 | 0x1 | FileType | xlsb | 255 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsx | 256 | | Snapshot in Excel | /_layouts/images/ewr134.gif | /_layouts/xlviewer.aspx?listguid={ListId}&itemid={ItemId}&Snapshot=1 | 0x0 | 0x1 | FileType | xlsb | 256 |
|
|
|
|
|
|
|
|