ASP .Net Core Razor: Can't return ViewComponent from my PageModel - razor

I am trying to use Ajax to call a handler in my Razor page that returns the result of a ViewComponent, however when I try the code below, it says:
Non-invocable member "ViewComponent" cannot be used like a method.
public IActionResult OnGetPriceist()
{
return ViewComponent("PriceList", new { id= 5 });
}

When using MVC, the Controller base class includes a ViewComponent method, which is just a helper method that creates a ViewComponentResult for you. This method does not yet exist in the Razor Pages world, where instead you use PageModel as the base class.
One option to work around this is to create an extension method on the PageModel class, that would look something like this:
public static class PageModelExtensions
{
public static ViewComponentResult ViewComponent(this PageModel pageModel, string componentName, object arguments)
{
return new ViewComponentResult
{
ViewComponentName = componentName,
Arguments = arguments,
ViewData = pageModel.ViewData,
TempData = pageModel.TempData
};
}
}
Apart from it being an extension method, the code above is just ripped out of Controller. In order to use it, you can call it from your existing OnGetPriceList (typo fixed) method, like this:
public IActionResult OnGetPriceList()
{
return this.ViewComponent("PriceList", new { id = 5 });
}
The key to making it work here is to use this, which will resolve it to the extension method, rather than trying to invoke the constructor as a method.
If you're only going to use this once, you could forego the extension method and just embed the code itself inside of your handler. That's entirely up to you - some people might prefer the extension method for the whole separation-of-concerns argument.

Related

Testing class which creates objects from other classes

I have a class Component which creates two objects from other classes JMSConnector and AppiaConnector and links both of them.
I have a method that reads the status of both objects and return a combined value.
Here is the code:
Problem I am facing when testing this method is I have no way of injecting mocks of appiaConnector and jmsConnector to test the behaviour of this method.
For example I tried this:
#RunWith(MockitoJUnitRunner.class)
public class ComponentTest {
#Mock
JMSConnector jmsConnector;
#Mock
AppiaConnector appiaConnector;
Component component = new Component();
#Test
public void testGetStatus() {
given(jmsConnector.getStatus()).willReturn(true);
given(appiaConnector.getStatus()).willReturn(true);
// this is what I want... but I can't dynamically inject these mocks into component ???
assertTrue(component.getStatus());
}
}
Is there a way that I can achieve this, or should I use stubs? (which seems an overkill for such a small method)
You can pass them as parameters instead of creating them inside the method.
public Component(AppiaConnector appiaConnector, JMSConnector jmsConnector) {
// do your status check
}
Now if in your production code, you don't want to inject them, you can always create another constructor with no parameters. That way you keep your code the same but gain in testing flexibility
public Component() {
this(new AppiaConnector(), new JMSConnector());
}
Component(AppiaConnector appiaConnector, JMSConnector jmsConnector) {
// do your status check
}

How to call Html.Raw and Html.Partial outside a razor page?

I have this requirements. I need to be able to write this code in my razor views:
#Filters.Render(Filters.DateRangeFilter, new DateRangeFilterParameters { });
The alternative is:
#Html.Partial("/Views/Shared/DateRangeFilter.cshtml", new DateRangeFilterParameters { });
In other words, I want Filters class to wrap Html.Partial. For that reason, I thought of this code:
public class Filters {
public const string DateRangeFilter = "/Views/Shared/DateRangeFilter.cshtml";
public static HtmlString Render(string filterPath, object parameters)
{
// Here I need to call Html.Partail, how?
}
}
To use Html.Raw within the controller you can request the injected IHtmlHelper service. E.g.:
HttpContext.RequestServices.GetService(typeof(IHtmlHelper)) as IHtmlHelper;
Or you can do your own implementation for the helper. And in order to use Html.Partial you need to use IRazorViewEngine, ViewContext, and other stuff. So basically you need to implement a service for that, and here is a good example Render Partial View To String Outside Controller Context.
I don't know if there is an easier way to achieve those, but that is what on my mind.

GET and parameterless GET causing AmbiguousActionException?

I'm trying to have two Web API methods in my controller. One for when GET is called with a MyViewModel object in the header, and one without.
MyController.cs:
[Produces("application/json")]
[Route("api/[controller]")]
public class MyController : Controller
{
[HttpGet]
public IEnumerable<UserModel> Get()
{
// ...
}
[HttpGet]
public IEnumerable<UserModel> Get(MyViewModel viewModel)
{
// ...
}
}
But browsing to the route address in Chrome without passing any MyViewModel gives me this error:
AmbiguousActionException: Multiple actions matched. The following
actions matched route data and had all constraints satisfied:
MyController.Get (MyProject)
MyController.Get (MyProject)
If I comment out the parameterless method and put a break point in the parameterized function and browse to the api URL, it looks like rather than the viewModel being null like I expected, it appears to be a new MyViewModel object made with a parameterless constructor. Seems like it may be relevant to my problem.
I'm running on Microsoft.AspNetCore v1.1.2 and Microsoft.AspNetCore.Mvc v1.1.3.
Add attribute routing to one of them.
For example:
[HttpGet("/myaction")]
public IEnumerable<UserModel> Get(MyViewModel viewModel)
{
// ...
}
Or add it to all of them. MVC can't distinguish two methods because viewModel can be null, and doesn't know if it should match first get action or another.
One for when GET is called with a MyViewModel object in the header, and one without.
Model Binding in ASP.NET Core by default uses query parameters as the source for model population, not headers. If you need to fill MyViewModel from the header, use [FromHeader] attribute:
public IEnumerable<UserModel> Get([FromHeader] MyViewModel viewModel)
ASP.NET Core routing implementation is not using headers for routing resolving. As you are using attribute routing, as #Vlado said, you need to use different Route Name for disambiguating actions.

DotnetCore : OnException method in a custom filter is called two times

I am using dotnet core and have created a custom exception filter to handle exceptions. The problem i face is that in case of exceptions , the onException method in the custom filter is called two times. Below is the code :
public class CustomExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
// Code
base.OnException(context);
}
}
Controller Code is :
[CustomExceptionFilter]
public class MyController : Controller
{
// Raise an exception in any apis
}
Why onException is called two times?
Using Visual Studio I created a new Asp.Net Core Web Application and used the standard Web Application Template. Then I added the CustomExceptionFilter class and added the [CustomExceptionFilter] attribute to the HomeController and threw and exception inside it's Index method:
[CustomExceptionFilter]
public class HomeController : Controller
{
public IActionResult Index()
{
throw new Exception("Time to bail!");
return View();
}
}
Finaly, I set a breakpoint on this line in the CustomExceptionFilter:
base.OnException(context);
And ran the website. The debugger stops on the throw of course, and then it stops on the line with the breakpoint. The breakpoint is only hit once. So my setup verifies the expected behavior.
Troubleshooting
In the past, I have ran into similar situations where something that should only be called once is called twice and it's almost always turned out to be because a second http request came in that I wasn't expecting. So it really was only being called once per http request. One way to check on that is to look at the path and query in the method which you can do as indicated below:
public class CustomExceptionFilter : ExceptionFilterAttribute {
public override void OnException(ExceptionContext context) {
//set breakpoing on the following line to see what the requested path and query is
string pathAndQuery = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
// Code
base.OnException(context);
}
}
Another scenario that can cause this is adding the attribute in two places:
I had this:
config.Filters.Add( new StandardExceptionHandlingAttribute() );
AND this:
[StandardExceptionHandling]
public async Task<int?> ....

Replacement for #helper in ASP.NET Core

So far, i don't think ViewComponent solves that neither does TagHelper. Is there any replacement to this? Something that takes parameters and returns a HtmlString?
I don't see anything harmful with:
#helper foo(string something) {
<div>Say #something</div>
}
var emailbody = classfilenameinAppCodefolder.foo("hello"); //store result in a variable for further processes
For now i believe its a temporary delete before RC. https://github.com/aspnet/Razor/issues/281 and https://github.com/aspnet/Mvc/issues/1130 Well! it better be. I hope someone is working on it. Without #helper, building large HtmlString or 'template' would be a serious pain.
Note: Partial View doesn't seem to do the trick. I think it only renders views not return view to variable.
Secondly, what happened to the App_Code folder?
According to the following Github issue, it looks like #helper is coming back and will be included in asp .net core 3.0.0 preview 4.
https://github.com/aspnet/AspNetCore/issues/5110
UPDATE
Starting in asp .net core 3, you can now define a local function within a Razor code block.
#{
void RenderName(string name)
{
<p>Name: <strong>#name</strong></p>
}
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#razor-code-blocks
Alternatively you can use the #functions directive like this:
#{
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}
#functions {
private void RenderName(string name)
{
<p>Name: <strong>#name</strong></p>
}
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#functions
#{
Func<String, IHtmlContent> foo = #<div>Say #item</div>;
}
I'd like to expand on #Alexaku's answer and show how I've implemented a helper like function. It's only useful on one specific page but it allows you to execute a piece of razor code multiple times with input parameters. The syntax is not great but I've found it very useful in the absence of razor's #helper function. First declare some kind of Dto that will contain the input parameters into the function.
#functions {
private class Dto
{
public string Data { get;set; }
}
}
Then declare the razor function. Note that the displayItem value can be multi-line and also note that you access the Dto variable using the #item.
#{
Func<Dto, IHtmlContent> displayItem = #<span>#item.Data</span>;
}
Then when you want to use the razor template you can call it like the following from anywhere in the page.
<div>
#displayItem(new Dto {Data = "testingData1" });
</div>
<div>
#displayItem(new Dto {Data = "testingData2" });
</div>
For .NET Core 3, you can use local functions:
#{
void RenderName(string name)
{
<p>Name: <strong>#name</strong></p>
}
RenderName("Mahatma Gandhi");
RenderName("Martin Luther King, Jr.");
}
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-3.1#razor-code-blocks
As #scott pointed out in his answer, local functions are finally available as of .NET Core 3. In prior versions one can resort to templated Razor delegates.
But none of the answers addresses the question "what happened to the App_Code folder?" The aforementioned features are local solutions, that is, helper functions defined in these ways cannot be shared between multiple views. But global helper functions could often be more convenient than the solutions MS provide out-of-the-box for view-related code re-use. (Tag helpers, partial views, view components all have their cons.) This was thoroughly discussed in this and this GitHub issue. According to these discourses, unfortunately, there's not much understanding from MS's side, so not much hope is left that this feature will be added any time soon, if ever.
However, after digging into the framework sources, I think, I could come up with a viable solution to the problem.
The core idea is that we can utilize the Razor view engine to look up an arbitrary view for us: e.g. a partial view which defines some local functions we want to use globally. Once we manage to get hold of a reference to this view, nothing prevents us from calling its public methods.
The GlobalRazorHelpersFactory class below encapsulates this idea:
public interface IGlobalRazorHelpersFactory
{
dynamic Create(string helpersViewPath, ViewContext viewContext);
THelpers Create<THelpers>(ViewContext viewContext) where THelpers : class;
}
public class GlobalRazorHelpersOptions
{
public Dictionary<Type, string> HelpersTypeViewPathMappings { get; } = new Dictionary<Type, string>();
}
public sealed class GlobalRazorHelpersFactory : IGlobalRazorHelpersFactory
{
private readonly ICompositeViewEngine _viewEngine;
private readonly IRazorPageActivator _razorPageActivator;
private readonly ConcurrentDictionary<Type, string> _helpersTypeViewPathMappings;
public GlobalRazorHelpersFactory(ICompositeViewEngine viewEngine, IRazorPageActivator razorPageActivator, IOptions<GlobalRazorHelpersOptions>? options)
{
_viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine));
_razorPageActivator = razorPageActivator ?? throw new ArgumentNullException(nameof(razorPageActivator));
var optionsValue = options?.Value;
_helpersTypeViewPathMappings = new ConcurrentDictionary<Type, string>(optionsValue?.HelpersTypeViewPathMappings ?? Enumerable.Empty<KeyValuePair<Type, string>>());
}
public IRazorPage CreateRazorPage(string helpersViewPath, ViewContext viewContext)
{
var viewEngineResult = _viewEngine.GetView(viewContext.ExecutingFilePath, helpersViewPath, isMainPage: false);
var originalLocations = viewEngineResult.SearchedLocations;
if (!viewEngineResult.Success)
viewEngineResult = _viewEngine.FindView(viewContext, helpersViewPath, isMainPage: false);
if (!viewEngineResult.Success)
{
var locations = string.Empty;
if (originalLocations.Any())
locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations);
if (viewEngineResult.SearchedLocations.Any())
locations += Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations);
throw new InvalidOperationException($"The Razor helpers view '{helpersViewPath}' was not found. The following locations were searched:{locations}");
}
var razorPage = ((RazorView)viewEngineResult.View).RazorPage;
razorPage.ViewContext = viewContext;
// we need to save and restore the original view data dictionary as it is changed by IRazorPageActivator.Activate
// https://github.com/dotnet/aspnetcore/blob/v3.1.6/src/Mvc/Mvc.Razor/src/RazorPagePropertyActivator.cs#L59
var originalViewData = viewContext.ViewData;
try { _razorPageActivator.Activate(razorPage, viewContext); }
finally { viewContext.ViewData = originalViewData; }
return razorPage;
}
public dynamic Create(string helpersViewPath, ViewContext viewContext) => CreateRazorPage(helpersViewPath, viewContext);
public THelpers Create<THelpers>(ViewContext viewContext) where THelpers : class
{
var helpersViewPath = _helpersTypeViewPathMappings.GetOrAdd(typeof(THelpers), type => "_" + (type.Name.StartsWith("I", StringComparison.Ordinal) ? type.Name.Substring(1) : type.Name));
return (THelpers)CreateRazorPage(helpersViewPath, viewContext);
}
}
After introducing the singleton IGlobalRazorHelpersFactory service to DI, we could inject it in views and call the Create method to acquire an instance of the view which contains our helper functions.
By using the #implements directive in the helper view, we can even get type-safe access:
#inherits Microsoft.AspNetCore.Mvc.Razor.RazorPage
#implements IMyGlobalHelpers
#functions {
public void MyAwesomeGlobalFunction(string someParam)
{
<div>#someParam</div>
}
}
(One can define the interface type to view path mappings explicitly by configuring the GlobalRazorHelpersOptions in the ordinary way - by services.Configure<GlobalRazorHelpersOptions>(o => ...) - but usually we can simply rely on the naming convention of the implementation: in the case of the IMyGlobalHelpers interface, it will look for a view named _MyGlobalHelpers.cshtml at the regular locations. Best to put it in /Views/Shared.)
Nice so far but we can do even better! It'd be much more convenient if we could inject the helper instance directly in the consumer view. We can easily achieve this using the ideas behind IOptions<T>/HtmlLocalizer<T>/ViewLocalizer:
public interface IGlobalRazorHelpers<out THelpers> : IViewContextAware
where THelpers : class
{
THelpers Instance { get; }
}
public sealed class GlobalRazorHelpers<THelpers> : IGlobalRazorHelpers<THelpers>
where THelpers : class
{
private readonly IGlobalRazorHelpersFactory _razorHelpersFactory;
public GlobalRazorHelpers(IGlobalRazorHelpersFactory razorHelpersFactory)
{
_razorHelpersFactory = razorHelpersFactory ?? throw new ArgumentNullException(nameof(razorHelpersFactory));
}
private THelpers? _instance;
public THelpers Instance => _instance ?? throw new InvalidOperationException("The service was not contextualized.");
public void Contextualize(ViewContext viewContext) => _instance = _razorHelpersFactory.Create<THelpers>(viewContext);
}
Now we have to register our services in Startup.ConfigureServices:
services.AddSingleton<IGlobalRazorHelpersFactory, GlobalRazorHelpersFactory>();
services.AddTransient(typeof(IGlobalRazorHelpers<>), typeof(GlobalRazorHelpers<>));
Finally, we're ready for consuming our global Razor functions in our views:
#inject IGlobalRazorHelpers<IMyGlobalHelpers> MyGlobalHelpers;
#{ MyGlobalHelpers.Instance.MyAwesomeGlobalFunction("Here we go!"); }
This is a bit more complicated than the original App_Code + static methods feature but I think this is the closest we can get. According to my tests, the solution also works nicely with runtime compilation enabled. I haven't had the time so far to do benchmarks but, in theory, it should generally be faster than using partial views as the shared view is looked up only once per consumer view and after that it's just plain method calls. I'm not sure about tag helpers though. It'd be interesting to do some benchmarks comparing them. But I leave that up to the adopter.
(Tested on .NET Core 3.1.)
Update
You can find a working demo of this concept in my ASP.NET boilerplate project:
Infrastructure (relevant files are only those whose name contains GlobalRazorHelpers)
Registration
Helper interface sample
Helper implementation sample
Usage sample
The #helper directive was removed since it was incomplete and its current design did not fit in the new 'ASP.NET 5 way'. One of the reasons is that helpers should be declared in the App_Code folder while ASP.NET 5 has no concept of special folders. Therefore the team decided to temporarily remove the feature.
There are plans to bring it back in the future though. See this and this.
You can easily replace that "feature" with a ViewComponent (and a TagHelper if you want). ASP.NET Core is much more friendly to web designers, and the ViewComponents allow you to write HTML without any (weird to most) razor code.
For example:
Create a SayComponent : ViewComponent class:
public class SayComponent : ViewComponent
{
public void Render(string message)
{
return View(message);
}
}
Create a View file under Views/Shared/Say/Default.cshtml with just
#model string
<div>Message: #Model.</div>
And call it:
#await Component.RenderAsync("Say", "some message")
For a better experience, add this to your _ViewImports.cshtml file:
#addTagHelper *, YourSolutionName
And then you can use it as a tag helper:
<vc:say message="some message"></vc:say>
How about using partials to recreate reusable tags?
MyProject/Views/Shared/_foo.cshtml
#model string
<div>#Model</div>
MyProject/Views/Courses/Index.cshtml
#{
Layout = "_Layout";
}
<div>
<partial name="_foo" model="foo" />
<partial name="_foo" model="bar" />
<partial name="_foo" model="baz" />
</div>