Есть ли (или будет) инструмент для преобразования разметки обозревателя WebForm (aspx) в разметку engine view (cshtml) Razor?
Инструмент для преобразования разметки обозревателя WebForm в разметку Engine Razor
Ответ 1
Мы тоже столкнулись с проблемой преобразования большого количества представлений WebForms в Razor. И угадайте, что, мы также придумали инструмент:
https://github.com/telerik/razor-converter
Он также полагается на регулярные выражения (и немало из них), чтобы понять WebMorms mumbo-jumbo, как инструмент JohnnyO. Наши могут охватывать еще несколько случаев, но не задумывайтесь об этом и попробуйте на нескольких взглядах.
Ответ 2
Вот просто консольное приложение, которое я написал для преобразования представлений WebForms в Razor. К сожалению, это не пуленепробиваемый. Я конвертировал около 20 просмотров с этим, и я нахожу это для простых представлений, он получает это на 100% правильно, но когда представление становится очень сложным, преобразование только приблизительно на 80% правильное. Даже на 80% он все же лучше, чем начинать с самого начала вручную.
В этом инструменте преобразования есть некоторые вещи, которые могут быть специфическими для того, как я разработал свои представления в WebForms, поэтому я прокомментировал его, чтобы вы могли удалить его или настроить его.
Вы можете использовать его, выполнив консольное приложение и пройдя путь к папке или путь к файлу. Если вы перейдете в папку, он преобразует все файлы .aspx и .ascx в папку (но не будет записывать в подпапки). Если вы пройдете путь к файлу, он преобразует только этот файл. Как только это будет сделано с преобразованием, он создаст новый .cshtml файл с тем же именем, что и файл .aspx или .ascx.
ПРЕДУПРЕЖДЕНИЕ. Если у вас уже есть файл .cshtml с тем же именем, что и файл .aspx/.ascx, который вы пытаетесь преобразовать, это приложение перезапишет файл .cshtml, когда он завершит .aspx/.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 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 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);
}
}
}
Ответ 3
Пользователи ReSharper могут проголосовать за возможность автоматического преобразования из aspx в cshtml. ReSharper будет иметь представление AST для обеих сторон, поэтому они могут (теоретически) выполнять очень прочную работу, не требуя регулярных выражений.