Sebelum memulai, pastikan Anda memahami apa yang dibutuhkan Google , khususnya penggunaan URL yang cantik dan jelek . Sekarang mari kita lihat implementasinya:
Sisi klien
Di sisi klien Anda hanya memiliki satu halaman html yang berinteraksi dengan server secara dinamis melalui panggilan AJAX. itu tentang SPA. Semua a
tag di sisi klien dibuat secara dinamis di aplikasi saya, nanti kita akan melihat bagaimana membuat tautan ini terlihat oleh bot google di server. Setiap seperti a
kebutuhan tag untuk dapat memiliki pretty URL
dalam href
tag sehingga bot bahwa google akan merangkak itu. Anda tidak ingin href
bagian itu digunakan ketika klien mengkliknya (meskipun Anda ingin server dapat menguraikannya, kami akan melihatnya nanti), karena kami mungkin tidak ingin halaman baru dimuat, hanya untuk membuat panggilan AJAX mendapatkan beberapa data untuk ditampilkan di bagian halaman dan mengubah URL melalui javascript (misalnya menggunakan HTML5 pushstate
atau dengan Durandaljs
). Jadi, kami memiliki keduanyahref
atribut untuk google dan juga onclick
yang melakukan pekerjaan ketika pengguna mengklik tautan. Sekarang, karena saya menggunakan push-state
saya tidak ingin ada #
di URL, jadi a
tag khas mungkin terlihat seperti ini:
<a href="http://www.xyz.com/#!/category/subCategory/product111" onClick="loadProduct('category','subCategory','product111')>see product111...</a>
'kategori' dan 'subkategori' mungkin akan menjadi frasa lain, seperti 'komunikasi' dan 'telepon' atau 'komputer' dan 'laptop' untuk toko peralatan listrik. Jelas akan ada banyak kategori dan sub kategori yang berbeda. Seperti yang Anda lihat, tautan langsung ke kategori, sub kategori dan produk, bukan sebagai parameter tambahan ke laman 'toko' tertentu seperti http://www.xyz.com/store/category/subCategory/product111
. Ini karena saya lebih suka tautan yang lebih pendek dan lebih sederhana. Ini menyiratkan bahwa saya tidak akan ada kategori dengan nama yang sama dengan salah satu 'halaman' saya, yaitu '
Saya tidak akan membahas cara memuat data melalui AJAX ( onclick
bagian), mencarinya di google, ada banyak penjelasan bagus. Satu-satunya hal penting di sini yang ingin saya sebutkan adalah ketika pengguna mengklik tautan ini, saya ingin URL di browser terlihat seperti ini:
http://www.xyz.com/category/subCategory/product111
. Dan ini URL tidak dikirim ke server! ingat, ini adalah SPA di mana semua interaksi antara klien dan server dilakukan melalui AJAX, tidak ada tautan sama sekali! semua 'halaman' diimplementasikan pada sisi klien, dan URL yang berbeda tidak membuat panggilan ke server (server tidak perlu tahu bagaimana menangani URL ini jika mereka digunakan sebagai tautan eksternal dari situs lain ke situs Anda, kita akan melihatnya nanti di bagian sisi server). Sekarang, ini ditangani dengan luar biasa oleh Durandal. Saya sangat merekomendasikannya, tetapi Anda juga dapat melewati bagian ini jika Anda lebih suka teknologi lainnya. Jika Anda memilihnya, dan Anda juga menggunakan MS Visual Studio Express 2012 untuk Web seperti saya, Anda dapat menginstal Durandal Starter Kit , dan di sana, di shell.js
, gunakan sesuatu seperti ini:
define(['plugins/router', 'durandal/app'], function (router, app) {
return {
router: router,
activate: function () {
router.map([
{ route: '', title: 'Store', moduleId: 'viewmodels/store', nav: true },
{ route: 'about', moduleId: 'viewmodels/about', nav: true }
])
.buildNavigationModel()
.mapUnknownRoutes(function (instruction) {
instruction.config.moduleId = 'viewmodels/store';
instruction.fragment = instruction.fragment.replace("!/", ""); // for pretty-URLs, '#' already removed because of push-state, only ! remains
return instruction;
});
return router.activate({ pushState: true });
}
};
});
Ada beberapa hal penting yang perlu diperhatikan di sini:
- Rute pertama (dengan
route:''
) adalah untuk URL yang tidak memiliki data tambahan di dalamnya, yaitu http://www.xyz.com
. Di halaman ini Anda memuat data umum menggunakan AJAX. Sebenarnya tidak ada a
tag sama sekali di halaman ini. Anda akan ingin menambahkan tag berikut sehingga bot bahwa google akan tahu apa yang harus dilakukan dengan itu:
<meta name="fragment" content="!">
. Tag ini akan membuat bot google mengubah URL www.xyz.com?_escaped_fragment_=
yang akan kita lihat nanti.
- Rute 'tentang' hanyalah contoh tautan ke 'laman' lain yang mungkin Anda inginkan di aplikasi web Anda.
- Sekarang, bagian yang sulit adalah tidak ada rute 'kategori', dan mungkin ada banyak kategori berbeda - tidak ada yang memiliki rute yang telah ditentukan. Di sinilah
mapUnknownRoutes
masuk. Ini memetakan rute yang tidak diketahui ini ke rute 'toko' dan juga menghapus semua '!' dari URL jika itu pretty URL
dihasilkan oleh mesin pencari google. Rute 'toko' mengambil info di properti 'fragmen' dan membuat panggilan AJAX untuk mendapatkan data, menampilkannya, dan mengubah URL secara lokal. Dalam aplikasi saya, saya tidak memuat halaman yang berbeda untuk setiap panggilan seperti itu; Saya hanya mengubah bagian halaman di mana data ini relevan dan juga mengubah URL secara lokal.
- Perhatikan
pushState:true
yang menginstruksikan Durandal untuk menggunakan URL push state.
Ini semua yang kami butuhkan di sisi klien. Itu dapat diimplementasikan juga dengan URL hash (di Durandal Anda cukup menghapusnya pushState:true
). Bagian yang lebih kompleks (setidaknya untuk saya ...) adalah bagian server:
Sisi server
Saya menggunakan MVC 4.5
di sisi server dengan WebAPI
pengontrol. Server sebenarnya perlu menangani 3 jenis URL: yang dihasilkan oleh google - keduanya pretty
dan ugly
dan juga URL 'sederhana' dengan format yang sama dengan yang muncul di browser klien. Mari kita lihat bagaimana melakukan ini:
URL yang cantik dan yang 'sederhana' pertama kali ditafsirkan oleh server seolah mencoba mereferensikan pengontrol yang tidak ada. Server melihat sesuatu seperti http://www.xyz.com/category/subCategory/product111
dan mencari pengontrol bernama 'kategori'. Jadi, web.config
saya menambahkan baris berikut untuk mengarahkan ini ke controller penanganan kesalahan tertentu:
<customErrors mode="On" defaultRedirect="Error">
<error statusCode="404" redirect="Error" />
</customErrors><br/>
Sekarang, ini mengubah URL ke sesuatu seperti: http://www.xyz.com/Error?aspxerrorpath=/category/subCategory/product111
. Saya ingin URL dikirim ke klien yang akan memuat data melalui AJAX, jadi triknya di sini adalah memanggil pengontrol 'indeks' default seolah-olah tidak mereferensikan pengontrol apa pun; Saya melakukannya dengan menambahkan hash ke URL sebelum semua parameter 'kategori' dan 'subkategori'; URL hash tidak memerlukan pengontrol khusus kecuali pengontrol 'indeks' default dan data dikirim ke klien yang kemudian menghapus hash dan menggunakan info setelah hash untuk memuat data melalui AJAX. Berikut adalah kode pengendali penangan kesalahan:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Routing;
namespace eShop.Controllers
{
public class ErrorController : ApiController
{
[HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH"), AllowAnonymous]
public HttpResponseMessage Handle404()
{
string [] parts = Request.RequestUri.OriginalString.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries);
string parameters = parts[ 1 ].Replace("aspxerrorpath=","");
var response = Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Location = new Uri(parts[0].Replace("Error","") + string.Format("#{0}", parameters));
return response;
}
}
}
Tapi bagaimana dengan URL Jelek ? Ini dibuat oleh bot google dan harus mengembalikan HTML biasa yang berisi semua data yang dilihat pengguna di browser. Untuk ini saya menggunakan phantomjs . Phantom adalah peramban tanpa kepala yang melakukan peramban di sisi klien - tetapi di sisi server. Dengan kata lain, hantu tahu (antara lain) cara mendapatkan halaman web melalui URL, menguraikannya termasuk menjalankan semua kode javascript di dalamnya (serta mendapatkan data melalui panggilan AJAX), dan memberikan Anda kembali HTML yang mencerminkan DOM. Jika Anda menggunakan MS Visual Studio Express Anda banyak keinginan untuk menginstal hantu melalui ini Link .
Tapi pertama-tama, ketika URL jelek dikirim ke server, kita harus menangkapnya; Untuk ini, saya menambahkan ke folder 'App_start' file berikut:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace eShop.App_Start
{
public class AjaxCrawlableAttribute : ActionFilterAttribute
{
private const string Fragment = "_escaped_fragment_";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.RequestContext.HttpContext.Request;
if (request.QueryString[Fragment] != null)
{
var url = request.Url.ToString().Replace("?_escaped_fragment_=", "#");
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } });
}
return;
}
}
}
Ini disebut dari 'filterConfig.cs' juga di 'App_start':
using System.Web.Mvc;
using eShop.App_Start;
namespace eShop
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AjaxCrawlableAttribute());
}
}
}
Seperti yang Anda lihat, 'AjaxCrawlableAttribute' merutekan URL yang jelek ke pengontrol bernama 'HtmlSnapshot', dan inilah pengontrol ini:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace eShop.Controllers
{
public class HtmlSnapshotController : Controller
{
public ActionResult returnHTML(string url)
{
string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
var startInfo = new ProcessStartInfo
{
Arguments = String.Format("{0} {1}", Path.Combine(appRoot, "seo\\createSnapshot.js"), url),
FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"),
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
StandardOutputEncoding = System.Text.Encoding.UTF8
};
var p = new Process();
p.StartInfo = startInfo;
p.Start();
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
ViewData["result"] = output;
return View();
}
}
}
Yang terkait view
sangat sederhana, hanya satu baris kode:
@Html.Raw( ViewBag.result )
Seperti yang dapat Anda lihat di controller, phantom memuat file javascript bernama di createSnapshot.js
bawah folder yang saya buat bernama seo
. Ini adalah file javascript ini:
var page = require('webpage').create();
var system = require('system');
var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();
page.onResourceReceived = function (response) {
if (requestIds.indexOf(response.id) !== -1) {
lastReceived = new Date().getTime();
responseCount++;
requestIds[requestIds.indexOf(response.id)] = null;
}
};
page.onResourceRequested = function (request) {
if (requestIds.indexOf(request.id) === -1) {
requestIds.push(request.id);
requestCount++;
}
};
function checkLoaded() {
return page.evaluate(function () {
return document.all["compositionComplete"];
}) != null;
}
// Open the page
page.open(system.args[1], function () { });
var checkComplete = function () {
// We don't allow it to take longer than 5 seconds but
// don't return until all requests are finished
if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) {
clearInterval(checkCompleteInterval);
var result = page.content;
//result = result.substring(0, 10000);
console.log(result);
//console.log(results);
phantom.exit();
}
}
// Let us check to see if the page is finished rendering
var checkCompleteInterval = setInterval(checkComplete, 300);
Pertama saya ingin mengucapkan terima kasih kepada Thomas Davis untuk halaman di mana saya mendapatkan kode dasar dari :-).
Anda akan melihat sesuatu yang aneh di sini: hantu terus memuat ulang halaman sampai checkLoaded()
fungsinya kembali benar. Mengapa demikian? ini karena SPA spesifik saya membuat beberapa panggilan AJAX untuk mendapatkan semua data dan menempatkannya di DOM di halaman saya, dan hantu tidak tahu kapan semua panggilan telah selesai sebelum mengembalikan saya kembali refleksi HTML dari DOM. Apa yang saya lakukan di sini adalah setelah panggilan AJAX terakhir saya tambahkan <span id='compositionComplete'></span>
, sehingga jika tag ini ada saya tahu DOM selesai. Saya melakukan ini sebagai respons terhadap compositionComplete
acara Durandal , lihat di siniuntuk lebih. Jika ini tidak terjadi dalam 10 detik, saya menyerah (seharusnya hanya membutuhkan waktu satu detik hingga yang paling banyak). HTML yang dikembalikan berisi semua tautan yang dilihat pengguna di peramban. Script tidak akan berfungsi dengan baik karena <script>
tag yang ada di snapshot HTML tidak mereferensikan URL yang tepat. Ini dapat diubah juga di file phantom javascript, tapi saya rasa ini tidak perlu karena HTML snapshort hanya digunakan oleh google untuk mendapatkan a
tautan dan bukan untuk menjalankan javascript; link ini melakukan referensi URL yang cukup, dan jika fakta, jika Anda mencoba untuk melihat snapshot HTML di browser, Anda akan mendapatkan error javascript tapi semua link akan bekerja dengan baik dan mengarahkan Anda ke server sekali lagi dengan URL yang cukup saat ini dapatkan halaman yang sepenuhnya berfungsi.
Ini dia. Sekarang server tahu cara menangani URL cantik dan jelek, dengan push-state diaktifkan di server dan klien. Semua URL jelek diperlakukan dengan cara yang sama menggunakan hantu sehingga tidak perlu membuat pengontrol terpisah untuk setiap jenis panggilan.
Satu hal yang mungkin lebih memilih untuk perubahan tidak untuk membuat panggilan umum 'kategori / subkategori / produk' tetapi menambahkan 'toko' sehingga link akan terlihat seperti: http://www.xyz.com/store/category/subCategory/product111
. Ini akan menghindari masalah dalam solusi saya bahwa semua URL yang tidak valid diperlakukan seolah-olah mereka benar-benar panggilan ke pengontrol 'indeks', dan saya kira ini dapat ditangani kemudian di dalam pengontrol 'toko' tanpa tambahan pada yang web.config
saya perlihatkan di atas. .