/// <summary> /// Generate a replacement .designer.cs file for the given markup file, overwriting the existing /// .designer.cs file if there is one. /// </summary> public static bool GenerateDesignerForFilename(ICompileContext compileContext, string filename, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, string assemblyDirectory, string rootPath) { string designer; string designerFilename = filename + ".designer.cs"; // Load the markup from the .aspx or .ascx file. MarkupReader markup = new MarkupReader(); MarkupInfo markupInfo; try { markupInfo = markup.LoadMarkup(compileContext, filename, tagRegistrations, assemblies, assemblyDirectory, rootPath); } catch (Exception e) { compileContext.Error("{0}: Failed to load markup file:\r\n{1}", filename, e.Message); compileContext.Verbose("Stopping file processing due to exception. Stack trace:\r\n{0}", e.StackTrace); return(false); } // If we're not inheriting a real class, there's no reason for a designer file to exist. if (markupInfo.ClassType == null) { compileContext.Verbose("Skipping generating designer file because markup does not have an Inherits=\"...\" attribute.", filename); return(true); } // Generate the output text for the new .designer.cs file. try { DesignerWriter designerWriter = new DesignerWriter(); designer = designerWriter.CreateDesigner(compileContext, markupInfo); } catch (Exception e) { compileContext.Error("{0}: Cannot regenerate designer file:\r\n{1}", filename, e.Message); compileContext.Verbose("Stopping file processing due to exception. Stack trace:\r\n{0}", e.StackTrace); return(false); } // Save the output .designer.cs file to disk. try { File.WriteAllText(designerFilename, designer, Encoding.UTF8); } catch (Exception e) { compileContext.Error("{0}: Cannot open designer file for writing:\r\n{1}", designerFilename, e.Message); compileContext.Verbose("Stopping file processing due to exception. Stack trace:\r\n{0}", e.StackTrace); return(false); } return(true); }
/// <summary> /// Verify the current .designer.cs file for the given markup file. /// </summary> /// <returns>True if the file passes inspection, false if it fails.</returns> public static bool VerifyDesignerForFilename(ICompileContext compileContext, string filename, IEnumerable <TagRegistration> tagRegistrations, AssemblyLoader assemblies, string assemblyDirectory, string rootPath) { DesignerInfo designerInfo; string designerFilename = filename + ".designer.cs"; // Load the markup from the .aspx or .ascx file. MarkupReader markup = new MarkupReader(); MarkupInfo markupInfo; try { markupInfo = markup.LoadMarkup(compileContext, filename, tagRegistrations, assemblies, assemblyDirectory, rootPath); } catch (Exception e) { compileContext.Error("{0}: Failed to load markup file:\r\n{1}", filename, e.Message); compileContext.Verbose("Stopping file processing due to exception. Stack trace:\r\n{0}", e.StackTrace); return(false); } if (markupInfo.ClassType == null) { compileContext.Verbose("Skipping verification of .designer file, because markup has no Inherits=\"...\" attribute and therefore has no .designer file.", filename); return(true); } compileContext.Verbose(string.Empty); // Read and parse the current .designer.cs file. try { DesignerReader designerReader = new DesignerReader(); designerInfo = designerReader.LoadDesignerFile(compileContext, designerFilename); } catch (Exception e) { compileContext.Error("{0}: Cannot load designer file:\r\n{1}", filename, e.Message); compileContext.Verbose("Stopping file processing due to exception. Stack trace:\r\n{0}", e.StackTrace); return(false); } compileContext.Verbose(string.Empty); // And finally compare the expectations of the markup against the reality of the .designer.cs file. return(CompareMarkupInfoToDesignerInfo(compileContext, filename, markupInfo, designerInfo)); }
/// <summary> /// For the given set of .aspx or .ascx files, generate all of their designer files. /// </summary> /// <param name="compileContext">The context in which errors are to be reported.</param> /// <param name="filenames">The filenames to generate.</param> /// <param name="rootPath">The root disk path of the website (usually the same as the path to "web.config").</param> /// <param name="websiteDllFileName">The disk path to the website's DLL.</param> public static bool GenerateDesignerFiles(ICompileContext compileContext, IEnumerable <string> filenames, string rootPath, string websiteDllFileName) { int filenameCount = filenames.Count(); compileContext.BeginTask(filenameCount); // Load and parse the "web.config". WebConfigReader webConfigReader = new WebConfigReader(); try { webConfigReader.LoadWebConfig(compileContext, Path.Combine(rootPath, WebConfigFilename), rootPath); } catch (Exception e) { compileContext.Error("Cannot load {0}:\r\n{1}", Path.Combine(rootPath, WebConfigFilename), e.Message); return(false); } // Load any assemblies we know we'll need. This includes the default assemblies, any declared // in the web.config, and, of course, the website's DLL itself. AssemblyLoader assemblyLoader = new AssemblyLoader(); List <string> assemblyNames = new List <string>(); assemblyNames.AddRange(_standardTagRegistrations.Where(r => !string.IsNullOrEmpty(r.AssemblyFilename)).Select(r => r.AssemblyFilename).Distinct()); assemblyNames.AddRange(webConfigReader.TagRegistrations.Where(r => !string.IsNullOrEmpty(r.AssemblyFilename)).Select(r => r.AssemblyFilename).Distinct()); string dllFullPath = Path.GetFullPath(websiteDllFileName); assemblyNames.Add(dllFullPath); string assemblyDirectory = Path.GetDirectoryName(dllFullPath); assemblyLoader.PreloadAssemblies(compileContext, assemblyNames, assemblyDirectory); assemblyLoader.PrimaryAssembly = assemblyLoader[dllFullPath]; // Add the default tag registrations, including those from System.Web and any declared in the "web.config". List <TagRegistration> tagRegistrations = new List <TagRegistration>(); tagRegistrations.AddRange(_standardTagRegistrations); tagRegistrations.AddRange(webConfigReader.TagRegistrations); // Spin through any user controls that were declared in the web.config and connect them to their actual // .NET class types via reflection. compileContext.Verbose("Resolving user controls declared in the web.config."); compileContext.VerboseNesting++; ResolveUserControls(compileContext, tagRegistrations, assemblyLoader, assemblyDirectory, rootPath, rootPath); compileContext.VerboseNesting--; compileContext.Verbose(string.Empty); // Now that all the setup is done, load and parse each individual markup file into its own .designer.cs output file. bool result = true; foreach (string filename in filenames) { compileContext.Verbose("Begin processing \"{0}\"...", filename); compileContext.Verbose(""); compileContext.VerboseNesting++; compileContext.BeginFile(filename); bool succeeded = GenerateDesignerForFilename(compileContext, filename, tagRegistrations, assemblyLoader, assemblyDirectory, rootPath); result &= succeeded; compileContext.EndFile(filename, succeeded); compileContext.VerboseNesting--; compileContext.Verbose(""); compileContext.Verbose("End processing \"{0}\".", filename); } return(result); }
/// <summary> /// Compare a parsed markup file against a parsed designer file to determine if they match each other. /// </summary> /// <param name="compileContext">The context in which errors should be reported.</param> /// <param name="filename">The filename to use for reporting errors.</param> /// <param name="markupInfo">The markup file to compare.</param> /// <param name="designerInfo">The designer file to compare.</param> /// <returns>True if they match, false if they do not.</returns> private static bool CompareMarkupInfoToDesignerInfo(ICompileContext compileContext, string filename, MarkupInfo markupInfo, DesignerInfo designerInfo) { compileContext.Verbose("Comparing markup controls to .designer file properties..."); compileContext.Verbose("Comparing classnames."); // First, make sure the type names match; we *should* be talking about the same classes here. if (markupInfo.ClassType == null) { compileContext.Error("{0}: Designer file exists, but markup file has no Inherits=\"...\" attribute.", filename); return(false); } if (markupInfo.ClassType.FullName != designerInfo.FullTypeName) { compileContext.Error("{0}: Designer file and markup file specify different type names (\"{1}\" in the markup, and \"{2}\" in the designer file.", filename, markupInfo.ClassType.FullName, designerInfo.FullTypeName); return(false); } // Build lookup tables for the property declarations in the designer file and in the markup file. // We'll use these to make searching for property matches that much faster, and to detect duplicates, // and to ensure that we're talking about the same set of properties in both files. compileContext.Verbose("Checking for duplicate control declarations."); Dictionary <string, OutputControl> markupPropertiesByName = new Dictionary <string, OutputControl>(); Dictionary <string, DesignerPropertyDeclaration> designerProperiesByName = new Dictionary <string, DesignerPropertyDeclaration>(); List <string> duplicateMarkupProperties = new List <string>(); foreach (OutputControl outputControl in markupInfo.OutputControls) { if (string.IsNullOrEmpty(outputControl.Name)) { continue; } if (markupPropertiesByName.ContainsKey(outputControl.Name)) { duplicateMarkupProperties.Add(outputControl.Name); } else { markupPropertiesByName.Add(outputControl.Name, outputControl); } } List <string> duplicateDesignerProperties = new List <string>(); foreach (DesignerPropertyDeclaration propertyDeclaration in designerInfo.PropertyDeclarations) { if (designerProperiesByName.ContainsKey(propertyDeclaration.Name)) { duplicateDesignerProperties.Add(propertyDeclaration.Name); } else { designerProperiesByName.Add(propertyDeclaration.Name, propertyDeclaration); } } // Check the lookup tables for duplicates. There shouldn't be any. if (duplicateMarkupProperties.Count > 0) { compileContext.Error("{0}: Malformed markup error: Found multiple controls in the markup that have the same ID. Stopping verification now due to invalid markup file. Duplicate IDs: {1}", filename, Join(duplicateMarkupProperties, ", ")); } if (duplicateDesignerProperties.Count > 0) { compileContext.Error("{0}: Malformed designer error: Found multiple property declarations in the .designer file that have the same name. Stopping verification now due to invalid designer file. Duplicate names: {1}", filename, Join(duplicateDesignerProperties, ", ")); } if (duplicateMarkupProperties.Count > 0 || duplicateDesignerProperties.Count > 0) { return(false); } // Okay, now check to see if the markup or designer declare property names that the other doesn't have. compileContext.Verbose("Checking for missing control declarations."); Type contentControl = typeof(System.Web.UI.WebControls.Content); List <string> missingDesignerProperties = markupInfo.OutputControls .Where(p => !string.IsNullOrEmpty(p.Name) && p.ReflectedControl.ControlType != contentControl && !designerProperiesByName.ContainsKey(p.Name)) .Select(p => p.Name) .ToList(); List <string> missingMarkupProperties = designerInfo.PropertyDeclarations .Where(p => !string.IsNullOrEmpty(p.Name) && !markupPropertiesByName.ContainsKey(p.Name)) .Select(p => p.Name) .ToList(); if (missingDesignerProperties.Count > 0) { compileContext.Error("{0}: Missing property error: Found controls declared in the markup that do not exist in the .designer file. Missing IDs: {1}", filename, Join(missingDesignerProperties, ", ")); } if (missingMarkupProperties.Count > 0) { compileContext.Error("{0}: Missing control error: Found property declarations in the .designer file that have no control declaration in the markup. Missing controls: {1}", filename, Join(missingMarkupProperties, ", ")); } // We've now established that both files refer to the same set of names. We now need to check // to make sure they all refer to the same control types. int numTypeMismatches = 0; compileContext.Verbose("Checking for type mismatches."); foreach (OutputControl outputControl in markupInfo.OutputControls) { if (string.IsNullOrEmpty(outputControl.Name) || outputControl.ReflectedControl.ControlType == contentControl || !designerProperiesByName.ContainsKey(outputControl.Name)) { continue; } DesignerPropertyDeclaration designerPropertyDeclaration = designerProperiesByName[outputControl.Name]; if (designerPropertyDeclaration.PropertyTypeName != outputControl.ReflectedControl.ControlType.FullName) { compileContext.Error("{0}: Type mismatch: Control \"{1}\" has type {2} in the markup but type {3} in the .designer file.", filename, outputControl.Name, outputControl.ReflectedControl.ControlType.FullName, designerPropertyDeclaration.PropertyTypeName); numTypeMismatches++; } } if (missingDesignerProperties.Count > 0 || missingMarkupProperties.Count > 0 || numTypeMismatches > 0) { return(false); } // One last very touchy check: All the properties exist in both files, and they have the same names and // same types --- but are they in the right order? Visual Studio is very picky about the order, and if // they don't match, the Visual Studio designer will break. compileContext.Verbose("Checking for mis-ordered declarations."); for (int m = 0, d = 0; m < markupInfo.OutputControls.Count;) { OutputControl outputControl = markupInfo.OutputControls[m++]; if (string.IsNullOrEmpty(outputControl.Name) || outputControl.ReflectedControl.ControlType == contentControl) { continue; } DesignerPropertyDeclaration designerPropertyDeclaration = designerInfo.PropertyDeclarations[d++]; if (designerPropertyDeclaration.Name != outputControl.Name || designerPropertyDeclaration.PropertyTypeName != outputControl.ReflectedControl.ControlType.FullName) { compileContext.Error("{0}: Ordering error: All of the same controls exist in both the markup and the .designer file, but they do not appear in the same order.", filename); return(false); } } compileContext.Verbose("{0}: Success!", filename); return(true); }