Why have both _ViewStart and _ViewImports? Why not one file? - c#

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

Related

Referencing enum by shorthand from .Master file

We have a structure roughly as below:
namespace a.b.c.d.e {
Master file
namespace f {
Page file
Enum Colours
Enum Fruits
}
}
The master file is made up of 3 files: .Master, .Master.cs & .Master.designer.cs.
Several functions within Page refer to Colours or Fruits. Within the .Master.cs file I have using a.b.c.d.e.f - this should mean that I can reference Colours & Fruits without fully referencing them. This works as expected in the .Master.cs file - however putting <%=Page.GetTitle(Colours.Red)%> in the .Master file does not work. It just says "The name 'Colours' does not exist in the current context". Instead I have to put <%=Page.GetTitle(a.b.c.d.e.f.Colours.Red)%>.
Where am I going wrong? If Colours.Red works fine in the .Master.cs file, surely it should work in the .Master file?
As per this question you can move the enum outside the namespace so you don't have to reference it in your code. Otherwise, you need to keep using the full qualified name of the enum.

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

Razor convetions

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.

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