hermanho / postal.aspnetcore

Email sending for asp.net mvc using the view engine system to render emails.
http://aboutcode.net/postal
MIT License
26 stars 9 forks source link

Support generating URLs via UrlHelper #16

Closed ajbeaven closed 1 year ago

ajbeaven commented 3 years ago

When a email razor template generates a link using @Url.Action("Index", "Home"), the following exception is thrown:

System.InvalidOperationException: Could not find an IRouter associated with the ActionContext. If your application is using endpoint routing then you can get a IUrlHelperFactory with dependency injection and use it to create a UrlHelper, or use Microsoft.AspNetCore.Routing.LinkGenerator.
   at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.get_Router()
   at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.GetVirtualPathData(String routeName, RouteValueDictionary values)
   at Microsoft.AspNetCore.Mvc.Routing.UrlHelper.Action(UrlActionContext actionContext)
   at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action(IUrlHelper helper, String action, String controller, Object values, String protocol, String host, String fragment)
   at Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action(IUrlHelper helper, String action, String controller)
   at AspNetCore.Views_Emails_Testing1.ExecuteAsync() in C:\Users\ajbea\source\repos\postal.aspnetcore.samples\WebSample\Views\Emails\Testing1.cshtml:line 9
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Postal.AspNetCore.TemplateService.RenderTemplateAsync[TViewModel](RouteData routeData, String viewName, TViewModel viewModel, Dictionary`2 additonalViewDictionary, Boolean isMainPage)

(this is the stacktrace from a modified version of the samples repo).

I know I can pass in the URL in to the email data, or do as the exception says and inject IUrlHelperFactory/LinkGenerator & IActionContextAccessor in to the view itself, but I was hoping to avoid handling URLs differently to how I do in other Razor templates (most notably, MVC views).

I note that there is an IRoute and RequestPath associated with the Email object; is this how this scenario is supposed to be supported?

I perhaps naively assume that the TemplateService could avoid this issue by using the current ActionContext when constructing the ViewContext that is used to render the Razor (by injecting IActionContextAccessor). I know this wouldn't work in cases where you don't have a context available, but I'm sure overloading a method somewhere to take an ActionContext could be added to support that case.

hermanho commented 3 years ago

Not a easy job. They can in different threading / context between current ActionContext and postal's TemplateService.

It need to cater whole routing inside TemplateService, including endpoint, conventional or attribute routing. The UrlHelper is created based on the matched routing in middleware each time. In postal, the middleware does not re-execute so it does not have enough information to inject the routing and urlhelper.

Ref: https://github.com/dotnet/aspnetcore/blob/90b9d805abbba0626c7ea156445a5640dc4c7bb6/src/Mvc/Mvc.Core/src/Routing/UrlHelperFactory.cs#L51

ajbeaven commented 1 year ago

Is this one actually 'completed'? I'm just checking because I see a bunch of issues closed at the same time. Thanks for your work on this great repo.

hermanho commented 1 year ago

@ajbeaven Yes!! with v3.1.0

https://github.com/hermanho/postal.aspnetcore.samples/blob/b57f31c1148c324d1873361b710c78cc87cd6355/WebSample/Views/Emails/Testing3.cshtml

https://github.com/hermanho/postal.aspnetcore.samples/blob/b57f31c1148c324d1873361b710c78cc87cd6355/WebSample/Controllers/HomeController.cs#L70

ajbeaven commented 1 year ago

Outstanding work. Thanks so much!