├── sharpsaprfc.snk ├── tests ├── Assets │ └── IDES_LOGO.bmp ├── packages.config ├── Model │ ├── RepositoryObject.cs │ ├── ZCustomer.cs │ ├── MaterialState.cs │ ├── ZMaraSingleDateTime.cs │ ├── AirlineCompany.cs │ ├── ZMara.cs │ └── Flight.cs ├── FunctionObjects │ ├── GetAllCustomersFunction.cs │ ├── SumOfTwoNumbersFunction.cs │ └── DivideTwoNumbersFunction.cs ├── TestCases │ ├── SoapConfigurationSectionTestCase.cs │ ├── ParallelTestCase.cs │ ├── PreparedFunctionTestCase.cs │ ├── AbapBoolTestCase.cs │ ├── AccessDeniedTestCase.cs │ ├── WrongCredentialsTestCase.cs │ ├── Soap_AbapValueMapperTestCase.cs │ ├── Plain_AbapValueMapperTestCase.cs │ ├── TimeoutTestCase.cs │ ├── FunctionObjectTestCase.cs │ ├── Soap_ExceptionDetailsTestCase.cs │ ├── ConnectionTestCase.cs │ ├── StructureTestCase.cs │ ├── FluentRfcReadTableTestCase.cs │ └── RfcReadTableTestCase.cs ├── Extension │ └── ImageAssert.cs ├── Properties │ └── AssemblyInfo.cs ├── Mapper │ ├── AbapValueMapperFromRemoteExceptionTestData.cs │ ├── AbapValueMapperToRemoteTestData.cs │ └── AbapValueMapperFromRemoteTestData.cs ├── App.config ├── Metadata │ ├── StructureMetadataTestCase.cs │ └── FunctionMetadataTestCase.cs └── SharpSapRfc.Test.csproj ├── src ├── base │ ├── Metadata │ │ ├── TableMetadata.cs │ │ ├── FieldMetadata.cs │ │ ├── ParameterMetadata.cs │ │ ├── StructureMetadata.cs │ │ └── FunctionMetadata.cs │ ├── RfcMappingException.cs │ ├── RfcResult.cs │ ├── Structure │ │ ├── Tab512.cs │ │ ├── RfcDbWhere.cs │ │ └── RfcDbField.cs │ ├── Types │ │ ├── AbapDataType.cs │ │ ├── AbapBool.cs │ │ └── AbapDateTime.cs │ ├── RfcFunctionObject.cs │ ├── RfcReadTableOption.cs │ ├── UnknownRfcParameterException.cs │ ├── TypeExtension.cs │ ├── SharpRfcException.cs │ ├── RfcEnumValueAttribute.cs │ ├── RfcParameter.cs │ ├── RfcStructureFieldAttribute.cs │ ├── SharpRfcCallException.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── RfcPreparedFunction.cs │ ├── RfcMetadataCache.cs │ ├── SapRfcConnection.cs │ ├── SharpSapRfc.csproj │ ├── RfcReadTableQueryBuilder.cs │ ├── RfcStructureMapper.cs │ └── RfcValueMapper.cs ├── soap │ ├── SoapRfcValueMapper.cs │ ├── Configuration │ │ ├── SapSoapRfcDestinationCollection.cs │ │ ├── SapSoapRfcDestinationElement.cs │ │ └── SapSoapRfcConfigurationSection.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── AbapDataTypeParser.cs │ ├── SoapSapRfcConnection.cs │ ├── SoapRfcResult.cs │ ├── SoapRfcStructureMapper.cs │ ├── SharpSapRfc.Soap.csproj │ ├── SoapRfcWebClient.cs │ ├── SoapRfcMetadataCache.cs │ └── SoapRfcPreparedFunction.cs └── plain │ ├── PlainRfcValueMapper.cs │ ├── PlainRfcResult.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── PlainSapRfcConnection.cs │ ├── Extension │ └── RfcElementMetadataExtension.cs │ ├── PlainRfcMetadataCache.cs │ ├── PlainRfcStructureMapper.cs │ ├── SharpSapRfc.Plain.csproj │ └── PlainRfcPreparedFunction.cs ├── LICENSE ├── SharpSapRfc.nuspec ├── SharpSapRfc.Soap.nuspec ├── SharpSapRfc.Plain.x64.nuspec ├── SharpSapRfc.Plain.x86.nuspec ├── .gitattributes ├── .gitignore ├── SharpSapRfc.sln └── README.md /sharpsaprfc.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goenning/SharpSapRfc/HEAD/sharpsaprfc.snk -------------------------------------------------------------------------------- /tests/Assets/IDES_LOGO.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goenning/SharpSapRfc/HEAD/tests/Assets/IDES_LOGO.bmp -------------------------------------------------------------------------------- /src/base/Metadata/TableMetadata.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Metadata 3 | { 4 | public class TableMetadata 5 | { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/base/RfcMappingException.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc 3 | { 4 | public class RfcMappingException : SharpRfcException 5 | { 6 | public RfcMappingException(string message) 7 | : base(message) 8 | { 9 | 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/base/RfcResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SharpSapRfc 4 | { 5 | public abstract class RfcResult 6 | { 7 | public abstract T GetOutput(string name); 8 | public abstract IEnumerable GetTable(string name); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/base/Structure/Tab512.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SharpSapRfc.Structure 6 | { 7 | public class Tab512 8 | { 9 | [RfcStructureField("WA")] 10 | public string Data { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/Model/RepositoryObject.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Test.Model 3 | { 4 | public class RepositoryObject 5 | { 6 | [RfcStructureField("OBJ_NAME")] 7 | public string Name { get; set; } 8 | [RfcStructureField("DELFLAG")] 9 | public bool DeletionFlag { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/soap/SoapRfcValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace SharpSapRfc.Soap 4 | { 5 | public class SoapRfcValueMapper : RfcValueMapper 6 | { 7 | protected override NumberFormatInfo GetNumberFormat() 8 | { 9 | return this.PeriodDecimalNumberFormat; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Model/ZCustomer.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Test.Model 3 | { 4 | public class ZCustomer 5 | { 6 | public int Id { get; set; } 7 | public string Name { get; set; } 8 | 9 | [RfcStructureField("ACTIVE")] 10 | public bool IsActive { get; set; } 11 | 12 | public int Age { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/base/Types/AbapDataType.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Types 3 | { 4 | public enum AbapDataType 5 | { 6 | SHORT, 7 | INTEGER, 8 | NUMERIC, 9 | CHAR, 10 | DECIMAL, 11 | DATE, 12 | TIME, 13 | STRUCTURE, 14 | TABLE, 15 | BYTE, 16 | FLOAT, 17 | DOUBLE 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/base/RfcFunctionObject.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc 3 | { 4 | public abstract class RfcFunctionObject 5 | { 6 | public abstract string FunctionName { get; } 7 | public abstract T GetOutput(RfcResult result); 8 | 9 | public virtual object Parameters 10 | { 11 | get { return null; } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/base/RfcReadTableOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc 7 | { 8 | public enum RfcReadTableOption 9 | { 10 | Equals, 11 | NotEquals, 12 | GreaterThan, 13 | LessThan, 14 | GreaterOrEqualThan, 15 | LessOrEqualThan 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/plain/PlainRfcValueMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Threading; 3 | 4 | namespace SharpSapRfc.Plain 5 | { 6 | public class PlainRfcValueMapper : RfcValueMapper 7 | { 8 | protected override NumberFormatInfo GetNumberFormat() 9 | { 10 | return Thread.CurrentThread.CurrentCulture.NumberFormat; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/base/UnknownRfcParameterException.cs: -------------------------------------------------------------------------------- 1 | namespace SharpSapRfc 2 | { 3 | public class UnknownRfcParameterException : SharpRfcException 4 | { 5 | public UnknownRfcParameterException(string parameterName, string functionName) 6 | : base(string.Format("Parameter {0} was not found on function {1}.", parameterName, functionName)) 7 | { 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/Model/MaterialState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc.Test.Model 7 | { 8 | public enum MaterialState 9 | { 10 | [RfcEnumValue("AVAL")] 11 | Available = 1, 12 | [RfcEnumValue("BLOK")] 13 | Blocked = 2, 14 | [RfcEnumValue("OOS")] 15 | OutOfStock = 3, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Model/ZMaraSingleDateTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc.Test.Model 7 | { 8 | public class ZMaraSingleDateTime 9 | { 10 | [RfcStructureField("ID")] 11 | public int Id { get; set; } 12 | [RfcStructureField("DATUM", "UZEIT")] 13 | public DateTime? DateTime { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/base/TypeExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSapRfc 4 | { 5 | public static class TypeExtension 6 | { 7 | public static bool IsNullable(this Type type) 8 | { 9 | if (!type.IsValueType) 10 | return true; 11 | 12 | if (Nullable.GetUnderlyingType(type) != null) 13 | return true; 14 | 15 | return false; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/base/Metadata/FieldMetadata.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Types; 2 | 3 | namespace SharpSapRfc.Metadata 4 | { 5 | public class FieldMetadata 6 | { 7 | public string Name { get; private set; } 8 | public AbapDataType DataType { get; private set; } 9 | 10 | public FieldMetadata(string name, AbapDataType dataType) 11 | { 12 | this.Name = name; 13 | this.DataType = dataType; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/base/SharpRfcException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSapRfc 4 | { 5 | [Serializable] 6 | public class SharpRfcException : Exception 7 | { 8 | public SharpRfcException(string message) 9 | : base(message) 10 | { 11 | 12 | } 13 | 14 | public SharpRfcException(string message, Exception innerException) 15 | : base(message, innerException) 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/base/Structure/RfcDbWhere.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc.Structure 7 | { 8 | public class RfcDbWhere 9 | { 10 | [RfcStructureField("TEXT")] 11 | public string Text { get; set; } 12 | 13 | public RfcDbWhere() 14 | { 15 | 16 | } 17 | 18 | public RfcDbWhere(string text) 19 | { 20 | this.Text = text; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/base/RfcEnumValueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc 7 | { 8 | [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] 9 | public class RfcEnumValueAttribute : Attribute 10 | { 11 | public string Value { get; private set; } 12 | public RfcEnumValueAttribute(string value) 13 | { 14 | this.Value = value; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/FunctionObjects/GetAllCustomersFunction.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Test.Model; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpSapRfc.Test.FunctionObjects 5 | { 6 | public class GetAllCustomersFunction : RfcFunctionObject> 7 | { 8 | public override string FunctionName 9 | { 10 | get { return "Z_SSRT_GET_ALL_CUSTOMERS2"; } 11 | } 12 | 13 | public override IEnumerable GetOutput(RfcResult result) 14 | { 15 | return result.GetTable("e_customers"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/base/RfcParameter.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Collections; 4 | using System.Reflection; 5 | using System.Text; 6 | namespace SharpSapRfc 7 | { 8 | public class RfcParameter 9 | { 10 | public string Name { get; private set; } 11 | public object Value { get; private set; } 12 | 13 | public RfcParameter(string name, object value) 14 | { 15 | this.Name = name.ToUpper(); 16 | this.Value = value; 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return string.Format("{0}: {1}", this.Name, this.Value); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Model/AirlineCompany.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc.Test.Model 7 | { 8 | public class AirlineCompany 9 | { 10 | [RfcStructureField("MANDT")] 11 | public int Client { get; set; } 12 | [RfcStructureField("CARRID")] 13 | public string Code { get; set; } 14 | [RfcStructureField("CARRNAME")] 15 | public string Name { get; set; } 16 | [RfcStructureField("CURRCODE")] 17 | public string Currency { get; set; } 18 | [RfcStructureField("URL")] 19 | public string Url { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/base/Types/AbapBool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpSapRfc 5 | { 6 | public static class AbapBool 7 | { 8 | private static List expectedValues = new List() { "X", "", " " }; 9 | 10 | public static Boolean FromString(string value) 11 | { 12 | if (expectedValues.Contains(value)) 13 | return value == "X"; 14 | 15 | string message = string.Format("'{0}' is not a valid boolean value", value); 16 | throw new RfcMappingException(message); 17 | } 18 | 19 | public static string ToString(bool value) 20 | { 21 | return value ? "X" : " "; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/base/RfcStructureFieldAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpSapRfc 4 | { 5 | [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)] 6 | public class RfcStructureFieldAttribute : Attribute 7 | { 8 | public string FieldName { get; private set; } 9 | public string SecondFieldName { get; private set; } 10 | 11 | public RfcStructureFieldAttribute(string fieldName) 12 | { 13 | this.FieldName = fieldName; 14 | } 15 | 16 | public RfcStructureFieldAttribute(string dateFieldName, string timeFieldName) 17 | { 18 | this.FieldName = dateFieldName; 19 | this.SecondFieldName = timeFieldName; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/base/Structure/RfcDbField.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Structure 3 | { 4 | public class RfcDbField 5 | { 6 | [RfcStructureField("FIELDNAME")] 7 | public string FieldName { get; set; } 8 | 9 | [RfcStructureField("OFFSET")] 10 | public int Offset { get; set; } 11 | 12 | [RfcStructureField("LENGTH")] 13 | public int Length { get; set; } 14 | 15 | [RfcStructureField("TYPE")] 16 | public string Type { get; set; } 17 | 18 | [RfcStructureField("FIELDTEXT")] 19 | public string FieldText { get; set; } 20 | 21 | public RfcDbField() 22 | { 23 | } 24 | 25 | public RfcDbField(string fieldName) 26 | { 27 | this.FieldName = fieldName; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Model/ZMara.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc.Test.Model 7 | { 8 | public class ZMara 9 | { 10 | [RfcStructureField("ID")] 11 | public int Id { get; set; } 12 | [RfcStructureField("NAME")] 13 | public string Name { get; set; } 14 | [RfcStructureField("PRICE")] 15 | public decimal Price { get; set; } 16 | [RfcStructureField("DATUM")] 17 | public DateTime Date { get; set; } 18 | [RfcStructureField("UZEIT")] 19 | public DateTime Time { get; set; } 20 | [RfcStructureField("Active")] 21 | public bool IsActive { get; set; } 22 | [RfcStructureField("STATE")] 23 | public MaterialState State { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/base/Metadata/ParameterMetadata.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Types; 2 | 3 | namespace SharpSapRfc.Metadata 4 | { 5 | public class ParameterMetadata 6 | { 7 | public string Name { get; private set; } 8 | public AbapDataType DataType { get; private set; } 9 | public StructureMetadata StructureMetadata { get; private set; } 10 | 11 | public ParameterMetadata(string name, AbapDataType dataType) 12 | { 13 | this.Name = name.ToUpper(); 14 | this.DataType = dataType; 15 | } 16 | 17 | public ParameterMetadata(string name, AbapDataType dataType, StructureMetadata structureMetadata) 18 | { 19 | this.Name = name.ToUpper(); 20 | this.DataType = dataType; 21 | this.StructureMetadata = structureMetadata; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/soap/Configuration/SapSoapRfcDestinationCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace SharpSapRfc.Soap.Configuration 8 | { 9 | public class SapSoapRfcDestinationCollection : ConfigurationElementCollection 10 | { 11 | protected override ConfigurationElement CreateNewElement() 12 | { 13 | return new SapSoapRfcDestinationElement(); 14 | } 15 | 16 | protected override object GetElementKey(ConfigurationElement element) 17 | { 18 | return ((SapSoapRfcDestinationElement)element).Name; 19 | } 20 | 21 | public IEnumerable Elements 22 | { 23 | get { return this.OfType(); } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/base/Metadata/StructureMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SharpSapRfc.Metadata 5 | { 6 | public class StructureMetadata 7 | { 8 | public string Name { get; private set; } 9 | public IEnumerable Fields { get; private set; } 10 | 11 | public StructureMetadata(string name, IEnumerable fields) 12 | { 13 | this.Name = name; 14 | this.Fields = fields; 15 | } 16 | 17 | public FieldMetadata GetField(string name) 18 | { 19 | var field = this.Fields.FirstOrDefault(x => x.Name == name.ToUpper()); 20 | if (field == null) 21 | throw new SharpRfcException(string.Format("Structure {0} does not have field named {1}.", this.Name, name)); 22 | 23 | return field; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestCases/SoapConfigurationSectionTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Soap.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace SharpSapRfc.Test.TestCases 9 | { 10 | public class SoapConfigurationSectionTestCase 11 | { 12 | [Fact] 13 | public void ParametersCanBeRead() 14 | { 15 | var destination = SapSoapRfcConfigurationSection.GetConfiguration("TST-SOAP"); 16 | Assert.Equal("TST-SOAP", destination.Name); 17 | Assert.Equal("http://sap-vm:8000/sap/bc/soap/rfc", destination.RfcUrl); 18 | Assert.Equal("http://sap-vm:8000/sap/bc/soap/wsdl", destination.WsdlUrl); 19 | Assert.Equal("001", destination.Client); 20 | Assert.Equal("rfc_super", destination.User); 21 | Assert.Equal("rfcsuper1", destination.Password); 22 | Assert.Equal(5000, destination.Timeout); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Model/Flight.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Test.Model 3 | { 4 | public class Flight 5 | { 6 | [RfcStructureField("CARRID")] 7 | public string Carrier { get; set; } 8 | [RfcStructureField("CONNID")] 9 | public string ConnectionId { get; set; } 10 | [RfcStructureField("COUNTRYFR")] 11 | public string ContryFrom { get; set; } 12 | [RfcStructureField("CITYFROM")] 13 | public string CityFrom { get; set; } 14 | [RfcStructureField("AIRPFROM")] 15 | public string AirportFrom { get; set; } 16 | [RfcStructureField("COUNTRYTO")] 17 | public string ContryTo { get; set; } 18 | [RfcStructureField("CITYTO")] 19 | public string CityTo { get; set; } 20 | [RfcStructureField("AIRPTO")] 21 | public string AirportTo { get; set; } 22 | [RfcStructureField("FLTIME")] 23 | public int FlightTime { get; set; } 24 | [RfcStructureField("DISTANCE")] 25 | public decimal FlightDistance { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /tests/Extension/ImageAssert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace Xunit 9 | { 10 | public static class ImageAssert 11 | { 12 | public static void AreEqual(Bitmap expected, Bitmap actual) 13 | { 14 | Assert.True(AreImageEquals(expected, actual)); 15 | } 16 | 17 | private static bool AreImageEquals(Bitmap bmp1, Bitmap bmp2) 18 | { 19 | if (!bmp1.Size.Equals(bmp2.Size)) 20 | { 21 | return false; 22 | } 23 | for (int x = 0; x < bmp1.Width; ++x) 24 | { 25 | for (int y = 0; y < bmp1.Height; ++y) 26 | { 27 | if (bmp1.GetPixel(x, y) != bmp2.GetPixel(x, y)) 28 | { 29 | return false; 30 | } 31 | } 32 | } 33 | return true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/FunctionObjects/SumOfTwoNumbersFunction.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Test.FunctionObjects 3 | { 4 | public class SumOfTwoNumbersFunction : RfcFunctionObject 5 | { 6 | public int First { get; private set; } 7 | public int Second { get; private set; } 8 | public int Total { get; private set; } 9 | 10 | public SumOfTwoNumbersFunction(int first, int second) 11 | { 12 | this.First = first; 13 | this.Second = second; 14 | } 15 | 16 | public override string FunctionName 17 | { 18 | get { return "Z_SSRT_SUM"; } 19 | } 20 | 21 | public override int GetOutput(RfcResult result) 22 | { 23 | return result.GetOutput("e_result"); 24 | } 25 | 26 | public override object Parameters 27 | { 28 | get 29 | { 30 | return new 31 | { 32 | i_num1 = this.First, 33 | i_num2 = this.Second 34 | }; 35 | } 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Guilherme Oenning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/base/SharpRfcCallException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc 7 | { 8 | public class SharpRfcCallException : SharpRfcException 9 | { 10 | public string RequestBody { get; private set; } 11 | 12 | public SharpRfcCallException(string message, string requestBody) 13 | : this(message, requestBody, null) 14 | { 15 | } 16 | 17 | public SharpRfcCallException(string message, Exception innerException) 18 | : this(message, null, innerException) 19 | { 20 | } 21 | 22 | public SharpRfcCallException(string message, string requestBody, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | this.RequestBody = requestBody; 26 | } 27 | 28 | public override string ToString() 29 | { 30 | if (string.IsNullOrEmpty(this.RequestBody)) 31 | return base.ToString(); 32 | 33 | return string.Format("{0} Request Body was: {1}", base.ToString(), this.RequestBody); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SharpSapRfc.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SharpSapRfc 5 | 2.0.11 6 | Sharp SAP RFC 7 | goenning 8 | goenning 9 | Easy to use, powerful, managed code for SAP RFC calls 10 | https://github.com/goenning/SharpSapRfc/blob/master/LICENSE 11 | https://github.com/goenning/SharpSapRfc 12 | false 13 | 14 | This package contains shared code for SharpSapRfc.Plain and SharpSapRfc.Soap. It does not have public API, you'll need Plain or Soap NuGet package. 15 | 16 | For code samples, please check the project site. https://github.com/goenning/SharpSapRfc 17 | 18 | sap rfc nco 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/FunctionObjects/DivideTwoNumbersFunction.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SharpSapRfc.Test.FunctionObjects 3 | { 4 | public class DivideTwoNumbersFunction : RfcFunctionObject 5 | { 6 | public int First { get; private set; } 7 | public int Second { get; private set; } 8 | public decimal Quotient { get; private set; } 9 | public int Remainder { get; private set; } 10 | 11 | public DivideTwoNumbersFunction(int first, int second) 12 | { 13 | this.First = first; 14 | this.Second = second; 15 | } 16 | 17 | public override string FunctionName 18 | { 19 | get { return "Z_SSRT_DIVIDE"; } 20 | } 21 | 22 | public override DivideTwoNumbersFunction GetOutput(RfcResult result) 23 | { 24 | this.Quotient = result.GetOutput("e_quotient"); 25 | this.Remainder = result.GetOutput("e_remainder"); 26 | return this; 27 | } 28 | 29 | public override object Parameters 30 | { 31 | get 32 | { 33 | return new 34 | { 35 | i_num1 = this.First, 36 | i_num2 = this.Second 37 | }; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SharpSapRfc.Soap.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SharpSapRfc.Soap 5 | 2.0.10 6 | Sharp SAP RFC - SOAP Interface 7 | goenning 8 | goenning 9 | Easy to use, powerful, managed code for SAP RFC calls 10 | https://github.com/goenning/SharpSapRfc/blob/master/LICENSE 11 | https://github.com/goenning/SharpSapRfc 12 | false 13 | 14 | SAP NCo 3 is an easy to use API. Sharp SAP RFC makes it even easier to call remote functions on SAP systems. This package uses HTTP protocol. You can also use SharpSapRfc.Plain if you are looking for RFC procotol. 15 | 16 | For code samples, please check the project site. https://github.com/goenning/SharpSapRfc 17 | 18 | sap rfc nco 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/plain/PlainRfcResult.cs: -------------------------------------------------------------------------------- 1 | using SAP.Middleware.Connector; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpSapRfc.Plain 5 | { 6 | public class PlainRfcResult : RfcResult 7 | { 8 | private IRfcFunction function; 9 | private PlainRfcStructureMapper structureMapper; 10 | 11 | internal PlainRfcResult(IRfcFunction function, PlainRfcStructureMapper structureMapper) 12 | { 13 | this.function = function; 14 | this.structureMapper = structureMapper; 15 | } 16 | 17 | public override T GetOutput(string name) 18 | { 19 | object returnValue = function.GetValue(name); 20 | if (returnValue is IRfcStructure) 21 | return this.structureMapper.FromStructure(returnValue as IRfcStructure); 22 | 23 | return (T)this.structureMapper.FromRemoteValue(typeof(T), returnValue); 24 | } 25 | 26 | public override IEnumerable GetTable(string name) 27 | { 28 | IRfcTable table = this.function.GetTable(name); 29 | List returnTable = new List(table.RowCount); 30 | for (int i = 0; i < table.RowCount; i++) 31 | returnTable.Add(this.structureMapper.FromStructure(table[i])); 32 | 33 | return returnTable; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/TestCases/ParallelTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace SharpSapRfc.Test.TestCases 7 | { 8 | public class Soap_ParallelTestCase : ParallelTestCase 9 | { 10 | protected override SapRfcConnection GetConnection() 11 | { 12 | return new SoapSapRfcConnection("TST-SOAP"); 13 | } 14 | } 15 | 16 | public class Plain_ParallelTestCase : ParallelTestCase 17 | { 18 | protected override SapRfcConnection GetConnection() 19 | { 20 | return new PlainSapRfcConnection("TST"); 21 | } 22 | } 23 | 24 | public abstract class ParallelTestCase 25 | { 26 | protected abstract SapRfcConnection GetConnection(); 27 | 28 | [Fact] 29 | public void TenTimesTest() 30 | { 31 | Parallel.For(1, 10, (int idx) => 32 | { 33 | using (SapRfcConnection conn = this.GetConnection()) 34 | { 35 | var result = conn.ExecuteFunction("Z_SSRT_SUM", 36 | new RfcParameter("i_num1", 2), 37 | new RfcParameter("i_num2", 4) 38 | ); 39 | } 40 | }); 41 | Task.WaitAll(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SharpSapRfc.Plain.x64.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SharpSapRfc.Plain.x64 5 | 2.0.10 6 | Sharp SAP RFC - RFC Interface x64 7 | goenning 8 | goenning 9 | Easy to use, powerful, managed code for SAP RFC calls 10 | https://github.com/goenning/SharpSapRfc/blob/master/LICENSE 11 | https://github.com/goenning/SharpSapRfc 12 | false 13 | 14 | SAP NCo 3 is an easy to use API. Sharp SAP RFC makes it even easier to call remote functions on SAP systems. This package uses RFC protocol. You can also use SharpSapRfc.Soap if you are looking for HTTP procotol. 15 | 16 | For code samples, please check the project site. https://github.com/goenning/SharpSapRfc 17 | 18 | sap rfc nco 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SharpSapRfc.Plain.x86.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SharpSapRfc.Plain.x86 5 | 2.0.10 6 | Sharp SAP RFC - RFC Interface x86 7 | goenning 8 | goenning 9 | Easy to use, powerful, managed code for SAP RFC calls 10 | https://github.com/goenning/SharpSapRfc/blob/master/LICENSE 11 | https://github.com/goenning/SharpSapRfc 12 | false 13 | 14 | SAP NCo 3 is an easy to use API. Sharp SAP RFC makes it even easier to call remote functions on SAP systems. This package uses RFC protocol. You can also use SharpSapRfc.Soap if you are looking for HTTP procotol. 15 | 16 | For code samples, please check the project site. https://github.com/goenning/SharpSapRfc 17 | 18 | sap rfc nco 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSapRfc")] 9 | [assembly: AssemblyProduct("SharpSapRfc")] 10 | [assembly: AssemblyCopyright("Copyright (c) 2014-2015 Guilherme Oenning")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("aa76e440-2138-46d0-99d6-1298ec604ac5")] 19 | 20 | // Version information for an assembly consists of the following four values: 21 | // 22 | // Major Version 23 | // Minor Version 24 | // Build Number 25 | // Revision 26 | // 27 | // You can specify all the values or you can default the Build and Revision Numbers 28 | // by using the '*' as shown below: 29 | // [assembly: AssemblyVersion("1.0.*")] 30 | [assembly: AssemblyVersion("2.0.9")] 31 | [assembly: AssemblyFileVersion("2.0.9")] -------------------------------------------------------------------------------- /src/base/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSapRfc")] 9 | [assembly: AssemblyProduct("SharpSapRfc")] 10 | [assembly: AssemblyCopyright("Copyright (c) 2014-2015 Guilherme Oenning")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("f8f318c9-d418-4348-9ac8-7c5a55158986")] 19 | 20 | // Version information for an assembly consists of the following four values: 21 | // 22 | // Major Version 23 | // Minor Version 24 | // Build Number 25 | // Revision 26 | // 27 | // You can specify all the values or you can default the Build and Revision Numbers 28 | // by using the '*' as shown below: 29 | // [assembly: AssemblyVersion("1.0.*")] 30 | [assembly: AssemblyVersion("2.0.10")] 31 | [assembly: AssemblyFileVersion("2.0.10")] -------------------------------------------------------------------------------- /src/plain/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSapRfc")] 9 | [assembly: AssemblyProduct("SharpSapRfc")] 10 | [assembly: AssemblyCopyright("Copyright (c) 2014-2015 Guilherme Oenning")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("240896ec-8419-4f59-aa08-0793aef08e66")] 19 | 20 | // Version information for an assembly consists of the following four values: 21 | // 22 | // Major Version 23 | // Minor Version 24 | // Build Number 25 | // Revision 26 | // 27 | // You can specify all the values or you can default the Build and Revision Numbers 28 | // by using the '*' as shown below: 29 | // [assembly: AssemblyVersion("1.0.*")] 30 | [assembly: AssemblyVersion("2.0.10")] 31 | [assembly: AssemblyFileVersion("2.0.10")] -------------------------------------------------------------------------------- /src/soap/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SharpSapRfc")] 9 | [assembly: AssemblyProduct("SharpSapRfc")] 10 | [assembly: AssemblyCopyright("Copyright (c) 2014-2015 Guilherme Oenning")] 11 | 12 | // Setting ComVisible to false makes the types in this assembly not visible 13 | // to COM components. If you need to access a type in this assembly from 14 | // COM, set the ComVisible attribute to true on that type. 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM 18 | [assembly: Guid("0878b98f-fdc7-48ea-a70d-3f5f82332278")] 19 | 20 | // Version information for an assembly consists of the following four values: 21 | // 22 | // Major Version 23 | // Minor Version 24 | // Build Number 25 | // Revision 26 | // 27 | // You can specify all the values or you can default the Build and Revision Numbers 28 | // by using the '*' as shown below: 29 | // [assembly: AssemblyVersion("1.0.*")] 30 | [assembly: AssemblyVersion("2.0.10")] 31 | [assembly: AssemblyFileVersion("2.0.10")] -------------------------------------------------------------------------------- /tests/TestCases/PreparedFunctionTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using Xunit; 4 | 5 | namespace SharpSapRfc.Test.TestCases 6 | { 7 | public class Soap_PreparedFunctionTestCase : PreparedFunctionTestCase 8 | { 9 | protected override SapRfcConnection GetConnection() 10 | { 11 | return new SoapSapRfcConnection("TST-SOAP"); 12 | } 13 | } 14 | 15 | public class Plain_PreparedFunctionTestCase : PreparedFunctionTestCase 16 | { 17 | protected override SapRfcConnection GetConnection() 18 | { 19 | return new PlainSapRfcConnection("TST"); 20 | } 21 | } 22 | 23 | public abstract class PreparedFunctionTestCase 24 | { 25 | protected abstract SapRfcConnection GetConnection(); 26 | 27 | [Fact] 28 | public void PreparedFunctionWithTwoParametersTest() 29 | { 30 | using (SapRfcConnection conn = GetConnection()) 31 | { 32 | var function = conn.PrepareFunction("Z_SSRT_SUM"); 33 | function.AddParameter(new RfcParameter("i_num1", 2)); 34 | function.AddParameter(new { i_num2 = 4 }); 35 | var result = function.Execute(); 36 | 37 | var total = result.GetOutput("e_result"); 38 | Assert.Equal(6, total); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/TestCases/AbapBoolTestCase.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using SAP.Middleware.Connector; 3 | using System; 4 | 5 | namespace SharpSapRfc.Test.TestCases 6 | { 7 | public class AbapBoolTestCase 8 | { 9 | [Fact] 10 | public void XToTrueTest() 11 | { 12 | Boolean boolean = AbapBool.FromString("X"); 13 | Assert.Equal(true, boolean); 14 | } 15 | 16 | [Fact] 17 | public void TrueToXTest() 18 | { 19 | string X = AbapBool.ToString(true); 20 | Assert.Equal("X", X); 21 | } 22 | 23 | [Fact] 24 | public void SpaceToFalseTest() 25 | { 26 | Boolean boolean = AbapBool.FromString(" "); 27 | Assert.Equal(false, boolean); 28 | } 29 | 30 | [Fact] 31 | public void FalseToSpaceTest() 32 | { 33 | string X = AbapBool.ToString(false); 34 | Assert.Equal(" ", X); 35 | } 36 | 37 | [Fact] 38 | public void EmptyToFalseTest() 39 | { 40 | Boolean boolean = AbapBool.FromString(""); 41 | Assert.Equal(false, boolean); 42 | } 43 | 44 | [Fact] 45 | public void UnknowToExceptionTest() 46 | { 47 | Assert.Throws(typeof(RfcMappingException), () => 48 | { 49 | AbapBool.FromString("A"); 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/TestCases/AccessDeniedTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace SharpSapRfc.Test.TestCases 10 | { 11 | public class Soap_AccessDeniedTestCase : AccessDeniedTestCase 12 | { 13 | protected override SapRfcConnection GetConnection() 14 | { 15 | return new SoapSapRfcConnection("TST_DENY_ACCESS-SOAP"); 16 | } 17 | } 18 | 19 | public class Plain_AccessDeniedTestCase : AccessDeniedTestCase 20 | { 21 | protected override SapRfcConnection GetConnection() 22 | { 23 | return new PlainSapRfcConnection("TST_DENY_ACCESS"); 24 | } 25 | } 26 | 27 | public abstract class AccessDeniedTestCase 28 | { 29 | protected abstract SapRfcConnection GetConnection(); 30 | 31 | [Fact] 32 | public void ShouldNotExecuteFunctionBecauseItIsNotAllowed() 33 | { 34 | using (SapRfcConnection conn = this.GetConnection()) 35 | { 36 | Assert.Throws(() => { 37 | var result = conn.ExecuteFunction("Z_SSRT_SUM", 38 | new RfcParameter("i_num1", 2), 39 | new RfcParameter("i_num2", 4) 40 | ); 41 | }); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/soap/AbapDataTypeParser.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Types; 2 | 3 | namespace SharpSapRfc.Soap 4 | { 5 | public static class AbapDataTypeParser 6 | { 7 | public static AbapDataType ParseFromTypeAttribute(string type, bool isSequence) 8 | { 9 | if (isSequence) 10 | return AbapDataType.TABLE; 11 | 12 | switch (type) 13 | { 14 | case "xsd:byte": 15 | case "xsd:base64Binary": 16 | return AbapDataType.BYTE; 17 | 18 | case "s0:date": 19 | return AbapDataType.DATE; 20 | 21 | case "s0:time": 22 | return AbapDataType.TIME; 23 | 24 | case "xsd:decimal": 25 | return AbapDataType.DECIMAL; 26 | 27 | case "xsd:string": 28 | return AbapDataType.CHAR; 29 | 30 | case "xsd:short": 31 | return AbapDataType.SHORT; 32 | 33 | case "xsd:double": 34 | case "xsd:float": 35 | return AbapDataType.DOUBLE; 36 | 37 | case "xsd:int": 38 | return AbapDataType.INTEGER; 39 | } 40 | 41 | if (type.StartsWith("s0:")) 42 | return AbapDataType.STRUCTURE; 43 | 44 | throw new SharpRfcException(string.Format("Could not map xml type attribute '{0}' to AbapDataType.", type)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/TestCases/WrongCredentialsTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace SharpSapRfc.Test.TestCases 10 | { 11 | public class Soap_WrongCredentialsTestCase : WrongCredentialsTestCase 12 | { 13 | protected override SapRfcConnection GetConnection() 14 | { 15 | return new SoapSapRfcConnection("TST_WRONG_CREDENTIALS-SOAP"); 16 | } 17 | } 18 | 19 | public class Plain_WrongCredentialsTestCase : WrongCredentialsTestCase 20 | { 21 | protected override SapRfcConnection GetConnection() 22 | { 23 | return new PlainSapRfcConnection("TST_WRONG_CREDENTIALS"); 24 | } 25 | } 26 | 27 | public abstract class WrongCredentialsTestCase 28 | { 29 | protected abstract SapRfcConnection GetConnection(); 30 | 31 | [Fact] 32 | public void ShouldNotExecuteFunctionBecauseItIsNotAllowed() 33 | { 34 | using (SapRfcConnection conn = this.GetConnection()) 35 | { 36 | Assert.Throws(() => 37 | { 38 | var result = conn.ExecuteFunction("Z_SSRT_SUM", 39 | new RfcParameter("i_num1", 2), 40 | new RfcParameter("i_num2", 4) 41 | ); 42 | }); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/base/RfcPreparedFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace SharpSapRfc 6 | { 7 | public abstract class RfcPreparedFunction 8 | { 9 | public string FunctionName { get; private set; } 10 | public IEnumerable Parameters 11 | { 12 | get { return this.parameters; } 13 | } 14 | 15 | private List parameters; 16 | 17 | public RfcPreparedFunction(string functionName) 18 | { 19 | this.FunctionName = functionName; 20 | this.parameters = new List(); 21 | } 22 | 23 | public abstract RfcPreparedFunction Prepare(); 24 | public abstract RfcResult Execute(); 25 | 26 | public RfcPreparedFunction AddParameter(RfcParameter parameter) 27 | { 28 | this.parameters.Add(parameter); 29 | return this; 30 | } 31 | 32 | public RfcPreparedFunction AddParameter(RfcParameter[] parameters) 33 | { 34 | this.parameters.AddRange(parameters); 35 | return this; 36 | } 37 | 38 | public RfcPreparedFunction AddParameter(object parameters) 39 | { 40 | if (parameters != null) 41 | { 42 | Type t = parameters.GetType(); 43 | PropertyInfo[] properties = t.GetProperties(); 44 | for (int i = 0; i < properties.Length; i++) 45 | this.parameters.Add(new RfcParameter(properties[i].Name, properties[i].GetValue(parameters, null))); 46 | } 47 | return this; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/base/Types/AbapDateTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace SharpSapRfc 5 | { 6 | public class AbapDateTime 7 | { 8 | private static CultureInfo enUS = new CultureInfo("en-US"); 9 | 10 | public static DateTime? FromString(string value) 11 | { 12 | return FromString(value, false); 13 | } 14 | 15 | public static DateTime? FromString(string value, bool acceptNull) 16 | { 17 | DateTime date; 18 | 19 | // ABAP Date and Time initial value 20 | if (value == "00000000" || value == "000000" || 21 | value == "00:00:00" || value == "0000-00-00") 22 | { 23 | if (acceptNull) 24 | return null; 25 | return DateTime.MinValue; 26 | } 27 | 28 | if (DateTime.TryParseExact(value, new string[] { "yyyy-MM-dd", "yyyyMMdd" }, enUS, DateTimeStyles.AssumeLocal, out date)) 29 | return date; 30 | 31 | if (DateTime.TryParseExact(value, new string[] { "HH:mm:ss", "HHmmss" }, enUS, DateTimeStyles.AssumeLocal, out date)) 32 | return DateTime.MinValue.Add(new TimeSpan(date.Hour, date.Minute, date.Second)); 33 | 34 | string message = string.Format("{0} is not a valid Date format.", value); 35 | throw new RfcMappingException(message); 36 | } 37 | 38 | public static string ToDateString(DateTime value) 39 | { 40 | return value.ToString("yyyyMMdd"); 41 | } 42 | 43 | public static string ToTimeString(DateTime value) 44 | { 45 | return value.ToString("HHmmss"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Mapper/AbapValueMapperFromRemoteExceptionTestData.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.Model; 4 | using SharpSapRfc.Types; 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Xunit; 10 | using Xunit.Extensions; 11 | 12 | namespace SharpSapRfc.Test.Mapper 13 | { 14 | public class Soap_AbapValueMapperFromRemoteExceptionTestData : AbapValueMapperFromRemoteExceptionTestData 15 | { 16 | } 17 | 18 | public class Plain_AbapValueMapperFromRemoteExceptionTestData : AbapValueMapperFromRemoteExceptionTestData 19 | { 20 | } 21 | 22 | public class AbapValueMapperFromRemoteExceptionTestData : IEnumerable 23 | { 24 | 25 | protected virtual List AdditionalTestData() 26 | { 27 | return new List(); 28 | } 29 | 30 | private List testData; 31 | public AbapValueMapperFromRemoteExceptionTestData() 32 | { 33 | this.testData = AdditionalTestData(); 34 | this.testData.AddRange(new[] { 35 | new object[] { typeof(MaterialState), 0 }, 36 | new object[] { typeof(MaterialState), "0000" }, 37 | new object[] { typeof(MaterialState), "" }, 38 | new object[] { typeof(MaterialState), "BLAH" } 39 | }); 40 | } 41 | 42 | public IEnumerator GetEnumerator() 43 | { 44 | return this.testData.GetEnumerator(); 45 | } 46 | 47 | IEnumerator IEnumerable.GetEnumerator() 48 | { 49 | return this.testData.GetEnumerator(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/TestCases/Soap_AbapValueMapperTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Soap; 2 | using SharpSapRfc.Test.Mapper; 3 | using SharpSapRfc.Types; 4 | using System; 5 | using Xunit; 6 | using Xunit.Extensions; 7 | 8 | namespace SharpSapRfc.Test.TestCases 9 | { 10 | public class Soap_AbapValueMapperTestCase 11 | { 12 | private SoapRfcValueMapper valueMapper; 13 | public Soap_AbapValueMapperTestCase() 14 | { 15 | this.valueMapper = new SoapRfcValueMapper(); 16 | } 17 | 18 | [Theory] 19 | [ClassData(typeof(Soap_AbapValueMapperFromRemoteTestData))] 20 | public void FromRemoteTest(object expected, Type expectedType, object remoteValue) 21 | { 22 | object result = this.valueMapper.FromRemoteValue(expectedType, remoteValue); 23 | if (result != null) 24 | Assert.IsType(expectedType, result); 25 | Assert.Equal(expected, result); 26 | } 27 | 28 | [Theory] 29 | [ClassData(typeof(Soap_AbapValueMapperFromRemoteExceptionTestData))] 30 | public void FromRemoteExceptionTest(Type expectedType, object remoteValue) 31 | { 32 | Assert.Throws(() => 33 | { 34 | this.valueMapper.FromRemoteValue(expectedType, remoteValue); 35 | }); 36 | } 37 | 38 | [Theory] 39 | [ClassData(typeof(Soap_AbapValueMapperToRemoteTestData))] 40 | public void ToRemoteValueTest(object expected, AbapDataType expectedType, object inputValue) 41 | { 42 | object result = this.valueMapper.ToRemoteValue(expectedType, inputValue); 43 | Assert.Equal(expected, result); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/soap/SoapSapRfcConnection.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using SharpSapRfc.Soap.Configuration; 3 | 4 | namespace SharpSapRfc.Soap 5 | { 6 | public class SoapSapRfcConnection : SapRfcConnection 7 | { 8 | public SapSoapRfcDestinationElement Destination { get; private set; } 9 | private SoapRfcWebClient _webClient; 10 | private SoapRfcMetadataCache metadataCache; 11 | 12 | private SoapRfcStructureMapper structureMapper; 13 | 14 | public SoapSapRfcConnection(string name) 15 | : this(SapSoapRfcConfigurationSection.GetConfiguration(name)) 16 | { 17 | } 18 | 19 | public SoapSapRfcConnection(SapSoapRfcDestinationElement destination) 20 | { 21 | this.Destination = destination; 22 | this._webClient = new SoapRfcWebClient(this.Destination); 23 | this.metadataCache = new SoapRfcMetadataCache(this._webClient); 24 | this.structureMapper = new SoapRfcStructureMapper(new SoapRfcValueMapper()); 25 | } 26 | 27 | public override RfcPreparedFunction PrepareFunction(string functionName) 28 | { 29 | FunctionMetadata metadata = this.metadataCache.GetFunctionMetadata(functionName); 30 | return new SoapRfcPreparedFunction(metadata, this.structureMapper, this._webClient); 31 | } 32 | 33 | public override void Dispose() 34 | { 35 | 36 | } 37 | 38 | public override RfcStructureMapper GetStructureMapper() 39 | { 40 | return this.structureMapper; 41 | } 42 | 43 | public override void SetTimeout(int timeout) 44 | { 45 | this.Destination.Timeout = timeout; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/TestCases/Plain_AbapValueMapperTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Test.Mapper; 3 | using SharpSapRfc.Types; 4 | using System; 5 | using Xunit; 6 | using Xunit.Extensions; 7 | 8 | namespace SharpSapRfc.Test.TestCases 9 | { 10 | public class Plain_AbapValueMapperTestCase 11 | { 12 | private PlainRfcValueMapper valueMapper; 13 | public Plain_AbapValueMapperTestCase() 14 | { 15 | this.valueMapper = new PlainRfcValueMapper(); 16 | } 17 | 18 | [Theory] 19 | [ClassData(typeof(Plain_AbapValueMapperFromRemoteTestData))] 20 | public void FromRemoteTest(object expected, Type expectedType, object remoteValue) 21 | { 22 | object result = this.valueMapper.FromRemoteValue(expectedType, remoteValue); 23 | if (result != null) 24 | Assert.IsType(expectedType, result); 25 | Assert.Equal(expected, result); 26 | } 27 | 28 | [Theory] 29 | [ClassData(typeof(Plain_AbapValueMapperFromRemoteExceptionTestData))] 30 | public void FromRemoteExceptionTest(Type expectedType, object remoteValue) 31 | { 32 | Assert.Throws(() => 33 | { 34 | this.valueMapper.FromRemoteValue(expectedType, remoteValue); 35 | }); 36 | } 37 | 38 | [Theory] 39 | [ClassData(typeof(Plain_AbapValueMapperToRemoteTestData))] 40 | public void ToRemoteValueTest(object expected, AbapDataType expectedType, object inputValue) 41 | { 42 | object result = this.valueMapper.ToRemoteValue(expectedType, inputValue); 43 | Assert.Equal(expected, result); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/base/Metadata/FunctionMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SharpSapRfc.Metadata 5 | { 6 | public class FunctionMetadata 7 | { 8 | public string Name { get; private set; } 9 | public ParameterMetadata[] InputParameters { get; private set; } 10 | public ParameterMetadata[] OutputParameters { get; private set; } 11 | 12 | public FunctionMetadata(string name, IEnumerable inputParameters, IEnumerable outputParameters) 13 | { 14 | this.Name = name; 15 | this.InputParameters = inputParameters.ToArray(); 16 | this.OutputParameters = outputParameters.ToArray(); 17 | } 18 | 19 | public ParameterMetadata GetInputParameter(int index) 20 | { 21 | return this.InputParameters[index]; 22 | } 23 | 24 | public ParameterMetadata GetInputParameter(string name) 25 | { 26 | var parameter = this.InputParameters.FirstOrDefault(x => x.Name == name.ToUpper()); 27 | if (parameter == null) 28 | throw new UnknownRfcParameterException(name, this.Name); 29 | return parameter; 30 | } 31 | 32 | public ParameterMetadata GetOutputParameter(int index) 33 | { 34 | return this.OutputParameters[index]; 35 | } 36 | 37 | public ParameterMetadata GetOutputParameter(string name) 38 | { 39 | var parameter = this.OutputParameters.FirstOrDefault(x => x.Name == name.ToUpper()); 40 | if (parameter == null) 41 | throw new UnknownRfcParameterException(name, this.Name); 42 | return parameter; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/TestCases/TimeoutTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace SharpSapRfc.Test.TestCases 10 | { 11 | public class Soap_TimeoutTestCase : TimeoutTestCase 12 | { 13 | protected override SapRfcConnection GetConnection() 14 | { 15 | return new SoapSapRfcConnection("TST-SOAP"); 16 | } 17 | 18 | [Fact] 19 | public void ShouldThrowTimeoutException() 20 | { 21 | using (SapRfcConnection conn = this.GetConnection()) 22 | { 23 | Exception ex = Assert.Throws(() => 24 | { 25 | var result = conn.ExecuteFunction("Z_SSRT_LONG_PROCESS", new 26 | { 27 | i_seconds = 10 28 | }); 29 | }); 30 | 31 | Assert.IsType(ex.InnerException); 32 | } 33 | } 34 | } 35 | 36 | public class Plain_TimeoutTestCase : TimeoutTestCase 37 | { 38 | protected override SapRfcConnection GetConnection() 39 | { 40 | return new PlainSapRfcConnection("TST"); 41 | } 42 | } 43 | 44 | public abstract class TimeoutTestCase 45 | { 46 | protected abstract SapRfcConnection GetConnection(); 47 | 48 | [Fact] 49 | public void ShouldNotThrowTimeoutException() 50 | { 51 | using (SapRfcConnection conn = this.GetConnection()) 52 | { 53 | var result = conn.ExecuteFunction("Z_SSRT_LONG_PROCESS", new 54 | { 55 | i_seconds = 1 56 | }); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/soap/SoapRfcResult.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using System.Collections.Generic; 3 | using System.Xml; 4 | 5 | namespace SharpSapRfc.Soap 6 | { 7 | public class SoapRfcResult : RfcResult 8 | { 9 | private SoapRfcStructureMapper structureMapper; 10 | private FunctionMetadata metadata; 11 | private XmlNode responseXml; 12 | 13 | public SoapRfcResult(FunctionMetadata metadata, XmlNode responseXml, SoapRfcStructureMapper structureMapper) 14 | { 15 | this.metadata = metadata; 16 | this.responseXml = responseXml; 17 | this.structureMapper = structureMapper; 18 | } 19 | 20 | public override T GetOutput(string name) 21 | { 22 | var parameter = this.metadata.GetOutputParameter(name); 23 | 24 | XmlNode node = this.responseXml.SelectSingleNode(string.Format("//{0}", name.ToUpper())); 25 | if (node != null) 26 | { 27 | if (node.HasChildNodes && node.FirstChild.HasChildNodes) 28 | return this.structureMapper.FromXml(parameter.StructureMetadata, node); 29 | 30 | return (T)this.structureMapper.FromRemoteValue(typeof(T), node.InnerText); 31 | } 32 | throw new RfcMappingException(string.Format("Could not find tag '{0}'", name)); 33 | } 34 | 35 | public override IEnumerable GetTable(string name) 36 | { 37 | var parameter = this.metadata.GetOutputParameter(name); 38 | 39 | XmlNodeList nodes = this.responseXml.SelectNodes(string.Format("//{0}/item", name.ToUpper())); 40 | List returnTable = new List(nodes.Count); 41 | for (int i = 0; i < nodes.Count; i++) 42 | returnTable.Add(this.structureMapper.FromXml(parameter.StructureMetadata, nodes[i])); 43 | 44 | return returnTable; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/soap/Configuration/SapSoapRfcDestinationElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | 4 | namespace SharpSapRfc.Soap.Configuration 5 | { 6 | public class SapSoapRfcDestinationElement : ConfigurationElement 7 | { 8 | [ConfigurationProperty("name", IsRequired = true)] 9 | public string Name 10 | { 11 | get { return this["name"] as string; } 12 | set { this["name"] = value; } 13 | } 14 | 15 | [ConfigurationProperty("rfcUrl", IsRequired = true)] 16 | public string RfcUrl 17 | { 18 | get { return this["rfcUrl"] as string; } 19 | set { this["rfcUrl"] = value; } 20 | } 21 | 22 | [ConfigurationProperty("wsdlUrl", IsRequired = true)] 23 | public string WsdlUrl 24 | { 25 | get { return this["wsdlUrl"] as string; } 26 | set { this["wsdlUrl"] = value; } 27 | } 28 | 29 | [ConfigurationProperty("client", IsRequired = true)] 30 | public string Client 31 | { 32 | get { return this["client"] as string; } 33 | set { this["client"] = value; } 34 | } 35 | 36 | [ConfigurationProperty("user", IsRequired = true)] 37 | public string User 38 | { 39 | get { return this["user"] as string; } 40 | set { this["user"] = value; } 41 | } 42 | 43 | [ConfigurationProperty("password", IsRequired = true)] 44 | public string Password 45 | { 46 | get { return this["password"] as string; } 47 | set { this["password"] = value; } 48 | } 49 | 50 | [ConfigurationProperty("timeout", IsRequired = false, DefaultValue=30000)] 51 | public int Timeout 52 | { 53 | get { return (int)this["timeout"]; } 54 | set { this["timeout"] = value; } 55 | } 56 | 57 | public override bool IsReadOnly() 58 | { 59 | return false; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/soap/Configuration/SapSoapRfcConfigurationSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | 5 | namespace SharpSapRfc.Soap.Configuration 6 | { 7 | public class SapSoapRfcConfigurationSection : ConfigurationSection 8 | { 9 | private static object _syncObject = new object(); 10 | private static IDictionary destinationByName = new Dictionary(); 11 | 12 | public static SapSoapRfcDestinationElement GetConfiguration(string name) 13 | { 14 | if (destinationByName.ContainsKey(name)) 15 | return destinationByName[name]; 16 | 17 | lock(_syncObject) 18 | { 19 | if (destinationByName.ContainsKey(name)) 20 | return destinationByName[name]; 21 | 22 | SapSoapRfcConfigurationSection section = ConfigurationManager.GetSection("sapSoapRfc") as SapSoapRfcConfigurationSection; 23 | if (section != null) 24 | { 25 | foreach (SapSoapRfcDestinationElement destination in section.Destinations.Elements) 26 | { 27 | if (name.Equals(destination.Name)) 28 | { 29 | destinationByName.Add(name, destination); 30 | return destinationByName[name]; 31 | } 32 | } 33 | } 34 | } 35 | 36 | 37 | throw new SharpRfcException(string.Format("Could not find configuration for destination named '{0}' under sapSoapRfc section", name)); 38 | } 39 | 40 | [ConfigurationProperty("destinations")] 41 | [ConfigurationCollection(typeof(SapSoapRfcDestinationCollection), AddItemName = "add")] 42 | public SapSoapRfcDestinationCollection Destinations 43 | { 44 | get { return (SapSoapRfcDestinationCollection)base["destinations"]; } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/TestCases/FunctionObjectTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.FunctionObjects; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace SharpSapRfc.Test.TestCases 11 | { 12 | public class Soap_FunctionObjectTestCase : FunctionObjectTestCase 13 | { 14 | protected override SapRfcConnection GetConnection() 15 | { 16 | return new SoapSapRfcConnection("TST-SOAP"); 17 | } 18 | } 19 | 20 | public class Plain_FunctionObjectTestCase : FunctionObjectTestCase 21 | { 22 | protected override SapRfcConnection GetConnection() 23 | { 24 | return new PlainSapRfcConnection("TST"); 25 | } 26 | } 27 | 28 | public abstract class FunctionObjectTestCase 29 | { 30 | protected abstract SapRfcConnection GetConnection(); 31 | 32 | [Fact] 33 | public void ObjectFunctionTest() 34 | { 35 | using (SapRfcConnection conn = GetConnection()) 36 | { 37 | var result = conn.ExecuteFunction(new DivideTwoNumbersFunction(9, 2)); 38 | Assert.Equal(4.5m, result.Quotient); 39 | Assert.Equal(1, result.Remainder); 40 | } 41 | } 42 | 43 | [Fact] 44 | public void ObjectFunctionWithSingleReturnValueTest() 45 | { 46 | using (SapRfcConnection conn = GetConnection()) 47 | { 48 | int value = conn.ExecuteFunction(new SumOfTwoNumbersFunction(2, 8)); 49 | Assert.Equal(10, value); 50 | } 51 | } 52 | 53 | [Fact] 54 | public void ObjectFunctionWithTableReturnValueTest() 55 | { 56 | using (SapRfcConnection conn = GetConnection()) 57 | { 58 | var customers = conn.ExecuteFunction(new GetAllCustomersFunction()); 59 | Assert.Equal(2, customers.Count()); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/base/RfcMetadataCache.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpSapRfc 5 | { 6 | public abstract class RfcMetadataCache 7 | { 8 | private static Dictionary _functionMetadataCache = new Dictionary(); 9 | private static Dictionary _structureMetadataCache = new Dictionary(); 10 | private static object _syncObject = new object(); 11 | 12 | public FunctionMetadata GetFunctionMetadata(string functionName) 13 | { 14 | if (_functionMetadataCache.ContainsKey(functionName)) 15 | return _functionMetadataCache[functionName]; 16 | 17 | lock (_syncObject) 18 | { 19 | if (_functionMetadataCache.ContainsKey(functionName)) 20 | return _functionMetadataCache[functionName]; 21 | 22 | var functionMetadata = this.LoadFunctionMetadata(functionName); 23 | _functionMetadataCache.Add(functionName, functionMetadata); 24 | return functionMetadata; 25 | } 26 | } 27 | 28 | protected StructureMetadata GetStructureMetadata(string structureName) 29 | { 30 | if (_structureMetadataCache.ContainsKey(structureName)) 31 | return _structureMetadataCache[structureName]; 32 | 33 | lock (_syncObject) 34 | { 35 | if (_structureMetadataCache.ContainsKey(structureName)) 36 | return _structureMetadataCache[structureName]; 37 | 38 | var structureMetadata = this.LoadStructureMetadata(structureName); 39 | _structureMetadataCache.Add(structureName, structureMetadata); 40 | return structureMetadata; 41 | } 42 | } 43 | 44 | protected abstract FunctionMetadata LoadFunctionMetadata(string functionName); 45 | protected abstract StructureMetadata LoadStructureMetadata(string structureName); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 49 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/plain/PlainSapRfcConnection.cs: -------------------------------------------------------------------------------- 1 | using SAP.Middleware.Connector; 2 | using System; 3 | 4 | namespace SharpSapRfc.Plain 5 | { 6 | public class PlainSapRfcConnection : SapRfcConnection 7 | { 8 | public RfcRepository Repository 9 | { 10 | get { 11 | this.EnsureConnectionIsOpen(); 12 | return this.repository; 13 | } 14 | } 15 | 16 | public RfcDestination Destination 17 | { 18 | get 19 | { 20 | this.EnsureConnectionIsOpen(); 21 | return this.destination; 22 | } 23 | } 24 | 25 | public PlainSapRfcConnection(string destinationName) 26 | { 27 | this.destinationName = destinationName; 28 | this.structureMapper = new PlainRfcStructureMapper(new PlainRfcValueMapper()); 29 | } 30 | 31 | private void EnsureConnectionIsOpen() 32 | { 33 | if (!isOpen) 34 | { 35 | try { 36 | this.destination = RfcDestinationManager.GetDestination(destinationName); 37 | this.repository = this.destination.Repository; 38 | this.isOpen = true; 39 | } 40 | catch (Exception ex) 41 | { 42 | throw new SharpRfcCallException("Could not connect to SAP.", ex); 43 | } 44 | } 45 | } 46 | 47 | public override RfcPreparedFunction PrepareFunction(string functionName) 48 | { 49 | EnsureConnectionIsOpen(); 50 | return new PlainRfcPreparedFunction(functionName, this.structureMapper, this.Repository, this.Destination); 51 | } 52 | 53 | public override void Dispose() 54 | { 55 | 56 | } 57 | 58 | private string destinationName; 59 | private bool isOpen = false; 60 | private RfcRepository repository; 61 | private RfcDestination destination; 62 | private PlainRfcStructureMapper structureMapper; 63 | 64 | public override RfcStructureMapper GetStructureMapper() 65 | { 66 | return this.structureMapper; 67 | } 68 | 69 | public override void SetTimeout(int timeout) 70 | { 71 | //there is no timeout for plain rfc 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/plain/Extension/RfcElementMetadataExtension.cs: -------------------------------------------------------------------------------- 1 | using SAP.Middleware.Connector; 2 | using SharpSapRfc.Types; 3 | 4 | namespace SharpSapRfc.Plain 5 | { 6 | public static class RfcElementMetadataExtension 7 | { 8 | public static AbapDataType GetAbapDataType(this RfcElementMetadata metadata) 9 | { 10 | switch (metadata.DataType) 11 | { 12 | case RfcDataType.CHAR: 13 | case RfcDataType.STRING: 14 | return AbapDataType.CHAR; 15 | 16 | case RfcDataType.DATE: 17 | return AbapDataType.DATE; 18 | 19 | case RfcDataType.BCD: 20 | return AbapDataType.DECIMAL; 21 | 22 | case RfcDataType.FLOAT: 23 | return AbapDataType.DOUBLE; 24 | 25 | case RfcDataType.INT1: 26 | return AbapDataType.BYTE; 27 | 28 | case RfcDataType.INT2: 29 | return AbapDataType.SHORT; 30 | 31 | case RfcDataType.INT4: 32 | return AbapDataType.INTEGER; 33 | 34 | case RfcDataType.NUM: 35 | return AbapDataType.NUMERIC; 36 | 37 | case RfcDataType.STRUCTURE: 38 | return AbapDataType.STRUCTURE; 39 | 40 | case RfcDataType.TABLE: 41 | return AbapDataType.TABLE; 42 | 43 | case RfcDataType.TIME: 44 | return AbapDataType.TIME; 45 | 46 | case RfcDataType.BYTE: 47 | case RfcDataType.XSTRING: 48 | return AbapDataType.BYTE; 49 | 50 | case RfcDataType.CDAY: 51 | case RfcDataType.CLASS: 52 | case RfcDataType.DTDAY: 53 | case RfcDataType.DTMONTH: 54 | case RfcDataType.DTWEEK: 55 | case RfcDataType.TMINUTE: 56 | case RfcDataType.TSECOND: 57 | case RfcDataType.UNKNOWN: 58 | case RfcDataType.UTCLONG: 59 | case RfcDataType.UTCMINUTE: 60 | case RfcDataType.UTCSECOND: 61 | default: 62 | throw new SharpRfcException(string.Format("Could not map RfcDataType '{0}' to AbapDataType", metadata.DataType.ToString())); 63 | 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/base/SapRfcConnection.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Structure; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace SharpSapRfc 6 | { 7 | public abstract class SapRfcConnection : IDisposable 8 | { 9 | public abstract RfcStructureMapper GetStructureMapper(); 10 | public abstract RfcPreparedFunction PrepareFunction(string functionName); 11 | public abstract void Dispose(); 12 | public abstract void SetTimeout(int timeout); 13 | 14 | public RfcResult ExecuteFunction(string functionName) 15 | { 16 | return this.PrepareFunction(functionName) 17 | .Execute(); 18 | } 19 | 20 | public RfcResult ExecuteFunction(string functionName, object parameters) 21 | { 22 | return this.PrepareFunction(functionName) 23 | .AddParameter(parameters) 24 | .Execute(); 25 | } 26 | 27 | public RfcResult ExecuteFunction(string functionName, params RfcParameter[] parameters) 28 | { 29 | return this.PrepareFunction(functionName) 30 | .AddParameter(parameters) 31 | .Execute(); 32 | } 33 | 34 | public IEnumerable ReadTable(string tableName, string[] fields = null, string[] where = null, int skip = 0, int count = 0) 35 | { 36 | fields = fields ?? new string[0]; 37 | where = where ?? new string[0]; 38 | 39 | List dbFields = new List(); 40 | for (int i = 0; i < fields.Length; i++) 41 | dbFields.Add(new RfcDbField(fields[i])); 42 | 43 | List dbWhere = new List(); 44 | for (int i = 0; i < where.Length; i++) 45 | dbWhere.Add(new RfcDbWhere(where[i])); 46 | 47 | var result = this.ExecuteFunction("RFC_READ_TABLE", new 48 | { 49 | Query_Table = tableName, 50 | Fields = dbFields, 51 | Options = dbWhere, 52 | RowSkips = skip, 53 | RowCount = count 54 | }); 55 | 56 | return this.GetStructureMapper().FromRfcReadTableToList( 57 | result.GetTable("DATA"), 58 | result.GetTable("FIELDS") 59 | ); 60 | } 61 | 62 | public RfcReadTableQueryBuilder Table(string tableName) 63 | { 64 | return new RfcReadTableQueryBuilder(this, tableName); 65 | } 66 | 67 | public T ExecuteFunction(RfcFunctionObject function) 68 | { 69 | var result = this.ExecuteFunction(function.FunctionName, function.Parameters); 70 | return function.GetOutput(result); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/TestCases/Soap_ExceptionDetailsTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using Xunit; 8 | 9 | namespace SharpSapRfc.Test.TestCases 10 | { 11 | public class Soap_ExceptionDetailsTestCase : ExceptionDetailsTestCase 12 | { 13 | protected override SapRfcConnection GetConnection() 14 | { 15 | return new SoapSapRfcConnection("TST-SOAP"); 16 | } 17 | 18 | protected override string GetExpectedRequestBody() 19 | { 20 | return @"20"; 21 | } 22 | } 23 | 24 | public class Plain_ExceptionDetailsTestCase : ExceptionDetailsTestCase 25 | { 26 | protected override SapRfcConnection GetConnection() 27 | { 28 | return new PlainSapRfcConnection("TST"); 29 | } 30 | 31 | protected override string GetExpectedRequestBody() 32 | { 33 | return @"FUNCTION Z_SSRT_DIVIDE (EXPORT PARAMETER E_QUOTIENT=0.00, EXPORT PARAMETER E_REMAINDER=0, IMPORT PARAMETER I_NUM1=2.00, IMPORT PARAMETER I_NUM2=0.00)"; 34 | } 35 | } 36 | 37 | public abstract class ExceptionDetailsTestCase 38 | { 39 | protected abstract SapRfcConnection GetConnection(); 40 | protected abstract string GetExpectedRequestBody(); 41 | 42 | [Fact] 43 | public void ShouldReturnRequestBodyOnExceptionTest() 44 | { 45 | using (SapRfcConnection conn = this.GetConnection()) 46 | { 47 | 48 | SharpRfcCallException ex = Assert.Throws(() => 49 | { 50 | var result = conn.ExecuteFunction("Z_SSRT_DIVIDE", new 51 | { 52 | i_num1 = 2, 53 | i_num2 = 0 54 | }); 55 | }); 56 | 57 | Assert.Equal(this.GetExpectedRequestBody(), ex.RequestBody); 58 | } 59 | } 60 | [Fact] 61 | public void ExceptionToStringShouldReturnRequestBodyTest() 62 | { 63 | using (SapRfcConnection conn = this.GetConnection()) 64 | { 65 | 66 | SharpRfcCallException ex = Assert.Throws(() => 67 | { 68 | var result = conn.ExecuteFunction("Z_SSRT_DIVIDE", new 69 | { 70 | i_num1 = 2, 71 | i_num2 = 0 72 | }); 73 | }); 74 | 75 | Assert.Contains(this.GetExpectedRequestBody(), ex.ToString()); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/soap/SoapRfcStructureMapper.cs: -------------------------------------------------------------------------------- 1 | 2 | using SharpSapRfc.Metadata; 3 | using System; 4 | using System.Reflection; 5 | using System.Xml; 6 | 7 | namespace SharpSapRfc.Soap 8 | { 9 | public class SoapRfcStructureMapper : RfcStructureMapper 10 | { 11 | public SoapRfcStructureMapper(RfcValueMapper valueMapper) 12 | : base(valueMapper) 13 | { 14 | 15 | } 16 | 17 | public T FromXml(StructureMetadata metadata, XmlNode node) 18 | { 19 | Type type = typeof(T); 20 | EnsureTypeIsCached(type); 21 | 22 | T returnObject = default(T); 23 | if (!type.Equals(typeof(string))) 24 | returnObject = Activator.CreateInstance(); 25 | 26 | if (metadata == null) 27 | return (T)this.FromRemoteValue(type, node.InnerText); 28 | 29 | foreach (var field in metadata.Fields) 30 | { 31 | string fieldName = field.Name; 32 | XmlNode valueNode = node.SelectSingleNode(fieldName); 33 | PropertyInfo property = null; 34 | if (typeProperties[type].TryGetValue(fieldName.ToLower(), out property)) 35 | SetProperty(returnObject, property, valueNode.InnerText); 36 | } 37 | return returnObject; 38 | } 39 | 40 | public XmlNode FromStructure(XmlDocument body, string nodeName, ParameterMetadata param, object parameterObject) 41 | { 42 | XmlNode node = body.CreateElement(nodeName); 43 | Type type = parameterObject.GetType(); 44 | EnsureTypeIsCached(type); 45 | 46 | if (param.StructureMetadata == null) 47 | { 48 | node.InnerText = this.ToRemoteValue(param.DataType, parameterObject).ToString(); 49 | } 50 | else 51 | { 52 | foreach (var field in param.StructureMetadata.Fields) 53 | { 54 | XmlElement parameterNode = body.CreateElement(field.Name); 55 | PropertyInfo property = null; 56 | 57 | object formattedValue = null; 58 | if (typeProperties[type].TryGetValue(field.Name.ToLower(), out property)) 59 | { 60 | object value = property.GetValue(parameterObject, null); 61 | formattedValue = this.ToRemoteValue(field.DataType, value); 62 | } 63 | else if (string.IsNullOrEmpty(field.Name)) 64 | formattedValue = this.ToRemoteValue(field.DataType, parameterObject); 65 | 66 | if (formattedValue != null) 67 | { 68 | parameterNode.InnerText = (formattedValue ?? string.Empty).ToString(); 69 | if (!string.IsNullOrEmpty(parameterNode.InnerText)) 70 | node.AppendChild(parameterNode); 71 | } 72 | } 73 | } 74 | return node; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Metadata/StructureMetadataTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using SharpSapRfc.Plain; 3 | using SharpSapRfc.Soap; 4 | using SharpSapRfc.Types; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using Xunit; 10 | 11 | namespace SharpSapRfc.Test.Metadata 12 | { 13 | public class Soap_StructureMetadataTestCase : StructureMetadataTestCase 14 | { 15 | private SoapSapRfcConnection conn; 16 | 17 | public override RfcMetadataCache GetMetadataCache() 18 | { 19 | this.conn = new SoapSapRfcConnection("TST-SOAP"); 20 | SoapRfcWebClient webClient = new SoapRfcWebClient(this.conn.Destination); 21 | return new SoapRfcMetadataCache(webClient); 22 | } 23 | 24 | public override void Dispose() 25 | { 26 | this.conn.Dispose(); 27 | } 28 | } 29 | 30 | public class Plain_StructureMetadataTestCase : StructureMetadataTestCase 31 | { 32 | private PlainSapRfcConnection conn; 33 | 34 | public override RfcMetadataCache GetMetadataCache() 35 | { 36 | this.conn = new PlainSapRfcConnection("TST"); 37 | return new PlainRfcMetadataCache(this.conn); 38 | } 39 | 40 | public override void Dispose() 41 | { 42 | this.conn.Dispose(); 43 | } 44 | } 45 | 46 | public abstract class StructureMetadataTestCase : IDisposable 47 | { 48 | public abstract RfcMetadataCache GetMetadataCache(); 49 | 50 | [Fact] 51 | public void InputStructureMetadataTest() 52 | { 53 | var cache = GetMetadataCache(); 54 | var metadata = cache.GetFunctionMetadata("Z_SSRT_IN_OUT"); 55 | 56 | var parameter = metadata.GetInputParameter("I_MARA"); 57 | Assert.NotNull(parameter.StructureMetadata); 58 | 59 | AssertStructureMetadataField(parameter.StructureMetadata, "MANDT", AbapDataType.CHAR); 60 | AssertStructureMetadataField(parameter.StructureMetadata, "ID", AbapDataType.INTEGER); 61 | AssertStructureMetadataField(parameter.StructureMetadata, "NAME", AbapDataType.CHAR); 62 | AssertStructureMetadataField(parameter.StructureMetadata, "PRICE", AbapDataType.DECIMAL); 63 | AssertStructureMetadataField(parameter.StructureMetadata, "DATUM", AbapDataType.DATE); 64 | AssertStructureMetadataField(parameter.StructureMetadata, "UZEIT", AbapDataType.TIME); 65 | AssertStructureMetadataField(parameter.StructureMetadata, "ACTIVE", AbapDataType.CHAR); 66 | AssertStructureMetadataField(parameter.StructureMetadata, "STATE", AbapDataType.SHORT); 67 | } 68 | 69 | private void AssertStructureMetadataField(StructureMetadata metadata, string name, AbapDataType dataType) 70 | { 71 | var field = metadata.GetField(name); 72 | Assert.Equal(name, field.Name); 73 | Assert.Equal(dataType, field.DataType); 74 | } 75 | 76 | public abstract void Dispose(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/TestCases/ConnectionTestCase.cs: -------------------------------------------------------------------------------- 1 | using SAP.Middleware.Connector; 2 | using SharpSapRfc.Plain; 3 | using SharpSapRfc.Soap; 4 | using SharpSapRfc.Soap.Configuration; 5 | using SharpSapRfc.Test.Model; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using Xunit; 11 | 12 | namespace SharpSapRfc.Test.TestCases 13 | { 14 | public class Soap_ConnectionTestCase : ConnectionTestCase 15 | { 16 | protected override SapRfcConnection GetConnection() 17 | { 18 | SapSoapRfcDestinationElement cfg = new SapSoapRfcDestinationElement(); 19 | cfg.Client = "001"; 20 | cfg.Name = "CUSTOM-SOAP-CONN"; 21 | cfg.RfcUrl = "http://sap-vm:8000/sap/bc/soap/rfc"; 22 | cfg.WsdlUrl = "http://sap-vm:8000/sap/bc/soap/wsdl"; 23 | cfg.User = "rfc_super"; 24 | cfg.Password = "rfcsuper1"; 25 | cfg.Timeout = 5000; 26 | return new SoapSapRfcConnection(cfg); 27 | } 28 | } 29 | 30 | public class Plain_ConnectionTestCase : ConnectionTestCase, IDisposable 31 | { 32 | public class CustonDestinationConfiguration : IDestinationConfiguration 33 | { 34 | public bool ChangeEventsSupported() 35 | { 36 | return false; 37 | } 38 | 39 | public RfcConfigParameters GetParameters(string destinationName) 40 | { 41 | if (destinationName == "CUSTOM-PLAIN-CONN") 42 | { 43 | var cfg = new RfcConfigParameters(); 44 | cfg.Add(RfcConfigParameters.Name, "CUSTOM-PLAIN-CONN"); 45 | cfg.Add(RfcConfigParameters.User, "rfc_super"); 46 | cfg.Add(RfcConfigParameters.Password, "rfcsuper1"); 47 | cfg.Add(RfcConfigParameters.Client, "001"); 48 | cfg.Add(RfcConfigParameters.SystemNumber, "00"); 49 | cfg.Add(RfcConfigParameters.AppServerHost, "sap-vm"); 50 | cfg.Add(RfcConfigParameters.Language, "EN"); 51 | return cfg; 52 | } 53 | return null; 54 | } 55 | 56 | public event RfcDestinationManager.ConfigurationChangeHandler ConfigurationChanged; 57 | } 58 | 59 | private CustonDestinationConfiguration cfg; 60 | public Plain_ConnectionTestCase() 61 | { 62 | this.cfg = new CustonDestinationConfiguration(); 63 | } 64 | 65 | protected override SapRfcConnection GetConnection() 66 | { 67 | RfcDestinationManager.RegisterDestinationConfiguration(this.cfg); 68 | return new PlainSapRfcConnection("CUSTOM-PLAIN-CONN"); 69 | } 70 | 71 | public void Dispose() 72 | { 73 | RfcDestinationManager.UnregisterDestinationConfiguration(this.cfg); 74 | } 75 | } 76 | 77 | public abstract class ConnectionTestCase 78 | { 79 | protected abstract SapRfcConnection GetConnection(); 80 | 81 | [Fact] 82 | public void ObjectFunctionTest() 83 | { 84 | using (SapRfcConnection conn = GetConnection()) 85 | { 86 | var scarr = conn.ReadTable("SCARR"); 87 | Assert.NotEqual(0, scarr.Count()); 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/plain/PlainRfcMetadataCache.cs: -------------------------------------------------------------------------------- 1 | using SAP.Middleware.Connector; 2 | using SharpSapRfc.Metadata; 3 | using SharpSapRfc.Types; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace SharpSapRfc.Plain 10 | { 11 | public class PlainRfcMetadataCache : RfcMetadataCache 12 | { 13 | private PlainSapRfcConnection connection; 14 | public PlainRfcMetadataCache(PlainSapRfcConnection connection) 15 | { 16 | this.connection = connection; 17 | } 18 | 19 | protected override FunctionMetadata LoadFunctionMetadata(string functionName) 20 | { 21 | var remoteFunctionMetadata = this.connection.Repository.GetFunctionMetadata(functionName); 22 | 23 | List inputParameters = new List(); 24 | List outputParameters = new List(); 25 | 26 | for (int i = 0; i < remoteFunctionMetadata.ParameterCount; i++) 27 | { 28 | StructureMetadata structureMetadata = null; 29 | if (remoteFunctionMetadata[i].DataType == RfcDataType.STRUCTURE) 30 | { 31 | string structureName = remoteFunctionMetadata[i].ValueMetadataAsStructureMetadata.Name; 32 | structureMetadata = this.GetStructureMetadata(structureName); 33 | } 34 | else if (remoteFunctionMetadata[i].DataType == RfcDataType.STRUCTURE) 35 | { 36 | string structureName = remoteFunctionMetadata[i].ValueMetadataAsTableMetadata.LineType.Name; 37 | structureMetadata = this.GetStructureMetadata(structureName); 38 | } 39 | 40 | var parameter = new ParameterMetadata(remoteFunctionMetadata[i].Name, remoteFunctionMetadata[i].GetAbapDataType(), structureMetadata); 41 | switch (remoteFunctionMetadata[i].Direction) 42 | { 43 | case RfcDirection.EXPORT: 44 | outputParameters.Add(parameter); 45 | break; 46 | case RfcDirection.IMPORT: 47 | inputParameters.Add(parameter); 48 | break; 49 | case RfcDirection.TABLES: 50 | case RfcDirection.CHANGING: 51 | inputParameters.Add(parameter); 52 | outputParameters.Add(parameter); 53 | break; 54 | default: 55 | break; 56 | } 57 | } 58 | 59 | return new FunctionMetadata(functionName, inputParameters, outputParameters); 60 | } 61 | 62 | protected override StructureMetadata LoadStructureMetadata(string structureName) 63 | { 64 | var remoteStructureMetadata = this.connection.Repository.GetStructureMetadata(structureName); 65 | 66 | List fields = new List(); 67 | for (int i = 0; i < remoteStructureMetadata.FieldCount; i++) 68 | { 69 | string fieldName = remoteStructureMetadata[i].Name; 70 | AbapDataType fieldType = remoteStructureMetadata[i].GetAbapDataType(); 71 | fields.Add(new FieldMetadata(fieldName, fieldType)); 72 | } 73 | 74 | return new StructureMetadata(structureName, fields); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Mapper/AbapValueMapperToRemoteTestData.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.Model; 4 | using SharpSapRfc.Types; 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Xunit; 10 | using Xunit.Extensions; 11 | 12 | namespace SharpSapRfc.Test.Mapper 13 | { 14 | public class Soap_AbapValueMapperToRemoteTestData : AbapValueMapperToRemoteTestData 15 | { 16 | protected override List AdditionalTestData() 17 | { 18 | return new List { 19 | new object[] { "56.2", AbapDataType.DECIMAL, 56.2m }, 20 | }; 21 | } 22 | } 23 | 24 | public class Plain_AbapValueMapperToRemoteTestData : AbapValueMapperToRemoteTestData 25 | { 26 | protected override List AdditionalTestData() 27 | { 28 | return new List { 29 | new object[] { "56.2", AbapDataType.DECIMAL, 56.2m }, 30 | }; 31 | } 32 | } 33 | 34 | public class AbapValueMapperToRemoteTestData : IEnumerable 35 | { 36 | 37 | protected virtual List AdditionalTestData() 38 | { 39 | return new List(); 40 | } 41 | 42 | private List testData; 43 | public AbapValueMapperToRemoteTestData() 44 | { 45 | this.testData = AdditionalTestData(); 46 | this.testData.AddRange(new[] { 47 | new object[] { "X", AbapDataType.CHAR, true }, 48 | new object[] { " ", AbapDataType.CHAR, false }, 49 | 50 | new object[] { "My test", AbapDataType.CHAR, "My test" }, 51 | 52 | new object[] { "12424", AbapDataType.DECIMAL, 12424m }, 53 | new object[] { "464", AbapDataType.DECIMAL, 464m }, 54 | 55 | new object[] { "20140406", AbapDataType.DATE, new DateTime(2014, 4, 6) }, 56 | new object[] { "124253", AbapDataType.TIME, new DateTime(2014, 4, 6, 12, 42, 53) }, 57 | 58 | new object[] { "AVAL", AbapDataType.CHAR, MaterialState.Available }, 59 | new object[] { "BLOK", AbapDataType.CHAR, MaterialState.Blocked }, 60 | new object[] { "OOS", AbapDataType.CHAR, MaterialState.OutOfStock }, 61 | new object[] { "", AbapDataType.CHAR, default(MaterialState) }, 62 | 63 | new object[] { "1", AbapDataType.NUMERIC, MaterialState.Available }, 64 | new object[] { "2", AbapDataType.NUMERIC, MaterialState.Blocked }, 65 | new object[] { "3", AbapDataType.NUMERIC, MaterialState.OutOfStock }, 66 | new object[] { "0", AbapDataType.NUMERIC, default(MaterialState) }, 67 | 68 | new object[] { 1, AbapDataType.INTEGER, MaterialState.Available }, 69 | new object[] { 2, AbapDataType.INTEGER, MaterialState.Blocked }, 70 | new object[] { 3, AbapDataType.INTEGER, MaterialState.OutOfStock }, 71 | new object[] { 0, AbapDataType.INTEGER, default(MaterialState) } 72 | }); 73 | } 74 | 75 | public IEnumerator GetEnumerator() 76 | { 77 | return this.testData.GetEnumerator(); 78 | } 79 | 80 | IEnumerator IEnumerable.GetEnumerator() 81 | { 82 | return this.testData.GetEnumerator(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/plain/PlainRfcStructureMapper.cs: -------------------------------------------------------------------------------- 1 | 2 | using SAP.Middleware.Connector; 3 | using System; 4 | using System.Collections; 5 | using System.Reflection; 6 | 7 | namespace SharpSapRfc.Plain 8 | { 9 | public class PlainRfcStructureMapper : RfcStructureMapper 10 | { 11 | public PlainRfcStructureMapper(RfcValueMapper valueMapper) 12 | : base(valueMapper) 13 | { 14 | 15 | } 16 | 17 | public IRfcTable CreateTable(RfcTableMetadata metadata, object parameterObject) 18 | { 19 | IRfcTable table = metadata.CreateTable(); 20 | RfcStructureMetadata structureMetadata = metadata.LineType; 21 | 22 | IEnumerable enumerable = parameterObject as IEnumerable; 23 | if (enumerable == null) 24 | { 25 | IRfcStructure row = CreateStructure(structureMetadata, parameterObject); 26 | table.Append(row); 27 | } 28 | else 29 | { 30 | var enumerator = enumerable.GetEnumerator(); 31 | while (enumerator.MoveNext()) 32 | { 33 | object current = enumerator.Current; 34 | IRfcStructure row = CreateStructure(structureMetadata, current); 35 | table.Append(row); 36 | } 37 | } 38 | return table; 39 | } 40 | 41 | public IRfcStructure CreateStructure(RfcStructureMetadata metadata, object parameterObject) 42 | { 43 | if (parameterObject == null) 44 | return null; 45 | 46 | IRfcStructure structure = metadata.CreateStructure(); 47 | Type type = parameterObject.GetType(); 48 | EnsureTypeIsCached(type); 49 | 50 | for (int i = 0; i < metadata.FieldCount; i++) 51 | { 52 | string fieldName = metadata[i].Name; 53 | PropertyInfo property = null; 54 | 55 | object formattedValue = null; 56 | if (typeProperties[type].TryGetValue(fieldName.ToLower(), out property)) 57 | { 58 | object value = property.GetValue(parameterObject, null); 59 | formattedValue = this.ToRemoteValue(metadata[i].GetAbapDataType(), value); 60 | } 61 | else if (string.IsNullOrEmpty(fieldName)) 62 | formattedValue = this.ToRemoteValue(metadata[i].GetAbapDataType(), parameterObject); 63 | 64 | structure.SetValue(fieldName, formattedValue); 65 | } 66 | return structure; 67 | } 68 | 69 | public T FromStructure(IRfcStructure structure) 70 | { 71 | Type type = typeof(T); 72 | EnsureTypeIsCached(type); 73 | 74 | T returnObject = default(T); 75 | if (!type.Equals(typeof(string))) 76 | returnObject= Activator.CreateInstance(); 77 | 78 | for (int i = 0; i < structure.Metadata.FieldCount; i++) 79 | { 80 | string fieldName = structure.Metadata[i].Name; 81 | object value = structure.GetValue(fieldName); 82 | if (string.IsNullOrEmpty(fieldName)) 83 | return (T)this.FromRemoteValue(type, value); 84 | 85 | PropertyInfo property = null; 86 | if (typeProperties[type].TryGetValue(fieldName.ToLower(), out property)) 87 | this.SetProperty(returnObject, property, value); 88 | } 89 | return returnObject; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/soap/SharpSapRfc.Soap.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD} 8 | Library 9 | Properties 10 | SharpSapRfc.Soap 11 | SharpSapRfc.Soap 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | ..\..\bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | ..\..\bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | true 34 | 35 | 36 | ..\..\sharpsaprfc.snk 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {db36edca-d7ce-4ce6-894e-8815f817c778} 63 | SharpSapRfc 64 | 65 | 66 | 67 | 68 | sharpsaprfc.snk 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | sap/ 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | x64/ 15 | build/ 16 | bld/ 17 | [Bb]in/ 18 | [Oo]bj/ 19 | 20 | # Roslyn cache directories 21 | *.ide/ 22 | 23 | # MSTest test Results 24 | [Tt]est[Rr]esult*/ 25 | [Bb]uild[Ll]og.* 26 | 27 | #NUNIT 28 | *.VisualState.xml 29 | TestResult.xml 30 | 31 | # Build Results of an ATL Project 32 | [Dd]ebugPS/ 33 | [Rr]eleasePS/ 34 | dlldata.c 35 | 36 | *_i.c 37 | *_p.c 38 | *_i.h 39 | *.ilk 40 | *.meta 41 | *.obj 42 | *.pch 43 | *.pdb 44 | *.pgc 45 | *.pgd 46 | *.rsp 47 | *.sbr 48 | *.tlb 49 | *.tli 50 | *.tlh 51 | *.tmp 52 | *.tmp_proj 53 | *.log 54 | *.vspscc 55 | *.vssscc 56 | .builds 57 | *.pidb 58 | *.svclog 59 | *.scc 60 | 61 | # Chutzpah Test files 62 | _Chutzpah* 63 | 64 | # Visual C++ cache files 65 | ipch/ 66 | *.aps 67 | *.ncb 68 | *.opensdf 69 | *.sdf 70 | *.cachefile 71 | 72 | # Visual Studio profiler 73 | *.psess 74 | *.vsp 75 | *.vspx 76 | 77 | # TFS 2012 Local Workspace 78 | $tf/ 79 | 80 | # Guidance Automation Toolkit 81 | *.gpState 82 | 83 | # ReSharper is a .NET coding add-in 84 | _ReSharper*/ 85 | *.[Rr]e[Ss]harper 86 | *.DotSettings.user 87 | 88 | # JustCode is a .NET coding addin-in 89 | .JustCode 90 | 91 | # TeamCity is a build add-in 92 | _TeamCity* 93 | 94 | # DotCover is a Code Coverage Tool 95 | *.dotCover 96 | 97 | # NCrunch 98 | _NCrunch_* 99 | .*crunch*.local.xml 100 | 101 | # MightyMoose 102 | *.mm.* 103 | AutoTest.Net/ 104 | 105 | # Web workbench (sass) 106 | .sass-cache/ 107 | 108 | # Installshield output folder 109 | [Ee]xpress/ 110 | 111 | # DocProject is a documentation generator add-in 112 | DocProject/buildhelp/ 113 | DocProject/Help/*.HxT 114 | DocProject/Help/*.HxC 115 | DocProject/Help/*.hhc 116 | DocProject/Help/*.hhk 117 | DocProject/Help/*.hhp 118 | DocProject/Help/Html2 119 | DocProject/Help/html 120 | 121 | # Click-Once directory 122 | publish/ 123 | 124 | # Publish Web Output 125 | *.[Pp]ublish.xml 126 | *.azurePubxml 127 | ## TODO: Comment the next line if you want to checkin your 128 | ## web deploy settings but do note that will include unencrypted 129 | ## passwords 130 | *.pubxml 131 | 132 | # NuGet Packages Directory 133 | packages/* 134 | *.nupkg 135 | ## TODO: If the tool you use requires repositories.config 136 | ## uncomment the next line 137 | #!packages/repositories.config 138 | 139 | # Enable "build/" folder in the NuGet Packages folder since 140 | # NuGet packages use it for MSBuild targets. 141 | # This line needs to be after the ignore of the build folder 142 | # (and the packages folder if the line above has been uncommented) 143 | !packages/build/ 144 | 145 | # Windows Azure Build Output 146 | csx/ 147 | *.build.csdef 148 | 149 | # Windows Store app package directory 150 | AppPackages/ 151 | 152 | # Others 153 | sql/ 154 | *.Cache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | 165 | # RIA/Silverlight projects 166 | Generated_Code/ 167 | 168 | # Backup & report files from converting an old project file 169 | # to a newer Visual Studio version. Backup files are not needed, 170 | # because we have git ;-) 171 | _UpgradeReport_Files/ 172 | Backup*/ 173 | UpgradeLog*.XML 174 | UpgradeLog*.htm 175 | 176 | # SQL Server files 177 | *.mdf 178 | *.ldf 179 | 180 | # Business Intelligence projects 181 | *.rdl.data 182 | *.bim.layout 183 | *.bim_*.settings 184 | 185 | # Microsoft Fakes 186 | FakesAssemblies/ -------------------------------------------------------------------------------- /src/soap/SoapRfcWebClient.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Soap.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Xml; 9 | 10 | namespace SharpSapRfc.Soap 11 | { 12 | public class SoapRfcWebClient 13 | { 14 | private SapSoapRfcDestinationElement destination; 15 | public SoapRfcWebClient(SapSoapRfcDestinationElement destination) 16 | { 17 | this.destination = destination; 18 | } 19 | 20 | public XmlDocument SendRfcRequest(string functionName, string soapBody) 21 | { 22 | HttpWebRequest request = CreateRequest(this.destination.RfcUrl, functionName, "POST"); 23 | 24 | string postData = string.Format(@" 25 | 26 | 27 | 28 | {0} 29 | 30 | ", soapBody); 31 | 32 | UTF8Encoding encoding = new UTF8Encoding(); 33 | byte[] bytes = encoding.GetBytes(postData); 34 | 35 | using (Stream stream = request.GetRequestStream()) 36 | stream.Write(bytes, 0, bytes.Length); 37 | 38 | return this.ResponseToXml(request); 39 | } 40 | 41 | public XmlDocument SendWsdlRequest(string functionName) 42 | { 43 | HttpWebRequest request = CreateRequest(this.destination.WsdlUrl, functionName, "GET"); 44 | return ResponseToXml(request); 45 | } 46 | 47 | private XmlDocument ResponseToXml(HttpWebRequest request) 48 | { 49 | XmlDocument responseXml = new XmlDocument(); 50 | try 51 | { 52 | using (WebResponse response = request.GetResponse()) 53 | { 54 | using (StreamReader rd = new StreamReader(response.GetResponseStream())) 55 | responseXml.Load(rd); 56 | } 57 | } 58 | catch (WebException ex) 59 | { 60 | var response = ex.Response as HttpWebResponse; 61 | if (response != null) 62 | { 63 | if (response.StatusCode == HttpStatusCode.Unauthorized) 64 | throw ex; 65 | 66 | using (StreamReader rd = new StreamReader(response.GetResponseStream())) 67 | responseXml.Load(rd); 68 | } 69 | 70 | if (ex.Status == WebExceptionStatus.Timeout) 71 | throw new TimeoutException(string.Format("Timeout on function call. Current timeout is {0} milliseconds.", request.Timeout)); 72 | 73 | if (responseXml.InnerXml.Length == 0) 74 | throw ex; 75 | } 76 | 77 | return responseXml; 78 | } 79 | 80 | private HttpWebRequest CreateRequest(string baseUrl, string functionName, string httpMethod) 81 | { 82 | string url = string.Format("{0}?sap-client={1}&services={2}", baseUrl, this.destination.Client, functionName); 83 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 84 | request.ContentType = "text/xml; charset=\"UTF-8\""; 85 | request.Accept = "text/xml"; 86 | request.Method = httpMethod; 87 | request.Timeout = this.destination.Timeout; 88 | request.KeepAlive = false; 89 | request.PreAuthenticate = true; 90 | request.Credentials = new NetworkCredential(this.destination.User, this.destination.Password); 91 | request.Headers.Add("SOAPAction", "urn:sap-com:document:sap:rfc:functions"); 92 | return request; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/base/SharpSapRfc.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778} 8 | Library 9 | Properties 10 | SharpSapRfc 11 | SharpSapRfc 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | ..\..\bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | AnyCPU 25 | 26 | 27 | pdbonly 28 | true 29 | ..\..\bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | AnyCPU 34 | 35 | 36 | true 37 | 38 | 39 | ..\..\sharpsaprfc.snk 40 | 41 | 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | sharpsaprfc.snk 83 | 84 | 85 | 86 | 87 | 94 | -------------------------------------------------------------------------------- /SharpSapRfc.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSapRfc.Test", "tests\SharpSapRfc.Test.csproj", "{37086A57-834D-4ACC-B922-CE3ADA4B9F31}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{6AB5428E-CECB-4689-B760-A7FF8211F8B1}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSapRfc", "src\base\SharpSapRfc.csproj", "{DB36EDCA-D7CE-4CE6-894E-8815F817C778}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSapRfc.Soap", "src\soap\SharpSapRfc.Soap.csproj", "{936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSapRfc.Plain", "src\plain\SharpSapRfc.Plain.csproj", "{64507431-51D3-4B2E-BEE3-A1BBE333B228}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspec", "nuspec", "{4415360D-1DFA-4B0F-8A9B-F9B842B1042B}" 20 | ProjectSection(SolutionItems) = preProject 21 | SharpSapRfc.nuspec = SharpSapRfc.nuspec 22 | SharpSapRfc.Plain.x64.nuspec = SharpSapRfc.Plain.x64.nuspec 23 | SharpSapRfc.Plain.x86.nuspec = SharpSapRfc.Plain.x86.nuspec 24 | SharpSapRfc.Soap.nuspec = SharpSapRfc.Soap.nuspec 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|x64 = Debug|x64 30 | Debug|x86 = Debug|x86 31 | Release|x64 = Release|x64 32 | Release|x86 = Release|x86 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Debug|x64.ActiveCfg = Debug|x64 36 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Debug|x64.Build.0 = Debug|x64 37 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Debug|x86.ActiveCfg = Debug|x86 38 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Debug|x86.Build.0 = Debug|x86 39 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Release|x64.ActiveCfg = Release|x64 40 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Release|x64.Build.0 = Release|x64 41 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Release|x86.ActiveCfg = Release|x86 42 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31}.Release|x86.Build.0 = Release|x86 43 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Debug|x64.ActiveCfg = Debug|Any CPU 44 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Debug|x64.Build.0 = Debug|Any CPU 45 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Debug|x86.ActiveCfg = Debug|Any CPU 46 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Debug|x86.Build.0 = Debug|Any CPU 47 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Release|x64.ActiveCfg = Release|Any CPU 48 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Release|x64.Build.0 = Release|Any CPU 49 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Release|x86.ActiveCfg = Release|Any CPU 50 | {DB36EDCA-D7CE-4CE6-894E-8815F817C778}.Release|x86.Build.0 = Release|Any CPU 51 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Debug|x64.ActiveCfg = Debug|Any CPU 52 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Debug|x64.Build.0 = Debug|Any CPU 53 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Debug|x86.ActiveCfg = Debug|Any CPU 54 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Debug|x86.Build.0 = Debug|Any CPU 55 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Release|x64.ActiveCfg = Release|Any CPU 56 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Release|x64.Build.0 = Release|Any CPU 57 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Release|x86.ActiveCfg = Release|Any CPU 58 | {936443D3-2181-4CCE-AE1D-BDB0EBDB64DD}.Release|x86.Build.0 = Release|Any CPU 59 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Debug|x64.ActiveCfg = Debug|x64 60 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Debug|x64.Build.0 = Debug|x64 61 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Debug|x86.ActiveCfg = Debug|x86 62 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Debug|x86.Build.0 = Debug|x86 63 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Release|x64.ActiveCfg = Release|x64 64 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Release|x64.Build.0 = Release|x64 65 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Release|x86.ActiveCfg = Release|x86 66 | {64507431-51D3-4B2E-BEE3-A1BBE333B228}.Release|x86.Build.0 = Release|x86 67 | EndGlobalSection 68 | GlobalSection(SolutionProperties) = preSolution 69 | HideSolutionNode = FALSE 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /src/base/RfcReadTableQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SharpSapRfc 7 | { 8 | public class RfcReadTableQueryBuilder 9 | { 10 | private string tableName; 11 | private string[] fields; 12 | private List conditions; 13 | private int count; 14 | private int skip; 15 | private SapRfcConnection connection; 16 | 17 | public RfcReadTableQueryBuilder(SapRfcConnection connection, string tableName) 18 | { 19 | this.tableName = tableName; 20 | this.connection = connection; 21 | this.conditions = new List(); 22 | } 23 | 24 | public IEnumerable Read() 25 | { 26 | return this.connection.ReadTable(this.tableName, fields, where: conditions.ToArray(), skip: skip, count: count); 27 | } 28 | 29 | public T ReadOne() 30 | { 31 | this.count = 1; 32 | return this.Read().FirstOrDefault(); 33 | } 34 | 35 | public RfcReadTableQueryBuilder Select(params string[] fields) 36 | { 37 | this.fields = fields; 38 | return this; 39 | } 40 | 41 | private string FromValueToString(object value) 42 | { 43 | if (value == null) 44 | return "''"; 45 | 46 | Type type = value.GetType(); 47 | if (type == typeof(string)) 48 | return string.Format("'{0}'", value); 49 | 50 | return value.ToString(); 51 | } 52 | 53 | public RfcReadTableQueryBuilder Where(string condition) 54 | { 55 | this.conditions.Add(condition); 56 | return this; 57 | } 58 | 59 | public RfcReadTableQueryBuilder And(string condition) 60 | { 61 | return this.Where(string.Concat("AND ", condition)); 62 | } 63 | 64 | public RfcReadTableQueryBuilder Or(string condition) 65 | { 66 | return this.Where(string.Concat("OR ", condition)); 67 | } 68 | 69 | public RfcReadTableQueryBuilder Where(string field, RfcReadTableOption option, object value) 70 | { 71 | string optionString = this.FromOptionToString(option); 72 | string valueString = this.FromValueToString(value); 73 | return Where(string.Format("{0} {1} {2}", field, optionString, valueString)); 74 | } 75 | 76 | public RfcReadTableQueryBuilder And(string field, RfcReadTableOption option, object value) 77 | { 78 | string optionString = this.FromOptionToString(option); 79 | string valueString = this.FromValueToString(value); 80 | return And(string.Format("{0} {1} {2}", field, optionString, valueString)); 81 | } 82 | 83 | public RfcReadTableQueryBuilder Or(string field, RfcReadTableOption option, object value) 84 | { 85 | string optionString = this.FromOptionToString(option); 86 | string valueString = this.FromValueToString(value); 87 | return Or(string.Format("{0} {1} {2}", field, optionString, valueString)); 88 | } 89 | 90 | private string FromOptionToString(RfcReadTableOption option) 91 | { 92 | switch (option) 93 | { 94 | case RfcReadTableOption.Equals: 95 | return "EQ"; 96 | case RfcReadTableOption.NotEquals: 97 | return "NE"; 98 | case RfcReadTableOption.GreaterThan: 99 | return "GT"; 100 | case RfcReadTableOption.LessThan: 101 | return "LT"; 102 | case RfcReadTableOption.GreaterOrEqualThan: 103 | return "GE"; 104 | case RfcReadTableOption.LessOrEqualThan: 105 | return "LE"; 106 | default: 107 | throw new NotImplementedException(); 108 | } 109 | } 110 | 111 | public RfcReadTableQueryBuilder Take(int count) 112 | { 113 | this.count = count; 114 | return this; 115 | } 116 | 117 | public RfcReadTableQueryBuilder Skip(int count) 118 | { 119 | this.skip = count; 120 | return this; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/base/RfcStructureMapper.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Structure; 2 | using SharpSapRfc.Types; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | namespace SharpSapRfc 8 | { 9 | public abstract class RfcStructureMapper 10 | { 11 | private RfcValueMapper valueMapper; 12 | public RfcStructureMapper(RfcValueMapper valueMapper) 13 | { 14 | this.valueMapper = valueMapper; 15 | } 16 | 17 | protected static IDictionary> typeProperties = new Dictionary>(); 18 | 19 | protected void EnsureTypeIsCached(Type type) 20 | { 21 | if (typeProperties.ContainsKey(type)) 22 | return; 23 | 24 | lock (type) 25 | { 26 | if (typeProperties.ContainsKey(type)) 27 | return; 28 | 29 | IDictionary propertyByFieldName = new Dictionary(); 30 | 31 | PropertyInfo[] properties = type.GetProperties(); 32 | foreach (var property in properties) 33 | { 34 | if (property.IsDefined(typeof(RfcStructureFieldAttribute), true)) 35 | { 36 | var attribute = ((RfcStructureFieldAttribute[])property.GetCustomAttributes(typeof(RfcStructureFieldAttribute), true))[0]; 37 | propertyByFieldName.Add(attribute.FieldName.ToLower(), property); 38 | if (!string.IsNullOrWhiteSpace(attribute.SecondFieldName)) 39 | propertyByFieldName.Add(attribute.SecondFieldName.ToLower(), property); 40 | } 41 | else 42 | propertyByFieldName.Add(property.Name.ToLower(), property); 43 | } 44 | typeProperties.Add(type, propertyByFieldName); 45 | } 46 | } 47 | 48 | public IEnumerable FromRfcReadTableToList(IEnumerable table, IEnumerable fields) 49 | { 50 | Type type = typeof(T); 51 | EnsureTypeIsCached(type); 52 | 53 | List entries = new List(); 54 | foreach (var row in table) 55 | { 56 | T entry = Activator.CreateInstance(); 57 | foreach (var field in fields) 58 | { 59 | PropertyInfo property = null; 60 | if (typeProperties[type].TryGetValue(field.FieldName.ToLower(), out property)) 61 | { 62 | string value = null; 63 | if (field.Offset >= row.Data.Length) 64 | value = string.Empty; 65 | else if (field.Length + field.Offset > row.Data.Length) 66 | value = row.Data.Substring(field.Offset).Trim(); 67 | else 68 | value = row.Data.Substring(field.Offset, field.Length).Trim(); 69 | 70 | SetProperty(entry, property, value); 71 | } 72 | } 73 | entries.Add(entry); 74 | } 75 | return entries; 76 | } 77 | 78 | protected void SetProperty(object targetObject, PropertyInfo property, object remoteValue) 79 | { 80 | object formattedValue = this.valueMapper.FromRemoteValue(property.PropertyType, remoteValue); 81 | 82 | if (property.PropertyType == typeof(DateTime) || 83 | property.PropertyType == typeof(DateTime?)) 84 | { 85 | DateTime? formattedDateTimeValue = (DateTime?)formattedValue; 86 | DateTime? actualValue = (DateTime?)property.GetValue(targetObject, null); 87 | if (actualValue.HasValue && formattedDateTimeValue.HasValue && actualValue.Value != DateTime.MinValue) 88 | formattedValue = actualValue.Value.AddTicks(formattedDateTimeValue.Value.Ticks); 89 | } 90 | 91 | property.SetValue(targetObject, formattedValue, null); 92 | } 93 | 94 | public object FromRemoteValue(Type type, object value) 95 | { 96 | return this.valueMapper.FromRemoteValue(type, value); 97 | } 98 | 99 | public object ToRemoteValue(AbapDataType remoteType, object value) 100 | { 101 | return this.valueMapper.ToRemoteValue(remoteType, value); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/Mapper/AbapValueMapperFromRemoteTestData.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.Model; 4 | using SharpSapRfc.Types; 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Xunit; 10 | using Xunit.Extensions; 11 | 12 | namespace SharpSapRfc.Test.Mapper 13 | { 14 | public class Soap_AbapValueMapperFromRemoteTestData : AbapValueMapperFromRemoteTestData 15 | { 16 | protected override List AdditionalTestData() 17 | { 18 | return new List { 19 | new object[] { 200.78m, typeof(Decimal), "200.78" }, 20 | new object[] { 514646.89m, typeof(Decimal), "514646.89" }, 21 | new object[] { 1988.89d, typeof(Double), "1988.89" }, 22 | }; 23 | } 24 | } 25 | 26 | public class Plain_AbapValueMapperFromRemoteTestData : AbapValueMapperFromRemoteTestData 27 | { 28 | protected override List AdditionalTestData() 29 | { 30 | return new List { 31 | new object[] { 200.78m, typeof(Decimal), "200,78" }, 32 | new object[] { 514646.89m, typeof(Decimal), "514646,89" }, 33 | new object[] { 1988.89d, typeof(Double), "1988,89" }, 34 | }; 35 | } 36 | } 37 | 38 | public class AbapValueMapperFromRemoteTestData : IEnumerable 39 | { 40 | 41 | protected virtual List AdditionalTestData() 42 | { 43 | return new List(); 44 | } 45 | 46 | private List testData; 47 | public AbapValueMapperFromRemoteTestData() 48 | { 49 | this.testData = AdditionalTestData(); 50 | this.testData.AddRange(new[] { 51 | new object[] { true, typeof(Boolean), "X" }, 52 | new object[] { false, typeof(Boolean), " " }, 53 | new object[] { false, typeof(Boolean), "" }, 54 | 55 | new object[] { "", typeof(string), "" }, 56 | new object[] { "", typeof(string), null }, 57 | new object[] { " ", typeof(string), " " }, 58 | new object[] { "abc", typeof(string), "abc" }, 59 | new object[] { "A@b2", typeof(string), "A@b2" }, 60 | 61 | new object[] { "My Name is", typeof(String), "My Name is" }, 62 | new object[] { 1, typeof(int), "0001" }, 63 | new object[] { 10, typeof(int), "10" }, 64 | new object[] { 15, typeof(int), 15 }, 65 | 66 | new object[] { new DateTime(1, 1, 1, 12, 42, 12), typeof(DateTime), "12:42:12" }, 67 | new object[] { new DateTime(1, 1, 1, 17, 9, 34), typeof(DateTime), "170934" }, 68 | new object[] { new DateTime(2014, 12, 10), typeof(DateTime), "2014-12-10" }, 69 | new object[] { new DateTime(2014, 7, 4), typeof(DateTime), "20140704" }, 70 | 71 | new object[] { 0m, typeof(Decimal), null }, 72 | new object[] { null, typeof(Decimal?), null }, 73 | new object[] { 0m, typeof(Decimal), "" }, 74 | new object[] { null, typeof(Decimal?), "" }, 75 | new object[] { -200m, typeof(Decimal), "-200" }, 76 | new object[] { -451d, typeof(Double), "-451" }, 77 | 78 | new object[] { MaterialState.Available, typeof(MaterialState), 1 }, 79 | new object[] { MaterialState.Blocked, typeof(MaterialState), 2 }, 80 | new object[] { MaterialState.OutOfStock, typeof(MaterialState), 3 }, 81 | 82 | new object[] { MaterialState.Available, typeof(MaterialState), "0001" }, 83 | new object[] { MaterialState.Blocked, typeof(MaterialState), "0002" }, 84 | new object[] { MaterialState.OutOfStock, typeof(MaterialState), "0003" }, 85 | 86 | new object[] { MaterialState.Available, typeof(MaterialState), "AVAL" }, 87 | new object[] { MaterialState.Blocked, typeof(MaterialState), "BLOK" }, 88 | new object[] { MaterialState.OutOfStock, typeof(MaterialState), "OOS" } 89 | }); 90 | } 91 | 92 | public IEnumerator GetEnumerator() 93 | { 94 | return this.testData.GetEnumerator(); 95 | } 96 | 97 | IEnumerator IEnumerable.GetEnumerator() 98 | { 99 | return this.testData.GetEnumerator(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/plain/SharpSapRfc.Plain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {64507431-51D3-4B2E-BEE3-A1BBE333B228} 8 | Library 9 | Properties 10 | SharpSapRfc.Plain 11 | SharpSapRfc.Plain 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | ..\..\bin\x64\Debug\ 18 | DEBUG;TRACE 19 | full 20 | x64 21 | prompt 22 | MinimumRecommendedRules.ruleset 23 | 24 | 25 | ..\..\bin\x64\Release\ 26 | TRACE 27 | true 28 | pdbonly 29 | x64 30 | prompt 31 | MinimumRecommendedRules.ruleset 32 | 33 | 34 | true 35 | ..\..\bin\x86\Debug\ 36 | DEBUG;TRACE 37 | full 38 | x86 39 | prompt 40 | MinimumRecommendedRules.ruleset 41 | 42 | 43 | ..\..\bin\x86\Release\ 44 | TRACE 45 | true 46 | pdbonly 47 | x86 48 | prompt 49 | MinimumRecommendedRules.ruleset 50 | 51 | 52 | true 53 | 54 | 55 | ..\..\sharpsaprfc.snk 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | False 76 | ..\..\sap\nco3-x64\sapnco.dll 77 | 78 | 79 | False 80 | ..\..\sap\nco3-x64\sapnco_utils.dll 81 | 82 | 83 | 84 | 85 | False 86 | ..\..\sap\nco3-x86\sapnco.dll 87 | 88 | 89 | False 90 | ..\..\sap\nco3-x86\sapnco_utils.dll 91 | 92 | 93 | 94 | 95 | {db36edca-d7ce-4ce6-894e-8815f817c778} 96 | SharpSapRfc 97 | 98 | 99 | 100 | 101 | sharpsaprfc.snk 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /src/soap/SoapRfcMetadataCache.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using SharpSapRfc.Types; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml; 8 | 9 | namespace SharpSapRfc.Soap 10 | { 11 | public class SoapRfcMetadataCache : RfcMetadataCache 12 | { 13 | private XmlDocument responseXml; 14 | private SoapRfcWebClient webClient; 15 | private XmlNamespaceManager nsmgr; 16 | 17 | public SoapRfcMetadataCache(SoapRfcWebClient webClient) 18 | { 19 | this.webClient = webClient; 20 | } 21 | 22 | protected override FunctionMetadata LoadFunctionMetadata(string functionName) 23 | { 24 | try 25 | { 26 | this.responseXml = this.webClient.SendWsdlRequest(functionName); 27 | 28 | List inputParameters = new List(); 29 | List outputParameters = new List(); 30 | 31 | this.nsmgr = new XmlNamespaceManager(this.responseXml.NameTable); 32 | this.nsmgr.AddNamespace("xsd", "http://www.w3.org/2001/XMLSchema"); 33 | 34 | var types = this.responseXml.GetElementsByTagName("types"); 35 | if (types.Count > 0) 36 | { 37 | var inputElement = types[0].SelectSingleNode(string.Format("xsd:schema/xsd:element[@name='{0}']", functionName), nsmgr); 38 | if (inputElement != null) 39 | { 40 | var nodes = inputElement.SelectNodes("xsd:complexType/xsd:all/xsd:element", this.nsmgr); 41 | foreach (XmlNode node in nodes) 42 | inputParameters.Add(this.ExtractParameterFromXmlNode(node)); 43 | } 44 | 45 | var outputElement = types[0].SelectSingleNode(string.Format("xsd:schema/xsd:element[@name='{0}.Response']", functionName), nsmgr); 46 | if (outputElement != null) 47 | { 48 | var nodes = outputElement.SelectNodes("xsd:complexType/xsd:all/xsd:element", this.nsmgr); 49 | foreach (XmlNode node in nodes) 50 | outputParameters.Add(this.ExtractParameterFromXmlNode(node)); 51 | } 52 | } 53 | return new FunctionMetadata(functionName, inputParameters, outputParameters); 54 | } 55 | catch (Exception ex) 56 | { 57 | if (ex.GetBaseException() is SharpRfcException) 58 | throw ex; 59 | 60 | throw new SharpRfcCallException(string.Format("Metadata loading failed for function {0}.", functionName), string.Empty, ex); 61 | } 62 | } 63 | 64 | private ParameterMetadata ExtractParameterFromXmlNode(XmlNode node) 65 | { 66 | string parameterName = node.Attributes["name"].Value; 67 | if (node.Attributes["type"] != null) 68 | return CreateParameterMetadata(parameterName, node.Attributes["type"].Value, false); 69 | 70 | var simpleType = node.SelectSingleNode("xsd:simpleType/xsd:restriction", nsmgr); 71 | if (simpleType != null) 72 | return CreateParameterMetadata(parameterName, simpleType.Attributes["base"].Value, false); 73 | 74 | var sequenceElement = node.SelectSingleNode("xsd:complexType/xsd:sequence/xsd:element", nsmgr); 75 | if (sequenceElement.Attributes["type"] != null) 76 | return CreateParameterMetadata(parameterName, sequenceElement.Attributes["type"].Value, true); 77 | 78 | var sequenceSimpleType = sequenceElement.SelectSingleNode("xsd:simpleType/xsd:restriction", nsmgr); 79 | return CreateParameterMetadata(parameterName, sequenceSimpleType.Attributes["base"].Value, true); 80 | 81 | throw new SharpRfcException("Error when trying to parse XmlNode to ParameterMetadata."); 82 | } 83 | 84 | private ParameterMetadata CreateParameterMetadata(string name, string typeName, bool isSequence) 85 | { 86 | AbapDataType parameterType = AbapDataTypeParser.ParseFromTypeAttribute(typeName, isSequence); 87 | StructureMetadata metadata = null; 88 | if (typeName.StartsWith("s0:")) 89 | metadata = this.LoadStructureMetadata(typeName.Replace("s0:", "")); 90 | 91 | return new ParameterMetadata(name, parameterType, metadata); 92 | } 93 | 94 | protected override StructureMetadata LoadStructureMetadata(string structureName) 95 | { 96 | string xpath = string.Format("//xsd:complexType[@name='{0}']/xsd:sequence/xsd:element", structureName); 97 | var nodes = this.responseXml.SelectNodes(xpath, this.nsmgr); 98 | 99 | List fields = new List(); 100 | foreach(XmlNode node in nodes) 101 | { 102 | ParameterMetadata param = this.ExtractParameterFromXmlNode(node); 103 | fields.Add(new FieldMetadata(param.Name, param.DataType)); 104 | } 105 | 106 | return new StructureMetadata(structureName, fields); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tests/Metadata/FunctionMetadataTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using SharpSapRfc.Plain; 3 | using SharpSapRfc.Soap; 4 | using SharpSapRfc.Soap.Configuration; 5 | using SharpSapRfc.Types; 6 | using System; 7 | using Xunit; 8 | 9 | namespace SharpSapRfc.Test.Metadata 10 | { 11 | public class Soap_FunctionMetadataTestCase : FunctionMetadataTestCase 12 | { 13 | private SoapSapRfcConnection conn; 14 | 15 | public override RfcMetadataCache GetMetadataCache() 16 | { 17 | this.conn = new SoapSapRfcConnection("TST-SOAP"); 18 | SoapRfcWebClient webClient = new SoapRfcWebClient(this.conn.Destination); 19 | return new SoapRfcMetadataCache(webClient); 20 | } 21 | 22 | public override void Dispose() 23 | { 24 | this.conn.Dispose(); 25 | } 26 | } 27 | 28 | public class Plain_FunctionMetadataTestCase : FunctionMetadataTestCase 29 | { 30 | private PlainSapRfcConnection conn; 31 | 32 | public override RfcMetadataCache GetMetadataCache() 33 | { 34 | this.conn = new PlainSapRfcConnection("TST"); 35 | return new PlainRfcMetadataCache(this.conn); 36 | } 37 | 38 | public override void Dispose() 39 | { 40 | this.conn.Dispose(); 41 | } 42 | } 43 | 44 | public abstract class FunctionMetadataTestCase : IDisposable 45 | { 46 | public abstract RfcMetadataCache GetMetadataCache(); 47 | 48 | [Fact] 49 | public void SimpleFunctionMetadataTest() 50 | { 51 | var cache = GetMetadataCache(); 52 | var metadata = cache.GetFunctionMetadata("Z_SSRT_SUM"); 53 | 54 | Assert.Equal(2, metadata.InputParameters.Length); 55 | Assert.Equal(1, metadata.OutputParameters.Length); 56 | Assert.Equal("Z_SSRT_SUM", metadata.Name); 57 | 58 | AssertInputParameter(metadata, "I_NUM1", AbapDataType.INTEGER); 59 | AssertInputParameter(metadata, "I_NUM2", AbapDataType.INTEGER); 60 | AssertOutputParameter(metadata, "E_RESULT", AbapDataType.INTEGER); 61 | } 62 | 63 | [Fact] 64 | public void InOutFunctionMetadataTest() 65 | { 66 | var cache = GetMetadataCache(); 67 | var metadata = cache.GetFunctionMetadata("Z_SSRT_IN_OUT"); 68 | 69 | Assert.Equal(13, metadata.InputParameters.Length); 70 | Assert.Equal(15, metadata.OutputParameters.Length); 71 | Assert.Equal("Z_SSRT_IN_OUT", metadata.Name); 72 | 73 | AssertInputParameter(metadata, "I_ID", AbapDataType.INTEGER); 74 | AssertInputParameter(metadata, "I_INT1", AbapDataType.BYTE); 75 | AssertInputParameter(metadata, "I_FLOAT", AbapDataType.DOUBLE); 76 | AssertInputParameter(metadata, "I_DEC_3_2", AbapDataType.DECIMAL); 77 | AssertInputParameter(metadata, "I_DEC_23_4", AbapDataType.DECIMAL); 78 | AssertInputParameter(metadata, "I_DEC_30_7", AbapDataType.DECIMAL); 79 | AssertInputParameter(metadata, "I_DATUM", AbapDataType.DATE); 80 | AssertInputParameter(metadata, "I_UZEIT", AbapDataType.TIME); 81 | AssertInputParameter(metadata, "I_ACTIVE", AbapDataType.CHAR); 82 | AssertInputParameter(metadata, "I_MARA", AbapDataType.STRUCTURE); 83 | AssertInputParameter(metadata, "I_MULTIPLE_ID", AbapDataType.TABLE); 84 | AssertInputParameter(metadata, "I_MULTIPLE_NAME", AbapDataType.TABLE); 85 | 86 | AssertOutputParameter(metadata, "E_ID", AbapDataType.INTEGER); 87 | AssertOutputParameter(metadata, "E_INT1", AbapDataType.BYTE); 88 | AssertOutputParameter(metadata, "E_FLOAT", AbapDataType.DOUBLE); 89 | AssertOutputParameter(metadata, "E_DEC_3_2", AbapDataType.DECIMAL); 90 | AssertOutputParameter(metadata, "E_DEC_23_4", AbapDataType.DECIMAL); 91 | AssertOutputParameter(metadata, "E_DEC_30_7", AbapDataType.DECIMAL); 92 | AssertOutputParameter(metadata, "E_DATUM", AbapDataType.DATE); 93 | AssertOutputParameter(metadata, "E_UZEIT", AbapDataType.TIME); 94 | AssertOutputParameter(metadata, "E_ACTIVE", AbapDataType.CHAR); 95 | AssertOutputParameter(metadata, "E_MARA_DATUM", AbapDataType.DATE); 96 | AssertOutputParameter(metadata, "E_MARA_UZEIT", AbapDataType.TIME); 97 | AssertOutputParameter(metadata, "E_MARA_ID", AbapDataType.INTEGER); 98 | AssertOutputParameter(metadata, "E_MULTIPLE_ID", AbapDataType.TABLE); 99 | AssertOutputParameter(metadata, "E_MULTIPLE_NAME", AbapDataType.TABLE); 100 | } 101 | 102 | private void AssertInputParameter(FunctionMetadata metadata, string name, AbapDataType dataType) 103 | { 104 | var parameter = metadata.GetInputParameter(name); 105 | Assert.Equal(name, parameter.Name); 106 | Assert.Equal(dataType, parameter.DataType); 107 | } 108 | 109 | private void AssertOutputParameter(FunctionMetadata metadata, string name, AbapDataType dataType) 110 | { 111 | var parameter = metadata.GetOutputParameter(name); 112 | Assert.Equal(name, parameter.Name); 113 | Assert.Equal(dataType, parameter.DataType); 114 | } 115 | 116 | public abstract void Dispose(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/plain/PlainRfcPreparedFunction.cs: -------------------------------------------------------------------------------- 1 | using SAP.Middleware.Connector; 2 | using System; 3 | using System.Text; 4 | 5 | namespace SharpSapRfc.Plain 6 | { 7 | public class PlainRfcPreparedFunction : RfcPreparedFunction 8 | { 9 | private RfcRepository repository; 10 | private RfcDestination destination; 11 | private PlainRfcStructureMapper structureMapper; 12 | 13 | public PlainRfcPreparedFunction(string functionName, 14 | PlainRfcStructureMapper structureMapper, 15 | RfcRepository repository, 16 | RfcDestination destination) 17 | : base(functionName) 18 | { 19 | this.repository = repository; 20 | this.destination = destination; 21 | this.structureMapper = structureMapper; 22 | } 23 | 24 | public override string ToString() 25 | { 26 | if (this.function != null) 27 | { 28 | StringBuilder importing = new StringBuilder(); 29 | StringBuilder tables = new StringBuilder(); 30 | StringBuilder changing = new StringBuilder(); 31 | 32 | for (int i = 0; i < this.function.Metadata.ParameterCount; i++) 33 | { 34 | var parameter = this.function.Metadata[i]; 35 | switch (parameter.Direction) 36 | { 37 | case RfcDirection.CHANGING: 38 | changing.AppendFormat(" - {0} = {1}", parameter.Name, this.function.GetObject(i)).AppendLine(); 39 | break; 40 | case RfcDirection.IMPORT: 41 | importing.AppendFormat(" - {0} = {1}", parameter.Name, this.function.GetObject(i)).AppendLine(); 42 | break; 43 | case RfcDirection.TABLES: 44 | tables.AppendFormat("- {0} = {1}", parameter.Name, this.function.GetObject(i)).AppendLine(); 45 | break; 46 | } 47 | } 48 | 49 | StringBuilder output = new StringBuilder(); 50 | output.AppendFormat("FUNCTION: {0}", this.FunctionName).AppendLine(); 51 | output.Append(" IMPORTING: ").AppendLine().Append(importing); 52 | output.Append(" CHANGING: ").AppendLine().Append(changing); 53 | output.Append(" TABLES: ").AppendLine().Append(tables); 54 | return output.ToString(); 55 | } 56 | 57 | return ""; 58 | } 59 | 60 | public override RfcPreparedFunction Prepare() 61 | { 62 | try 63 | { 64 | this.function = this.repository.CreateFunction(this.FunctionName); 65 | foreach (var parameter in this.Parameters) 66 | { 67 | int idx = this.function.Metadata.TryNameToIndex(parameter.Name); 68 | if (idx == -1) 69 | throw new UnknownRfcParameterException(parameter.Name, this.FunctionName); 70 | 71 | RfcDataType pType = this.function.Metadata[idx].DataType; 72 | switch (pType) 73 | { 74 | case RfcDataType.STRUCTURE: 75 | RfcStructureMetadata structureMetadata = this.function.GetStructure(idx).Metadata; 76 | IRfcStructure structure = this.structureMapper.CreateStructure(structureMetadata, parameter.Value); 77 | this.function.SetValue(parameter.Name, structure); 78 | break; 79 | case RfcDataType.TABLE: 80 | RfcTableMetadata tableMetadata = this.function.GetTable(idx).Metadata; 81 | IRfcTable table = this.structureMapper.CreateTable(tableMetadata, parameter.Value); 82 | this.function.SetValue(parameter.Name, table); 83 | break; 84 | default: 85 | object formattedValue = this.structureMapper.ToRemoteValue(this.function.Metadata[idx].GetAbapDataType(), parameter.Value); 86 | this.function.SetValue(parameter.Name, formattedValue); 87 | break; 88 | } 89 | } 90 | return this; 91 | } 92 | catch (Exception ex) 93 | { 94 | if (ex.GetBaseException() is SharpRfcException) 95 | throw ex; 96 | 97 | throw new SharpRfcCallException(ex.Message, function == null ? this.FunctionName : function.ToString(), ex); 98 | } 99 | } 100 | 101 | public override RfcResult Execute() 102 | { 103 | try 104 | { 105 | if (this.function == null) 106 | this.Prepare(); 107 | 108 | function.Invoke(this.destination); 109 | } 110 | catch (Exception ex) 111 | { 112 | if (ex.GetBaseException() is SharpRfcException) 113 | throw ex; 114 | 115 | throw new SharpRfcCallException(ex.Message, function == null ? "null" : function.ToString(), ex); 116 | } 117 | 118 | return new PlainRfcResult(function, this.structureMapper); 119 | } 120 | 121 | public IRfcFunction function { get; set; } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/TestCases/StructureTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.Model; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace SharpSapRfc.Test.TestCases 9 | { 10 | public class Soap_StructureTestCase : StructureTestCase 11 | { 12 | protected override SapRfcConnection GetConnection() 13 | { 14 | return new SoapSapRfcConnection("TST-SOAP"); 15 | } 16 | } 17 | 18 | public class Plain_StructureTestCase : StructureTestCase 19 | { 20 | protected override SapRfcConnection GetConnection() 21 | { 22 | return new PlainSapRfcConnection("TST"); 23 | } 24 | } 25 | 26 | public abstract class StructureTestCase 27 | { 28 | protected abstract SapRfcConnection GetConnection(); 29 | 30 | [Fact] 31 | public void ImportStructureTest() 32 | { 33 | using (SapRfcConnection conn = this.GetConnection()) 34 | { 35 | var customer = new ZCustomer { Id = 3, Name = "Microsoft", IsActive = true }; 36 | var result = conn.ExecuteFunction("Z_SSRT_ADD_CUSTOMER", 37 | new RfcParameter("i_customer", customer) 38 | ); 39 | 40 | string message = result.GetOutput("e_success"); 41 | Assert.Equal("Created: 3 - Microsoft - X", message); 42 | } 43 | } 44 | 45 | [Fact] 46 | public void ImportStructureTest_WithAnonymousType() 47 | { 48 | using (SapRfcConnection conn = this.GetConnection()) 49 | { 50 | var customer = new ZCustomer { Id = 3, Name = "Microsoft", IsActive = true }; 51 | var result = conn.ExecuteFunction("Z_SSRT_ADD_CUSTOMER", new 52 | { 53 | i_customer = customer 54 | }); 55 | 56 | string message = result.GetOutput("e_success"); 57 | Assert.Equal("Created: 3 - Microsoft - X", message); 58 | } 59 | } 60 | 61 | [Fact] 62 | public void ExportStructureTest() 63 | { 64 | using (SapRfcConnection conn = this.GetConnection()) 65 | { 66 | var result = conn.ExecuteFunction("Z_SSRT_GET_CUSTOMER", 67 | new RfcParameter("i_id", 2) 68 | ); 69 | 70 | var customer = result.GetOutput("e_customer"); 71 | Assert.Equal(2, customer.Id); 72 | Assert.Equal("Walmart", customer.Name); 73 | Assert.Equal(false, customer.IsActive); 74 | } 75 | } 76 | 77 | [Fact] 78 | public void ExportTableTest() 79 | { 80 | using (SapRfcConnection conn = this.GetConnection()) 81 | { 82 | var result = conn.ExecuteFunction("Z_SSRT_GET_ALL_CUSTOMERS"); 83 | 84 | var customers = result.GetTable("t_customers"); 85 | Assert.Equal(2, customers.Count()); 86 | 87 | Assert.Equal(1, customers.ElementAt(0).Id); 88 | Assert.Equal("Apple Store", customers.ElementAt(0).Name); 89 | Assert.Equal(0, customers.ElementAt(0).Age); 90 | Assert.Equal(true, customers.ElementAt(0).IsActive); 91 | 92 | Assert.Equal(2, customers.ElementAt(1).Id); 93 | Assert.Equal("Walmart", customers.ElementAt(1).Name); 94 | Assert.Equal(0, customers.ElementAt(1).Age); 95 | Assert.Equal(false, customers.ElementAt(1).IsActive); 96 | } 97 | } 98 | 99 | [Fact] 100 | public void ExportTableCategoryTest() 101 | { 102 | using (SapRfcConnection conn = this.GetConnection()) 103 | { 104 | var result = conn.ExecuteFunction("Z_SSRT_GET_ALL_CUSTOMERS2"); 105 | 106 | var customers = result.GetTable("e_customers"); 107 | Assert.Equal(2, customers.Count()); 108 | 109 | Assert.Equal(1, customers.ElementAt(0).Id); 110 | Assert.Equal("Apple Store", customers.ElementAt(0).Name); 111 | Assert.Equal(0, customers.ElementAt(0).Age); 112 | Assert.Equal(true, customers.ElementAt(0).IsActive); 113 | 114 | Assert.Equal(2, customers.ElementAt(1).Id); 115 | Assert.Equal("Walmart", customers.ElementAt(1).Name); 116 | Assert.Equal(0, customers.ElementAt(1).Age); 117 | Assert.Equal(false, customers.ElementAt(1).IsActive); 118 | } 119 | } 120 | 121 | [Fact] 122 | public void ChangingSingleStructureAsTableTest() 123 | { 124 | using (SapRfcConnection conn = this.GetConnection()) 125 | { 126 | ZCustomer customer = new ZCustomer() { Id = 1 }; 127 | 128 | var result = conn.ExecuteFunction("Z_SSRT_QUERY_CUSTOMERS", 129 | new RfcParameter("c_customers", customer) 130 | ); 131 | 132 | var customers = result.GetTable("c_customers"); 133 | Assert.Equal(1, customers.Count()); 134 | 135 | Assert.Equal(1, customers.ElementAt(0).Id); 136 | Assert.Equal("Apple Store", customers.ElementAt(0).Name); 137 | Assert.Equal(0, customers.ElementAt(0).Age); 138 | Assert.Equal(true, customers.ElementAt(0).IsActive); 139 | } 140 | } 141 | 142 | [Fact] 143 | public void ChangingTableCategoryTest() 144 | { 145 | using (SapRfcConnection conn = this.GetConnection()) 146 | { 147 | IEnumerable customers = new ZCustomer[] { 148 | new ZCustomer() { Id = 1 } 149 | }; 150 | 151 | var result = conn.ExecuteFunction("Z_SSRT_QUERY_CUSTOMERS", 152 | new RfcParameter("c_customers", customers) 153 | ); 154 | 155 | customers = result.GetTable("c_customers"); 156 | Assert.Equal(1, customers.Count()); 157 | 158 | Assert.Equal(1, customers.ElementAt(0).Id); 159 | Assert.Equal("Apple Store", customers.ElementAt(0).Name); 160 | Assert.Equal(0, customers.ElementAt(0).Age); 161 | Assert.Equal(true, customers.ElementAt(0).IsActive); 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /tests/TestCases/FluentRfcReadTableTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.Model; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace SharpSapRfc.Test.TestCases 11 | { 12 | public class Soap_FluentRfcReadTableTestCase : FluentRfcReadTableTestCase 13 | { 14 | protected override SapRfcConnection GetConnection() 15 | { 16 | return new SoapSapRfcConnection("TST-SOAP"); 17 | } 18 | } 19 | 20 | public class Plain_FluentRfcReadTableTestCase : FluentRfcReadTableTestCase 21 | { 22 | protected override SapRfcConnection GetConnection() 23 | { 24 | return new PlainSapRfcConnection("TST"); 25 | } 26 | } 27 | 28 | public abstract class FluentRfcReadTableTestCase 29 | { 30 | protected abstract SapRfcConnection GetConnection(); 31 | 32 | [Fact] 33 | public void ReadAllEntriesTest() 34 | { 35 | using (SapRfcConnection conn = this.GetConnection()) 36 | { 37 | var scarr = conn.Table("SCARR").Read(); 38 | Assert.Equal(18, scarr.Count()); 39 | } 40 | } 41 | 42 | [Fact] 43 | public void ReadTwoFieldsTest() 44 | { 45 | using (SapRfcConnection conn = this.GetConnection()) 46 | { 47 | var scarr = conn.Table("SCARR").Select("CARRID", "CARRNAME").Read(); 48 | Assert.Equal(18, scarr.Count()); 49 | 50 | var aa = scarr.FirstOrDefault(x => x.Code == "AA"); 51 | Assert.Equal("AA", aa.Code); 52 | Assert.Equal("American Airlines", aa.Name); 53 | Assert.Equal(null, aa.Currency); 54 | Assert.Equal(null, aa.Url); 55 | } 56 | } 57 | 58 | [Fact] 59 | public void ReadTwoFieldFromDeltaAirlineCompanyTest() 60 | { 61 | using (SapRfcConnection conn = this.GetConnection()) 62 | { 63 | var scarr = conn.Table("SCARR") 64 | .Select("CARRID", "CURRCODE") 65 | .Where("CARRID = 'DL'") 66 | .Read(); 67 | 68 | Assert.Equal(1, scarr.Count()); 69 | Assert.Equal("DL", scarr.ElementAt(0).Code); 70 | Assert.Equal("USD", scarr.ElementAt(0).Currency); 71 | 72 | Assert.Equal(null, scarr.ElementAt(0).Name); 73 | Assert.Equal(null, scarr.ElementAt(0).Url); 74 | Assert.Equal(0, scarr.ElementAt(0).Client); 75 | } 76 | } 77 | 78 | [Fact] 79 | public void StrongTypeEqualsConditionTest() 80 | { 81 | using (SapRfcConnection conn = this.GetConnection()) 82 | { 83 | var scarr = conn.Table("SCARR") 84 | .Select("CARRID", "CURRCODE") 85 | .Where("CARRID", RfcReadTableOption.Equals, "DL") 86 | .Read(); 87 | 88 | Assert.Equal(1, scarr.Count()); 89 | Assert.Equal("DL", scarr.ElementAt(0).Code); 90 | Assert.Equal("USD", scarr.ElementAt(0).Currency); 91 | 92 | Assert.Equal(null, scarr.ElementAt(0).Name); 93 | Assert.Equal(null, scarr.ElementAt(0).Url); 94 | Assert.Equal(0, scarr.ElementAt(0).Client); 95 | } 96 | } 97 | 98 | [Fact] 99 | public void StrongTypeNotEqualsConditionTest() 100 | { 101 | using (SapRfcConnection conn = this.GetConnection()) 102 | { 103 | var scarr = conn.Table("SCARR") 104 | .Select("CARRID", "CURRCODE") 105 | .Where("CARRID", RfcReadTableOption.NotEquals, "DL") 106 | .Read(); 107 | 108 | foreach (var company in scarr) 109 | { 110 | Assert.NotEqual("DL", company.Code); 111 | } 112 | } 113 | } 114 | 115 | [Fact] 116 | public void StrongTypeGreaterAndLessThanConditionTest() 117 | { 118 | using (SapRfcConnection conn = this.GetConnection()) 119 | { 120 | var flights = conn.Table("SPFLI") 121 | .Where("DISTANCE", RfcReadTableOption.GreaterThan, 8000) 122 | .And("DISTANCE", RfcReadTableOption.LessThan, 10000) 123 | .Read(); 124 | 125 | Assert.Equal(4, flights.Count()); 126 | } 127 | } 128 | 129 | [Fact] 130 | public void StrongTypeGreaterEqualAndLessEqualThanConditionTest() 131 | { 132 | using (SapRfcConnection conn = this.GetConnection()) 133 | { 134 | var flights = conn.Table("SPFLI") 135 | .Where("DISTANCE", RfcReadTableOption.GreaterOrEqualThan, 6030) 136 | .And("DISTANCE", RfcReadTableOption.LessOrEqualThan, 9100) 137 | .Read(); 138 | 139 | Assert.Equal(12, flights.Count()); 140 | } 141 | } 142 | 143 | [Fact] 144 | public void ReadWithTwoAndConditionsTest() 145 | { 146 | using (SapRfcConnection conn = this.GetConnection()) 147 | { 148 | var scarr = conn.Table("SCARR") 149 | .Select("CARRID", "CURRCODE") 150 | .Where("CARRID = 'DL'") 151 | .And("CURRCODE = 'BRL'") 152 | .Read(); 153 | 154 | Assert.Equal(0, scarr.Count()); 155 | } 156 | } 157 | 158 | [Fact] 159 | public void ReadWithTwoOrConditionsTest() 160 | { 161 | using (SapRfcConnection conn = this.GetConnection()) 162 | { 163 | var scarr = conn.Table("SCARR") 164 | .Select("CARRID", "CURRCODE") 165 | .Where("CARRID = 'DL'") 166 | .Or("CARRID = 'AA'") 167 | .Read(); 168 | 169 | Assert.Equal(2, scarr.Count()); 170 | } 171 | } 172 | 173 | [Fact] 174 | public void TakeFirstCompanyTest() 175 | { 176 | using (SapRfcConnection conn = this.GetConnection()) 177 | { 178 | var scarr = conn.Table("SCARR").Take(1).Read(); 179 | Assert.Equal(1, scarr.Count()); 180 | } 181 | } 182 | 183 | [Fact] 184 | public void FirstCompanyShouldBeDifferentThanSecondCompanyTest() 185 | { 186 | using (SapRfcConnection conn = this.GetConnection()) 187 | { 188 | var firstCompany = conn.Table("SCARR").Take(1).ReadOne(); 189 | var secondCompany = conn.Table("SCARR").Skip(1).Take(1).ReadOne(); 190 | 191 | Assert.NotEqual(firstCompany.Code, secondCompany.Code); 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/base/RfcValueMapper.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Types; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.IO; 6 | 7 | namespace SharpSapRfc 8 | { 9 | public abstract class RfcValueMapper 10 | { 11 | protected abstract NumberFormatInfo GetNumberFormat(); 12 | 13 | protected NumberFormatInfo CommaDecimalNumberFormat 14 | { 15 | get { return new NumberFormatInfo() { NumberDecimalSeparator = "," }; } 16 | } 17 | 18 | protected NumberFormatInfo PeriodDecimalNumberFormat 19 | { 20 | get { return new NumberFormatInfo() { NumberDecimalSeparator = "." }; } 21 | } 22 | 23 | protected static Dictionary> cachedEnumMembers = new Dictionary>(); 24 | 25 | protected static void EnsureEnumTypeIsCached(Type enumType) 26 | { 27 | if (!enumType.IsEnum || cachedEnumMembers.ContainsKey(enumType)) 28 | return; 29 | 30 | lock (enumType) 31 | { 32 | if (cachedEnumMembers.ContainsKey(enumType)) 33 | return; 34 | 35 | Dictionary membersByName = new Dictionary(); 36 | foreach (string name in Enum.GetNames(enumType)) 37 | { 38 | var memberInfo = enumType.GetMember(name); 39 | var attributes = memberInfo[0].GetCustomAttributes(typeof(RfcEnumValueAttribute), false); 40 | if (attributes.Length > 0) 41 | { 42 | var enumValue = ((RfcEnumValueAttribute)attributes[0]).Value; 43 | membersByName.Add(name, enumValue); 44 | } 45 | } 46 | cachedEnumMembers.Add(enumType, membersByName); 47 | } 48 | } 49 | 50 | public virtual object FromRemoteValue(Type type, object value) 51 | { 52 | if (type.IsEnum) 53 | return this.ConvertToEnum(type, value); 54 | 55 | if (type.Equals(typeof(string))) 56 | return value == null ? "" : value.ToString(); 57 | 58 | if (value == null || value.ToString().Equals("")) 59 | { 60 | if (type.IsNullable()) 61 | return null; 62 | 63 | return Activator.CreateInstance(type); 64 | } 65 | 66 | if (type.Equals(typeof(Boolean))) 67 | return AbapBool.FromString(value.ToString()); 68 | 69 | if (type.Equals(typeof(DateTime))) 70 | return AbapDateTime.FromString(value.ToString()); 71 | 72 | if (type.Equals(typeof(DateTime?))) 73 | return AbapDateTime.FromString(value.ToString(), true); 74 | 75 | if (type.Equals(typeof(Decimal))) 76 | { 77 | if (value.ToString().StartsWith("*")) 78 | throw new SharpRfcException(string.Format("SAP truncated value {0}. Operation aborted", value)); 79 | 80 | return Convert.ToDecimal(value, value.ToString().Contains(",") ? this.CommaDecimalNumberFormat : this.PeriodDecimalNumberFormat); 81 | } 82 | 83 | if (type.Equals(typeof(Double))) 84 | { 85 | if (value.ToString().StartsWith("*")) 86 | throw new SharpRfcException(string.Format("SAP truncated value {0}. Operation aborted", value)); 87 | 88 | return Convert.ToDouble(value, value.ToString().Contains(",") ? this.CommaDecimalNumberFormat : this.PeriodDecimalNumberFormat); 89 | } 90 | 91 | if (type.Equals(typeof(byte[]))) 92 | return this.ToBytes(value); 93 | 94 | if (type.Equals(typeof(Stream))) 95 | return new MemoryStream(this.ToBytes(value)); 96 | 97 | return Convert.ChangeType(value, type); 98 | } 99 | 100 | private byte[] ToBytes(object value) 101 | { 102 | if (value is string) 103 | return Convert.FromBase64String(value.ToString()); 104 | 105 | return (byte[])value; 106 | } 107 | 108 | public virtual object ConvertToEnum(Type enumType, object remoteValue) 109 | { 110 | if (remoteValue != null) 111 | { 112 | int hashCode = 0; 113 | if (int.TryParse(remoteValue.ToString(), out hashCode)) 114 | if (Enum.IsDefined(enumType, hashCode)) 115 | return Enum.ToObject(enumType, hashCode); 116 | 117 | EnsureEnumTypeIsCached(enumType); 118 | foreach (var keyPair in cachedEnumMembers[enumType]) 119 | { 120 | if (keyPair.Value == remoteValue.ToString()) 121 | return Enum.Parse(enumType, keyPair.Key); 122 | } 123 | } 124 | 125 | throw new RfcMappingException(string.Format("Cannot convert from remote value '{0}' to enum type '{1}'.", remoteValue, enumType.Name)); 126 | } 127 | 128 | public virtual object ToRemoteValue(AbapDataType remoteType, object value) 129 | { 130 | if (value == null) 131 | return null; 132 | 133 | Type valueType = value.GetType(); 134 | 135 | if (valueType == typeof(Boolean)) 136 | return AbapBool.ToString((Boolean)value); 137 | 138 | if (valueType.Equals(typeof(double))) 139 | return ((double)value).ToString(this.GetNumberFormat()); 140 | 141 | if (valueType.Equals(typeof(float))) 142 | return ((float)value).ToString(this.GetNumberFormat()); 143 | 144 | if (valueType.Equals(typeof(Decimal))) 145 | return ((Decimal)value).ToString(this.GetNumberFormat()); 146 | 147 | if (valueType.Equals(typeof(Double))) 148 | return ((Double)value).ToString(this.GetNumberFormat()); 149 | 150 | if (valueType.IsEnum) 151 | return ConvertFromEnum(remoteType, value); 152 | 153 | if (remoteType == AbapDataType.DATE) 154 | return AbapDateTime.ToDateString((DateTime)value); 155 | 156 | if (remoteType == AbapDataType.TIME) 157 | return AbapDateTime.ToTimeString((DateTime)value); 158 | 159 | return value; 160 | } 161 | 162 | public virtual object ConvertFromEnum(AbapDataType remoteType, object value) 163 | { 164 | if (remoteType == AbapDataType.INTEGER || 165 | remoteType == AbapDataType.SHORT) 166 | return value.GetHashCode(); 167 | 168 | if (remoteType == AbapDataType.NUMERIC) 169 | return value.GetHashCode().ToString(); 170 | 171 | if (remoteType == AbapDataType.CHAR) 172 | { 173 | Type enumType = value.GetType(); 174 | EnsureEnumTypeIsCached(enumType); 175 | foreach (var keyPair in cachedEnumMembers[enumType]) 176 | { 177 | if (keyPair.Key == value.ToString()) 178 | return keyPair.Value; 179 | } 180 | return string.Empty; 181 | } 182 | 183 | throw new RfcMappingException(string.Format("Cannot convert from Enum '{0}' to remote type '{1}'.", value, remoteType.ToString())); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/soap/SoapRfcPreparedFunction.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Metadata; 2 | using SharpSapRfc.Types; 3 | using System; 4 | using System.Collections; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml; 8 | 9 | namespace SharpSapRfc.Soap 10 | { 11 | public class SoapRfcPreparedFunction : RfcPreparedFunction 12 | { 13 | private SoapRfcWebClient webClient; 14 | private FunctionMetadata function; 15 | private SoapRfcStructureMapper structureMapper; 16 | private XmlDocument requestBody; 17 | 18 | public override string ToString() 19 | { 20 | if (this.requestBody != null) 21 | return Beautify(this.requestBody); 22 | 23 | return ""; 24 | } 25 | 26 | public SoapRfcPreparedFunction(FunctionMetadata function, SoapRfcStructureMapper structureMapper, SoapRfcWebClient webClient) 27 | : base(function.Name) 28 | { 29 | this.function = function; 30 | this.webClient = webClient; 31 | this.structureMapper = structureMapper; 32 | } 33 | 34 | public override RfcPreparedFunction Prepare() 35 | { 36 | XmlDocument body = new XmlDocument(); 37 | try 38 | { 39 | body.PreserveWhitespace = true; 40 | XmlNode parametersNode = body.CreateElement("urn", this.function.Name, "urn:sap-com:document:sap:rfc:functions"); 41 | body.AppendChild(parametersNode); 42 | 43 | foreach (var parameter in this.Parameters) 44 | { 45 | var param = this.function.GetInputParameter(parameter.Name); 46 | if (parameter.Value != null) 47 | { 48 | switch (param.DataType) 49 | { 50 | case AbapDataType.STRUCTURE: 51 | XmlNode structureNode = this.structureMapper.FromStructure(body, param.Name, param, parameter.Value); 52 | parametersNode.AppendChild(structureNode); 53 | break; 54 | case AbapDataType.TABLE: 55 | XmlNode tableNode = body.CreateElement(param.Name.ToUpper()); 56 | 57 | IEnumerable enumerable = parameter.Value as IEnumerable; 58 | if (enumerable == null) 59 | { 60 | XmlNode itemNode = this.structureMapper.FromStructure(body, "item", param, parameter.Value); 61 | tableNode.AppendChild(itemNode); 62 | } 63 | else 64 | { 65 | var enumerator = enumerable.GetEnumerator(); 66 | while (enumerator.MoveNext()) 67 | { 68 | object current = enumerator.Current; 69 | XmlNode itemNode = this.structureMapper.FromStructure(body, "item", param, current); 70 | tableNode.AppendChild(itemNode); 71 | } 72 | } 73 | 74 | parametersNode.AppendChild(tableNode); 75 | break; 76 | default: 77 | XmlNode valueNode = body.CreateElement(param.Name.ToUpper()); 78 | valueNode.InnerText = this.structureMapper.ToRemoteValue(param.DataType, parameter.Value).ToString(); 79 | //if (!string.IsNullOrEmpty(valueNode.InnerText)) 80 | parametersNode.AppendChild(valueNode); 81 | break; 82 | } 83 | } 84 | } 85 | 86 | //table parameters are mandatory 87 | foreach (var param in this.function.InputParameters) 88 | { 89 | if (param.DataType == AbapDataType.TABLE) 90 | { 91 | bool notAdded = this.Parameters.All(x => x.Name != param.Name); 92 | if (notAdded) 93 | parametersNode.AppendChild(body.CreateElement(param.Name)); 94 | } 95 | } 96 | 97 | this.requestBody = body; 98 | return this; 99 | } 100 | catch (Exception ex) 101 | { 102 | if (ex.GetBaseException() is SharpRfcException) 103 | throw ex; 104 | 105 | throw new SharpRfcCallException("RFC SOAP prepare failed.", body.InnerXml, ex); 106 | } 107 | } 108 | 109 | public override RfcResult Execute() 110 | { 111 | if (this.requestBody == null) 112 | this.Prepare(); 113 | 114 | try 115 | { 116 | var responseXml = this.webClient.SendRfcRequest(this.function.Name, Beautify(this.requestBody)); 117 | 118 | var responseTag = responseXml.GetElementsByTagName(string.Format("urn:{0}.Response", this.function.Name)); 119 | if (responseTag.Count > 0) 120 | return new SoapRfcResult(this.function, responseTag[0], this.structureMapper); 121 | 122 | var exceptionTag = responseXml.GetElementsByTagName(string.Format("rfc:{0}.Exception", this.function.Name)); 123 | if (exceptionTag.Count > 0) 124 | throw new SharpRfcCallException(exceptionTag[0].InnerText, this.requestBody.InnerXml); 125 | 126 | var faultErrorTag = responseXml.GetElementsByTagName("rfc:Error"); 127 | if (faultErrorTag.Count > 0) 128 | { 129 | string errorText = faultErrorTag[0].SelectSingleNode("message").InnerText; 130 | throw new SharpRfcCallException(errorText, this.requestBody.InnerXml); 131 | } 132 | 133 | var soapFaultTag = responseXml.GetElementsByTagName("SOAP-ENV:Fault"); 134 | if (soapFaultTag.Count > 0) 135 | { 136 | string faultstring = soapFaultTag[0].SelectSingleNode("faultstring").InnerText; 137 | string detail = soapFaultTag[0].SelectSingleNode("detail").InnerText; 138 | string errorMessage = string.Format("Fault: {0} Detail: {1}", faultstring, detail); 139 | throw new SharpRfcCallException(errorMessage, this.requestBody.InnerXml); 140 | } 141 | 142 | throw new SharpRfcCallException("Could not fetch response tag.", this.requestBody.InnerXml); 143 | } 144 | catch (Exception ex) 145 | { 146 | if (ex.GetBaseException() is SharpRfcException) 147 | throw ex; 148 | 149 | throw new SharpRfcCallException("RFC SOAP call failed.", this.requestBody.InnerXml, ex); 150 | } 151 | } 152 | 153 | static public string Beautify(XmlDocument doc) 154 | { 155 | StringBuilder sb = new StringBuilder(); 156 | XmlWriterSettings settings = new XmlWriterSettings(); 157 | settings.OmitXmlDeclaration = true; 158 | settings.Indent = true; 159 | settings.IndentChars = " "; 160 | settings.NewLineChars = "\r\n"; 161 | settings.NewLineHandling = NewLineHandling.Replace; 162 | using (XmlWriter writer = XmlWriter.Create(sb, settings)) 163 | { 164 | doc.Save(writer); 165 | } 166 | return sb.ToString(); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/TestCases/RfcReadTableTestCase.cs: -------------------------------------------------------------------------------- 1 | using SharpSapRfc.Plain; 2 | using SharpSapRfc.Soap; 3 | using SharpSapRfc.Test.Model; 4 | using System; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace SharpSapRfc.Test.TestCases 9 | { 10 | public class Soap_RfcReadTableTestCase : RfcReadTableTestCase 11 | { 12 | protected override SapRfcConnection GetConnection() 13 | { 14 | return new SoapSapRfcConnection("TST-SOAP"); 15 | } 16 | } 17 | 18 | public class Plain_RfcReadTableTestCase : RfcReadTableTestCase 19 | { 20 | protected override SapRfcConnection GetConnection() 21 | { 22 | return new PlainSapRfcConnection("TST"); 23 | } 24 | } 25 | 26 | public abstract class RfcReadTableTestCase 27 | { 28 | protected abstract SapRfcConnection GetConnection(); 29 | 30 | [Fact] 31 | public void ReadAllEntriesTest() 32 | { 33 | using (SapRfcConnection conn = this.GetConnection()) 34 | { 35 | var scarr = conn.ReadTable("SCARR"); 36 | Assert.Equal(18, scarr.Count()); 37 | } 38 | } 39 | 40 | [Fact] 41 | public void ReadAllFieldsTest() 42 | { 43 | using (SapRfcConnection conn = this.GetConnection()) 44 | { 45 | var scarr = conn.ReadTable("SCARR"); 46 | 47 | var aa = scarr.FirstOrDefault(x => x.Code == "AA"); 48 | Assert.Equal("AA", aa.Code); 49 | Assert.Equal("American Airlines", aa.Name); 50 | Assert.Equal("USD", aa.Currency); 51 | Assert.Equal("http://www.aa.com", aa.Url); 52 | } 53 | } 54 | 55 | [Fact] 56 | public void ReadSingleFieldTest() 57 | { 58 | using (SapRfcConnection conn = this.GetConnection()) 59 | { 60 | var scarr = conn.ReadTable("SCARR", fields: new string[] { "CARRID" }); 61 | Assert.Equal(18, scarr.Count()); 62 | 63 | var aa = scarr.FirstOrDefault(x => x.Code == "AA"); 64 | Assert.Equal("AA", aa.Code); 65 | Assert.Equal(null, aa.Name); 66 | Assert.Equal(null, aa.Currency); 67 | Assert.Equal(null, aa.Url); 68 | } 69 | } 70 | 71 | [Fact] 72 | public void ReadSingleEntryTest() 73 | { 74 | using (SapRfcConnection conn = this.GetConnection()) 75 | { 76 | var scarr = conn.ReadTable("SCARR", count:1); 77 | Assert.Equal(1, scarr.Count()); 78 | Assert.Equal("AA", scarr.ElementAt(0).Code); 79 | Assert.Equal("American Airlines", scarr.ElementAt(0).Name); 80 | Assert.Equal("USD", scarr.ElementAt(0).Currency); 81 | Assert.Equal("http://www.aa.com", scarr.ElementAt(0).Url); 82 | } 83 | } 84 | 85 | [Fact] 86 | public void ReadDeltaAirlineCompanyTest() 87 | { 88 | using (SapRfcConnection conn = this.GetConnection()) 89 | { 90 | var scarr = conn.ReadTable("SCARR", where: new string[] { "CARRID = 'DL'" }); 91 | 92 | Assert.Equal(1, scarr.Count()); 93 | Assert.Equal("DL", scarr.ElementAt(0).Code); 94 | Assert.Equal("Delta Airlines", scarr.ElementAt(0).Name); 95 | } 96 | } 97 | 98 | [Fact] 99 | public void ReadTwoFieldFromDeltaAirlineCompanyTest() 100 | { 101 | using (SapRfcConnection conn = this.GetConnection()) 102 | { 103 | var scarr = conn.ReadTable("SCARR", 104 | fields: new string[] { "CARRID", "CURRCODE" }, 105 | where: new string[] { "CARRID = 'DL'" } 106 | ); 107 | 108 | Assert.Equal(1, scarr.Count()); 109 | Assert.Equal("DL", scarr.ElementAt(0).Code); 110 | Assert.Equal("USD", scarr.ElementAt(0).Currency); 111 | 112 | Assert.Equal(null, scarr.ElementAt(0).Name); 113 | Assert.Equal(null, scarr.ElementAt(0).Url); 114 | Assert.Equal(0, scarr.ElementAt(0).Client); 115 | } 116 | } 117 | 118 | [Fact] 119 | public void WhenChar1IsFirstField() 120 | { 121 | using (SapRfcConnection conn = this.GetConnection()) 122 | { 123 | var objects = conn.ReadTable("TADIR", 124 | fields: new string[] { "DELFLAG", "OBJ_NAME" }, 125 | where: new string[] { "PGMID = 'R3TR'", "AND OBJECT = 'TABL'", "AND OBJ_NAME = 'TADIR'" } 126 | ); 127 | 128 | Assert.Equal(1, objects.Count()); 129 | Assert.Equal("TADIR", objects.ElementAt(0).Name); 130 | Assert.Equal(false, objects.ElementAt(0).DeletionFlag); 131 | } 132 | } 133 | 134 | [Fact] 135 | public void WhenChar1IsLastField() 136 | { 137 | using (SapRfcConnection conn = this.GetConnection()) 138 | { 139 | var objects = conn.ReadTable("TADIR", 140 | fields: new string[] { "OBJ_NAME", "DELFLAG" }, 141 | where: new string[] { "PGMID = 'R3TR'", "AND OBJECT = 'TABL'", "AND OBJ_NAME = 'TADIR'" } 142 | ); 143 | 144 | Assert.Equal(1, objects.Count()); 145 | Assert.Equal("TADIR", objects.ElementAt(0).Name); 146 | Assert.Equal(false, objects.ElementAt(0).DeletionFlag); 147 | } 148 | } 149 | 150 | [Fact] 151 | public void ReadTableAllFieldsType() 152 | { 153 | using (SapRfcConnection conn = this.GetConnection()) 154 | { 155 | ZMara mara = null; 156 | var maras = conn.ReadTable("ZSSRT_MARA"); 157 | 158 | Assert.Equal(3, maras.Count()); 159 | 160 | mara = maras.ElementAt(0); 161 | Assert.Equal(1, mara.Id); 162 | Assert.Equal("AOC MONITOR", mara.Name); 163 | Assert.Equal(254.54m, mara.Price); 164 | Assert.Equal(DateTime.MinValue, mara.Date); 165 | Assert.Equal(DateTime.MinValue, mara.Time); 166 | Assert.Equal(true, mara.IsActive); 167 | Assert.Equal(MaterialState.Blocked, mara.State); 168 | 169 | mara = maras.ElementAt(1); 170 | Assert.Equal(2, mara.Id); 171 | Assert.Equal("KOBO GLO", mara.Name); 172 | Assert.Equal(64m, mara.Price); 173 | Assert.Equal(new DateTime(2014, 6, 4), mara.Date); 174 | Assert.Equal(new DateTime(0001, 1, 1, 15, 42, 22), mara.Time); 175 | Assert.Equal(true, mara.IsActive); 176 | Assert.Equal(MaterialState.OutOfStock, mara.State); 177 | 178 | mara = maras.ElementAt(2); 179 | Assert.Equal(3, mara.Id); 180 | Assert.Equal("AVELL NOTEBOOK", mara.Name); 181 | Assert.Equal(21253154.2464m, mara.Price); 182 | Assert.Equal(new DateTime(2000, 1, 4), mara.Date); 183 | Assert.Equal(new DateTime(0001, 1, 1, 10, 0, 23), mara.Time); 184 | Assert.Equal(false, mara.IsActive); 185 | Assert.Equal(MaterialState.Available, mara.State); 186 | } 187 | } 188 | 189 | [Fact] 190 | public void ReadDateTimeSingleField() 191 | { 192 | using (SapRfcConnection conn = this.GetConnection()) 193 | { 194 | ZMaraSingleDateTime mara = null; 195 | var maras = conn.ReadTable("ZSSRT_MARA"); 196 | 197 | Assert.Equal(3, maras.Count()); 198 | 199 | mara = maras.ElementAt(0); 200 | Assert.Equal(1, mara.Id); 201 | Assert.Equal(null, mara.DateTime); 202 | 203 | mara = maras.ElementAt(1); 204 | Assert.Equal(2, mara.Id); 205 | Assert.Equal(new DateTime(2014, 6, 4, 15, 42, 22), mara.DateTime); 206 | 207 | mara = maras.ElementAt(2); 208 | Assert.Equal(3, mara.Id); 209 | Assert.Equal(new DateTime(2000, 1, 4, 10, 0, 23), mara.DateTime); 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SharpSapRfc 2 | =========== 3 | 4 | ## What is SharpSapRfc? 5 | 6 | SAP NCo 3 is a library developed by SAP that allows .Net Developers connect to SAP through very easy to use API. 7 | **Sharp SAP RFC** is a top-level library that makes it **even easier** to call remote functions on SAP systems. 8 | Just read the examples bellow and you will see how powerfull it is. 9 | 10 | Instead of this code: 11 | 12 | ```C# 13 | RfcDestination destination = RfcDestinationManager.GetDestination("TST"); 14 | RfcRepository repository = destination.Repository; 15 | IRfcFunction function = repository.CreateFunction("Z_SSRT_SUM"); 16 | function.SetValue("i_num1", 2); 17 | function.SetValue("i_num2", 4); 18 | function.Invoke(destination); 19 | int result = function.GetInt("e_result"); 20 | ``` 21 | 22 | You can write this: 23 | 24 | ```C# 25 | using (SapRfcConnection conn = new PlainSapRfcConnection("TST")) 26 | { 27 | var result = conn.ExecuteFunction("Z_SSRT_SUM", new { 28 | i_num1 = 2, 29 | i_num2 = 4 30 | }); 31 | 32 | int result = result.GetOutput("e_result"); 33 | } 34 | ``` 35 | 36 | You might be asking: It's almost the same! 37 | Well, yes, it is. 38 | 39 | But what about write this: 40 | 41 | ```C# 42 | using (SapRfcConnection conn = new PlainSapRfcConnection("TST")) 43 | { 44 | var customer = new ZCustomer(3, "Some Company", true); 45 | var result = conn.ExecuteFunction("Z_SSRT_ADD_CUSTOMER", new { 46 | i_customer = customer 47 | }); 48 | } 49 | 50 | public class ZCustomer 51 | { 52 | public int Id { get; set; } 53 | public string Name { get; set; } 54 | 55 | [RfcStructureField("ACTIVE")] 56 | public bool IsActive { get; set; } 57 | 58 | public ZCustomer(int id, string name, bool isActive) 59 | { 60 | this.Id = id; 61 | this.Name = name; 62 | this.IsActive = isActive; 63 | } 64 | } 65 | ``` 66 | 67 | Instead of: 68 | 69 | ```C# 70 | RfcDestination destination = RfcDestinationManager.GetDestination("TST"); 71 | RfcRepository repository = destination.Repository; 72 | IRfcFunction function = repository.CreateFunction("Z_SSRT_ADD_CUSTOMER"); 73 | 74 | IRfcStructure customer = function.GetStructure("i_customer"); 75 | customer.SetValue("ID", 3); 76 | customer.SetValue("NAME", "Some Company"); 77 | customer.SetValue("ACTIVE", true); 78 | 79 | function.SetValue("i_customer", customer); 80 | function.Invoke(destination); 81 | ``` 82 | 83 | That's more code, but it's because of `Customer` class. 84 | But because of this class we can work with strongly-typed parameters! 85 | 86 | Check this out. 87 | 88 | ```C# 89 | using (SapRfcConnection conn = new PlainSapRfcConnection("TST")) 90 | { 91 | var result = conn.ExecuteFunction("Z_SSRT_GET_ALL_CUSTOMERS"); 92 | var customers = result.GetTable("t_customers"); 93 | } 94 | ``` 95 | 96 | It`s way easier than a iterating on each row and building the object by yourself. 97 | It also works for input parameters! 98 | 99 | ## Two options available 100 | 101 | SharpSapRfc comes with two flavors: RFC and SOAP. Both options have the same interface, so you can swap between them with a single line of code. ABAP requiriments are the same for both libraries, just create an RFC-enable Function Module and you're good to go, be it with RFC or SOAP. 102 | 103 | ## Which one should I use? 104 | 105 | We generally recommend RFC protocol because it is faster than SOAP. The main advantage of SOAP is that enables you to publish you application outside your LAN and connect to SAP thought a common protocol (HTTP/HTTPS) combined with DMZ and Reverse Proxy, for example. 106 | 107 | ## How to swap between Plain RFC and SOAP 108 | 109 | All examples on this document are using Plain RFC (class is **PlainSapRfcConnection**). If you want to use SOAP, just change to **SoapSapRfcConnection**. The configuration file is different for each library. Examples are on the bottom of this document. 110 | 111 | ## How to install 112 | 113 | For Plain RFC x86 apps 114 | 115 | PM> Install-Package SharpSapRfc.Plain.x86 116 | 117 | For Plain RFC x64 apps 118 | 119 | PM> Install-Package SharpSapRfc.Plain.x64 120 | 121 | `Important:` To use Plain RFC we need to reference assemblies `sapnco.dll` and `sapnco_utils.dll` found in `SAP .NET Connector 3.0` (x86 or x64). Downloads for SAP Connectors are available at SAP Service Marketplace for customers and partners. More info at [http://service.sap.com/connectors](http://service.sap.com/connectors). 122 | 123 | For SOAP 124 | 125 | PM> Install-Package SharpSapRfc.Soap 126 | 127 | `Important:` There are no additional dependencies to run Soap RFC, but the target SAP server has to enable RFC calls in ICF. 128 | 129 | 130 | ## RFC Function Object 131 | 132 | If you think your code that calls RFC is too much complicated, you can take advantage of the **RFC Function Object Pattern**. 133 | With this pattern you can encapsulate your RFC call in a class that is used by SharpSapRfc. 134 | Take a look at the following example. 135 | 136 | ```C# 137 | using (SapRfcConnection conn = new PlainSapRfcConnection("TST")) 138 | { 139 | var customers = conn.ExecuteFunction(new GetAllCustomersFunction()); 140 | } 141 | 142 | //GetAllCustomersFunction is a RFC Function Object that encapsulate the configuration and parameters for calling the remote function 143 | public class GetAllCustomersFunction : RfcFunctionObject> 144 | { 145 | public override string FunctionName 146 | { 147 | get { return "Z_SSRT_GET_ALL_CUSTOMERS2"; } 148 | } 149 | 150 | public override IEnumerable GetOutput(RfcResult result) 151 | { 152 | return result.GetTable("e_customers"); 153 | } 154 | } 155 | ``` 156 | 157 | For more examples using simple return type and multiple return values, please check test case named [FunctionObjectTestCase](https://github.com/oenning/SharpSapRfc/blob/master/tests/TestCases/FunctionObjectTestCase.cs) 158 | 159 | 160 | ## RFC Read Table 161 | 162 | There's also a shortcut to the RFC_READ_TABLE function. 163 | 164 | You can use it like this: 165 | 166 | ```C# 167 | public class AirlineCompany 168 | { 169 | [RfcStructureField("MANDT")] 170 | public int Client { get; set; } 171 | [RfcStructureField("CARRID")] 172 | public string Code { get; set; } 173 | [RfcStructureField("CARRNAME")] 174 | public string Name { get; set; } 175 | [RfcStructureField("CURRCODE")] 176 | public string Currency { get; set; } 177 | [RfcStructureField("URL")] 178 | public string Url { get; set; } 179 | } 180 | 181 | //Reading all table entries 182 | using (SapRfcConnection conn = new PlainSapRfcConnection("TST")) 183 | { 184 | var scarr = conn.ReadTable("SCARR"); 185 | Assert.AreEqual(18, scarr.Count()); 186 | } 187 | 188 | //Reading two fields with where clause 189 | using (SapRfcConnection conn = new PlainSapRfcConnection("TST")) 190 | { 191 | var scarr = conn.ReadTable("SCARR", 192 | fields: new string[] { "CARRID", "CARRNAME" }, 193 | where: new string[] { "CARRID = 'DL'" } 194 | ); 195 | } 196 | ``` 197 | 198 | ## Configuration Example 199 | 200 | ###### Plain RFC 201 | 202 | ```xml 203 | 204 | 205 | 206 |
207 | 208 |
209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 219 | 220 | 221 | 222 | 223 | 224 | ``` 225 | 226 | 227 | ###### SOAP 228 | 229 | ```xml 230 | 231 | 232 |
233 | 234 | 235 | 236 | 237 | 243 | 244 | 245 | 246 | ``` 247 | 248 | ## All Features 249 | 250 | - Mapping for Structures and Tables 251 | - 2 remote fields (DATE and TIME) mapped to a single DateTime property 252 | - Enum Mapping (both numbers and strings) 253 | - Boolean. Use True and False instead of "X" and " 254 | - Shortcut to RFC_READ_TABLE (with Fluent API) 255 | - RFC Object Function Pattern 256 | - Easier RFC calls 257 | 258 | Take a look at the **tests** project for more examples. 259 | -------------------------------------------------------------------------------- /tests/SharpSapRfc.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {37086A57-834D-4ACC-B922-CE3ADA4B9F31} 8 | Library 9 | Properties 10 | SharpSapRfc.Test 11 | SharpSapRfc.Test 12 | v4.0 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 10.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | efb2a340 22 | 23 | 24 | true 25 | ..\bin\x64\Debug\ 26 | DEBUG;TRACE 27 | full 28 | x64 29 | prompt 30 | MinimumRecommendedRules.ruleset 31 | 32 | 33 | bin\x64\Release\ 34 | TRACE 35 | true 36 | pdbonly 37 | x64 38 | prompt 39 | MinimumRecommendedRules.ruleset 40 | 41 | 42 | true 43 | ..\bin\x86\Debug\ 44 | DEBUG;TRACE 45 | full 46 | x86 47 | prompt 48 | MinimumRecommendedRules.ruleset 49 | 50 | 51 | bin\x86\Release\ 52 | TRACE 53 | true 54 | pdbonly 55 | x86 56 | prompt 57 | MinimumRecommendedRules.ruleset 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ..\packages\xunit.1.9.2\lib\net20\xunit.dll 66 | 67 | 68 | ..\packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll 69 | 70 | 71 | 72 | 73 | False 74 | ..\sap\nco3-x64\sapnco.dll 75 | 76 | 77 | False 78 | ..\sap\nco3-x64\sapnco_utils.dll 79 | 80 | 81 | 82 | 83 | False 84 | ..\sap\nco3-x86\sapnco.dll 85 | 86 | 87 | False 88 | ..\sap\nco3-x86\sapnco_utils.dll 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | Always 141 | 142 | 143 | 144 | 145 | {db36edca-d7ce-4ce6-894e-8815f817c778} 146 | SharpSapRfc 147 | 148 | 149 | {64507431-51d3-4b2e-bee3-a1bbe333b228} 150 | SharpSapRfc.Plain 151 | 152 | 153 | {936443d3-2181-4cce-ae1d-bdb0ebdb64dd} 154 | SharpSapRfc.Soap 155 | 156 | 157 | 158 | 159 | 160 | 161 | False 162 | 163 | 164 | False 165 | 166 | 167 | False 168 | 169 | 170 | False 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 180 | 181 | 182 | 183 | 190 | --------------------------------------------------------------------------------