November 19th, 2012

All about

Background – on quirks and compatibility

The .NET Framework (including ASP.NET) strives to maintain near-100% compatibility when an existing framework is updated on a machine. We try to ensure as much as possible that if an application was developed and deployed against .NET Framework 4, it will just continue to work on 4.5. This normally means keeping quirky, buggy, or undesirable behaviors in the product between versions, as fixing them may negatively affect applications which were relying on those behaviors.

Consider creating a new Console Application project targeting .NET Framework 4 and putting the following in Program.cs:

Program
{
   static void Main(string[] args)
    {
        List < int > list = new List < int >() { 1, 2, 3 };
        list.ForEach(i =>
        {
            Console.WriteLine(i);
            if (i < 3) { list.Add(i + 1); }
        });
    }
}

When run, the output of this application is 1, 2, 3, 2, 3, 3. Now change the project to target .NET Framework 4.5, recompile, and run again. The output is 1, followed by an exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute. What happened?

The contract of IEnumerator.MoveNext() specifies that the method shall throw an InvalidOperationException if the underlying collection changes while an enumeration is currently taking place. Indeed, if you use the foreach keyword instead of the List<T>.ForEach() method in the above sample, you will see the exception thrown regardless of target framework. This discrepancy between the foreach keyword and the ForEach() method is one example of a quirk. The feature team wanted to bring the ForEach() method behavior in line with the foreach keyword behavior, but doing so for all applications would have been a breaking change. So they require the application to opt in to the new 4.5 behavior by switching the target framework.

Given that the .NET Framework 4.5 is an in-place update to .NET Framework 4, how is this pulled off? If you look closely at the console application project, you’ll see that it also contains one additional file App.config:

<configuration >
    < startup > 
        < supportedRuntime version = "v4.0" sku=".NETFramework,Version=v4.0"/>
    </startup>
</configuration>

When the application is targeted to 4.5, Visual Studio modifies the configuration file. There are two related concepts in the <supportedRuntime> element: runtime version and target SKU. The .NET Framework 4.5 SKU rides on top of version 4.0 of the CLR, much like how the .NET Framework 3.0 and 3.5 SKUs ride on top of version 2.0 of the CLR.

<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
    </startup>
</configuration>

This information is captured into an attribute and compiled into the executable:

[assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")]

When a component in the .NET Framework needs to decide whether to apply quirks behavior to a particular code path or whether it should use new 4.5-standards logic, it consults the [TargetFramework] attribute to see what framework version the application targets, hence what logic it expects to take place. (If the attribute is not present, the runtime assumes 4.0 quirks behavior. There is additional fallback and resolution logic, but it’s not important to the topic at hand.)

<httpRuntime targetFramework>

But what if ASP.NET applications want to opt in to the new 4.5 behaviors? ASP.NET developers can’t use the <supportedRuntime> element since there’s no .exe the [TargetFramework] attribute can be compiled into. However, we do control the <httpRuntime> element, and we can use its values to make decisions on how we should configure the CLR before loading your application into memory.

To this effect, we introduced the targetFramework attribute on the <httpRuntime> element. When you create a new ASP.NET application using the 4.5 project templates, the following line will be present in Web.config:

<httpRuntime targetFramework="4.5" />

The effect of this attribute is twofold. First, it controls the CLR’s quirks mode behavior, just like the <supportedRuntime> element does in a console application. Consider a handler ~/MyHandler.ashx whose contents are similar to the console application above.

<%@ WebHandler Language="C#" Class="MyHandler" %>

using System.Collections.Generic;
using System.Web;
public class MyHandler : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        List<int> list = new List<int>() { 1, 2, 3 };
        list.ForEach(i =>
        {
            context.Response.Write(i + " ");
            if (i < 3) { list.Add(i + 1); }
        });
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

If the targetFramework attribute reads “4.0”, the output will be 1 2 3 2 3 3. If the targetFramework attribute reads “4.5”, an InvalidOperationException will be thrown due to the collection having been modified during the ForEach() operation.

Second, <httpRuntime targetFramework=”4.5″ /> is a shortcut that allows the ASP.NET runtime to infer a wide array of configuration settings. If the runtime sees this setting, it will expand it out just as if you had written the following:

<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="WebForms" />
  </appSettings>
    <system.web>
      <compilation targetFramework="4.5" />
      <machineKey compatibilityMode="Framework45" />
      <pages controlRenderingCompatibilityVersion="4.5" />
    </system.web>
</configuration>

By inferring all of these settings from a single line, we help shrink the size of Web.config. Thus the file contains less runtime configuration and more application-specific configuration, such as connection strings.

I’ll go over each of these in turn, explaining what they do.

<add key=”aspnet:UseTaskFriendlySynchronizationContext” value=”true” />

Enables the new await-friendly asynchronous pipeline that was introduced in 4.5. Many of our synchronization primitives in earlier versions of ASP.NET had bad behaviors, such as taking locks on public objects or violating API contracts. In fact, ASP.NET 4’s implementation of SynchronizationContext.Post is a blocking synchronous call! The new asynchronous pipeline strives to be more efficient while also following the expected contracts for its APIs. The new pipeline also performs a small amount of error checking on behalf of the developer, such as detecting unanticipated calls to async void methods.

Certain features like WebSockets require that this switch be set. Importantly, the behavior of async / await is undefined in ASP.NET unless this switch has been set. (Remember: setting <httpRuntime targetFramework=”4.5″ /> is also sufficient.)

<add key=”ValidationSettings:UnobtrusiveValidationMode” value=”WebForms” />

Causes server controls to render data-val attributes in markup rather than send a snippet of JavaScript for each input element which requires validation. This provides a better extensibility story for customizing how client-side validation is performed. Depending on the complexity of the page, it can also significantly reduce the size of the generated markup.

<compilation targetFramework=”4.5″ />

Selects which version of the .NET Framework’s reference assemblies are used when performing compilation. (Note: Visual Studio requires that this element be present in Web.config, even though we auto-infer it.)

<machineKey compatibilityMode=”Framework45″ />

Causes ASP.NET to use an enhanced pipeline when performing cryptographic operations. More information on the new pipeline can be found here (links to the first part of a three-part blog series).

<pages controlRenderingCompatibilityVersion=”4.5″ />

Similar to quirks mode, but instead of affecting core runtime behavior it affects how controls render markup. For example, consider the control <img runat="server" src="..." alt="" /> (note the empty alt tag). In earlier versions of ASP.NET, this stripped the empty alt tag and output <img src="..." />, which fails HTML validation. When the rendering compatibility version is set to “4.5”, the empty alt tag is preserved, and the output is <img src="..." alt="" />.

<pages controlRenderingCompatibilityVersion> defines the default value of the Control.RenderingCompatibility property. The value can still be overridden on a per-control basis if desired.

Miscellaneous questions

Can I detect the target framework programmatically?

Yes! Take a look at the HttpRuntime.TargetFramework static property. Keep in mind that this property should only be queried from within an ASP.NET application. Any other access (such as via a console application) could result in an undefined return value.

But what if I don’t want the runtime to infer all of those configuration settings?

No problem! You can always explicitly change any setting. For example, you could put this in Web.config:

<httpRuntime targetFramework="4.5" />
<pages controlRenderingCompatibilityVersion="4.0" />

Even though <httpRuntime targetFramework=”4.5″ /> would normally imply <pages controlRenderingCompatibilityVersion=”4.5″ />, the runtime will notice that you have already explicitly set controlRenderingCompatibilityVersion and will respect your setting.

My hoster is displaying “Unrecognized attribute ‘targetFramework'” errors!

This error means that you have created an ASP.NET 4.5 application but are trying to deploy it to a server that only has the .NET Framework 4 (not 4.5) installed. Many hosters offer ASP.NET 4.5 as an option. They may require you to contact them first so that they can enable 4.5 for your account.

How does this affect existing ASP.NET 4 applications?

There was no <httpRuntime targetFramework> attribute in .NET 4, hence existing ASP.NET 4 applications won’t have this setting in Web.config. If the targetFramework attribute is not present, we assume a default value of “4.0” and run the application in quirks mode. Web developers can manually set <httpRuntime targetFramework=”4.5″ /> to opt-in to the new behaviors.

If there is no <httpRuntime targetFramework> attribute present in Web.config, we assume that the application wanted 4.0 quirks behavior.

Wrapping Up

I hope I have conveyed the intent and the utility of the <httpRuntime targetFramework> setting. In the future, we envision that this can be a useful mechanism for moving applications forward, as application developers can simply set targetFramework to “5.0” or “6.0” to opt-in wholesale to whatever new behaviors those framework versions may bring.

As always, please feel free to leave feedback in the comments!

Category
ASP.NET

Author

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Dennis Sheridan

    Is the following configuration permissible?<compilation targetFramework="4.5.2" /><httpRuntime targetFramework="4.6.1" />
    We have a web application that was compiled in 4.5.2 and which we do not have easy access to recompile to a later version. We are experiencing TLS issues with some remote calls which we were able to solve by simply altering our web.config to <httpRuntime targetFramework="4.6.1" />. Are there any caveats to setting the httpRuntime target to a version that's later than the compilation target?

    Read more
  • Joshua Van der Merwe

    Thanks for the great read!
    Does this affect how the ServicePointManager in WCF detects and uses different TLS versions?
    In .NET versions prior to 4.7, the TLS version is hardcoded to 1.0.
    In .NET 4.7 and later, the ServicePointManager defaults to the OS choosing the best protocol available (including TLS 1.1 and 1.2)
    If I have a WCF project which is compiled with .NET 4.7 or later, but the TargetFramework is 4.6 or earlier, which...

    Read more