Tuesday, March 25, 2008

The Controls collection cannot be modified because the control contains code blocks (i.e. <% %>)

This one hit me some time ago but was easy to work around. Today I had to face it in a ListView's LayoutTemplate:

<LayoutTemplate>
    <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
    <% if ( ApplicationUser.IsCurrentUserTheAdmin )
       { %>
    <hr />
    <asp:ImageButton ID="SwitchAddSectionButton" runat="server" OnClick="SwitchAddSectionButton_Click"
        ImageUrl="~/Resources/item_add.gif" />
    <% } %>
</LayoutTemplate>

As you can see there is a dynamic part of the template which is available to users with sufficient priviledges.


Although google provides a lot of articles referening the issue, the solution I've came up is amazingly simple:



<LayoutTemplate>
    <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>
    <div runat="server">
        <% if ( ApplicationUser.IsCurrentUserTheAdmin )
           { %>
        <hr />
        <asp:ImageButton ID="SwitchAddSectionButton" runat="server" 
            OnClick="SwitchAddSectionButton_Click"
            ImageUrl="~/Resources/item_add.gif" />
        <% } %>
    </div>
</LayoutTemplate>
Note that the only difference is the <div ... runat="server"> tag which contains all other additional controls in a control tree.

Monday, March 17, 2008

WebServices, 302 Object moved and "Client found response content type of 'text/html', but expected 'text/xml'" exception

The issue

This article focuses on the issue of WebServices intentionally redirecting requests to other locations. Please first make sure that the exception is not caused unintentionally, for example by incorrect authorization policy in web.config.

In general, Web Browsers handle redirects with no issues. If "302 Object moved" is sent to indicate a redirect, the web browser happily checks new location and retrieves the data.

However, while web browsers automatically handle redirects regarding requests to WebServices, it seems that client proxy classes do not automatically handle such redirects. This is quite annoying - my WebBrowser automatically redirects 302s, the HttpWebRequest also does but the WebService proxy inheriting from SoapHttpClientProtocol does not!

I am not going to answer a general question "why do you need to redirect a web service call". I just assume you need, just as I do.

General solution

My first attempt to find the solution was to use Google. I've came upon nice article by Matt Powell, Using ASP.NET Session State in a Web Service (look for the "Cookieless Sessions" section).

In short, Matt's solution is to wrap each invocation in a try-catch clause which tries to remap original WebService's Uri to the new one. This solves the issue, however is not practical since you'd end up with wrapping each single WebService method with a bloating try-catch clause.

The nice thing in Matt's approach is that it's general - if any subsequent WebService call causes 302 to be sent to the client, it is handled on the client side.

Specific solution - single redirect

The case of my application is not as general as to require the remapping to be handled for every request. I need to remap the url just once during the first call, however an additional cookie is appended to this first request. Instead of wrapping each single method, I've just wrote a simple proxy class which inherit from the SoapHttpClientProtocol class.

using System.Diagnostics;
using System.Web.Services;
using System.ComponentModel;
using System.Web.Services.Protocols;
using System;
using System.Xml.Serialization;
using System.Net;
using System.IO;
 
namespace Vulcan.Web.WebServices
{
    public class SoapHttpClientProtocol302 : 
        System.Web.Services.Protocols.SoapHttpClientProtocol
    {
        protected override System.Net.WebRequest GetWebRequest( Uri uri )
        {
            if ( !_302Fixed )
            {
                Fix302( new Uri( this.Url ) );
                _302Fixed = true;
 
                return base.GetWebRequest( new Uri( this.Url ) );
            }
 
            return base.GetWebRequest( uri );
        }
 
        private bool _302Fixed    = false;
        private void Fix302( Uri uri )
        {
            HttpWebRequest request    = (HttpWebRequest)HttpWebRequest.Create( uri );
            request.CookieContainer   = new CookieContainer();
            request.AllowAutoRedirect = false;
            HttpWebResponse response  = (HttpWebResponse)request.GetResponse();
 
            if ( response.StatusCode == HttpStatusCode.Redirect )
            {
                this.Url = new Uri( uri, response.Headers["Location"] ).ToString();
                this.CookieContainer.Add( response.Cookies );
            }
        }
    }
}

As you can see, the first request is handled in a special way: a new request is made to the application server and if the response code is 302 the Url of the proxy class is remapped to the new location returned by the appliaction server.


What I then need to do is to move my client proxy classes in the class hierarchy from SoapHttpClientProtocol to SoapHttpClientProtocol302.