1.服务层代码
1.1接口
namespace Services
{
[ServiceContract(Name = "GetProducts", Namespace = "http://www.yoyo.com")]
public interface IGetProducts
{
//Action,ReplyAction用于维持接口层级之间的关系,主要作用体现在有接口继承的情况下
[OperationContract(Name = "GetProduct", Action = "http://www.yoyo.com/IGetProducts/GetProduct", ReplyAction = "http://www.yoyo.com/IGetProducts/GetProductResponse")]
Products GetProduct(int products);
}
/// <summary>
/// 回调契约:CallbackContract指定回调契约;使用wsDualHttpBinding通信协议
/// </summary>
[ServiceContract(Name = "AddProducts", Namespace = "http://www.yoyoyo.com", CallbackContract = typeof(IAddProductsCallback))]
public interface IAddProducts
{
//, IsOneWay = true:是一个异步调用标记,不标记它就会形成同步阻塞
//此操作将死锁,因为在当前邮件完成处理以前无法收到答复。
//这个异常是在AddProduct和GetResult都没有标记IsOneWay = true时出现的
//如果要允许无序的邮件处理,则在 ServiceBehaviorAttribute
//上指定可重输入的或多个 ConcurrencyMode。
[OperationContract(Name = "AddProduct")]
void AddProduct(Products products);
}
public interface IAddProductsCallback
{
//因为有IsOneWay标记,此时就不能有返回值,即不能有输出参数
//(IsOneWay = true)
[OperationContract]
void GetResult(string msg);
}
/// <summary>
/// 回调契约:CallbackContract指定回调契约;使用netTcpBinding通信协议
/// </summary>
[ServiceContract(Name = "DelProducts", Namespace = "http://www.yoyoyoyo.com", CallbackContract = typeof(IDelProductsCallback))]
public interface IDelProducts
{
/*注意:此接口和对应的回调接口中的方法名可以相同,序列化到
*SOAP包中就会报如下错误:
*
* 元数据包含无法解析的引用:“http://localhost:8110/”。
* 没有终结点在侦听可以接受消息的 http://localhost:8110/。这通常是由于不正确的地址或者
* SOAP 操作导致的。如果存在此情况,请参阅 InnerException 以了解详细信息。远程服务器返
* 回错误: (404) 未找到。
*
* 因此,需要指定别名
*/
[OperationContract(Name = "DelProduct", IsOneWay = true)]
void DelProduct(int productsID);
}
public interface IDelProductsCallback
{
[OperationContract(Name = "DelProductsCallback", IsOneWay = true)]
void DelProduct(string msg);
}
/// <summary>
/// 用于测试异常
/// </summary>
[ServiceContract(Name = "UpdateProducts", Namespace = "http://www.yoyoyoyoyo.com")]
public interface IUpdateProducts
{
/*注意:此处可能会报以下错误:
*
* 由于 ContractFilter 在 EndpointDispatcher 不匹配,因此 Action 为
* “http://www.yoyoyoyoyoyo.com/UpdateProducts/UpdateProduct”的消息
* 无法在接收方处理。这可能是由于协定不匹配(发送方和接收方 Action 不
* 匹配)或发送方和接收方绑定/安全不匹配。请检查发送方和接收方是否具有
* 相同的协定和绑定(包括安全要求,如 Message、Transport、None)。[MessageParameter(Name = "Products")]
*
* 当在契约上加了[FaultContract(typeof(Exception))]标记后,服务端的异常并不影响客户端的执行
*
*/
[OperationContract]
void UpdateProduct( Products products);
}
/// <summary>
/// 用于测试异常
/// </summary>
[ServiceContract(Name = "GetProductssList", Namespace = "http://www.yoyoyoyoyoyo.com")]
public interface IGetProductssList
{
/*注意:此处不使用[ServiceKnownType(typeof(NotebookComputer))]
* 基类数据契约上不使用[KnownType(typeof(NotebookComputer))]
* 在客户端是带不出其派生类的
*/
[OperationContract]
List<Products> GetProductsList();
[OperationContract]
[ServiceKnownType(typeof(NotebookComputer))]
void AddNotebookComputer(Products products);
[OperationContract]
[ServiceKnownType(typeof(NotebookComputer))]
List<Products> GetNotebookComputer();
}
}
1.2接口实现
namespace Services
{
public class GetProducts : IGetProducts
{
DBHelper dbHelp = new DBHelper();
#region IGetProducts 成员
public Products GetProduct(int products)
{
return dbHelp.GetProducts(products);
}
#endregion
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class AddProducts : IAddProducts
{
#region IAddProducts 成员
public void AddProduct(Products products)
{
DBHelper dbHelp = new DBHelper();
string msg = null;
try
{
dbHelp.AddProducts(products);
msg = "添加完成";
}
catch
{
msg = "添加失败!";
}
IAddProductsCallback callback = OperationContext.Current.GetCallbackChannel<IAddProductsCallback>();
callback.GetResult(msg);
}
#endregion
}
public class DelProducts : IDelProducts
{
#region IDelProducts 成员
public void DelProduct(int productsID)
{
DBHelper dbHelp = new DBHelper();
string msg = null;
try
{
dbHelp.DelProducts(productsID);
msg = "删除成功";
}
catch
{
msg = "删除失败!";
}
IDelProductsCallback callback = OperationContext.Current.GetCallbackChannel<IDelProductsCallback>();
callback.DelProduct(msg);
}
#endregion
}
public class UpdateProducts : IUpdateProducts
{
#region IUpdateProducts 成员
public void UpdateProduct(Products products)
{
DBHelper dbHelp = new DBHelper();
try
{
dbHelp.UpdateProducts(products);
}
catch
{
InvalidOperationException ex = new InvalidOperationException("传入参数缺少主键");
ExceptionDetail detail = new ExceptionDetail(ex);
//ExceptionDetail用于调试错误代码
//FaultException就是SOAP错误;此时服务端的错误和客户端的错误信息一致
throw new FaultException<ExceptionDetail>(detail, ex.Message.ToString());
}
}
#endregion
}
public class GetProductssList : IGetProductssList
{
DBHelper dbHelp = new DBHelper();
#region IGetProductssList 成员
public List<Products> GetProductsList()
{
return dbHelp.GetProductsList();
}
public List<Products> GetNotebookComputer()
{
return dbHelp.GetNotebookComputer();
}
public void AddNotebookComputer(Products products)
{
dbHelp.AddNotebookComputer(products);
}
#endregion
}
}
1.3数据对象
namespace DBModel
{
/// <summary>
/// Name:设置Products在SOAP包中的名称,同时反映到客户端
/// http://schemas:数据契约命名空间需要使用此字符串开头
/// </summary>
[DataContract(Name = "Products", Namespace = "http://schemas.Products")]
//[KnownType(typeof(NotebookComputer))]
public class Products : IExtensibleDataObject
{
private Int32 m_ProductID;
/// <summary>
/// DataMember:使用在属性上,而不是使用在成员上
/// IsRequired:表示此字段在序列化之前是否需要赋值
/// Order:指该对象在序列化时的顺序,即在SOAP包中的顺序,主要作用体现在等效数据契约上
/// </summary>
[DataMember(Name = "ProductID", IsRequired = true, Order = 1)]
public Int32 ProductID
{
set { m_ProductID = value; }
get { return m_ProductID; }
}
[DataMember(Name = "ProductName", IsRequired = true, Order = 2)]
public String ProductName { get; set; }
[DataMember(Name = "ProductOrigin", IsRequired = true, Order = 3)]
public String ProductOrigin { get; set; }
[DataMember(Name = "ProductCategory", IsRequired = false, Order = 4)]
public String ProductCategory { get; set; }
#region IExtensibleDataObject 成员
private ExtensionDataObject m_extensionDataObject;
public ExtensionDataObject ExtensionData
{
get
{
return m_extensionDataObject;
}
set
{
m_extensionDataObject = value;
}
}
#endregion
}
public class NotebookComputer : Products
{
public string Bluetooth { get; set; }
}
}
2.数据访问层(略)
3.宿主
3.1
static void Main(string[] args)
{
//GetProducts
ServiceHost host1 = new ServiceHost(typeof(GetProducts));
host1.Open();
Console.WriteLine("basicHttpBinding服务已启动!");
//AddProducts
ServiceHost host2 = new ServiceHost(typeof(AddProducts));
host2.Open();
Console.WriteLine("wsDualHttpBinding双工服务已启动!");
//DelProducts
ServiceHost host3 = new ServiceHost(typeof(DelProducts));
host3.Open();
Console.WriteLine("netTcpBinding双工服务已启动!");
//UpdateProducts
ServiceHost host4 = new ServiceHost(typeof(UpdateProducts));
host4.Open();
Console.WriteLine("测试异常服务已启动!");
//GetProductssList
ServiceHost host5 = new ServiceHost(typeof(GetProductssList));
host5.Open();
Console.WriteLine("获取数据列表服务已启动!");
Console.ReadKey();
}
}
3.2服务配置:
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding name="dualHttpBinding" transactionFlow="true" maxReceivedMessageSize="100000"
messageEncoding="Text" clientBaseAddress="http://localhost:8850/Services/" />
</wsDualHttpBinding>
</bindings>
<services>
<!--这一部分使用wsDualHttpBinding进行双通道通信-->
<service name="Services.AddProducts" behaviorConfiguration="serviceBehavior">
<endpoint address="AddProducts" binding="wsDualHttpBinding" bindingConfiguration="dualHttpBinding"
contract="Services.IAddProducts" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8020" />
</baseAddresses>
</host>
</service>
<!--这一部分使用netTcpBinding进行双通道通信-->
<service name="Services.DelProducts" behaviorConfiguration="serviceBehavior">
<!--使用http协议绑定;绑定具体的服务协议;设置服务地址,这个地址是个相对baseAddress的地址-->
<endpoint binding="netTcpBinding" contract="Services.IDelProducts" address="DelProducts" ></endpoint>
<!--这是元数据交换特有的绑定协议;IMetadataExchange元数据交换协定-->
<endpoint binding="mexTcpBinding" contract="IMetadataExchange" address="mex"></endpoint>
<!--这个节配置宿主信息-->
<host>
<baseAddresses>
<add baseAddress="http://localhost:8030"/>
<add baseAddress="net.tcp://localhost:8031"/>
</baseAddresses>
</host>
</service>
<!--处理服务异常-->
<service name="Services.UpdateProducts" behaviorConfiguration="serviceBehavior">
<endpoint address="UpdateProducts" binding="basicHttpBinding" contract="Services.IUpdateProducts" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8040" />
</baseAddresses>
</host>
</service>
<!--获取数据列表-->
<service name="Services.GetProductssList" behaviorConfiguration="serviceBehavior">
<endpoint address="GetProductssList" binding="basicHttpBinding" contract="Services.IGetProductssList" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8050" />
</baseAddresses>
</host>
</service>
<!--这个Name是必须的,它指定了具体提供服务的类;behaviorConfiguration设置行为配置策略-->
<service name="Services.GetProducts" behaviorConfiguration="serviceBehavior">
<!--使用http协议绑定;绑定具体的服务协议;设置服务地址,这个地址是个相对baseAddress的地址-->
<endpoint binding="basicHttpBinding" contract="Services.IGetProducts" address="GetProducts" ></endpoint>
<!--这是元数据交换特有的绑定协议;IMetadataExchange元数据交换协定-->
<endpoint binding="mexHttpBinding" contract="IMetadataExchange" address="mex"></endpoint>
<!--这个节配置宿主信息-->
<host>
<baseAddresses>
<add baseAddress="http://localhost:8010"/>
</baseAddresses>
</host>
</service>
</services>
<!--这个节使用来配置服务的行为-->
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<!--指定元数据使用http的get方式获取-->
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<!--<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="DBModel.Products, DBModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<knownType type="DBModel.NotebookComputer, DBModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>-->
4.客户端:
客户端代理生成参见学习笔记(二),单独说明回调的使用:
public class AddCallBack : AddProductsCallback
{
private static string message;
#region AddProductsCallback 成员
/*
* 注意:此回调方法是在本次服务调用完并且在页面完全显示后才执行
* GetMsg()方法最初的用意是返回到界面上弹出提示信息,由于此时页
* 面已完全显示,所以此方法是没必要的
*
* 注意:http://localhost:8788/Services/其中的端口号,每
* 启动一次客户端进程,这个端口号就需要改变,否则报错:
* HTTP 无法注册 URL http://+:8788/Services/。另一应用程序已使用 HTTP.SYS 注册
* 解决方案:使用编程的方式创建客户端代理
*/
public void GetResult(string msg)
{
message = msg;
}
#endregion
public string GetMsg()
{
return message;
}
}
public class DelCallBack : DelProductsCallback
{
private static string message;
#region DelProductsCallback 成员
public void DelProductsCallback(string msg)
{
message = msg;
}
#endregion
public string GetMsg()
{
return message;
}
}
}
5.动态设置客户端监听基址,此代码在双工通信方式下调用:
namespace WebClient
{
/*
动态设置客户端Endpoint地址
IPAddress ipAddress;int port;
client.Endpoint.Address = new EndpointAddress("http://" + ipAddress.ToString() + ":"
+ port.ToString());
*/
/// <summary>
/// 动态指定客户端终结点地址,避免出现以下错误:
///
/// </summary>
public class AddProductClientBaseAddressEx
{
public static void GetAddProductsClientBaseAddress(AddProductsClient client)
{
string clientBaseAddress = null;
IPAddress address = Dns.GetHostAddresses("127.0.0.1")[0];
clientBaseAddress = "http://" + address.ToString() + ":";
WSDualHttpBinding binding = client.Endpoint.Binding as WSDualHttpBinding;
int port = AddProductClientBaseAddressEx.FindFreePort(address);
binding.ClientBaseAddress = new Uri(clientBaseAddress + port.ToString());
}
public static int FindFreePort(IPAddress hostAddress)
{
//hostAddress:这个服务的对应的主机地址;
IPEndPoint endPoint = new IPEndPoint(hostAddress, 0);
//InterNetwork:IPV4地址协议
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(endPoint);
IPEndPoint local = (IPEndPoint)socket.LocalEndPoint;
return local.Port;
}
}
}
}