[Lucene]Lucene 学习笔记(二)——搜索方式(一)

mikel阅读(737)

Lucene有多种搜索方式,可以根据需要选择不同的方式。

1、词条搜索(单个关键字查找)

     主要对象是TermQuery

     调用方式如下:


Term term=new Term(字段名,搜索关键字);
Query query
=new TermQuery(term);
Hits hits
=searcher.search(query);

 

2、组合搜索(允许多个关键字组合搜索)

     主要对象是BooleanQuery

     调用方式如下:

 


Term term1=new Term(字段名,搜索关键字);
TermQuery query1
=new TermQuery(term1);
Term term2
=new Term(字段名,搜索关键字);
TermQuery query2
=new TermQuery(term2);
BooleanQuery booleanquery
=new BooleanQuery();
booleanquery.add(query1,参数)
booleanquery.add(query2,参数)
Hits hits
=searcher.search(booleanquery);

此方法中的核心在BooleanQuery的add方法上,其第二个参数有三个可选值,对应着逻辑上的与或非关系

参数如下:

BooleanClause.Occur.MUST                必须包含,类似于逻辑运算的与

BooleanClause.Occur.MUST _NOT       必须不包含,类似于逻辑运算的非

BooleanClause.Occur.SHOULD           可以包含,类似于逻辑运算的或

这三者组合,妙用无穷。

 

3、范围搜索(允许搜索指定范围内的关键字结果)

     主要对象是RangeQuery

     调用方式如下: 


Term term1=new Term(字段名,起始值);
Term term2
=new Term(字段名,终止值);
RangeQuery rangequery
=new RangeQuery(term1,term2,参数);
Hits hits
=searcher.search(rangequery);

此方法中的参数是Boolean类型的,表示是否包含边界 。

true 包含边界

false不包含边界

 

4、前缀搜索(搜索起始位置符合要求的结果)

     主要对象是PrefixQuery

     调用方式如下:


Term term=new Term(字段名,搜索关键字);
PrefixQuery prefixquery
=new PrefixQuery (term);
Hits hits
=searcher.search(prefixquery);

 

5、短语搜索(根据零碎的短语组合成新的词组进行搜索)

     主要对象是PhraseQuery

     调用方式如下:


Term term1=new Term(字段名,搜索关键字);
Term term2
=new Term(字段名,搜索关键字);
PhraseQuery phrasequery
=new PhraseQuery();
phrasequery.setSlop(参数);
phrasequery.add(term1);
phrasequery.add(term2);
Hits hits
=searcher.search(phrasequery);

其中setSlop的参数是设置两个关键字之间允许间隔的最大值。

[ASP.NET]Web开发学习心得7——MasterPage的实现原理

mikel阅读(854)

      MasterPage是ASP.NET2.0引入的一个非常实用的特性,怎么用,我想不用我说,基本上大家都会,这里要讲的是,它是如何实现的。

      在深入源代码去探索MasterPage之前,我以为MasterPage的实现应该是比较复杂的,也一直纳闷为什么 MasterPage类会继承于UserControl类,感觉这两者好像差得很远。昨天晚上,我专门抽出时间,阅读了部分与MasterPage有关的 源代码,终于明白了是怎么回事,在那突然明白的那一刻,真有如醍醐灌顶,拍案叫绝,不得不佩服微软的那些guys。

      下面就是我的探索之旅的过程(大家也可以跳过该部分,直接看后面的真相大白部分):

      1、我首先查看的是Page.ProcessRequestMain方法,我们知道,Page类大部分特性,包括LifeCycle、 PostBack、ProcessPostData等都是在该方法中实现,所以,我想,MasterPage的实现肯定在该方法中有不少的体现。然而,令 我惊讶的是,我居然没有在该方法中找到任何有关MasterPage的线索,而仅仅在this.PerformPreInit()中,找到唯一一个 ApplyMasterPage()方法。而该方法也出奇的简单,感觉仅仅是将递归的各级MasterPage的._masterPageApplied 字段设为true而已。当时,我忽略了一个重要的东西,就是代码中对this.Master这个属性的访问,实际上,奥秘就在对这个属性的访问上(下文将 叙述)。

private void PerformPreInit()
{
    
this.OnPreInit(EventArgs.Empty);
    
this.InitializeThemes();
    
this.ApplyMasterPage();
    
this._preInitWorkComplete = true;
}

 


private void ApplyMasterPage()
{
    
if (this.Master != null)
    
{
        ArrayList appliedMasterFilePaths 
= new ArrayList();
        appliedMasterFilePaths.Add(
this._masterPageFile.VirtualPathString.ToLower(CultureInfo.InvariantCulture));
        MasterPage.ApplyMasterRecursive(
this.Master, appliedMasterFilePaths);
    }

}

 

      2、我查看了MasterPage的源代码,出奇的是,竟也如此简单,以至于我也没有从该源代码中找到多少有价值的信息。  


[ControlBuilder(typeof(MasterPageControlBuilder)), Designer("Microsoft.VisualStudio.Web.WebForms.MasterPageWebFormDesigner, Microsoft.VisualStudio.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"typeof(IRootDesigner)), ParseChildren(false), AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
public class MasterPage : UserControl
{
    
private IList _contentPlaceHolders;
    
private IDictionary _contentTemplateCollection;
    
private IDictionary _contentTemplates;
    
private MasterPage _master;
    
private bool _masterPageApplied;
    
private VirtualPath _masterPageFile;
    
internal TemplateControl _ownerControl;

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal void AddContentTemplate(string templateName, ITemplate template)
    
{
        
if (this._contentTemplateCollection == null)
        
{
            
this._contentTemplateCollection = new Hashtable(10, StringComparer.OrdinalIgnoreCase);
        }

        
try
        
{
            
this._contentTemplateCollection.Add(templateName, template);
        }

        
catch (ArgumentException)
        
{
            
throw new HttpException(SR.GetString("MasterPage_Multiple_content"new object[] { templateName }));
        }

    }


    
internal static void ApplyMasterRecursive(MasterPage master, IList appliedMasterFilePaths)
    
{
        
if (master.Master != null)
        
{
            
string str = master._masterPageFile.VirtualPathString.ToLower(CultureInfo.InvariantCulture);
            
if (appliedMasterFilePaths.Contains(str))
            
{
                
throw new InvalidOperationException(SR.GetString("MasterPage_Circular_Master_Not_Allowed"new object[] { master._masterPageFile }));
            }

            appliedMasterFilePaths.Add(str);
            ApplyMasterRecursive(master.Master, appliedMasterFilePaths);
        }

        master._masterPageApplied 
= true;
    }


    
internal static MasterPage CreateMaster(TemplateControl owner, HttpContext context, VirtualPath masterPageFile, IDictionary contentTemplateCollection)
    
{
        MasterPage child 
= null;
        
if (masterPageFile == null)
        
{
            
if ((contentTemplateCollection != null&& (contentTemplateCollection.Count > 0))
            
{
                
throw new HttpException(SR.GetString("Content_only_allowed_in_content_page"));
            }

            
return null;
        }

        VirtualPath virtualPath 
= VirtualPathProvider.CombineVirtualPathsInternal(owner.TemplateControlVirtualPath, masterPageFile);
        ITypedWebObjectFactory vPathBuildResult 
= (ITypedWebObjectFactory) BuildManager.GetVPathBuildResult(context, virtualPath);
        
if (!typeof(MasterPage).IsAssignableFrom(vPathBuildResult.InstantiatedType))
        
{
            
throw new HttpException(SR.GetString("Invalid_master_base"new object[] { masterPageFile }));
        }

        child 
= (MasterPage) vPathBuildResult.CreateInstance();
        child.TemplateControlVirtualPath 
= virtualPath;
        
if (owner.HasControls())
        
{
            
foreach (Control control in owner.Controls)
            
{
                LiteralControl control2 
= control as LiteralControl;
                
if ((control2 == null|| (Util.FirstNonWhiteSpaceIndex(control2.Text) >= 0))
                
{
                    
throw new HttpException(SR.GetString("Content_allowed_in_top_level_only"));
                }

            }

            owner.Controls.Clear();
        }

        
if (owner.Controls.IsReadOnly)
        
{
            
throw new HttpException(SR.GetString("MasterPage_Cannot_ApplyTo_ReadOnly_Collection"));
        }

        
if (contentTemplateCollection != null)
        
{
            
foreach (string str in contentTemplateCollection.Keys)
            
{
                
if (!child.ContentPlaceHolders.Contains(str.ToLower(CultureInfo.InvariantCulture)))
                
{
                    
throw new HttpException(SR.GetString("MasterPage_doesnt_have_contentplaceholder"new object[] { str, masterPageFile }));
                }

            }

            child._contentTemplates 
= contentTemplateCollection;
        }

        child._ownerControl 
= owner;
        child.InitializeAsUserControl(owner.Page);
        owner.Controls.Add(child);
        
return child;
    }


    [Browsable(
false), EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal IList ContentPlaceHolders
    
{
        
get
        
{
            
if (this._contentPlaceHolders == null)
            
{
                
this._contentPlaceHolders = new ArrayList();
            }

            
return this._contentPlaceHolders;
        }

    }


    [Browsable(
false), EditorBrowsable(EditorBrowsableState.Advanced)]
    
protected internal IDictionary ContentTemplates
    
{
        
get
        
{
            
return this._contentTemplates;
        }

    }


    [WebSysDescription(
"MasterPage_MasterPage"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
    
public MasterPage Master
    
{
        
get
        
{
            
if ((this._master == null&& !this._masterPageApplied)
            
{
                
this._master = CreateMaster(thisthis.Context, this._masterPageFile, this._contentTemplateCollection);
            }

            
return this._master;
        }

    }


    [WebSysDescription(
"MasterPage_MasterPageFile"), WebCategory("Behavior"), DefaultValue("")]
    
public string MasterPageFile
    
{
        
get
        
{
            
return VirtualPath.GetVirtualPathString(this._masterPageFile);
        }

        
set
        
{
            
if (this._masterPageApplied)
            
{
                
throw new InvalidOperationException(SR.GetString("PropertySetBeforePageEvent"new object[] "MasterPageFile""Page_PreInit" }));
            }

            
if (value != VirtualPath.GetVirtualPathString(this._masterPageFile))
            
{
                
this._masterPageFile = VirtualPath.CreateAllowNull(value);
                
if ((this._master != null&& this.Controls.Contains(this._master))
                
{
                    
this.Controls.Remove(this._master);
                }

                
this._master = null;
            }

        }

    }

}

      

      3、我又查看了ContentPlaceHolder类、Content类,心想,难道奥秘在这两个控件上?打开源代码一看,彻底晕 倒,这两个类简单得简直不能让人相信,ContentPlaceHolder居然是一个空类,仅仅起到一个标识的作用,而Content居然也仅仅只有 ContentPlaceHolderID唯一一个string属性。

public class ContentPlaceHolder : Control, INonBindingContainer, INamingContainer
{
}

 

Content

      

      4、此时,我几乎已经没法再从ASP.NET源代码中找到其他有关MasterPage的有价值的信息了。于是,我决定写一个简单的 Web应用程序,该应用程序仅仅只有一个MasterPage页与Default页,并将其编译,查看编译后的代码,看看是否能找到有价值的信息。

      源代码如下:


<%@ Master Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    
<title></title>
</head>
<body>
    
<form id="form1" runat="server">
    
<div>
        
<asp:ContentPlaceHolder id="ContentPlaceHolder" runat="server">
            
<asp:Button runat="server" ID="master" Text="master"/>
        
</asp:ContentPlaceHolder>
    
</div>
    
</form>
</body>
</html>

 


<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>

<asp:Content ID="Content" ContentPlaceHolderID="ContentPlaceHolder" Runat="Server">
    
<asp:Button runat="server" ID="default" Text="default" />
</asp:Content>

 

      编译后代码如下:


[CompilerGlobalScope]
public class masterpage_master : MasterPage
{
    
private static bool __initialized;
    
private ITemplate __Template_ContentPlaceHolder;
    
protected ContentPlaceHolder ContentPlaceHolder;
    
protected HtmlForm form1;
    
protected Button master;

    [DebuggerNonUserCode]
    
public masterpage_master()
    
{
        
base.AppRelativeVirtualPath = "~/MasterPage.master";
        
if (!__initialized)
        
{
            __initialized 
= true;
        }

        
base.ContentPlaceHolders.Add("contentplaceholder");
    }


    [DebuggerNonUserCode]
    
private ContentPlaceHolder __BuildControlContentPlaceHolder()
    
{
        ContentPlaceHolder container 
= new ContentPlaceHolder();
        
this.ContentPlaceHolder = container;
        container.ID 
= "ContentPlaceHolder";
        
if (base.ContentTemplates != null)
        
{
            
this.__Template_ContentPlaceHolder = (ITemplate) base.ContentTemplates["ContentPlaceHolder"];
        }

        
if (this.__Template_ContentPlaceHolder != null)
        
{
            
this.__Template_ContentPlaceHolder.InstantiateIn(container);
            
return container;
        }

        IParserAccessor accessor 
= container;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n            "));
        Button button 
= this.__BuildControlmaster();
        accessor.AddParsedSubObject(button);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n        "));
        
return container;
    }


    [DebuggerNonUserCode]
    
private HtmlForm __BuildControlform1()
    
{
        HtmlForm form 
= new HtmlForm();
        
this.form1 = form;
        form.ID 
= "form1";
        IParserAccessor accessor 
= form;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    <div>\r\n        "));
        ContentPlaceHolder holder 
= this.__BuildControlContentPlaceHolder();
        accessor.AddParsedSubObject(holder);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    </div>\r\n    "));
        
return form;
    }


    [DebuggerNonUserCode]
    
private Button __BuildControlmaster()
    
{
        Button button 
= new Button();
        
this.master = button;
        button.ApplyStyleSheetSkin(
this.Page);
        button.ID 
= "master";
        button.Text 
= "master";
        
return button;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlTree(masterpage_master __ctrl)
    
{
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n\r\n<!DOCTYPE html PUBLIC \"//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n    <title></title>\r\n</head>\r\n<body>\r\n    "));
        HtmlForm form = this.__BuildControlform1();
        accessor.AddParsedSubObject(form);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n</body>\r\n</html>\r\n"));
    }


    [DebuggerNonUserCode]
    
protected override void FrameworkInitialize()
    
{
        
base.FrameworkInitialize();
        
this.__BuildControlTree(this);
    }


    
protected HttpApplication ApplicationInstance
    
{
        
get
        
{
            
return this.Context.ApplicationInstance;
        }

    }


    
protected DefaultProfile Profile
    
{
        
get
        
{
            
return (DefaultProfile) this.Context.Profile;
        }

    }


    
protected override bool SupportAutoEvents
    
{
        
get
        
{
            
return false;
        }

    }


    [TemplateContainer(
typeof(MasterPage)), TemplateInstance(TemplateInstance.Single)]
    
public virtual ITemplate Template_ContentPlaceHolder
    
{
        
get
        
{
            
return this.__Template_ContentPlaceHolder;
        }

        
set
        
{
            
this.__Template_ContentPlaceHolder = value;
        }

    }

}

 


[CompilerGlobalScope]
public class default_aspx : Page, IRequiresSessionState, IHttpHandler
{
    
private static object __fileDependencies;
    
private static bool __initialized;
    
protected Button @default;

    [DebuggerNonUserCode]
    
public default_aspx()
    
{
        
base.AppRelativeVirtualPath = "~/Default.aspx";
        
if (!__initialized)
        
{
            
string[] virtualFileDependencies = new string[] "~/Default.aspx""~/MasterPage.master" };
            __fileDependencies 
= base.GetWrappedFileDependencies(virtualFileDependencies);
            __initialized 
= true;
        }

        
base.Server.ScriptTimeout = 0x1c9c380;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlContent(Control __ctrl)
    
{
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n    "));
        Button button 
= this.__BuildControldefault();
        accessor.AddParsedSubObject(button);
        accessor.AddParsedSubObject(
new LiteralControl("\r\n"));
    }


    [DebuggerNonUserCode]
    
private Button __BuildControldefault()
    
{
        Button button 
= new Button();
        
this.@default = button;
        button.TemplateControl 
= this;
        button.ApplyStyleSheetSkin(
this);
        button.ID 
= "default";
        button.Text 
= "default";
        
return button;
    }


    [DebuggerNonUserCode]
    
private void __BuildControlTree(default_aspx __ctrl)
    
{
        __ctrl.Title 
= "";
        __ctrl.MasterPageFile 
= "~/MasterPage.master";
        
this.InitializeCulture();
        
base.AddContentTemplate("ContentPlaceHolder"new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControlContent)));
        IParserAccessor accessor 
= __ctrl;
        accessor.AddParsedSubObject(
new LiteralControl("\r\n\r\n"));
    }


    [DebuggerNonUserCode]
    
protected override void FrameworkInitialize()
    
{
        
base.FrameworkInitialize();
        
this.__BuildControlTree(this);
        
base.AddWrappedFileDependencies(__fileDependencies);
        
base.Request.ValidateInput();
    }


    [DebuggerNonUserCode]
    
public override int GetTypeHashCode()
    
{
        
return 2002306427;
    }


    [DebuggerNonUserCode]
    
public override void ProcessRequest(HttpContext context)
    
{
        
base.ProcessRequest(context);
    }


    
protected HttpApplication ApplicationInstance
    
{
        
get
        
{
            
return this.Context.ApplicationInstance;
        }

    }


    
public masterpage_master Master
    
{
        
get
        
{
            
return (masterpage_master) base.Master;
        }

    }


    
protected DefaultProfile Profile
    
{
        
get
        
{
            
return (DefaultProfile) this.Context.Profile;
        }

    }


    
protected override bool SupportAutoEvents
    
{
        
get
        
{
            
return false;
        }

    }

}

      

      我们首先观察default_aspx的BuildControlTree方法,该方法调用了 base.AddContentTemplate("ContentPlaceHolder", new CompiledTemplateBuilder(new BuildTemplateMethod(this.__BuildControlContent)));而该方法实质上只有一行代码,即 this._contentTemplateCollection.Add(templateName, template);其中,_contentTemplateCollection是IDictionary类型。因 此,base.AddContentTemplate只有一个功能,即在Page的_contentTemplateCollection中添加一个 CompliedTemplateBuilder类型的对象。那么该对象有啥作用呢?为了不岔开话题,这里不对该对象做详细描述,只给出结论:该对象实现 ITemplate接口,其接口方法InstantiateIn(Control container)具体实现为为container添加BuildTemplateMethod委托所创建的控件。这话有点拗口,简单地就刚才那个例子 来说,就是如果你调用该对象的InstantiateIn(Control container)方法,就为该container添加this.__BuildControlContent()方法所创建的控件做为子控件。

 

      5、我们继续来看看masterpage_master的__BuildControlContentPlaceHolder()方 法,我们发现,该方法即调用了刚才讨论的那个InstantiateIn方法。哈,原来在这里,终于明白了,原来Page里Content控件中的所有内 容最终都将变成其对应的MasterPage中的ContentPlaceHolder的子控件。等一下,这个结论下得有点早,难道这里的 base.ContentTemplates属性就等于Page的_contentTemplateCollection字段吗?如果是,那么上面的结论 就是正确的。

 

      6、我们重新回到1中的代码,查看Page.Master属性的实现,我们发现,它调用了MasterPage的CreateMaster静态方法,而该方法传送的其中一个参数就是._contentTemplateCollection字段。


public MasterPage Master
{
    
get
    
{
        
if ((this._master == null&& !this._preInitWorkComplete)
        
{
            
this._master = MasterPage.CreateMaster(thisthis.Context, this._masterPageFile, this._contentTemplateCollection);
        }

        
return this._master;
    }

}

 

      我们再回头来看看MasterPage.CreateMaster方法,在倒数第6行,我们发现,该方法果然将 contentTemplateCollection赋给了MasterPage实例(child)的_contentTemplates字段。再往下 看,我们还看到了owner.Controls.Add(child),什么意思呢?意思就是将该MasterPage实例作为普通控件加入到owner 实例的子控件集合中。而往上,我们又可以找到owner.Controls.Clear()语句,因此,该MasterPage将作为owner的唯一子 控件而存在。而该owner,常常就是Page实例。(在存在嵌套MasterPage的时候,该owner还可能是下一层次的MasterPage实 例)。

      至此,真相大白,原来,MasterPage最终将作为Page的唯一子控件而存在,难怪它要继承自 UserControl,而Page中Content控件定义的各个子控件,又将作为该MasterPage的ContentPlaceHolder的子控件而存在,难 怪ContentPlaceHolder无需实现任何代码,因为它仅仅是一个容器。正因为MasterPage最终成为Page的唯一子控件,那么后来的 处理就与普通的控件没什么两样了,难怪ProcessRequestMain方法里无需为MasterPage单独编码,哈哈,一切都真相大白了。这里, 我们还发现一个比较有趣的现象,即Content控件本身却消失不见了,这应该是Asp.net解析器所做的优化,因为ContentPlaceHolder完全没必要先装上Content控件,然后再装上Content中的那些控件。

      另外,从Page.Master属性与Page.MasterPageFile属性的实现上,我们也不难明白为什么MasterPageFile属性只能在 PreInit 事件中进行设置的原因。 

      如何证明以上所说都是正确的呢?呵呵,其实很简单,我们可以观察最终页面的控件树,就可证明上面分析是正确的。(写这篇blog时才想起看控件树,要是早想起,就能让我少走不少歪路了,唉,幸好打开控件树发现结果与预期完全一致。)

ControlTree

      

      那么嵌套MasterPage是如何实现的呢?呵呵,其实也一样,即Top1MasterPage成为Top2MasterPage的 唯一子控件,Top2MasterPage成为Top3MasterPage的唯一子控件,……,直到TopNMasterPage成为Page的唯一子 控件。

      

      最后我用两幅图来做总结。

      下图为初始的控件树结构:

 

      下图为最终的控件树结构:

 

[优化]Memcahed分布式缓存服务替换Session解决方案

mikel阅读(872)

需求:

       有个ASP.NET网站系统,有一级域名,二级域名,三级域名,系统的各个功能模块分布在不同的域名,同一域名的也有可能分布在不同省份的服务器或者同一同一省份的不同的服务器中,同一省份的的服务器可以通过内部局域网访问。在系统中,现在需要所有功能模块共享用户会话信私有数据。

常用的方式是启用Session的数据库持久会模式可以达到上面的需求(没尝试过),现在需要使用Memcached分布式缓存服务来达到保存用户的会话数据,而达到各个功能模块都能够跨省份、跨服务器共享本次会话中的私有数据的目的。

解决方案:       

     每个省份使用一台服务器来做为Memcached服务器来存储用话的会话中的数据,当然也可以多台服务器,但必须确保每个省份的做Memcached服务器数量必须一致,这样才能够保证Memcached客户端操作的是同一份数据,保证数据的一致性。

会话数据的添加、删除、修改:

Memcached客 户端,添加、删除和、修改会话信息数据时,不仅要添加、删除、修改本省的Memcached服务器数据,而且同时要对其它省份的Memcahed服务器做 同样的操作,这样用户访问其它省份的服务器的功能模块进也能读取到相同的会话数据。Memcached客户端服务器的列表使用局域网的内网 IP(如:192.168.1.179)操作本省的Memcahed服务器,使用公网的IP((如:202.183.62.210))操作其它省份的 Memcahe服务器。

会话数据的读取

系统所有模块读取会话数据的Memcached客户端服务器列表都设为本省Memcached服务器地址的内网IP来向Memcahed服务器中读取会话数据。

 

    

  如 上图所示,A省有四台服务器,B省也有四台服务器,两个三份都有三台Web服务器、一台Memcached服务器,且A省四台服务器同在一个局域网内的可 能通过内网IP相互访问,B省也一样。假如:A省的A1,B1,C1这三台Web服务器其中的一台要添加或修改、或删除会话数据,它首先调服 Memcached客户端使服D1服务器的内网IP向D1服务器中添加或修改、或删除会话数据,操作完成后,还用调用Memcache客户端使服D2服务 器的公网IP向D2服务器做同样的操作,这样才算完整的操作过程,也可以当做一个事务来处理。假如:A省的A1,B1,C1这三台Web服务器其中一台服 务器想要读取会话的数据,只需要调用Memcached客户端使服D1服务器的内网IP读取数据即可,如果发现该数据不存在,即做向Memcached服 务器添加数据的相关业务处理。B省处理逻辑同A省。 

 同一会话的确认:

        使用Cookie来保持客户与服务端的联系。 每一次会话开始就生成一个GUID作为SessionID,保存在客户端的Cookie中,作用域是顶级域名,这样二级、三级域名就可以共享到这个Cookie,系统中就使用这个SessionID来确认它是否是同一个会话。

        

会话数据的唯一ID

    会话数据存储在Memcached服务器上的唯一键Key也就是会话数据数据的唯一ID定义为:SessionID_Name, SessionID就是保存在客户端Cookie中的SessionID, Name就是会话数据的名称,同一次会话中各个会话数据的Name必须是唯一的,否则新的会话数据将覆盖旧的会话数据。

会话的失效时间:

       会话的失效通过控制Cookie的有效时间来实现,会话的时间设为SessionIDCookie中的有效时间,且每一次访问SessionID时都要重新设置一下Cookie的有效时间,这样就达到的会话的有效时间就是两次间访问CookieSessionID值的的最长时间,如果两次访问的间隔时间超过用效时间,那么保存在SessionIDCookie将会失效,并生成新的SessionID存放在Cookie, SessionID改变啦,会话就结束啦。

Memcached服务器中会话数据的失效

      每 一次向Memcache服务器中添加会话数据时,都把有效时间设为一天也就是24小时,让Memcached服务使用它内部的机制去清除,不必在程序中特 别做会话数据的删除操作。数据在Memcache服务器中有有效时间只是逻辑上的,就算是过了24 小时,如果分配给Memcached服务的内存还够用的话,数据还是保存在内存当中的,只是Memcache客户端读取不到而已。只有到了分配给 Memcached服务的内存不够用时,它才会清理没用或者比较旧的数据,也就是懒性清除。

[Flex]FluorineFx.NET与.Net互操作系列教程

mikel阅读(770)


Flex与.NET互操作系列文章索引
Flex与.NET互操作(一):基于Socket的网络连接 
Flex与.NET互操作(二):基于WebService的数据访问(上) 
Flex与.NET互操作(三):基于WebService的数据访问(下)
Flex与.NET互操作(四):使用HttpService、URLReqeust和URLLoader加载/传输数据
Flex与.NET互操作(五):使用FileReference+HttpHandler实现文件上传/下载
Flex与.NET互操作(六):Flex和.NET协同开发利器FluorineFx
Flex与.NET互操作(七):了解FluorineFx的环境配置(远程对象、网关、通道、目的地)
Flex与.NET互操作(八):使用FluorineFx网关实现远程访问
Flex与.NET互操作(九):FluorineFx.NET的认证(Authentication )与授权(Authorization)
Flex与.NET互操作(十):基于FluorineFx.Net的及时通信应用(ApplicationAdapter)(一) 
Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Remote Procedure Call)(二)





[Flex]Flex与.NET互操作(十):基于FluorineFx.Net的及时通信应用(Appl

mikel阅读(668)

   使用FluorineFx.Net开发的每一个实时通讯功能应用都拥有一个应用程序适配器(ApplicationAdapter),用来管 理整个实时通讯应用的生命周期,以及接受和拒绝客户端的连接等。应用程序适配器对象也就相当于是一个Flash媒体服务器应用程序的对象。

      使用FluorineFx.Net开发及时通信应用,我们可以通过ASP.NET网站来宿主,一个ASP.NET网站可以承载多个实时通讯应用程序。这些应用程序都存储ASP.NET网站的根目录下指定文件夹的根目录中。如下图示:

                               

      在开发FluorineFx的及时通信应用的时候,按照FluorineFx的目录结构配置ASP.NET的站点目录是非常简单的,只 需要在ASP.NET站点下面建立一个名为apps的目录,那么FluorineFx就会认为此目录下的每一个文件夹就表示一个FluorineFx应 用。

      我们可以通过FluorineFx来开发及时文字聊天、视频聊天、视频录制、媒体播放程序以及WebGame等类似及时应用程序。比如 我们需要开发一个视频聊天系统,OK,那便在ASP.NET站点目录下建立一个apps目录,然后在apps下建立一个名为VideoChat的目录,如 下目录结构:

                              

       在Flash或是Flex客户端,我们就可以使用下面的代码来连到这个基于FluorineFx的视频聊天应用。

private var ns:NetConnection;
public function ConnectionFxApp():void
{
    ns 
= new NetConnection();
    ns.connect(
"rtmp://localhost:1974/VideoChat");
    ns.client
=this;
    ns.addEventListener(NetStatusEvent.NET_STATUS,onNetStatusHandler);
}
private function onNetStatusHandler(evt:NetStatusEvent):void
{}

 

      每个应用程序都有其自己的应用范围和层次的根源,这和FMS、Red5是一样的规律。无论是范围还是层次,他都有一个唯一的名字(应用名、实例名等)来区分,我们也可以叫他们为应用程序的实例。这些实例可以是聊天室房间、视频流以及共享对象等。   

                                 

      这些其实是非常容易理解的,如上图示的聊天室房间分配,分别有Room1,Room2,Room3三个聊天房间,那么对于VideoChat这个聊天应用来说要进行视频聊天就有三条连接线路,分别是:

      1、http://localhost:1974/VideoChat/Room1

      2、http://localhost:1974/VideoChat/Room2

      3、http://localhost:1974/VideoChat/Room3

      除了聊天室和应用的层次外,应用程序里还有诸入视频流、共享对象等。他们可以共同应用,也可以为不同的房间分别建立不同的共享对象,如果你要想实现不同房间里的用户可以相互聊天不想让别的房间的人看到,OK,此时你就可以为该房间独立创建一个共享对象来实现。

 

      当开发程序需要去连接客户端,处理服务端事件、调用客户端方法等,就需要自定义一个应用程序适配器 (ApplicationAdapter),此适配器必须继承并实现 FluorineFx.Messaging.Adapter.ApplicationAdapter。 FluorineFx.Messaging.Adapter.ApplicationAdapter做为一个新的应用程序的基类,提供了客户端与应用程序 之间通信的接口方法、数据流以及共享对象等。它能够时时处理应用程序的启动、停止、客户端的连接和断开。

 

      在Flash或是Flex客户端,我们可以通过NetConnectin.call()调用应用程序适配器的方法。如下示例代码块:

服务器端应用适配器所定义的代码:

public class Application : ApplicationAdapter
{
   
public string Echo(string msg)
   {
      
return "Echo: " + msg;
   }
}

Flash/Flex客户端可以通过以下代码块调用服务器端的方法:

private var ns:NetConnection;
private function CallServerMethod():void
{
    nc 
= new NetConnection();
    nc.connect(
"rtmp://rtmp://localhost:1974/application");
    nc.onResult 
= function(obj) 
    {
        trace(
"The result is " + obj);
    }
    nc.call(
"Echo", nc, "Hello");
}

 

     本文就先介绍到这里,下篇将接着本篇介绍FluorineFx.NET中客户端调用服务器端方法和服务器端呼叫客户端的方法。

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

[Flex]Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Rem

mikel阅读(611)

      FluorineFx.NET提供了完善的RPC(Remote Procedure Call)功能,无论是通过Flash还是Flex开发的客户端应用(.swf)都可以非常简单方便的采用RPC的方式调用.NET的服务器端方 法,.NET的服务器端同样也可以非常方便的呼叫客户端,调用客户端的方法(比如实现系统广播)。
一、客户端的RPC(客户端调用服务器端

       要想实现客户端访问服务器端的方法,首先得对ActionScript中的NetConnection比较熟悉,该类提供了一个示例方法call()专们用来做RPC访问,该方法的定义如下:

public function call(command:String, responder:Responder,  arguments):void 

 

      比如说我们在开发一个及时应用的时候,所有的客户端都需要同步服务器的系统时间,这个时候我们就可以在服务器端提供一个返回当前系统时间的方法给客户端调用。如下:

//返回当前系统时间
public string GetServerTime()
{
     
return DateTime.Now.ToString();
}

      

      有了这个方法客户端就可以通过NetConnection的call()方法实行远程调用了,并通过Responder来处理调用的结果,如果所调用的服务器端方法没有返回值,则在调用的时候可以不使用Responder。如下完整的Flex实例代码:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" fontSize="12">
    
<mx:Script>
        
<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
        
            
private var nc:NetConnection;
            
private var responder:Responder = new Responder(onResult,onError);
            
            
private function connectionServer(event:MouseEvent):void
            {
                nc 
= new NetConnection();
                nc.connect(
"rtmp://localhost:1617/RPCDemo","abc","123");
                nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
                nc.client 
= this;
            }
            
            
private function onStatusHandler(event:NetStatusEvent):void
            {
                
this.connStatus.text = "连接状态:" + event.info.code;
                
if(event.info.code == "NetConnection.Connect.Success")
                {
                    trace(
"连接服务器成功");
                }
            }
            
            
/* 调用服务器端方法 */
            
private function onGetServerTime(event:MouseEvent):void
            {
                nc.call(
"GetServerTime",responder);
            }
            
            
/* 处理调用服务器端方法成功时返回的结果 */
            
private function onResult(result:String):void
            {
                
this.txtResult.text = "方法GetServerTime返回结果" + result;
            }
            
            
/* 处理调用服务器端方法时候时返回的结果 */
            
private function onError(event:Event):void
            {
                trace(
"调用服务器端方法出错");
            }
        ]]
>
    
</mx:Script>
    
<mx:Button x="30" y="43" label="连接服务器" click="connectionServer(event)"/>
    
<mx:Label x="30" y="75" width="296" id="connStatus"/>
    
<mx:Button x="30" y="112" label="调用服务端方法" click="onGetServerTime(event)"/>
    
<mx:Label x="30" y="153" id="txtResult" width="296"/>
</mx:Application>

 

       FluorineFx.Net的RPC就这么简单,客户端调用服务器端的方法,直接通过当前连接的call()方法调用,使用Responder来处理调用的结果。

二、服务器端的RPC(服务器端调用客户端)

      客户端和服务器端的调用是可以双向的,服务器端也同样可以调用客户端的方法。无论是客户端还是服务器端,相互调用始终都是离不开当前的 连接对象。如上面客户端是通过当前连接的call()方法实现的调用服务器端方法,那么服务器端调用客户端的方法同样如此,也是通过当前连接对象来处理。

      FluorineFx中用于连接的接口是IConnection,只要客户端成功的连接上了服务器端,那么在服务器端就会保存所有连接 到服务器端的连接对象的各种属性。实现调用客户端方法的则是IServiceCapableConnection接 口,IServiceCapableConnection接口的定义如下所示:

      如果要想实现服务器端调用客户端的方法,则必须使用此接口来完成,需要将当前连接的接口IConnection强制转化为

IServiceCapableConnection接口,然后调用其invoke()方法来完成服务器端对客户端方法的调用。

 

/// <summary>
/// 调用客户端的方法
/// </summary>
/// <param name="connection"></param>
public void InvokeClient()
{

    IServiceCapableConnection conn 
= FluorineContext.Current.Connection as IServiceCapableConnection;
    
if (connection != null)
    {
        
object[] args = new object[] { GetServerTime() };
        conn.Invoke(
"RecServerMessage", args);
    }
}

 

      在服务器端可以通过FluorineContext.Current.Connection获取到当前客户端的连接对象,然后将其转化为IServiceCapableConnection接口对象,调用其Invoke()方法便可实现调用客户端的方法。

      这里需要注意一点就是,提供给服务器端调用的客户端方法必须是公共方法(public修饰的方法),否则服务器端会报"Not found method:方法名"的异常。如下代码块:

/**
 * 接收服务器端的调用方法
 * 
*/
public function RecServerMessage(message:Object):void
{
    Alert.show(message.toString());
}

 

      FluorineFx.Net所提供的上面这两种功能是非常实用的,特别是在开发及时应用的时候,客户端RPC可以方便是实现将客户端 是数据请求到服务器端进行处理,比如开发网络游戏中,游戏客户端的不同数据随时都在改变,那么我们就可以使用客户端RPC来实现这一需求。服务器端RPC 同样可以应用于此,游戏中的数据变化了通过客户端RPC请求到服务器端,服务器端进行相与的业务计算和处理之后将客户端需要的数据通过服务器端RPC返回 到当前请求的客户端。

 

      或许有的朋友会想或是问到,如果我要开发一个聊天室,要实现系统广播消息是不是可以使用这种方式来实现。答案是肯定的,系统要广播消 息,可以从服务器端获取到所有连接到服务器端的客户端,然后循环的调用客户端的方法来实现系统广播消息。关于如何实现系统广播这里我就不作介绍,因为 FluorineFx.Net为我们提供了更好的方式来实现系统广播。有兴趣的朋友请关注我的后续文章,在下一篇文章里我将详细介绍这方面的知识点。

 

版权说明

  本文属原创文章,欢迎转载,其版权归作者和博客园共有。  

  作      者:Beniao

 文章出处:http://beniao.cnblogs.com/  或  http://www.cnblogs.com/

 

[MVC]Asp.net MVC 示例项目"Suteki.Shop"分析之---IOC(控制反转)

mikel阅读(714)

   在Suteki.Shop中,未使用微软自已的Unity框架来实现IOC,而是使用了大名鼎鼎Castle Windsor。
因为引用了Windsor,就有必要简要介绍一下。而我的理解,这个IOC容器(Container)包括下面几个
重要概念:
 

      容器(Container):Windsor是一个反转控制容器。它创建在一个微内核的基础之上,这个微内
核能够扫描类并且试图找到这些类用到哪些对象引用、对象依赖,然后把这些依赖信息提供给类使用

      组件(Component):也就是我们通常所说的业务逻辑单元及相应的功能实现,组件是一个可复
用的代码单元。它应该实现并暴露为一个服务。组件是实现一个服务或接口的类

      服务(Service) :也就是相应的组件接口或N个Component按业务逻辑组合而成的业务逻辑接口。
接口是服务的规范,它创建一个抽象层,你可以轻松的替换服务的实现

      扩张单元插件(Facilities):提供(可扩张)容器以管理组件
      
     我们可以直接使用组件(会在下面的内容中提到),也可以把组件转换成相应的服务接口来使用。
 
     还记得上一篇文章中提到的Service吗? 说白了,它就是一个服务。而Suteki.Shop做的更“夸张”,
只要是带有业务逻辑性质的功能代码都可以被视为Component或服务,比如说前几篇文章中所提到的
Filter,ModelBinder。甚至是服务组件初始化的辅助类(WindsorServiceLocator)也一并拿下。

     为了便于理解,下面就到Suteki.Shop中看一下其是如何做的:)
   
     首先我们看一下整个Suteki.Shop项目启动的入口,同时这也是Windsor IOC容器初始化的起点。
而这块功能代码是放在了Global.asax(Suteki.Shop\Global.asax)中的Application_Start方法中
实现的,下面是该方法的声明:

protected void Application_Start(object sender, EventArgs e)
{
    RouteManager.RegisterRoutes(RouteTable.Routes);
    InitializeWindsor();
}

 

     代码中的RouteManager.RegisterRoutes是实现对Route规则的绑定,而规则的内容是被硬编码到
RouteManager中实现的。关于Route的资料网上有不少,园子里也有不少朋友写过,这里就不做说明了。

     接就上面方法就会运行InitializeWindsor(),这就是Windsor容器初始化的方法:

/// <summary>
/// This web application uses the Castle Project's IoC container, Windsor see:
/// http://www.castleproject.org/container/index.html
/// </summary>
protected virtual void InitializeWindsor()
{
    
if (container == null)
    {
        
// create a new Windsor Container
        container = ContainerBuilder.Build("Configuration\\Windsor.config"); 
        WcfConfiguration.ConfigureContainer(container);
        ServiceLocator.SetLocatorProvider(() 
=> container.Resolve<IServiceLocator>());
        
// set the controller factory to the Windsor controller factory (in MVC Contrib)
        System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));
    }
}

 

      注:“Configuration\\Windsor.config”中的内容较长,主要是一些XML配置节点。大家可以抽时
间阅读一下即可。

     这个方法是今天讲解的主要内容,下面就介绍一下其中的代码。
   
     首先是判断container(IWindsorContainer类型)是否为空,如果容器为空则创建并初始化该容器。
也就是调用ContainerBuilder(Suteki.Shop\ContainerBuilder)类的Build方法来从外部的config文件
中加载默认信息。我们这里就看一下Build方法的实现:

public static IWindsorContainer Build(string configPath)
{
        var container 
= new WindsorContainer(new XmlInterpreter(configPath));
        
// register handler selectors
        container.Kernel.AddHandlerSelector(new UrlBasedComponentSelector(
            
typeof(IBaseControllerService),
            
typeof(IImageFileService),
            
typeof(IConnectionStringProvider)
            ));
        
// automatically register controllers
        container.Register(AllTypes
            .Of
<Controller>()
            .FromAssembly(Assembly.GetExecutingAssembly())
            .Configure(c 
=> c.LifeStyle.Transient.Named(c.Implementation.Name.ToLower())));
        container.Register(
            Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSQLUnitOfWorkManager>().LifeStyle.Transient,
            Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
            Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
            Component.For
<AuthenticateFilter>().LifeStyle.Transient,
            Component.For
<UnitOfWorkFilter>().LifeStyle.Transient,
            Component.For
<DataBinder>().LifeStyle.Transient,
            Component.For
<LoadUsingFilter>().LifeStyle.Transient,
            Component.For
<CurrentBasketBinder>().LifeStyle.Transient,
            Component.For
<ProductBinder>().LifeStyle.Transient,
            Component.For
<OrderBinder>().LifeStyle.Transient,
            Component.For
<IOrderSearchService>().ImplementedBy<OrderSearchService>().LifeStyle.Transient,
            Component.For
<IEmailBuilder>().ImplementedBy<EmailBuilder>().LifeStyle.Singleton
        );
        
return container;
}

     

      首先是读入指定配置文件的XML结点信息,将构造一个 WindsorContainer实现,同时在其微内核中
添加“容器处理组件”的方式(AddHandlerSelector),注意这种处理方式是按我们在业务逻辑中规定
的方式处理的。

      紧跟着又向该容器中注册了Controller,而且配置属性的LifeStyle被指定为Transient类型,这里
有必要介绍一下Castle容器的组件生存周期,主要有如下几种:   

    Singleton : 容器中只有一个实例将被创建
    Transient : 每次请求创建一个新实例
    PerThread: 每线程中只存在一个实例
    PerWebRequest : 每次web请求创建一个新实例
    Pooled :使用"池化"方式管理组件,可使用PooledWithSize方法设置池的相关属性。

     

      可以看到在本项目中,组件的生命周期基本上都被指定成为Transient类型,即当请求发生时创建,
在处理结束后销毁。

      接着再看一下该方法的其余代码,也就是对ModelBinder,Filter,Service这类业务逻辑的组件注
册。同时我们看到有的组类在进行接口注册的同时还被绑定了默认的实现类,其这种硬编码的方法是
是一种“可选”方式。

      说完了Build方法之前,再回到Global.asax文件中的InitializeWindsor方法,看一下其余的代码。
我们看到这样一行:
 

     WcfConfiguration.ConfigureContainer(container);

    
      类WcfConfiguration的ConfigureContainer方法就是继续向当前创建的容器中添加组件,而这次要
加入的组件是Windows Live Writer的IMetaWeblog接口实现类,如下:

public static class WcfConfiguration
{
    
public static void ConfigureContainer(IWindsorContainer container)
    {
        var returnFaults 
= new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true };
        container.AddFacility
<WcfFacility>(f =>
        {
            f.Services.AspNetCompatibility 
= AspNetCompatibilityRequirementsMode.Required;
            f.DefaultBinding 
= new XmlRpcHttpBinding();
        })
            .Register(
                Component.For
<IServiceBehavior>().Instance(returnFaults),
                Component.For
<XmlRpcEndpointBehavior>(),
                Component.For
<IMetaWeblog>().ImplementedBy<MetaWeblogWcf>().Named("metaWebLog").LifeStyle.Transient
                );
    }
}

 
      如前面所说的,扩张单元插件(Facilities)可以在不更改原有组件的基础上注入你所需要的功能代码,
这里就使用了其AddFacility方法来添加扩展单元来注册并管理我们的Windows Live Writer组件。

      下面继分析InitializeWindsor方法中的其余代码,看完了ConfigureContainer方法,接着就是下面这
一行代码了:

     ServiceLocator.SetLocatorProvider(() => container.Resolve<IServiceLocator>());

  
      刚看到这一行让我感觉似曾相识,记得以前在看Oxite的Global.asax中也看过类似的这样一行代码。

     ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));  

     
      只不过那个项目中用的是 Unity而不是Castle Windsor。但实际的功能是一样的。即完成对容器中服务
地址的解析绑定。有了它,就可以通过Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase
中所定义的方法如:DoGetInstanceDoGetAllInstances 来获取相应的服务组件(集合)的实例。

     比如本项目中的DoGetInstance及DoGetAllInstances()实现代码如下:

    (文件位于:Suteki.Common\Windsor\WindsorServiceLocator.cs):

protected override object DoGetInstance(Type serviceType, string key)
{
    
if (key != null)
        
return container.Resolve(key, serviceType);
    
return container.Resolve(serviceType);
}
/// <summary>
/// When implemented by inheriting classes, this method will do the actual work of
/// resolving all the requested service instances.
/// </summary>
/// <param name="serviceType">Type of service requested.</param>
/// <returns>
/// Sequence of service instance objects.
/// </returns>
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
    
return (object[])container.ResolveAll(serviceType);
}

        
        
      注,对该WindsorServiceLocator类的IOC绑定在ContainerBuilder.Build中,如下:  

 
  container.Register(
       Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSQLUnitOfWorkManager>().LifeStyle.Transient,
       Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
       Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
 

     
     
      而InitializeWindsor方法中的最后一行代码如下:    

     System.Web.Mvc.ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));

     
      这里要说明的是WindsorControllerFactory这个类是在 MvcContrib项目中提供的,用于构造一个
Castle项目类型的Controller工厂。
     
    
      好了,今天的内容可能有点杂,主要就是Castle闹的,因为其中有一些概念要了解之后才能把项目中
的代码看懂。不过从整体上来看,Suteki.Shop的IOC机制实现的还是很清晰的,没什么太难看的代码。

      今天就先到这里了。
    
     
   
      原文链接: http://www.cnblogs.com/daizhj/archive/2009/05/26/1456678.html

      作者: daizhj,代震军,LaoD

      Tags: mvc,Suteki.Shop,Castle,IOC

      网址: http://daizhj.cnblogs.com/

[MVC]Asp.net MVC生命周期

mikel阅读(922)


     public class MvcApplication : System.Web.HttpApplication
        {
            
public static void RegisterRoutes(RouteCollection routes)
            {
                routes.IgnoreRoute(
"{resource}.axd/{*pathInfo}");
               
                
//The controller route value is a special value that the System.Web.Mvc.MvcHandler class uses to call into the IControllerFactory interface.
                
//The basic route handler is an instance of IRouteHandler named MvcRouteHandler.
                
//We have complete control and could provide our own implementation of IRouteHandler if we wished.
                routes.MapRoute(
                    
"Default",                                              // Route name
                    "{controller}/{action}/{id}",                           // URL with parameters
                    new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
                );
             
    
            }
    
            
protected void Application_Start()
            {
                RegisterRoutes(RouteTable.Routes);
            }
    

 

UrlRoutingMoudulePostResolveRequestCache阶段从RouteCollection中获取当前请求的RouteData.RouteData包含了一个请求处理对应的ControllerAction,RouteData这个作用贯穿请求的处理过程.RouteData中提取RouteHandler,这里默认是MvcRouteHandler,MvcRouteHandler获取HttpHandler,这里默认的是MvcHandler.

 


    public virtual void PostResolveRequestCache(HttpContextBase context)
{
    RouteData routeData 
= this.RouteCollection.GetRouteData(context);
    
if (routeData != null)
    
{
        IRouteHandler routeHandler 
= routeData.RouteHandler;
        
if (routeHandler == null)
        
{
            
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0]));
        }

        
if (!(routeHandler is StopRoutingHandler))
        
{
            RequestContext requestContext 
= new RequestContext(context, routeData);
            IHttpHandler httpHandler 
= routeHandler.GetHttpHandler(requestContext);
            
if (httpHandler == null)
            
{
                
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() }));
            }

            RequestData data2 
= new RequestData();
            data2.OriginalPath 
= context.Request.Path;
            data2.HttpHandler 
= httpHandler;
            context.Items[_requestDataKey] 
= data2;
            context.RewritePath(
"~/UrlRouting.axd");
        }

    }

}

rh 

  

MvcHandler.ProcessRequest()中首先使用HttpContextWrapperHttpContext进行封装,封装的目的是为了解耦以获得可测试性.然后从RequestContext.RouteData中提取Controller名称.
ControllerBuilder.GetControllerFactory –> ControllerFactory.CreateController –> IController.Execute

ControllerBase实现了IController接口,Initialize时将RequestContext封装成为ControllerContext,Controller继承自ControllerBase并实现抽象方法ExecuteCore()

 

mvchttp 

 

ExecuteCore,Controller首先从RouteData中获得ActionName,然后执行ActionInvoker.InvokeAction.

ActionInvoker中我们可以看到各种Filter,这是一种AOP实践:Action方法执行的前后执行若干方法.这里有四种Filter:ActionFilters,ResultFilters,AuthorizationFilters,ExceptionFilters.这四种Filter并不是封闭的,都有对应的接口,这四个只是默认实现.Filter的执行顺序是:AuthorizationFilter—>Action Filter.OnActionExecuting—>Action Method—>ActionFilter.OnActionExecuted.InvokeActionMethodWithFilters返回的结果是ActionExecutedContext,接下来将Controller执行OnResultExecuting 方法.ActionResult执行的结果可以是ViewResult,JsonResult,RedirectResult,ContentResult,或者是自定义的Result类型.

       如果返回的类型是ViewResult,我们先看一下ViewReuslt的继承关系:ViewResult–>ViewResultBase–>ActionResult,ViewResult包含两个属性ViewViewEngineCollection,实际上是包含了两个接口的实现:IViewEngine定义了怎么定位View/Partial View.IView定义了如何RenderView.默认的实现时WebFormViewWebFormViewEngine.

Filter OnResultExecuted 最后一步了,可以这里捕获异常.上面我们说过还有ExceptionFilters,如果前面过程中的异常没有被捕获那么最终都会到冒泡到ExceptionFilters.

  • RouteData中获得ActionName
  • ActionInvoker.InvokeAction
  • 通过ControllerContext获取ControllerDescriptor
  • FindAction-获取ActionDescriptor
  • GetFilters
  • ModelBinderRequest中的数据转换成Action方法需要的参数
  • AuthorizationFilter
  • Action Filter.OnActionExecuting
  • Action
  • ActionFilter.OnActionExecuted
  • ResultFilter.OnResultExecuting
  • ActionResult Execution
  • ResultFilter.OnResultExecuted
  • WebFormViewEngine.CreateView
  • WebFormView.Render
  • ResultFilter.OnExecuted

result 

 

控制权归还到HttpApplication完成后续的生命周期.

 

嗯哼,全文完.

[Flex]Flash Builder 4 beta、Flex SDK(Gumbo)

mikel阅读(779)

详细请看:http://www.k-zone.cn/zblog/post/adobe-flash-builder-gumbo-flash-catalyst-list.html

昨天是2009年的国际儿童节,Adobe也没忘记作为不是儿童的我们,如期的发布了Adobe Flash Builder 4 beta 和 Adobe Flash Catalyst beta 这两个我认为09年上半年最为重磅级的产品:)

而昨天也只是匆忙的介绍了一下Adobe Flash Builder 4 beta 和 Adobe Flash Catalyst beta的下载地址,作为Flex程序员来说,不详细介绍一下这些工具下都对不起大家。:)

其实昨天对外发布的除了Adobe Flash Builder 4 beta 和 Adobe Flash Catalyst beta以外,还应该包括一个重量级的选手 – Flex SDK 4 ( Gumbo )

只不过Gumbo应该是旧消息了,因此才没有大书特书:)
早在MAX 2008的时候,就已经放出了Flex SDK 4 ( Gumbo )的内容,同时在Adobe驱动的open source site上面就可以下载Nightly Builds的Flex SDK 4 ( Gumbo )

Flex SDK 4 ( Gumbo )的一些知识的汇总:
Flex SDK 4 ( Gumbo )主页:
包含了Flex SDK 4 (&
nbsp;Gumbo )的功能、文档、规格等内容:
http://opensource.adobe.com/wiki/display/flexsdk/Gumbo

Flex SDK 4 ( Gumbo )的SDK下载地址:
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4

Adobe Flex SDK 4 ( Gumbo ) Language Reference:
http://livedocs.adobe.com/flex/gumbo/langref/index.html

Adobe Flex SDK 4 ( Gumbo )官方介绍:
http://www.adobe.com/devnet/flex/articles/flex4sdk_whatsnew.html

Flash Builder 4.0 beta的一些知识的汇总:
Flash Builder 4.0 beta是专门配合FFlex SDK 4 ( Gumbo ) ,虽然我们可以在其他版本中导入Flex SDK 4 ( Gumbo ) ,但是却无法体现Flex SDK 4 ( Gumbo ) 的一些特点。
Flash Builder 4.0 beta包含的功能:
1、支持Theme切换。
2、FXP的导入和导出。
3、FlexUnit集成。
4、ASDoc视图。
5、Data Services支持。
等等。

这里有一篇关于Flash Builder 4.0 beta最新功能的介绍:
http://www.adobe.com/devnet/flex/articles/flex4builder_whatsnew.html

这是一个关于Flash Builder 4.0 beta的video介绍。(MAX 2008的介绍,已经很

[MVC]Asp.net MVC 示例项目"Suteki.Shop"分析之---NVelocity模

mikel阅读(844)

   在Suteki.Shop中使用了NVeloctiy模版引擎,用于提供可订制的邮件模版。而邮件的功能就是当定单状
态发生变化时,系统会向买家发送邮件通知。其中的邮件信息内容就是采用NVeloctiy的模版(.vm扩展名)
进行订制的。
     因为在Sutekie.Shop的最新源码包中只是部分实现了其功能,而全部的功能还在完善中,所以要运行本
文中所说的功能,需要在下面的链接地址中下载其最新程序文件(包括单元测试文件):

    http://code.google.com/p/sutekishop/source/detail?r=282
   
     要下载的文件包括:
    

/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/OrderConfirmation.vm 
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/OrderDispatch.vm  
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Views/EmailTemplates/_orderDetails.vm
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Controllers/OrderStatusController.cs  
/branches/JtG_Enhancements/Suteki.Shop/Suteki.Shop/Services/EmailService.cs 

    
     等等。
   
     当下载并覆盖(或添加)到本地项目中后,我们还需要在Castle Windsor中注册相应的EmailBuilder
组件。我们只要打开ContainerBuilder类并找到其Build方法(Suteki.Shop\ContainerBuilder.cs),并
添加如下代码:

    Component.For<IEmailService>().ImplementedBy<EmailService>().LifeStyle.Singleton
   
    注:有关Castle Windsor 的 IOC的内容我已在这篇文章中做了介绍.  

  
    最终的代码如下所示:   
 

   
container.Register(
  Component.For
<IUnitOfWorkManager>().ImplementedBy<LinqToSQLUnitOfWorkManager>().LifeStyle.Transient,
  Component.For
<IFormsAuthentication>().ImplementedBy<FormsAuthenticationWrapper>(),
  Component.For
<IServiceLocator>().Instance(new WindsorServiceLocator(container)),
  Component.For
<AuthenticateFilter>().LifeStyle.Transient,
  Component.For
<UnitOfWorkFilter>().LifeStyle.Transient,
  Component.For
<DataBinder>().LifeStyle.Transient,
  Component.For
<LoadUsingFilter>().LifeStyle.Transient,
  Component.For
<CurrentBasketBinder>().LifeStyle.Transient,
  Component.For
<ProductBinder>().LifeStyle.Transient,
  Component.For
<OrderBinder>().LifeStyle.Transient,
  Component.For
<IOrderSearchService>().ImplementedBy<OrderSearchService>().LifeStyle.Transient,
  Component.For
<IEmailBuilder>().ImplementedBy<EmailBuilder>().LifeStyle.Singleton,
        Component.For
<IEmailService>().ImplementedBy<EmailService>().LifeStyle.Singleton //新加的代码
 ); 
 

 

     完成了这些工作后,我们就可以编译运行该项目了。
   
     下面我们来看一下今天的主角 EMailBuilder,其实现了使用NVelocityEngine加载模版信息并将
ViewData中的数据与模版中的指定变量进行绑定的工作。下面是其类图:
    
        
 

     下面对其中相关类和接口做一下介绍:
   
     首先是IEmailBuilder接口,该接口中只有一个方法GetEmailContent,用于将指定的NVelocity
版与ViewData的数据进行绑定,其中参数templateName就是指定的NV模版名称,而 viewdata就是服务
(EmailService)所传递过来的定单数据。

 /// <summary>
 
/// Provide the base method and property to build email
 
/// </summary>
 public interface IEmailBuilder
 {
      
/// <summary>
      
/// Get the email content
      
/// </summary>
      
/// <returns>Return the email content.</returns>
      string GetEmailContent(string templateName, IDictionary<stringobject> viewdata);
 }

 
     而做为IEmailBuilder接口的实现类,EMailBuilder 中相应的GetEamilContent方法实现代码如下:

public string GetEmailContent(string templateName, IDictionary<stringobject> viewdata)
{
    
return BuildEmail(templateName, viewdata);
}
string BuildEmail(string templateName, IDictionary<stringobject> viewdata)
{
    
if (viewdata == null)
    {
      
throw new ArgumentNullException("viewData");
    }
    
if (string.IsNullOrEmpty(templateName))
    {
      
throw new ArgumentException("TemplateName");
    }
    var template 
= ResolveTemplate(templateName);
    var context 
= new VelocityContext();
    
foreach (var key in viewdata.Keys)
    {
     context.Put(key, viewdata[key]);
    }
    
using (var writer = new StringWriter())
    {
       template.Merge(context, writer);
       
return writer.ToString();
    }
}

   

     可以看出,最终获取Email内容的工作交给了BuildEmail这个方法,其实现的逻辑还是很清晰的。
首要是判断传入参数是否为空(包括viewdata,templateName),然后调用ResolveTemplate
方法来获取指定NV模版的信息内容,其方法(ResolveTemplate)内容如下:

Template ResolveTemplate(string name)
{
    name 
= Path.Combine(templatePath, name);
    
if (!Path.HasExtension(name))
    {
      name 
+= ".vm";
    }
    
if (!velocityEngine.TemplateExists(name))
    {
      
throw new InvalidOperationException(string.Format("Could not find a template named '{0}'", name));
    }
    
return velocityEngine.GetTemplate(name);
}

   

      ResolveTemplate的工作流程即:先判断指定的模版路径中是否包括扩展名,如不包括则添加"vm"
结尾的扩展名(该扩展名是NV模版的指定扩展名)。然后继续判断指定路径下的模版文件是否存在“
TemplateExists”。并最终使用velocityEngine来完成获取模版文件内容信息的功能。

     注:velocityEngine的初始化在构造方法:
    
     EmailBuilder(IDictionary<string, object> nvelocityProperties) 中实现。

      分析完ResolveTemplate方法,再回到上面的BuildEmail方法中看一下其余的代码。运行完获取模
版信息的方法之后, 接着就是使用viewdata中的数据构造一个 VelocityContext对象并使用它来完成
与指定模版信息的绑定了。即下面的这几行代码:

   var context = new VelocityContext();
   
foreach (var key in viewdata.Keys)
   {
     context.Put(key, viewdata[key]);
   }
   
using (var writer = new StringWriter())
   {
     template.Merge(context, writer);
     
return writer.ToString();
   }

  

     到这里其本上就完成了对NV模版的数据绑定并返回其最终结果了。下面看一下Suteki.Shop是如何
使用它的。

     首先我们要看一下整个EMail通知发送体系的类图:

 
   

     注:图中右下角就是上面我们所说的那个EmailBuilder 
     
     我们要先清楚了解一下上图中的类关系:
   
     图中右上角OrderStatusController这个Controller,顾名思义其用于定单状态发生变化时的控制器操作,
它定义了几个Action方法(图中的Method),其中Dispatch方法就包括对EmailService类中“发送Dispatch通
知(SendDispatchNotification)”的方法调用,而就是该方法会最终完成对EMailBuilder调用,下面就以调用
的先后顺序依次介绍一下其实代码:

    首先是OrderStatusController中的Dispatch方法,其实现代码如下:  

  [AdministratorsOnly]
    
public class OrderStatusController : ControllerBase
    {
        
readonly IRepository<Order> orderRepository;
        
readonly IUserService userService;
        
readonly IEmailService emailService;
        
public OrderStatusController(IRepository<Order> orderRepository, IUserService userService, IEmailService emailService)
        {
            
this.orderRepository = orderRepository;
            
this.emailService = emailService;
            
this.userService = userService;
        }
        [UnitOfWork]
        
public ActionResult Dispatch(int id)
        {
            var order 
= orderRepository.GetById(id);
            
if (order.IsCreated)
            {
                order.OrderStatusId 
= OrderStatus.DispatchedId;
                order.DispatchedDate 
= DateTime.Now;
                order.UserId 
= userService.CurrentUser.UserId;
                emailService.SendDispatchNotification(order);
            }
            
return this.RedirectToAction<OrderController>(c => c.Item(order.OrderId));
        }
        
    }

    
     首先就是把Http请求过来的定单ID作为参数,并调用orderRepository.GetById方法获取该定单ID的相关
信息,然后判断其是否已被创建(IsCreated为“true”), 如果已创建就可以将当前的定单信息中的状态设
为“DispatchedId”,同时将 DispatchedDate时间设置为系统当前时间,然后是用户ID的绑定。当一切完
成后,就可以将该定单数据作为参数传递给IEmailService的SendDispatchNotification方法,以启动“发送
Email通知买家”的流程了。

     下面是接口IEmailService的实现类“EmailService”(Suteki.Shop\Services\EmailService.cs)中相应
方法的实现代码:
  

public void SendDispatchNotification(Order order)
{
    var viewdata 
= new Dictionary<stringobject>
    {
       { 
"order", order },
       { 
"shopName", baseService.ShopName }
    };
    var email 
= builder.GetEmailContent(OrderDispatchTemplate, viewdata);
    var subject 
= "{0}: Your Order has Shipped".With(baseService.ShopName);
    sender.Send(order.Email, subject, email, 
true);
}

 

     在这里就完成了对EmailBuilder类中的GetEmailContent方法的调用,并最终使用EmailSender(发送
邮件的功能类)来发送邮件(“Send方法”)给指定的买家。

     下面就完看一下其最终的运行效果,首先我们要先创建一个定单(注:创建定单的流程在本系列文章的第
一篇中已做过说明,这里就暂且略过了)。然后我们以管理员的身份登录系统,并单击顶部导航的“Online-
Shop”链接,然后点击左侧的“Orders”链接即可看到一个订单列表页面,如下图所示:

      
    

     然后点击相应的订单“Number”,就会进入到相应订单明细页面,如下:
   
      

     点击页面中的“Dispatch”链接,之后就会修改当前定单的状态同时发送相应的Email给买家了,这里为
了清楚起见,我在EmailBuilder中设置了一个断点,并截了一张图,来让大家看一下其最终返回的邮件信息内
容:

       
   
     因为我本地的机器上未安装发送Email的插件,所以无法真正将该Email发送到我指定的邮箱,以便能看到
最终效果,但这并不影响大家对本文的了解,呵呵。
   
    
    今天就先到这里了。

    
    原文链接:http://www.cnblogs.com/daizhj/archive/2009/06/03/1457521.html

    作者: daizhj,代震军,LaoD

    Tags: mvc,Suteki.Shop,NVelocity

    网址: http://daizhj.cnblogs.com/