Sunday, March 29, 2015

Interoperability : WCF Secured Service, .NET Client, SoapUI, Java Client

Hi Guys,

I had a requirement to make a secured WCF service to be hosted in HTTPS and the credentials must be passed with the soap header within the username token tags. So this post is about that.

1) WCF Service

 public class MyService : IMyService
    {
   
        public ReturnMsg SaveTransaction(Transaction objTransaction, Patient objPatient)
         {
             System.Data.OracleClient.OracleConnection con = new System.Data.OracleClient.OracleConnection();
             System.Data.OracleClient.OracleCommand cmd = new System.Data.OracleClient.OracleCommand();
             ReturnMsg objReturnMsg = new ReturnMsg() { QaidNo = -1 };
            

            //The Logics have been removed 
             try
             {
                 //Validate for Mandatory Fields
                 if (Validate(objTransaction, objPatient, objReturnMsg))
                 {
                     

                     objReturnMsg.QaidNo = 10000;

                     objReturnMsg.Status = Status.Success;
                 }
                 

             }
             catch (Exception ex)
             {
                 objReturnMsg.Status = Status.Failed;
                 objReturnMsg.Message = ex.Message;
             }
             finally
             {
                 if (con.State == ConnectionState.Open)
                     con.Close();

                 con.Dispose();
             }
             return objReturnMsg;



         }
}

2) WCF Configurations (Web.config)

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="defaultProfile">
          <dataContractSerializer maxItemsInObjectGraph="2147483646" />
          <serviceMetadata httpGetEnabled="true" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WcfService.UsernameValidator, WcfService" />
            <clientCertificate>
              <authentication certificateValidationMode="None" />
            </clientCertificate>
          </serviceCredentials>
        </behavior>
       
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="basic">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </basicHttpBinding>
     
    </bindings>
    <services>
      <service behaviorConfiguration="defaultProfile" name="WcfService.MyService">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basic" name="wsSecureService" contract="WcfService.IMyService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

  </system.serviceModel>

3) Custom Credential Validator 

public class UsernameValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            // validate arguments
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");
            if (string.IsNullOrEmpty(password))
                throw new ArgumentNullException("password");

            // check if the user is not test
            if (userName.Trim().ToLower() != WcfService.Properties.Settings.Default.WSSE_USER.Trim().ToLower()
                || password.Trim().ToLower() != WcfService.Properties.Settings.Default.WSSE_PWD.Trim().ToLower())
                throw new SecurityTokenException("Unknown username or password");
        }

    }

4) I created a Self signed certificate in IIS and hosted my Service in HTTPS 
 How to Create a Self Signed Certificate

.NET Client (Console Application)

1) Add Service Reference to MyService

2) Paste the following code in Program.cs

static void Main(string[] args)
        {

            

            //Creating an instance for the client   
            
            ServiceReference.MyServiceClient client = new ServiceReference.MyServiceClient();
            //Setting the Credentials
            client.ClientCredentials.UserName.UserName = "username";
            client.ClientCredentials.UserName.Password = "password";

            try
            {

                //Creating and Filling the Objects which will be passed to method
                ClientApplication.ServiceReference.Transaction objTransaction = new ClientApplication.ServiceReference.Transaction();
                ClientApplication.ServiceReference.Patient objPatient = new ClientApplication.ServiceReference.Patient();

               
                ClientApplication.ServiceReference.ReturnMsg objReturnMsg = client.SaveTransaction(objTransaction, null);

                //If Status is Success
                if (objReturnMsg != null && objReturnMsg.Status == ServiceReference.Status.Success)
                    Console.WriteLine(objReturnMsg.QaidNo);
                else
                    Console.WriteLine(objReturnMsg.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally { 
                 Console.ReadLine();
            }            

        }

3) Output



Soap UI 

1) Create a New Soap Project with Soap UI for the hosted WCF service (WSDL)

2) Double click on the created the Request and PasswordText for WSS-Password Type, specify the correct username and password in Request Properties

3) Output 


Java Client

1) Create a Java Application using Net Beans

2) Add a Web Service Client for the Java Application 

3) Add a SecurityHandler.java class for the project and paste the following code. Here you will specify the username and password.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package serviceclient;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.handler.MessageContext;
import javax.xml.soap.*;
import java.util.*;
import javax.xml.namespace.QName;

/**
 *
 * @author Sshahim
 */
public final class SecurityHandler implements SOAPHandler<SOAPMessageContext> {

    @Override public Set<QName> getHeaders()
    {
         QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", 
                "Security"); 
        HashSet<QName> headers = new HashSet<QName>(); 
        headers.add(securityHeader);         
        return headers; 
    }
    
    @Override public void close(MessageContext context)
    {
        
    }
    
    @Override public boolean handleFault(SOAPMessageContext context)
    {
        return true;
    }

    @Override  public boolean handleMessage(final SOAPMessageContext msgCtx) {

        // Indicator telling us which direction this message is going in
        final Boolean outInd = (Boolean) msgCtx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        // Handler must only add security headers to outbound messages
        if (outInd.booleanValue()) {
            try {
                // Get the SOAP Envelope
                final SOAPEnvelope envelope = msgCtx.getMessage().getSOAPPart().getEnvelope();

                // Header may or may not exist yet
                SOAPHeader header = envelope.getHeader();
                if (header == null)
                    header = envelope.addHeader();

                // Add WSS Usertoken Element Tree 
                final SOAPElement security = header.addChildElement("Security", "wsse",
                        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                final SOAPElement userToken = security.addChildElement("UsernameToken", "wsse");
                userToken.addChildElement("Username", "wsse").addTextNode("username");
                userToken.addChildElement("Password", "wsse").addTextNode("password");

            } catch (final Exception e) {
                
                return false;
            }
        }
        return true;
    }

    
    // Other required methods on interface need no guts
}

4) Open the main java file and paste the following code. This code will bind the created handler before calling the service.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package serviceclient;

import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.datacontract.schemas._2004._07.wcfservice.ReturnMsg;
import javax.xml.ws.BindingProvider;
import com.sun.xml.wss.XWSSConstants;
import javax.xml.ws.Binding;
import javax.xml.soap.SOAPException;
import java.util.*;
import javax.xml.ws.handler.Handler;
import org.datacontract.schemas._2004._07.wcfservice.Status;

/**
 *
 * @author Sshahim
 */
public class ServiceClient {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        
        org.datacontract.schemas._2004._07.wcfservice.Transaction objTransaction =
                new org.datacontract.schemas._2004._07.wcfservice.Transaction();
        
        org.datacontract.schemas._2004._07.wcfservice.Patient objPatient = 
                new org.datacontract.schemas._2004._07.wcfservice.Patient();
        
       ReturnMsg objReturn = saveTransaction(objTransaction, objPatient);
       if (objReturn.getStatus() == Status.SUCCESS)
           System.out.println(objReturn.getQaidNo()); 
       else
           System.out.println(objReturn.getMessage().getValue());  
        
                 
        
    }

    

    private static ReturnMsg saveTransaction(org.datacontract.schemas._2004._07.wcfservice.Transaction objTransaction, org.datacontract.schemas._2004._07.wcfservice.Patient objPatient) {
          
       

        org.tempuri.MyService service = new org.tempuri.MyService();
        org.tempuri.IMyService port = service.getWsSecureService();
        


        final Binding binding = ((BindingProvider)port).getBinding();
        List<Handler> handlerList = binding.getHandlerChain();
        if (handlerList == null)
            handlerList = new ArrayList<Handler>();

        handlerList.add(new SecurityHandler());
        binding.setHandlerChain(handlerList); // <- important!
     
        
        return port.saveTransaction(objTransaction, objPatient);
    }
    
    
    
}

5) Output







Wednesday, March 4, 2015

Employee Time Log : Reporting using SSRS and Generic Handler

Hi Guys,

I added new page to the Employee Time Log to generate a simple report which I made using SQL Server Reporting Services (SSRS) and a ASP.NET Generic Handler (.ashx) File. Hope you like it.































Solution

1) Add a Generic Handler File, Report File and Report.html file as shown in the image.


2) Add the following html in Report.html

<form name="ReportForm" ng-submit="ReportForm.$valid && GenerateReport(CurrentLog)" novalidate>




    <div>
        <div class="panel panel-primary">

            <div class="panel-body">







                <div class="form-group">
                    <label>Start Time</label>
                    <span> {{ CurrentLog.StartTime = (StartTime | date:'yyyy-MM-dd HH:mm:ss')}}</span>
                    <div class="dropdown">
                        <a class="dropdown-toggle" id="dropdown1" role="button" data-toggle="dropdown" data-target="#" href="#">Click here to Set Start Time</a>
                        <ul class="dropdown-menu" role="menu">
                            <datetimepicker data-ng-model="StartTime" data-datetimepicker-config="{ dropdownSelector: '#dropdown1' }" />
                        </ul>
                    </div>
                </div>

                <div class="form-group">
                    <label>End Time</label>
                    <span> {{ CurrentLog.EndTime = (EndTime | date:'yyyy-MM-dd HH:mm:ss')}}</span>
                    <div class="dropdown">
                        <a class="dropdown-toggle" id="dropdown2" role="button" data-toggle="dropdown" data-target="#" href="#">Click here to Set End Time</a>
                        <ul class="dropdown-menu" role="menu">
                            <datetimepicker data-ng-model="EndTime" data-datetimepicker-config="{ dropdownSelector: '#dropdown2' }" />
                        </ul>
                    </div>
                </div>

                




                <div data-ng-hide="message == ''" class="alert " ng-class="alertClass">
                    {{message}}
                </div>


                <div class="pull-right">


                    <button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-print"></span> Generate</button>


                    <button type="reset" class="btn btn-primary" ng-click="Reset()"><span class="glyphicon glyphicon-remove"></span> Reset</button>
                </div>

            </div>

        </div>





</form>

3) Add the following code in ReportHandler.ashx


using Microsoft.Reporting.WebForms;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;

namespace EmployeeTimeLog.Handlers
{
    /// <summary>
    /// Summary description for ReportHandler
    /// </summary>
    public class ReportHandler : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {


            ReportViewer viewer = new ReportViewer();

            viewer.LocalReport.ReportPath = context.Server.MapPath("~/Reports/SummaryReport.rdlc");

            Warning[] warnings;
            string[] streamids;
            string mimeType;
            string encoding;
            string filenameExtension;

            viewer.LocalReport.DataSources.Add(new ReportDataSource("TimeLog", GetTimeLogsReport(context.Request.QueryString["username"],
               context.Request.QueryString["startdate"], context.Request.QueryString["enddate"])));
            CultureInfo enCultr = new CultureInfo("en-US");
            viewer.LocalReport.SetParameters(new ReportParameter("StartDate",
                DateTime.ParseExact(context.Request.QueryString["startdate"], "yyyy-MM-dd HH:mm:ss", enCultr).ToShortDateString()
                ));
            viewer.LocalReport.SetParameters(new ReportParameter("EndDate", 
                DateTime.ParseExact(context.Request.QueryString["enddate"], "yyyy-MM-dd HH:mm:ss", enCultr).ToShortDateString()
                ));

            viewer.LocalReport.Refresh();

            byte[] bytes = viewer.LocalReport.Render(
                "PDF", null, out mimeType, out encoding, out filenameExtension,
                out streamids, out warnings);                
            
            context.Response.Buffer = true;
            context.Response.Clear();
            context.Response.ContentType = mimeType;
            context.Response.AddHeader("content-disposition", "attachment; filename=" + System.IO.Path.GetTempFileName() + ".pdf");
            context.Response.BinaryWrite(bytes);
            context.Response.Flush();

        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }




        private List<EP_TimeLog> GetTimeLogsReport(string userName, string startDate, string endDate)
        {
            CultureInfo enCultr = new CultureInfo("en-US");
            using (EmployeePortalDataContext context = new EmployeePortalDataContext())
            {

                var temp = (from r in context.EP_TimeLogs
                            where r.CreatedBy.Trim().ToLower() == userName.Trim().ToLower() &&
                            r.StartTime.Date >= DateTime.ParseExact(startDate, "yyyy-MM-dd HH:mm:ss", enCultr).Date &&
                            r.EndTime.Value.Date <= DateTime.ParseExact(endDate, "yyyy-MM-dd HH:mm:ss", enCultr).Date
                            orderby r.StartTime ascending
                            select r);
                return temp.ToList();

            }
        }

      
    }
}


5) Add the following code to the app.js file

function ReportController($scope, $http, authService, $filter,$sce) {
    var scope = $scope;

    scope.collection = [];
    scope.message = '';
    scope.alertClass = 'alert-danger';

    scope.rowCollection = [];
    scope.CurrentLog = [];

    scope.StartTime = '';
    scope.EndTime = '';
    

    scope.authentication = authService.authentication;

    scope.GenerateReport = function (log) {
        
        
        window.open("/Handlers/ReportHandler.ashx?username=" + scope.authentication.userName
            + "&startdate=" + log.StartTime
            + "&enddate=" + log.EndTime);


     

       
    }

    scope.Reset = function () {



        var responsePromise = getById(2, $http);

        responsePromise.success(function (data, status, headers, config) {

            scope.CurrentLog = clone(data[0]);
            scope.message = '';
            scope.StartTime = '';
            scope.EndTime = '';
            scope.CurrentLog.Duration = 0;
            scope.alertClass = 'alert-danger';

        });
        responsePromise.error(function (data, status, headers, config) {
            scope.alertClass = 'alert-danger';
            scope.message = data;

        });

    }



    
    scope.Reset();


   





}