WCF(Windows Communication Foundation)-Transaction Support

Thank you everyone for your support till now, as discussed in last post we will cover transaction support in WCF. Let’s see how we can implement the same, we will make sure that we will be wsHttpBinding as it supports Transaction.

INTRODUCTION:

We will create a library service, where we will see the actual transaction feature working. We will use entity framework as our data access to interact with DB. You may think that entity framework already supports transaction as well as ADO.NET, but in case we want multiple methods we want to to expose to the client, and give control to client on how to call methods and in what sequence.

Let’s create the service.

  1. Let’s add a empty Solution and name it as “WCFTransactionService”, under empty solution right click and add a new project, and name it as “WCFTransactionService”. service
  2. We will add few methods and configure them to use transaction.After adding this project we will get service1 already present in our project, let’s remove all classes and interfaces from project.
  3. Add a new interface called “ILibraryService”, and add following methods
    1. AddBooks
    2. AddRemoveQuantity
    3. AddStudent
    4. GetBooks
    5. GetStudent
    6. ProvideBookToStudent
      using Library.Model;
      using System.Collections.Generic;
      using System.ServiceModel;
      using System.Threading.Tasks;
      
      namespace WCFTransactionService
      {
       [ServiceContract]
       public interface ILibraryService
       {
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.NotAllowed)]
       Task<List<Book>> GetBooks();
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.NotAllowed)]
       Task<List<Student>> GetStudent();
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<int> AddBooks(Book book);
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<int> AddStudent(Student student);
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<int> ProvideBookToStudent(StudentBookDetails details);
       [OperationContract]
       [TransactionFlow(TransactionFlowOption.Mandatory)]
       Task<bool> AddRemoveQuantity(Book book);
       }
      }
      
  4. Now let’s add our models,add a new class project and name it as Library.Model, we will use same class as datacontract and same class as our tables for Entity Framework. Never do that in real time project, have 2 different classes. to avoid conflicts as we have requirement of some properties which are not actually required in table, and running migration for such changes is overhead.We will have 3 models.
    1. Book
    2. Student
    3. StudentBookDetails
      using System.Runtime.Serialization;
      
      namespace Library.Model
      {
          [DataContract]
          public class StudentBookDetails
          {
              [DataMember]
              public int Id { get; set; }
              [DataMember]
              public int StudentID { get; set; }
              [DataMember]
              public int BookId { get; set; }
              [DataMember]
              public virtual Book Book { get; set; }
              [DataMember]
              public virtual Student Student { get; set; }
          }
      }
      
      using System;
      using System.Collections.Generic;
      using System.Runtime.Serialization;
      
      namespace Library.Model
      {
          [DataContract]
          public class Student
          {
              [DataMember]
              public int StudentID { get; set; }
              [DataMember]
              public string FristName { get; set; }
              [DataMember]
              public string LastName { get; set; }
              [DataMember]
              public DateTime DateOfBirth { get; set; }
              [DataMember]
              public DateTime CreatedDate { get; set; }
              [DataMember]
              public DateTime? ModeifiedDate { get; set; }
              [DataMember]
              public virtual ICollection&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;StudentBookDetails&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; StudentBookDetails { get; set; }
          }
      }
      
      
      using System;
      using System.Collections.Generic;
      using System.Runtime.Serialization;
      
      namespace Library.Model
      {
          [DataContract]
          public class Book
          {
              [DataMember]
              public int BookId { get; set; }
              [DataMember]
              public string Name { get; set; }
              [DataMember]
              public string Author { get; set; }
              [DataMember]
              public int Quantity { get; set; }
              [DataMember]
              public DateTime PublishDate { get; set; }
              [DataMember]
              public string Section { get; set; }
              [DataMember]
              public DateTime CreatedDate { get; set; }
              [DataMember]
              public DateTime? ModeifiedDate { get; set; }
              [DataMember]
              public virtual ICollection&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;StudentBookDetails&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; StudentBookDetails { get; set; }
          }
      }
      
  5. Next Add a new class project and name it as Library.Repository, install NuGet package for entity framework, once installed go ahead a add an interface ILibraryRepository. also add reference of Library.Model in this project.
    using Library.Model;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace Library.Repository
    {
     public interface ILibraryRepository
     {
     Task<List<Book>> GetBooks();
     Task<List<Student>> GetStudent();
     Task<int> AddBooks(Book book);
     Task<int> AddStudent(Student student);
     Task<int> ProvideBookToStudent(StudentBookDetails details);
     Task<bool> AddRemoveQuantity(Book book);
     }
    }
    
  6. Next let’s add a new class and name it as LibraryContext
    using Library.Model;
    using System.Data.Entity;
    using System.Data.Entity.ModelConfiguration.Conventions;
    
    namespace Library.Repository
    {
     public class LibraryContext : DbContext
     {
     public LibraryContext() : base("DefaultConnection") { }
     public DbSet<Student> Student { get; set; }
     public DbSet<Book> Book { get; set; }
     public DbSet<StudentBookDetails> StudentBookDetails { get; set; }
     protected override void OnModelCreating(DbModelBuilder modelBuilder)
     {
     base.OnModelCreating(modelBuilder);
    
     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
     }
     }
    }
    
  7. Now add a new class LibraryRepository, and implement the interface.
    using Library.Model;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Threading.Tasks;
    
    namespace Library.Repository
    {
     public class LibraryRepository : ILibraryRepository
     {
     private LibraryContext _context = new LibraryContext();
     public LibraryRepository(LibraryContext context)
     {
     this._context = context;
     }
    
     public async Task<int> AddBooks(Book book)
     {
     book.CreatedDate = DateTime.Now;
     _context.Book.Add(book);
     int x = await _context.SaveChangesAsync();
     return x;
     }
    
     public async Task<bool> AddRemoveQuantity(Book book)
     {
     Book bupdate = await _context.Book.FindAsync(book.BookId);
     bupdate.Quantity = bupdate.Quantity - 1;
     int x = await _context.SaveChangesAsync();
     return x == 0 ? false : true;
     }
    
     public async Task<int> AddStudent(Student student)
     {
     student.CreatedDate = DateTime.Now;
     _context.Student.Add(student);
     int x = await _context.SaveChangesAsync();
     return x;
     }
    
     public async Task<List<Book>> GetBooks()
     {
     return await _context.Book.ToListAsync();
     }
    
     public async Task<List<Student>> GetStudent()
     {
     return await _context.Student.ToListAsync();
     }
    
     public async Task<int> ProvideBookToStudent(StudentBookDetails details)
     {
     _context.StudentBookDetails.Add(details);
     int x = await _context.SaveChangesAsync();
     return x;
     }
     }
    }
    
  8. Repository is done, now compile this project and check if we are getting any error.
  9. Now let’s get back to our WCFTransactionService service, open app.config to set our service to use the transaction feature, to use that we need use “wsHttpBinding”, so go to binding and change it.and in binding add transactionFlow=”true” .
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
     <configSections>
     <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
     <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
     </configSections>
     <appSettings>
     <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
     </appSettings>
     <system.web>
     <compilation debug="true" />
     </system.web>
     <!-- When deploying the service library project, the content of the config file must be added to the host's 
     app.config file. System.Configuration does not support config files for libraries. -->
     <system.serviceModel>
     <services>
     <service name="WCFTransactionService.LibraryService">
     <endpoint address="" binding="wsHttpBinding" contract="WCFTransactionService.ILibraryService">
     <identity>
     <dns value="localhost" />
     </identity>
     </endpoint>
     <host>
     <baseAddresses>
     <add baseAddress="http://localhost:8733/Design_Time_Addresses/WCFTransactionService/LibraryService/" />
     </baseAddresses>
     </host>
     </service>
     </services>
     <behaviors>
     <serviceBehaviors>
     <behavior>
     <!-- To avoid disclosing metadata information, 
     set the values below to false before deployment -->
     <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
     <!-- To receive exception details in faults for debugging purposes, 
     set the value below to true. Set to false before deployment 
     to avoid disclosing exception information -->
     <serviceDebug includeExceptionDetailInFaults="true" />
     </behavior>
     </serviceBehaviors>
     </behaviors>
     <bindings>
     <wsHttpBinding>
     <binding transactionFlow="true" />
     </wsHttpBinding>
     </bindings>
     </system.serviceModel>
     <entityFramework>
     <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
     <providers>
     <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
     </providers>
     </entityFramework>
    </configuration>
  10. Now add a class as we already have interface we need to implement the same, name the class as “LibraryService”.
  11. To configure our service we need to add some attributes, if you noticed the interface we have an attribute  called TransactionFlow, this attribute is required to configure which method will take part in transaction and which will not.
  12. Now lets come to our class which will implement the interface .
    using Library.Model;
    using Library.Repository;
    using System.Collections.Generic;
    using System.ServiceModel;
    using System.Threading.Tasks;
    using System.Transactions;
    
    namespace WCFTransactionService
    {
     [ServiceBehavior(TransactionIsolationLevel = IsolationLevel.Serializable, TransactionTimeout = " 00:00:30")]
     public class LibraryService : ILibraryService
     {
    
     private ILibraryRepository _repo;
     private LibraryContext _context = new LibraryContext();
     public LibraryService()
     {
     _repo = new LibraryRepository(_context);
     }
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<int> AddBooks(Book book)
     {
     return await _repo.AddBooks(book);
     }
    
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<bool> AddRemoveQuantity(Book book)
     {
     return await _repo.AddRemoveQuantity(book);
     }
    
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<int> AddStudent(Student student)
     {
     return await _repo.AddStudent(student);
     }
    
     public async Task<List<Book>> GetBooks()
     {
     return await _repo.GetBooks();
     }
    
     public async Task<List<Student>> GetStudent()
     {
     return await _repo.GetStudent();
     }
    
     [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
     public async Task<int> ProvideBookToStudent(StudentBookDetails details)
     {
     return await _repo.ProvideBookToStudent(details);
     }
     }
    }
    
  13. If you notice there is ServiceBehavior attribute where we can set multiple attributes, we are using TransactionIsolationLevel this is similar to isolation level in SQL, this is set to IsolationLevel.Serializable which is default, it will give only committed rows, you can change it to ReadCommited, ReadUnCommitted and many more, choose which you think is required in your scenario.
  14. Next attribute us OperationBehavior, where we are using 2 properties TransactionScopeRequired and TransactionAutoComplete, we can use TransactionScopeRequired to set which methods will be used in TransactionScope and TransactionAutoComplete can be set to true or false.
  15. Now add a console project and name it as LibraryClient, add a service reference, click on discover to get the service. 
  16. Go to program.cs and add below code, if you notice on PublishDate = Convert.ToDateTime(“3423”) this is intentional error generated to show the if transaction works or not.
    1. using LibraryClient.LibraryServiceRef;
      using System;
      using System.ServiceModel;
      using System.Transactions;
      
      namespace LibraryClient
      {
       class Program
       {
       static void Main(string[] args)
       {
       try
       {
       using (var transScope = new TransactionScope())
       {
       using (LibraryServiceClient client = new LibraryServiceClient())
       {
       Student s = new Student()
       { FristName = "Santosh", LastName = "Yadav", DateOfBirth = Convert.ToDateTime("13-Nov-1986") };
       int student=client.AddStudent(s);
      
       Console.WriteLine("Number of student:" + student);
      
       Book b = new Book() { PublishDate = Convert.ToDateTime("3423"), Author = "Santosh", Name = "WCF", Quantity = 10, Section = "Test" };
       client.AddBooks(b);
      
       transScope.Complete();
       }
       }
       }
       catch (FaultException ex)
       {
       Console.Write(ex);
       }
       }
       }
      }
      
  17. if you see the output we will get the “Number of student:1”, post which error will be generated, now run it again we will get the count as 1 again, which shows that transaction is working fine.

CONCLUSION:

The transaction feature is one of the powerful feature when you want to give more controls to client on how and in which sequence to call the methods. One of the real-time example is ecommerce, where you can call PlaceOrder after Payment or Payment after PlaceOrder in transaction.

You can reach out to me on santosh.yadav198613@gmail.com for any queries.

For code refer this link.

In next post we will continue with angular.

Leave a Reply

Your email address will not be published. Required fields are marked *