2010-10-16 86 views

回答

36

我们也面临将大量WebForms视图转换为Razor的问题。你猜怎么着,我们也想出了一个工具:

https://github.com/telerik/razor-converter

它也依赖于正则表达式(和相当多的人)作出的WebForms天书的感觉,很像工具由JohnnyO。我们可能涵盖了一些情况,但不要听我的话,并尝试在一些意见。

+1

这对我很好。感谢分享! – 2011-01-21 19:24:05

+0

感谢这个有用的工具。我发现的一个问题是我的服务器注释在转换过程中被删除。将它们转换为'@ * ... * @'语法会很好。 – 2011-02-09 20:30:30

+0

确实:)我们将解决这个问题 – 2011-02-10 12:09:41

10

下面是我写的一个简单的控制台应用程序,用于将WebForms视图转换为Razor。不幸的是,这不是防弹的。我已经用这个转换了大约20个视图,而且我发现对于简单视图,它可以100%正确,但是当视图变得非常复杂时,转换只有大约80%正确。即使在80%的情况下,仍然比不得不从头开始手动更好。

这个转换工具中有一些东西可能特定于我设计WebForms视图的方式,所以我对它进行了评论,以便您可以将其删除或定制。

您可以通过执行控制台应用程序并传入文件夹路径或文件路径来使用它。如果您传入文件夹,它将转换文件夹中的所有.aspx和.ascx文件(但不会递归到子文件夹中)。如果你传入一个文件路径,它只会转换该文件。完成转换后,它将创建一个与.aspx或.ascx文件同名的新.cshtml文件。

警告:如果您已经有一个与要转换的.aspx/.ascx文件名称相同的.cshtml文件,则该应用程序将在完成.aspx /时覆盖.cshtml文件。 ascx转换。

使用您自己的风险。在使用本工具之前,请确保备份所有内容。

注意:如果您的aspx/ascx文件被标记为“只读”,您将收到错误信息,因为控制台应用程序无法打开它。确保您试图转换的所有aspx/ascx文件中的只读标志关闭。

class Program { 
    private readonly static string razorExtension = ".cshtml"; 

    /// <summary>Usage: RazorConverter.exe "C:\Files" or RazorConverter.exe "C:\Files\MyFile.aspx"</summary> 
    static void Main(string[] args) { 

     if (args.Length < 1) 
      throw new ArgumentException("File or folder path is missing."); 
     string path = args[0]; 

     List<string> convertedFiles = new List<string>(); 
     Regex webFormsExtension = new Regex(".aspx$|.ascx$"); 
     if (Directory.Exists(path)) { 
      foreach (var file in Directory.GetFiles(path, "*.aspx")) { 
       var outputFile = webFormsExtension.Replace(file, razorExtension); 
       ConvertToRazor(file, outputFile); 
       convertedFiles.Add(file); 
      } 

      foreach (var file in Directory.GetFiles(path, "*.ascx")) { 
       var outputFile = webFormsExtension.Replace(file, razorExtension); 
       ConvertToRazor(file, outputFile); 
       convertedFiles.Add(file); 
      } 
     } else if (File.Exists(path)) { 
      var match = webFormsExtension.Match(path); 
      if (match.Success) { 
       ConvertToRazor(path, webFormsExtension.Replace(path, razorExtension)); 
       convertedFiles.Add(path); 
      } else { 
       throw new ArgumentException(String.Format("{0} file isn't a WebForms view", path)); 
      } 
     } else { 
      throw new ArgumentException(String.Format("{0} doesn't exist", path)); 
     } 

     Console.WriteLine(String.Format("The following {0} files were converted:", convertedFiles.Count)); 
     foreach (var file in convertedFiles) { 
      Console.WriteLine(file); 
      } 
    } 

    private static void ConvertToRazor(string inputFile, string outputFile) { 

     // Known Bug: when writing anything directly to the response (other than for HTML helpers (e.g. Html.RenderPartial or Html.RenderAction)), 
     // this Converter will not correctly generate the markup. For example: 
     // <% Html.RenderPartial("LogOnUserControl"); %> will properly convert to @{ Html.RenderPartial("LogOnUserControl"); } 
     // but 
     // <% MyCustom("Foo"); %> will incorrectly convert to @MyCustom("Foo"); 

     string view; 
     using (FileStream fs = new FileStream(inputFile, FileMode.Open)) 
     using (StreamReader sr = new StreamReader(fs)) { 
      view = sr.ReadToEnd(); 
     } 

     // Convert Comments 
     Regex commentBegin = new Regex("<%--\\s*"); 
     Regex commentEnd = new Regex("\\s*--%>"); 
     view = commentBegin.Replace(view, "@*"); 
     view = commentEnd.Replace(view, "*@"); 

     // Convert Model 
     Regex model = new Regex("(?<=Inherits=\"System.Web.Mvc.ViewPage<|Inherits=\"System.Web.Mvc.ViewUserControl<)(.*?)(?=>\")"); 
     Regex pageDeclaration = new Regex("(<%@ Page|<%@ Control).*?%>"); 
     Match modelMatch = model.Match(view); 
     if (modelMatch.Success) { 
      view = pageDeclaration.Replace(view, "@model " + modelMatch.Value); 
     } else { 
      view = pageDeclaration.Replace(view, String.Empty); 
     } 

     // TitleContent 
     // I'm converting the "TitleContent" ContentPlaceHolder to View.Title because 
     // that's what TitleContent was for. You may want to ommit this. 
     Regex titleContent = new Regex("<asp:Content.*?ContentPlaceHolderID=\"TitleContent\"[\\w\\W]*?</asp:Content>"); 
     Regex title = new Regex("(?<=<%:\\s).*?(?=\\s*%>)"); 
     var titleContentMatch = titleContent.Match(view); 
     if (titleContentMatch.Success) { 
      var titleVariable = title.Match(titleContentMatch.Value).Value; 
      view = titleContent.Replace(view, "@{" + Environment.NewLine + " View.Title = " + titleVariable + ";" + Environment.NewLine + "}"); 
      // find all references to the titleVariable and replace it with View.Title 
      Regex titleReferences = new Regex("<%:\\s*" + titleVariable + "\\s*%>"); 
      view = titleReferences.Replace(view, "@View.Title"); 
     } 

     // MainContent 
     // I want the MainContent ContentPlaceholder to be rendered in @RenderBody(). 
     // If you want another section to be rendered in @RenderBody(), you'll want to modify this 
     Regex mainContent = new Regex("<asp:Content.*?ContentPlaceHolderID=\"MainContent\"[\\w\\W]*?</asp:Content>"); 
     Regex mainContentBegin = new Regex("<asp:Content.*?ContentPlaceHolderID=\"MainContent\".*?\">"); 
     Regex mainContentEnd = new Regex("</asp:Content>"); 
     var mainContentMatch = mainContent.Match(view); 
     if (mainContentMatch.Success) { 
      view = view.Replace(mainContentMatch.Value, mainContentBegin.Replace(mainContentEnd.Replace(mainContentMatch.Value, String.Empty), String.Empty)); 
     } 

     // Match <%= Foo %> (i.e. make sure we're not HTML encoding these) 
     Regex replaceWithMvcHtmlString = new Regex("<%=\\s.*?\\s*%>"); // removed * from the first <%=\\s.*?\\s*%> here because I couldn't figure out how to do the equivalent in the positive lookbehind in mvcHtmlStringVariable 
     Regex mvcHtmlStringVariable = new Regex("(?<=<%=\\s).*?(?=\\s*%>)"); 
     // Match <%, <%: 
     Regex replaceWithAt = new Regex("<%:*\\s*"); 
     // Match %>, <% (but only if there's a proceeding }) 
     Regex replaceWithEmpty = new Regex("\\s*%>|<%\\s*(?=})"); 

     var replaceWithMvcHtmlStrings = replaceWithMvcHtmlString.Matches(view); 
     foreach (Match mvcString in replaceWithMvcHtmlStrings) { 
      view = view.Replace(mvcString.Value, "@MvcHtmlString.Create(" + mvcHtmlStringVariable.Match(mvcString.Value).Value + ")"); 
      } 

     view = replaceWithEmpty.Replace(view, String.Empty); 
     view = replaceWithAt.Replace(view, "@"); 

     Regex contentPlaceholderBegin = new Regex("<asp:Content[\\w\\W]*?>"); 
     Regex contentPlaceholderId = new Regex("(?<=ContentPlaceHolderID=\").*?(?=\")"); 
     Regex contentPlaceholderEnd = new Regex("</asp:Content>"); 
     MatchCollection contentPlaceholders = contentPlaceholderBegin.Matches(view); 
     foreach (Match cp in contentPlaceholders) { 
      view = view.Replace(cp.Value, "@section " + contentPlaceholderId.Match(cp.Value).Value + " {"); 
     } 
     view = contentPlaceholderEnd.Replace(view, "}"); 

     // if we have something like @Html.RenderPartial("LogOnUserControl");, replace it with @{ Html.RenderPartial("LogOnUserControl"); } 
     Regex render = new Regex("@Html\\.\\S*\\(.*\\)\\S*?;"); 
     var renderMatches = render.Matches(view); 
     foreach (Match r in renderMatches) { 
      view = view.Replace(r.Value, "@{ " + r.Value.Substring(1) + " }"); 
     } 


     using (FileStream fs = new FileStream(outputFile, FileMode.Create)) { 
      byte[] bytes = Encoding.UTF8.GetBytes(view); 
      fs.Write(bytes, 0, bytes.Length); 
     } 
    } 
} 
2

ReSharper的用户可能喜欢投票选择自动从aspx转换为cshtml的功能。 ReSharper将为双方提供AST表示,因此他们可以(理论上)在不需要正则表达式的情况下做一个非常强大的工作。

http://youtrack.jetbrains.net/issue/RSRP-194060