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
Related
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
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.
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.
I am getting the following error in an editor template of mine, ApplicantAddressMode:
error CS0019: Operator '!=' cannot be applied to operands of type
'Comair.RI.ViewModels.ApplicantAddressType' and
'Comair.RI.Models.ApplicantTypesOfAddress'"}
The type Comair.RI.ViewModels.ApplicantAddressType is nowhere to be found. A global solution search of all files for just the ApplicantAddressType returns no results. I changed the name of the enum in Comair.RI.Models from ApplicantAddressType to ApplicantTypesOfAddress to try and avoid an unintentional match, and cleaned out both 32 bit and 64 bit Temporary ASP Internet Files, and yet the error still persists on this line of the editor template:
#using Comair.RI.Models
#model Comair.RI.ViewModels.ApplicantAddressModel
#Html.ValidationSummary(true)
<fieldset>
<legend>#Model.AddressTypeDescription</legend>
<ul class="form-column">
#if (Model.AddressType != ApplicantTypesOfAddress.Residential)
{
Model.AddressType is declared as:
[ScaffoldColumn(false)]
public ApplicantTypesOfAddress AddressType { get; set; }
I am at my wits end about to start throwing flaming, pointed flags around, like IsResidentialStreetNumber and IsPostalSuburb. I don't think the ambulances will arrive long after that and take me away to peace.
The enum declaration is like this:
namespace Comair.RI.Models
{
public enum ApplicantTypesOfAddress
{
Residential,
Postal
}
}
The tpe that was suspected missing was declared in a file not included in the project. When you tell VS to build view at compile time, which in normally only does just before rendering them, not when it builds the application assembly, it builds all views, and uses any files they refer to whether they are included in the project or not.
Jim Lamb has a good post on how to do this called Turn on Compile-time View Checking for ASP.NET MVC Projects in TFS Build 2010
I don't know about the TFS Build part though. I don't use that, in my case simply adding the following element to my .csproj file did the trick. Note, if the MvcBuildViews element already exists, it's text value must be true.
<PropertyGroup>
<MvcBuildViews>true</MvcBuildViews>
</PropertyGroup>
Caveat Emptor.
I am using an MVC3 WebGrid, and want to add custom attributes of the form "data-xxx" to the <table> element rendered by WebGrid.GetHtml().
I tried to do this as follows:
grid.GetHtml(...
htmlAttributes: new { data_xxx = "value" }
);
However this renders as:
<table ... data_xxx="value">
instead of the expected:
<table ... data-xxx="value">
I.e. unlike other MVC helpers, it doesn't replace the underscore in the property name with a hyphen.
After a bit of spelunking with Reflector it seems that:
WebGrid.GetHtml() calls System.Web.WebPages.Html.ObjectToDictionary to convert the anonymous attributes object to a dictionary. Unlike the method System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes used by other helpers, this doesn't replace "_" by "-".
Unlike most other MVC helpers, WebGrid.GetHtml() does not have an overload that takes an IDictionary<string, object>, so I can't work around the bug by specifying the attributes in this way.
This looks like a bug to me, and I've submitted it to Connect:
http://connect.microsoft.com/VisualStudio/feedback/details/767456/webgrid-gethtml-helper-does-not-allow-data-attributes-to-be-set
Any suggestions for a workaround? Or is it fixed in MVC4?
Thanks for reporting this issue:
This was fixed yesterday in CodePlex by resolving issues 610 & 575.
https://aspnetwebstack.codeplex.com/workitem/610
https://aspnetwebstack.codeplex.com/workitem/575
You can try out the nightly bits.