Skip to content
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

Attempt to integrate razor view engine #6

Merged
merged 7 commits into from Feb 25, 2017
Merged

Attempt to integrate razor view engine #6

merged 7 commits into from Feb 25, 2017

Conversation

ghost
Copy link

@ghost ghost commented Feb 10, 2017

I hope this closes the #1

I googled about razor engine and F# and found some interesting projects like FSRazor, Shaver and RazorEngine. But none of those is .net core 😢 Then, i found a nice C# implementation based on SO this answer so i decided to use this.

By now you can configure where your views (.cshtmls) are placed: let view = configureRazor "views" or use a default config with let view = defaultRazor (), then you call your view function like view "Person.cshtml" model

There are some pitfalls, like you must use namespaces on your models other way razor can't find the @model on .cshtml.

@dustinmoris
Copy link
Member

Hi,

I wanted to let you know that I saw your pull request and I already started to look into it.

You are right, it seems to be difficult to find a Razor library for .NET Core at the moment. RazorLight looks cool, but I saw a potential issue with it which I raised with the maintainer now.

Apart from that I like what you have done. There's a few things I would suggest to improve (e.g. have IRazorLightEngine as a dependency so we don't have to instantiate it every time) but I will give more detailed feedback when I have more clarity regarding the async/await issue I raised.

Many thanks for helping out here and taking the initiative to resolve #1. I appreciate it a lot and will do my best to get this polished with you before merging.

Just FYI I am travelling tomorrow and will be back on Sunday late night, so might not be able to respond or work on this for the next 2 days, but will get back to it as soon as possible. Many thanks again!

razor (EngineFactory.CreatePhysical(views))

let defaultRazor () = configureRazor ""
Copy link
Member

@dustinmoris dustinmoris Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that instead of having to create the RazorLightEngine every time you could merge all 3 functions to one like this perhaps:

let razorView (viewName : string) (model : obj) =
    fun (ctx : HttpHandlerContext) ->
        let engine = ctx.Services.GetService<IRazorLightEngine>()
        let view = engine.Parse(viewName, model)
        setHttpHeader "Content-Type" "text/html"
        >>= setBodyAsString view
        <| ctx

Here the HttpHandler tries to resolve an IRazorLightEngine from the service provider.

You could create an extension method like this:

type IServiceCollection with
    member this.AddRazorEngine (viewsFolderPath : string) =
        this.AddSingleton<IRazorLightEngine>(EngineFactory.CreatePhysical(viewsFolderPath));

... which then can be used like this:

type Startup() =
    member __.ConfigureServices (services : IServiceCollection) =
        services.AddRazorEngine("C:\\SampleApp\\views") |> ignore

I think this would be more incline with existing ASP.NET Core patterns and it would allow users to configure the engine only once as a singleton or provide a custom implementation.

What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not creating the engine on each call (i think so), those functions configure and default returns a partial applied function with the engine, so, in the app you do let view = razor () and now the engine is created once and you can use it wherever view "foo" model But, however, hey! I like your approach, uses the built in DI and looks more "aspnet friendly" 😸

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah looking back at the code you're right, it was instantiated only once.

"Program.fs"
]
}
},
"embed": [ "**/*.cshtml" ],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using the physical RazorLight engine then the views don't need to be embedded and this line can be removed.

}
},
"preserveCompilationContext": true,
"embed": [ "Person.cshtml" ]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well.

[<CLIMutable>]
type Person = {
Name : string
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only formatting, but to be consistent I'd prefer to stick with this style please:

type Person = 
    {
        Name : string
    }

@@ -35,7 +35,8 @@
"Microsoft.AspNetCore.Hosting": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Newtonsoft.Json": "9.0.1",
"DotLiquid": "2.0.64"
"DotLiquid": "2.0.64",
"RazorLight": "1.0.0-rc1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tested it, but could imagine that the trailing , might cause a json formatting error?

Copy link
Member

@dustinmoris dustinmoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have a look at my comments and what you think of the suggestions. Also the unit tests seem to have failed in the AppVeyor build. If you fix these things I'd be happy to merge.

@ghost
Copy link
Author

ghost commented Feb 16, 2017

hi, sorry for delay, was a busy week. I've updated the implementation following all of your comments on my last commit. Let me know if there is any other issue on this! ❤️

@dustinmoris
Copy link
Member

Hi, thanks! I am currently out but will have a look later tonight!


let combinePaths (path1 : string) (path2 : string) =
Path.GetFullPath(Path.Combine(path1, path2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is probably not needed anymore.

setHttpHeader "Content-Type" "text/html"
>>= setBodyAsString view
<| ctx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some XML documentation to the handler. Example:

/// Parses and compiles a Razor view with the associated model and then writes its content to the body of the response.
/// It also sets the HTTP header Content-Type to text/html.

Copy link
Member

@dustinmoris dustinmoris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Only two minor things to change please and if you could please update the README with this new handler as well?

@ghost
Copy link
Author

ghost commented Feb 23, 2017

Hi again, man, thanks for your guidance and patience on this PR! I've added the comments and updated the readme as you suggested. Let me know if there is any other issue on this!

@dustinmoris
Copy link
Member

Hey no worries. There's been many changes happening since the last merge again, so there are more conflicts again which prohibit me from merging your PR. This weekend I will take everything you've done and update a branch with it and then probably merge it back into dev/master from there if that is okay with you?

@ghost
Copy link
Author

ghost commented Feb 24, 2017

sure!

@ghost
Copy link
Author

ghost commented Feb 24, 2017

Hi! i think i get done the merge with develop branch! 🎉 those was a lot of changes, yay fsproj! I had some troubles, though:

  • I've changed the <TargetFramework> to netstandard1.6 'cause 1.3 was generating the error The target "CreateManifestResourceNames" does not exist
  • I've added the line <EnableDefaultCompileItems>false</EnableDefaultCompileItems> 'cause i was getting the error Duplicate 'Compile' items were included.
  • i've changed the ItemGroup Condition to <ItemGroup Condition=" '$(TargetFramework)' == '*netstandard1.6*' "> 'cause RazorLight doen't support 1.3. isn't there a way to use >= or something like that? à la paket...?

Even though my tests, both of them, main tests and sample app's tests, are broken... can't find the View! i've added a todo line on the sampleapp in order to get some advice... buuut the sample app is working yay!

@dustinmoris dustinmoris changed the base branch from master to develop February 24, 2017 21:20
@dustinmoris dustinmoris merged commit c8245b9 into giraffe-fsharp:develop Feb 25, 2017
@dustinmoris
Copy link
Member

Thanks for the effort!!!

This was referenced Feb 25, 2017
@ghost ghost deleted the 1-razor-support branch April 17, 2017 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant