mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-22 17:47:53 +00:00
Implement cache priming prototype
This commit is contained in:
44
Microsoft.AspNet.AngularServices/PrimeCacheHelper.cs
Normal file
44
Microsoft.AspNet.AngularServices/PrimeCacheHelper.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Mvc.Rendering;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNet.AngularServices {
|
||||
public static class PrimeCacheHelper {
|
||||
public static async Task<HtmlString> PrimeCache(this IHtmlHelper html, string url) {
|
||||
// TODO: Consider deduplicating the PrimeCache calls (that is, if there are multiple requests to precache
|
||||
// the same URL, only return nonempty for one of them). This will make it easier to auto-prime-cache any
|
||||
// HTTP requests made during server-side rendering, without risking unnecessary duplicate requests.
|
||||
|
||||
if (string.IsNullOrEmpty(url)) {
|
||||
throw new ArgumentException("Value cannot be null or empty", "url");
|
||||
}
|
||||
|
||||
try {
|
||||
var request = html.ViewContext.HttpContext.Request;
|
||||
var baseUri = new Uri(string.Concat(request.Scheme, "://", request.Host.ToUriComponent(), request.PathBase.ToUriComponent(), request.Path.ToUriComponent(), request.QueryString.ToUriComponent()));
|
||||
var fullUri = new Uri(baseUri, url);
|
||||
var response = await new HttpClient().GetAsync(fullUri.ToString());
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
return new HtmlString(FormatAsScript(url, response.StatusCode, responseBody));
|
||||
} catch (Exception ex) {
|
||||
var logger = (ILogger)html.ViewContext.HttpContext.ApplicationServices.GetService(typeof (ILogger));
|
||||
if (logger != null) {
|
||||
logger.LogWarning("Error priming cache for URL: " + url, ex);
|
||||
}
|
||||
return new HtmlString(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatAsScript(string url, HttpStatusCode responseStatusCode, string responseBody)
|
||||
{
|
||||
return string.Format(@"<script>window.__preCachedResponses = window.__preCachedResponses || {{}}; window.__preCachedResponses[{0}] = {1};</script>",
|
||||
JsonConvert.SerializeObject(url),
|
||||
JsonConvert.SerializeObject(new { statusCode = responseStatusCode, body = responseBody })
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@ builder.config({
|
||||
'angular2-aspnet/*': 'dist/*'
|
||||
},
|
||||
meta: {
|
||||
'angular2/*': { build: false }
|
||||
'angular2/*': { build: false },
|
||||
'@reactivex/*': { build: false }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular2-aspnet",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.3",
|
||||
"description": "Helpers for Angular 2 apps built on ASP.NET",
|
||||
"main": "./dist/Exports",
|
||||
"scripts": {
|
||||
@@ -15,7 +15,7 @@
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"angular2": "^2.0.0-alpha.44"
|
||||
"angular2": "2.0.0-alpha.44"
|
||||
},
|
||||
"devDependencies": {
|
||||
"systemjs-builder": "^0.14.11",
|
||||
|
||||
63
Microsoft.AspNet.AngularServices/npm/src/CachePrimedHttp.ts
Normal file
63
Microsoft.AspNet.AngularServices/npm/src/CachePrimedHttp.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { provide, Injectable, Provider } from 'angular2/core';
|
||||
import { Connection, ConnectionBackend, Http, XHRBackend, RequestOptions, Request, RequestMethods, Response, ResponseOptions, ReadyStates } from 'angular2/http';
|
||||
|
||||
@Injectable()
|
||||
export class CachePrimedConnectionBackend extends ConnectionBackend {
|
||||
private _preCachedResponses: PreCachedResponses;
|
||||
|
||||
constructor(private _underlyingBackend: ConnectionBackend, private _baseResponseOptions: ResponseOptions) {
|
||||
super();
|
||||
this._preCachedResponses = (<any>window).__preCachedResponses || {};
|
||||
}
|
||||
|
||||
public createConnection(request: Request): Connection {
|
||||
let cacheKey = request.url;
|
||||
if (request.method === RequestMethods.Get && this._preCachedResponses.hasOwnProperty(cacheKey)) {
|
||||
return new CacheHitConnection(request, this._preCachedResponses[cacheKey], this._baseResponseOptions);
|
||||
} else {
|
||||
return this._underlyingBackend.createConnection(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CacheHitConnection implements Connection {
|
||||
readyState: ReadyStates;
|
||||
request: Request;
|
||||
response: any;
|
||||
|
||||
constructor (req: Request, cachedResponse: PreCachedResponse, baseResponseOptions: ResponseOptions) {
|
||||
this.request = req;
|
||||
this.readyState = ReadyStates.Done;
|
||||
|
||||
// Workaround for difficulty consuming CommonJS default exports in TypeScript. Note that it has to be a dynamic
|
||||
// 'require', and not an 'import' statement, because the module isn't available on the server.
|
||||
// All this badness goes away with the next update of Angular 2, as it exposes Observable directly from angular2/core.
|
||||
// --
|
||||
// The current version of Angular exposes the following SystemJS module directly (it is *not* coming from the
|
||||
// @reactivex/rxjs NPM package - it's coming from angular2).
|
||||
let obsCtor: any = require('@reactivex/rxjs/dist/cjs/Observable');
|
||||
this.response = new obsCtor(responseObserver => {
|
||||
let response = new Response(new ResponseOptions({ body: cachedResponse.body, status: cachedResponse.statusCode }));
|
||||
responseObserver.next(response);
|
||||
responseObserver.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare var require: any; // Part of the workaround mentioned below. Can remove this after updating Angular.
|
||||
|
||||
interface PreCachedResponses {
|
||||
[url: string]: PreCachedResponse;
|
||||
}
|
||||
|
||||
interface PreCachedResponse {
|
||||
statusCode: number;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export const CACHE_PRIMED_HTTP_PROVIDERS = [
|
||||
provide(Http, {
|
||||
useFactory: (xhrBackend, requestOptions, responseOptions) => new Http(new CachePrimedConnectionBackend(xhrBackend, responseOptions), requestOptions),
|
||||
deps: [XHRBackend, RequestOptions, ResponseOptions]
|
||||
}),
|
||||
];
|
||||
@@ -1 +1,2 @@
|
||||
export * from './CachePrimedHttp';
|
||||
export * from './Validation';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"target": "es5",
|
||||
"sourceMap": false,
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"noLib": false,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"Microsoft.CSharp": "4.0.1-beta-*",
|
||||
"System.Collections": "4.0.11-beta-*",
|
||||
"System.Linq": "4.0.1-beta-*",
|
||||
"System.Net.Http": "4.0.1-beta-*",
|
||||
"System.Runtime": "4.0.21-beta-*",
|
||||
"System.Threading": "4.0.11-beta-*"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user