This is jqMVC# – CNBLOGS Google Tracer Sample

In previous post - This is jqMVC# - Definition & Summary, I briefly introduced what is jqMVC#. In this post, I’ll show you a “CNBLOGS Google Tracer” sample application which is applying the jqMVC# architecture.

Function

image

“Google Tracer” is a HTML & JavaScript application, tracing the blog you are reading with Google Web Search. You should be able to see it running in the left navigation panel in my blog. It is visible only when you are reading any of my blog posts. Technically, it is just googling the title of a post as the search keyword through the Google AJAX Search API.

The function of this application is pretty simple, I believe any of you could implement a similar feature in even half an hour. The points I really want to demonstrate are the benefits from applying the jqMVC# architecture.

MVC Pattern In Script# based JavaScript

Please realize we are writing C# code which is to be compiled info JavaScript by Script# compiler. Like in other server-side MVC pattern implementation, we should have Model, View and Controller.

Models here are value objects (in Script#, they are called Records) representing the search response data. They are strong typed and just matching the JSON response of the Google AJAX Search API.

复制代码
Google Tracer Records
[Record]
public sealed class GoogleSearchResponse
{
    
public GoogleSearchResponseData ResponseData;
    
public string ResponseDetails;
    
public int ResponseStatus;
}

[Record]
public sealed class GoogleSearchResponse
{
    
public GoogleSearchResponseData ResponseData;
    
public string ResponseDetails;
    
public int ResponseStatus;
}

[Record]
public sealed class GoogleSearchResponseDataResult
{
    [PreserveCase]
    
public string GsearchResultClass;
    
public string UnescapedUrl;
    
public string Url;
    
public string VisibleUrl;
    
public string CacheUrl;
    
public string Title;
    
public string TitleNoFormatting;
    
public string Content;
}

… 
复制代码

A view logically wraps the data and events of a UI clip implementation. A view is a bridge clearly separating and connecting the pure UI presentation and the controller. Here we define the view interface first:

复制代码
IGoogleTracerView
public interface IGoogleTracerView
{
    
int SearchStart { getset; }
    
string GetSearchKeyword();
    
void RenderSearchResult(GoogleSearchResponse response);
    
event DOMEventHandler ShowMoreResults;
复制代码

 The view interface could have different implementation, representing different but share the same data and event contracts.

Below is our demo view implementation.

复制代码
CnblogsGoogleSearchTracerView
public class CnblogsGoogleSearchTracerView : IGoogleTracerView
{
    
private DOMEventHandler _showMoreResults;
    
private int _searchStart = 0;

    
#region IGoogleTracerView Members

    
public int SearchStart
    {
        
get
        {
            
return _searchStart;
        }
        
set
        {
            _searchStart 
= value;
        }
    }

    
public string GetSearchKeyword()
    {
        
string keyword = (string)(object)JQueryFactory.JQuery(JQuerySelectors.SEARCH_KEYWORD).Text();
        
return keyword;
    }

    
public void RenderSearchResult(GoogleSearchResponse response)
    {
        ((JTemplatePlugin)JQueryFactory.JQuery(JQuerySelectors.SEARCH_RESULTS_PANEL))
            .SetTemplateElement(JTemplateElements.GOOGLE_TRACER)
            .ProcessTemplate(response);

        JQueryFactory.JQuery(JQuerySelectors.SHOW_MORE_RESULTS_BUTTON).Click(_showMoreResults);
    }

    
public event DOMEventHandler ShowMoreResults
    {
        add
        {
            _showMoreResults 
= (DOMEventHandler)Delegate.Combine(_showMoreResults, value);
        }
        remove
        {
            _showMoreResults 
= (DOMEventHandler)Delegate.Remove(_showMoreResults, value);
        }
    }

    
#endregion
复制代码

A controller is responsible for querying data, binding Model and event handlers to View.

复制代码
GoogleTracerController
public class GoogleTracerController
{
    
private IGoogleTracerView _view;

    
#region Properties

    
public IGoogleTracerView View
    {
        
get
        {
            
if (_view == null)
                _view 
= (IGoogleTracerView)Container.GetInstance(typeof(IGoogleTracerView));
            
return _view;
        }
    }

    
#endregion

    
#region Public Methods

    
public void Execute()
    {
        View.ShowMoreResults 
+= new DOMEventHandler(ShowMoreResults);

        LoadSearchResults();
    }

    
public void ShowMoreResults()
    {
        View.SearchStart 
= View.SearchStart + 4;
        LoadSearchResults();
    }

    
public static void GoogleWebSearchCallback(object data)
    {
        ((Dictionary)(
object)Window.Self)["_googlewebsearchresults"= data;
    }

    
#endregion

    
#region Private Methods

    
private void LoadSearchResults()
    {
        
if (string.IsNullOrEmpty(View.GetSearchKeyword().Trim()))
            
return;

        jQuery.GetScript(
            
string.Format(
                SearchUrls.WEB_SEARCH_URL,
                View.GetSearchKeyword().Replace(
"'""").Replace(" ""+").Replace("&""").Replace("?"""),
                View.SearchStart,
                
"NIntegrate.Scripts.Test.Demo.GoogleTracer.Controllers.GoogleTracerController.googleWebSearchCallback"
            ),
            (Function)(
object)new DOMEventHandler(delegate
                {
                    View.RenderSearchResult((GoogleSearchResponse)((Dictionary)(
object)Window.Self)["_googlewebsearchresults"]);
                })
        );
    }

    
#endregion
复制代码

I use a simple Container which decouples the dependency from the controller to the concrete view implementation in the readonly View property of the controller class.

复制代码
Container
public static class Container
{
    
private static Dictionary _cache = new Dictionary();

    
public static void RegisterInstance(Type type, object instance)
    {
        _cache[type.FullName] 
= instance;
    }

    
public static object GetInstance(Type type)
    {
        
return _cache[type.FullName];
    }
复制代码

OK, this is all the implementation. Benefited from the MVC pattern, it is well separated and easy to understand, right? But wait, where is the testing?

Test Driven Development (TDD)

To do integration testing, I created a demo HTML page after all the implementation.

复制代码
GoogleTracer Demo HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
    
<head>
        
<title>GoogleTracer Demo</title>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/jquery-1.3.2.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/jquery-jtemplates.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/sscompat.debug.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/sscorlib.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/ssfx.Core.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/JQuerySharp.debug.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/NIntegrate.Scripts.debug.js"></script>
        
<script type="text/javascript" language="javascript" src="http://www.cnblogs.com/_scripts/NIntegrate.Scripts.Test.debug.js"></script>
    
</head>
    
<body>
        
<div class="post">
            Search keyword: 
<span class="postTitle">teddyma wcf</span>
        
</div>
        
<hr />
        
<textarea id="jtGoogleTracer" style="display: none">
            {#if $T.responseStatus == 200}
                {#foreach $T.responseData.results as result}
                
<href="{$T.result.url}" title="{$T.result.content.replace('"', '&quot;')}">{$T.result.titleNoFormatting}</a><br />
                {#/for}
                
<b><href="javascript:void(0)" id="btnShowMoreResults">More&gt;&gt;</a></b>
            {#else}
                Network error, please try again later!
            {#/if}
        
</textarea>
        
        
<div id="divSearchResults"></div>
        
        
<script type="text/javascript" language="javascript">
            NIntegrate.Scripts.Test.Demo.GoogleTracer.Container.registerInstance(
                NIntegrate.Scripts.Test.Demo.GoogleTracer.Views.IGoogleTracerView,
                
new NIntegrate.Scripts.Test.Demo.GoogleTracer.Views.CnblogsGoogleSearchTracerView()
            );

            
new NIntegrate.Scripts.Test.Demo.GoogleTracer.Controllers.GoogleTracerController().execute();
        
</script>
    
</body>
</html>
复制代码

This demo page runs smoothly without any error even the first time. How it achieves? I do TDD.

复制代码
CnblogsGoogleSearchTracerViewTest
    public class CnblogsGoogleSearchTracerViewTest : TestCase
    {
        
public override void Execute()
        {
            
base.Execute();

            CnblogsGoogleSearchTracerView view 
= new CnblogsGoogleSearchTracerView();

            QUnit.Test(
"Test SearchStart"delegate
            {
                QUnit.Equals(
0, view.SearchStart);
                view.SearchStart 
= 1;
                QUnit.Equals(
1, view.SearchStart);
                view.SearchStart 
= 2;
                QUnit.Equals(
2, view.SearchStart);
            });

            QUnit.Test(
"Test GetSearchKeyword()"delegate
            {
                JQueryFactory.JQuery(JQuerySelectors.SEARCH_KEYWORD).Html(
"keyword1");
                QUnit.Equals(
"keyword1", view.GetSearchKeyword());
                JQueryFactory.JQuery(JQuerySelectors.SEARCH_KEYWORD).Html(
"keyword2");
                QUnit.Equals(
"keyword2", view.GetSearchKeyword());
            });

            QUnit.Test(
"Test RenderSearchResult()"delegate
            {
                jQuery pnlSearchResults 
= JQueryFactory.JQuery(JQuerySelectors.SEARCH_RESULTS_PANEL);
                jQuery btnShowMoreResults 
= JQueryFactory.JQuery(JQuerySelectors.SHOW_MORE_RESULTS_BUTTON);

                Mock mockJQuery 
= new Mock(Window.Self, "jQuery");
                mockJQuery.Modify().Args(JQuerySelectors.SEARCH_RESULTS_PANEL).ReturnValue(pnlSearchResults);
                mockJQuery.Modify().Args(JQuerySelectors.SHOW_MORE_RESULTS_BUTTON).ReturnValue(btnShowMoreResults);
                Mock mockSetTemplateElement 
= new Mock(pnlSearchResults, "setTemplateElement");
                mockSetTemplateElement.Modify().Args(JTemplateElements.GOOGLE_TRACER).ReturnValue(pnlSearchResults);
                Mock mockProcessTemplate 
= new Mock(pnlSearchResults, "processTemplate");
                mockProcessTemplate.Modify().Args(Is.Anything).ReturnValue();
                Mock mockShowMoreResultsBindClick 
= new Mock(btnShowMoreResults, "click");
                mockShowMoreResultsBindClick.Modify().Args(Is.Anything).ReturnValue();

                view.RenderSearchResult(
new GoogleSearchResponse());

                mockJQuery.Verify();
                mockJQuery.Restore();
                mockSetTemplateElement.Verify();
                mockSetTemplateElement.Restore();
                mockProcessTemplate.Verify();
                mockProcessTemplate.Restore();
                mockShowMoreResultsBindClick.Verify();
                mockShowMoreResultsBindClick.Restore();
            });

            QUnit.Test(
"Test ShowMoreResults Event"delegate
            {
                QUnit.Equals(
false, _showMoreResultsClicked);

                view.ShowMoreResults 
+= new System.DHTML.DOMEventHandler(view_ShowMoreResults);
                view.RenderSearchResult(
new GoogleSearchResponse());
                JQueryFactory.JQuery(JQuerySelectors.SHOW_MORE_RESULTS_BUTTON).Click();

                QUnit.Equals(
true, _showMoreResultsClicked);
            });
        }

        
private bool _showMoreResultsClicked = false;

        
void view_ShowMoreResults()
        {
            _showMoreResultsClicked 
= true;
        }
    }
复制代码
复制代码
GoogleTracerControllerTest
    public class MockGoogleTracerView : IGoogleTracerView
    {
        
private int _searchStart = 0;

        
#region IGoogleTracerView Members

        
public int SearchStart
        {
            
get
            {
                
return _searchStart;
            }
            
set
            {
                _searchStart 
= value;
            }
        }

        
public string GetSearchKeyword()
        {
            
return "keyword";
        }

        
public void RenderSearchResult(NIntegrate.Scripts.Test.Demo.GoogleTracer.Records.GoogleSearchResponse response)
        {
            
return;
        }

        
public event System.DHTML.DOMEventHandler ShowMoreResults;

        
#endregion
    }

    
public class GoogleTracerControllerTest : TestCase
    {
        
public override void Execute()
        {
            
base.Execute();

            MockGoogleTracerView mockView 
= new MockGoogleTracerView();
            Container.RegisterInstance(
typeof(IGoogleTracerView), mockView);

            GoogleTracerController controller 
= new GoogleTracerController();

            QUnit.Test(
"Test get View"delegate
            {
                QUnit.Equals(mockView, controller.View);
            });

            QUnit.Test(
"Test Execute() & ShowMoreResults()"delegate
            {
                GoogleSearchResponse data 
= new GoogleSearchResponse();

                Mock mockAddShowMoreResults 
= new Mock(mockView, "add_showMoreResults");
                mockAddShowMoreResults.Modify().Args(Is.Anything).ReturnValue();
                Mock mockRenderSearchResult 
= new Mock(mockView, "renderSearchResult");
                mockRenderSearchResult.Modify().Args(data).ReturnValue();
                mockRenderSearchResult.Modify().Args(data).ReturnValue();
                Mock mockGetScript 
= new Mock(Script.Eval("jQuery"), "getScript");
                mockGetScript.Modify().Args(Is.Anything, Is.Anything).Callback(
1null).ReturnValue();
                mockGetScript.Modify().Args(Is.Anything, Is.Anything).Callback(
1null).ReturnValue();

                QUnit.Equals(
0, mockView.SearchStart);
                ((Dictionary)(
object)Window.Self)["_googlewebsearchresults"= data;
                controller.Execute();
                ((Dictionary)(
object)Window.Self)["_googlewebsearchresults"= data;
                controller.ShowMoreResults();
                QUnit.Equals(
4, mockView.SearchStart);

                mockAddShowMoreResults.Verify();
                mockAddShowMoreResults.Restore();
                mockRenderSearchResult.Verify();
                mockRenderSearchResult.Restore();
                mockGetScript.Verify();
                mockGetScript.Restore();
            });
        }
    }
复制代码

 The testing results of QUnit:

Source Code

You could download the latest source code of this demo from SVN: http://nintegrate.googlecode.com/svn/trunk/jqMVCSharp/

or download this zip file: jqMVCSharpDemo.zip

To open the project files in Visual Studio 2008, you should install Script# 0.5.6 for VS 2008 first.

posted @   Teddy's Knowledge Base  Views(2379)  Comments(7Edit  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
历史上的今天:
2005-07-01 两篇觉得比较好的今年高考满分作文
点击右上角即可分享
微信分享提示