I have an Angular.js application, and, because it is a single page application, I'm loading some scripts dynamically, depending on the user navigation, so I don't get an overload.
The problem is, some of these scripts are uglified and minified in a ASP.NET MVC Bundle, and when I update a source script, the imported bundle never gets updated.
Why that happens, and what can I do to force an update?
Why that happens
The ASP.NET bundle comes with a caching mechanism. When you add the bundle to the page using Scripts.Render, the engine automatically puts a v query string into the bundle URL.
#Scripts.Render("~/bundles/commands")
produces something like:
<script src="/bundles/commands?v=eiR2xO-xX5H5Jbn3dKjSxW7hNCH9DfgZHqGApCP3ARM1"></script>
If this parameter is not provided, the cached result will be returned. If you add the script tag manually, without it, you can face the same caching issue.
Info about the v query string is provided here ("Bundle Caching"), but is not very helpful.
What can I do
You can still load the bundled scripts dynamically, but you will have to add the v parameter. Note that it doesn't work if you try a randomly generated hash (I tried). Thanks to Frison B Alexander, this is possible using this approach:
private static string GetHashByBundlePath(string bundlePath)
{
BundleContext bundleContext = new BundleContext(new HttpContextWrapper(System.Web.HttpContext.Current), BundleTable.Bundles, bundlePath);
Bundle bundle = BundleTable.Bundles.GetBundleFor(bundlePath);
BundleResponse bundleResponse = bundle.GenerateBundleResponse(bundleContext);
Type bundleReflection = bundleResponse.GetType();
MethodInfo method = bundleReflection.GetMethod("GetContentHashCode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
object contentHash = method.Invoke(bundleResponse, null);
return contentHash.ToString();
}
So what you can do is: Return the bundle hash from the ASP.NET view and get it when you need to load the script.
I my application, I created a JS object specific to it:
var appBundles = {
commands: "/bundles/commands?v=eiR2xO-xX5H5Jbn3dKjSxW7hNCH9DfgZHqGApCP3ARM1"
};
Hope this helps!
I had this problem with bundles not updating when I was loading bundles from one MVC app in another MVC app using GTM (sound messed up, but it actually makes sense in the context of multiple MVC apps sharing code between).
What I came up with is what Marcos Lima wrote in his answer, but taken a step further.
I've added a Bundle controller with following code:
public class BundleController : Controller
{
private static string GetHashByBundlePath(string bundlePath)
{
BundleContext bundleContext = new BundleContext(new HttpContextWrapper(System.Web.HttpContext.Current), BundleTable.Bundles, bundlePath);
Bundle bundle = BundleTable.Bundles.GetBundleFor(bundlePath);
BundleResponse bundleResponse = bundle.GenerateBundleResponse(bundleContext);
Type bundleReflection = bundleResponse.GetType();
MethodInfo method = bundleReflection.GetMethod("GetContentHashCode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
object contentHash = method.Invoke(bundleResponse, null);
return contentHash.ToString();
}
public ActionResult Index(string bundleName)
{
string bundlePath = "~/bundles/" + bundleName;
var hash = GetHashByBundlePath(bundlePath);
return RedirectPermanent(bundlePath + "?v=" + hash);
}
}
Then I've added this route:
routes.MapRoute(
name: "Bundle",
url: "Bundle/{bundleName}",
defaults: new { controller = "Bundle", action = "Index" }
);
The end result is that I request the bundles through the controller, but because I do a 301 redirect the Index action is run only once per user and it returns the current version of the bundle and the bundle is then served from browser cache afterwards. When I actually update the bundle I add some query parameter in the request url (in GTM) and all users now get the updated bundle.
Of course, I assume that bundles are placed in ~/bundles/ path, but that should be easy enough to change if yours are placed elsewhere. In fact the route isn't even necessary.
Related
I am using undertow to statically serve a react single page application. For client side routing to work correctly, I need to return the same index file for routes which do not exist on the server. (For a better explanation of the problem click here.)
It's currently implemented with the following ResourceHandler:
ResourceHandler(resourceManager, { exchange ->
val handler = FileErrorPageHandler({ _: HttpServerExchange -> }, Paths.get(config.publicResourcePath + "/index.html"), arrayOf(OK))
handler.handleRequest(exchange)
}).setDirectoryListingEnabled(false)
It works, but it's hacky. I feel there must be a more elegant way of achieving this?
I could not find what I needed in the undertow documentation and had to play with it to come to a solution. This solution is for an embedded web server since that is what I was seeking. I was trying to do this for an Angular 2+ single page application with routing. This is what I arrived at:
masterPathHandler.addPrefixPath( "/MY_PREFIX_PATH_", myCustomServiceHandler )
.addPrefixPath( "/MY_PREFIX_PATH",
new ResourceHandler( new FileResourceManager( new File( rootDirectory+"/MY_PREFIX_PATH" ), 4096, true, "/" ),
new FileErrorPageHandler( Paths.get( rootDirectory+"/MY_PREFIX_PATH/index.html" ) , StatusCodes.NOT_FOUND ) ) );
Here is what it does:
the 'myCustomServiceHandler' provides the handler for server side logic to process queries sent to the server
the 'ResourceManager/FileResourceManager' delivers the files that are located in the (Angular) root path for the application
The 'FileErrorPageHandler' serves up the 'index.html' page of the application in the event that the query is to a client side route path instead of a real file. It also serves up this file in the event of a bad file request.
Note the underscore '_' after the first 'MY_PREFIX_PATH'. I wanted to have the application API URL the same as the web path, but without extra logic, I settled on the underscore instead.
I check the MIME type for null and serve index.html in such a case as follows:
.setHandler(exchange -> {
ResourceManager manager = new PathResourceManager(Paths.get(args[2]));
Resource resource = manager.getResource(exchange.getRelativePath());
if(null == resource.getContentType(MimeMappings.DEFAULT))
resource = manager.getResource("/index.html");
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, resource.getContentType(MimeMappings.DEFAULT));
resource.serve(exchange.getResponseSender(), exchange, IoCallback.END_EXCHANGE);
})
I am building a UI into a JAR for Spring Server. I have a bunch of Angular JS pages. I want to pass in a command line argument to my jar that tells it where the API server is like so:
java -jar application.jar --api=http://ip:9000
So my application.properties file has:
url=${api:http://localhost:9000}
The way I am currently doing is it just having a hardocoded js config file and on each of my .html pages:
<script src="../js/appName/config.angular.js"></script>
Which contains:
var configData = {
url:"http://localhost:9000"
};
And called in each file:
$scope.apiUrl = configData.url;
How do I tap into the applications.properties file that I can override with my JAR command line parameter during runtime vs. the way it has been coded now.
When you pass a value from command line and the same property name is present in properties file then spring boot overrides the value from command line. So to achieve what you want do something like this
In application.properties
#this is default value
app.url=localhost:8080
Create a class to map the properties value or you can use existing class or something else based on your project structure.
#Component
public class Sample {
#Value("${app.url}")
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
Now when your execute a jar with argument --app.url="someserver:9090" the value will be overriden and you can use this value anywhere.
Note it will also work if you try to access the properties value directly in jsp using expression.
Try it, it works. I have used the same thing in my latest project which is a composite microservices and each component need each others url.
[Edit]
Reference : http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
Am I getting it right: The client part is delivered by the application? So the part of the last sentence 'during runtime' has more the meaning of 'bootstrap/initial loading', right? One (old school) approach is to provide the entry html (e.g. index.html) through the application (a simple template engine) and provide the needed information with a setter in a JS config object:
// pseudo js code with thymeleaf
<script th:inline="javascript">
/*<![CDATA[*/
myConfig.url = [[${#httpServletRequest.remoteHost}]];
/*]]>*/
</script>
This is just a sample that will only set the remote host name but I think you get the idea.
Side note: I still don't really get why do you have to set this. If the application contains the client code, why do you work with absolute URLs for remote calls? (Disclaimer: I have only experience in Angular(2) and not with AngularJS)
Cross-posted on the MiniProfiler community.
I'm trying to throw MiniProfiler into my current stack. I think I'm mostly setup, but am missing the UI approach and would like recommendations on the best way to proceed.
Current Stack
SQL for DB (including MiniProfiler tables)
EF 6
WebAPI 2 API app
Angular 1.x. app for the UI (separate app, no MVC backing it) -- I think it's 1.5.x at this point.
So, the current method of RenderIncludes() isn't available to me.
What's the best method to include the JS files and set them up to retrieve the information from the SQL Server storage? I know that the files are included in the UI repo, but I didn't see docs for explicit configuration.
What I've Tried So Far -- Web API App
Installed the MiniProfiler and MiniProfiler.EF6 packages.
Web.Config -- Added Handler
(not sure if this is necessary):
<add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
Added a CORS filter to expose the MiniProfiler IDs to my client app:
public class AddMiniProfilerCORSHeaderFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.Add("Access-Control-Expose-Headers", "X-MiniProfiler-Ids");
}
}
Add that filter as part of startup:
config.Filters.Add(new AddMiniProfilerCORSHeaderFilter());`
In Global.asax, added to Application_Start():
var connectionString = ConfigurationReader.GetConnectionString(Constants.ConfigSettings.CONNECTION_STRING_NAME);
MiniProfiler.Settings.Storage = new SqlServerStorage(connectionString);
MiniProfilerEF6.Initialize();
Update the begin/end requests:
protected void Application_BeginRequest()
{
if (Request.IsLocal || ConfigurationReader.GetAppSetting(Constants.ConfigSettings.USE_PROFILER, false))
{
var sessionId = Guid.NewGuid().ToString();
MiniProfiler.Start(sessionId);
}
}
protected void Application_EndRequest()
{
MiniProfiler.Stop();
}
What I've tried so far -- client (Angular) App
Snagged the UI files from the Github Repo
Copied the UI directory to my project's output
Reference the CSS:
<link rel="stylesheet" href="js/lib/miniprofiler/includes.css" />
Call the JavaScript
<script async type="text/javascript"
id="mini-profiler"
src="js/lib/miniprofiler/includes.js?v=1.0.0.0"
data-current-id=""
data-path="https://localhost:44378/api/profiler/"
data-children="true"
data-ids=""
data-version="1.0.0.0"
data-controls="true"
data-start-hidden="false"
data-trivial-milliseconds="5">
</script>
Current Status
When I do these things, it looks like it just can't find the appropriate WebAPI controller to render the result. If I can figure out where that controller is or replicate it (as I'm attempting to do currently) I think I'll be in business.
The RenderIncludes function results in a <script> tag being output to the page. It is defined in the UI Repo as include.partial.html and currently looks like this:
<script async type="text/javascript" id="mini-profiler"
src="{path}includes.js?v={version}" data-version="{version}"
data-path="{path}" data-current-id="{currentId}"
data-ids="{ids}" data-position="{position}"
data-trivial="{showTrivial}" data-children="{showChildren}"
data-max-traces="{maxTracesToShow}" data-controls="{showControls}"
data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}"
data-start-hidden="{startHidden}" data-trivial-milliseconds="{trivialMilliseconds}">
</script>
This is the piece of Javascript that runs the rendering.
The RenderIncludes function is defined here. It does the following:
Makes sure that you have storage set up
Checks that current request is authorized to view results
Gets the Ids of the unviewed profiles for the current user
Takes the Ids along with any other params that you passed into the function and inserts them into the placeholders in the script defined in include.partial.html
Outputs this <script>
So if you cannot call RenderIncludes, there should be no reason why you cannot just put the script file in place, retrieve the unviewed Ids, but them along with any other setup values you want into the <script> tag, and output the tag.
The key lines of code for retrieving the Id values are:
var ids = authorized
? MiniProfiler.Settings.Storage.GetUnviewedIds(profiler.User)
: new List<Guid>();
ids.Add(profiler.Id);
where profiler is the current instance of MiniProfiler (run on the current request.
You will also probably need to make sure that you can handle the call that the script will make to /mini-profiler-resources/results (passing in the id of the profiler as a param). The guts of this is located here in the GetSingleProfilerResult(HttpContext context) function
I'm working on an AngularJS project with the Play Framework 2.2. I'm supposed to develop a mobile version for the web application (not responsive, its part of a given uni project). For the desktop version I'm loading the index page with:
def index(any: String) = Assets.at(path = "/public", file = "app/html/index.html")
which works fine. Detection of the mobile browser works as well by examining the user agent in a Scala Action.
I changed the above code as follows to get the request header:
def index(any: String) = Action { implicit request: RequestHeader =>
if(isMobile(request)) {
// result for mobile version
}
else //result for desktop version
}
However, I don't know how to serve the different asset files as result type.
Any help is appreciated.
If I understand your question correctly, you wish to serve different files from Assets.at() based on your isMobile test, but can't work out how to get the types to line up?
Assets.at() returns an Action[AnyContent] which is at its simplest a function from Request[AnyContent] to Future[Result].
So knowing this, we just need a couple of tweaks to your index function and everything fits:
def index(any: String) = Action.async { request: Request[AnyContent] =>
if(isMobile(request)) {
Assets.at(path = "/public", file = "mobile.html").apply(request)
} else {
Assets.at(path = "/public", file = "desktop.html")(request)
}
}
Explanations:
The inner call returns a Future[Result] so we've become an Action.async
implicit is not needed here so I dropped it
An Action needs to be given a Request not a RequestHeader so I changed that
I'm showing both .apply(request) and just (request) - they are exactly the same
In my application we are using RequireJs and Backbone
So a typical model might look like the following in a separate file so we can attempt to modularize this application better:
define([
'Require statements here if needed'
],
function() {
var toDo = Backbone.Model.extend({
// Model Service Url
url: function () {
var base = 'apps/dashboard/todo';
return (this.isNew()) ? base : base + "/" + this.id;
},
// Other functions here
});
return toDo;
});
Right now we keep each model and collection in its own file and return the Model/Collection as above. The bigger the application gets the harder it is to keep the files and naming convention straight. I would like to combine similar collections/models together into 1 file and maintain the modularity.
What is a good way to achieve this? Or should I stick with them in separate files and get a better naming convention? If so, what do you use for your naming convention between similar Collections/Models?
This is the way I structure my application :
I have a javascript path, which I'm minifying on demand by the server when client access "/javascript", so I have only one script line in my index.html :
<script src='/javascript'></script>
My directory structure of /javascript is the following :
application.js
router.js
lib/
lib/jquery.js
lib/underscore.js
lib/backbone.js
lib/require.js
lib/other_libraries.js
views/
views/navigation.js
views/overlay.js
views/content.js
views/page
views/page/constructor.js
views/page/elements
views/page/elements/constructor.js
views/page/elements/table.js
views/page/elements/form.js
views/page/elements/actions.js
collections/
collections/paginated.js
All those files are minified and loaded from client in the first request. By doing this I have a lot of my code already loaded in my browser before the application makes any requests with RequireJS.
On my server I have a directory, which is also public, but is for dynamic javascript loading and templates ( it is accessed by demand from the application at any given time ). The directory looks like this :
dynamic/
dynamic/javascript
dynamic/javascript/pages
dynamic/javascript/pages/articles.js
dynamic/templates
dynamic/templates/pages
dynamic/templates/pages/articles.hb
dynamic/templates/pages/items.hb
When my server requests "/templates/pages/articles.hb" the server returns JSON object which looks like this :
{ html : "<div class='page' id='articles'>blah blah blah</div>", javascript : "javascript/pages/articles.js" }
And when my client app receives "javascript" property in the returned JSON object it triggers a RequireJS request
if ( typeof json.javascript === string ) {
require([ json.javascript ], function(constructor) {
window.app.page = new constructor();
});
}
In the dynamic/javascript/pages/articles.js I have something like :
define(function() {
var Model = Backbone.Model.extend({});
// Collections.Paginated is in fact the Collection defined by /javascript/collection/paginated.js
// it is already loaded via the initial javascript loading
var Collection = Collections.Paginated.extend({
url : '/api/articles'
});
// Views.Page is in fact the View defined by /javascript/views/page/constructor.js
// it is also already loaded via the initial javascript loading
var articles = Views.Page.extend({
collection : Collection,
initialize: function(options) {
this.collection = new Collection();
});
});
return articles;
});
Pretty much that's it. I have minimum requests with RequireJS, because every time you hit require('something.js') it makes a request to the server, which is not good for your application speed.
So the exact answer of your question ( in my opinion ) is : You should make your initial loaded files separated as much as possible, but later loaded files with requireJS should be as small as possible to save traffic.