शुरू करने से पहले, कृपया सुनिश्चित करें कि आप समझते हैं कि Google को क्या आवश्यकता है , विशेष रूप से सुंदर और बदसूरत का उपयोग URL का उपयोग। अब कार्यान्वयन को देखते हैं:
ग्राहक की ओर
क्लाइंट की तरफ आपके पास केवल एक ही html पृष्ठ होता है जो AJAX कॉल के माध्यम से गतिशील रूप से सर्वर के साथ इंटरैक्ट करता है। एसपीए जो है। a
क्लाइंट साइड में सभी टैग गतिशील रूप से मेरे एप्लिकेशन में बनाए गए हैं, हम बाद में देखेंगे कि सर्वर में Google के बॉट के लिए इन लिंक को कैसे बनाया जाए। ऐसी प्रत्येक a
टैग की जरूरत है एक के लिए सक्षम होने के लिए pretty URL
में href
इतना है कि गूगल के बॉट यह क्रॉल करेगा टैग। href
जब ग्राहक उस पर क्लिक करता है, तब भी आप उस भाग का उपयोग नहीं करना चाहते (भले ही आप चाहते हैं कि सर्वर उसे पार्स करने में सक्षम हो, हम बाद में देखेंगे), क्योंकि हम लोड करने के लिए एक नया पृष्ठ नहीं चाहते हैं। केवल AJAX कॉल करने के लिए पृष्ठ के भाग में प्रदर्शित होने के लिए कुछ डेटा प्राप्त करना और जावास्क्रिप्ट के माध्यम से URL बदलना (जैसे HTML5 pushstate
या साथ का उपयोग करना Durandaljs
)। तो, हम दोनों एक हैhref
onclick
उपयोगकर्ता के लिए लिंक पर क्लिक करने पर भी कार्य करता है। अब, चूंकि मैं उपयोग करता push-state
हूं मुझे #
URL पर कोई भी नहीं चाहिए , इसलिए एक विशिष्ट a
टैग इस तरह दिखाई दे सकता है:
<a href="http://www.xyz.com/#!/category/subCategory/product111" onClick="loadProduct('category','subCategory','product111')>see product111...</a>
'श्रेणी' और 'उपश्रेणी' शायद अन्य वाक्यांश होंगे, जैसे 'संचार' और 'फोन' या 'कंप्यूटर'। और एक बिजली के उपकरणों की दुकान के लिए 'लैपटॉप'। जाहिर है कि कई अलग-अलग श्रेणियां और उप श्रेणियां होंगी। जैसा कि आप देख सकते हैं, लिंक सीधे श्रेणी, उप श्रेणी और उत्पाद के लिए है, न कि अतिरिक्त मापदंडों के रूप में एक विशिष्ट 'स्टोर' संस्करण के रूप में http://www.xyz.com/store/category/subCategory/product111
। ऐसा इसलिए है क्योंकि मैं छोटे और सरल लिंक पसंद करता हूं। तात्पर्य यह है कि मैं अपने 'पृष्ठ' अर्थात '' के समान नाम वाली श्रेणी नहीं रखूंगा।
मैं AJAX ( onclick
भाग) के माध्यम से डेटा को लोड करने के तरीके में नहीं जाऊंगा , इसे Google पर खोजूंगा, कई अच्छे स्पष्टीकरण हैं। यहाँ केवल एक महत्वपूर्ण बात जिसका मैं उल्लेख करना चाहता हूं वह यह है कि जब उपयोगकर्ता इस लिंक पर क्लिक करता है, तो मैं चाहता हूं कि ब्राउज़र में URL इस तरह दिखे:
http://www.xyz.com/category/subCategory/product111
। और यह URL सर्वर पर नहीं भेजा जाता है! याद रखें, यह एक एसपीए है जहां ग्राहक और सर्वर के बीच सभी इंटरैक्शन AJAX के माध्यम से किए जाते हैं, कोई लिंक नहीं है! सभी 'पृष्ठ' क्लाइंट साइड पर लागू किए जाते हैं, और अलग-अलग URL सर्वर पर कॉल नहीं करते हैं (सर्वर को यह जानने की आवश्यकता है कि इन URL को कैसे संभालना है, जब वे किसी अन्य साइट से आपकी साइट पर बाहरी लिंक के रूप में उपयोग किए जाते हैं,) हम देखेंगे कि बाद में सर्वर साइड भाग पर)। अब, यह डर्न्डल द्वारा आश्चर्यजनक रूप से संभाला गया है। मैं दृढ़ता से इसकी सिफारिश करता हूं, लेकिन यदि आप अन्य तकनीकों को पसंद करते हैं तो आप इस हिस्से को भी छोड़ सकते हैं। यदि आप इसे चुनते हैं, और आप भी मेरी तरह वेब के लिए MS Visual Studio Express 2012 का उपयोग कर रहे हैं, तो आप Durandal स्टार्टर किट स्थापित कर सकते हैं , और वहाँ, shell.js
इस तरह से कुछ का उपयोग कर सकते हैं:
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 });
}
};
});
यहाँ ध्यान देने योग्य कुछ महत्वपूर्ण बातें हैं:
- पहला मार्ग
route:''
उस URL के लिए है, जिसमें कोई अतिरिक्त डेटा नहीं है, यानी http://www.xyz.com
। इस पृष्ठ में आप AJAX का उपयोग करके सामान्य डेटा लोड करते हैं। a
इस पृष्ठ में वास्तव में कोई टैग नहीं हो सकता है । आप निम्नलिखित टैग जोड़ना चाहेंगे ताकि Google के बॉट को पता चले कि उसके साथ क्या करना है
<meta name="fragment" content="!">
:। यह टैग Google के बॉट को URL में बदल www.xyz.com?_escaped_fragment_=
देगा, जिसे हम बाद में देखेंगे।
- 'के बारे में' मार्ग अन्य उदाहरणों के लिए एक लिंक है, जिसे आप अपने वेब एप्लिकेशन पर चाहते हैं।
- अब, मुश्किल हिस्सा यह है कि कोई 'श्रेणी' मार्ग नहीं है, और कई अलग-अलग श्रेणियां हो सकती हैं - जिनमें से कोई भी पूर्वनिर्धारित मार्ग नहीं है। यह वह जगह है जहाँ
mapUnknownRoutes
यह इन अज्ञात मार्गों को 'स्टोर' मार्ग पर मैप करता है और किसी भी 'को हटाता है!' URL के मामले में यह pretty URL
google के seach इंजन द्वारा उत्पन्न होता है। 'स्टोर' मार्ग जानकारी को 'टुकड़े' की संपत्ति में ले जाता है और डेटा प्राप्त करने, उसे प्रदर्शित करने और URL को स्थानीय रूप से बदलने के लिए AJAX कॉल करता है। मेरे आवेदन में, मैं ऐसे हर कॉल के लिए एक अलग पेज लोड नहीं करता हूं; मैं केवल उस पृष्ठ के हिस्से को बदलता हूं जहाँ यह डेटा प्रासंगिक है और स्थानीय रूप से URL भी बदलता है।
- ध्यान दें,
pushState:true
जो ड्यूरंडल को पुश स्टेट यूआरएल का उपयोग करने का निर्देश देता है।
यह सब हम ग्राहक पक्ष में की जरूरत है। इसे हैशेड URL के साथ भी लागू किया जा सकता है (Durandal में आप इसके लिए सरल निष्कासन करते हैं pushState:true
)। अधिक जटिल हिस्सा (कम से कम मेरे लिए ...) सर्वर हिस्सा था:
सर्वर साइड
मैं नियंत्रकों के MVC 4.5
साथ सर्वर साइड पर उपयोग कर रहा हूं WebAPI
। सर्वर को वास्तव में 3 प्रकार के URL को संभालने की आवश्यकता है: Google द्वारा उत्पन्न - दोनों pretty
और ugly
क्लाइंट के ब्राउज़र में दिखाई देने वाले एक ही प्रारूप के साथ एक 'सरल' URL। ऐसा करने के तरीके पर ध्यान दें:
सुंदर URL और 'सरल' पहले सर्वर द्वारा व्याख्या किए जाते हैं जैसे कि एक गैर-मौजूद नियंत्रक को संदर्भित करने का प्रयास करना। सर्वर कुछ ऐसा देखता है http://www.xyz.com/category/subCategory/product111
और 'श्रेणी' नामक नियंत्रक की तलाश करता है। इसलिए web.config
मैं निम्नलिखित लाइन को एक विशेष त्रुटि नियंत्रक से पुनर्निर्देशित करने के लिए जोड़ता हूं:
<customErrors mode="On" defaultRedirect="Error">
<error statusCode="404" redirect="Error" />
</customErrors><br/>
अब, यह URL को कुछ इस तरह से रूपांतरित करता है http://www.xyz.com/Error?aspxerrorpath=/category/subCategory/product111
:। मैं चाहता हूं कि URL को क्लाइंट के पास भेजा जाए जो AJAX के माध्यम से डेटा लोड करेगा, इसलिए यहां ट्रिक को डिफ़ॉल्ट 'इंडेक्स' नियंत्रक को कॉल करना है जैसे कि किसी भी नियंत्रक को संदर्भित नहीं करना; मैं सभी 'श्रेणी' और 'उपश्रेणी' मापदंडों से पहले URL में एक हैश जोड़कर ऐसा करता हूं ; हैशेड URL को डिफॉल्ट 'इंडेक्स' कंट्रोलर को छोड़कर किसी विशेष कंट्रोलर की आवश्यकता नहीं होती है और डेटा क्लाइंट को भेजा जाता है जो तब हैश को हटा देता है और हैश के बाद जानकारी को AJAX के माध्यम से डेटा लोड करने के लिए उपयोग करता है। यहाँ त्रुटि हैंडलर नियंत्रक कोड है:
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;
}
}
}
लेकिन बदसूरत यूआरएल के बारे में क्या ? ये google के bot द्वारा बनाए गए हैं और उन्हें सादे HTML को वापस करना चाहिए जिसमें उपयोगकर्ता द्वारा देखे गए सभी डेटा शामिल हैं। इसके लिए मैं फैंटमज का इस्तेमाल करता हूं । फैंटम एक सिर रहित ब्राउज़र है जो ब्राउज़र क्लाइंट साइड पर कर रहा है - लेकिन सर्वर साइड पर। दूसरे शब्दों में, प्रेत जानता है (अन्य बातों के अलावा) एक URL के माध्यम से वेब पेज कैसे प्राप्त करें, इसमें सभी जावास्क्रिप्ट कोड चलाने के साथ-साथ (AJAX कॉल के माध्यम से डेटा प्राप्त करने सहित) पार्स करें, और आपको प्रतिबिंबित करने वाले HTML को वापस दें। डोम। यदि आप MS Visual Studio Express का उपयोग कर रहे हैं, तो आप बहुत से इस लिंक के माध्यम से प्रेत स्थापित करना चाहते हैं ।
लेकिन पहले, जब एक बदसूरत URL सर्वर पर भेजा जाता है, तो हमें उसे पकड़ना चाहिए; इसके लिए, मैंने निम्न फ़ाइल 'फ़ोल्डर_स्टार्ट' फ़ोल्डर में जोड़ा:
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;
}
}
}
इसे 'FilterConfig.cs' से '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());
}
}
}
जैसा कि आप देख सकते हैं, 'AjaxCrawlableAttribute' मार्ग बदसूरत URL को 'HtmlSnapshot' नामक कंट्रोलर में रूट करता है, और यहाँ यह कंट्रोलर है:
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();
}
}
}
संबंधित view
बहुत सरल है, कोड की सिर्फ एक पंक्ति:
@Html.Raw( ViewBag.result )
जैसा कि आप नियंत्रक में देख सकते हैं, प्रेत createSnapshot.js
मेरे द्वारा बनाए गए फ़ोल्डर के तहत एक जावास्क्रिप्ट फ़ाइल को लोड करता है जिसका नाम है seo
। यहाँ यह जावास्क्रिप्ट फ़ाइल है:
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);
मैं पहले पेज के लिए थॉमस डेविस को धन्यवाद देना चाहता हूं जहां मुझे मूल कोड मिला :-)।
आपको यहाँ कुछ अजीब लगेगा: checkLoaded()
फंक्शन पेज को तब तक फिर से लोड करता रहता है, जब तक कि फ़ंक्शन सही न हो जाए। ऐसा क्यों है? इसका कारण यह है कि मेरा विशिष्ट एसपीए सभी डेटा प्राप्त करने के लिए कई AJAX कॉल करता है और इसे अपने पेज पर DOM में रखता है, और फ़ैंटम को यह पता नहीं चल सकता है कि DOM के HTML प्रतिबिंब को वापस करने से पहले सभी कॉल कब पूरी हो चुकी हैं। एजेएएक्स कॉल के फाइनल होने के बाद मैंने यहां क्या किया <span id='compositionComplete'></span>
, मैं एक जोड़ देता हूं , ताकि यदि यह टैग मौजूद हो तो मुझे पता है कि डोम पूरा हो गया है। मैं ड्यूरंडल की compositionComplete
घटना के जवाब में ऐसा करता हूं , यहां देखेंअधिक जानकारी के लिए। अगर यह 10 सेकंड के साथ नहीं होता है तो मैं हार जाता हूं (यह केवल एक सेकंड में सबसे अधिक समय लेना चाहिए)। HTML में लौटे सभी लिंक हैं जो उपयोगकर्ता ब्राउज़र में देखता है। स्क्रिप्ट ठीक से काम नहीं करेगी क्योंकि <script>
HTML स्नैपशॉट में मौजूद टैग सही URL का संदर्भ नहीं देते हैं। इसे जावास्क्रिप्ट फ़ैंटम फ़ाइल में भी बदला जा सकता है, लेकिन मुझे नहीं लगता कि यह necassary है क्योंकि HTML स्नैपशॉट का उपयोग केवल a
लिंक प्राप्त करने के लिए Google द्वारा किया जाता है न कि जावास्क्रिप्ट को चलाने के लिए; इन कड़ियों करना संदर्भ एक सुंदर यूआरएल, और तथ्य यह है कि अगर, यदि आप एक ब्राउज़र में एचटीएमएल स्नैपशॉट देखने की कोशिश, आप जावास्क्रिप्ट त्रुटियां मिलेंगी लेकिन सभी लिंक ठीक से काम करते हैं और एक बहुत URL इस समय के साथ एक बार फिर से सर्वर को निर्देशित करेंगे पूरी तरह से काम कर रहे पृष्ठ।
यह बात है। अब सर्वर को पता है कि कैसे सुंदर और बदसूरत दोनों यूआरएल को संभालना है, जिसमें सर्वर और क्लाइंट दोनों पर पुश-स्टेट सक्षम है। सभी बदसूरत यूआरएल को प्रेत का उपयोग करके उसी तरह से व्यवहार किया जाता है, इसलिए प्रत्येक प्रकार के कॉल के लिए एक अलग नियंत्रक बनाने की आवश्यकता नहीं है।
एक बात आप परिवर्तन करना पसंद कर सकते एक सामान्य 'श्रेणी / उपश्रेणी / उत्पाद' कॉल करने के लिए नहीं है, लेकिन एक 'दुकान' जिससे वे सभी तरह दिखाई देगा जोड़ने के लिए है: http://www.xyz.com/store/category/subCategory/product111
। यह मेरे समाधान में समस्या से बचना होगा कि सभी अमान्य URL को ऐसे माना जाता है जैसे कि वे वास्तव में 'अनुक्रमणिका' नियंत्रक को कॉल करते हैं, और मुझे लगता है कि इन्हें तब संभाला जा सकता है, जबकि 'स्टोर' नियंत्रक के भीतर जो web.config
मैंने दिखाया उसके अतिरिक्त नहीं ।