Closed luanmm closed 4 years ago
See #5227 for SPA module,
Orchard Core allows you to configure your SPA according to your needs, some that we use are
Configure SPA for headless as you suggested above, static SPA landing page index.html and assets are served always and configured in application startup.
Modular SPA where SPA is developed as orchard module and module route serves static landing page and assets for SPA, configured in module startup as in #5227
SPA served by MVC or Razor page in module, you can use Orchard core resource management to define you css/js assets, in this case you don’t need to define UseSpa
, I prefer this third option for production and define mymodule/dev
route only for development that use UseProxyToSpaDevelopmentServer
using UseSpa
Hello, @ns8482e! Thank you for your reply!
I have seen already the #5227 and some other proposals and tried to achieve something similar. But my scenario is a bit different because the intention is to use Orchard Core as CMS in its headless mode, but still use just one server (IIS/Kestrel) to host it (to simplify the infrastructure in some ways).
The thing is that the most common scenarios are not using the SPA as "HomePage", but, instead, for sub-modules or sub-pages (sections inside the web portal). In my case, I would need it (being in a separated module or not) to be the last "catch-all" handler. So, for instance, if the user accesses "/admin", Orchard Core will handle it. But any 404 requests for Orchard Core should be "redirected" to the SPA page.
Another thing is that the user should see the SPA page when navigating in "root path" (when acessing "/" or "/about" or "/products", ...). As I saw in other SPA attempts, no one was concerned about this is being handled by a module (like user needing to access by "/Orchard.MyCustomModule/
In general, what I'm addressing here is the lack of support I see in Orchard for SPA pages (or other common architectures/scenarios/technologies supported by ASP.NET), which IMHO should be considered, as Orchard Core does not seems to have any special requirement in its processing flow that justify any "blocks" like this one, or I'm missing something about this? Maybe Orchard Core could be improved to simplify some things and get the things done more "clearly" and "cleanly".
By the way, I've ended up, for now, with something like these (to guarantee it works in Development and Production, which was a challenge for other reasons):
private readonly string[] _reservedPathSegments = new string[] {
"/Api",
"/Admin",
"/Login",
"/Logout",
"/ChangePassword",
"/ExternalLogins"
};
public void ConfigureServices(IServiceCollection services)
{
services.AddOrchardCms();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IShellHost host)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseOrchardCore(builder =>
{
if (env.IsDevelopment())
{
ShellSettings settings;
builder.UseWhen(ctx =>
host.TryGetSettings("Default", out settings) &&
settings.State == TenantState.Running &&
!IsReservedPathSegment(ctx.Request.Path),
builder =>
{
builder.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.UseProxyToSpaDevelopmentServer("http://localhost:8080/");
});
});
}
});
}
private bool IsReservedPathSegment(PathString pathSegment)
{
foreach (var reservedPathSegment in _reservedPathSegments)
if (pathSegment.StartsWithSegments(reservedPathSegment))
return true;
return false;
}
It works, but I couldn't make it work out-of-the-box as I would do in a common ASP.NET project (and I had to give up on "SpaServices.Extensions" functionality for Production, using directly the static files of my SPA app inside the main project "wwwroot" folder). That's my point: I don't see why Orchard Core just can't work without "messing up" the flow for simple scenarios like this one. I even tried to put a condition on "404 error" in response after the "UseOrchardCore" has been called, but it just doesn't work (the response is never 404, even when it is, which I suspect that is because of internal Orchard Core handlers).
As I ended up with some "workaround" here and now maybe it may help someone to achieve something similar, I will close the issue. Maybe someone could reopen (the issue or this topic) someday to improve it... it would be awesome.
Thanks for the help and congratulations for all the effort from you guys that helped somehow to bring Orchard Core to its v1.0! It is a great project and the ideas behind it are great indeed! Best regards!
@luanmm
It works, but I couldn't make it work out-of-the-box as I would do in a common ASP.NET project
It's not so simple because OrchardCore is multi tenancy, each tenant is an isolated application composed of modules that are enabled or not, each module is a micro app with its own controller / views / wwwroot folder, and the main app itself is also a module that is always enabled for all tenants.
That said when we are aware that most of the OrchardCore things are executed in a tenant container (we have at least one Default
tenant), we can pretty much do everything like a common AspnetCore app. This e.g by using from the main app the 2 following OrchardCoreBuilder
helpers
Here some examples with different delegate signatures that are allowed
public void ConfigureServices(IServiceCollection services)
{
services.AddOrchardCms()
.ConfigureServices(tenantServiceCollection =>
{
// Register tenant services;
}, order: -100) // optional order
.ConfigureServices((tenantServiceCollection, appLevelServiceProvider) =>
{
// Configure tenant pipeline;
}, order: -100) // optional order
.Configure(tenantAppBuilder =>
{
// Configure tenant pipeline;
}, order: -100) // optional order
.Configure((tenantAppBuilder, tenantEndpointBuilder) =>
{
// Configure tenant pipeline;
}, order: -100) // optional order
.Configure((tenantAppBuilder, tenantEndpointBuilder, tenantServiceProvider) =>
{
// Configure tenant pipeline;
}, order: -100) // optional order
;
}
I see, @jtkech. I don't know deeply about Orchard Core to tell what is the point that is changing the context so I can't use the "UseSpa" directly, but it is in the ApplicationBuilder, not in the service configuration. In my scenario, I just use one tenant ("Default"). But one thing that may give different results is trying to use this methods as in your examples, which I missed in my tests.
I hoped that configuration for Orchard could be very similar to a simple ASP.NET MVC project and "it would just work" without major conflicts, so I ended up with the code above.
Thank you very much for the response and the tips! I will try to play a little bit more with this when get a free time. Or if someone else is doing something similar to the scenario I described, maybe we get more contributions from other users.
@luanmm I'm also trying to get headless Orchard Core to run together with ASP.NET Core hosted SPA. I think I finally found a "clever" way by splitting the Orchard to be run on different subfolder with IApplicationBuilder.Map(..)
functionality. So now my React SPA is running on server root and Orchard Core in i.e. /cms
sub-folder. With this setup my SPA would catch all other URLs like /products
, /about
etc. so I'm free to use something like React Routes to handle those.
Here's my ConfigureServices
and Configure
from Startup.cs
- baseline SPA stuff was created by dotnet new react
template.
public void ConfigureServices(IServiceCollection services)
{
// services.AddControllersWithViews();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddOrchardCms();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
// Host Orchard Core in /cms subfolder. After this i.e. GraphQL can be found from /cms/api/graphql
app.Map(new PathString("/cms"), cms =>
{
cms.UseOrchardCore();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
Hope this helps someone like me who spent half a day to figure this out 😅
Please how do I integrate orchard core blog to my react application
// Host Orchard Core in /cms subfolder. After this i.e. GraphQL can be found from /cms/api/graphql app.Map(new PathString("/cms"), cms => { cms.UseOrchardCore(); });
Quick question; (I like your solution) -- when you said host OrchardCore under the cms folder .. could you please let me know how I would do that ?
Hello,
First of all, thanks for all the effort with Orchard Core. It is very promising regarding its versatility for a lot of projects.
What I'm concerned right now in a project is because it is being a little difficult to use Orchard Core and SPA in the same project, which is a scenario that seems to be something usual.
I had to use a workaround to make it work and it does not seems to be the right way to do it:
As you may see, I had to put the "UseSpa" inside a condition, avoiding it to process in three situations:
However, it seems to be an issue with Orchard Core, because the CMS should not have a "catch all" endpoint/route, does it? When the user tries to access some endpoint that is not handled by any route, it should just not handle it (which will cause, in the end, an error from server-side or, in my case, would leave the request to the "UseSpa" middlewares, as it would be present after "UseOrchardCore" if not being used with "UseWhen" as the example).
The way I think it should be:
That is my point. Hope somebody may enlight me (as I hope there is something I may be doing wrong).
Thanks in advance!