Calling a Sage X3 Object From ASP.NET
This is part 4 of a 5 part series on creating and consuming web services for Sage X3. This post will focus on calling an Sage X3 Object from ASP.NET that has been exposed as a web service.
To review previous posts in the series, click the links below:
Day 1 – Setting up X3 Web Services
Day 2 – Creating X3 Web Services
Day 3 – Testing X3 Web Services
Day 5 - Calling a Sage X3 Program from ASP.NET
Now on to our current topic Calling a Sage X3 Object from ASP.NET ...
The wsdl for the X3 web services is below, where x3v6prmp23.rkldev.local is the server name where the web services are hosted.
http://x3v6prmp23.rkldev.local:28880/adxwsvc/services/CAdxWebServiceXmlCC?wsdl
Calling the SOH object via Web Services
Create the Call Context
The call context must be set prior to calling a web service, and is passed as part of the web service call. This is where the language, the X3 user and password, and the X3 connection pool name are set.
// Initialize Context
X3DataAccessConn.CallContextClass oCallContext = GetCallContext();
oCallContext.CallContext.requestConfig = "adxwss.trace.on=off&adxwss.trace.size=16384&adonix.trace.on=off&adonix.trace.level=3&adonix.trace.size=8";
public
CallContextClass GetCallContext()
{
// Initialize connection values
CAdxCallContext callContext = new
CAdxCallContext();
CAdxWebServiceXmlCCService webService = new
CAdxWebServiceXmlCCService();
CAdxParamKeyValue[] paramKey = new
CAdxParamKeyValue[1];
CAdxResultXml resultXML = new
CAdxResultXml();
callContext.codeLang = "ENG"; // Connection language
callContext.codeUser = "admin"; // X3 user
callContext.password = "pwd"; // X3 password
callContext.poolAlias = "NATRAINV6"; // Connection pool name
CallContextClass context = new
CallContextClass(callContext, webService, paramKey, resultXML);
return context;
}
Retrieve Order Details
The c# code below can be used to retrieve Sales Orders details from X3. The ResultXML will be an XML string that matches the SOH object screen. In the example below, the value in sSohNum is the X3 Sales Order Number.
// Reads a Single Sales Order with Details
X3_SalesOrderDetails cSalesOrd = new
X3_SalesOrderDetails();
_success = true;
_result = "";
try
{
// Initialize Connection Values
X3DataAccessConn.CallContextClass oCallContext = _connDetails.GetCallContext();
// Complete call context
oCallContext.CallContext.requestConfig = "adxwss.trace.on=off&adxwss.trace.size=16384&adonix.trace.on=off&adonix.trace.level=3&adonix.trace.size=8";
oCallContext.ParamKey = new
CAdxParamKeyValue[1];
// Prepare paramKeyValue
oCallContext.ParamKey[0] = new
CAdxParamKeyValue();
oCallContext.ParamKey[0].key = "0"; // Other method: paramKey[0].key = "SOHNUM";
oCallContext.ParamKey[0].value = sSohNum;
// Call web service
oCallContext.ResultXML = oCallContext.WebService.read(oCallContext.CallContext, serviceName, oCallContext.ParamKey);
if (oCallContext.ResultXML.status == 1)
{
cSalesOrd = ExtractSODetails(oCallContext.ResultXML.resultXml.ToString());
}
}
catch (Exception ex)
{
_success = false;
_result = ex.ToString();
}
return cSalesOrd;
If the order is found, the ResultXML string will be similar to below:
<?xml version="1.0" encoding="UTF-8"?>
<RESULT>
<GRP ID="SOH0_1" >
<FLD NAME="SALFCY" TYPE="Char" >P21</FLD>
<FLD NAME="SOHTYP" TYPE="Char" >SOL</FLD>
<FLD NAME="ZSOHTYP" TYPE="Char" >Loan</FLD>
<FLD NAME="SOHNUM" TYPE="Char" >SOL000004</FLD>
<FLD NAME="REVNUM" TYPE="Integer" >0</FLD>
<FLD NAME="CUSORDREF" TYPE="Char" ></FLD>
<FLD NAME="ORDDAT" TYPE="Date" >20140226</FLD>
<FLD NAME="BPCORD" TYPE="Char" >C2201</FLD>
<FLD NAME="BPCNAM" TYPE="Char" >Beverly Hills Bike Shop</FLD>
<FLD NAME="CUR" TYPE="Char" >USD</FLD>
</GRP>
<GRP ID="SOH1_1" >
<FLD NAME="BPCINV" TYPE="Char" >C2201</FLD>
<FLD NAME="BPINAM" TYPE="Char" >Beverly Hills Bike Shop</FLD>
<FLD NAME="BPCPYR" TYPE="Char" >C2201</FLD>
<FLD NAME="ZBPCPYR" TYPE="Char" >Beverly Hills Bike Shop</FLD>
<FLD NAME="BPCGRU" TYPE="Char" >C2201</FLD>
<FLD NAME="ZBPCGRU" TYPE="Char" >Beverly Hills Bike Shop</FLD>
<FLD NAME="BPAADD" TYPE="Char" >ST002</FLD>
<FLD NAME="BPDNAM" TYPE="Char" >Beverly Hills Bike Shop</FLD>
</GRP>
<GRP ID="SOH1_2" >
<FLD NAME="PJT" TYPE="Char" ></FLD>
</GRP>
<GRP ID="SOH1_3" >
<LST NAME="REP" SIZE="1" TYPE="Char" >
<ITM>REP003</ITM>
</LST>
</GRP>
<GRP ID="SOH1_4" >
<FLD NAME="LNDRTNDAT" TYPE="Date" >20140227</FLD>
<FLD NAME="CCLREN" TYPE="Char" ></FLD>
<FLD NAME="VACBPR" TYPE="Char" >NTX</FLD>
<FLD NAME="ZVACBPR" TYPE="Char" >No Tax</FLD>
<FLD NAME="CUR" TYPE="Char" >USD</FLD>
<FLD NAME="ZCUR" TYPE="Char" >US Dollar</FLD>
<FLD MENULAB="Ex tax" MENULOCAL="243" NAME="PRITYP" TYPE="Integer" >1</FLD>
</GRP>
<GRP ID="SOH1_5" >
<FLD MENULAB="Not Managed" MENULOCAL="280" NAME="APPFLG" TYPE="Integer" >4</FLD>
<FLD MENULAB="Closed" MENULOCAL="415" NAME="ORDSTA" TYPE="Integer" >2</FLD>
<FLD MENULAB="Not Allocated" MENULOCAL="416" NAME="ALLSTA" TYPE="Integer" >1</FLD>
<FLD MENULAB="Shipped" MENULOCAL="417" NAME="DLVSTA" TYPE="Integer" >3</FLD>
<FLD MENULAB="Invoiced" MENULOCAL="418" NAME="INVSTA" TYPE="Integer" >3</FLD>
<FLD MENULAB="OK" MENULOCAL="419" NAME="CDTSTA" TYPE="Integer" >1</FLD>
<FLD MENULAB="OK" MENULOCAL="491" NAME="HLDSTA" TYPE="Integer" >1</FLD>
<FLD NAME="HLDBTN" TYPE="Char" >280</FLD>
<FLD NAME="HLDCOD" TYPE="Char" ></FLD>
</GRP>
<GRP ID="SOH1_6" >
<FLD MENULAB="No" MENULOCAL="1" NAME="OCNPRN" TYPE="Integer" >1</FLD>
<FLD MENULAB="No" MENULOCAL="1" NAME="BETFCY" TYPE="Integer" >1</FLD>
<FLD MENULAB="No" MENULOCAL="1" NAME="BETCPY" TYPE="Integer" >1</FLD>
</GRP>
<GRP ID="SOH2_1" >
<FLD NAME="STOFCY" TYPE="Char" >P21</FLD>
<FLD NAME="ZSTOFCY" TYPE="Char" >Bikes</FLD>
<FLD MENULAB="Normal" MENULOCAL="410" NAME="DLVPIO" TYPE="Integer" >1</FLD>
</GRP>
<GRP ID="SOH2_2" >
<FLD NAME="DEMDLVDAT" TYPE="Date" >20140226</FLD>
<FLD NAME="DAYLTI" TYPE="Integer" >0</FLD>
<FLD NAME="SHIDAT" TYPE="Date" >20140226</FLD>
</GRP>
<GRP ID="SOH2_3" >
<FLD NAME="MDL" TYPE="Char" >GRN</FLD>
<FLD NAME="ZMDL" TYPE="Char" ></FLD>
<FLD NAME="BPTNUM" TYPE="Char" >UPS</FLD>
</GRP>
<GRP ID="SOH2_4" >
<FLD NAME="LASDLVNUM" TYPE="Char" >P211402SDL00000002</FLD>
<FLD NAME="LASDLVDAT" TYPE="Date" >20140226</FLD>
</GRP>
<GRP ID="SOH2_5" >
<FLD MENULAB="Yes" MENULOCAL="1" NAME="ORDCLE" TYPE="Integer" >2</FLD>
<FLD MENULAB="No" MENULOCAL="1" NAME="ODL" TYPE="Integer" >1</FLD>
<FLD MENULAB="No" MENULOCAL="1" NAME="UNL" TYPE="Integer" >1</FLD>
</GRP>
<GRP ID="SOH2_6" >
<FLD MENULAB="Allowed" MENULOCAL="414" NAME="DME" TYPE="Integer" >1</FLD>
</GRP>
<GRP ID="SOH2_7" >
<FLD MENULAB="Detailed" MENULOCAL="450" NAME="ALLTYP" TYPE="Integer" >2</FLD>
</GRP>
<GRP ID="SOH3_1" >
<FLD MENULAB="One / Shipment" MENULOCAL="408" NAME="IME" TYPE="Integer" >1</FLD>
</GRP>
<GRP ID="SOH3_2" >
<FLD NAME="PTE" TYPE="Char" >NET30</FLD>
<FLD NAME="DEP" TYPE="Char" ></FLD>
<FLD NAME="ZDEP" TYPE="Char" ></FLD>
<FLD NAME="SQHNUM" TYPE="Char" ></FLD>
<FLD NAME="PRFNUM" TYPE="Char" ></FLD>
<FLD NAME="LASINVNUM" TYPE="Char" >INV000147</FLD>
<FLD NAME="LASINVDAT" TYPE="Date" >20140227</FLD>
</GRP>
<TAB DIM="30" ID="SOH3_4" SIZE="3" >
<LIN NUM="1" >
<FLD NAME="SHO" TYPE="Char" >Freight</FLD>
<FLD NAME="INVDTAAMT" TYPE="Decimal" >0</FLD>
<FLD MENULAB="Tax excl" MENULOCAL="2227" NAME="INVDTATYP" TYPE="Integer" >1</FLD>
</LIN>
<LIN NUM="2" >
<FLD NAME="SHO" TYPE="Char" >Discount %</FLD>
<FLD NAME="INVDTAAMT" TYPE="Decimal" >0</FLD>
<FLD MENULAB="%" MENULOCAL="2227" NAME="INVDTATYP" TYPE="Integer" >3</FLD>
</LIN>
<LIN NUM="3" >
<FLD NAME="SHO" TYPE="Char" >Discount $</FLD>
<FLD NAME="INVDTAAMT" TYPE="Decimal" >0</FLD>
<FLD MENULAB="Tax excl" MENULOCAL="2227" NAME="INVDTATYP" TYPE="Integer" >1</FLD>
</LIN>
</TAB>
<GRP ID="SOH4_2" >
<FLD NAME="DLRNOT" TYPE="Decimal" >0</FLD>
<FLD NAME="PFMTOT" TYPE="Decimal" >491.16</FLD>
</GRP>
<GRP ID="SOH4_3" >
<FLD NAME="ORDNOT" TYPE="Decimal" >3000</FLD>
</GRP>
<GRP ID="SOH4_4" >
<FLD NAME="ORDINVNOT" TYPE="Decimal" >3000</FLD>
<FLD NAME="ORDINVATI" TYPE="Decimal" >3000</FLD>
</GRP>
<TAB DIM="200" ID="SOH4_1" SIZE="1" >
<LIN NUM="1" >
<FLD NAME="NUMLIG" TYPE="Integer" >0</FLD>
<FLD NAME="ITMREF" TYPE="Char" >21101</FLD>
<FLD NAME="ITMDES" TYPE="Char" >Bicycle, R330, 54cm, Grey</FLD>
<FLD NAME="ITMDES1" TYPE="Char" >Bicycle, R330, 54cm, Grey</FLD>
<FLD NAME="DSTOFCY" TYPE="Char" >P21</FLD>
<FLD NAME="SAU" TYPE="Char" >EA</FLD>
<FLD NAME="QTY" TYPE="Decimal" >2</FLD>
<FLD NAME="SAUSTUCOE" TYPE="Decimal" >1</FLD>
<FLD NAME="STU" TYPE="Char" >EA</FLD>
<FLD NAME="ALLQTY" TYPE="Decimal" >0</FLD>
<FLD NAME="SHTQTY" TYPE="Decimal" >0</FLD>
<FLD NAME="WALLQTY" TYPE="Decimal" >0</FLD>
<FLD MENULAB="Detailed" MENULOCAL="450" NAME="DALLTYP" TYPE="Integer" >2</FLD>
<FLD NAME="TDLQTY" TYPE="Decimal" >0</FLD>
<FLD NAME="GROPRI" TYPE="Decimal" >1500</FLD>
<FLD NAME="DISCRGVAL1" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL2" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL3" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL4" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL5" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL6" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL7" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL8" TYPE="Decimal" >0</FLD>
<FLD NAME="DISCRGVAL9" TYPE="Decimal" >0</FLD>
<FLD NAME="NETPRI" TYPE="Decimal" >1500</FLD>
<FLD NAME="CPRPRI" TYPE="Decimal" >1254.42</FLD>
<FLD NAME="PFM" TYPE="Decimal" >245.58</FLD>
<FLD NAME="VACITM1" TYPE="Char" >NOR</FLD>
<FLD NAME="VACITM2" TYPE="Char" ></FLD>
<FLD NAME="VACITM3" TYPE="Char" ></FLD>
<FLD NAME="REP1" TYPE="Char" >REP003</FLD>
<FLD NAME="REPRAT1" TYPE="Decimal" >5</FLD>
<FLD NAME="REP2" TYPE="Char" ></FLD>
<FLD NAME="REPRAT2" TYPE="Decimal" >0</FLD>
<FLD NAME="REPCOE" TYPE="Decimal" >1</FLD>
<FLD NAME="DDEMDLVDAT" TYPE="Date" >20140226</FLD>
<FLD NAME="DDAYLTI" TYPE="Integer" >0</FLD>
<FLD NAME="DSHIDAT" TYPE="Date" >20140226</FLD>
<FLD NAME="EXTDLVDAT" TYPE="Date" >20140226</FLD>
<FLD NAME="DBPAADD" TYPE="Char" >ST002</FLD>
<FLD NAME="CNDNAM" TYPE="Char" >000000000000005</FLD>
<FLD NAME="USEPLC" TYPE="Char" ></FLD>
<FLD MENULAB="Normal" MENULOCAL="410" NAME="DDLVPIO" TYPE="Integer" >1</FLD>
<FLD MENULAB="Route Code 1" MENULOCAL="409" NAME="DDRN" TYPE="Integer" >1</FLD>
<FLD NAME="DMDL" TYPE="Char" >GRN</FLD>
<FLD NAME="DBPTNUM" TYPE="Char" >UPS</FLD>
<FLD NAME="PRECOD" TYPE="Char" ></FLD>
<FLD NAME="PCK" TYPE="Char" ></FLD>
<FLD NAME="PCKCAP" TYPE="Decimal" >0</FLD>
<FLD NAME="CCE1" TYPE="Char" ></FLD>
<FLD NAME="CCE2" TYPE="Char" >BIKE</FLD>
<FLD NAME="CCE3" TYPE="Char" >RESELLER</FLD>
<FLD NAME="CCE4" TYPE="Char" ></FLD>
<FLD NAME="CCE5" TYPE="Char" ></FLD>
<FLD NAME="CCE6" TYPE="Char" ></FLD>
<FLD NAME="CCE7" TYPE="Char" ></FLD>
<FLD NAME="CCE8" TYPE="Char" ></FLD>
<FLD NAME="CCE9" TYPE="Char" ></FLD>
<FLD NAME="CCE10" TYPE="Char" ></FLD>
<FLD NAME="CCE11" TYPE="Char" ></FLD>
<FLD NAME="CCE12" TYPE="Char" ></FLD>
<FLD NAME="CCE13" TYPE="Char" ></FLD>
<FLD NAME="CCE14" TYPE="Char" ></FLD>
<FLD NAME="CCE15" TYPE="Char" ></FLD>
<FLD NAME="CCE16" TYPE="Char" ></FLD>
<FLD NAME="CCE17" TYPE="Char" ></FLD>
<FLD NAME="CCE18" TYPE="Char" ></FLD>
<FLD NAME="CCE19" TYPE="Char" ></FLD>
<FLD NAME="CCE20" TYPE="Char" ></FLD>
<FLD MENULAB="Firm" MENULOCAL="317" NAME="DEMSTA" TYPE="Integer" >1</FLD>
<FLD MENULAB="Stock" MENULOCAL="445" NAME="FMI" TYPE="Integer" >1</FLD>
<FLD NAME="FMINUM" TYPE="Char" ></FLD>
<FLD NAME="LINORDNOT" TYPE="Decimal" >3000</FLD>
<FLD NAME="LINORDATI" TYPE="Decimal" >3000</FLD>
<FLD NAME="LINPFM" TYPE="Decimal" >491.16</FLD>
<FLD NAME="DETSQHNUM" TYPE="Char" ></FLD>
<FLD MENULAB="Normal" MENULOCAL="423" NAME="LINTYP" TYPE="Integer" >1</FLD>
<FLD MENULAB="No" MENULOCAL="439" NAME="FOCFLG" TYPE="Integer" >1</FLD>
<FLD MENULAB="Closed" MENULOCAL="279" NAME="SOQSTA" TYPE="Integer" >3</FLD>
<FLD NAME="DCCLREN" TYPE="Char" ></FLD>
</LIN>
</TAB>
<GRP ID="ADB1_1" >
<LST NAME="BPRNAM" SIZE="1" TYPE="Char" >
<ITM>Beverly Hills Bike Shop</ITM>
</LST>
<FLD NAME="BPAADD" TYPE="Char" >ST001</FLD>
<FLD NAME="CRY" TYPE="Char" >US</FLD>
<FLD NAME="CRYNAM" TYPE="Char" >United States of America</FLD>
<LST NAME="BPAADDLIG" SIZE="1" TYPE="Char" >
<ITM>854 S Robertson Blvd</ITM>
</LST>
<FLD NAME="POSCOD" TYPE="Char" >90035</FLD>
<FLD NAME="CTY" TYPE="Char" >Los Angeles</FLD>
<FLD NAME="ITINERAIRE" TYPE="Char" ></FLD>
<FLD NAME="SAT" TYPE="Char" >CA</FLD>
<FLD NAME="CNTNAM" TYPE="Char" >000000000000005</FLD>
<FLD NAME="ZCNTNAM" TYPE="Char" ></FLD>
</GRP>
<GRP ID="ADB2_1" >
<LST NAME="BPRNAM" SIZE="1" TYPE="Char" >
<ITM>Beverly Hills Bike Shop</ITM>
</LST>
<FLD NAME="BPAADD" TYPE="Char" >ST002</FLD>
<FLD NAME="CRY" TYPE="Char" >US</FLD>
<FLD NAME="CRYNAM" TYPE="Char" >United States of America</FLD>
<LST NAME="BPAADDLIG" SIZE="1" TYPE="Char" >
<ITM>1053 Madison St</ITM>
</LST>
<FLD NAME="POSCOD" TYPE="Char" >98104</FLD>
<FLD NAME="CTY" TYPE="Char" >Seattle</FLD>
<FLD NAME="ITINERAIRE" TYPE="Char" ></FLD>
<FLD NAME="SAT" TYPE="Char" >WA</FLD>
<FLD NAME="CNTNAM" TYPE="Char" >000000000000005</FLD>
<FLD NAME="ZCNTNAM" TYPE="Char" ></FLD>
</GRP>
<GRP ID="ADB3_1" >
<LST NAME="BPRNAM" SIZE="1" TYPE="Char" >
<ITM>Beverly Hills Bike Shop</ITM>
</LST>
<FLD NAME="BPAADD" TYPE="Char" >ST002</FLD>
<FLD NAME="CRY" TYPE="Char" >US</FLD>
<FLD NAME="CRYNAM" TYPE="Char" >United States of America</FLD>
<LST NAME="BPAADDLIG" SIZE="1" TYPE="Char" >
<ITM>1053 Madison St</ITM>
</LST>
<FLD NAME="POSCOD" TYPE="Char" >98104</FLD>
<FLD NAME="CTY" TYPE="Char" >Seattle</FLD>
<FLD NAME="ITINERAIRE" TYPE="Char" ></FLD>
<FLD NAME="SAT" TYPE="Char" >WA</FLD>
<FLD NAME="CNTNAM" TYPE="Char" >000000000000005</FLD>
<FLD NAME="ZCNTNAM" TYPE="Char" ></FLD>
</GRP>
<GRP ID="ADXTEC" >
<FLD NAME="WW_MODSTAMP" TYPE="Char" ></FLD>
<FLD NAME="WW_MODUSER" TYPE="Char" ></FLD>
</GRP>
</RESULT>
Submit the Order
Construct the XML for the Order Create:
// XML Header to be included in all xml input strings
private
const
string Cn_XMLHeader = "<?xml version="1.0" encoding="UTF-8"?>";
StringBuilder xmlInput = new
StringBuilder(Cn_XMLHeader);
try
{
// Initialize Context
X3DataAccessConn.CallContextClass oCallContext = _connDetails.GetCallContext();
// Complete call context
oCallContext.CallContext.requestConfig = "adxwss.trace.on=off&adxwss.trace.size=16384&adonix.trace.on=off&adonix.trace.level=3&adonix.trace.size=8";
// Build xml string - starts with the fixed header
xmlInput.Append("<PARAM>");
// Add customer code
addField(xmlInput, "BPCORD", so.SoldToCustNum);
// Add Customer Ref Nbr
addField(xmlInput, "CUSORDREF", so.CustPORefNbr);
// Add site code
addField(xmlInput, "SALFCY", so.SalesSite);
// Add order type
addField(xmlInput, "SOHTYP", hdr.Sohtyp_OrderType);
// Add Shipment site
addField(xmlInput, "STOFCY", so.SalesSite);
// Add Requested Delivery Date
addField(xmlInput, "DEMDLVDAT", hdr._demdlvdat_ReqDelDate);
// Add order lines
addLines(xmlInput, so.Lines);
xmlInput.Append("</PARAM>");
// Call web service
oCallContext.ResultXML = oCallContext.WebService.save(oCallContext.CallContext, "ZSOH", xmlInput.ToString());
// Loop through results
foreach (CAdxMessage msg in oCallContext.ResultXML.messages)
{
_result += msg.message.ToString() + "^";
}
}
catch (Exception ex)
{
_success = false;
_result = ex.ToString();
}
if (_success == false) { _result += xmlInput.ToString(); }
The xmlInput string will look similar to below:
<PARAM>
<FLD NAME="BPCORD" >C3301</FLD>
<FLD NAME="CUSORDREF" >PO112613</FLD>
<FLD NAME="STOFCY" >P21</FLD>
<TAB ID="SOH4_1" SIZE="2" >
<LIN NUM="1" >
<FLD NAME="ITMREF" >21304</FLD>
<FLD NAME="QTY" >2</FLD>
<FLD NAME="DSTOFCY" >P21</FLD>
</LIN>
<LIN NUM="2" >
<FLD NAME="ITMREF" >22301</FLD>
<FLD NAME="QTY" >4</FLD>
<FLD NAME="DSTOFCY" >D22</FLD>
</LIN>
</TAB>
</PARAM>
If the order creation fails, the errors will be returned in the ResultXML.messages.
Need Help with Web Services?
Contact us if you want to learn more about Sage ERP X3 Web Services features or to request help with your system.
The Ultimate Web Services Guide For Sage X3
Want all 5 days in one comprehensive guide that you can download and take with you?
Leave your name below and we'll send you a copy of our Ultimate Guide to Sage X3 Web Services. This 38-page how-to manual provides everything you need to know about using X3 web services with screenshots, step-by-step instructions, and insider tips from our expert consultants.