Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,21 @@ public static IScaffoldBuilder WithMvcViewsStep(this IScaffoldBuilder builder)

//TODO add extensions if 'TemplateFoldersUtilities' is not reworked.
var allT4TemplatePaths = new TemplateFoldersUtilities().GetAllT4TemplatesForTargetFramework(["Views"], viewModel.ProjectInfo.ProjectPath);
var viewTemplateProperties = ViewHelper.GetTextTemplatingProperties(allT4TemplatePaths, viewModel);

// Use controller root name for view folder to match MVC conventions
// e.g. BlogsController -> /Views/Blogs/
string? viewFolderName = null;
context.Properties.TryGetValue(nameof(EfControllerModel), out var efControllerModelObj);
if (efControllerModelObj is EfControllerModel efControllerModel)
{
var controllerName = efControllerModel.ControllerName;
const string controllerSuffix = "Controller";
viewFolderName = controllerName.EndsWith(controllerSuffix, StringComparison.Ordinal)
? controllerName.Substring(0, controllerName.Length - controllerSuffix.Length)
: controllerName;
Comment on lines +187 to +191
}

var viewTemplateProperties = ViewHelper.GetTextTemplatingProperties(allT4TemplatePaths, viewModel, viewFolderName);
if (viewTemplateProperties.Any())
{
step.TextTemplatingProperties = viewTemplateProperties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ internal class ViewHelper
/// </summary>
/// <param name="allT4TemplatePaths">The collection of all T4 template paths.</param>
/// <param name="viewModel">The view model containing project and model information.</param>
/// <param name="viewFolderName">An optional folder name for the view output path. If not provided, the model type name will be used.</param>
/// <returns>An enumerable collection of <see cref="TextTemplatingProperty"/> instances.</returns>
internal static IEnumerable<TextTemplatingProperty> GetTextTemplatingProperties(IEnumerable<string> allT4TemplatePaths, ViewModel viewModel)
internal static IEnumerable<TextTemplatingProperty> GetTextTemplatingProperties(IEnumerable<string> allT4TemplatePaths, ViewModel viewModel, string? viewFolderName = null)
{
Comment on lines +45 to 46
var textTemplatingProperties = new List<TextTemplatingProperty>();
if (allT4TemplatePaths is not null && allT4TemplatePaths.Any())
Expand All @@ -57,7 +58,10 @@ internal static IEnumerable<TextTemplatingProperty> GetTextTemplatingProperties(
continue;
}

string baseOutputPath = GetBaseOutputPath(viewModel.ModelInfo.ModelTypeName, viewModel.ProjectInfo.ProjectPath);
string folderName = viewFolderName is not null && IsValidFolderName(viewFolderName)
? viewFolderName
: viewModel.ModelInfo.ModelTypeName;
string baseOutputPath = GetBaseOutputPath(folderName, viewModel.ProjectInfo.ProjectPath);
string outputFileName = Path.Combine(baseOutputPath, $"{templateName}{Common.Constants.ViewExtension}");
textTemplatingProperties.Add(new()
{
Expand Down Expand Up @@ -153,4 +157,12 @@ private static string GetBaseOutputPath(string modelName, string? projectPath)
string projectBasePath = Path.GetDirectoryName(projectPath) ?? Directory.GetCurrentDirectory();
return Path.Combine(projectBasePath, "Views", modelName);
}

private static bool IsValidFolderName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return false;
if (name == "." || name == "..") return false;
if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) return false;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,85 @@ public void IndexTemplate_Constant_HasExpectedValue()
// Assert
Assert.Equal("Index.tt", ViewHelper.IndexTemplate);
}

[Fact]
public void GetTextTemplatingProperties_WithViewFolderName_UsesViewFolderNameInOutputPath()
{
// Arrange
string projectPath = Path.Combine("test", "project", "TestProject.csproj");
string templatePath = Path.Combine("templates", ViewHelper.CreateTemplate);
List<string> templatePaths = [templatePath];

ViewModel viewModel = new ViewModel
{
PageType = "Create",
ModelInfo = new ModelInfo { ModelTypeName = "Blog" },
ProjectInfo = new ProjectInfo(projectPath),
DbContextInfo = new DbContextInfo()
};

// Act
IEnumerable<TextTemplatingProperty> result = ViewHelper.GetTextTemplatingProperties(templatePaths, viewModel, "Blogs");

// Assert
TextTemplatingProperty? property = result.FirstOrDefault();
Assert.NotNull(property);
Assert.Contains("Blogs", property.OutputPath);
Assert.DoesNotContain("Blog" + Path.DirectorySeparatorChar, property.OutputPath.Replace("Blogs", string.Empty));
}

[Fact]
public void GetTextTemplatingProperties_WithNullViewFolderName_FallsBackToModelTypeName()
{
// Arrange
string projectPath = Path.Combine("test", "project", "TestProject.csproj");
string templatePath = Path.Combine("templates", ViewHelper.CreateTemplate);
List<string> templatePaths = [templatePath];

ViewModel viewModel = new ViewModel
{
PageType = "Create",
ModelInfo = new ModelInfo { ModelTypeName = "Product" },
ProjectInfo = new ProjectInfo(projectPath),
DbContextInfo = new DbContextInfo()
};

// Act
IEnumerable<TextTemplatingProperty> result = ViewHelper.GetTextTemplatingProperties(templatePaths, viewModel, null);

// Assert
TextTemplatingProperty? property = result.FirstOrDefault();
Assert.NotNull(property);
Assert.Contains("Product", property.OutputPath);
}

[Theory]
[InlineData("..")]
[InlineData(".")]
[InlineData("Views/../secrets")]
[InlineData("foo/bar")]
[InlineData("foo\\bar")]
public void GetTextTemplatingProperties_WithInvalidViewFolderName_FallsBackToModelTypeName(string invalidFolderName)
{
// Arrange
string projectPath = Path.Combine("test", "project", "TestProject.csproj");
string templatePath = Path.Combine("templates", ViewHelper.CreateTemplate);
List<string> templatePaths = [templatePath];

ViewModel viewModel = new ViewModel
{
PageType = "Create",
ModelInfo = new ModelInfo { ModelTypeName = "Product" },
ProjectInfo = new ProjectInfo(projectPath),
DbContextInfo = new DbContextInfo()
};

// Act
IEnumerable<TextTemplatingProperty> result = ViewHelper.GetTextTemplatingProperties(templatePaths, viewModel, invalidFolderName);

// Assert
TextTemplatingProperty? property = result.FirstOrDefault();
Assert.NotNull(property);
Assert.Contains("Product", property.OutputPath);
}
}
Loading