Add Razor Pages Module Pipeline
See original GitHub issueLiving Document
This document is a living document and will be edited as we get feedback from community members to refine the Spec
Description of problem
There is no easy way to migrate DNN to .NET Core as Web.Forms does not exist in .NET Core. Adding support for a Razor Pages Module will support this soft migration path.
Description of solution
Add a new Module Pipeline that will be a carbon copy of .NET Core Razor Pages into the DNN Platform. The goal of this new module pipeline will to be use the EXACT same .zip installer and module code for both DNN on .NET Framework and DNN on .NET Core which will allow module developers to migrate their modules once to Razor Pages and they should work on their .NET version of DNN.
Please read the spec below for implementation details and the plan of action (which we can discuss in the comments)
The Spec
DNN Razor Pages will follow as close as we possibly can to feature parity with .NET Core Razor Pages. If you are unfamiliar with how it works please take a look at the docs from Microsoft. Every item in the Spec is a design element that is pulled from the Razor Pages implementation in .NET Core and our Proof of Concept of DNN running in .NET Core
DNN Manifest File
A minor update to the Module Loading pipeline will be needed in the ModuleControlFactory
to allow a new type of file .razorpages
.
Snippet Example Manifest:
<moduleControl>
<controlKey />
<controlSrc>DNNTAG.RazorPagesModule/Index.cshtml</controlSrc>
<supportsPartialRendering>False</supportsPartialRendering>
<controlTitle>RazorPagesModule</controlTitle>
<controlType>View</controlType>
<iconFile />
<helpUrl />
<viewOrder>0</viewOrder>
</moduleControl>
Module Control Properties
Property | Description |
---|---|
controlKey |
|
controlSrc |
The path to the root index.cshtml starting from the DesktopModules Folder |
supportsPartialRendering |
|
controlTitle |
|
controlType |
|
iconFile |
|
helpUrl |
|
viewOrder |
note: This isn’t the final spec as this is the module manifest from the existing mvc/razor pages implementation which is subject to change.
The View aka Razor Page
The view is a .cshtml
Razor Page. The exact same razor syntax that you see in Razor Modules or MVC Modules. Razor Syntax (the code of a .cshtml
page) is a combination of C# and HTML.
Simple View
@page
@model HelloWorld
<h1>@Model.Title</h1>
<div>@Model.Content</div>
Directives The top of the Razor Page has several directives that are required for the page to work correctly in DNN Razor Pages
Directive | Description | MVP Plan |
---|---|---|
@page |
Required at the top of every Razor Page to tell the runtime that the page is using Razor Pages instead of MVC or other framework | Must Have |
@using |
Includes a .NET namespace to be using in the C# code included in the Razor Page | Must Have |
@model |
Binds the specific Model to the view which allows the use of @Model syntax to retrieve values of the model’s properties |
Must Have |
@inject |
Injects different services or objects that are registered with the Razor Pages Dependency Injection Provider | Nice to Have |
View Context Utilities The DNN Platform has special utility libraries that are used throughout the different module platforms to make DNN specific API calls easier for the module developer.
API Utility | Description |
---|---|
DnnUrlHelper |
Manages DNN specific URLs and routing |
TBD | We need feedback on required view context APIs |
The Page Model
The Page Model is NOT a code behind
Every *.cshtml
file in Razor Pages modules will have a corresponding *.cshtml.cs
file which will inherit a DnnPageModel
the base class will provide access to critical DNN, .NET Framework and .NET Core APIs depending on the runtime environment.
The Page Model simplifies binding a model to a view similar to how you would do it in MVC but with less convention based programming. The Page Model can have any number of properties on it that can be bound in the View using Razor Syntax. The properties can be assigned at Page Model Construction time or using different HTTP Handlers that are implemented in the Razor Pages spec.
Handlers
Razor Pages requires several different HTTP Handles that execute depending on the type of HTTP Method that is invoked by the client. This allows the Page Model to execute different code depending on the type of action the user is trying to accomplish. For example if the user is just trying to retrieve data the module may have a HTTP GET
Method implemented on their Page Model
Handler | Route Example | Description | MVP Plan |
---|---|---|---|
GET |
https://localhot.dnndev.me/MyModulePage/ModuleId/1234/index |
The basic GET HTTP Request Handler to populate the model at request time | Must Have |
PUT |
https://localhot.dnndev.me/MyModulePage/ModuleId/1234/index |
TBD | Won’t Have |
POST |
https://localhot.dnndev.me/MyModulePage/ModuleId/1234/index |
The POST HTTP Request Handler to submit data to the Page Model using [BindableProperty] |
Must Have |
DELETE |
https://localhot.dnndev.me/MyModulePage/ModuleId/1234/index |
TBD | Won’t Have |
Routing Table A Razor Pages module can have many pages which have many routing features. The table below documents how the folder structure in a Razor Pages Module will map to the DNN Routes
File Path | Matching URL |
---|---|
/Pages/Index.cshtml |
https://localhost.dnndev.me/MyModulePage/ModuleId/1234/ or https://localhost/dnndev.me/MyModulePage/ModuleId/1234/Index |
/Pages/Contact.cshtml |
https://localhost.dnndev.me/MyModulePage/ModuleId/1234/Contact |
/Pages/Store/Contact.cshtml |
https://localhost.dnndev.me/MyModulePage/ModuleId/1234/Store/Contact |
/Pages/Store/Index.cshtml |
https://localhost.dnndev.me/MyModulePage/ModuleId/1234/Store or https://localhost.dnndev.me/MyModulePage/ModuleId/1234/Store/Index |
HTTP GET Page Model
public class HelloWorldModel : DnnPageModel
{
public string Message { get; private set; } = "PageModel in C#";
public IDnnActionResult OnGet()
{
Message += $" Server time is { DateTime.Now }";
}
}
Async Handler Methods HTTP GET Page Model
public class HelloWorldModel : DnnPageModel
{
public string Message { get; private set; } = "PageModel in C#";
public async Task<IDnnActionResult> OnGetAsync()
{
// Simulate Service call
await Task.Delay(100);
Message += $" Server time is { DateTime.Now }";
}
}
HTTP Post Page Model
public class HelloWorldModel : DnnPageModel
{
[DnnBindProperty]
public Customer Customer { get; set; }
public async Task<IDnnActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
// Simulate a database save
await Task.Delay(100);
return RedirectToPage("/Index");
}
}
DNN Base Classes
DnnPageModel The DnnPageModel provides a shared implementation of the .NET Core PageModel that will work in .NET Framework and .NET Core. This is the expected Model that the DNN Module Pipeline expects when the module is loading. If this file doesn’t inherit from this class the Module will not load correctly. The DnnPageModel includes DNN specific APIs for URLs, HTTP and DNN.
An example of what the abstract base class may look like
public abstract class DnnPageModel
{
protected DnnHelper Dnn { get; private set; }
protected HtmlHelper Html { get; private set; }
protected UrlHelper Url { get; private set; }
}
To get an idea on what the cross-platform DnnPageModel may look at take a look at the Proof of Concept I wrote last year. DnnPageModel. Be advised that this Proof of Concept did not implement the Razor Pages spec correctly so the code may not 100% be copied into the final solution.
Required Methods/APIs
Method/Property Name | Status | Description |
---|---|---|
ViewData |
Must Have | |
PageContext |
Must Have | |
HttpContext |
Must Have | |
Request |
Must Have | |
Url |
Must Have | |
RouteData |
Must Have | |
ModelState |
Must Have | Handles model validation |
User |
Must Have | |
Response |
Must Have | |
TempData |
Must Have | |
BadRequest() |
Must Have | |
Challenge() |
Must Have | |
Content() |
Must Have | |
File() |
Must Have | |
Forbid() |
Must Have | |
LocalRedirect() |
||
LocalRedirectPermanent() |
||
LocalRedirectPermanentPreserveMethod() |
||
LocalRedirectPreserveMethod() |
||
NotFound() |
Must Have | |
OnPageHandler() |
Must Have | |
Page() |
Must Have | Returns the default OnGet() HTTP Handler |
PhysicalFile() |
||
RedirectPermanent() |
||
RedirectPermanentPreserveMethod() |
||
RedirectPreserveMethod() |
||
RedirectToAction() |
Must Have | |
RedirectToActionPermanent() |
||
RedirectToActionPermanentPreserveMethod() |
||
RedirectToActionPreserveMethod() |
||
RedirectToPage() |
Must Have | |
RedirectToPagePermanent() |
||
RedirectToPagePermanentPreserveMethod() |
||
RedirectToPagePreserveMethod() |
||
RedirectToRoute() |
||
RedirectToRoutePermanent() |
||
RedirectToRoutePermanentPreserveMethod() |
||
RedirectToRoutePreserveMethod() |
||
SignIn() |
Research | |
SignOut() |
Research | |
StatusCode() |
||
TryValidateModel() |
||
Unauthorized() |
Must Have | |
Redirect() |
Must Have | |
TryUpdateModelAsync() |
DnnBindProperty (Attribute) Provides the DNN Wrapper for binding properties to be used on POST HTTP requests. This needs to be included as a wrapper as the behavior doesn’t exist in .NET Framework.
IDnnActionResult This is kind of an unknown at this point as this has been used with different names and purposes throughout the MVC pattern and the Proof of Concept.
This is a placeholder that needs to be flushed out more and how it will work between .NET Framework and .NET Core.
Dependency Injection
This is a placeholder and needs to be flushed out. It will not be included in the 9.4 release
Razor Modules vs. Razor Pages Modules
With the addition of a new Module Pipeline we will now have 2 module types that have very similar names and it will be our job as Open Source Maintainers to document this as best as we can. I am open to ideas on reducing the confusion here but I don’t see any way around calling this new module pattern Razor Pages Modules
as this is the name Microsoft uses.
Razor Modules are an existing Module Pipeline that uses a simple .cshtml
or .vbhtml
view and optionally a model binding. See “Why can’t we use Razor Modules?” below for more info on why we can’t use it
If you want more information on the existing Razor Module Platform take a look at the DotNetNuke.Web.Razor
library in the Platform Code
Description of alternatives considered
This was originally an effort that I undertook in March of 2018 and was put on hold for several reasons. By taking our time considering how the implementation works we have learned quite a bit. The initial implementation was written as a fork of the MVC Module Platform which I don’t think is appropriate anymore.
The new Razor Pages Module Platform will be a combination of the Razor Module Platform and the MVC Module Platform as they both have things that will be needed to accomplish this. The code we originally wrote in 2018 will still be useful, but some things will not be.
Take a look at what we did last year:
- Razor Pages Fork of Dnn.Platform
- Module Developer SDK An early SDK of Razor Pages
- DNN vNext A prototype of DNN running on .NET Core (This is experimental and should not be used)
Again, some of this code will be re-used and some of it will be scrapped as we work towards a lean Razor Pages implementation in DNN.
Why can’t we use Razor Modules?
I am glad you asked why can’t we just use the existing Razor Modules that already exist in DNN today. This is a great question and I asked myself this as I was researching the DNN Module Pipeline.
Razor Modules are tightly coupled to several System.Web objects and will not work in a .NET Core implementation. Since there are many modules in the field that use Razor Modules I believe it is best to not create any breaking change for them.
The Razor Module pattern will be leveraged as it has the building blocks for completing a Razor Pages Module Pipeline
Migration Plan
Razor Modules
#2613 Deprecates the existing Razor Module pattern. As we move forward with the spec and the implementation, we will document here how to migrate from an existing Razor Module to Razor Pages.
Screenshots
Not Applicable
Additional context
Add any other context about the feature that may be helpful with implementation.
Affected version
- 9.4.0
Affected browser
- Chrome
- Firefox
- Safari
- Internet Explorer
- Edge
Implementation Plan
The plan is to create the Razor Pages Module Platform as an Experimental Feature Toggle for DNN 9.4 and have it turned off by default. This will allow developers to start testing it out and provide feedback as soon as possible.
<appSettings>
<add key="Experimental:RazorPages" value="false" />
</appSettings>
When you turn on Razor Pages it will disable existing razor modules since the ControlSrc
conflict
When we have determined Razor Pages is production ready we will remove the feature toggle
Linked Work Items
This new feature may be too large to complete in one Pull Request and we should break it down to Linked Work Items to make it more manageable. As we create the linked work items we should add to the table below
Work Item # | Target Release | Completed |
---|---|---|
#2613 | 9.3.1 | PR Submitted #2618 |
#2615 | 9.4.0 | PR Submitted #2644 |
#2619 | ||
#2637 | 9.3.1 | PR Submitted #2638 |
Issue Analytics
- State:
- Created 5 years ago
- Reactions:4
- Comments:49 (49 by maintainers)
Top GitHub Comments
Also, there’s one recurring thought I’ve been having that I’ll just lay out here in case it piques anyone’s interest. Something that Andrew’s demo last year highlighted was that, while you can target .NET Standard, you still need to have separate packages for .NET Framework and .NET Core, b/c the dependencies won’t end up being the same. I’m wondering if we could create a new packaging system that used NuGet, which would allow a module package to be just a NuGet package, and then DNN would be able to unpack it and grab the right versions of assemblies. This would also give us 1️⃣ dependency management (e.g. my module depends on this other module, or some library, etc.) and 2️⃣ a simple means of hosting a collection of extensions that are available to install based on those dependencies (i.e. we can just stand up a NuGet server, and easily configure the site to be able to look at extra third party servers, or the main NuGet feed for libraries, etc.).
If there’s interest in that idea, I suppose it should be spun off into its own RFC. @ahoefling, from your perspective, does that sound like it would make packaging/distributing/deploying simpler for Razor Pages Modules?
Fantastic work on this @ahoefling - exciting times!