| Julius's profileJulius Ganns . netzkernPhotosBlogNetwork | Help |
|
July 12 The Very Best Books on Software Architecture, Software Development, Self Development and ManagementEveryone knows, I'm a freak when it comes to books. Usually, I read about one book a week (although I didn't in the last couple of weeks, but usually I do). As of now, I've read about 180 books about .NET, Software Architecture and Java EE, as well as some other topics like Ruby on Rails, Requirements Management, Personal Productivity and Management. My family gifted me that incredible ebook reader from iRex, the Iliad about 1.5 years ago, and so I can take a large number of books with me on vacation, when I'm traveling or just having breakfast in a cafe. I'm also a proud owner of an iPod and an iPhone, so listening to audiobooks and reading newsfeeds (Google Reader is great on the iPhone) on the move are other great ways to learn steadily. There are, however, some books that are really great and I actually read them two or three times, at least in parts. Today I want to share those book tips with you and I hope, you enjoy them as much as I did. Advanced .NET Software Development Software & Solution Architecture Self Development & Personal Productivity Management I will extend this list further in the next few months. The most important thing to keep in mind while reading is the following: Your unconscious mind can process information much faster than your conscious mind - don't try to understand everything in the moment you read it. Fast-read the whole page and then stop at the end and think a couple of seconds with closed eyes about what you've just learned. In my experience, this way makes your reading three times more productive. July 09 A Conceptual Alignment of Sitecore, ASP.NET WebForms, ASP.NET MVC and the Microsoft AJAX Client-side LibraryObviously there are a lot of very interesting technologies out there that should help every developer to create great .NET-based web applications. Unfortunately with .NET growing faster with every year, the rising number of conceptually overlapping technologies sometimes creates confusion and insecurity, especially in the beginners developer community. "What .NET framework should I use?" is a common question these days. Coming from the Java EE world I remember a time a couple of years ago when we were developing with different Java web frameworks. I worked a lot with JSF and Spring Web MVC, but we also used Struts, Beehive, Tapestry, Cocoon and some other in different projects. Whenever we started a project using one particular framework, we usually had to stick with it or we had to create a completely different application root and development style when we decided to add another framework to the toolset that could fulfill the requirement with more elegance and effectiveness than the primary framework. When I look back at that time now, I see the need for guidance in our world and I see that we have to make good choices in our technical strategy. I will try to provide this guidance in this article, but I invite you to share your thoughts and views with me. First of all, I have to say that I have always been a fan of a very "clean" development style. "It runs" is not enough for me. Having said that, I have to admit that ASP.NET WebForms aren't really the cleanest way to develop web applications - or better, most people are somewhat lazy and take the path of the least resistance, so they use WebForms in a "dirty" way: They don't use data binding (either native ASP.NET or Spring.NET), events and customer controls appropriately. Instead they directly assign values to properties in code-behind files at several states of the page lifecycle and wonder why they get Spaghetti code in their Page_Load and Page_PreRender methods. Sometimes they even get strange results because they lack the knowledge of the page lifecycle in WebForms. Well, of course there is a better way, but because the WebForms model is more or less a "conceptual porting" of the WinForms model to the web and because it tries to support a development style that is based on state by emulating several desktop-like environmental conditions, the root of the problem lies in the underlying architecture of the framework. The Sitecore development model uses several features of ASP.NET WebForms. But by taking a closer look at the system, it actually becomes clear that there is no real dependency between Sitecore and the WebForms model - it is just one way to create Sitecore renderings. I'm pretty sure that Sitecore will work very well with ASP.NET MVC in the near future, because it is in itself an implementation of a FrontController pattern with different View engines. The most common ones (Web Controls, Sublayouts, XSLT renderings) happen to be based on the WebForms model. (By the way, you could use XSLT without ASP.NET WebForms, but by just placing XSLT output in typical content zones of the website using Sitecore Web Controls, the definition of overall website design with standard HTML and WebForm tools is much easier.) So, apparently being a Sitecore developer is a very good starting point for learning ASP.NET MVC. With ancestors like Struts and Ruby on Rails the whole approach of the Model-View-Controller pattern has been proven very useful in the last couple of years. ASP.NET MVC not only removes the curiosities that had to be introduced with the WebForms model to support the Postback mechanism and the event emulation, it also focuses much more on things like cross-page navigation, meaningful URLs and a better separation of concerns (routing, integration logic, presentation). Instead of trying to hide the way the web is really working and to replace it with a sometimes inconsistent and emulated desktop-like development model, it acknowledges that way and provides appropriate tools and helpers for developers to deal with request / response sequences. As you can see, I like the way ASP.NET MVC is doing things and I also like the possibility to use some ASP.NET WebForms features "in the back". So, what's wrong with the page / control model in ASP.NET WebForms? Nothing. Actually, the only thing we need to do is to shift it to a place where it works in a more natural way. Well, in my opinion, that place is the client. And that is exactly what the Microsoft AJAX Library is trying to do. So, why's that? First of all, the client has something that the Postback machanism can only simulate - the client has real state. Because of that, client-side events are really what they pretend to be: A way to respond to a actual user interaction directly without the need to "recreate" the whole state of the page first, perform the changes and then send it back to the client. Usually we need data to perform the operation that has just been triggered by a user, so what do we need to do now? Well, here is the revival of the good old client-server-architecture: We use WCF and client-side JavaScript proxies to communicate with the server and receive updated data structures directly. So instead of using slow UpdatePanels that complicate the whole Postback model further and create additional load on the client without reducing the work on the server, we let the server serve the data and our client update the user interface. We are not bonding an inconsistent framework with additional band-aids just because "it's so easy to use". We face the challenges of Web 2.0 and use our know how to create a responsive, well-designed Rich Internet Application by using client-side technology (like JavaScript with the Microsoft AJAX Library or Silverlight) for the user interface and server-side technology for cross-page navigation, initial response creation, business logic and the transformation of information. So, what is the master way to go? Our future applications take the following approach: The first rendering of a webpage is done by Sitecore or ASP.NET MVC, sometimes using some ASP.NET controls in the back, ideally with DataBinding. After the initial page has been received by the client, we switch to "real" AJAX and use object-oriented JavaScript to create straight user interface logic using the Microsoft AJAX Library and communicate over Web Services with our WCF services on the server-side. There might be situations where it is appropriate to use partial rendering features of ASP.NET MVC, but ASP.NET WebForm pages with Postbacks and UpdatePanels are definitely in the past. April 30 Windows Vista File Sharing sucks...Hi Everyone,
I just spent 4 hours searching for a solution to one of the most annoying "features" of Windows Vista. I found a lot of people out there who might most likely have the same problem so I share this with you.
I've got my own development server in the office, running Windows Server 2008 and a couple of notebooks and desktops at home. Because I needed to sync my OneNote Notebook to a fileshare on that server (syncing OneNote to SharePoint 2007 is somewhat painful at the moment), I connected my notebook using VPN (PPTP for the geeks) to our firewall (ISA Server for the geeks). Everything fine so far.
I then opened my Windows Explorer and entered \\servername to access my file server through the VPN. Bang. Error:
"\\servername is not accesssible. [...] the user name could not be found."
The first thing an experienced network administrator should think in this moment: Uh, security, of course. ("user name", you get the point...) But there was no problem with security. File permissions, share permissions, username, password, everything was totally fine, because I already synced my notebooks several times before that day. I then figured, it might have something to do with the whole Active Directory / Kerberos infrastructure, invalid ticket, whatever. But that wasn't the case either.
During my tests I discovered that when I entered \\ip-address, a username / password dialog popped up and I could log in - it didn't work automatically, because Windows couldn't be sure whether that address was a trusted ressource or not: LAN behind VPN, so no "local LAN IP address", even thought it was a private address range -and- no trusted extension (like mydomain.net) because I used direct IP to access the share.
So I thought by myself: Just put "servername" to your hosts file to bypass the DNS request. Didn't work either. I then figured, why not try \\servername.mydomain.net (mydomain.net is a placeholder for your Active Directory domain that is severed by your primary DNS server - in my case behind the VPN tunnel). It worked. And: No username / password dialog, because "mydomain.net" is trusted as part of my domain / group policy settings.
At that point, I should have been happy, but I cannot stand things that I do not understand. After searching for a couple of minutes, an idea popped up: As some of you might know, Windows still has two different ways to resolve names in a local area network: DNS and the old NetBIOS name lookup. It even uses two different ways to connect to a file share. The Pre-Windows2000 way by using SMB over NBT (which by itself stands for NetBIOS over TCP/IP, which means that the complete name is SMB over NetBIOS over TCP/IP... oh dear...) and the Post-Windows2000 way using CIFS (SMB over TCP port 445). Since the days of Windows 2000, DNS is the preferred way but NetBIOS still has the advantage that there is no need for a nameserver running in the network, receiving DNS registration requests from Windows machines and responding to queries. With NetBIOS, Windows just broadcasts his name or asks for anothers name using some strange combination of UDP and TCP requests.
I used the nbtstat utility to query the name of my server and it didn't come up with a result - of course, because NetBIOS wouldn't send a broadcast through a dial up connection like my VPN (although I received an IP address in a private range from the gateway, but let's not discuss that right now).
So, my guess in now the following. Whenever I used TCP/IP or DNS names (like IP address or a FQDN like servername.mydomain.net), Windows wouldn't ask NetBIOS to help resolving the name. But \\servername surely looks like NetBIOS could be helpful to the system. I don't know if Windows sents out two requests in a row, starting with a NetBIOS name resolution broadcast followed by a DNS request to the nameserver (or a lookup in my hosts file, which should've been fine to), but it looks that way.
UPDATE:
I just found the following link which explains the way, Windows 2000 does that name resolution thing. I guess, they haven't changed it since then and so my earlier guess seems to be right.
UPDATE:
By the way: Disabling NetBIOS over TCP/IP completely might not work for you if you have "older" servers or clients in your network, which do not support the new CIFS way of doing filesharing over TCP port 445 by encapsulating SMB packets. If you have those clients, you might have a real problem here, because if you disable NBT, you won't be able to talk to them at all. Try using the IP address instead. I tried to disable NBT on my VPN dial-up connection with very strange results - sometimes it worked, sometimes it didn't.
The whole name resolution thing doesn't really explain the problem I had accessing the file share. And here I am stuck. The error "the user name could not be found." indicates some kind of access to the server. I will keep you updated with my endeavours.
UPDATE:
The "CIFS-way" of using SMB works completely without NBT and relies on DNS name resolution. So, I'm starting to think about the following possibility: Maybe Vista is trying to use the "old way" to connect to a file share if the name of the server looks like an old NBT name, so it uses the old "SMB over NetBIOS sessions"-way instead of CIFS (which is used to connect to "real" DNS names and IP addresses). A very interesting overview can be found on Timothy Evan's website. But I'm not sure, yet.
UPDATE:
Using SMB over NBT is of course using the "old" way of encapsulating SMB packets into NetBIOS packet and sending them through the NBT session port (TCP/UDP 139). Obviously this NBT packet has a header named "CALLED NAME", so the server is aware of the name you used to open the connection (see http://www.ubiqx.org/cifs/SMB.html#SMB.2.1). Maybe this is the problem... Using CIFS (SMB over TCP/IP directly), the service on the server side cannot know what we called him - we just access an IP address that may have been resolved earlier. But using NBT maybe the server is a little bit bitchy, if we do not use the name he wants us to call him...
Looking forward to your comments and maybe if someone from Microsoft comes over this site, he or she can tell me, whether my guesses are correct or not. If they are correct, maybe that someone can tell me, who designed that thing so that I can go and kill him... (just kidding, of course... I surely would torture him at first. ;-))
Take care! April 13 iPhone - Visions about a new platformEveryone who knows me and had to listen to my "Why the iPhone will become a great mobile application platform" speach should take a look at the following video (I just finished watching it myself). I haven't had the chance to play around with the SDK until now, but I will definitely do that as soon as possible.
Of course there is a lot of blabla and fancy words like "unbelievable" and "oustanding" in the whole presentation, but bottom line, Apple invented a device that for the first time has the chance to become a real mobile application platform everyone can use. Palm, Microsoft and RIM worked very hard over the last 8 to 10 years but at the moment, their products cannot compete against the usability and the "personality" of the iPhone. March 10 Eight Reasons Why Omnipotence Makes A Bad ManagerOne of my best friends once said to me: "Julius, you cannot define the point in time in advance, you are going to change important things in your life at. You can only look back and determine it approximately in retrospect." Well, he was right.
When I started to redefine the way I work and live a couple of months ago, I would never have expected the things that happened since then. I really changed a lot of things in various roles in my life and I'm still learning new things every day.
It started with a very profound level by implementing a personalized GTD methodoly for better managing my actions, projects and commitments. I received a benefit from that new level of productivity and growing inner peace, I cannot value enough: Time.
This extra time enabled me to start with a much deeper way of thinking about my life, to invest time and effort for learning and studying and eventually brought me to initialize a process of inner renewal. At that time, I remember to back out from my daily job to some extend to focus myself on my feelings, my goals and my intentions that form the corner stones of my being. This process is by far not finished - it is literally a life long process.
After that initial period, I started to incorporate the habit of learning, excelling and renewing into my dayly schedule and returned with full consciousness to my personal and professional life. At that point, I decided to extend my evolution to my work as manager and leader in my own company.
"The first thing a real leader needs to learn is to manage himself. After that he can learn to manage others." (Steven R. Covey)
I have been a manager for about four years now, and I haven't always been a good one. I learned a lot of things from my parents, they both are very intelligent, reflective and emotionaly strong people - and they both have been managers for a long time. So I always had a strong basis for "management", but to become a really good manager, those basic approaches aren't enough.
I am developing technical IT solutions for more than 13 years now. I wrote my first program when I was 12, so I spent more time with programming, designing and architecting software and systems in my life than I spent without it. The reason I mention that is to make one thing really clear: Sometimes, I am a geek.
And when a geek becomes a manager, he needs to let go on things he usually would never had let gone before. For example, if anything fascinating to work on comes up, the geek inside me starts screaming: "I want to work on that, it is so great! I know how to do it and no one could possibly do it better! Let me do it!". If you are an individual contributor, it is totally okay to follow this inner voice, grab the task and immediately start to work on it. But if you are a manager, you better think twice.
It was such a moment in my life, when I started to think about what my job realy is. My job - as my dad always put it - is to produce results through the members of my team. Right, "through them", not by myself. So am I doing my job by doing certain tasks myself? No, I don't. Every time it did certain things myself instead of effectively delegating them to someone from my team, I created a self-repeating point of failure, because I remained the only one who could handle those kind of jobs now and in the future. Of course, it was nice to know, that they always had to call my when there is something "special" to do. It felt so good to be needed, although I never admited that openly before. So I took my phone on vacation, to family dinners and to nearly all other personal events. And when the phone rang, I said things like "I'm sorry, but they need me. They can't go on without me.". Sometimes, this way of fooling myself did go that far that I started thinking things like "Why can't they do those things on their own?". Of course, that is ridiculous, if I never told someone how to do it.
So let's do a reality check of what happens if you have that "omnipotent manager"-style:
1. Your company suffers. This is the major problem caused by several of the problems below. A "one man show" is bad for every company in many different areas: Performance, productive use of resources, quality and sustainability. Every company needs to make sure that there is no dependency on a single resource whatsoever.
2. Your job as manager suffers. Your job as manager is to have the members of your team produce results by thinking and executing for and by themselfes. The best way to do that is by giving them a goal to reach, a vision to follow, an desired outcome to produce and rules and guidelines to follow, by helping them to launch the project, by providing samples, support, feedback and advice on their way, by clearing their path without babysitting them to much and by shielding them from "other stuff" that may show up.
But it is neither your job to do the work yourself nor to execute your own methods through the hands of someone else and bypassing your team members brains. In the first case, there is absolutely no creation of additional value at all - the complete team is a wasted pool of resources. In the second case, it may look a little bit more like management, but in reality, there is no involvement of someone else's experience, no enhancement of productivity, no delegation of responsibility and no real learning.
A manager that is only "delegating" his own actions and methods to someone else can at best "manage" a maximum of ten to twelve people. A manager that helps his team to manage themselfes by providing guidance, goals and support can handle fifty or a hundred people.
3. Your job may be at stake. Despite the fact that you don't do your job very well at all, you will force your manager to decide whether to tolerate an incapable member on his management team that isn't executing well -or- to take the risc of having trouble for a relatively short period of time by replacing you with a new manager. There are only two reasons why you are not performing well as manager: You don't known how to do it or you refuse to do it on purpose. So what's it gonna be? Either way, eliminating the dependency is the way to go here: It's best to get unpleasant things over and done with.
4. Your future career suffers. Even if your manager isn't going to replace you because of your lack of effective delegation and management skills, he surely will never promote you. There are a lot of wrong reasons why some managers doesn't resolve dangerous bottlenecks (missing ability to see the problem, missing ability to deal with conflicts, etc.) but in that case, those people cannot work without you. Because you don't qualify someone else from your team to do their job, there is no way your boss is going to let you go. So most likely, you will do this job for the rest of your career in that company.
5. Your team suffers. If you continue to do all the important and interesting jobs by yourself - for whatevery reason - you will eventually send the following messages to your team: I don't trust you to do it right, I don't trust you to learn how do it right, and I don't tolerate you making mistakes by learning how to do it right.
Regardless of your intentions, that is the message everyone gets. And how can you expect from your guys to work with their minds, their hearts, their spirit and their consciousness on a project if you fail to communicate your appreciation for them and their work?
6. Your time and inner peace suffers. Because you do most of the important things by yourself, your workload is obviously increased exponential. So the time you spent at the office doing the work of your whole team instead of recharging your batteries at home, the stress to complete those things might kill you sooner or later, simply because so many other people depent directly on your actions.
7. The quality of your work suffers. Most of the work you are doing on your own is most likely supposed to be done by several people. So you will never enough time to finish your stuff with quality - by the way: the same quality that might have been the reason why you chose do it yourself in the first place.
8. Your schedule suffers. Doing everything on you own is not really a very smart way to get those things done very soon, even by making drawbacks in quality, which should never be an option in software development.
There are other implications as well, but those listed above are the most significant in my oppinion.
Of course, there are a lot of reasons to justify this "omnipotent way of management": Most people on your team may lack the experience, the knowhow, the performance or they cannot see whole picture (by the way: Whay can't they?). But seriously, how are you going to change that by not changing the way of handling those things? And is it really true, that your way is the only way to do things right? Probably not...
I started to change this wrong way of "management" (and really, we shouldn't call it that way anymore) somewhere in the past. I'm not done with it, I sometimes struggle and then I need to recall my motivations, particularly if there are setbacks. But living by correct principles and executing on the basis of accurate paradigms always means to hold on to the right way of seeing things, adjusting to the current situation and fulfil inner commitments, especially in tough situations. I learned that this is the best way to work with my team and to produce sustainable results, extensible value, strong knowhow, growing experience and superior performance.
Learning how to manage those things and delegate effectively is one of the most important steps from management to real leadership. March 05 Process Your Outlook Inbox in GTD Style: Ctrl-H-MThe new Ribbon interface of Office 2007 is not only much better for unexperienced users, it has even greater functionality for "hard core" powerusers as well. I use it in good old UNIX keyboard shortcut fashion (reminds me of my old Emacs days, somehow) and process my email inbox with Ctrl-H-M to quickly sort my emails into appropriate GTD folders. March 02 Wrong SignalsFrom time to time, I make mistakes. Yes, I do and I'm adhere to that. Sometimes I need to revoke a decision, I need to apologize for something I said, I need to fix something I did wrong or I just need to admit, that I fooled myself.
In these moments, although they are usually everything else than delightful, I realize that I have come one step closer to doing things right and becoming better in everything I do.
The last time I realized such a mistake was a few days ago. Two weeks earlier I set up a new reward system during the weekend for our team. I wanted my guys to be more interested in reading about new IT stuff and playing around more with new technologies, so I came up with the idea of a quiz. I wrote an email to the whole team and included different questions for different kinds of team members: One question for software developers, one question for project managers, a third one for sysadmins and a fourth one for web designers. They all touched "new ground", like new features of C# 3.0, questions about internal OS structures, SOA-related conceptional knowhow and HTML5.
The plan was to receive answers to one questions from any specialist and to receive a second answer from anyone regarding a "soft skill" questions as well: "What is the most important criteria for a successful customer project?" The replies to the second question were going to be published in our corporate intranet (assuming that answer to the first question was correct) and after that, everyone was going to vote for the best response, only the one he had given was prohibited. The winner with the most votes would gain one hour of free time extra and leave the office early at a certain day.
I was very suprised how few people responded to the survey. I was disappointed and I couldn't believe, how ungrateful everyone seemed to be by not even trying to learn those few things and not appreciating the work I had put into it, as well as the "reward" that he or she could gain, which of course would cost my company some money as well.
Some days later I started to realize what the real problem was. Let me share my insight with you.
At netzkern, we have strong values and paradigms for everything we do. One of these basic paradigms is to focus on Win-Win. Regardless of the various relationships in and around our company, be it within our team, with partners or with our customers, Win-Win is one of our most important philosophies in everything we do. We encourage and strengthen team work, guided by Scrum and other great methods. And we communicate Win-Win to our employees every day, because we expect them to communicate and live it towards their colleagues, partners and customers.
Another paradigm we focus on at netzkern is our working experience. We do that by equipping people with very good work places, fast notebooks which they can use for private pleasure, huge screens, a nice office and drinks for free. We have lunch together, we make jokes the whole day long and we have a lot of team events after hours, like soccer, playing Wii, going to the movies or doing sports together. Every few weeks we do some special event like cocktail parties, poker nights or just watching a movie with the whole crew. Part of everyone's contract is a fitness club membership, free entry to a lot of parties in our city and several other small things. And there is no "Sir" or any hard hierarchy - a lot of people in our team are actually real friends, regardless of their position. Of course, we need to make sure that the work gets done and we are reachable during business hours, but it is no problem to come in the very morning and leave earlier -or- to start your workday at 10am and stay late from time to time.
The result of this paradigm that I inherited from my senior partners and developed it further is very simple: Everyone enjoys their time at the office and to work in our team. Our people like the projects they move forward, because we let them choose most of the work they do and there is no "dropping the pencil at 5pm" or "hoping for the end of the work day before it is even noon" at all.
What I didn't realize a few weeks ago (when I had built that reward system) was that I had created something totally out of line with these basic paradigms. First of all, I created competition. I created Win-Lose. By letting team members compete against each other to gain a fortune, I ignored the spirit behind the Win-Win paradigm. I expected from every individual in my team to be better than the others - to beat their own team. I even expected them to judge the answer of their colleagues by choosing one over the other. And I expected them to choose the answer of someone else over the answer they had given themselves - although I asked them to give me the best answer they could possibly imagine in the first place. So why on earth should they rate the answer of someone else better than their own?
The second problem was the reward itself. Why should I reward someone with extra "out of office" time when my basic philosophy is to have my team to like it being at the office, to be productive and to move our projects foward? Even worse: Underneath I gave my people the impression that I myself would recognize "leaving the office early" as something everyone might desire, because I made it a reward. And seriously, I don't. I myself love being at the office, I like to be with my team and I like to be productive and focussed on interesting projects.
So eventually, my "new" reward system was full of flaws. And when I presented it by asking the first "question of the week", my team responded in a totally appropriate manner. Although no one directly explained to me why he or she didn't like the new idea, they all seemed to feel that my idea wasn't as shiny and excellent as I thought. I had given them a wrong signal. So they didn't respond to it at all.
Now, that I realized that, I will come up with a new reward system. But was everything a waste of time? Not at all. Now I can incorporate my findings into the new system and make sure that it is in harmony with the values and paradigms of our company. February 08 Fitness for Body, Brain and SoulSome people might have noticed that I changed some things in my behavior and my habits during the last months. The truth is, I have changed really a lot of things, but most of them had much more silent impact on my life. What is visible outside is more or less the result of my efforts and to be honest, I'm a little proud of what I accomplished. But let's start at the beginning... I worked really hard over the last 4 years and I achieved a lot. I had to overcome a lot of obstacles to get where I am today. I loved my job, our company was running very well and we were getting more and more successful with every step we took - but instead of being overwhelmingly happy about that I felt worse with every step forward we made. My work seemed like an excuse for all the things I wasn't doing, especially in my personal life. The truth is, I hadn't treated my body very well, I was overwhelmed by all the additional work that came with the success and there was this feeling that there were so many things in my professional and personal life that I should have done already - and being just 24 years old, I had the feeling that something was fundamentally wrong with that. So I decided I had to change some major things in my life. As Marquise du Deffand said: Distance is nothing, it is the first step that is difficult. And indeed, it was. I knew, I had to introduce a major change at the very beginning. Usually habits should be developed in small steps, one at a time. But to have a point of reference for my decision, like a gunshot at the beginning of a race, I had to start with something really big. So I decided to take the money I worked so hard for over the last years to get myself a Personal Trainer. Ironic, isn't it? The training really changed my life. Going forward with my body seemed in some way to free my soul and relieved me from a lot of pressure I felt the years before. Always finding excuses NOT to do sport obviously needed much more energy than just doing it. Of course my ultimate goal was to reduce my overall stress and to focus more on the qualities of my life, at work and at home. But to have that freedom, I had to increase my productivity and to get much more control over my "stuff" in a more natural, relaxed way. Some years ago, I received a present from a very special person in my life. The book "Getting Things Done" from David Allen. Of course, I had never time to read it. I should have done it. It really changed the way I work today. There are some great books out there that can really make a difference in your life. Two other books I'm listening to at the moment (both for the second time) are "Eat That Frog" from Brian Tracy and "The 7 Habits Of Highly Effective People" from Steven R. Covey. Another great resource for personal productivity is the Zen Habits blog of Leo Babauta. Especially the articles about inner resistance and the way especially smart people often start playing around with their productivity system were some kind of eye-opener. So what happened to netzkern while I was focusing on myself a bit more than I did the years before? Well, today we are somewhere among the top 100 new media companies in Germany and we will most likely be awarded as one of the 50 fastest growing IT companies in Germany over the last 5 years as well. Compared with the time four months ago, I'm feeling great today. I still have a long way to go, but I'm really looking forward to it. There are a lot of productivity and happiness secrets to discover, but I keep you posted about my endeavors. Have a great day! February 02 Declaratively Re-Executing Methods on ExceptionsIn my previous post I've written about AOP and provided a lot of links, especially pointing to one article that combines WCF and AOP. Shortly after that I watched this video from InfoQ showing the use of Spring AOP (in Java) to re-execute an operation that fails with an exception. Actually this 'Retry' pattern is a great idea in a lot of SOA scenarios where you should be prepared to handle some connection problems or load issues at the foreign party. Usually we've implemented this way of dealing with messages in our systems using an asynchronous backend process that re-submits messages several times (up to a max) if the initial submission fails. This is of course only possible if the information within the message is not time critical, because we work with time spans from 5 minutes to several hours. Also this solution is fairly complex as it uses a windows service, several asynchronous threads, a database as well as logging and exception handling mechanisms. Of course there are other SOA situations were you would like to have a 'Retry' mechanism although you have only a couple of seconds max to execute your call to an inconstant resource again (an external check for example), but do you really want to implement this whole "while(maxAttempts) { try blablabla catch ex }" thing over and over again? I wouldn't. So creating an aspect in whatever AOP framework (I prefer Spring.NET, AspectDNG and PIAB) that handles this 'Retry' for you, ideally configured using META-Data (Annotation, Attributes, whatever it is called in another language, e.g. [RetryOnFail(5)] in C#), looks very straight forward. We can then "attach" it to every operation we would like to execute more than once in case of trouble. So trading in a little bit of performance (even if there is no exception at all, AOP creates some processing overhead) for the higher reliability of our app and less error messages in the UI seems like a good deal to me. AOP in .NET SOA applicationsComing from the Java Enterprise World and always being very curious in new and rising technologies, I came across AOP about 5 years ago and started by first playing around with the Spring Framework and AspectJ. Since my commitment to .NET some years ago (2003, which means 5 years now, time is passing by...) I was searching for an alternative and eventually found one of the first beta's of Spring.NET (I'm a huge fan of this project) and some other implementations like Aspect#, Aspect.NET, AspectDNG, PostSharp and even created my own little, never-leaving-the-prototype-state based on .NET Enterprise Services (formerly known as COM+ Component Services). AOP always needs some kind of "interception", which leads us to the difference between compile-time, link-time and runtime approaches. The first two can also be summarized as "weaving", the last one is a "proxy" approach. Both have advantages and disadvantages. To use the runtime approach, you need a way to "intercept" calls to the objects you want to "wrap" with other code. The .NET framework brings some features that support you in creating these "shells" around your code, namely contexts and proxies. You find contexts in .NET in two flavors: .NET Remoting and .NET Enterprise Services ("COM+", "Component Services", etc.). Contexts can be summarized as a "room" where your object lives. By calling a method on the object from outside the room, you need to use the door and the room can change your call (or "message") to whatever he wants. Juval Löwy has written an excellent article in one of his O'Reilly books named ".NET Components". Last year, Microsoft introduced Enterprise Library 3.0 and 3.1, which included the PIAB (the "Policy Injection Application Block"). PIAB is a runtime proxy that uses .NET Remoting contexts, which makes it necessary to use a factory and to inherit your objects from the special .NET Remoting base class MarshalByRefObj. The main reason they did that was that PIAB is supposed to run without installing or configuring complex features. Other reasons are explained here. In my opinion the best way to do AOP in .NET might be to extend the CLR to allow full-fledged proxy objects (and not only those that transparently "simulate" interfaces), and I hope to see that in the near future. At netzkern we are really into SOA and Web Apps, so our primary objective by using Spring.NET and EntLib every day is to improve the quality of our "composability", which means our code base, our maintainability and our overall software design. SOA is - in itself - the component thinking of the 21st century. Of course, components are important within a software or library also, but in terms of reusability an approach that treats services as loosely coupled, distributed components (like the Service Component Architecture in Java EE) should by a primary goal of every enterprise developer. So how can we use AOP to help us with our SOA development, especially in WCF projects? Have a look at this very good article in MSDN Mag, that uses MS EntLib and the PIAB to create a "chain of responsibility" by fading out cross-cutting concerns. Have a look at the following links from my link collection (yes, Mattes, I will upload it completely some time): January 25 Does .NET 3.5 break compatibility?At netzkern we use WCF (see my earlier posts) in a couple of SOA projects. We had to create a custom message formatter for the use with an external partner that uses a service interface for wrapping several XML messages of various types (different root nodes and purposes). Because of that I created an IOperationBehavior that attaches my custom formatter if the attribute [ProjectFormatter] is present at the service contract. This formatter knows anything about the messages and is able to 'understand' the other party, because it uses the 'old' XmlSerializer (and not the DataContractSerializer) with additional type information about the contracts.
In .NET 3.0, that runs on our production servers, the following code fragment from my IOperationBehavior runs without any problems:
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{ clientOperation.Formatter = formatter; } But since we installed .NET 3.5 and VS2008 on our development machines, our library started to throw an InvalidCastException error. It claimed that the framework wasn't able to cast our internal message contract to the appropriate Message object in WCF. I debugged my code and noticed that my custom formatter wasn't called anymore. Luckily, I also came across a property that looked suspicious: SerializeRequest. I couldn't remember have seen it before and as it was set to 'false', I decided to be brave and switch the value to 'true':
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{ clientOperation.SerializeRequest = true; clientOperation.Formatter = formatter; } Now everything works fine again and I'm a little pissed at Microsoft, but I think, it will go away in a couple of days. I haven't found any word about this 'problem' in the web now, but I'm sure, I will. Breaking compatibility is - by the way - not very professional.
Update
Obviously Microsoft always had that property, but ignored it. The framework documentation shows it in v3.0 (http://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.clientoperation_members(VS.85).aspx) as well as in v3.5 (http://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.clientoperation_members.aspx). So I wonder why my code worked in the first place...
Update
As one can see from the following link (http://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.clientoperation.serializerequest(VS.85).aspx), in .NET 3.0 the default value for the property is 'true' so there was no need to explicitly set it. The documentation in .NET 3.5 hasn't changed (http://msdn2.microsoft.com/en-us/library/system.servicemodel.dispatcher.clientoperation.serializerequest.aspx), but obviously the property isn't 'true' by default anymore.
January 18 Windows Server 32bit and Web ApplicationsDid you ever wonder why a lot of companies buy a lot of large servers with 8 GB and then waste their horse power by not configuring them properly?
Every experienced software architect in the Microsoft world knows, that a 32bit Windows (even a server) can allocate a maximum of 2 GB per user-space process and additionally 2 GB of kernel-space memory. So why buying a server with 8 GB of RAM and having it wasted all that memory? The trick is of course a way to use more than one process to do the work the server was bought for. In an ASP.NET web application that means using multiple w3wp.exe worker processes, also known as a Web Garden. But how do you configure your application pool to make maximum use of your server resources? Well, Jesper has written an execellent article about that specific topic. You should really take a look!
And by the way: Don't forget about the Session State or you might loose a lot of customers. Sitecore Competence Center Germany - Vol. 2We're at netzkern are really excited to announce three more Sitecore Level 1 and Level 2 developers in our team. Phil and Heiko have completed SCD1 and SCD2, while Sijo and Markus additionally completed their SCD2 exam very successfully. They now extend our Sitecore Developer Team to 12 certified Level One and 9 Level Two developers as well as 3 upcoming trainers (Markus, Boris, and me) which are going to be certified over the next months.
Especially I want to thank Lars for doing this great training here in Germany. It was a pleasure to have you here and I'm looking forward to our next meeting as soon as possible!
Congratulations by the way to my buddy Phil in Austrailia, who has successfully passed the SCD1 and SCD2 trainer exam as well! Sitecore Installer Error on Windows VistaHi everyone,
by installing Sitecore on Windows Vista with UAC enabled, you may get an error regarding th VBScript runtime that is used by the installer. To solve the problem, you need to DELETE the Registry Key "HKCU\SOFTWARE\Classes\CLSID\{ B54F3741-5B07-11CF-A4B0-00AA004A55E8}", which is kind of a wrong registration of the VBScript runtime within the local user registry part. Follow the following link for more information:
December 01 The RichCommandResult PatternToday I want to show you a new pattern we developed in my last project: The RichCommandResult pattern is an approach to utilize some common features of high level programming languages to provide a more unified way of communication between application layers (especially UI and business logic) and to bypass common mistakes, especially made by unexperienced developers.
A common problem in modern OOP languages, especially statically typed ones, is out-of-band communication and the use of optional information that need to be returned from a method call (like a Success-bool). For example consider the following scenario:
A method that loads a database entry is called from the user interface: Order o = LoadOrder("HDG6734ZS");
The normal way of programming is to assume that everything went fine until an exception is thrown. But because we are in the user interface we need to make sure that the user gets a customized error message instead of a whole screen exception message. So we need to wrap our code:
try { Order o = LoadOrder("HDG6734ZS"); } catch (SomeException ex) { ShowErrorMessage(ex.Message); }
Wow, a lot of code to write here just to show a simple error to the user that may happen a lot of times. Additionally: Exceptions are really not the fastest way to distribute information between methods and not really a "defined way" either, especially between application layers (Java is the exception to the rule here because exceptions are part of a method's signature).
Exceptions are a great way for the flow of applications WITHIN a layer, but especially by taking a look at the UI, they have some drawbacks. An exception from a "deeper" layer (e.g., data access) needs to be caught within the business layer and wrapped into a new exception for the UI layer, if there should be no tight coupling between layers underneath the business layer and the UI. So you may end up with a couple of generic exceptions in the business layer that wrap specific errors from the data access -or- a lot of wrapper-exceptions that are doubled across the layers. So, what can we do about this?
The first idea crossing every developers mind is of course to return a special ReturnObject instead of void that has a properties like "Success" and the "real" return value in a generic property called "ReturnValue". But this leads to another problem. Some developers started writing code like this:
==== WRONG ====
if (UpdateOrder(order).Success && UpdateOrder(order).ReturnValue != null) ShowSuccessMessage();
The problem here is obvious: The method is called twice. Of course an experienced developer should see that, but a good software design is not based on assumptions about the knowhow of your developers. Fortunately C#, Java and others programming languages provide us with methods that normally have reference parameters when using custom objects, some even with additional features like the "out" keyword in C#.
So what is the preferred way to design a public method that is called from one layer to the other? The signature of a RichCommandResult method is as follows:
void DoSomething(object paramA, object paramB, out CommandResult<TReturnType> result);
CommandResult<TReturnType> is a generic object that contains the result of the method (which is of type "TReturnType") as well as additional information like "Success" (boolean), "TimeTaken" (TimeSpan), "HasReturnValue" (boolean), etc. So you can write your UI code like this:
CommandResult<Order> result; LoadOrder("HZS67SG67", out result); if (result.Success && result.HasResturnValue) { ShowOrderToUser(result.ReturnValue); // ReturnValue is of type "Order" in this case. } else { ShowErrorToUser(result.ErrorMessage); }
Much cleaner, isn't it? And by the way: All exceptions are caught in the layer below, the UI developer can focus on simple boolean properties in the result object and retrieve additional information from the method call. Even unexperienced developers cannot call the method twice mistakenly to work with any type of return value like they could do it in "normal" functions with a "normal" return type:
==== WRONG ====
if (LoadOrder("HDG6734ZS") != null) ShowOrderToUser(LoadOrder("HDG6734ZS"));
...which of course is also error prone in several other ways. Just to be clear: This pattern only makes sense if used wisely. It is no solution to "common" application flow tasks which needs a well designed exception hierarchy. But for the convenience and security of the UI developer it turned out to be a very good way.
Have a nice day! ASP.NET Web Development - Some Thoughts...As I started to work with ASP.NET some years go (Wow, time is passing by...), I realized that there was a huge potential using components, controls, events and the code-behind model, especially by looking at developer productivity and a familiar approach for UI developers coming from the desktop world - although my roots have always been the web world and Java EE.
However, being a Software Architect, I realized that the ASP.NET WebForms model was somewhat "dirty" compared with other approaches I worked with before, for example JSF, Tapestry and Struts as well as some others. Of course, having no real "hard" seperation between UI presentation, UI logic and especially business logic as well as data access powered the productivity of most developers, but also lead to code that was not as clean as I it could have been.
In the last years it came clear that the architectural benefits of MVC frameworks like Ruby on Rails, Struts as well as other MVC Front-Controller frameworks and also the features of better seperated component frameworks like JSF and Tapestry had some advantages in bigger projects because of the "cleaner" developmend model, but couldn't really reach the productivity of ASP.NET.
Even highly sophisticated, dyanmic languages like Ruby and well-designed frameworks like Rails, which I totally love, did not lead to the same results in functionality, security (e.g., exception and error handling) and productivity.
I have to admit that I'm really the "tool guy" when it comes to software development: I want to be able to use any tool out there in the appropriate way to solve a problem the best way. So I came across MonoRail and lately I started playing aroung with ProMesh while searching through the .NET web world, but ScottGu's team had done such a remarkable job by providing great tools and features to overcome the minor architectural weaknesses in ASP.NET that there was no real chance for other frameworks to compete with a well-designed ASP.NET application with wisely-used components and features (the focus is on "well-designed" and "wisely-used", by the way).
Now ASP.NET MVC is coming. Of course I'm the first one to get my hands on the CTP, and I'm quite sure that there is indeed a lot of things one can do with this new framework, especially in combination with the upcoming DLR and languages like IronRuby, IronPython and others. But I wonder if it's really the new way of developing .NET web applications, or more like an iterims addition in combination with WebForms and its controls to overcome some of the architectural drawbacks until the development has moved completely to "real" AJAX apps with much more client-side logic.
In my oppinion, the MVC approach to AJAX by using server-generated snippets and sending them to the client ("partial page-rendering") cannot compete with a "pure" AJAX application that uses server-side services and provides some kind of real "desktop-feeling". Using components, XML-Script and controls the way ASP.NET AJAX does it, seems to me the right way to outperform the "classic" MVC way of web development over time.
Okay, enough thoughts for now.
Enjoy the weekend! November 03 Mobile GPS Tracking AppThis week I received my new TyTN II, a HTC Windows Mobile 6.0 PocketPC. Because it has GPS included and I am ill, having nothing to do and lying around in my bed (very boring...), I decided to code a little application for my new phone that reads the GPS coordinates and publishes it to a web service running on a server. Then I connected this information with Google Maps and now you can track my current location at http://bsky.de/JT/Show.aspx. Wanna Proof? Have a look... August 26 WorkflowRegistry - A good friend and helperToday I want to make a short trip to WF and how I used it in a current project. It is the same project I've blogged about in my last post and we're talking again about a windows service that runs a lot of asynchronous tasks. This windows service exposes its functionality as a service to others components that by this means do not have to deal with all the asynchronous, synchronization and evaluation stuff.
The service calls two external web services in parallel on their own threads. In the past we did this using pure C# code and asynchronous delegates but as the logic became more and more complex we switched to WF to handle the complexity in a more convenient way.
We created a master workflow and two child workflows that perform the requests to the external services. The main workflow receives the outcome of both and performs some evaluation of the reponses, before it returns to the main thread. The child requests are called using the "InvokeWorkflow" activity in the master workflow.
Our goal was the following execution model:
Service Thread: Receive the request from another component and start the master workflow
Service Thread: Wait for the master workflow to complete
Master Workflow Thread: Perform some validation
Master Workflow Thread: Start the First and the Second Child Thread asynchronously
Child Threads (Sub-Workflows): Do the work side by side
Master Workflow Thread: Wait for the First and Second Child Thread to complete
Master Workflow Thread: Perform some evaluation and return to the Service Thread
Service Thread: Read the response from the Master Workflow Thread and return it to the component
Because we want the child threads to run simultaneously, we couldn't use the ManualWorkflowSchedulerService for the master workflow. By using it our child workflows would have been executed on the same thread as the master workflow and by that means in a row instead of in parallel on their own threads.. A solution to that problem could be to create an additional WorkflowRuntime without a manual scheduler service and let the child services run on that, but that hadn't solve our problem in the master workflow to wait for the completion of the child workflows.
Of course the WF infrastructure has the ability to provide the application with some execution status of all running workflows, but only if the workflow runtime has a persistence service in place. Because our workflows only have a very short lifetime, a persistence service was to much overhead and so we decided not to use it.
Essentially what we want to do is to have some kind of function like "WaitForWorkflowStatus(instanceId, status)" in the master workflow to wait for the child workflows and in the service thread to wait for the completion of the master workflow. So I wrote it.
The netzkern.Workflow.WorkflowRegistry is a static helper class that has some additional functions to help you deal with WF. First of all it is a static factory for a WorkflowRuntime. The runtime is internally prepared, started and some events are registered that allows the WorkflowRegistry to give every application a more convenient access to some features of WF.
First of all it exposes a function called RegisterCallback(instanceId, delegate) for registering a callback. By using it one can register a special callback just for a special workflow instead of registering for every workflow event through the use of WorkflowRuntime events.
Additionally it has the static function GetState(insanceId) that returns a WorkflowState object and provides access to the running workflow instance and a property called Status to read the current workflow status even if there is not persistence service registered in the worklfow runtime.
Last but not least it has a function to wait for a special status to occure on a executing workflow while blocking the current thread as long as the workflow does not have that status. This function made is possible for us to write the following two lines of code after invoking the external child workflows using the InvokeWorkflow activity:
WorkflowRegistry.WaitForWorkflowStatus(InvokeChildWorkflowOneActivity.InstanceId, WorkflowRegistry.Status.Complete);
WorkflowRegistry.WaitForWorkflowStatus(InvokeChildWorkflowTwoActivity.InstanceId, WorkflowRegistry.Status.Complete);
Internally the WorkflowRegistry uses an AutoResetEvent in a thread-safe manner to wait for the workflow to enter the requested status by blocking the running thread as long as necessary.
I will post the source code soon. July 29 The XmlSerializer odyssey - Learning about .NET, XmlSerializer, the CLR and WCFHi Folks, Today I picked an issues from our bug tracking system that can kill your whole day. Although in the end it was really very interesting and you learned a lot of things, these are not my favourite kind of bugs I like to solve. The description of the ticket was "Windows service consumes a lot memory over time." - Right, great description, but lets start with a brief overview of the project: We're running a lot of asynchronous tasks in our projects, especially in projects which make extensive use of web services to communicate with slow external services in the background. As we switched to WCF some months ago a lot of tasks became much easier. Some of these services are hosted inside IIS, others are windows services. In a current project one of the external parties is a Java-based XML interface, which indeed makes use of SOAP for the transport of messages, but relies completely on "plain" XML inside the SOAP message. All the messages are based on a XSD but there are several "root level" messages that may appear in a request or response. As I tried to deserialize the schema with WCF's internal utilities in the first place it stated that it was not able to, but I should instead use the xsd.exe, which creates code that must be used with the "older" XmlSerializer of .NET 2.0 instead of the "newer" DataContractSerializer (a.k.a. XmlFormatter) of .NET 3.0. So I did. The generated .cs file looks like a typical file designed for use with XmlSerializer - several [XmlWhatever] Attributes and lots of classes with even more properties. To create a WCF service that uses the XmlSerializer for serialization and de-serialization instead of the DataContractSerializer, one has to annotate the service interface with the attribute [XmlSerializerFormat] (and some additional parameters maybe). At that point the problem was the following: I couldn't create a service interface that was strongly typed. Reason: The plain XML root node within the SOAP message could be one of several, but does not have any "real" type information in it. It was just named like the type that xsd.exe generated for me, certainly without the C# namespace. Because I couldn't get WCF to recognize the type correctly when I created a service interface that accepted and returned 'object', I decided to work with generics to not loose more time with the problem. So whenever I created a service client in code, I passed the request and the response type to the client and after that the WCF engine had enough information about the XML messages to (de-)serialize it. Worked great for the moment and I moved to the next part of the project. But back to the issue: "Windows service consumes a lot memory over time." I started by installing ProcessExplorer from Microsofts TechNet SysInternals website and took a look at the process. I was very suprised after discovering that the process had a huge "loader heap" and about 2500 assemblies loaded. So why on earth should a process have 2500 assemblies loaded? I also recognized that the windows service frequently created a subprocess which then called the C# compiler (csc.exe), so obviously there was some kind of code generation going on. Because I worked with the XmlSerializer a lot in other projects, I knew that by calling XmlSerializer a specialized assembly was created for the type that was going to be (de-)serialized, but why should WCF create a new assembly every time a XML message was received or created? After thinking about the problem for a couple of minutes I came to the conclusion that it must have something to do with the way our project uses WCF and that there may be a connection with the way we're using generics to create the "right kind" of client proxy at runtime. In fact it turned out that WCF calls the XmlSerializer with a special constrcutor and passes the ExtraTypes[] array as parameter which then results in a XmlSerializer object that can not be correctly cached by the framework. So I decided to re-design the whole service library and to create my own "DTO aware" XmlSerializer. It should evaluate the right kind of internal object by taking a look at the root element of the incoming XML message, create the type by using reflection (or maybe later by using some kind of mapping for performance purposes) and of course use a cached version of XmlSerializer. First of all let me say that WCF is a really a great example of software architecture. It's like LEGO - everything up and running in minutes but you can replace any pieace by your own code, configuration and extension. To accomplish my goal I created my own implementation of IOperationBehavior and made it available to the my service library by creating it as a C# Attribute named [ProjectFormatter]. By annotating a [ServiceContract]'ed Interface with my [ProjectFormatter]-Attribute, the behavior replaces the default formatter with the one I created. This ProjectMessageFormatter implements IClientMessageFormatter and IDispatchMessageFormatter. There are only a few methods to override and some simple Singleton patterns and in the end I had my own project serializer that not only resolved my memory issue but also provided my team with a much more convenient way to use the WCF client. In the end I have to say that I'am really happy having read so much about C#, the CLR, XML handling in .NET and the WCF in books, blogs, on MSDN and by watching so many webcasts - no wasted time at all. I really hope to help someone else with this blog entry to understand the details a little bit better and looking forward to resolve my next "level 3" issue. :-) June 29 Professional Build Management in Small and Mid-sized ProjectsWith growing projects and project teams with 3 or more persons the need for a professional Software Change, Configuration, Source Control and Build Managagement ("SCM") is rising dramatically, especially if there are a lot of specialized internal and external libraries that change frequently.
At netzkern we evaluated NAnt, MSBuild, CruiseControl.NET and (of course) Visual Studio Team System over the last week.
Although VSTS is by far the most expensive solution (about 70.000,- € for 10 developers with Visual Studio Team Editions and 22.000 for 10 developers using Visual Studio Professional + CAL for Team Foundation Server), it seems not to be the best solution at all.
Working some days in projects with several of the tools mentioned above, we found that the best solution for our team (at the moment 6 developers with about 8 visual studio projects and several libraries in the running project) is a tool that is mostly unknown: FinalBuilder.
It is not only very flexible, intuitive, based on .NET and features great functionality with a lot of integrated actions and javascript/vbscript support, it also has the ability to work with a variety of version control systems (we use Subversion, but may switch to TFS because of its VS integration), integrates other build tools like MSBuild and NAnt and can be extended through the integrated IDE and complete .NET-based development.
Because of its integrated .NET and XML support it is also great for Sitecore-based projects, has support for databases, IIS management and can be used with a build server for Continous Integration.
To be honest I would expect such flexibility and functions in a so expensive tool like VSTS. |
|
|