Syntax Error Passing Razor Markup As Parameter - c#

I'm trying to write a custom control and associated html helper that can have it's output changed by providing a template. I've found examples of this on the web (example) but keep hitting upon a strange syntax error that I don't understand.
Here's a simplified version of what I've got so far:
Method signature that takes the razor template
public IHtmlString Template<T>(Func<T, object> template) {
// Implementation not important
}
Calling the method in my view:
#Html.Control(Of Integer)().Template(#<div>#item</div>)
However, I keep getting a syntax error which is showing on the closing bracket of the Template method. I can't figure out what I'm doing wrong here nor could I find any similar problems else where. Does anyone have any ideas what's going wrong here?
Also yes I realise that the helper is written in c# and the razor is in vb sadly that's the way things are round here.

Related

Call Component.InvokeAsync() from within an IViewComponentHelper extension method

I have a solution which has both an ASP.NET Core 3.1 web application project as well as a Razor Client Library (RCL) project. I am trying write a view component which will be distributed with the Razor Client Library, but can be referenced from the ASP.NET Core web application.
I can successfully render this ViewComponent when I call the InvokeAsync() Tag Helper on the web application's _Layout.cshtml:
#await Component.InvokeAsync("NavBar", new {})
But I want to make it so that the RCL is not dependent on a string name provided on the web application. Instead, I would like to call the ViewComponent via an extension method like this:
#{ await Component.RenderMyViewComponentAsync(); }
To me, this way is more beneficial to whoever will use this shared RCL Library, as they don't need to specify the exact name of the ViewComponent, and can rely on IntelliSense to complete the extension method.
I have created a helper class that takes the ViewComponent object and simply calls its InvokeAsync() method within an extension method:
public static class PartialHelper
{
public static async Task<IHtmlContent> RenderMyViewComponentAsync(this IViewComponentHelper vcHelper)
{
return await vcHelper.InvokeAsync("NavBar", new { });
}
}
And, of course, inside of NavBarViewComponent.cs, I have implemented the InvokeAsync() method:
public class NavBarViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
return View();
}
}
Here's the problem, though: I'm not seeing my view render on the screen from the latter method, even though both ways of doing it will still hit my NavBarViewComponent.InvokeAsync().
From what I see, both ways of returning the ViewComponents are functionally equivalent and use the same methods, except #{ await Component.RenderMyViewComponentAsync(); } goes through a helper function first.
I've tried debugging both ways, but to no avail. I am stuck!
Is there any way to achieve what I'm asking for here? If any clarification is needed, please ask.
Your extension method is actually working exactly like it should. The issue is in how you're calling it from your _Layout.cshtml. You're using the following syntax:
#{ await Component.RenderMyViewComponentAsync(); }
That treats it as though it's within a script block, instead of rendering the IHtmlContent to the view. The IHtmlContent is correctly returned, but it isn't assigned to anything or written to the output.
Instead, you simply need to use the same style syntax you used when calling InvokeAsync():
#await Component.RenderMyViewComponentAsync()
Then this will render exactly like you're expecting.
Had the same issue with the extension method, so thank you for the answer.
One addition - instead of using a string, use the generic to catch errors at compile time:
await Component.InvokeAsync<NavBar>();
or directly from the controller:
public IActionResult Index()
{
return ViewComponent(typeof(NavBar));
}
In addition to answering your specific question, I also want to answer the question behind your question. Your general objective seems to be the simplification of the syntax, without relying on strings which don't provide any design- or compile-time validation.
I really like your approach of using extension methods as a solution for this, and will likely borrow that myself. But another approach that solves a similar objective is to invoke your ViewComponent as a Tag Helper:
<vc:NavBar />
This gives you basic IntelliSense support in Visual Studio, and is much cleaner than the InvokeAsync() approach.
That said, it's worth noting that the Tag Helper approach does have a couple of critical limitations, as I've discussed in a previous answer:
It doesn't support excluding any optional parameters you may have defined on your InvokeAsync() method*.
Neither Visual Studio nor the compiler will provide (obvious) warnings if you're missing any parameters.
Given that, your approach of using extension methods may still be preferable if your view components have any parameters—and, especially, if any of those parameters have logical defaults.
*Note: As of ASP.NET Core 6 Preview 6, View Components invoked as Tag Helpers will now honor optional parameters (source).

Use a variable to reference a partial view's location

I am new to asp.net core and am trying to render a partial view in an ASP.Net Core application. The address of the partial view is determined at run time. I have constructed a view model which is parsed into the view from the controller and contains the desired file address.
The following code throws the following compiler error:
#Html.Partial(Model.File);
Error CS1973 'IHtmlHelper' has no applicable method named 'Partial'
but appears to have an extension method by that name. Extension methods
cannot be dynamically dispatched. Consider casting the dynamic arguments or
calling the extension method without the extension method syntax.
Any help would be greatly appreciated.
The solution in the end was to specify the type in the Razor file. The following line worked:
#Html.Partial((string) Model.File)
Just to add to Lachlan Fergusson's excellent answer (thank you!) just to say that you also get this message if the name of your view contains a variable, without it's type.
So, the following line threw the error for me:
#Html.Partial("UserDetailsPartial." + language, Model)
...but it went away when I added this...
#Html.Partial("UserDetailsPartial." + (string)language, Model)
Behind the scenes, I had different partials based on language,
UserDetailsPartial.es.html
UserDetailsPartial.de.html
UserDetailsPartial.fr.html
The strange thing is that previously (with an earlier version of .Net Core?) the original line worked fine.
So, add this to Microsoft's "list of error messages which don't really explain what the problem is.."

What does an #functions code block in a razor file do, and when (if ever) should I use it?

Whilst looking at a theme I downloaded from the Orchard CMS gallery, I noticed that a Layout.cshtml file had this block of code at the top of the file:
#functions {
// To support the layout classifaction below. Implementing as a razor function because we can, could otherwise be a Func<string[], string, string> in the code block following.
string CalcuClassify(string[] zoneNames, string classNamePrefix)
{
var zoneCounter = 0;
var zoneNumsFilled = string.Join("", zoneNames.Select(zoneName => { ++zoneCounter; return Model[zoneName] != null ? zoneCounter.ToString() : ""; }).ToArray());
return HasText(zoneNumsFilled) ? classNamePrefix + zoneNumsFilled : "";
}
}
I know what the declared function does (calculates which zones are populated in order to return the width of each column), my question is- what is the correct use of the #function block, and when should I ever use it?
The #functions block lets you define utility functions directly in the view, rather than adding them as extensions to the #Html helper or letting the controller know about display properties. You'd want to use it when you can meet these conditions:
The functionality is tied closely to the view and is not generally useful elsewhere (such as "How wide do I make my columns").
The functionality is more than a simple if statement, and/or is used in multiple places in your view.
Everything that the function needs to determine it's logic already exists in the Model for the view.
If you fail the first one, add it as a #Html helper.
If you fail the second one, just inline it.
If you fail the third one, you should do the calculation in your controller and pass the result as part of the model.
Others have explained what #functions does so I won't rehash that. But I would like to add this:
If your view is typed to a viewmodel, I think a viable option would be to move this logic into the viewmodel to avoid cluttering your markup with too much code. Otherwise your views start to look more and more like classic ASP and I don't think anybody wants that.
I don't think there's anything wrong with using #functions or #helper in your view, but once you get beyond a couple of methods in your view, or even if the function is somewhat complicated, it might be worth refactoring to the viewmodel if at all possible. If it's code that can be reused, it may be a good idea to to pull it out into a helper class or an extension to the HtmlHelper class. One thing that is a bummer is realizing you just rewrote a piece of code that already existed because you didn't know it was hidden away in some arbitrary view.
From msdn blogs, #functions block is to let you wrap up reusable code, like the methods and properties
In this particular case, the people who have created the theme you are using probably were trying to keep it as a simple theme (only views, css and images).
If you need to write some code for a theme for Orchard, you have to turn to a module (as stated here: http://docs.orchardproject.net/Documentation/Anatomy-of-a-theme) unless you write this code in the view.
I am not sure it is worth the time to switch from a theme to a module only to get the size of a column.

MVC 3 html helper with a template

I am trying to make a combination of a html helper and a Reder CSHTML.
In other words, how can I use a cshtml "template" with a html helper so I do not need to parse all the ugly HTML in the methods.
I am currently using #Html.Action but that is not preferable as it needs a working URL.
#Html.RenderAction("GetThreads", "Forum")
public ActionResult GetThreads()
{
return new EmptyResult();
}
This gives the exception:
Argument 1: cannot convert from 'void' to 'System.Web.WebPages.HelperResult
Do I always need to add a route in the Global.asax file? Or are there ways to call Actions without it (like HTML helpers, but with a template file).
I have been trying with RenderPartial, but I keep getting these errors: Argument 1: cannot convert from 'void' to 'System.Web.WebPages.HelperResult'
Surround it with #{}
#{Html.RenderPartial("_SomePartial");}
Use #Url.Action to get the URL of an action. I think that is what you need.
Or #Html.RenderPartial or #Html.RenderAction will spit out the view to the page.
EmptyResult won't render a view. That might be your problem.
You might be looking for RenderPartial.

ModelState.AddModelError on form level with ASP.NET MVC 2 RC

In the previous versions on ASP.NET MVC, you could use ModelState.AddModelError("**_FORM**", "error") to make an error on the form-level instead of a property-level. This seems not to work anymore with the new RC of ASP.NET MVC 2.
Is there another way how to do it? I use <%= Html.ValidationSummary() %> at my View-file...
I personally use AddModelError("" - I don't know where does _FORM come from? - and ValidationSummary() is no black magic - I use my own helper instead that, for example, includes specific (user-specific) exceptions messages into output. The helper is very easy to write - a simple foreach loop over ModelState - so you may consider writing your own one. There you're free to show form-level, property-level, or whatever you like.
the error are inside modelstate and is send to the helper class of validationsummary by following argument:
this.ViewData.ModelState.Keys
this.ViewData.ModelState.Values

Categories