I have an ASP.Net Core application. I have a Class with some attributes:
public class myClass
{
public string name {get; set;}
public int id{get; set;}
...
}
and in PageModel of Index.cshtml, I am creating on object of that class and setting it is a property:
public class IndexModel : PageModel
{
public myObj data { get; set; }
public void OnGet(int inputId)
{
data = new myClass();
data.name = "name";
data.id = inputId;
}
}
Now, in my Index.cshtml, I have some default html and then I add a script like this:
<script type="module" src="~/built/script.js"></script>
Finally, my question: I need the data that I have defined in IndexModel in my script.js. In a normal cshtml-page, I would do #Model.data, but that decorator is not available in my js file. Is there a way to do this, or should I use one of the following which I think might work:
Adding an API-controller in ASP.Net and calling it in my script.js with ajax: I think this should work, but it seems to me like I am supposed to do it with #Model instead
Somehow storing it in a global variable in my .cshtml file and then accessing that global variable in script.js: Seems like a hack
I'm pretty new to ASP.Net and JS, so there might be an obvious answer to this that I'm just too inexperienced to know. Any help is appreciated!
You could use model binding as intended and convert the model to a javascript variable at the top of your view, then it will be available in the script file as a javascript variable as long as you load the javascript file after you create the variable to hold your model.
---YOUR VIEW---
#model YourModel
#using Newtonsoft.Json;
<script type="text/javascript">
let mymodel = #Html.Raw(JsonConvert.SerializeObject(Model));
</script>
--Import your script file after creating javascript variable and mymodel should then be available
<script type="text/javascript" src=""></script>
--Use mymodel in your script file
There are four ways to do this, you found two of them. I'll give you the pro's and cons in order of complexity:
AJAX:
It's not that hard, use the "fetch" method built into the window object. But, it's asynchronous so you have to deal with callbacks in JavaScript, you will need to secure the API so it only accepts authorized clients (usually done with OAuth, but you could just inject an authorization code for the client JavaScript to use), and you need to identify yourself (like a session) to the API to get the right data so that is another code. Oh wait, we have to inject a code? So what's the point of using AJAX? True, only use AJAX when you need to dynamically get new data without reloading the page!
JavaScript injection:
You never want to inject into a .js file, they should be static and that's why you discovered it won't work. So you have to inject some JavaScript code creating variables into the page that is loading the JavaScript. That's what you proposed and that is completely fair to do. It's isn't a hack. It should be one variable with a JSON string.
Cookie:
Put the data into a temporary cookie delivered with the page and read the cookie from JavaScript. This is cleaner than the variable solution, but isn't the simplest way.
HTML injection:
There is one other possibility for using #Model, instead of injecting the JSON string into a JavaScript variable, which requires injecting JavaScript tags too, put it into a property on an HTML element and then access the property from JavaScript. This is simpler and my preferred method. Property names beginning with "data-" are designed for this.
Related
In my application I added the multilanguage support following this documentation.
Now I'm facing an issue: I need to translate also some javascript plugins, so I need to use the .resx string inside the js logic:
For access to those strings I can use the IStringLocalizer in the specific controller, let's suppose that the string above are part of UserController, I can access declaring in the View this:
#inject IStringLocalizer<UserController> Localizer
and then:
<h2>#Localizer["LastName"]</h2>
Suppose now that I need to pass LastName string in something like a JQuery plugin
localization. For doing so, I actually found a workaround, which consists to declare a javascript variable in the required View:
#inject IStringLocalizer<UserController> Localizer
#section UserScript{
<script>
var Lang = {
PasswordEqual: '#Localizer["PasswordEqual"]',
PasswordMismatch: '#Localizer["PasswordMismatch"]',
}
</script>
}
so I can access inside the javascript code to the Lang object and localize the plugin eg:
$('#birthDate').daterangepicker({
singleDatePicker: true,
locale: {
format: 'DD/MM/YYYY',
daysOfWeek: [
Lang.Sunday,
Lang.Monday,
This works, but it's really huge to mantain for the following reason:
I need to declare in each .cshtml file which require a script localization the Lang object
I need to set each time the object key for the specific string available in the IStringLocalizer
If I change a string key, I also need to update all the files manually.
What I'm looking for:
I'm looking for a solution that automatically fills the Lang object with all the properties available in the .resx file loaded in the current View.
I guess the best place for handle this is _Layout.
Someone could help me?
I have one tricky question.
Is there a way to take C# const and use it in .JS script with Jquery ?
This is how const look:
public class UserRoles
{
public const string Read = "Read";
public const string ReadWrite = "ReadWrite";
}
It depends a bit of what you are trying to do with those values.
You could place the values in a js object when your UI initializes (ex: window.YourAppName.Constants.Read = "your C# constant" in Index.html). Then you could load your jquery script and make use of the constant values once the document finished loading.
Alternatively, if you are using MVC, you can make use of tags within your views, and thus have access to C# code (viewmodel, enums, etc.). However, if you have lots of js code then it would be best to keep that in js files and in such a case I would go for the first option.
You can't use Razor in JavaScript files, you would have to have the variable passed to a razor view in the viewbag/data or model.
Then in the shared layout you could create a javascript function that returns this variable, then in your .JS file you could call that function to get the variable as long as it is loaded after.
All,
I have the following route/action defined on my controller :
[RoutePrefix("widgets/download-functions")]
[Route("download/{publishedReportId}"), HttpGet]
public ActionResult Download(int publishedReportId)
And inside my js code, I want to create some routing with the appropriate id.
This is what I have inside my js code (which doesn't work). What am I missing?
self.downloadFile = function (data) {
console.log(data);
console.log("#(Url.Action("Download", new { publishedReportId = 9999 }))");
console.log("#(Url.Action("Download"))");
};
Still new to this stuff and learning, I am sure it is something simple.
When I run this code, I get the following in the console
Console output
Thx
jonpfl
If you are using Url.Action, it doesn't take advantage of attribute routing, you will need to follow the original method of routing, which is to use, Action, Controller, area and params to get the extension method to produce the url.
[RoutePrefix("widgets/download-functions")]
public class WidgetDownloadController : Controller
...
[Route("download/{publishedReportId}"), HttpGet]
public ActionResult Download(int publishedReportId)
You would get a Url.Action like:
console.log("#(Url.Action("Download","WidgetDownload", new { publishedReportId = 9999 }))");
Also, this will only work on scripts that are parsed by the razor engine, ie. scripts that are written directly in the view.
I am writing a Jenkins Builder, and in the jelly script for its configuration in the build configuration page I have some Javascript that I want to run when the form is loaded, to do a server lookup and get some information to help the user out with their configuration, which will also be performed when the user changes the form's values.
Previously I have got references to the form elements by passing this in to functions in onchange or onkeyup attributes. However, now I want to run some script even when the form hasn't changed.
I know I could set ID attributes on the form elements, however that's not going to work if the users add to a build two build steps both using this builder.
I've tried generating a random ID on my builder class, and then use that to construct IDs for the elements and write that into some Javascript in the jelly file so I can find those elements there, but that doesn't get initialised until the user saves, so it won't work if the user adds two instances of this builder without saving the job:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="Entry 1">
<f:textbox field="field1" id="${instance.id}-field1" onchange="fieldChanged('${instance.id}-field1')"/>
</f:entry>
<script type="text/javascript">
function fieldChanged(elementId) {
...
}
fieldChanged('${instance.id}-field1');
</script>
</j:jelly>
Are there any conventions on how to do this sort of thing? Anything built in to Jenkins/jelly to support multiple instances of the same jelly file being able to refer to their own elements?
There's a solution using j:set which is simpler than my other answer.
com.example.MyBuilder.DescriptorImpl:
private int lastEditorId = 0;
...
#JavaScriptMethod
public synchronized String createEditorId() {
return String.valueOf(lastEditorId++);
}
com/example/MyBuilder/config.jelly:
...
<j:set var="editorId" value="${descriptor.createEditorId()}" />
<f:entry title="Field">
<f:textbox field="field" id="field-${editorId}"/>
<p id="message-${editorId}"></p>
</f:entry>
<script>
setTimeout(function(){
var field = document.getElementById('field-${editorId}');
var p = document.getElementById('message-${editorId}');
p.textContent = "Initial value: "+field.value;
}, 50);
</script>
(The call to setTimeout is still due to the fact that when adding new build steps, the elements haven't been added to the DOM by the time that the script executes, so the script execution has to be deferred slightly).
It looks like this solution below might work, but I haven't got very far with it yet.
In my builder class I've added an inner class called Editor:
com.example.MyBuilder(.Editor):
...
public static class Editor {
private final String id;
public Editor(final String id) {
this.id = id;
}
public String getId() {
return id;
}
}
...
Then in the descriptor Java class, provide a JavaScript function to create one of these with a unique ID:
com.example.MyBuilder.DescriptorImpl:
private int lastEditorId = 0;
#JavaScriptMethod
public synchronized Editor createEditor() {
return new Editor(String.valueOf(lastEditorId++));
}
Then in my jelly file I call that method and pass the returned object into st:include, loading a new jelly file to render the fields:
com/example/MyBuilder/config.jelly:
<st:include page="editor.jelly" it="${descriptor.createEditor()}" />
(Although this appears to have to be inside an f:entry element - or perhaps other elements, I haven't tried - otherwise it doesn't seem to get included when a new build step for this builder is added to the job config.)
And finally I create that new editor.jelly file to render the fields (which has to be in a folder whose name reflects the Editor class, as the it object being passed into st:include is of type Editor):
com/example/MyBuilder/Editor/editor.jelly:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:ajax>
<f:entry title="Field">
<f:textbox field="field" id="field-${it.id}"/>
<p id="message-${it.id}"></p>
</f:entry>
<script>
setTimeout(function(){
var field = document.getElementById('field-${it.id}');
var p = document.getElementById('message-${it.id}');
p.textContent = "Initial value: "+field.value;
}, 50);
</script>
</l:ajax>
</j:jelly>
(The call to setTimeout is due to the fact that when adding new build steps, the elements haven't been added to the DOM by the time that the script executes, so the script execution has to be deferred slightly).
However, this breaks the link between the f:entry elements and the equivalent fields in the builder class, and I'm not sure what to do about that. So this is an incomplete answer.
EDIT: I'm not sure if the f:entry elements would have worked or not, as I had forgotten to add the field to the builder class when I was testing it, which was (at least one reason) why I did not see any data saved from this field when I tried this. However, I am now using the solution from my other answer, so I have not gone back to test whether it would have worked or not.
Most of the examples you find on the web, of using javascript from ASP.NET pages puts the javascript in the markup file (*.aspx). This is, of course, a really bad idea(tm), for all but the simplest uses of javascript.
What we want, of course, is to wrap the javascript up into a class, and to instantiate an instance of that class and tie it to the code-behind.
Microsoft provides a framework for doing this for user controls and server controls, in its IScriptControl interface. This allows a developer to create a javascript "component" - to define a javascript class in a *.js file, to include the *.js file on the page that contains the control, to instantiate an instance of the component, to set variables in the component from values in the code-behind, and to get a reference to the component in javascript on the client side.
The thing is - IScriptControl only works for user and server controls. It cannot be used to instantiate javascript objects at the page level.
So - how do people do this? We have some patterns we've been using, that seem to work. I was wondering what everyone thought of them, and what other people were using.
We start by defining a javascript class in a *.js file. In the code-behind, we create a loadJavascript() function, that we call from Page_Load on initial load or full postback (but not on partial postbacks).
In loadJavascript(), we include the *.js file with ScriptManager.RegisterClientScriptInclude(), and then construct a bit of javascript that instantiates an instance of the class, assigns a reference to a known name, and registers the object's initialize() and dispose() methods as handlers for window.load and window.unload.
E.g.:
string url = this.ResolveUrl("./FooBar.js");
ScriptManager.RegisterClientScriptInclude(this, this.GetType(), url, url);
string script = #"
if (typeof {0}_obj == 'undefined')
{0}_obj = {{}};
{0}_obj.fooBar = new FooBar();
Sys.UI.DomEvent.addHandler(window, 'load',
function()
{{
{0}_obj.fooBar.initialize('{1}', '{2}');
}}
);
Sys.UI.DomEvent.addHandler(window, 'unload', {0}_obj.fooBar.dispose);
";
script = String.Format(script,
new object[]
{
this.ClientID,
this.foo.ClientID,
this.bar.ClientID
});
ScriptManager.RegisterStartupScript(
this, this.GetType(), this.ClientID, script, true);
We construct an object name in the global namespace, based on the ClientID of the page, if we haven't already. We add an instance of our new class as a member of our global object. We add a window.load handler that calls our object's intialize() method, passing the clientIDs of the controls on the page that the object's methods need to access. And we add a window.unload handler that calls our object's dispose() method, that does whatever cleanup that is necessary.
This seems to be working, for us. We've used this pattern on a number of pages, some of which did significant amounts of partial-postbacks, without any problems.
I was wondering, first, what people thought of the pattern.
But more, I was wondering if we'd been reinventing the wheel, and if there were other approaches to dealing with the issues we were addressing, that we weren't aware of.
Anyone have any better ideas?
But more, I was wondering if we'd been reinventing the wheel, and if there were other approaches to dealing with the issues we were addressing, that we weren't aware of.
I think this the most good approaches, I use the same way some years now with out any problem in very complex javascript code. I do not see why you question your self :)
The idea is this you follow, now maybe there are some variations, maybe I not call the unload, nether create an object to keep the foobar and call the foobar rightway, but the idea is the same. I also check if the Javascript file have been loaded...
string script = #"
if (typeof (FooBar) != "undefined") {{
var {0}fooBar = new FooBar();
Sys.UI.DomEvent.addHandler(window, 'load',
function()
{{
{0}fooBar.initialize('{1}', '{2}');
}}
);
}}