WCF Complete Material
WCF Complete Material
WCF Complete Material
Chapter1: Introduction Evolution of WCF Evolution of Service Oriented Architecture (SOA) Four Tenets of SOA What is WCF Where does WCF Services fit in? Evolution of WCF: 1. Monolithic Applications a. Fox Pro and MS Access Applications which have both database and code at one place b. SQL Server / Oracle database were used on network but the application was still a monolithic one. c. Problem: Takes lots of time and No Code Reusability 2. Object Orientation 1980s a. Polymorphism b. Encapsulation c. Sub-classing d. Problem: It by itself didnt facilitate the dynamic evolution of software at runtime. Once an application was built, it was static. There wasnt an easy way to infuse new code into an application. 3. COM Components a. Write code once and reuse in multiple applications. b. Location Transparent c. Tight Coupling d. Runtime Metadata (self describing systems) e. Problem: Worked well on single machine(using method invocation on an object reference), we hit scaling problems when we tried to stretch it out and apply it as the substrate for distributed software integration (across machines) 4. DCOM a. Network Version of COM Sharing COM objects over network b. Biggest failure of Microsoft as not at all reliable and scalable. 5. COM+ a. DCOM + MTS (Transaction Services) b. Object Pooling and Just In Time Activation. 6. .NET Remoting a. Option for .Net developers for Distributed application development. b. Best option only when both client and server are on same network. 7. Web Services a. Provides Objects functionality over HTTP b. Data is exchanged over the network in XML format. c. SOAP is the protocol used for communication. 8. WCF is the next generation of Web Service with following enhancements a. Supports sending messages not only using HTTP but also using TCP and Named-Pipe and MSMQ b. Support for sending messages using formats other than SOAP which includes Representational State Transfer (REST) and Plain Old XML (POX) c. Service can be hosting in different types of application unlike web services which needs Web server only. d. Built in support for Transaction and reliable sessions Service Oriented Architecture: A service is a program that perform a task and that you can interact with through well defined messages. o XML is the format in which Web Service and its client communicate with each other. Service Oriented applications consists of loosely coupled services that communicates through Messages and Contracts o Client does not instantiate the service o Messages are Requests and Responses exchanged between client and server. o Contracts specify what requests you can make on the service and how it will respond. Four Tenets of Service Orientation 1. Boundaries are Explicit
Walkthrough 1: IIS / ASP.NET as Hosting Environment In Server 1. File New Website WCF Service, Location = File System, Path=d:\WCF\HostInIIS\ 2. Delete existing Service.svc, IService.cs, Serivce.cs 3. Add New Item WCF Service MathService 4. Add to the Project Complex class as below [DataContract] public class Complex { private int _Real, _Imag; [DataMember] public int Real { get { return _Real; } set { _Real = value; } } [DataMember] public int Imag { get { return _Imag; } set { _Imag = value; } } 5. } Modify the IMathService and MathService as below [ServiceContract] public interface IMathService { [OperationContract] int Add(int a,int b); [OperationContract] Complex AddComplex(Complex c1,Complex c2); [OperationContract] int GetCounter(); } public class MathService : IMathService { public int Add(int a, int b) { Count++;
In Client: 1. Start New Instance of VS.NET 2. Add Service Reference Use the URL from above (Step 6) 3. Add Button and handle its Click event with the following code. localhost.MathServiceClient ds = new localhost. MathServiceClient(); MessageBox.Show(ds.Add(100,200).ToString()); Demo of InstanceContextMode Instancing behavior In Server 1. In IEmpService interface add a method called as GetCount() 2. In EmpService declare a variable Count and in every method increment its value. 3. In EmpService Implement GetCount to return Count 4. For the EmpService add the attribute ServiceBehaviour with following a. InstanceContextMode = PerSession For every client a new Service instance is created and same is used for all the methods called by that client. b. InstanceContextMode = PerCall New Instance of Service is created for every method called by client c. InstanceContextMode = Single Only one Service instance is created and the same is used by all the clients for their methods called. 5. For every InstanceContextMode, Update Service Reference in the client application and view return value of GetCount() method Walkthrough 2: Console Application as Hosting Environment Server Application 1. Create a WCF Library Project (WCFLibrary) File New Project Visual C# WCF WCF Service Library. Project Name = WCFLibrary and SolutionName = WCFDemo 2. Delete from that the default Service1 and also delete the App.Config file of the project. 3. Add to the project a new WCF Service (MathService) which generates the following interface IDemoService and the DemoService. using System; using System.Runtime.Serialization; using System.ServiceModel; namespace WCFLibrary { [ServiceContract()] public interface IMathService { [OperationContract]
Configuration File 1. Everything will be under <system.serviceModel> 2. <services> contains service type definitions 3. <endpoints> contains ABC for a service 4. <binding> contains one or more binding sections to configure individual bindings 5. <behavior> provides configuration for service. Service Endpoints: 1. A Service endpoint is used by Service Host to receive the request from the client and forward it to the service. 2. A service can have one or more endpoints. 3. Every endpoint will have unique combination of ABC 4. Can be defined in Code or in Configuration File a. If configuration file is used it gives facility to change the endpoints when needed. We can either manually write the configuration file or use Service Configuration Editor. b. If defined in code: No one can change the endpoints once the code is build and deployed. Endpoint in config file: <endpoint address="" binding="wsHttpBinding" contract="WCFLibrary.IMathService"> What is ABC? Address Where do you send the message? Binding How do you send the message? Contract What should the message look like? Metadata Exchange Endpoint: <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> this is used by clients to add service reference i.e to generate the proxy class. Base Address: <baseAddresses> <add baseAddress="http://localhost:8731/WCFLibrary/MathService" /> <add baseAddress="net.pipe://localhost/WCFService"/> <add baseAddress="net.tcp://localhost:8000/WCFService"/> </baseAddresses> Creating Endpoint Programmatically: Following can be written in Main: ServiceHost sh = new ServiceHost(typeof(MathService)); System.ServiceModel.Description.ServiceEndpoint sep; sep = sh.AddServiceEndpoint(typeof(IService1), new WSHttpBinding(), "http://localhost:8731/WCFLibrary/MathService/"); sh.Open(); ... sh.Close();
1. 2. 3. 4. 5.
Clients and services communicate by passing messages and the mechanism they use to communicate is a channel stack. Channel Stack is made up of multiple channels as shown above. Protocol channels are responsible for preparing the message to be sent. These are optional. The order is important Transport channels are responsible for sending/receiving the message as per the protocol i.e. Http, TCP etc. Message Encoder converts the Message from XML to bytes for transport and Transport actually sends the message over the socket.
Introduction to Binding: The Binding is an attribute of an endpoint and it lets you configure the channels like transport protocol, encoding and security requirements as shown below
One of the design goals of WCF is to unify the way distributed systems are developed prior to release of .Net Framework 3.0. WCF offers a single development framework for all scenarios where distributed solutions were implemented using different technologies such as ASMX web services, .Net Remoting, COM+, etc. WCF achieves this by configuring binding attributes of an endpoint. WCF lets you choose HTTP or TCP transport protocol, encoding, etc. just by tweaking the value of binding attribute for an endpoint.
Types of Bindings (All are class names which are inherited from Binding class). BasicHttpBinding WSHttpBinding WSDualHttpBinding
Web Service Bindings 1. BasicHttpBinding for SOAP 1.1 Compatibility (for ASMX based client WS-Basic Profile 1.1) 2. WSHttpBinding for SOAP 1.2 with WS* specification to support enterprise requirement of security, reliability, ordered delivery and transaction management. This is also Interoperable. 3. WSDualHttpBinding for callbacks over HTTP with WS* support (Not Interoperable) 4. WSFederationHttpBinding for federation security and single sign on scenarios Cross Process / Machine Bindings 1. NetNamePipeBinding for in-process on same machine calls 2. NetTcpBinding is used for same machine or cross-machine calls. It is not interoperable but is highly optimized for .NET 3.0 and above clients. Its best replacement for .NET Remoting and COM+ model. 3. NetPeerTcpBinding for same machine or cross machine peer to peer messaging. Messaging Bindings 1. NetMsmqBinding for reliable, transacted and persistent messaging over MSMQ 2. MsmqIntegrationBinding for MSMQ interoperability with earlier MSMQ clients Binding Comparison Binding Class Name
Transport Message Message Encoding Version HTTP HTTP HTTP Text/XML MTOM Text/XML MTOM Text/XML MTOM SOAP 1.1 SOAP 1.2 WS-A 1.0 SOAP 1.2 WS-A 1.0
Tx Flow*
X WS-AT WS-AT
WSFederationHttpBinding HTTP NetTcpBinding NetPeerTcpBinding NetNamedPipeBinding NetMsmqBinding TCP P2P Named Pipes MSMQ
Text/XML SOAP 1.2 MTOM WS-A 1.0 Binary SOAP 1.2 Binary Binary Binary SOAP 1.2 SOAP 1.2 SOAP 1.2
MsmqIntegrationBinding MSMQ X** X Transport X X = Not Supported MTOM = Message Transmission Optimization Mechanism WS-A = WS-Addressing WS-AT = WS-AtomicTransaction OleTx = OleTransactions * Transaction flow is always disabled by default, but when you enable it, these are the default tx protocols ** This binding doesnt use a WCF message encoding instead it lets you choose a pre-WCF serialization format Thumb rules in choosing endpoint' binding If you require your service to be consumed by clients compatible with SOAP 1.1, use basicHttpBinding for interoperability If you require your service to be consumed within the corporate network, use netTCPBinding for performance If you require your service to be consumed over the internet and the client is a WCF compatible, use wsHttpBinding to reap full benefits of WS* specifications
Binding class Properties static void PrintBindingProperties(Binding binding) { Console.WriteLine("Name: " + binding.Name); Console.WriteLine("MessageVersion: " + binding.MessageVersion.ToString()); Console.WriteLine("Namespace: " + binding.Namespace); Console.WriteLine("OpenTimeout: " + binding.OpenTimeout.ToString()); Console.WriteLine("CloseTimeout: " + binding.CloseTimeout.ToString()); Console.WriteLine("SendTimeout: " + binding.SendTimeout.ToString()); Console.WriteLine("ReceiveTimeout: " + binding.ReceiveTimeout.ToString()); Console.WriteLine("Scheme: " + binding.Scheme); Console.WriteLine(); } static void Main(string[] args) { ServiceHost sh = new ServiceHost(typeof(Service1)); //Create WSHttpBinding System.ServiceModel.Channels.Binding binding = new WSHttpBinding(); //To Create and Endpoint System.ServiceModel.Description.ServiceEndpoint sep; sep = sh.AddServiceEndpoint(typeof(IService1), binding, ""); PrintBindingProperties(binding); //To Get Reference to binding mentioned on .config file. binding = sh.Description.Endpoints[0].Binding; PrintBindingProperties(binding); sh.Open(); Console.WriteLine("Service has started"); Console.WriteLine("Press any key to stop the server"); System.Console.ReadKey(true); Console.WriteLine("Service has stopped"); sh.Close(); }
10
DataContract changes Add new non-required member Add new required member Remove non-required members
Impact on existing clients Client unaffected Missing values are initialized to defaults An exception is thrown for missing values Data lost the services Unable to return the full data set back to the client No Exceptions An exception is thrown when client receives response from the service with missing values If types are compatible, no exception but may receive unexpected result
Known Types Normally, when passing parameters and return values between a client and a service, both endpoints share all of the data contracts of the data to be transmitted. However, this is not the case in the following circumstances: The sent data contract is derived from the expected data contract. In that case, the transmitted data does not have the same data contract as expected by the receiving endpoint. The declared type for the information to be transmitted is an interface. The declared type for the information to be transmitted is Object. Some types, which include .NET Framework types, have members that are in one of the preceding three categories. For example, Hashtable uses Object to store the actual objects in the hash table. When serializing these types, the receiving side cannot determine in advance the data contract for these members Known Types can be declared at 3 places Step1: Add the following classes to the Library Project. [DataContract] public class TrainingEmployee : Employee { [DataMember] public string Subject { get; set; } } [DataContract] public class DevelopmentEmployee : Employee {
11
12
Overview WCF does not communicate CLR Exceptions. WCF Exceptions are passed as SOAP Messages. The underlying principle of service-oriented error handling consists of SOAP fault messages, which convey the failure semantics and additional information associated with the failure (such as the reason). For security purpose, an exception message doesnt contain any information about the actual exception. During development we can configure the service to return more detailed exception information. <serviceBehaviors> <behavior name="ServiceBehavior"> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> or [ServiceBehavior(IncludeExceptionDetailInFaults=true)] public class DemoService : IDemoService {...} Managed Exceptions Vs SOAP Fault If a managed exception is thrown from a service operation, client is immediately reported with a FaultException and the communication channel of the client is faulted and the proxy object cannot be used for calling any more methods. If it is used then CommunicationObjectFaultedException is thrown in the client application. If a managed exception is thrown from a one way service operation, client is not immediately reported about the exception in the service. If the client application after this calls any other method then MessageSecurityException will be reported to client and the communication channel will be in faulted state. If a FaultException is thrown from the service operation on server then the client application can handle this FaultException and the communication channel of client and server will not be in faulted state. Producing Faults Use FaultException Class to customize the Error Message the service returns. o Clients cant distinguish types of faults o Use FaultCode to specify a SOAP fault code o Use FaultReason to specify the description of the fault. It supports locale based translation of message
SOAP fault with FaultCode and FaultReason throw new FaultException("Reason for Exception", new FaultCode("SomeError")) Culture specific SOAP fault List<FaultReasonText> lstReasons = new List<FaultReasonText>(); lstReasons.Add(new FaultReasonText("This is in english",new System.Globalization.CultureInfo("en-US"))); lstReasons.Add(new FaultReasonText("This is in french",new System.Globalization.CultureInfo("fr-FR"))); lstReasons.Add(new FaultReasonText("This is in hindi",new System.Globalization.CultureInfo("hi-IN"))); FaultReason reason = new FaultReason(lstReasons); throw new FaultException(reason, new FaultCode("SomeError")); Strongly typed SOAP fault Most services which require error handling also require additional information to be passed with the error notification. This information can be transferred to the client as a standard WCF data contract, in the disguise of a fault.
13
14
There are three types of Message Exchange Patters 1. Request Reply: a. Client sends a message to the service and waits for the service to send the message back 2. One way: a. Client sends a message to the service and doesnt wait for the reply b. Used for logging or storing data c. Set IsOneWay property of methods OperationContract to true 3. Duplex Pattern a. Client and Service can initiate communication by sending a message to each other. b. Use when service needs to notify the client that it finished processing an operation or when service can publish events to which client can subscribe. Example of One way: Client application should Log to file the start time and stop time with the Service Proxy.LogStartTime (WindowsIdentity.GetCurrent ().Name); Example of Duplex Communication General Steps to Setup Duplex Communication 1. Add a one-way operation to the service contract 2. Add a call back contract to the service 3. Associate the call back contact with the service contract. 4. Use binding that supports duplex binding (WsHttpBinding should be replaced with WSDualHttpBinding). 5. Create a class in the client that implements the callback contract. 6. Create a proxy class and pass it the context information for the service. Step 1: Add a Service Contract to the class library project. [ServiceContract ()] public interface IDemoService { [OperationContract(IsOneWay = true)] void SendEmailAndNotifyCompletion(string name,string toEmailId,string body); } Step 2: Add a new interface ICallbackService public interface ICallbackService { [OperationContract(IsOneWay = true)] void OperationCompleted(string message); } Step 3: Associate the callback contract with service contract [ServiceContract (CallbackContract = typeof(ICallbackService))] Step 4: Implement NotifyClient method public void SendEmailAndNotifyCompletion(string name, string toEmailId, string body) { Console.WriteLine("Now sending email"); //simulate sending of email by sleeping for 5 secs. System.Threading.Thread.Sleep(5000); //Callback client method. ICallbackService callback = OperationContext.Current.GetCallbackChannel<ICallbackService>(); callback.OperationCompleted(string.Format("Hello {0}, Your email to {1} is sent", name, toEmailId)); ; Console.WriteLine("Email Sent"); } Step 5: Change the binding on server to WSDualHttpBinding In Client Application:
15
16
Transaction: Logical units of work comprising of multiple activities that should all succeed or all fail Transaction needs to meet ACID principles: Atomic: Transaction executes at once and all of the work occurs or none does. Consistency: Needs to preserve data consistency i.e. commit or rollback Isolation: Needs to be independent of other transactions and one transaction should not be allowed to see the uncommitted state of another transaction. Durable: transactions are recoverable i.e. needs to log its state so that it should know what do undo if the transaction fails. .NET framework provides System.Transactions Namespace and TransactionScope class. To Enable Transaction in WCF Service 1. Create New Binding. Please note the Bindings that supports transaction: NetTcpBinding, NetNamedPipeBinding, WSHttpBinding, WSDualHttpBinding, WSFederationBinding <bindings> <wsHttpBinding> <binding transactionFlow="true" name="WSHttpTransactionBindingConfig"/> </wsHttpBinding> </bindings> 2. Assign the binding to the endpoint. <endpoint . . . bindingConfiguration="WSHttpTransactionBindingConfig"> 3. Add the following attribute to the participating methods in the Service Contract interface [TransactionFlow(TransactionFlowOption.Mandatory)] NotAllowed: A transaction should not be flowed. This is the default value. Allowed: Transaction may be flowed Mandatory: Transaction must be flowed 4. For the method implemented by the Service class add the following [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)] Default value of TransactionScoreRequired is false. Default value of TransactionAutoComplete is true TransactionScopeRequired Binding permits Caller Result transaction flows flow transaction False False No Method executes without a transaction. True False No Method creates and executes within a new transaction. True or False False Yes A SOAP fault is returned for the transaction header. False True Yes Method executes without a transaction. True True Yes Method executes under the flowed transaction. TransactionIsolationLevel: Optionally you may configure transaction isolation in ServiceBehaviorAttribute [ServiceBehavior(TransactionIsolationLevel=System.Transactions.IsolationLevel.Serializable)] Different transaction isolation levels in WCF Read Uncommitted: So, you can read an uncommitted transaction that might get rolled back later. This isolation level is also called dirty read. This is the lowest isolation level. Read Committed: It ensures that physically corrupt data will not be read and will never read data that another application has changed and not yet committed, but it does not ensure that the data will not be changed before the end of the transaction. Repeatable Read: It means that locks will be placed on all data that is used in a query, and other transactions cannot update the data.
17
In some bindings transaction protocol can be set. <binding transactionProtocol="OleTransaction"/> a. OleTransaction: Optimal for .NET client b. Web Service Atomic Transaction: Use when clients are not .NET Note: WCF runtime chooses protocol by default. Starting a Transaction in client application: 1. Add reference to System.Transactions using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required)) { //Call WCF Service methods. ts.Complete(); } Required: A transaction is required by the scope. It uses an ambient transaction if one already exists. Otherwise, it creates a new transaction before entering the scope. This is the default value. RequiresNew: A new transaction is always created for the scope. Suppress: The ambient transaction context is suppressed when creating the scope. All operations within the scope are done without an ambient transaction context. Transactions and Sessions: By default, when you add transactions, client uses a different service instance for each call so no state is maintained. To configure transactional service to maintain state 1. [ServiceContract(SessionMode=SessionMode.Required)] 2. [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession,TransactionAutoCompleteOnSessionClose= true)] 3. [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=false)]
18
Introduction Microsoft Message Queuing, or MSMQ, is technology for asynchronous messaging. MSMQ can be used whenever there's need for two or more applications to send messages to each other without having to immediately know results. MSMQ can communicate between remote machines, even over internet. It's free and comes with Windows, but is not installed by default. The MSMQ server is similar to an e-mail server, but it's designed to let applications "talk" to each other, whereas a mail server is designed to let humans talk to each other
Advantages of using MSMQ Increase application robustness, as clients can send messages even when services are not running. Increase application scalability, because multiple service instances can be used to process messages from a single queue. Improve client responsiveness, which eliminates the need for clients to wait for a response from the service. Reduce dependencies between client and services, because all communication is indirect via queues. Ensure the durability of messages, because they can survive service and system failures
Transactional Queues When transactions are used to send and receive messages, there are actually two separate transactions. When the client sends messages within the scope of a transaction, the transaction is local to the client and the client queue manager. When the service receives messages within the scope of the transaction, the transaction is local to the service and the receiving queue manager. It is very important to remember that the client and the service are not participating in the same transaction; rather, they are using different transactions when performing their operations (such as send and receive) with the queue. Steps to Install and Verify MSMQ Server Go to Control Panel Program and Features Turn Windows Features on or off Check Microsoft Message Queue (MSMQ) Server OK Go to Control Panel Administrative Tools Computer Management Expand Services and Application Message Queuing
1. 2. 3. 4.
19
20
2.
1. 2.
3.
21
Demo how messages are encrypted. Go to Tools WCF Configuration Editor Open the config file in Server. Enable message logging (Diagnostics Click on Enable Message Logging) Select Messages Logging and Set LogMessagesAtServiceLevel = True, LogMessagesAtTransportLevel = True Using basicHttpBinding build and run Server and Client application. Log file will be created. o Open the Log file using Service Trace Viewer: Start Programs Microsoft Windows SDK Service Trace Viewer. Then in Messages Tab View Message. o Note that client built and client sent messages will be same and not encrypted. (Body of SOAP envelope is easily readable) 5. Now Delete the Log file. 6. Repeat 4 this time using WsHttpBinding o Open the Log file using Service Trace Viewer In Message Tab View Messages. o Note that client and service built are not encrypted but what messages client and service sent are encrypted. Note: For every message we will have two entries. 1. 2. 3. 4. Authentication Identify users and control who can access a service Client can authenticate itself by passing credentials to the service either using Windows Credentials, Digital certificates or User name and password. <security mode="Transport"> <transport clientCredentialType="Windows"/> </security Transport Security Credentials Windows Use Windows authentication to authenticate. Basic Use user name and password against Active Directory (HTTP only) Certificate Use an X. 509 certificate NTLM Use a challenge-response scheme against Windows accounts (HTTP only) None No authentication. Message Security Credentials Windows Use Windows authentication to authenticate Username Use user name and password against either Windows accounts or ASP.NET membership provider Certificate Use an X. 509 certificate IssueToken Use Secure Token Service to issue tokens (eg. Windows CardSpace) None No authentication Default Security Settings BasicHttpBinding: No Security WsHttpBinding: Client credentials are Windows tokens. NetTcpBinding: Client credentials are Windows tokens. Demo: Windows Authentication In Server: Part 1: Print in Operation: System.Threading.Thread.CurrentPricipal.Identity.Name Print the following properties for basicHttpBinding, wsHttpBinding and netTcpBinding. 1. Securty.Mode, 2. Security.Message.AlgorithmSuite, 3. Security.Message.ClientCredentialType, 4. Security.Transport.ClientCredentialType ------BasicHttpBinding Properties Output-----Security.Mode: Transport Security.Message.AlgorithmSuite: Basic256 Security.Message.ClientCredentialType: UserName Security.Transport.ClientCredentialType: Windows ------WSHttpBinding Properties Output ------
22
23
24
5.
Authorization Control who can access a service and what operations of the service Role based Authorization Options: Use Windows Groups Use custom roles ASP.NET Role Provider 1. Authorization Based on Windows Groups. 1. Enable Windows Authentication on the binding. 2. Add the following attribute to the method [PrincipalPermission [SecurityAction.Demand, Role="BUILTIN\\BackupOperators")] 3. To control from inside the method: var user = new WindowsPrincipal ( (WindowsIdentity) System.Threading.Thread.CurrentPrincipal.Identity); if (! User.IsInRole (WindowsBuiltInRole.Administrator) Throw new SecurityException ("Access denied"); Authorization based on Generic Identity (Custom Roles) 1. In service class declare the following variable public static GenericPrincipal UserPrincipal = null; 2. In every method which is secured add the following in the beginning. if (UserPrincipal.IsInRole("Manager") != true) throw new SecurityException("Access denied"); 3. To the Library Project add the following class public class UserValidation : UserNamePasswordValidator { public override void Validate(string userName, string password) { //For Demo lets validate as: If username and password are same then its a valid user others invalid. if (userName != password) throw new SecurityTokenException("Unknown Username or Incorrect Password"); //based on username attach role to the user. GenericIdentity identity = new GenericIdentity(userName); if (userName.ToUpper().StartsWith("m")) Service1.UserPrincipal = new GenericPrincipal(identity, new string[] { "Manager" }); else Service1.UserPrincipal = new GenericPrincipal(identity, new string[] { "User" }); } }
2.
25
5.
6.
7.
26