How can I access asp net inside a separate JS file [duplicate] - asp.net-mvc

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.

Related

Using an #section inside a ChildAction to add additional scripts to a page

I've got a MVC View which is made up of a main view and some additional content added via #Html.Action.
In the additional content (which is a ChildOnlyAction) I want to be able to add some JS to the page, but I want to add it to the #RenderSection("Scripts") block which I've defined in the layout.
Can my Child Action's View use this:
#section Scripts {
//Add scripts
}
So far I haven't been able to get it to work, so if it doesn't what would be an alternative approach?
Sections do not work in partial views. You could use a conjunction of extension methods that I illustrated here: https://stackoverflow.com/a/9663249/29407
So in your _Layout.cshtml you will have at some location:
#Html.RegisteredScripts()
and then in your partial:
#{Html.RegisterScript("~/scripts/foo.js");}
I was willing today to create a global dialog that would open under some conditions, I needed the script to be at bottom of the page. As others have already mention, a #section inside a child action is not possible.
I had the same problem as you did, the solultion to use custom helpers and js files should work, but I don't like, because usually I operate the javascript with razor and files make the requests longer to load.
the solution at https://stackoverflow.com/a/9663249/29407 is valid if you like that, for me no thanks.
I came with a new solution that is clean, the problem if you analyze it is that we have one controller and a view with 2 parts that have to be injected at different position in the final result.
After my analysis I realize that we have 2 views but one controller that has to control them once per request, below is how I did it, I moved the javascript to a new view with same name endig with script.
XDialogController.cs
XDialog.cshtml
XDialogScript.cshtml
Then before returning the ActionResult from the child action method, one sets the model or values for the other view inside the TempData object.
for example:
[ChildActionOnly]
public ActionResult Popup()
{
// pass variable or model if you need it to script view.
TempData[TempDataKeys.ScriptXDialogModel] = new ModelScriptX();
// pass variable or model to regular view.
return PartialView("XDialog", new ModelX());
}
Inside your ...Script.cshtml file you can read the variable or model as you need.
for example:
#if((TempData[TempDataKeys.DisplayXDialog] as bool?) == true)
{
<script type="text/javascript">
...jquery functions ....
</script>
}
Remember that TempData can only be read only once, one can keep the value inside a variable inside the view.
To invoke my dialog in the layout page I do the following:
<body>
#RenderBody()
#Html.Action("Popup", "XDialog")
#Scripts.Render("~/Scripts/core")
#RenderSection("ExtraScripts", required: false)
#Html.Partial("XDialogScript")
</body>
I hope that can help anybody.

Is it ever good to use Razor views as AngularJS views?

When, if ever, is it appropriate to render an angular partial view using Razor?
I've generally tried to stay away from mixing server side rendering with client-side rendering when using Angular but I have found it convenient to sometimes:
Set variables/data that is known server-side
An example might be a client API key that you might want to use client-side without making an additional call to a backend for that information.
Here is an example of a template:
<div config="{ soundcloudApiKey: '#soundcloudApiKey' }">
</div>
Where #soundcloudApiKey is presumably available in a model server-side and config is a directive that brings data from the markup into your Angular code in case you want to use it elsewhere:
myModule.directive('config', function() {
return {
link: function(scope, elm, attrs) {
// you can save config to a service and use it elsewhere
var config = scope.$eval(attrs.config);
console.log(config.soundcloudApiKey);
}
};
});
Note: there is likely a cleaner way to get to this type of configuration data like via a login process or an explicit call for config settings but I've used this type of thing in the past and it works just fine!

Emit JS code from an HTML helper MVC

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?

How to handle "magic strings" in MVC views, e.g. element id:s?

In my MVC views I frequently need to reference various elements from JavaScript, and for that purpose I define the id attribute for those elements. This is often in conjunction with Ajax calls that update content of various container elements.
A short example (Razor):
<table id="PersonList">
...
</table>
<div id="PersonDetails">
<!-- This would be loaded dynamically using Ajax -->
<input type="hidden" name="CurrentPersonId" value="#Model.PersonId">
...
</div>
This contains three "magic strings": "PersonList", "PersonDetails" and "CurrentPersonId". An Ajax call might look like this:
$('#PersonDetails').load('#Url.Action("GetPersonDetails")', { PersonId: ... });
Again, the magic string "PersonDetails" appears.
Not good!
Can anyone suggest some "best practice" to define these magics string in a single place and use them in 1) the views, 2) preferably static JavaScript files that implement Ajax calls etc, and 3) CSS files?
I'm, thinking perhaps _Layout.cshtml could include an partial view that defines the magic strings for that controller or even for a specific action. It would examine what controller and/or action called it, and call the appropriate partial view based on that. I do something similar for .css and static .js already, letting me simply add Person.css and have that automatically included for all views for the Person controller.
The partial view would do something like this:
#{
const string PersonListId = "PersonList";
const string PersonDetailsId = "PersonDetails";
const string CurrentPersonIdName = "CurrentPersonId";
}
<script type="text/javascript">
NamesAndIds = {};
NamesAndIds.PersonListId = '#Html.Raw(PersonListId)';
NamesAndIds.PersonDetailsId = '#Html.Raw(PersonDetailsId)';
NamesAndIds.CurrentPersonIdName = '#Html.Raw(CurrentPersonIdName)';
</script>
This should let Razor code use the C# string consts to generate appropriate HTML, and static JavaScript files could reference NamesAndIds in jQuery selectors etc. (Assumes that the consts defined in the partial view will be available in the calling view, which I doubt (haven't checked it yet)... How to use them in .css files I don't know.
Any better suggestions? How do you handle this problem?
I hope someone can come up with something better, but this is at least something.
In the main (non-partial) view I have a section at the top that defines the ids and names I need to use in multiple places in C#, HTML and JavaScript:
#{
const string PersonListId = "PersonList";
const string PersonDetailsId = "PersonDetails";
const string CurrentPersonIdName = "CurrentPersonId";
}
At the bottom, I have a script section that assigns the strings to suitable namespace container objects:
<script type="text/javascript">
MyNamespace = {};
MyNamespace.Ids = {};
MyNamespace.Names = {};
MyNamespace.Ids.PersonList = '#Html.Raw(PersonListId)';
MyNamespace.Ids.PersonDetails = '#Html.Raw(PersonDetailsId)';
MyNamespace.Names.CurrentPersonId = '#Html.Raw(CurrentPersonIdName)';
</script>
In each partial view that introduces additional items that I need to reference by id or name, I add similar code to extend MyNamespace.Ids and MyNamespace.Names with the required strings.
Now I can use the C# string constants in Razor view code to generate markup with the right ids and names and I can write regular static JavaScript files that reference MyNamespace.Ids and MyNamespace.Names to find the right ids and names, e.g. in jQuery selectors.
I also added similar stuff for action URLs that my Ajax calls use, and put them in MyNamespace.Urls:
MyNamespace.Urls.GetPerson = '#Html.Raw(Url.Action("GetPerson"))';
It's not ideal but it's straightforward and solves the most pressing issue of magic strings scattered all over the place. It will not detect errors at compile time, but renaming items will require a single string to be renamed at a single place, and if I rename or misspell MyNamespace.Ids.Something it will at least generate a runtime JavaScript error that can be seen in a JS console or similar.
For actions and file names (js, css) use T4MVC.
T4MVC is a T4 template for ASP.NET MVC apps that creates strongly
typed helpers that eliminate the use of literal strings in many
places.
I wouldn't worry about ids and css class names.
Try defining custom routes
PeopleLists/{PeopleList}/Person/{PersonID}
Then your URL would look like this
http://www.mysite.com/PeopleLists/Friends/Person/Pete

MVC alternatives to UserControls

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.

Resources