- Utwórz projekt IServices i dodaj interfejs
public interface IHelloRemotingService
{
string Ping();
string Ping(string message);
}
- Utwórz projekt RemotingServices i dodaj implementację
public class HelloRemotingService : MarshalByRefObject, IHelloRemotingService
{
public string Ping()
{
return "Pong";
}
public string Ping(string message)
{
return message;
}
}
- Utwórz projekt RemotingServiceHost
static void Main(string[] args)
{
// add reference System.Runtime.Remoting
int port = 8080;
RemotingServices.HelloRemotingService remotingService = new RemotingServices.HelloRemotingService();
TcpChannel channel = new TcpChannel(port);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingServices.HelloRemotingService), "Ping", WellKnownObjectMode.Singleton);
Console.WriteLine($"Remoting service started on {port}");
Console.ReadLine();
}
- Utwórz projekt HelloRemotingServiceConsoleClient
private static void PingTest()
{
IServices.IHelloRemotingService client;
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
client = (IServices.IHelloRemotingService)Activator.GetObject(typeof(IServices.IHelloRemotingService), "tcp://localhost:8080/Ping");
while (true)
{
Console.WriteLine("Ping");
string response = client.Ping("Pong");
Console.WriteLine(response);
Thread.Sleep(TimeSpan.FromSeconds(2));
}
}
- Utwórz projekt HelloService
- Utwórz interfejs
// add reference System.ServiceModel
[ServiceContract]
public interface IHelloService
{
[OperationContract]
string Ping(string message);
}
- Utwórz implementację
public class HelloService : IHelloService
{
public string Ping(string message)
{
return message;
}
}
- Utwórz projekt HelloServiceHost
static void Main(string[] args)
{
using(ServiceHost host = new ServiceHost(typeof(HelloService.HelloService)))
{
host.Open();
Console.WriteLine("Host started on");
foreach (var uri in host.BaseAddresses)
{
Console.WriteLine(uri);
}
Console.ReadLine();
}
}
- Dodaj konfigurację w pliku app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<system.serviceModel>
<services>
<service name="HelloService.HelloService" behaviorConfiguration="mexBehavior">
<endpoint address="HelloService" binding="basicHttpBinding" contract="HelloService.IHelloService">
</endpoint>
<endpoint address="HelloService" binding="netTcpBinding" contract="HelloService.IHelloService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange">
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/" />
<add baseAddress="net.tcp://localhost:8090/ "/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="mexBehavior">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
- Uruchom jako administator
-
Utwórz projekt HelloServiceConsoleClient
-
Wybierz opcję Add Service Reference i wskaż adres http://localhost:8080/
-
Utwórz kod klienta
static void Main(string[] args)
{
HelloService.HelloServiceClient client = new HelloService.HelloServiceClient();
string response = client.Ping("Hello");
Console.WriteLine(response);
}
uwaga - W przypadku gdy w pliku konfiguracyjnym zdefiniowania wiele bindingów należy w konstruktorze przekazać nazwę konfiguracji.
Zalety:
- łatwe generowanie
Wady:
- Brak odporności na zmianę (po każdej zmianie kontraktu należy wygenerować klienta na nowo
static void Main(string[] args)
{
HelloService.HelloServiceClient client = new HelloService.HelloServiceClient("BasicHttpBinding_IHelloService");
string response = client.Ping("Hello");
Console.WriteLine(response);
}
public class HelloServiceProxy : ClientBase<IHelloService>, IHelloService
{
public string Ping(string message)
{
return base.Channel.Ping(message);
}
public Task<string> PingAsync(string message)
{
return base.Channel.PingAsync(message);
}
}
private static void ChannelFactoryTest()
{
BasicHttpBinding myBinding = new BasicHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress("http://localhost:8080/HelloService");
ChannelFactory<IHelloService> proxy = new ChannelFactory<IHelloService>(myBinding, myEndpoint);
IHelloService client = proxy.CreateChannel();
string response = client.Ping("Hello");
((IClientChannel)client).Close();
}
Zalety:
- odporność na zmianę (nie wymaga żadnych modyfikacji przy zmianie kontraktu)
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
http://localhost:8080/?xsd=xsd2
brak - domyślnie używa DataContractSerializer. Serializuje wszystkie publiczne właściwości w porządku alfabetycznym. Prywatne pola i właściwości nie są serializowane.
[Serializable] - serializacuje wszystkie pola. Nie mamy kontroli nad tym, które pola będą zawarte lub pominięte w danych.
[DataContract] - serializacuje wszystkie pola oznaczone [DataMember]. Pola, które nie są oznaczone atrybutem [DataMember] są pomijane z serializacji. Atrybut [DataMember] może być stosowany również do prywatnych pól i publicznych właściwości.
[DataContract]
public class Employee
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
namespace, nazwy pól, kolejność
[DataContract(Namespace = "http://www.altkom.pl/Employee")]
public class Employee
{
[DataMember(Name = "ID", Order = 1)]
public int Id { get; set; }
[DataMember(Order = 2)]
public string FirstName { get; set; }
[DataMember(Order = 3, IsRequired = true)]
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
[KnownType(typeof(FullTimeEmployee))]
[KnownType(typeof(PartTimeEmployee))]
[DataContract(Namespace = "http://www.altkom.pl/Employee")]
public class Employee
{
[DataMember(Name = "ID", Order = 1)]
public int Id { get; set; }
[DataMember(Order = 2)]
public string FirstName { get; set; }
[DataMember(Order = 3, IsRequired = true)]
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class FullTimeEmployee : Employee
{
public decimal AnnualSalary { get; set; }
}
public class PartTimeEmployee : Employee
{
public decimal HourlyPay { get; set; }
}
Dotyczy wszystkich operacji tylko w kontrakcie usługi.
[ServiceKnownType(typeof(PartTimeEmployee))]
[ServiceKnownType(typeof(FullTimeEmployee))]
[ServiceContract]
public interface IEmployeeService
{
[OperationContract]
Employee Get(int id);
[OperationContract]
void Add(Employee employee);
}
[ServiceContract]
public interface IEmployeeService
{
[ServiceKnownType(typeof(PartTimeEmployee))]
[ServiceKnownType(typeof(FullTimeEmployee))]
[OperationContract]
Employee Get(int id);
[OperationContract]
void Add(Employee employee);
}
[MessageContract(IsWrapped = true, WrapperName = "EmployeeRequestObject", WrapperNamespace = "http://altkom.pl")]
public class EmployeeRequest
{
[MessageHeader(Namespace = "http://altkom.pl")]
public string LicenseKey { get; set; }
[MessageBodyMember(Namespace = "http://altkom.pl")]
public int EmployeeId { get; set; }
}
[MessageContract(IsWrapped = true, WrapperName = "EmployeeInfoObject", WrapperNamespace = "http://altkom.pl")]
public class EmployeeInfo
{
[MessageBodyMember(Order = 1, Namespace = "http://altkom.pl")]
public int Id { get; set; }
[MessageBodyMember(Order = 2, Namespace = "http://altkom.pl")]
public string FirstName { get; set; }
[MessageBodyMember(Order = 3, Namespace = "http://altkom.pl")]
public string LastName { get; set; }
public EmployeeInfo()
{
}
public EmployeeInfo(Employee employee)
{
this.Id = employee.Id;
this.FirstName = employee.FirstName;
this.LastName = employee.LastName;
}
}
<behavior name="includeExceptionDetails">
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class CalculatorService : ICalculatorService
{
public int Divide(int numerator, int denominator)
{
return numerator / denominator;
}
}
W WCF należy rzucać wyjątkami FaultException lub FaultException zamiast wyjątków .NET
Są tego 2 przyczyny:
- po wystąpieniu wyjątku kanał komunikacyjny przechodzi w stan Fault
- wyjątki są specyficzne dla platformy .NET
public class CalculatorService : ICalculatorService
{
public int Divide(int Numerator, int Denominator)
{
if (Denominator == 0)
throw new DivideByZeroException();
return Numerator / Denominator;
}
}
public class CalculatorService : ICalculatorService
{
public int Divide(int Numerator, int Denominator)
{
if (Denominator == 0)
throw new FaultException("Denomintor cannot be ZERO", new FaultCode("DivideByZeroFault"));
return Numerator / Denominator;
}
}
[DataContract]
public class DivideByZeroFault
{
[DataMember]
public string Error { get; set; }
[DataMember]
public string Details { get; set; }
}
public int Divide(int numerator, int denominator)
{
if (denominator == 0)
{
DivideByZeroFault divideByZeroFault = new DivideByZeroFault
{
Error = "DivideByZero",
Details = "denominator is zero"
};
throw new FaultException<DivideByZeroFault>(divideByZeroFault);
}
return numerator / denominator;
}
- Przechwytywanie
public int Calculate()
{
try
{
int result = client.Divide(10, 0);
} catch(FaultException<CalculatorService.DivideByZeroFault> e)
{
}
}
`
SOAP/WCF -> RCP (Remote Call Procedures) REST API -> resources
- Pobranie zasobu
request:
GET http://localhost:8080/api/products HTTP/1.1
Host: localhost
Accept: application/xml
{blank line}
response:
200 OK
Content-Type: application/xml
<xml>
<product></product>
<product></product>
<product></product>
</xml>
- Utworzenie zasobu
POST http://localhost:8080/api/products HTTP/1.1
Host: localhost
Content-Type: application/json
Accept: application/xml
{"name":"komputer","unitprice":150 }
{blank line}
response:
201 Created
Content-Type: application/xml
- Podmiana zasobu
PUT http://localhost:8080/api/products/10
Host: localhost
Content-Type: application/json
Accept: application/xml
{"name":"komputer","unitprice":250 }
- Aktualizacja zasobu
PATCH http://localhost:8080/api/products/10
Host: localhost
Content-Type: application/json
Accept: application/xml
{"unitprice":250 }
- Usunięcie zasobu
DELETE http://localhost:8080/api/products/10
- Przykłowe trasy
GET http://localhost:8080/api/products
GET http://localhost:8080/api/products/10
GET http://localhost:8080/api/products?from=100&to=200
GET http://localhost:8080/api/products/10/customers
[ServiceContract(Namespace = "http://altkom.pl")]
public interface IProductService
{
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, UriTemplate = "api/products")]
IEnumerable<Product> Get();
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, UriTemplate = "api/products?from={from}&to={to}")]
IEnumerable<Product> GetByPrice(decimal from, decimal to);
[OperationContract]
[WebGet(BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json, UriTemplate = "api/products/{id}")]
Product GetById(string id);
[OperationContract]
[WebInvoke(UriTemplate = "api/products", Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
void Add(Product product);
[OperationContract]
[WebInvoke(UriTemplate = "api/products", Method = "PUT", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
void Update(Product product);
[OperationContract]
[WebInvoke(UriTemplate = "api/products/{id}", Method = "DELETE", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
void Remove(string id);
}