YSOD when passing complex type as razor model in NancyFX - razor

I'm getting a YSOD when sending a model of type IEnumerable<string> to my Razor view in NancyFX. Everything works well if supply a string as the model, with the relevant #model statement in the view, so its working.
The error is
Unable to discover CLR Type for model by the name of System.Collections.Generic.IEnumerable. Ensure that the model passed to the view is assignable to the model declared in the view.
What have I missed?
View.cshtml
#model System.Collections.Generic.IEnumerable<System.String>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h1></h1>
#foreach (var item in Model)
{
<h3>#item</h3>
}
</body>
</html>
The Module
public class MyModule: NancyModule
{
public MyModule()
{
Get["/"] = parameters => View["View", this.GetModel()];
}
private IEnumerable<string> GetModel()
{
return new[] { "one", "two" };
}
}

The problem seems to be the #model directive isn't supported in Nancy. Swapping the #model for an #inherits with the correct type fixes the issue:
#inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<My.ViewModels.WhateverViewModel>

In addtion to Greg B's answer, #model is nonetheless a reserved term in the RazorEngine for Nancy, even though this isn't clear from the Nancy Razor View Engine page.
So, you can't declare a variable with the name model and reference it with #model.Property for instance; the view engine will still try to bind it to the model, even if that doesn't actually work (Razor View Engine line 354) and you'll get the same error.

Related

Pass ViewBag value to PartailView MVC

My ViewBag value return empty when passing through PartialView.
CONTROLLER
[HttpGet]
public ActionResult Name()
{
ViewBag.Name = "Bob";
return PartialView();
}
View for above Name.cshtml
#{
<p>Name is: #ViewBag.Name </p>
}
Parent view Index.cshtml
#Html.Partial("Name")
When render it shows Name is: (blank) instead of Name is: Bob. I am still fairly new with MVC. Thank you for your help.
Please close this thread. I used #Html.Action("action","controller") and it solved my problem. Thank you.

Why does an empty list that is passed to a razor view in ASP.NET MVC Core become null?

Imagine a controller action like:
public async Task<IActionResult> Ask()
{
...
ViewData["MyItems"] = Enumerable.Empty<MyItem>();
return View();
}
And a view like:
#{
var myItems = ViewData["MyItems"] as IEnumerable<MyItem>
}
#foreach (var item in myItems) <-- this throws null reference exception
{
<div>#item.SomeProp</div>
}
I forgot to add the following import statement to my view (or to _ViewImports.cshtml) that provides the IEnumerable:
#using System.Collections.Generic;
I am using Visual Studio Code and it does not have intellisense on Razor views to warn me about it. You be careful out there; Razor views without intellisense is like walking in a cemetery at night!

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>

Model binding with a child object

I have a class:
public class Application
{
....
public Deployment NewDeployment { get; set; }
....
}
I have an editor template for Deployment within the Application View folder.
The ApplicationViewModel has a SelectedApplication (of type Application), in my Index.cshtml where I use ApplicationViewModel as my Model, I have this call:
#using (Html.BeginForm("Create", "Deployment", new { #id = Model.SelectedId,
q = Model.Query }, FormMethod.Post, new { id = "form", role = "form" }))
{
#Html.EditorFor(m => m.SelectedApplication.NewDeployment)
}
Which then correctly renders out the control in my DisplayTemplates\Deployment.cshtml (though, it may just be pulling the display code and nothing in relation to the NewDeployment object's contents). All is well in the world until I go to submit. At this stage everything seems good. Controller looks like:
public class DeploymentController : Controller
{
[HttpPost]
public ActionResult Create(Deployment NewDeployment)
{
Deployment.CreateDeployment(NewDeployment);
return Redirect("/Application" + Request.Url.Query);
}
}
However, when it goes to DeploymentController -> Create, the object has nulls for values. If I move the NewDeployment object to ApplicationViewModel, it works fine with:
#Html.EditorFor(m => m.NewDeployment)
I looked at the output name/id which was basically SelectedApplication_NewDeployment, but unfortunately changing the Create signature to similar didn't improve the results. Is it possible to model bind to a child object and if so, how?
Your POST action should accept the same model your form is working with, i.e.:
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
Then, you'll be able to get at the deployment the same way as you did in the view:
model.SelectedApplication.NewDeployment
It was technically an accident that using #Html.EditorFor(m => m.NewDeployment) worked. The only reason it did is because the action accepted a parameter named NewDeployment. If the parameter had been named anything else, like just deployment. It would have also failed.
Per Stephen Muecke's comment and with slight modifications, I was able to find how to correct it:
[HttpPost]
public ActionResult Create ([Bind(Prefix="SelectedApplication.NewDeployment")] Deployment deployment)
{
// do things
}

MVC Razor syntax: # followed by HTML

I came across this code today and don't really understand it. Please could someone tell me what this means and how to interpret it? I have simplified it but it's basically the # symbol followed by some HTML.
The call is:
#Html.Tmpl(#<p>text to display</p>)
The function is:
public static HelperResult Tmpl<TModel>( this HtmlHelper<TModel> html, Func<HtmlHelper<TModel>, HelperResult> template )
{
return new HelperResult( writer => template( html ).WriteTo( writer ) );
}
Please enlighten me. Thank you.
This is an example of what is known as a Templated Razor Delegate. Quite simply, it is a type of HTML helper which accepts a block of Razor template code which can be used to compose the result of a complex operation.
A simple use case might be an Html.List(data, template) method which accepts a list of records and a template for each row of data. The template markup is a delegate which can be invoked and passed a model within the helper's logic.
public static HelperResult List<T>(this IEnumerable<T> items,
Func<T, HelperResult> template) {
return new HelperResult(writer => {
foreach (var item in items) {
template(item).WriteTo(writer);
}
});
}
Phil Haacked goes into more detail here: http://haacked.com/archive/2011/02/27/templated-razor-delegates.aspx.