Update (Oct 3, 2009): Thanks to the reader that pointed out that the MS Build task was using a hard-coded path. The example has been corrected to use a parameterized path that relies on the MSBuildProjectDirectory [3] reserved property.
An innocent “mistake” [1], if you can even call it that, has been punishing developers worldwide for a good part of the last decade – can you spot it?
1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>2:3: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">4:5: <html xmlns="http://www.w3.org/1999/xhtml" >6: <head runat="server">7: <title></title>8: </head>9: <body>10: <form id="form1" runat="server">11: <div>12: <asp:HiddenField runat="server" ID="RecordId" />13: </div>14: </form>15: </body>16: </html>1: using System;2:3: namespace WebApplication14: {5: public partial class _Default : System.Web.UI.Page6: {7: private int recordId = 0;8:9: protected void Page_Load(object sender, EventArgs e)10: {11: Response.Write(recordId);12: }13: }14: }
What’s worse is that the exception thrown doesn’t lend any clues as to what the source of the ambiguity is – even this level of detail requires some custom logging:
System.Reflection.AmbiguousMatchException: Ambiguous match found.
at System.RuntimeType.GetField(String name, BindingFlags bindingAttr)
at System.Web.UI.Util.GetNonPrivateFieldType(Type classType, String fieldName)
at System.Web.Compilation.BaseTemplateCodeDomTreeGenerator.BuildFieldDeclaration
ControlBuilder builder)
So what’s wrong with the code?
Two fields differ only in case. Yup – that’s it. RecordId is both a HiddenField and an integer. C# allows this – not a word is raised during compilation. Based on what I’ve seen and read (it’s widely covered), it only happens only when you pre-compile either a Website or Web application and navigate to the page in question.
Now imagine having a page with 20+ controls and trying to figure out where this is coming from – how’s the reflector supposed to help here?
How can we catch it at compile time?
It’s surprisingly easy, actually:
Until it gets fixed – and yes, it is a defect – here’s a custom task (MemberCaseTask) that you can add to your projects to catch it at build-time as opposed to already-in-staging-and-didn’t-think-to-navigate-to-each-view-after-precompilation-time.
Reference the custom Task in the project/solution script as follows:
1: <UsingTask TaskName="MemberCaseTask"2: AssemblyFile="C:\Users\admin\Documents\Visual Studio 2008\Projects\ClassLibrary1\ClassLibrary1\bin\Debug\ClassLibrary1.dll" />3: <Target Name="AfterBuild">4: <MemberCaseTask AssemblyPath="$(MSBuildProjectDirectory)\bin" />5: </Target>
Build this file and update the AssemblyFile reference above (note that we copy the original definition of GetNonPrivateFieldType from System.Web.UI.Util) :
1: using System;2: using System.Collections.Generic;3: using System.Linq;4: using System.Text;5: using System.Reflection;6: using System.IO;7: using Microsoft.Build.Framework;8: using Microsoft.Build.Utilities;9:10: namespace ClassLibrary111: {12: public class MemberCaseTask: Task13: {14: [Required]15: public string AssemblyPath { get; set; }16:17: public override bool Execute()18: {19: foreach (Assembly assembly in GetAllAssemblies())20: {21: try22: {23: foreach (Type type in assembly.GetTypes())24: foreach (FieldInfo field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))25: try26: {27: GetNonPrivateFieldType(type, field.Name);28: }29: catch (Exception)30: {31: Log.LogError(string.Format("{0} has a field conflict on field {1}.", type.Name, field.Name));32: return false;33: }34: }35: catch (System.Reflection.ReflectionTypeLoadException)36: { }37: }38:39: return true;40: }41:42: private IEnumerable<Assembly> GetAllAssemblies()43: {44: foreach (FileInfo file in new DirectoryInfo(AssemblyPath).GetFiles("*.dll", SearchOption.TopDirectoryOnly))45: {46: Assembly assembly = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(file.FullName));47: yield return assembly;48: }49: }50:51: static Type GetNonPrivateFieldType(Type classType, string fieldName)52: {53: FieldInfo field = classType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);54: if ((field != null) && !field.IsPrivate)55: {56: return field.FieldType;57: }58: return null;59: }60: }61: }
References:
[1] - System.Reflection.AmbiguousMatchException: Ambiguous match found
http://dotnetdebug.net/2006/03/21/ambiguous-match-found-in-a-web-control-a-possible-bug/
http://weblogs.asp.net/pjohnson/archive/2006/08/11/Ambiguous-match-found.aspx
http://www.velocityreviews.com/forums/t153094-error-ambiguous-match-found.html
[2] - How to Write a Task:
http://msdn.microsoft.com/en-us/library/t9883dzc.aspx
[3] - MSBuild Reserved Properties:
http://msdn.microsoft.com/en-us/library/ms164309.aspx
2 comments:
Hey NARIMAN HAGHIGHI,
Can u please Share Your Sample Project With Us.
Thanks
Thank you for the informarion! nice post!sites are creating whole new ways for users to share and gain information.
Post a Comment