Skip to content

Commit

Permalink
#72 Fixed an issue in the MVC application where views were not being … (
Browse files Browse the repository at this point in the history
#75)

* #72 Fixed an issue in the MVC application where views were not being discovered in the same controller’s view location. Now, if the controller is HomeController and the view file Index.cshtml is located under Views/Home/Index.cshtml, the library can render the view from HomeController simply by passing Index as the view name. Previously, it was necessary to pass the full relative URL Views/Home/Index.cshtml. This functionality is available only when IRazorTemplateEngine is utilized through Dependency Injection (DI). For applications that are not MVC-based, this view discovery method will not work, and the library will revert to the default behavior, which requires the full relative path of the view file.

* #71 Adds support for MVC View localization
  • Loading branch information
soundaranbu authored Aug 6, 2024
1 parent f1788b1 commit c690b92
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 53 deletions.
82 changes: 47 additions & 35 deletions examples/Console/ExampleConsoleApp.Net6_0/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,9 @@ async static Task Main(string[] args)
{
Console.WriteLine(DateTime.Now);

var model = new ExampleModel()
{
PlainText = "Some text",
HtmlContent = "<em>Some emphasized text</em>"
};

var viewData = new Dictionary<string, object>();
viewData["Value1"] = "1";
viewData["Value2"] = "2";

var html = await RazorTemplateEngine.RenderAsync("/Views/ExampleView.cshtml", model, viewData);
Console.Write(html);
Console.WriteLine(DateTime.Now);

// Render View with View Component
html = await RazorTemplateEngine.RenderAsync("/Views/ExampleViewWithViewComponent.cshtml");
Console.Write(html);

// Use service collection
// Arrange
var model1 = new ExampleModel()
{
PlainText = "Lorem Ipsium",
HtmlContent = "<em>Lorem Ipsium</em>"
};

// Add dependencies to the service collection and add razor templating to the collection
var services = new ServiceCollection();
services.AddTransient<ExampleService>();
// Add after registering all dependencies
// this is important for the razor template engine to find the injected services
services.AddRazorTemplating();
// Act
var html1 = await RazorTemplateEngine.RenderAsync("~/Views/ExampleViewServiceInjection.cshtml", model);
Console.WriteLine(html1);
await RenderViewWithModelAsync();
await RenderViewComponentWithoutModelAsync();
await RenderWithDependencyInjectionAsync();

Console.WriteLine(DateTime.Now);
}
Expand All @@ -59,5 +27,49 @@ async static Task Main(string[] args)

Console.ReadLine();
}

private static async Task RenderViewComponentWithoutModelAsync()
{
// Render View with View Component
var html = await RazorTemplateEngine.RenderAsync("/Views/ExampleViewWithViewComponent.cshtml");
Console.Write(html);
}

private static async Task RenderViewWithModelAsync()
{
var model = new ExampleModel()
{
PlainText = "Some text",
HtmlContent = "<em>Some emphasized text</em>"
};
var viewData = new Dictionary<string, object>();
viewData["Value1"] = "1";
viewData["Value2"] = "2";

var html = await RazorTemplateEngine.RenderAsync("/Views/ExampleView.cshtml", model, viewData);
Console.Write(html);
Console.WriteLine(DateTime.Now);
}

private static async Task RenderWithDependencyInjectionAsync()
{
// Use service collection
// Arrange
var model = new ExampleModel()
{
PlainText = "Lorem Ipsium",
HtmlContent = "<em>Lorem Ipsium</em>"
};

// Add dependencies to the service collection and add razor templating to the collection
var services = new ServiceCollection();
services.AddTransient<ExampleService>();
// Add after registering all dependencies
// this is important for the razor template engine to find the injected services
services.AddRazorTemplating();
// Act
var html1 = await RazorTemplateEngine.RenderAsync("~/Views/ExampleViewServiceInjection.cshtml", model);
Console.WriteLine(html1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ public HomeController(IRazorTemplateEngine engine)

public async Task<IActionResult> Index()
{
//var html = await _engine.RenderAsync("~/Views/Home/Index.cshtml");
//return Content(html);

return View();
var html = await _engine.RenderAsync("Index");
return Content(html);
}

public async Task<IActionResult> RenderRcl()
Expand Down
7 changes: 3 additions & 4 deletions examples/Mvc/ExampleWebApp.Net6_0/ExampleWebApp.Net6_0.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
<ProjectReference Include="..\..\Templates\ExampleAppRazorTemplates\ExampleRazorTemplatesLibrary.csproj" />
</ItemGroup>

<!--<ItemGroup>
<ProjectReference Include="..\..\Templates\ExampleAppRazorTemplates\ExampleRazorTemplatesLibrary.csproj" />
</ItemGroup>-->

<ItemGroup>
<InternalsVisibleTo Include="Razor.Templating.Core.Test" />
</ItemGroup>
</Project>
13 changes: 11 additions & 2 deletions examples/Mvc/ExampleWebApp.Net6_0/Program.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
builder.Services.AddMvc().AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
builder.Services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[] { "en-US", "fr" };
options.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
});
builder.Services.AddRazorTemplating();

var app = builder.Build();
Expand All @@ -23,6 +30,8 @@
app.UseRouting();
app.UseAuthorization();

app.UseRequestLocalization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7024;http://localhost:5024",
"applicationUrl": "http://localhost:5024",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
Expand Down
8 changes: 3 additions & 5 deletions examples/Mvc/ExampleWebApp.Net6_0/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
ViewData["Title"] = "Home Page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div>Content from Index.cshtml</div>

<ul>
<li><a href="/home/Index">Render content by using only the view name instead of path</a></li>
<li><a href="/home/RenderRcl">Render content using templating engine</a></li>
<li><a href="/home/RenderPartialTest">Render partial content using templating engine</a></li>
</ul>


<partial name="_partialTest"/>
7 changes: 7 additions & 0 deletions examples/Mvc/ExampleWebApp.Net6_0/Views/Home/Index.fr.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@{
ViewData["Title"] = "Home Page";
}

<div>Content from Index.fr.cshtml</div>

<partial name="_partialTest"/>
8 changes: 6 additions & 2 deletions src/Razor.Templating.Core/RazorViewToStringRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
Expand All @@ -23,20 +24,23 @@ internal sealed class RazorViewToStringRenderer
private readonly IRazorViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
private readonly IActionContextAccessor _actionContextAccessor;

public RazorViewToStringRenderer(
IRazorViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IActionContextAccessor actionContextAccessor)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
_actionContextAccessor = actionContextAccessor;
}

public async Task<string> RenderViewToStringAsync(string viewName, object? model, ViewDataDictionary viewDataDictionary, bool isMainPage = true)
{
var actionContext = GetActionContext();
var actionContext = _actionContextAccessor.ActionContext ?? GetActionContext();
var view = FindView(actionContext, viewName, isMainPage);

await using var output = new StringWriter();
Expand Down
51 changes: 51 additions & 0 deletions test/Razor.Templating.Core.Test/MvcApplicationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Mvc.Testing;
using System.Threading.Tasks;
using Xunit;

namespace Razor.Templating.Core.Test;
public class MvcApplicationTest
{
private readonly WebApplicationFactory<Program> _factory;

public MvcApplicationTest()
{
_factory = new WebApplicationFactory<Program>();
}

[Fact]
public async Task HomePage_Should_RenderViewByName()
{
// Arrange
var client = _factory.CreateClient();

// Act
var response = await client.GetAsync("/Home/Index");

// Assert
response.EnsureSuccessStatusCode();

var pageHtml = await response.Content.ReadAsStringAsync();

Assert.Contains(@"<li><a href=""/home/Index"">Render content by using only the view name instead of path</a></li>", pageHtml);
Assert.Contains(@"<h1>This is a partial page</h1>", pageHtml);
}

[Theory]
[InlineData("fr", "<div>Content from Index.fr.cshtml</div>")]
[InlineData("en-US", "<div>Content from Index.cshtml</div>")]
public async Task RequestWithCulture_Should_RenderLocalizedView(string culture, string expectedContent)
{
// Arrange
var client = _factory.CreateClient();

// Act
var response = await client.GetAsync($"/Home/Index?culture={culture}");

// Assert
response.EnsureSuccessStatusCode();

var pageHtml = await response.Content.ReadAsStringAsync();

Assert.Contains(expectedContent, pageHtml);
}
}
20 changes: 20 additions & 0 deletions test/Razor.Templating.Core.Test/Razor.Templating.Core.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,28 @@


<ItemGroup>
<ProjectReference Include="..\..\examples\Mvc\ExampleWebApp.Net6_0\ExampleWebApp.Net6_0.csproj" />
<ProjectReference Include="..\..\examples\Templates\ExampleAppRazorTemplates\ExampleRazorTemplatesLibrary.csproj" />
<ProjectReference Include="..\..\src\Razor.Templating.Core\Razor.Templating.Core.csproj" />
</ItemGroup>


<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing">
<Version>8.0.7</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing">
<Version>7.0.20</Version>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing">
<Version>6.0.32</Version>
</PackageReference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ public async Task RenderInvalidView_Should_ThrowError()
Assert.Contains("Unable to find view '/Views/SomeInvalidView.cshtml'.", actual.Message);
}

[Fact]
public async Task RenderViewByNameOutsideMvcApplication_Should_ThrowError()
{
var actual = await Assert.ThrowsAnyAsync<InvalidOperationException>(() => RazorTemplateEngine.RenderAsync("Index"));
Assert.Contains("Unable to find view 'Index'.", actual.Message);
}

[Fact]
public async Task Throws_ArgumentNullException_If_RenderAsync_When_ViewName_Is_Null()
{
Expand Down

0 comments on commit c690b92

Please sign in to comment.