是否(或将)存在将WebForm视图引擎标记(aspx)转换为Razor视图引擎标记(cshtml)的工具?
答案 0 :(得分:37)
我们也遇到了将大量WebForms视图转换为Razor的问题。猜猜看,我们还想出了一个工具:
https://github.com/telerik/razor-converter
它还依赖于正则表达式(以及其中的一些)来理解WebForms mumbo-jumbo,就像JohnnyO的工具一样。我们可能会涵盖更多案例,但不要相信我的话,而是在几个观点上尝试。
答案 1 :(得分:11)
这是我编写的一个简单的控制台应用程序,用于将WebForms视图转换为Razor。不幸的是,它不是防弹。我用它转换了大约20个视图,我发现对于简单的视图,它使它100%正确,但是当视图变得非常复杂时,转换只有大约80%的正确。即使是80%,它仍然比从头开始手动更好。
此转换工具中有一些内容可能与我设计WebForms视图的方式有关,因此我对其进行了评论,以便您可以将其删除或自定义。
您可以通过执行控制台应用程序并传入文件夹路径或文件路径来使用它。如果您传入一个文件夹,它将转换文件夹中的所有.aspx和.ascx文件(但它不会递归到子文件夹中)。如果传入文件路径,它将只转换该文件。完成转换后,它将创建一个与.aspx或.ascx文件同名的新.cshtml文件。
警告:如果您已经有一个与您尝试转换的.aspx / .ascx文件同名的.cshtml文件,则此应用程序将在完成.aspx / .ascx时覆盖.cshtml文件转换。
自行承担风险。在使用这个工具之前,请务必备份所有内容。
注意:如果您的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 :(得分:2)
ReSharper的用户可能会投票支持自动从aspx转换为cshtml的功能。 ReSharper将为双方提供AST表示,因此他们可以(理论上)在不需要正则表达式的情况下完成非常强大的工作。