Razor convetions - c#

I have been working a Razor templeting system but am running into a consistent syntax error. In many of my .cshtml files I am swapping between .cs and .js multiple times on one line of code which causes the intellisense get confused.
Example
<script type="text/javascript" id="dtscript">
///...
#if (!string.IsNullOrWhiteSpace(ColumnDefs))
{
#:columnDefs: #model.ColumnDefs,
}
///...
</script>
In the above line the trailing comma after #ColumnDefs is a syntax error, however when the .cshtml file compiles and I render the template the rendered code is correct. This syntax error holds for alternate ways of generating the code...
#if (!string.IsNullOrWhiteSpace(ColumnOrder))
{
<text>order: #model.ColumnOrder</text>,
}
//or
#if (!string.IsNullOrWhiteSpace(ColumnOrder))
{
<text>order: #model.ColumnOrder,</text>
}
Since the template generates the correct view I have been slow about addressing the syntax error, but I am getting tired of all of the red squiggly lines. So my question is what is the correct way to splice .cs and .js to avoid incorrectly reported syntax errors throughout the razor file.
Update:
Let me expand on this scenario a little. There is no controller, this system is a stand alone library. The templeting system is product agnostic and is part of a Domain Specific Language for common plugins. The #model.ColumnDefs is actually a json object that renders into the following code.
columnDefs: [{"sortable":false,"targets":[0,3]},
{"visible":false,"targets":[0,7]},
{"searchable":false,"targets":[0]},
{"name":"Id","targets":0},
{"name":"Email","targets":1},
{"name":"Name","targets":2},
{"name":"IsAdmin","targets":3},
{"name":"Salary","targets":4},
{"name":"Position","targets":5},
{"name":"Hired","targets":6},
{"name":"Number","targets":7}],
It can not be wrapped in "" or '' otherwise the plugin is not able to parse the code.

You should move most logic to the controller. Which means the line:
#if (!string.IsNullOrWhiteSpace(ColumnOrder))
should be inside the controller:
if(!string.IsNullOrWhiteSpace(ColumnOrder)) ViewBag.Something = ...;
In razor, initiate your desired state as javascript variables:
var something = "#ViewBag.Something"; //this is a javascript line
In my experience, Visual Studio's intellisense work correctly in this case and identifies #ViewBag.Something as a razor syntax (but note the double quotes, they belong to javascript, which encloses the string value).

Currently this syntax error issue is known and marked as Deferred.
https://connect.microsoft.com/VisualStudio/feedback/details/760339/valid-javascript-razor-syntax-marked-as-syntax-error
Using the suggested workarounds are good enough for now.

Related

"The attribute names could not be inferred from bind attribute 'bind-value'" error in Blazor

I've just migrated a Blazor project from Core 3 Preview 6 to Preview 8 and I'm now getting this error:
The attribute names could not be inferred from bind attribute
'bind-value'. Bind attributes should be of the form 'bind' or
'bind-value' along with their corresponding optional parameters like
'bind-value:event', 'bind:format' etc.
I've isolated the component that's causing this to happen, and the code certainly seems to bind-value set as per the instructions in the error message:
<TelerikDropdownList Data="#State.ContainerSizes"
ValueField=#nameof(ContainerSize.ContainerSizeId)
TextField=#nameof(ContainerSize.ContainerSizeName)
#bind-Value="#ContainerSizeIdNoNull"
>
</TelerikDropdownList>
I've tried removing the # from #bind-Value and changing the capitalisation #bind-Valueetc. but all to no avail.
What can be causing this?
It turns out there are at least two causes of this:
1. The component name is now case-sensitive
The answer turns out to be that naming of blazor components is now case-sensitive, and I was missing a capital letter in 'TelerikDropdownList' which should be TelerikDropDownList.
The change to use case-sensitive names is documented here and is also discussed here and in the official documentation here. The idea was to reduce misleading messages, but it's had the consequence of introducing another one, so I've raised an issue for that on the AspNetCore repo.
2. You've forgotten the #using statement for the component's namespace
You'll also get the same error if you've forgotten or removed the #using statement for the component's namespace. That's very easy to do if you're using ReSharper as it is currently flagging them as unused and offering to remove them.
Checking if this is the issue
A good way to check if the compiler has correctly identified your component as a Blazor component rather than a HTML tag is to check the colour coding of the keywords. They will be the same colour if things are working correctly (green in the example below):
Whereas if the name or namespace are wrong you'll see a mix of colours (note that Data and ValueField are now a different colour to TelerikDropdownList):
“The attribute names could not be inferred from bind attribute 'bind-value'” exception in Blazor
I had a similar issue, but the solution was rather easy than intuitive!
Finally I found the information that adding a missing using statement of the used component was helpful. so did I. And it worked!
Despite the component name was shown as green (like it is recognized) it wasn't. Only the missing using solved the problem. This is a missleading behavior.
So if you have the same problem, check if you're missing a 'using' for the actual component even if the component's name is shown in green.
In my case I had following parameters:
[Parameter]
public string[] BindingValue { get; set; }
[Parameter]
public EventCallback<string[]> BindingValueChanged { get; set; }
And the binding:
<MultiselectDropdownComponent
#bind-BindingValue="SessionState.MyArray" />
Was producing the same error as in subject. I had a using statement specified as well..
From the MS documentation:
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-5.0#binding-with-component-parameters
<Child #bind-Year="year" />
The Year parameter is bindable because it has a companion YearChanged event that matches the type of the Year parameter.
By convention, a property can be bound to a corresponding event handler by including an #bind-{PROPERTY}:event attribute assigned to the handler. <Child #bind-Year="year" /> is equivalent to writing:
<Child #bind-Year="year" #bind-Year:event="YearChanged" />
So I decided to explicitly specify the event and it worked!
<MultiselectDropdownComponent
#bind-BindingValue="SessionState.MyArray"
#bind-BindingValue:event="BindingValueChanged" />
edit: using Blazor WASM and .Net 5
I can add another - not obvious pitfall (at least to me).
Fully qualifying the component, and next relying on the using statement to identify the properties does not work. This got added by intellisense.
Not working example:
#using WebUI.Components.Modals
<WebUI.Components.Modals.WebUI.Components.Modals.AssetModal #bind-IsVisible="_assetDialogVisible" Asset="_selectedAsset"></WebUI.Components.Modals.WebUI.Components.Modals.AssetModal>
Working version:
#using WebUI.Components.Modals
<AssetModal #bind-IsVisible="_assetDialogVisible" Asset="_selectedAsset"></AssetModal>
I changed #bind-Value to #bind-value (lowercase) and it worked.
Just to add that I have have been recently struggling with this error when I was writing a Razor Component Library for a Blazor Project. The reason why I was getting the error was a missing #using Microsoft.AspNetCore.Components.Forms in the _Imports.razor file in my library.
Unfortunately the error message is so generic, it took me a fair while to track this down. Better error reporting would make this so much easier.
If you're doing something similar to me and you get stuck on this problem, hopefully this will help!
I imported the library:
#using Syncfusion.Blazor.DropDowns

Why have both _ViewStart and _ViewImports? Why not one file?

In ASP.NET Core MVC we can put a file with the exact name of _ViewStart.cshtml inside a folder to contain the common C# code to be run before every razor view/page in that folder. Something like this:
#{
const string SomeConstant = "some value";
}
Similarly a file with the exact name of _ViewImports.cshtml inside a folder can hold all the common razor directives to be shared among the razor views/pages in that folder. Like this:
#layout _Layout
#using MyApp.Models
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
But here's a question that I couldn't google, no matter how I rephrased it:
Can somebody please explain to me why we have both a _ViewStart.cshtml and a _ViewImports.cshtml to define common code & directives? Why aren't these functionalities (which don't seem to be conflicting with each-other) defined in a single file?
The _ViewStart file
It is used to set up shared-memory (public static variables) across all view files.
For example, the common practice for ViewStart is to set up a default value that you can override for the Layout and the ViewData / ViewBag dictionary.
The _ViewImports file
In this file you can summarize (abstract) all using statements that you commonly use in all your views.
Why to use _ViewImports file for common "using directives" instead of ViewStart?
Because using directives has the scope of the body of the current view file. So, putting #using statements inside ViewStart file won't make them available for any other view file except the body of the viewStart file itself. Therefore, comes the special ViewImports file which is designed to serve this scope extension purpose of the #using statements and other useful things, such as the tag helper, which without this special file, would be repeated inside each view file which violates the DRY (Don't Repeat Yourself) Principle.
One thing that has been overlooked in the other answers is that according to the official documentation:
Code in the _ViewStart.cshtml file will only be run for non-layout pages.
Code in the _ViewImports.cshtml file will be run for both layout and non-layout pages.
I've tested this by moving the default Application Insights JavaScript snippet (the code below) from the imports file to the start file and it causing a build error on my layout page as it can no longer find the defined variable JavaScriptSnippet.
The code I moved:
#inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet
Given this, the difference between the files is probably 'code I want to run everywhere' vs 'code I want to only run for full views', similar to the difference between .bashrc and .bash_profile.
Code that needs to be executed before each page should be placed _ViewStart.cshtml file.
For _ViewImport.cshtml - the contents of this file applied to all the files present in the same folder and subfolder.
So _ViewStart is execution whereas _ViewImport applies its content to each file.
TEST1
Placing both "Layout [Correct]" reference and "using statement[Incorrect]" at _ViewStart will give compiler Error.
TEST2
Placing both "Layout [InCorrect]" reference and "using statement[Correct]" at _ViewImport will not apply _Layout to other pages
As per MSDN ViewImport Support following directives
#addTagHelper, #removeTagHelper: all run, in order.
#tagHelperPrefix: the closest one to the view overrides any others
#model: the closest one to the view overrides any others
#inherits: the closest one to the view overrides any others
#using: all are included; duplicates are ignored
#inject: for each property, the closest one to the view overrides any others with the same property name

Compile error when using local cshtml files in CloudScribe SimpleContent

I'm 'skinning' the blog engine SimpleContent by Cloudscribe, and have copied locally the necessary partial views to give me editorial control over the html. There are two views which give me an error when running, namely ArvhieListPartial.cshtml and CategoryListPartial.cshtml.
The error is the same in both, and not present in any other blog related cshtml pages:
<li>
<a asp-route="#blogRoutes.BlogArchiveRouteName"
asp-route-year="#cat.Key.Substring(0,4)"
asp-route-month="#cat.Key.Substring(5,2)">#cat.Key.Replace("/", "-")(#cat.Value)</a>
</li>
#cat.Key is the error point, the browser reports:
Non-invocable member KeyValuePair<string, int>. Key cannot be used like a method.
I notice that these two partial views are the only ones which have references at the top like this:
#model Dictionary<string, int>
I'm using Visual Studio 2017 version 15.2 (26430.16)
I think it is because you removed the space between these
#cat.Key.Replace("/", "-")(#cat.Value)
the original view has a space there but by removing it razor is interpreting the ( like the beginning of a method signature instead of as literal text as it is intended to be, and since it is just a string property it throws this error because it interprets that you are using it like a method

How to include #Html.Actionlink in C# text string?

While the #Html.Actionlink() helper is very convenient for building <a> elements in the .cshtml files, is it possible to construct them inside C# strings, such that they are subsequently rendered correctly in the HTML output?
For example, if I assign a string variable a value similar to the following:
Book.ReadMore = "Click #Html.ActionLink(\"this link\", \"About\", \"Home\") to read more.";
And then I try to display it (the literal text plus the link) through my .cshtml page, using code similar to:
<p>#Model.ReadMore</p>
All I get in the browser is the whole string exactly as I typed it, including the #Html... etc:
Click #Html.ActionLink("this link", "About", "Home") to read more.
Now, for proper SoC, I know that it's not the best of practices to have HTML stuff included in C# code, but is it at all possible to get the proper <a> link in this scenario, instead of the string itself?
EDIT: More information - This string is just one item in a collection of about 20-30 strings (displayed using a for loop in the View). Only a small handful of those items need a link (which is different in each case). Since, as mentioned above, I agree that it's obviously not good practice to use Razor/HTML in Model code, I'm trying to get a simple approach (if possible) which would give me the flexibility of building the link somewhere at the right place, while still yielding the maintainability of MVC SoC.
There must be a "right" way of doing this, which is simple yet maintainable.
Your model should not contain HTML, that's a view concern and belongs in view code. Probably you should be using a Razor helper.
In your App_Code folder (create one if you don't have one), add a file, ReadMoreHelpers.cshtml:
#helper ReadMore() {
<text>Click #Html.ActionLink("this link", "About", "Home") to read more.</text>
}
Then in any view:
#ReadMoreHelpers.ReadMore()
And that will output what you want. If you insist on putting that property in your view, you could do:
Book.ReadMore = "Click " + #Html.ActionLink("this link", "About", "Home").ToHtmlString() + " to read more.";
Then in your view, make sure you use Raw:
#Html.Raw(Book.ReadMore)
However, I couldn't recommend more strongly that you do not put HTML in your model properties.
I don't think so. The Razor view engine will interpret the ActionLink code during run-time while stuffing it as part of a C# string will be interpreted during compile time.

Convention over configuration in ASP.NET MVC

I am relatively new to ASP.NET MVC, and am very impressed with the clarity of the platform so far. However, there is one aspect that I find uncomfortable.
At first, I accepted the fact that when I say
return View();
I am calling a helper method that returns an ActionResult, and makes some assumptions about which view to present, route values, etc. But lately I have been writing code that looks more like this:
return View("Index", new { id = myID })
because it is immediately clear to me what's happening by reading that single line of code.
Lately I have been struggling with the fact that I can have an Index.ASPX view open on the tabs, and I can't immediately tell where it comes from because the IDE doesn't highlight the current tab in the Object Explorer. I haven't resorted to changing the names of the files to ControllerNameIndex.ASPX, but I do put a title in the view that is more specific. Still, it doesn't help much.
How do you deal with these kinds of ambiguities?
I think you answered your own question.
There's no hard rule preventing you from calling your views very specific names, such as "ListOfFooBars" or "EditFizzBuzz" or "AddNewGeeblup". The naming convention for the default view engine only specifies that there's a folder corresponding to your model name under views, and there's an ASPX or ASPC file under that folder that corresponds to your view name.

Categories

Resources