// BEFORE YOU START THIS PROGRAM
// 1) Load the data list spec in the SPDataList.Catalog project:  LongRunning.DataList.xml
// 2) Within this code file (Program.cs) change the following:
//      a) Within InitAppFxService(), change the URL for the AppFxWebService.asmx
//      b) Within GetRequestHeader(), change the REDatabaseToUse database key value
//      c) Within GetNetworkCredentials(), change the user name and password

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CancelSPDataList
{
    class Program
    {

        static CancelSPDataList.localhost.AppFxWebService svcclient;
        static CancelSPDataList.localhost.ClientAppInfoHeader clientAppInfoHeader;
        static int requestcounter;
        static void Main(string[] args)
        {
            Console.WriteLine("Initializing the AppFxWebService...this may take a moment.  Please be patient.");
            InitAppFxService();

            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("Enter # of rows to return. Try entering 1000 for starters:");
            string RowNumber = Console.ReadLine(); // Get string from user
            Int32 intnumber;

            bool isInt = Int32.TryParse(RowNumber, out intnumber);

            if (!isInt)
            {
                Console.WriteLine("");
                Console.WriteLine("");
                Console.WriteLine("Error.  The number you entered is not a number.");
                Console.WriteLine("press the Enter key to quit.:");
                Console.ReadLine();
            }
            else
            {
                DataListLoadAsync("Long Running Data List (custom)", Convert.ToInt32(RowNumber), "ABC123");
            }

            // while the counter is greater than zero the datalistload is 
            // still waiting for the completed event to be handled
            // Since the datalistload is asyncronous we can do other things in the meantime
            //   like ask the user if they want to cancel the Async Operation.
            if (requestcounter == 1)
            {
                Console.WriteLine("");
                Console.WriteLine("");
                Console.WriteLine("Is this taking too long? Enter 'y' to cancel the data list load.  Otherwise, the process will continue.:");
                string line = Console.ReadLine(); // Get string from user
                if (line == "y" & requestcounter == 1) // Check string
                {
                    //passing in the cancelid.  this tells CancelAsyncOperation which operation to cancel
                    CancelDataListLoad("ABC123");
                    while (requestcounter > 0)
                    {
                        System.Threading.Thread.Sleep(5000);
                    }

                }
            }
        }

        static void CancelDataListLoad(string cancelid)
        {
            CancelSPDataList.localhost.CancelAsyncOperationRequest cancelrequest = new CancelSPDataList.localhost.CancelAsyncOperationRequest();
            CancelSPDataList.localhost.CancelAsyncOperationReply cancelreply;
            cancelrequest.ClientAppInfo = clientAppInfoHeader;

            // telling CancelAsyncOperation which operation to cancel via the CancelID
            cancelrequest.CancelID = cancelid;

            Console.WriteLine("");
            Console.WriteLine("");
            Console.WriteLine("Canceling DataListLoad.....");

            // Once the DataListLoad method has been canceled, 
            // it will return to the calling application as if it had normally completed. 
            // See svcclient_DataListLoadCompleted
            cancelreply = svcclient.CancelAsyncOperation(cancelrequest);
        }

        //Launches a data list asynchronously 
        static void DataListLoadAsync(string DataListName, int LoopRows, string cancelid)
        {

            // The API utilizes a Request-Response pattern.  The pattern consists of request-response pairs.
            // An operation is called on the client proxy, such as DataListLoadAsync.  
            // The request is passed to the operation.
            // So, the proxy's DataListLoadAsync operation will require a DataListLoadRequest
            CancelSPDataList.localhost.DataListLoadRequest request = new CancelSPDataList.localhost.DataListLoadRequest();
            CancelSPDataList.localhost.DataFormItem dfi = new CancelSPDataList.localhost.DataFormItem();

            // Receive notification when DataListLoadAsync completes
            // Hook up a new  event handler procedure named svcclient_DataListLoadCompleted
            //  When the datalistload is completed svcclient_DataListLoadCompleted will handle the event.
            svcclient.DataListLoadCompleted += new CancelSPDataList.localhost.DataListLoadCompletedEventHandler(svcclient_DataListLoadCompleted);

            request.ClientAppInfo = clientAppInfoHeader;

            request.DataListName = DataListName;

            // How many times do you want to loop?  In other words, how long do you want to wait for the SP to complete?
            // Add an item to the collection of the DataFormFieldValueSet.  
            // Use the form field's/parameter's FieldID value for the DataFormFieldValue.ID and
            // a value via the DataFormFieldValue.Value
            // DataFormFieldValue's ID property represents the FieldID attribute within <FormField> element of the xml datalistspec.

            CancelSPDataList.localhost.DataFormFieldValue[] TheValue;
            TheValue = new CancelSPDataList.localhost.DataFormFieldValue[1];

            CancelSPDataList.localhost.DataFormFieldValue DFFieldValue = new CancelSPDataList.localhost.DataFormFieldValue();

            DFFieldValue.ID = "LOOPCOUNT";
            DFFieldValue.Value = LoopRows;

            TheValue[0] = DFFieldValue;
            dfi.Values = TheValue;

            request.Parameters = dfi;


            // For SDK v3.0, when using the AppFx Web API, calls to stored procedure based data lists (non CLR based data lists) 
            // can now be canceled. The AppFx web service DataListLoad method/operation now honors the .CancelID property 
            //for the DataListLoadRequest. Once the DataListLoad method has been canceled, 
            // it will return to the calling application as if it had normally completed. 
            //However, the API developer can inspect the DataListLoadReply to verify the cancellation 
            //via the .DataListLoadWasCancelled property. 


            // A cancelid is passed into this request.  Later on, if we need to cancel the operation,
            // this provides a way to identify the operation we wish
            // to cancel.  We cancel the operation by calling the web service's CancelAsyncOperation
            // see CancelDataListLoad for details.
            request.CancelID = cancelid;


            try
            {
                Console.WriteLine("");
                Console.WriteLine("");
                Console.WriteLine("Calling DataListLoad Asyncronously. The program will continue to run.");

                // Increase a counter that denotes the datalistload has been launched.
                System.Threading.Interlocked.Increment(ref requestcounter);

                //Call the operation asyncronously passing the request
                // The applications performs the task, yet remain responsive
                // 
                // See Event-based Asynchronous Pattern Overview for details
                //http://msdn.microsoft.com/en-us/library/wewwczdw(v=vs.80).aspx
                svcclient.DataListLoadAsync(request);
            }
            catch (System.Web.Services.Protocols.SoapException exSoap)
            {
                // If an error occurs we attach a SOAP fault error header.
                // You can check the proxy.ResponseErrorHeaderValue to get rich
                // error information including a nicer message compared to the raw exception message.
                if (svcclient.ResponseErrorHeaderValue != null)
                {
                    string wsMsg = svcclient.ResponseErrorHeaderValue.ToString();
                    Console.WriteLine("SoapException caught: {0}.", wsMsg.ToString());
                }
                else
                {
                    Console.WriteLine("SoapException caught: {0}.", exSoap.Message.ToString());
                }
            }
            catch (Exception Ex)
            {
                Console.WriteLine("Other Exception caught: {0} ", Ex.Message.ToString());
            }
        }

        // DataListLoadCompletedEventHandler event handler
        static void svcclient_DataListLoadCompleted(object sender, CancelSPDataList.localhost.DataListLoadCompletedEventArgs e)
        {
            if (e.Error == null && e.Result != null)
            {
                Console.WriteLine("");
                Console.WriteLine("");
                Console.WriteLine("The DataListLoad Completed.  Checking to see if it was cancelled...");
                Console.WriteLine("");

                // Consume the result
                // use the e.Result to check the results of the datalistload operation
                // Once the DataListLoad method has been canceled via the AppFx web service's CancelAsyncOperation, 
                // it will return to the calling application as if it had normally completed. 
                // However, the API developer can inspect the DataListLoadCompletedEventArgs to verify the cancellation 
                // via the .DataListLoadWasCancelled property. 
                if (e.Result.DataListLoadWasCancelled)
                {
                    Console.WriteLine("The looooong running data list WAS CANCELLED.");
                }
                else
                {
                    Console.WriteLine("The data list was NOT CANCELLED. The data list load completed successfully");
                    Console.WriteLine("Name: {0} | Value: {1}", "Row Count", e.Result.Rows.Count());
                }
                System.Threading.Interlocked.Decrement(ref requestcounter);
                Console.WriteLine("");
                Console.WriteLine("");
                Console.WriteLine("All done.  press the Enter key to quit.:");
                Console.ReadLine();
            }
        }


        #region "Helpers"

        static void InitAppFxService()
        {

            svcclient = new CancelSPDataList.localhost.AppFxWebService();

            // This code assumes a web reference to the http://{service host}/bbappfx/appfxwebservice.asmx web service
            // Your URL path to the appfxwebservice.asmx web service will vary.

            //  See the the project's Web References folder for a listing of web services.
            //  Adding a web service to the project creates a client proxy class.  Ours is named localhost.

            // Sample BBEC url...yours will vary.
            svcclient.Url = "http://localhost/bbappfx/appfxwebservice.asmx";

            // Must set credentials.  
            svcclient.Credentials = GetNetworkCredentials(true);

            clientAppInfoHeader = GetRequestHeader();
        }

        static System.Net.ICredentials GetNetworkCredentials(bool UseCurrentWindowsUserDefaultCredentials = true)
        {
            // *** AUTHENTICATION ****
            // Each Blackbaud CRM, Altru, or other Infinity based application user will have a Windows AD account within the hosted domain.
            // You will need to authenticate with the appropriate credentials.  
            // The BBEC/Altru IIS web application will attempt to authenticate the user against Windows AD.
            // For basic authentication you will need a username and password to authenticate against the Windows Active Directory.  
            //  or you can provide System.Net.CredentialCache.DefaultCredentials which represents the currently logged in windows user.
            // For Altru, the appropriate domain name is configured on the Altru IIS server by Blackbaud Hosting.
            // Typically the developer does not need to provide the domain name in the credentials.  
            //  This holds true for BBEC instances, as well. 

            // **** AUTHORIZATION ****
            // Each windows user account is linked to an application user within the applicaiton (BBEC/Altru) database.
            // BBEC/Altru will authorize which features, such as a data list, the application user is allowed to automate/consume.
            // Therefore, the credentials you provide must link to an application user which is authorized to 
            // utilize all the features that you wish to manipulate with the API
            // Within BBEC or Altru See Administration > Application > Security to associate application users to system roles 
            //  and system roles to features.
            // Blackbaud Professional Services will need to provide credentials to 3rd party Altru API developers

            System.Net.ICredentials NetworkCredential;

            if (UseCurrentWindowsUserDefaultCredentials == true)
            {
                // See http://msdn.microsoft.com/en-us/library/system.net.credentialcache.defaultcredentials.aspx
                // DefaultCredentials represents the system credentials for the current security context 
                // in which the application is running. For a client-side application, 
                // these are usually the Windows credentials (user name, password, and domain) of the user running the application. 
                NetworkCredential = System.Net.CredentialCache.DefaultCredentials;
            }
            else
            {
                String securelyStoredUserName, securelyStoredPassword;

                // The user name does not need to be prefixed with the domain name... 
                // For example, for the user name dont use DOMAINNAME\USERNAME just USERNAME

                // **** IMPORTANT *****
                // Don't hard code the URL, database identifier, user name and password to the Infinity application
                // The URL to the application, Database identifiers, User Names, and passwords need to be 
                // configurable and secure.  
                securelyStoredUserName = "<User Name>";
                securelyStoredPassword = "<Pwd>";

                // **** Providing Credentials
                // System.Net.NetworkCredential implements System.NET.ICredentials
                // For Altru, the appropriate domain name is configured on the Altru IIS server by Blackbaud Hosting.
                // Typically the developer does not need to provide the domain name.  This holds true for BBEC instances, as well. 

                NetworkCredential = new System.Net.NetworkCredential(securelyStoredUserName, securelyStoredPassword);
            }
            return NetworkCredential;
        }

        static CancelSPDataList.localhost.ClientAppInfoHeader GetRequestHeader()
        {
            //Create the client app header (all requests require this)
            CancelSPDataList.localhost.ClientAppInfoHeader header = new CancelSPDataList.localhost.ClientAppInfoHeader();

            //  Provide some name to distinguish this application using the service. 
            //  Provide the ClientAppName which will be logged in the Infinity database and 
            //      used for auditing purposes.
            //  Use a client application name that identifies your custom client software 
            //      from the web shell and any other client user interfaces.
            //  This name will ultimately be passed to the
            //      database, so don't make this random or per user or you won't benefit from connection pooling.

            header.ClientAppName = "Test SP DataList Load App";

            // ****************************************
            // ****** PROVIDING A DATABASE KEY  *******
            // ****************************************
            // Before you can consume features with the API, a developer should provide a key which
            // identifies the database.  There are two options when obtaining a database key:
            //   1) Manually obtain a database key, store the database key in a config file, 
            //      and retreive database key at runtime
            //   2) Retrieve a listing of databases associated with the Infinity application at run time and present the listing
            //      of databases to the end user.  Let the end user pick the appropriate database.

            //  A single webservice instance can serve multiple databases.
            //  You must specify the database key name for every request. 

            // **********************************************************************************************
            // **** Option 1A - Hosted:  Manually Providing a GUID for Hosting's SiteManager (ALTRU/RP) *****
            // **********************************************************************************************
            // ALTRU HOSTED CLIENTS USE A GUID WHICH REFERS TO THE DATABASE KEY within Blackbaud Hosting's SiteManager
            // For Hosted Altru clients the server side IIS virtual directory is configured 
            // to retrieve a listing of database keys from Blackbaud Hosting's SiteManager.  For Altru, pass a Guid to the 
            // site manager instead of the typical database key that you would normally do for a non hosted Infinty instance such as BBEC.
            // The REDatabasueToUse typically refers to a "database key" that points to a SQL Server instance and database
            // For hosted Altru clients use a GUID that site manager will use to obtain the database key
            // The GUID can be found by locating the databaseName value at the end of the webshell's url.  See sample below.
            // In BB hosted environments, on the server side, blackbaud's site manager will use this Guid to look up 
            // the appropriate database key
            // https://altrurig01bo3.blackbaudhosting.com/5740Altru_9a731bb7-0e50-48e3-b8c3-e03e16a5ac15/webui/webshellpage.aspx?databaseName=10399621-5E99-4916-8625-2703496B1C41
            //header.REDatabaseToUse = "10399621-5E99-4916-8625-2703496B1C41";

            //**********************************************************************
            //**** Option 1B - Non Hosted:  Manually Providing a Database Key  *****
            //**********************************************************************
            //Non hosted clients (BBEC) can obtain the database key by reviewing the connectionStrings element within
            //the web.config file on IIS. 

            //   The list of available databases key names are what you see in the click once login form or
            //    you will see this database key name when looking at the databasename value in the URL for the web shell URL
            //    ex:  http://localhost/bbappfx/webui/webshellpage.aspx?databasename=BBInfinity
            //  The REDatabaseToUse string refers to a database key found within the Infinity web application's web.config 
            //  The REDatabaseToUse database key name tells the web application which connection string to use

            header.REDatabaseToUse = "BBInfinity";

            // **************************************************************
            // *** Option 2 - Retrieving a list of available databases  *****
            // **************************************************************
            // Alternatively, if you want the end user to select the database name, you can code a request to 
            // retrieve a list of databases and have the user select from the list of the databases within the 
            // reply.Databases which will return a string array of databases configured for a particular Infinity application

            // The value for each item in the string array will be different for hosted versus non hosted applications.
            // For a hosted application, each item in the array will contain two values delimited with a semi-colon. 

            // Example of one database value in the Altru Hosted string array:  "10399621-5E99-4916-8625-2703496B1C41;5740Altru"
            // The value before the semi-colon (0399621-5E99-4916-8625-2703496B1C41) is the GUID that Altur will 
            // use to obtain the database key.
            // The value after the semi-colon (5740Altru) is the friendly name to display to the end user. 
            // If hitting an Altru instance the Reply.Databases values will be separated with a semicolon so,
            //  parse out the GUID and the friendly display name.
            //  Get the GUID for the Altru database

            // If hitting a non hosted database then the Reply.Databases values will NOT be separated with a semicolon.


            // localhost.GetAvailableREDatabasesRequest request = new localhost.GetAvailableREDatabasesRequest();
            // request.ClientAppInfo = header;

            //IMPORATANT:  For hosted, You must pass a valid HostedSiteInfoID to GetAvailableREDatabases 
            // to retrieve the database list from SiteManager.  If you dont, you will get the following error message:
            //       Server was unable to process request. ---> 
            //       You must pass a valid siteInfoID to GetAvailableDatabases if the virtual directory is configured 
            // HostedSiteInfoID is ignored in hon hosted scenarios. 
            // The guid used for HostedSiteInfoID can be found within URL for hosted Altru instance
            // request.HostedSiteInfoID = New System.Guid("9a731bb7-0e50-48e3-b8c3-e03e16a5ac15")

            // localhost.GetAvailableREDatabasesReply reply = svcclient.GetAvailableREDatabases(request);
            // use the reply.Databases string array to list database to user

            return header;
        }

        #endregion

    }
}

