New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How does one integrate F# Giraffe with Orchard Core MVC rendering? #7141
Comments
Oh, I think I finally found it. // Get module assets which are razor files.
var assets = module.Assets.Where(a => a.ModuleAssetPath
.EndsWith(".cshtml", StringComparison.Ordinal));
if (assets.Any())
{
var asset = assets.First();
var index = asset.ModuleAssetPath.IndexOf(module.Root, StringComparison.Ordinal);
// Resolve the physical "{ModuleProjectDirectory}" from the project asset.
var filePath = asset.ModuleAssetPath.Substring(index + module.Root.Length);
var root = asset.ProjectAssetPath.Substring(0, asset.ProjectAssetPath.Length - filePath.Length);
// ...
// Add the module project root.
roots[module.Name] = root;
} I'll try giving this a shot I guess, though I need to figure out how to grab just my module's info... Edit: Looks like Edit 2: Well, looking at the file providers doesn't directly seem to help because they all attempt to serve files from virtual directories within the OC ApplicationContext (that is, the context's Edit 3: I tried injecting |
Yes, you are in the right place, look at the properties of application context and of a given module, and then its assets that have physical and virtual paths. Yes, we have physical and virtual paths, the virtual ones are related to the files that are embedded in the assembly of a given module, we not only embedd razor files but also liquid files, json files and so on. In Development mode we use physical files for razor views, so that at runtime we can re-compile them on change, in Production mode we only use embedded files and their virtual paths. Normally in a prod env the razor views physical files of a given module don't exist anymore. So, even with another engine you may need to use both physical and virtual paths as we do. If at runtime you don't need to re-compile a razor view on change, you would just have to use virtual paths. To retrieve the virtual path of a given module
In dev mode, to map it to a physical path you need to use the module assets collection as we do |
@jtkech Ok, thanks for the feedback! Seems relatively complex though, and I've seen other intriguing things that I have to keep note of that the MVC framework seems to just do automatically. I might need to look into the possibility of having declared Giraffe routes emulate controllers and the like, so that I can slip Giraffe HttpHandler logic into the MVC pipeline / masquerade it as such rather than attempting to replace that pipeline with custom Giraffe logic. Hmmm. We'll see if this works first. |
Looks like Giraffe.Razor just gets the IoC injected
Yup, when I switched to injecting
Nevermind, I looked more into the Giraffe.Razor's middleware code, and then I stumbled upon this genius gem: namespace Giraffe.Razor
[<AutoOpen>]
module Middleware =
open Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.FileProviders
type IServiceCollection with
member this.AddRazorEngine(viewsFolderPath: string) =
this.Configure<MvcRazorRuntimeCompilationOptions>(fun (options: MvcRazorRuntimeCompilationOptions) ->
options.FileProviders.Clear()
options.FileProviders.Add(new PhysicalFileProvider(viewsFolderPath))).AddMvc().AddRazorRuntimeCompilation()
|> ignore
this.AddAntiforgery() Why on earth you would choose to clear all existing file providers before adding your own, I have no clue. But, once I implemented my own version of this same middleware that omits that line of configuration, my views suddenly started working! Thanks again for all your help! |
Welp, nevermind. I got a little ahead of myself. If I take out the clear operation and then put a Looks to me like it's actually getting to the F# logic, but the F# code can't seem to find the file for some reason (it's just passing the requested viewName to the engine). That error message is displayed when the configured view engine doesn't find the view here. There's gotta be something about the way the file providers are configured that is making it miss it. Is there perhaps a way for me to confirm exactly where the IRazorViewEngine is looking, so that I can explicitly check where the paths aren't matching up? Edit: I read on this StackOverflow question that if you are using file paths, you have to provide the actual file name, but when I specify |
It probably has something to do with the Themes in OC. Your views will work if they are Shared because they will be accessible in any module / themes of any project . But here, when you put your Views in the /Views folder it doesn't find it because it's contextual to the module and/or theme you are actually using in your Tenant. I'm just saying without looking in the code too much. I would need to analyze this more to be honest. Maybe hopefully @jtkech will have a more detailed answer for you 😄 Edit : Keep posting your progress. |
I wonder if I can get approval to share a minimum version of my project without any proprietary stuff...will ask about that, since giving you guys an example project might make it easier to understand what I'm struggling with...? Not that I know whether or not anyone here even uses / understands how to read/write F#, lol. It's pretty under-represented in the .NET community, from what I understand. |
Oh, well, crap. According to this, even if I do manage to get the views inside the project to work, none of the intellisense and tooling even works in F# projects. -_- I hadn't tested that yet and assumed it would be the same. What a waste of time going down that rabbit hole. Guess I'll create a C# Razor Class Library to define the views and see if Giraffe.Razor can pick up those views (since the docs say that is an option rather than creating an explicit views directory path). Really annoying though...Makes me wanna write up a whole F# Fable library for doing OrchardCore stuff so I don't have to deal with it, but who has the time for that? :-P Oh well. I'll write back here to let you know if I can get class libraries to work. |
Well, I'm thinking that I don't necessarily want to have to write up a separate class library every time I want to put together views for a module (2 projects per feature that must be paired together is just nonsense). So, I can think of only 2 remaining alternatives.
Of the two, option 2 seems much more realistic, but I don't know if it'll even work. Need to know if F# projects approve .liquid files / whether I can get valid syntax highlighting and autocompletion (if there even is any?). And if I do go down that route, I also have to figure out how to find the files and load them in a module (same issue I had before). In contrast, if I go with option 1, I'm guaranteed to "find my template" because the template code is directly written in F# code (and I can use all of the existing Fable libraries to help me), and I also get proper syntax highlighting, autocompletion, and other IDE features associated with Visual Studio (not to mention the language features of F# at my disposal). Regardless, I've exhausted all of the time I can allocate to this task during work hours, so the remaining stuff will probably all need to be done in my off time. It's a shame that OC, and MVC in general, is so antagonistic to F# workflows, but I can't exactly blame it since the two just aren't inherently very agreeable. I've already got a WIP Giraffe.Fluid library on my GitHub account so when I'm done with that, I'll try testing out option 2 in OC, but chances are, I won't be satisfied with that and will need to start option 1. But I'll keep updating this Issue as I move along, so the community is updated. |
Option 2 will only give you the Fluid templates working without the TagHelpers I think. We do add TagHelpers in different modules of OC on top of the Fluid implementation. So we extend Fluid functionalities in some modules. But normally, if our modules that extend Fluid functionalities are loaded in your project the taghelpers should then work. Just like the Razor ones. |
@Skrypt Well, for option 2, I'm just going to be adding Giraffe HttpHandlers that find a ".liquid" file template, load its text, and then use DI to find some sort of ITemplateContextFactory (or something similar) that can build me a TemplateContext from OC's modules, at which point, I can pass in my string, get the generated output, and pass that output to the handler for sending in the response. Just have to figure out the middle section to make sure I get all the right module support from OC when generating the output. |
Brief status update. I just recently managed to get a baseline Giraffe webserver finding a local .liquid file and rendering it with Fluid, so yay! Gonna refine the API more, but it's working! Next steps...
Edit: I'm now back where I started with the app needing to find the Liquid file at runtime properly, so I'm going to try investigating the physical-path-during-development-and-virtual-path-in-production workflow in OrchardCore source code to see how that works. Thanks for all the help/explanation of that @jtkech. I'll post here again if I have further questions about it. |
So, looking at the OrchardCore source code, it seems as though the LiquidViewsFeatureProvider goes out, finds all .liquid files in each application module from the IFileProviders, and registers them as "LiquidPage" views in the MVC framework. That way, if someone tries to do Rather than trying to manually fetch a FluidTemplateContext for the appropriate endpoint and dealing with physical paths vs virtual paths with embedded files, it seems like it would be simpler to just find a way to forcibly look up the MVC Razor view and let the ILiquidViewsFeatureProvider do the work of connecting my F# project's .liquid files with the requested view. So, I would actually still be using the Giraffe.Razor API, but I would adapt to not give a crap about the actual file path...somehow. I'll have to look more into how the actual MVC API works to see if I can adapt it. Like, if you just had a non-MVC project with no Controller, how would you take an incoming HttpRequest, generate a simple local Model, pass it to a requested View, get the generated output, and write that output to the HttpResponse? I think that's the kind of low-level operation I'm gonna have to figure out. Edit: and I've already confirmed that my own F# project with a .liquid file in the |
Trying to render Razor views using F# Giraffe.Razor. I can register endpoints to the EndpointRouteBuilder in my StartupBase-derived Startup class, but the library seems limited to actually rendering .cshtml files either from Razor class libraries or from an absolute file path.
Unfortunately, if I use
IWebHostEnvironment
orIHostEnvironment
to get theContentRootPath
, this will provide me a path to the OC web app, not the module.What would be the correct procedure for getting the absolute file path to the module project, so that I might provide a
Views
path to my rendering logic? Or, is there something else I'm missing that I should be doing?Edit: I looked through the docs as best I could, but I didn't see any mention of how to get this information. Closest I found was the
Shells
documentation that talked about how to configure things from an appsettings in the local folder. There is information on how to add MVC support to your module in theModules
docs, but that kinda does all the work for full MVC rather than just letting me use the Razor view engine. I don't have any Controller classes or anything like that in F#, so I don't think that solution would work.Edit 2: I see that I can fetch the
IExtensionManager
and that it provides ways to fetch info on extensions which themselves store info on asubPath
, but it looks like its a relative path forArea\<extensionId>
.Edit 3: None of these StackOverflow approaches seem to work since every time I get the executing assembly's location/codebase, it always returns something related to the OC web app rather than the module (likely because the modules only provide class definitions that are pulled in to the main app?). So, no solutions found there either.
The text was updated successfully, but these errors were encountered: