This project is read-only.

RazorGenerator.MsBuild instead of RazorGenerator.MVC for unit testing?

Jun 18, 2014 at 7:03 AM
Edited Jun 18, 2014 at 7:05 AM
First, congratulations for the projects, I am keen on it, it is really a shame that MS does not include this directly in MVC framework or VS.

One question: Would it be possible to further modify MsBuild package so it would be possible to unit test views with strong references (just like with RazorGenerator.MVC)?
I am asking because I find the current implementation of RazorGenerator.MVC not robust enough for our needs - Visual Studio is needed and a special add-in, furthermore, when a new view is added, the script in Nuget console must be called manually (or to create another VS plugin). I do not like so many steps - if one works alone, ok, but with many developers ...

RazorGenerator.MsBuild is much better in this aspect, just that the generated files are not added to the Visual Studio project - is it not possible to create some script, e.g. in Powershell, that would add created .cs files into a project, and invoke it on build event (in xml project config)?
Coordinator
Jun 18, 2014 at 5:29 PM
You should be able to do this without editing the MsBuild package. Here's one way to go about it:
  • Set a property named RazorViewsCodeGenDirectory in your csproj file to point to a directory where you'd like your cs files to be added
  • Add an AfterBuild task that edits your csproj file and adds Items for cs files that don't already exist.
MyProj.csproj

<RazorViewsCodeGenDirectory>$(MsBuildThisFileDirectory)CodeGened</RazorViewsCodeGenDirectory>

<UsingTask TaskName="UpdateCsproj" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
  <ParameterGroup>
      <CsProjFilePath ParameterType="System.String" Required="true" />
  </ParameterGroup>
  <Task>
      <Reference Include="System.Core" />
      <Using Namespace="System" />
      <Using Namespace="System.IO" />
      <Using Namespace="System.Xml" />
      <Using Namespace="Microsoft.Build.Framework" />
      <Using Namespace="Microsoft.Build.Utilities" />
      <Code Type="Fragment" Language="cs">
          <![CDATA[
          try {
            var codeGenDir = Path.Combine(CsProjFilePath, "CodeGenedViews");
            var doc = XDocument.Load(Path.Combine(CsProjFilePath, "MyCsProj.csproj"));
            var existing = doc.Descendants("Compile").SelectMany(c => c.Attribute("Include").Value);
            var files = Directory.EnumerateFiles(codeGenDir, "*.cs").Except(existing);
            foreach (var newFile in files)
            {
               doc.Elements("ItemGroup").Last().Add(new XElement("Compile", new XAttribute("Include", newFile)));
            }
           if (files.Any()) { doc.Save(...): }
            return true;
          }
          catch (Exception ex) {
              Log.LogErrorFromException(ex);
              return false;
          }
      ]]>
      </Code>
  </Task>
</UsingTask>
<AfterBuild>
   <UpdateCsProj CsProjFilePath="$(MsBuildThisFileDirectory)" />
</AfterBuild>
The code definitely doesn't work as is, but you should get the gist of what it'strying to do.
Jun 20, 2014 at 7:09 AM
Thank you very much for this great solution, I didn't even know that it was possible to include C# code in csproj file. I can now abandon my powershell scripts :) Unfortunately I don't have time in the moment to finish this code, but as soon as it is done I will post it here.
Jul 9, 2014 at 10:36 AM
Edited Jul 10, 2014 at 6:57 AM
My task:
  <PropertyGroup>
      <RazorViewsCodeGenDirectory>.\</RazorViewsCodeGenDirectory>
  </PropertyGroup>
...
  <UsingTask TaskName="UpdateCsproj" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <CsProjFilePath ParameterType="System.String" Required="true" />
      <CsProjFileName ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Core" />
      <Reference Include="System.Xml" />
      <Reference Include="System.Xml.Linq" />
      <Reference Include="System.Windows.Forms" />
      <Using Namespace="System" />
      <Using Namespace="System.IO" />
      <Using Namespace="Microsoft.Build.Framework" />
      <Using Namespace="Microsoft.Build.Utilities" />
      <Using Namespace="System.Windows.Forms" />
      <Using Namespace="System.Xml" />
      <Using Namespace="System.Text.RegularExpressions" />
      <Using Namespace="System.Linq" />
      <Code Type="Fragment" Language="cs"><![CDATA[
            try
            {
                //System.Diagnostics.Debugger.Launch();
                var appFolder = CsProjFilePath;
                var path = Path.GetFullPath(Path.Combine(appFolder, @"Views"));
                var files = Directory.EnumerateFiles(path, "*.cshtml.cs", SearchOption.AllDirectories);
                var projXmlPath = CsProjFileName;
                var projXml = new XmlDocument();
                projXml.Load(projXmlPath);

                var namespaceUri = projXml.DocumentElement != null ? projXml.DocumentElement.NamespaceURI : "";
                var projectNode = projXml.ChildNodes.Cast<XmlNode>().First(n => n.Name == "Project");
                var itemGroup = projectNode.LastChild.Name == "ItemGroup" ? projectNode.LastChild : projXml.CreateElement("ItemGroup", namespaceUri);
                if(projectNode.LastChild.Name != "ItemGroup")
                    projectNode.AppendChild(itemGroup);

                foreach (var file in files)
                {
                    var fileName = Path.GetFileName(file) ?? "";
                    var compileNode = projXml.GetElementsByTagName("Compile").Cast<XmlNode>().FirstOrDefault(n => n.Attributes != null && n.Attributes["Include"].Value.Contains(fileName));
                    if (compileNode == null)
                    {
                        compileNode = projXml.CreateElement("Compile", namespaceUri);
                        var includeAttr = projXml.CreateAttribute("Include");
                        includeAttr.Value = file.Replace(appFolder + "\\", "");
                        compileNode.Attributes.Append(includeAttr);
                        var compileAttr = projXml.CreateAttribute("Condition");
                        compileAttr.Value = "False";
                        compileNode.Attributes.Append(compileAttr);
                        itemGroup.AppendChild(compileNode); 
                    }
                    var existingDependUponNode = compileNode.ChildNodes.Cast<XmlNode>().FirstOrDefault(n => n.Name == "DependentUpon");
                    var node = existingDependUponNode ?? projXml.CreateElement("DependentUpon", namespaceUri);
                    node.InnerText = Regex.Replace(fileName, ".cs$", "");
                    if (existingDependUponNode == null)
                        compileNode.AppendChild(node);
                }
                projXml.Save(projXmlPath);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message + " " + e.StackTrace);
            }
          ]]></Code>
    </Task>
  </UsingTask>
  <Target Name="AfterRebuild">
    <UpdateCsProj CsProjFilePath="$(MsBuildThisFileDirectory)" CsProjFileName="$(ProjectPath)" />
  </Target>
It updates csProj file, the end result looks just like with RazorGenerator.Mvc - note: condition is set to false for each added item, otherwise one would get compiler warnings - true solution would be to disable RazorGenerator.MSBuild output in the RazorGenerator.MsBuild.targets file, but I do not want to mess with that updating this file every time a new version comes.

There is, however, one problem: one must click ReloadAll in Visual Studio dialog every time a project gets built. The most elegant solution I have found so far is simply to run the compiled views adding task only after the rebuild.