Say I want to write an HTML helper to draw a chart.
This helper would need to emit specific JS script code into the body of the page it was called from.
Is this recommended ?
If not how would i workaround that ?
Can i tell the helper to emit the code into a specific section defined in the main layout ?
This should do it:
public static class HtmlHelperExtensions
{
public static MvcHtmlString HelloWorld(this HtmlHelper helper)
{
return new MvcHtmlString("alert('Hello World');");
}
}
In Your Razor View:
<script type="text/javascript">
#Html.HelloWorld();
</script>
You probably should not be doing this.
I would consider JS front end content that forms part of your view. Your view should simply display your view model.
I would be interested in knowing why you feel you need to be able to deliver JS in this way?
Related
We are working on an MVC application. As per requirement, we need to populate a VIEW using a method. I have created a sample application. You can see the method which is using to generate control in Helper.cs class under “Controller” folder. When we directly placing the #Html.TextBox("SampleTextBox") code in the view it is rendering correctly. But when we generate the same code using a method, it is not rendering properly and it is showing as a plain string.
If anybody has any idea regarding this please let us know, it would be very helpful.
Sample code
Instead of adding the following code directly to view, we need to populate it using a method.
#Html.TextBox("SampleTextBox")
That is some thing like
#Html.Raw(Helper.GetStringCode())
Method:
public static string GetStringCode()
{
return "#Html.TextBox(\"SampleTextBox\")";
}
You could add a partial view for the Kendo Grid in the Shared folder and add an Html helper extension that returns your partial view:
Partial view Shared/_KendoGrid.cshtml:
#model CustomViewModel
#(Html.Kendo().Grid()...)
Html helper method:
public static MvcHtmlString KendoGrid(this HtmlHelper helper, string header)
{
return helper.Partial("Shared/_KendoGrid", new CustomViewModel { Header = header });
}
In your .cshtml:
#Html.KendoGrid("Custom header")
I have a .csHtml-razor file with a javascript function that uses the #Url.Content C# function inside for the ajax URL.
I want to move that function to a .js file referenced from my view.
The problem is that javascript doesn't "know" the # symbol and doesn't parse the the C# code.
Is there a way to reference .js files from view with "#" symbol?
You could use HTML5 data-* attributes. Let's suppose that you want to perform some action when some DOM element such as a div is clicked. So:
<div id="foo" data-url="#Url.Content("~/foobar")">Click me</div>
and then in your separate javascript file you could work unobtrusively with the DOM:
$('#foo').click(function() {
var url = $(this).data('url');
// do something with this url
});
This way you could have a pure separation between markup and script without you ever needing any server side tags in your javascript files.
Well I've just found a razor engine on nuget that does it! Meaning solves # syntax!
It's name is RazorJS.
The Nuget package
2016 Update:
The package wasn't updated for 5 years, and the project site link is dead. I do not recommend people to use this library anymore.
One way to tackle the problem is:
Adding a partial view with the javascript functions to the view.
This way you can use the # symbol and all your javascript functions are separated from the view.
You have two options:
Use the value as a parameter in the function and wire-up in the view
Create a namespace (instead of public level variable which is considered bad practice in JS) and set this value at the top of the page and then use it in your js
For example:
var MyCompany =
{
MyProject: {
MyVariable:""
}
};
And then in your view, set it:
MyCompany.MyProject.MyVariable = #....
UPDATE
You might wonder none is any good because of coupling, well it is true, you are coupling js and view. That is why scripts must be oblivious to the location they are running in so it is a symptom of non-optimum organization of files.
Anyway there is a third option to create a view engine and run the js files against the razor and send the results back. This is cleaner but much slower so not recommended either.
In order to get the # variable into your .js file you'll have to use a global variable and set the value of that variable from the mvc view that is making use of that .js file.
JavaScript file:
var myValue;
function myFunc() {
alert(myValue);
}
MVC View file:
<script language="text/javascript">
myValue = #myValueFromModel;
</script>
Just be sure that any calls to your function happen AFTER the value has been set by the view.
Probably this is not the right approach. Considering separation of concerns. You should have a data injector on your JavaScript class and which is in most cases data is JSON.
Create a JS file in your script folder and add this reference to your View
<script src="#Url.Content("~/Scripts/yourJsFile.js")" type="text/javascript"></script>
Now, consider a JavaScript literal class in your yourJsFile.js:
var contentSetter = {
allData: {},
loadData: function (data) {
contentSetter.allData = eval('(' + data + ')');
},
setContentA: function () {
$("#contentA").html(allData.contentAData);
},
setContentB: function () {
$("#contentB").html(allData.contentAData);
}
};
Also declare a class
public class ContentData
{
public string ContentDataA { get; set }
public string ContentDataB { get; set }
}
Now, from your Action do this:
public ActionResult Index() {
var contentData = new ContentData();
contentData.ContentDataA = "Hello";
contentData.ContentDataB = "World";
ViewData.Add("contentData", contentData);
}
And from your view:
<div id="contentA"></div>
<div id="contentB"></div>
<script type="text/javascript">
contentSetter.loadData('#Json.Encode((ContentData) ViewData["contentData"])');
contentSetter.setContentA();
contentSetter.setContentB();
</script>
I recently blogged about this topic: Generating External JavaScript Files Using Partial Razor Views.
My solution is to use a custom attribute (ExternalJavaScriptFileAttribute) which renders a partial Razor view as is and then returns it without the surrounding <script> tags. That makes it a valid external JavaScript file.
I usually wrap JS needing access to model properties, in functions and then pass the #something in the view. For example
<script type="text/javascript">
function MyFunction(somethingPresentInTheView) {
alert(somethingPresentInTheView);
}
</script>
in the view I add function invocation via (just an example):
<input type='button' onclick="MyFunction('#Model.PropertyNeeded')" />
I think you are stuck with having to put that JS code in the View. The Razor parser, as far as I know, won't look at .js files, thus anything you have which uses # won't work. PLus, as you have spotted, Javascript itself does not like this # character hanging around for no reason, other then, say, in a string.
In our ASP.NET MVC3 project we have written couple of custom HTML Helper extension method, which basically renders some composit controls (say a text and a label with some needed styles). Now we also want to render some javascript along with HTML tags, but looks MVCHtmlString does not render javascript test as javascript ! Any options or alternatives to render dynamic javascript from custom HTML Helpers ?
It works fine for me :)
here is what I used as an extension method:
namespace MvcApplication1.ExtensionMethods
{
public static class MyExtensionMethods
{
public static MvcHtmlString SomeJavascript(this HtmlHelper helper)
{
StringBuilder sb = new StringBuilder();
sb.Append("<script> alert('testing 123')</script>");
return MvcHtmlString.Create(sb.ToString());
}
}
}
and in my index.cshtml i call it like this:
#using MvcApplication1.ExtensionMethods
....
#Html.SomeJavascript()
and it shows me the pop-up :)
We're in the process of transferring a big WebForms app to MVC.
In the WebForms app we have some reusable controls (.ascx). For instance, a TextBox that shows username suggestions as you type. The control adds some JS/CSS to the page, has some server-side logic and custom properties (like SelectedUserID).
What should it be in an MVC app? How do I encapsulate those things into one reusable entity in an MVC app? Maybe a partial view? But you can't add, say, JS/CSS to the page's <head> from a partial view (preferably, with a check that it's not already been added)... Also, how do I return something like the mentioned SelectedUserID in a partial view?
To rephrase the question - how would you implement such a control in an MVC app?
PS. I know you can still use .ascx user-controls in MVC apps but it seem a "hacky/legacy" way. What is the "legitimate" option?
Your question is pretty broad. All my comments/answer are going to be in Razor. In my opinion, if you're going to switch to MVC, you might as well use Razor. There are plenty of good reasons to switch, but I'd say my top pick for anyone who is migrating, the best reason is two fold; first razor allowed me to drop some bad habits about how programming works with webforms and in the same manor it forced me to re-write my code instead of trying to copy and paste and change the original code to work in MVC.
The control adds some JS/CSS to the page, has some server-side logic and custom properties.
What should it be in an MVC app?
This is a pretty religious software question. There are plenty of ways of doing it.
So onto my religous opinion about how to add JS/CSS in MVC. One way to include server side is to create a region in the Layout/Master template.
Views/_ViewStart.cshtml
#{
Layout = "~/Views/Shared/Layout.cshtml";
}
Views/Shared/Layout.cshtml
<!doctype html>
<head>
<link href="/Styles/Global.css" rel="stylesheet" type="text/css" />
#RenderSection("Styles", required: false)
<script src="/Scripts/Global.js" type="text/javascript"></script>
#RenderSection("Scritps", required: false)
This would allow each view to optionally (because required=false) add any Styles or Scripts using the RenderSecion code.
Views/Home/Index.cshtml
#section Styles {
<link href="/Styles/Home/Index.css" rel="stylesheet" type="text/css" />
}
This is pretty simple, and probably a good solution for many simple to moderately complex sites. This wasn't enough for me, as I needed to do what you requested, and only include files if they were truly needed. Additionally, partial views and templates cannot render sections, which I found to be a giant PITA. So I added some HtmlHelper extension methods. (i'm only going to show the code for Javascript as the Stylesheets are nearly Identical code. These methods don't allow duplicate urls.
Domain/HtmlHelperExtensions.cshtml
public static class HtmlHelperExtensions
{
private const string _JSViewDataName = "RenderJavaScript";
private const string _StyleViewDataName = "RenderStyle";
public static void AddJavaScript(this HtmlHelper HtmlHelper, string ScriptURL)
{
List<string> scriptList = HtmlHelper.ViewContext.HttpContext.Items[HtmlHelperExtensions._JSViewDataName] as List<string>;
if (scriptList != null)
{
if (!scriptList.Contains(ScriptURL))
{
scriptList.Add(ScriptURL);
}
}
else
{
scriptList = new List<string>();
scriptList.Add(ScriptURL);
HtmlHelper.ViewContext.HttpContext.Items.Add(HtmlHelperExtensions._JSViewDataName, scriptList);
}
}
public static MvcHtmlString RenderJavaScripts(this HtmlHelper HtmlHelper)
{
StringBuilder result = new StringBuilder();
List<string> scriptList = HtmlHelper.ViewContext.HttpContext.Items[HtmlHelperExtensions._JSViewDataName] as List<string>;
if (scriptList != null)
{
foreach (string script in scriptList)
{
result.AppendLine(string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", script));
}
}
return MvcHtmlString.Create(result.ToString());
}
}
Updated Views/Shared/Layout.cshtml
<!doctype html>
<head>
<link href="/Styles/Global.css" rel="stylesheet" type="text/css" />
#Html.RenderStyleSheets()
<script src="/Scripts/Global.js" type="text/javascript"></script>
#Html.RenderJavascripts()
Updated Views/Home/Index.cshtml
#Html.AddStyleSheet("/Styles/Home/Index.css")
Onto your next question...
How do I encapsulate those things into one reusable entity in an MVC app? Maybe a partial view?
Razor supports both partial views and templates. Although technically there is large overlap in what each can do, there are limitations of how each were designed to allow a programmer to take advantage of each when needed.
Partial views do not require a Model/Class in order to be rendered. Here is a completely valid partial view:
/Views/Home/Index.cshtml
#Html.Partial("Partial-Menu")
/Views/Shared/Partial-Menu.cshtml
<div id="menu">
Home
About Us
</div>
Templates on the other had, do required a Model in order to be rendered. This is because Templates are a way to render any class or struct as either an Editing Template or a Display Template.
/Models/Person.cs
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
/Controllers/HomeController.cs
public ActionResult Index()
{
Person model = new Person();
model.FirstName = "Jon";
model.LastName = "Doe";
return this.View(model);
}
/Views/Home/Index.cshtml
#model Project1.Models.Person
#Html.DisplayModel()
#Html.EditforModel()
/Views/Shared/DisplayTemplates/Person.cshtml
#model Project1.Models.Person
<div>#Html.LabelFor(x => x.FirstName) #Html.DisplayFor(x => x.FirstName)</div>
<div>#Html.LabelFor(x => x.LastName) #Html.DisplayFor(x => x.LastName)</div>
/Views/Shared/EditorTemplates/Person.cshtml
#model Project1.Models.Person
<div>#Html.LabelFor(x => x.FirstName) #Html.EditorFor(x => x.FirstName)</div>
<div>#Html.LabelFor(x => x.LastName) #Html.EditrorFor(x => x.LastName)</div>
The way I see it, any model that might turn into a Form for data entry should probably be a Template. This is the way I prefer to encapsulate my models. Using the extension methods provided earlier, both my partial views and templates can load includes as needed (currently only one of my models of oodles of them actually needed to use this).
My preference is to have up to three includes (of each Javascript and Styles) per page rendered. I basically have a Global.css/js, a controller Home.css/js, and a page Index.css/js as the possible includes. It's been very seldom that I have a controller include.
The best solution I've found so far is Razor declarative helpers. They fit awesomely.
#helper UserTextBox() {
<input type="text" />
<!--my own helper that registers scripts-->
#Html.AddJS("../js/myscript.js")
}
Related question: Razor: Declarative HTML helpers
Related note: you can't use #Html helper inside a declarative helper but there's a workaround: https://stackoverflow.com/a/5557503/56621
Well, you can't have something that "automatically" includes css/js. That's something the user has to add to the page (either the master/layout or the current page). But in general, one would create an Html Helper. Don't expect these to be complex systems (like the gigantic grids of asp.net days) but you can do a lot with them.
Yes, a partial page may be easier for simple things. Even simpler might be an Editor or Display Template.
In general, however, most "controls" for MVC these days are jQuery based.
A short video is available here: http://www.asp.net/mvc/videos/mvc-2/how-do-i/how-do-i-create-a-custom-html-helper-for-an-mvc-application
Don't be tempted to include your javascript in these mechanisms. While it may work for a single control, if you have multiple controls on a page you will get duplicate css/js.
I would add the js/css to the main layout then write an html helper to render your custom control.
Many controls that uses jquery in aspnet mvc works this way.
Take a look at this controls here
1) for custom js/css you can use sections like this:
//code in view
#section someName
{
...
}
//code in layoutpage
#if (IsSectionDefined("someName"))
{
#RenderSection("someName")
}
2) i'd better do the following:
create model SelectedUser with Id property
create templates for this model
add a SelectedUser object to any model, which needs it
They must be separated from surrounding text by blank lines.
The begin and end tags of the outermost block element must not be indented.
Markdown can't be used within HTML blocks.
I have a large view that needs some conditional logic to decide which of several html chunks to render in the middle of the view. I have a property on my model which can have several different values which determines the html to be output.
I would normally put conditional logic in an html helper, but given that each output is a fair chunk of html, I am not sure that escaping these in a c# file would be great. I could also put the logic in the action and render different views but given that the majority of the view is the same, this does not seem great either. So I am left with multiple if statements in my view (or partial?) which also seems ugly (and is obviously untestable).
What is the best way of doing this?
(I am using MVC3 in case there is something new and funky I can use!)
I usually put separate visual chunks in their own partials. Then my view conditionally calls each partial with Html.Partial. This keeps you main view from bloating.
In general, I try to avoid Html.Helpers that contain more than a single element.
Something like:
#if(Model.HasA)
{
#Html.Partial("widgetdetails-hasa")
}
#if(Model.HasB)
{
#Html.Partial("widgetdetails-hasb")
}
// etc
IMHO logic like this is fine for a view:
#if (Model.ShouldShowSomeSection)
{
... some large chunk of HTML
}
else
{
... some alternative
}
I agree with the answer from #bmancini , but here's what I'd do slightly differently:
I would logically group those 'several html chunks to render' into different partial views :
_partialViewA.cshtml and _partialViewB.cshtml
I then would use extension methods and have my logic in a Helpers folder, then Html sub-folder like this:
using System.Web.Mvc.Html;
public static class SomeViewHelper
{
public static MvcHtmlString OutputHtmlString(this HtmlHelper helper , SomeModel model)
{
if(model.HasA)
{
return helper.Partial("_partialViewA", model)
}
if(model.HasB)
{
return helper.Partial("_partialViewB", model)
}
}
}
This would remove all the logic from the view which would now only have this code:
#Html.OutputHtmlString(Model);
At least this would remove the 'ugliness' and avoid the conditional statements, and also avoid 'escaping the html chinks in C# code'...
Of course I would have to reference the Helpers.Html folder with a #using statement in the view.