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

This is jqMVC# – CNBLOGS Google Tracer Sample_第1张图片

“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 {  get set ; }
    
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(
1 null ).ReturnValue();
                mockGetScript.Modify().Args(Is.Anything, Is.Anything).Callback(
1 null ).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:

This is jqMVC# – CNBLOGS Google Tracer Sample_第2张图片

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.

你可能感兴趣的:(Google)