Skip to content

Commit

Permalink
Merge pull request #66 from soundaranbu/feature/check-view-exists
Browse files Browse the repository at this point in the history
New methods to render if view not exists
  • Loading branch information
soundaranbu authored Mar 4, 2024
2 parents a57b5a5 + 584823e commit d7d0c84
Show file tree
Hide file tree
Showing 39 changed files with 4,486 additions and 1,122 deletions.
20 changes: 16 additions & 4 deletions .github/workflows/dotnetcore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@ name: Build+Test

on:
push:
branches: [ master ]
branches: [ main ]
pull_request:
branches: [ master ]
branches: [ main ]

jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET 6

- name: Setup .NET 8
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
dotnet-version: '8.0.x'
#include-prerelease: true

- name: Setup .NET 7
uses: actions/setup-dotnet@v1
with:
dotnet-version: '7.0.x'
#include-prerelease: true
# to be used by azure functions

- name: Setup .NET 6
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'

- name: Setup .NET Core 3.1
uses: actions/setup-dotnet@v1
with:
Expand Down
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This project makes use of [Razor SDK](https://docs.microsoft.com/en-us/aspnet/co

| | .NET Core 3.0 | .NET Core 3.1 | .NET 5 | > .NET 6 |
|------------------|---------------|---------------|---------|--------------|
| Preferred Version| v1.6.0 | v1.6.0 | v1.6.0 | 1.9.0 |
| Preferred Version| v1.6.0 | v1.6.0 | v1.6.0 | 2.0.0-rc.1 |
| Console | ✓ | ✓ | ✓ | ✓ |
| Api | ✓ | ✓ | ✓ | ✓ |
| Mvc | ✓ | ✓ | ✓ | ✓ |
Expand Down Expand Up @@ -48,16 +48,19 @@ This project makes use of [Razor SDK](https://docs.microsoft.com/en-us/aspnet/co
## Installing Nuget Package
This library is available as [Nuget package](https://www.nuget.org/packages/Razor.Templating.Core/)

##### Using .NET CLI
### Using .NET CLI
```bash
dotnet add package Razor.Templating.Core
```
##### Using Package Reference .csproj
### Using Package Reference .csproj
```bash
<PackageReference Include="Razor.Templating.Core" Version="1.9.0" />
<PackageReference Include="Razor.Templating.Core" Version="2.0.0-rc.1" />
```

## Simple Usage:
## Usage - Render View With Layout
To render a view with layout, model, viewdata or viewbag, then call the `RenderAsync()` on the `RazorTemplateEngine` static class

### RenderAsync() method
```csharp
using Razor.Templating.Core;

Expand All @@ -82,15 +85,33 @@ Before applying this code, follow this article for sample implementation: https:

## Render View Without Layout
In case if there's a need to render a view without layout, use `RenderParitalAsync()` method.

### RenderPartialAsync() method
```cs
var html = await RazorTemplateEngine.RenderPartialAsync("/Views/ExampleView.cshtml", model, viewDataOrViewBag);
```

## Render Views Without Throwing Exception
There are `TryRenderAsync()` and `TryRenderPartialAsync` methods which will not throw exception if the view doesn't exist.
Instead they return a tuple to indicate whether the view exists and the rendered string.

### TryRenderAsync() method
```cs
var (viewExists, renderedView) = await engine.TryRenderAsync("~/Views/Feature/ExampleViewWithoutViewModel.cshtml");
```

### TryRenderPartialAsync() method
```cs
var (viewExists, renderedView) = await engine.TryRenderPartialAsync("~/Views/_ExamplePartialView.cshtml", model);
```


## Razor Views in Library
Razor view files(.cshtml) can be organized in a separate shared Razor Class Libary(RCL). Sample library can be found [here](https://github.com/soundaranbu/RazorTemplating/tree/master/examples/Templates/ExampleAppRazorTemplates)
We can organize the Razor view files(.cshtml) in a separate shared Razor Class Libary(RCL). Please find a sample library [here](https://github.com/soundaranbu/Razor.Templating.Core/tree/main/examples/Templates/ExampleAppRazorTemplates)

The Razor Class Library's `.csproj` file should look something like below. Whereas, `AddRazorSupportForMvc` property is important.
The Razor Class Library's `.csproj` file should look something like below. Whereas, `AddRazorSupportForMvc` property is mandatory.

Also, RCL should be referenced to the main project or where the `RazorTemplateEngine.RenderAsync` method is invoked.
Also, RCL should be referenced by the main project or where any of the rendering methods like `RazorTemplateEngine.RenderAsync()` are invoked.
```xml
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
Expand Down Expand Up @@ -128,12 +149,12 @@ services.AddRazorTemplating();

Once the dependencies are registered, we can use either one of these ways:

#### Using `RazorTemplateEngine` static class
### Using `RazorTemplateEngine` static class
```cs
var html = await RazorTemplateEngine.RenderAsync("~/Views/ExampleViewServiceInjection.cshtml");
```

#### Using `IRazorTemplateEngine`
### Using `IRazorTemplateEngine`
- Instead of using the `RazorTemplateEngine` static class, it's also possible to use the `IRazorTemplateEngine` interface to inject dependency directly into the constructor.

```cs
Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.100",
"rollForward": "latestMajor"
"version": "8.0.100",
"rollForward": "latestMinor"
}
}
11 changes: 11 additions & 0 deletions src/Razor.Templating.Core/Exceptions/ViewNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Razor.Templating.Core.Exceptions;

public class ViewNotFoundException : InvalidOperationException
{
public ViewNotFoundException(string message) : base(message){

}

}
38 changes: 31 additions & 7 deletions src/Razor.Templating.Core/IRazorTemplateEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,39 @@ public interface IRazorTemplateEngine
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view bag or view data</param>
/// <returns>Rendered HTML string of the view</returns>
Task<string> RenderAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null);
/// <exception cref="Exceptions.ViewNotFoundException">Invalid View</exception>
Task<string> RenderAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null);

/// <summary>
/// Renders the Razor View(.cshtml) Without Layout to String
/// </summary>
/// <param name="viewName">Relative path of the .cshtml view. Eg: /Views/YourView.cshtml or ~/Views/YourView.cshtml</param>
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view bag or view data</param>
/// <summary>
/// Renders the Razor View(.cshtml) To String. It does not throw exception when View is not found
/// </summary>
/// <param name="viewName"></param>
/// <param name="viewModel"></param>
/// <param name="viewBagOrViewData"></param>
/// <returns></returns>
Task<(bool ViewExists, string? RenderedView)> TryRenderAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null);

/// <summary>
/// Renders the Razor View(.cshtml) Without Layout to String
/// </summary>
/// <param name="viewName">Relative path of the .cshtml view. Eg: /Views/YourView.cshtml or ~/Views/YourView.cshtml</param>
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view bag or view data</param>
/// <returns>Rendered HTML string of the view</returns>
/// <exception cref="Exceptions.ViewNotFoundException">Invalid View</exception>
Task<string> RenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null);

/// <summary>
/// Renders the Razor View(.cshtml) Without Layout to String. This method does not throw exception when View is not found.
/// If the ViewExists return false, please check the following
/// Check whether you have added reference to the Razor Class Library that contains the view files or
/// Check whether the view file name is correct or exists at the given path or
/// Refer documentation or file issue here: https://github.com/soundaranbu/Razor.Templating.Core"}
/// </summary>
/// <param name="viewName">Relative path of the .cshtml view. Eg: /Views/YourView.cshtml or ~/Views/YourView.cshtml</param>
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view bag or view data</param>
/// <returns></returns>
Task<(bool ViewExists, string? RenderedView)> TryRenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null);
}
}
16 changes: 8 additions & 8 deletions src/Razor.Templating.Core/Razor.Templating.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<CopyRefAssembliesToPublishDirectory>true</CopyRefAssembliesToPublishDirectory>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>Render your .cshtml files to string. Works out of the box for Web, Console, Worker Service, Desktop Apps in .NET Core.</Description>
<RepositoryUrl>https://github.com/soundaranbu/RazorTemplating</RepositoryUrl>
<Description>Render your .cshtml files to string. Works out of the box for Web, Console, Worker Service, Desktop Apps in .NET.</Description>
<RepositoryUrl>https://github.com/soundaranbu/Razor.Templating.Core</RepositoryUrl>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright />
<PackageProjectUrl>https://github.com/soundaranbu/RazorTemplating</PackageProjectUrl>
<PackageProjectUrl>https://github.com/soundaranbu/Razor.Templating.Core</PackageProjectUrl>
<Authors>Soundar Anbalagan</Authors>
<Version>1.9.0</Version>
<Version>2.0.0-rc.1</Version>
<RepositoryType>git</RepositoryType>
<PackageTags>razor-templating,html-templatingrazor,render,dotnet-core,core,template-engine,email,emails,razor-view-to-string-renderer, razor-view-engine</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReleaseNotes>
- Added method RenderPartialAsync when you don't need a layout page
- Fixed overriding default IWebHostEnvironment object
- Added TryRenderAsync() and TryRenderPartialAsync() methods to render the views without throwing exceptions
- Removed obselete methods RazorTemplateEngine.RenderAsync() generic method and RazorTemplateEngine.Initialize()
</PackageReleaseNotes>
<Nullable>Enable</Nullable>
</PropertyGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<None Include="assets\icon.png">
<Pack>True</Pack>
Expand Down
36 changes: 12 additions & 24 deletions src/Razor.Templating.Core/RazorTemplateEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class RazorTemplateEngine
/// <see cref="IRazorTemplateEngine"/>.
/// </summary>
/// <param name="services"></param>
/// <exception cref="InvalidOperationException">The service has already been initiaized.</exception>
/// <exception cref="InvalidOperationException">The service has already been initialized.</exception>
internal static void UseServiceCollection(IServiceCollection services)
{
_services = services;
Expand All @@ -25,15 +25,6 @@ internal static void UseServiceCollection(IServiceCollection services)
_instance = new(CreateInstance, true);
}

/// <summary>
/// Creates the cache of RazorViewToStringRenderer. If already initialized, re-initializes.
/// </summary>
[Obsolete("This method is now marked as obsolete and no longer used. It will be removed in the upcoming versions. You can safely remove it and it doesn't affect any functionality.")]
public static void Initialize()
{
// TODO: Remove this method in v2.0.0
}

/// <summary>
/// Creates an instance of <see cref="RazorTemplateEngine"/> using an internal <see cref="ServiceCollection"/>.
/// </summary>
Expand Down Expand Up @@ -74,21 +65,18 @@ public async static Task<string> RenderAsync(string viewName, object? viewModel
public async static Task<string> RenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null)
{
return await _instance.Value.RenderPartialAsync(viewName, viewModel, viewBagOrViewData).ConfigureAwait(false);
}

/// <summary>
/// Renders the Razor View(.cshtml) To String
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="viewName">Relative path of the .cshtml view. Eg: /Views/YourView.cshtml or ~/Views/YourView.cshtml</param>
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view data</param>
/// <returns>Rendered HTML string of the view</returns>
[Obsolete("This method with generic type param is now obsolete and it will be removed in the upcoming versions. Please use the overload method without generic parameter instead.")]
public async static Task<string> RenderAsync<TModel>(string viewName, object viewModel, Dictionary<string, object> viewBagOrViewData)
}

/// <summary>
/// Renders the Razor View(.cshtml) Without Layout to String. This method does not throw exception when View is not found.
/// </summary>
/// <param name="viewName"></param>
/// <param name="viewModel"></param>
/// <param name="viewBagOrViewData"></param>
/// <returns></returns>
public async static Task<(bool ViewExists, string? RenderedView)> TryRenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null)
{
// TODO: Remove this method in v2.0.0
return await _instance.Value.RenderAsync(viewName, viewModel, viewBagOrViewData).ConfigureAwait(false);
return await _instance.Value.TryRenderPartialAsync(viewName, viewModel, viewBagOrViewData).ConfigureAwait(false);
}
}
}
71 changes: 50 additions & 21 deletions src/Razor.Templating.Core/RazorTemplateEngineRenderer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Razor.Templating.Core.Exceptions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
Expand Down Expand Up @@ -40,38 +41,66 @@ public async Task<string> RenderAsync(string viewName, object? viewModel = null,
using var serviceScope = _serviceProvider.CreateScope();
var renderer = serviceScope.ServiceProvider.GetRequiredService<RazorViewToStringRenderer>();
return await renderer.RenderViewToStringAsync(viewName, viewModel, viewDataDictionary, isMainPage: true).ConfigureAwait(false);
}

/// <summary>
/// Renders the Razor View(.cshtml) Without Layout to String
/// </summary>
/// <param name="viewName">Relative path of the .cshtml view. Eg: /Views/YourView.cshtml or ~/Views/YourView.cshtml</param>
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view bag or view data</param>
/// <returns>Rendered HTML string of the view</returns>
public async Task<string> RenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null)
{
}

/// <summary>
/// Renders the Razor View(.cshtml) Without Layout to String
/// </summary>
/// <param name="viewName">Relative path of the .cshtml view. Eg: /Views/YourView.cshtml or ~/Views/YourView.cshtml</param>
/// <param name="viewModel">Optional model data</param>
/// <param name="viewBagOrViewData">Optional view bag or view data</param>
/// <returns>Rendered HTML string of the view</returns>
public async Task<string> RenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null)
{
if (string.IsNullOrWhiteSpace(viewName))
{
throw new ArgumentNullException(nameof(viewName));
}

var viewDataDictionary = GetViewDataDictionaryFromViewBagOrViewData(viewBagOrViewData);

var viewDataDictionary = GetViewDataDictionaryFromViewBagOrViewData(viewBagOrViewData);

using var serviceScope = _serviceProvider.CreateScope();
var renderer = serviceScope.ServiceProvider.GetRequiredService<RazorViewToStringRenderer>();
return await renderer.RenderViewToStringAsync(viewName, viewModel, viewDataDictionary, isMainPage: false).ConfigureAwait(false);
}

private static ViewDataDictionary GetViewDataDictionaryFromViewBagOrViewData(Dictionary<string, object>? viewBagOrViewData)
{
return await renderer.RenderViewToStringAsync(viewName, viewModel, viewDataDictionary, isMainPage: false).ConfigureAwait(false);
}

private static ViewDataDictionary GetViewDataDictionaryFromViewBagOrViewData(Dictionary<string, object>? viewBagOrViewData)
{
var viewDataDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary());

foreach (var keyValuePair in viewBagOrViewData ?? new())
{
viewDataDictionary.Add(keyValuePair!);
}
return viewDataDictionary;
}
}
return viewDataDictionary;
}

public async Task<(bool ViewExists, string? RenderedView)> TryRenderAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null)
{
try
{
var renderedView = await RenderAsync(viewName, viewModel, viewBagOrViewData);
return (true, renderedView);
}
catch (ViewNotFoundException)
{
}

return (false, null);
}

public async Task<(bool ViewExists, string? RenderedView)> TryRenderPartialAsync(string viewName, object? viewModel = null, Dictionary<string, object>? viewBagOrViewData = null)
{
try
{
var renderedView = await RenderPartialAsync(viewName, viewModel, viewBagOrViewData);
return (true, renderedView);
}
catch (ViewNotFoundException)
{
}

return (false, null);
}
}
}
Loading

0 comments on commit d7d0c84

Please sign in to comment.