/// <summary> /// Migrates logic class files over to the blazor server project. /// </summary> /// <param name="webFormProjectData">List of pre cached models for from the web form project.</param> /// <param name="webFormProject">The web forms project that we are migrating data from.</param> /// <param name="blazorServerProject">The blazor server project this is being migrated to.</param> public async Task MigrateLogic(IReadOnlyList <VsModel> webFormProjectData, VsProject webFormProject, VsProject blazorServerProject) { try { //Informing the dialog the migration step has started. await _statusTracking.UpdateStepStatusAsync(MigrationStepEnum.AppLogic, MigrationStatusEnum.Running); var childFiles = webFormProjectData.GetSourceCodeDocumentsAsync(true); //we don't want any known aspx/ascx files hitching a ride. just plain vanilla *.cs files should qualify. var logicFiles = childFiles.Where(p => (!p.Name.ToLower().Contains("aspx.") && !p.Name.ToLower().Contains("ascx.") && !p.Name.ToLower().Contains("asax."))).ToList(); //put logic files (using the file system into the target under the project root) foreach (VsCSharpSource sourceDocument in logicFiles) { //look for specific files that are native to a WebForm app and skip them. ** TODO: move this to a config setting maybe? if (sourceDocument.Name.ToLower().Contains("bundleconfig")) { continue; } if (sourceDocument.Name.ToLower().Contains("assemblyinfo")) { continue; } if (sourceDocument.Name.ToLower().Contains("startup")) { continue; } if (sourceDocument.Name.ToLower().Contains(".master")) { continue; } var logicDocument = await sourceDocument.LoadDocumentModelAsync(); var parentFolders = await logicDocument.GetParentFolders(); var source = sourceDocument.SourceCode; var docText = await logicDocument.GetDocumentContentAsStringAsync(); if (parentFolders.Count >= 1) { parentFolders.Reverse(); //The folders are returned in leaf-to-trunk so need to reverse the order for the next step. VsProjectFolder createdFolder = null; //deal with source folder hierarchy for (int i = 0; i < parentFolders.Count; i++) { if (i > 0) { createdFolder = await createdFolder.CheckAddFolder(parentFolders[i].Name); } else { createdFolder = await blazorServerProject.CheckAddFolder(parentFolders[i].Name); } } //copy the file. We only really care about the most leaf/edge subfolder so its safe to use the creatdFolder variable here. docText = docText.Replace(source.Classes.First().Namespace, $"{blazorServerProject.Name}.{createdFolder.Name}"); var targetFolderFiles = await createdFolder.GetChildrenAsync(false); if (!targetFolderFiles.Any(c => c.Name.ToLower().Equals(logicDocument.Name.ToLower()))) { await createdFolder.AddDocumentAsync(logicDocument.Name, docText); //Updating the dialog with a status await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AppLogic, MessageTypeEnum.Information, $"Copied logic file: {logicDocument.Name} to project {blazorServerProject.Name} location: {Path.Combine(createdFolder.Path, logicDocument.Name)}"); } else { await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AppLogic, MessageTypeEnum.Warning, $"Logic file: {logicDocument.Name} already exists in target folder location: {Path.Combine(createdFolder.Path, logicDocument.Name)} and was skipped."); } } else { var projFiles = await blazorServerProject.GetChildrenAsync(false); if (!projFiles.Any(c => c.Name.ToLower().Equals(logicDocument.Name.ToLower()))) { docText = docText.Replace(source.Classes.First().Namespace, $"{blazorServerProject.Name}"); var thing = await blazorServerProject.AddDocumentAsync(logicDocument.Name, docText); //Updating the dialog with a status await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AppLogic, MessageTypeEnum.Information, $"Copied static file: {logicDocument.Name} to project {blazorServerProject.Name} location: {Path.Combine(blazorServerProject.Path, logicDocument.Name)}"); } else { //Updating the dialog with a status await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AppLogic, MessageTypeEnum.Warning, $"Static file: {logicDocument.Name} already exists in project {blazorServerProject.Name} location: {Path.Combine(blazorServerProject.Path, logicDocument.Name)} and was skipped."); } } } //Completed the migration step informing the dialog. await _statusTracking.UpdateStepStatusAsync(MigrationStepEnum.AppLogic, MigrationStatusEnum.Passed); } catch (Exception unhandledError) { //Dumping the exception that occured directly into the status so the user can see what happened. await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AppLogic, MessageTypeEnum.Error, $"The following unhandled error occured. '{unhandledError.Message}'"); //Updating the status that the step failed await _statusTracking.UpdateStepStatusAsync(MigrationStepEnum.AppLogic, MigrationStatusEnum.Failed); } }
/// <summary> /// Helper method that converts Aspx Pages to Blazor pages. /// </summary> /// <param name="sourcePage">The source aspx page to be converted.</param> /// <param name="targetProject">The target blazor project to write to.</param> /// <param name="targetPagesFolder">The target visual studio project folder the converted aspx will be added to.</param> /// <param name="sourceCodeBehind">Optional parameter that provides the code behind file for the aspx page to be converted also.</param> /// <returns>Flag used to determine if the conversion was successful.</returns> private async Task ConvertAspxPage(VsDocument sourcePage, VsProject targetProject, VsProjectFolder targetPagesFolder, VsCSharpSource sourceCodeBehind = null, VsDocument sourceDocCodeBehind = null) { try { //Getting the content from the source document. var pageContent = await sourcePage.GetDocumentContentAsStringAsync(); //File.ReadAllText(result.Path); //grab the <%Page element from the source and pull it from the text. Its meta data anyway and just screws up the conversion down the line. var pageHeaderData = System.Text.RegularExpressions.Regex.Match(pageContent, @"<%@\s*[^%>]*(.*?)\s*%>").Value; if (pageHeaderData.Length > 0) { pageContent = Regex.Replace(pageContent, @"<%@\s*[^%>]*(.*?)\s*%>", string.Empty); } //Swap ASP.NET string tokens for Razor syntax (<%, <%@, <%:, <%#:, etc var targetText = RemoveASPNETSyntax(pageContent); //Convert ASP.NET into Razor syntax. **This actually presumes that any controls like <asp:ListView.. //will have an equivalent Razor component in place called <ListView.. etc var conversionData = await ReplaceAspControls(targetText); //Drop the pageHeaderData into the Dictionary for later processing by any downstream T4 factories conversionData.Add("HeaderData", pageHeaderData); //Getting the source code from the code behind file provided. var codeSource = sourceCodeBehind?.SourceCode; if ((codeSource == null) && (sourceDocCodeBehind != null)) { codeSource = await sourceDocCodeBehind.GetCSharpSourceModelAsync(); } //put the files in the target project String targetFileName = Path.GetFileNameWithoutExtension(sourcePage.Path); conversionData.Add("Namespace", $"{targetProject.DefaultNamespace}.Pages"); //Setup Page directives, using statements etc var targetWithMeta = await SetRazorPageDirectives(targetFileName, conversionData); //Adding the converted content from the aspx page to the new razor page. VsDocument success = await targetPagesFolder.AddDocumentAsync($"{targetFileName}.razor", targetWithMeta); if (success != null) { //Updating the dialog with the status the aspx page has been converted. await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AspxPages, MessageTypeEnum.Information, $"Converted the aspx page to a razor page, added razor page {targetFileName}.razor"); //If we have the source code from the original aspx page add it to the razor pages folder. if (codeSource != null) { //Creating a CodeFactory model store this will be used to pass data to a T4 factory. CsModelStore modelStore = new CsModelStore(); //Adding the current class from the code behind into the model store for processing. modelStore.SetModel(codeSource.Classes.FirstOrDefault()); //Processing the T4 factory and loading the source code. var codeBehindFormattedSourceCode = Templates.PageCodeBehind.GenerateSource(modelStore, conversionData); //Calling the CodeFactory project system and adding a new code behind file and injecting the formatted code into the file. var codeBehind = await targetPagesFolder.AddDocumentAsync($"{targetFileName}.razor.cs", codeBehindFormattedSourceCode); if (codeBehind != null) { //Updating the dialog with the status the aspx page code behind has been converted. await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AspxPages, MessageTypeEnum.Information, $"Converted the aspx page code behind file to a razor page code behind file, added code behind file {targetFileName}.razor.cs"); } else { //Updating the dialog with the status the aspx page code behind failed await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AspxPages, MessageTypeEnum.Error, $"Failed the conversion of the aspx page code behind file to a razor page code behind file {targetFileName}.razor.cs"); } } } else { //Updating the dialog with the status the aspx page failed conversion. await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AspxPages, MessageTypeEnum.Error, $"Failed the conversion of the aspx page {targetFileName}.razor. Will not convert the code behind file."); } } catch (Exception unhandledError) { await _statusTracking.UpdateCurrentStatusAsync(MigrationStepEnum.AspxPages, MessageTypeEnum.Error, $"The following unhandled error occured while trying to convert the aspx page. '{unhandledError.Message}'"); } }