02 March, 2011

Love, Hate and the ViewState

I was recently tasked with creating a SharePoint interface to Microsoft Dynamics GP Item Maintenance. As the client's business had grown, inconsistencies in theirItem Master became apparent. When new items were needed, a similar existing item was copied and the details updated to match the new item. If there were no similar items, a new item was created.

The problem was, many of the existing items were not properly categorized. There were people in the organization who knew bits of information about items, but nobody had all the information to correctly set up an item. Setting up an item properly required a combination of phone calls, emails and a little bit of luck. It could sometimes take weeks to get the item set up. This held up BOMs, Routings and pretty much everything else dependent on the new item.

As usual for a new project, we started with a discovery phase. We tried to identify the groups that knew the necessary bits of information about items and proceeded to schedule interviews. During the interviews we gathered a lot of information to help us get started. We also found several points that were unclear: i.e. Accounting said Billing provides this. Billing said it was Sales, Sales thought it was Purchasing, Purchasing pointed to Engineering and Engineering said it was Accounting. After a few round trips, we were able to pin most things down but in the end, there were still a few bits of information that nobody understood.

It was clear the Workflow would have to be very flexible. Adding to the complexity, some Items could bypass entire groups. For example, there's no need to set a price on an item if you don’t sell it. And why bother Purchasing if you are making this item in your own shop?

To solve this business problem, we needed to deliver a product with:
  • Flexible Workflow rules that could be adapted as the business changes (and as the users work with the system and discover steps that had been overlooked.)
  • Field-Level security to ensure each group can only edit their section.
  • The ability to assign some fields to multiple groups and users.
  • The ability to add new fields and groups as new requirements surface.
  • Audit Logging for accountability.
  • An intuitive User Interface.
  • The ability to open, edit and copy existing GP Items.
  • The ability to push the approved changes back to GP.
This was not a simple SharePoint WebPart with an ASP.NET Form you can throw on a Page and start using. The form had to be generated dynamically, with different sections editable by different users. Lookup Fields had to be populated dynamically. Some based on values stored in GP like Class Code, others with a Hard-Coded list of choices, like Item Type. Oh, and new lookup fields could be added at any time.

What's more, the fields and their rules were not known at design time. All the metadata describing the rules had to be parsed during form generation and postback.

While building this application I ran into a few challenges. One in particular had to do with updating posted values based on business rules and then rendering the fields, not as the user had posted, but as the rules dictated. Now that you know the background, how do we make this happen?

The ViewState is great! It makes your life as a web developer so much easier. Some action causes a postback and all the fields are repopulated with their values. You can handle events for a control without having to worry about the rest of the form. Controls get their IDs set automatically. What's not to love? Plenty.
  1. The ViewState is transmitted and parsed with every page post/load cycle using bandwidth, memory and CPU cycles for the server and the client.
  2. The ViewState, while encoded, is not encrypted. I've seen this argument and I don’t think it's really all that relevant. After all, you are sending the same information back and forth through the form fields. And if you need to keep a secret, use HTTPS.
  3. The ViewState is persistent insistent. If I take the users' submitted data and do some processing on the backend, I just might want to change some values on the form when it is sent back. Suppose they tried to order 100 widgets but you just sold some and you only have 75. Send the form back to the user with quantity set to 75 and display a nice alert telling them they are lucky to get that many. Thanks to ViewState, the quantity field gets set back to 100 automatically. I know, JavaScript could validate the page before the user submits. But what if the user is running without JavaScript? I know I do unless I'm on a trusted page. Add-ons like NoScript offer significant protection while surfing and after a whitelisting your common sites, they pretty much stay out of the way. But even with JavaScript, if I'm dealing with Dynamic Data, (what other kind is there?) the validation rules may have changed since the page was loaded. I suppose I could build some AJAXy validation JavaScript, but again, you can't count on JavaScript being there and you should never trust anything a user submits, even if you think your JavaScript has sanitized it.
So, lets say we want to prevent ViewState from running. Easy enough, just set the EnableViewState property of the control to false:

         TextBox t = new TextBox();
         t.EnableViewState = false;
         t.Text = this.ToString();
         return t;

And what if you want to disable ViewState on the whole page?

      private void Page_Init(object sender, System.EventArgs e)
      {
         this.EnableViewState = false;
         //do some other interesting stuff 

      }

So, you’ve defeated the ViewState on a couple of projects and now it's time to build a SharePoint Web Part. Create the Web Part, add some controls, disable ViewState, deploy the Web Part and life is good! And we still have time to make happy hour (the first one!)

Wait a minute. Didn't you disable ViewState? Why aren't your backend changes sticking? Because SharePoint Web Parts love ViewState so much, they insist on using it. Disable it on the control? Doesn't matter. It happens anyway. Control.ClearChildViewState() doesn't even help. How about firing up SharePoint Designer and disabling it for the entire Page that hosts the Web Part? Congratulations, you've done it, and you’ve disabled pretty much all the SharePoint functionality too. No, there has to be way around this.

Actually, there are two:
  1. Create a LiteralControl and build it's Text property with the HTML you need to create your form field. Now you can render the LiteralControl and IIS will never even see the field. Remember, you will have to inspect the Post Variables yourself to find out how many widgets you just sold. Unfortunately, I tend to make the occasional mistake dynamically building HTML from within a C Sharp app. Leave off a quote or miss a closing tag and your form starts acting really strange. That brings us to the other option.
  2. Create your controls like normal and add them to parent controls if you like. No need to worry about the HTML, IIS will get it right. But instead of adding the controls to the page (or a page element) render them into the Text property of our friend the LiteralControl. Using a StringBuilder, a StringWriter and an HTMLTextWriter, it all falls into place
             //...Build your textbox as you like. 
             //Don't forget a unique ID. 
             TextBox tbQuantity = new TextBox();
             LiteralControl LControl = new LiteralControl();
             LControl.Text = this.RenderControlToString(tbQuantity);
             this.Controls.Add(LControl);
    

          public string RenderControlToString(WebControl Control)
          {
             StringBuilder sb = new StringBuilder();
             using (StringWriter sw = new StringWriter(sb))
             {
                using (HtmlTextWriter textWriter = new HtmlTextWriter(sw))
                {
                   Control.RenderControl(textWriter);
                }
             } return sb.ToString();
          }
Using the HtmlTextWriter, we have well-formed HTML without having to worry about ViewState moving our cheese. Even in a SharePoint Web Part. And as a bonus, the ID you assigned to the Control is the ID that will be returned. No prepending all the parent control IDs by IIS. This, too helps to reduce the page size and load time.

No comments: