コード例 #1
0
ファイル: BatchProcessor.cs プロジェクト: DonaldAirey/quasar
        /// <summary>
        /// Execute the batch.
        /// </summary>
        /// <param name="batch"></param>
        Result IBatchHandler.Execute(Batch batch)
        {
            // Execute the transactions contained in the batch.  Each transaction is committed or rejected as a unit.  The commands
            // within the batch are specified as text strings and object arrays which are be resolved through the 'Reflection'
            // classes to assemblies, types, methods and parameters.  A wrapper around the .NET Framework Transaction class is used
            // to commit or reject these collections of commands as a unit.
            foreach (Batch.Transaction batchTransaction in batch.Transactions)
            {
                // The 'Transaction' is used to commit or reject the pseudo-methods contained in the 'Batch.Transaction. as a
                // unit.  The transaction will be rejected and all changes will be rejected if an exception is taken during the
                // execution of any of the methods contained in the 'Batch.Transaction'. If there are no exceptions taken, then all
                // the changes are committed to their respective data stores.
                Transaction transaction = new Transaction();

                // Every transaction has one or more data repositories that can be read or modified.  This step will create a
                // Resource Manager for every one of the data stores that can be referenced by this server during the processing of
                // a transaction.  A transaction can include one or more of these databases and the internal framework will
                // escelate from a single phase transaction to a two-phase transaction when there is more than one durable
                // resource.
                foreach (ResourceDescription resourceDescription in BatchProcessor.resourceDescriptionList)
                {
                    // This will create a resource manager for an ADO data resource based on the configuration settings.
                    if (resourceDescription is AdoResourceDescription)
                    {
                        transaction.Add(new AdoResourceManager(resourceDescription.Name));
                    }

                    // This will create a resource manager for SQL databases based on the configuration settings.
                    if (resourceDescription is SqlResourceDescription)
                    {
                        transaction.Add(new SqlResourceManager(resourceDescription.Name));
                    }
                }

                try
                {
                    // This variable controls whether the transaction is committed or rejected.
                    bool hasExceptions = false;

                    // This step will use reflection to resolve the assemblies, types and method names into objects that are
                    // loaded in memory. Once the objects are resolved, the list of parameters is passed to the method for
                    // execution.  The beautiful part about this architecture is that the target methods themselves are written and
                    // supported as strongly typed interfaces.  The 'Reflection' class takes care of matching the variable array of
                    // objects to the strongly typed, individual parameters.
                    foreach (Batch.Method batchMethod in batchTransaction.Methods)
                    {
                        try
                        {
                            // The 'Reflection' classes are used to resolve the method specified in the batch to an object loaded
                            // in memory that can be used to invoke that method.
                            Assembly assembly = Assembly.Load(batchMethod.Type.Assembly.DisplayName);
                            Type     type     = assembly.GetType(batchMethod.Type.Name);
                            if (type == null)
                            {
                                throw new Exception(String.Format("Unable to locate type \"{0}\" in assembly {1}",
                                                                  batchMethod.Type.Name, batchMethod.Type.Assembly.DisplayName));
                            }
                            MethodInfo methodInfo = type.GetMethod(batchMethod.Name);
                            if (methodInfo == null)
                            {
                                throw new Exception(String.Format("The method \"{0}.{1}\" can't be located in assembly {2}.",
                                                                  batchMethod.Type.Name, batchMethod.Name, batchMethod.Type.Assembly.DisplayName));
                            }

                            // This will invoke the method specified in the batch and match each of the variable, generic
                            // parameters to strongly typed parameters.
                            object result = methodInfo.Invoke(null, batchMethod.Parameters);

                            // Like WebServices, this will return the parameters to the client as a variable, generic array.  It is
                            // up to the client to sort out the order of the returned parameters.
                            List <Object> resultList = new List <object>();
                            if (methodInfo.ReturnType != typeof(void))
                            {
                                resultList.Add(result);
                            }
                            foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
                            {
                                if (parameterInfo.ParameterType.IsByRef)
                                {
                                    resultList.Add(batchMethod.Parameters[parameterInfo.Position]);
                                }
                            }
                            batchMethod.Results = resultList.ToArray();
                        }
                        catch (Exception exception)
                        {
                            // This indicates that there was some problem with the transaction and the results should be rejected,
                            // but only after the remaining items in the transaction have been attempted.  This maximizes the
                            // information that the caller has so the next time the batch is sent, all the errors can be fixed.  If
                            // the transaction were to stop after the first exception was discovered, there could be a sequence of
                            // many back-and-forth attempts to run the batch before all the errors were discovered and corrected.
                            hasExceptions = true;

                            // This mechanism will pass the actual exception in the method back to the caller and correlate it with
                            // the Batch.Method that caused the problem.  Passing the 'TargetInvocationException' back to the
                            // client is of no practical value, so the inner exception is extracted instead and passsed back to the
                            // client.  When the client processes the BatchException, it will appear as if the exception that
                            // happend here on the server actually happeded local to the client machine.  This is the desired
                            // action.
                            batchMethod.Exceptions.Add(exception is System.Reflection.TargetInvocationException ?
                                                       exception.InnerException : exception);
                        }
                    }

                    // If any exceptions were taken while processing the batch, reject the changes to the data model.  Otherwise,
                    // the results can be committed as a unit.
                    if (hasExceptions)
                    {
                        transaction.Rollback();
                    }
                    else
                    {
                        transaction.Commit();
                    }
                }
                catch (Exception exception)
                {
                    // This will catch any errors that occurred while using 'Reflection' to execute the batch.
                    batchTransaction.Exceptions.Add(exception);
                }
                finally
                {
                    // Release the locks and any database connection resources.
                    transaction.Dispose();
                }
            }

            // Only the return parameters and exceptions are returned to the client.  There is no need to transmit the entire batch
            // across the process boundary.  The client will integrate the return parameters and exceptions back into the original
            // batch making it appear as if the bach made a round trip.
            return(new Result(batch));
        }
コード例 #2
0
        /// <summary>
        /// Execute the batch on the Web Transaction server and return the results to the caller.
        /// </summary>
        /// <param name="batch">Contains one or more transactions that are executed on the server.  The results of executing the
        /// batch are returned to the caller in this object.</param>
        Result IBatchHandler.Execute(Batch batch)
        {
            Result batchResult = null;

            // These streams must be shut down, even when an exception is taken.  They are declared here so they can be accessed by
            // the 'finally' clause should something go wrong.
            Stream      requestStream  = null;
            WebResponse webResponse    = null;
            Stream      responseStream = null;

            try
            {
                // The client communicates the 'batch' data structure to the server using an HTTP channel.  The channel is
                // configured to use SSL and Certificates to insure the privacy of the conversation.
                WebRequest webRequest = CreateCertificateRequest();

                // The 'batch' data structure is serialized and compressed before being sent to the server.
                MemoryStream    memoryStreamRequest = new MemoryStream();
                BinaryFormatter binaryFormatter     = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStreamRequest, batch);

                // This section will compress the serial stream.  Note that it must be compressed into an intermediate buffer
                // before being sent via the HttpWebRequest class because the number of bytes must be known before the call is made
                // for the 'ContentLength' parameter of the HttpWebRequest object.
                memoryStreamRequest.Position = 0L;
                requestStream = webRequest.GetRequestStream();
                DeflateStream requestDeflateStream = new DeflateStream(requestStream, CompressionMode.Compress, true);
                requestDeflateStream.Write(memoryStreamRequest.GetBuffer(), 0, (int)memoryStreamRequest.Length);
                requestDeflateStream.Close();
                requestStream.Close();

                // This is where all the work is done.  Send the Request to the HTTP Server and get the response.  In this
                // case, the response will be the results of executing the remote batch.  Note that we get back only the
                // framework of the RPB, not all the parameters, methods, assemblies and types.  This is an optimization so
                // that the same data isn't sent twice over the network.  This 'differential' data will be merged with the
                // original.  To the user, it appears that the Remote Batch.Method Batch was sent to the server, filled in with
                // result and exception data, then returned to the calling method.  Actually, the batch was sent, the results
                // and exceptions returned, and it was all stitched back together again before the user gets it.
                webResponse = webRequest.GetResponse();

                // Decompress the results from the HttpWebResponse stream.
                responseStream = webResponse.GetResponseStream();
                DeflateStream responseDeflateStream = new DeflateStream(responseStream, CompressionMode.Decompress);
                byte[]        uncompressedBuffer    = CompressedStreamReader.ReadBytes(responseDeflateStream);
                responseDeflateStream.Close();

                // Deserialize the batch results and merge it back with the original structure.  The 'BatchResult'
                // structure has the same basic structure as the original batch, but only contains output parameters and
                // exceptions.
                MemoryStream    memoryStreamResponse    = new MemoryStream(uncompressedBuffer);
                BinaryFormatter binaryFormatterResponse = new BinaryFormatter();
                batchResult = (Result)binaryFormatterResponse.Deserialize(memoryStreamResponse);
                memoryStreamResponse.Close();
            }
            catch (WebException webException)
            {
                // The response in the exception usually indicates that we found the server, but the protocol failed for some
                // reason.  When the response is empty, that usually indicates that the URL is bad or the server is down for
                // some reason.
                if (webException.Response == null)
                {
                    // Log the error.
                    EventLog.Error("Web Exception {0}, {1}", webException.Status, webException.Message);
                }
                else
                {
                    // Extract the information sent back from the server and log it.
                    webResponse = (HttpWebResponse)webException.Response;
                    EventLog.Error(webException.Message);

                    // Present the user with the error code.  There may be a better way to do this, but this will work for now.
                    MessageBox.Show(webException.Message, string.Format("{0} - {1}", Application.ProductName, Properties.Resources.ConnectionError));
                }

                // Prompt the user for the URL and credentials again.
                HttpBatchProcessor.IsUrlPrompted        = true;
                HttpBatchProcessor.IsCredentialPrompted = true;

                // This gives the caller a unified way to handle all exceptions that may have occurred while processing a batch.
                // The Web Exceptions are handled the same way as errors from the server.
                throw new BatchException(webException);
            }
            catch (UserAbortException)
            {
                // forward the exception so the caller can decide what to do.
                throw;
            }
            catch (Exception exception)
            {
                // This gives the caller a unified way to handle all exceptions that may have occurred while processing a batch.
                // The general exceptions are handled the same way as exceptions from the server.
                throw new BatchException(exception);
            }
            finally
            {
                // Make sure that every stream involved with the request is closed when the WebRequest is finished.
                if (requestStream != null)
                {
                    requestStream.Close();
                }
                if (responseStream != null)
                {
                    responseStream.Close();
                }
                if (webResponse != null)
                {
                    webResponse.Close();
                }
            }

            return(batchResult);
        }