Thursday, September 16, 2010

Tips: FedEx WebService for Rate Quotes C#/Asp.Net

After a long run of using the UPS quotes for online orders, we moved to FedEx. So that meant, I needed to update the ecommerce app to reflect our new shipper.

This task wasn't *too* hard since I had already written the UPS one about 4 years ago, but the FedEx one uses a WebService, for the UPS rates, I sent an HttpWebRequest. I don't know why they don't have webservice, maybe they do now...

Anyhow, hopefully these tips will help get you started on your way.

1. Get the WSDL. To do this, I downloaded the WSDL file from FedEx, this file includes the RateService_v9.wsdl file. I unzipped this file to my local webserver, then to my production webserver.

2. Create a reference to the WebService in Visual Studio. In my project, I right-clicked then selected Add Web Reference. Then I pointed the reference to my localhost (http://localhost/fedex/RateService_v9.wsdl) and gave it a name.

3. Requested a Test Key from FedEx that I plugged into the values Account Number, Meter Number, Key and Password.

4. Downloaded the FedEx example for getting Rate Service Quotes. Modified it to my liking. Here's a snippet:

RateRequest request = new RateRequest();

RateService service = new RateService(); // Initialize the service

request.WebAuthenticationDetail = new WebAuthenticationDetail();
request.WebAuthenticationDetail.UserCredential = new WebAuthenticationCredential();
request.WebAuthenticationDetail.UserCredential.Key = this._key; // Replace "XXX" with the Key
request.WebAuthenticationDetail.UserCredential.Password = this._password; // Replace "XXX" with the Password
//
request.ClientDetail = new ClientDetail();
request.ClientDetail.AccountNumber = this._accountNumber; // account number
request.ClientDetail.MeterNumber = this._meter; // meter number
//
request.TransactionDetail = new TransactionDetail();
request.TransactionDetail.CustomerTransactionId = "MY-RATE-REQUEST";
//
request.Version = new VersionId(); // automatically set from wsdl


if (packages != null && packages.Count > 0)
{
SetShipmentDetails(request, packages.Count);

int i = 0;
decimal totalWeight = 0.0M;

foreach (Package package in packages){
RequestedPackageLineItem rpli = new RequestedPackageLineItem();
Weight packageWeight = new Weight();
packageWeight.Value = (decimal)package.Weight;
packageWeight.Units = WeightUnits.LB;
rpli.Weight = packageWeight;

totalWeight += (decimal)package.Weight;

request.RequestedShipment.RequestedPackageLineItems[i] = rpli;
i++;
}

Weight weight = new Weight();
weight.Value = totalWeight;
weight.Units = WeightUnits.LB;
request.RequestedShipment.TotalWeight = weight;

}
.....

5. Once I was satisfied with the results, I requested a Production Key from FedEx then plugged in those values. I kept receiving a "Meter Number is missing or invalid".

This where I got tripped up. I had to change the URL being called in the app.config (that seems to have been created for me with properties in it). There is a url value that is referenced by the WebService.

However, even though I kept changing this value, it would continue to use the old value which was the test site, and of course, give me the same error - since my meter number was a production one not a test one.

Finally, I thought to open up the Properties - Settings.settings file. Right when I double-clicked the file to open it, the proprety inside there was updated with the new value I had in the app.config file.

I assume it was some sort of Visual Studio 2010 thing. It seems like it was supposed to auto-update but it didn't. Later, to be sure, I just updated it manually.

After that everything worked like a charm.

FYI:
(for SOAP Requests)
The test url is: https://wsbeta.fedex.com:443/web-services/rate
The production url is: https://gateway.fedex.com:443/web-services

26 comments:

  1. Thank you, thank you, and thank you. I just completed the same exact task, except that I was upgrading from an old fubar FedEx to the newest web service version in order to increase stability, speed and accuracy in rates requests.

    However, I've been working in a little bit more of the root code world of PHP working with libxml and the SOAP Client class so things are a bit weirder when trying to track problems down.

    I knew that the SOAP Client had a function of $client->__setLocation(), but I wasn't entirely sure of where to set the new location once I got the production keys and continued getting 803 Errors of Meter Number is missing or invalid. But my hunch was that I was hitting the test and not production server.

    Your insight here and reference to the production url totally saved me here from banging my head around for hours. Thanks again!

    If you ever post back to your comments, I'd love to hear where you found that production URL. I didn't see it anywhere in the FedEx system or in the WSDL.

    Cheers

    ReplyDelete
  2. i can't quite remember where i finally got the production url. it may have been by googling it or by sending a request to the fedex support and asking for it.

    which is a real pain, it should be provided at the time your credentials are given. would have saved me some time -- not having to dig around for it.

    i'm sooo glad you got your stuff working. those generic error codes are annoying!

    ReplyDelete
  3. Excellent post!

    I've made the same migration - from UPS to FedEx

    God bless you!

    Doru PIRVU

    ReplyDelete
  4. hi frnd, can i ask where did you find WSDL? and how r u getting final result? i downloaded the code but not able to find wsdl.

    ReplyDelete
  5. Thank you for posting the production server address!

    ReplyDelete
  6. @UTS I believe it is located in the zip file i downloaded (RateService_v9.zip)

    ReplyDelete
  7. elleshort can you tell me where/how you used the initialized "service"? Is "package" an object type? I don't find it in the WSDL. I was looking for a: RateReply reply = service.getRates(request); but probably this use was implied and not used in your snippet. Just curious, as my code was shut down in web service migration and now my clients are broke. Guess you can call it panic mode.

    ReplyDelete
  8. @pathfinder did you set up the reference as a web reference in your project and then include that reference in your 'using' area like:

    using Shipping.Wrapper.RateServiceWebReference;

    ReplyDelete
  9. @pathfinder i got the name RateServiceWebRefence from the 'Folder Name' of the web reference I set up..

    Shipping.Wrapper.RateServiceWebReference.RateService

    ReplyDelete
  10. Thank you so much @elleshort. I have it running now. Phew! Pissed off clients. FEDEX shut me down with no warning. Nothing on web site about forced migration. Anyway, my net price is showing two decimals only if digits not zero. Like this "FEDEX, 6.3, GROUND, TWO-DAYS" Any luck parsing the RatedShipmentDetail to read like a price, $X.XX?

    ReplyDelete
  11. @pathfinder, yes you will have to loop thru the results. then pull out the amount from the rated shipment detail like this:

    ratedShipmentDetail.ShipmentRateDetail.TotalNetCharge.Amount

    into a decmial variable or whatever works best for you.

    ReplyDelete
  12. Great post. Can I ask how you you display the return data in the asp.net pages of your app? In the code from the FedEx examples all responses are 'Console.WriteLine' Thanks!

    ReplyDelete
  13. Drop-down in asp page:

    <div><asp:DropDownList ID="ddShippingRates" runat="server" SkinID="dropdownSmall"></asp:DropDownList></div>

    Code to populate:
    try
    {

        List<RatesResult> rates = WebShippingManager.GetRates(packages, zip, country, residential, surcharge, handlingFee);

        if (rates != null)
         this.ddShippingRates.Items.Clear();

         foreach (RatesResult rate in rates)
         {
          if (string.IsNullOrEmpty(rate.ErrorDescription))
          {
          this.ddShippingRates.Items.Add(new ListItem(rate.ToString(), rate.TotalCharge.ToString()));
          }
          else
          {
          upsError = upsError + " " + rate.ErrorDescription;
          }
          }
          }
    catch (Exception ex)
    {
          string displayError = "We were unable to retrieve shipping quotes for this order. Please ensure a valid shipping zip code was supplied.";
         
    WebError webError = new WebError(Request, true, CurrentUser.Email, true, displayError, ex, true, string.Empty);

    }

    ReplyDelete
  14. Thanks for the response. I downloaded the C# code exampl from FedEx and for example in the ShowRateReply() they use console.writeline. I tried writing the response to a web control in this function but can not access the controls from with in the method. I'm sure it something I am missing.....

    private static void ShowRateReply(localhost.RateReply reply)
    {
    Console.WriteLine("RateReply details:");


    foreach (localhost.RateReplyDetail rateReplyDetail in reply.RateReplyDetails)
    {
    if (rateReplyDetail.ServiceTypeSpecified)
    Console.WriteLine("Service Type: {0}", rateReplyDetail.ServiceType);
    if (rateReplyDetail.PackagingTypeSpecified)
    Console.WriteLine("Packaging Type: {0}", rateReplyDetail.PackagingType);
    Console.WriteLine();
    foreach (localhost.RatedShipmentDetail shipmentDetail in rateReplyDetail.RatedShipmentDetails)
    {
    ShowShipmentRateDetails(shipmentDetail);
    Console.WriteLine();
    }
    ShowDeliveryDetails(rateReplyDetail);
    Console.WriteLine("**********************************************************");



    }
    }

    Thanks!!

    ReplyDelete
  15. capture the values you need into a list, then have the method return that list instead of void kind of like this:

    private static List ShowRateReply(localhost.RateReply reply)
    {


    List lstValues() = new List();

    foreach (localhost.RateReplyDetail rateReplyDetail in reply.RateReplyDetails)
    {

    foreach (localhost.RatedShipmentDetail shipmentDetail in rateReplyDetail.RatedShipmentDetails)
    {
    string rate = shipmentDetail.Rate; //<-- just guessing the should have some sort of data you want
    lstValues().Add(rate);
    }

    }
    return lstValues();
    }

    however, you probably want to return list of class type rather than a simple string. Like you could make a class called Rate that has the values you are interested in. Does that make sense?

    ReplyDelete
  16. Still getting an error when CreateRateRequest(); Getting reply.HighestSeverity == 'ERROR'. Do you know if there is a asp.net implementation download that somone has posted? Thanks elleshort!

    ReplyDelete
  17. I have console class of Fed ex RateAvailableServiceWebServiceClient which is successfully run with console output.Now i want to use this in my Asp.net we site.
    i use the

    1.Bin RateAvailableServiceWebServiceClient.Dll
    2.web service
    3.Web Reference

    Still there is error in using with namespaces so i cat access any method or object of this.
    Can anybody tell me how to use this console class in Asp.net Website?

    ReplyDelete
  18. After adding my "using..." reference, all the objects are recognized except RateService, which is this line:

    RateService service = new RateService(); // Initialize the service

    What am I doing wrong? Or how do I get it to recognize RateService?

    ReplyDelete
  19. It has been a long time since I have working on the code but off the top of my head I am thinking I made my own class called RateService? If there is no reference to be found that is likely what I did.

    ReplyDelete
  20. Thanks, Elle. Maybe it's that I'm using VS 2012 and this article is from 2010. 2012 indicates that the "Add a Webservice" procedure is deprecated, so I should Add a Service Reference. That seems to generate all the necessary code, EXCEPT for the RateService piece. I'm wondering if I should instead use WDSL.exe to generate the necessary classes for my local WDSL file? Thanks again.

    ReplyDelete
  21. Ok. I went back to look at the code, I added a Service Reference to the project it was added as RateServiceWebReference and that is what I am calling when I am instantiating RateService().

    So it must be your reference is not being brought into the page for some reason. You will have to added like this:

    using XXX.XXX.RateServiceWebReference;

    ReplyDelete
  22. Background: Visual Studio 2010 using vb.net

    I know this has been a while, but I am having a similar issue with RateService not being defined.

    Here are the steps I took:
    1) I copied WSDL file to /fedex/RateService_v14.wsdl - success
    2) I created a Service Reference to it using URL http://localhost/fedex/RateService_v14.wsdl and named the reference FedExRates - success
    3) I copied the RateWebServiceClient.vb file from the downloaded files from FedEx (which are v13 I might add)
    4) I replaced "Imports RateWebServiceClient.RateWebReference" with "Imports FedExRates" and it seems to accept it.
    5) All the references in the RateWebServiceClient work accept "RateService".

    Checking the intellisense for FedExRates there are no items that start with RateService or RateWebService etc....

    I had downloaded the entire solution from the FedEx dev site and it had a references.vb file there and it contained the sub-routines needed. I am trying to manually port those into my code file and use them from there.... but i don't like it......

    ReplyDelete
  23. It has been a while, can you try to call RateWebService instead. Or the name that you gave your service maybe.

    I think I may have added my reference as 'RateServiceWebReference' and given it a name of 'RateService'. Let me know if that works because it is a common problem on this blog post :)

    ReplyDelete
  24. Hi Elleshort,

    I am soooo new to this so pardon the stupid question. But where does the return happen with the FedEx Rates? The code isn't crashing at all it just isn't returning a rate.

    Thanks in advance

    ReplyDelete
  25. This is a very old post so I am not sure if FedEx calls have changed. You probably need to debug your code to verify the calls are being sent correctly.

    ReplyDelete