Posted by admin on 7/23/2010 7:27 AM | Comments (0)

I am using the USPS shipping apis (http://www.usps.com/webtools/shipping.htm) to create USPS priority mail labels from ecommerce orders.  I thought I’d share some things I learned.

  • First you have to sign up for web services here (https://secure.shippingapis.com/registration/).  You’ll get your userid emailed to you; you don’t need your password as far as I can tell.
  • I used the wrapper here in my code  (http://www.codeproject.com/KB/cs/USPS_Web_Tools_Wrapper.aspx).  It isn’t rocket science, and you could roll your own, but he already created all of the xml, so why reinvent the wheel?
  • If you’re using DeliveryConfirmationV3 to generate your labels (why do they call the api that can generate a label “Delivery confirmation” – but I digress), you might get an error like the following:

    <Error>
        <Number>80040b1a</Number>
        <Description>API Authorization failure. DeliveryConfirmationV3 is not a valid API name for this protocol.</Description>
        <Source>UspsCom::DoAuth</Source>
    </Error>
     
    The solution is to test against their production servers, not their test servers.  Maybe there’s a way to make it work on their test servers, but I saw some posts on the web (not sure as to their accuracy) that stated you can’t.  Anyway, to be able to use the production system, you have to call with your user id that you got when you signed up, and explain the situation.  Here’s the contact information.

    USPS Internet Customer Care Center (ICCC). The ICCC is staffed from 7:00AM to 11:00PM Eastern Time.

    Email: icustomercare@usps.com

    Telephone: 1-800-344-7779 (7:00AM to 11:00PM ET)


    • Here’s the code I borrowed/wrote to create a label and save it as a tif to a file.  Again, it’s using the library I referenced above.  The key is that you have to convert the byte array from base 64, or else it won’t be a valid tif (or PDF, if you choose that option) when you save it.

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using NUnit.Framework;
      using MAX.USPS;
      using System.IO;
      using System.Drawing;
       
      namespace TestHarness
      {
          [TestFixture]
          class USPSTests
          {
       
              [Test]
              public void CreateLabel()
              {
                  USPSManager m = new USPSManager("468WESTC3009", false);
                  Package p = new Package();
                  p.FromAddress.Contact = "John Smith";
                  p.FromAddress.Address2 = "475 L'Enfant Plaza, SW";
                  p.FromAddress.City = "Washington";
                  p.FromAddress.State = "DC";
                  p.FromAddress.Zip = "20260";
                  p.ToAddress.Contact = "Joe Customer";
                  p.ToAddress.Address1 = "STE 201";
                  p.ToAddress.Address2 = "6060 PRIMACY PKWY";
                  p.ToAddress.City = "Memphis";
                  p.ToAddress.State = "TN";
                  p.WeightInOunces = 2;
                  p.ServiceType = ServiceType.Priority;
                  p.SeparateReceiptPage = false;
                  p.LabelImageType = LabelImageType.TIF;
                  p.PackageSize = PackageSize.Regular;
                  p.PackageType = PackageType.Flat_Rate_Box;
                  p = m.GetDeliveryConfirmationLabel(p);
       
                  //important - the byte array is base 64, so you have to convert it
                  //there may be a better way than the code below, but it gets the job done
                  System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
                  var str = enc.GetString(p.ShippingLabel);
                  var bytes = Convert.FromBase64String(str);
                  File.WriteAllBytes("c:\\temp\\label.tif", bytes);
              }
          }
      }

    That’s all.  Hopefully this will help someone.

    Update after using the library above a couple more days:

    The values passed need to be XML encoded.  In addition, since they’re passed via URL, they need to be URL encoded.  To XML encode a string, there’s a little-known method called System.Security.SecurityElement.Escape in the .net framework.

    Also, the library above wasn’t retrieving the delivery confirmation number.  I updated the project to build against the 4.0 framework, and then used XML parsing to get the delivery confirmation and return it via a new property on Package (you’ll need to add it to the library above).

    The new code for USPSManager in the library above is here.

    //////////////////////////////////////////////////////////////////////////
    ///This software is provided to you as-is and with not warranties!!!
    ///Use this software at your own risk.
    ///This software is Copyright by Scott Smith 2006
    ///You are free to use this software as you see fit.
    //////////////////////////////////////////////////////////////////////////
     
     
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Net;
    using System.Xml;
    using System.Xml.Linq;
    using System.Security;
    using System.Web;
     
    namespace MAX.USPS
    {
        public class USPSManager
        {
            #region Private Members
            private const string ProductionUrl = "https://secure.shippingapis.com/ShippingAPI.dll";
            private const string TestingUrl = "https://secure.shippingapis.com/ShippingAPITest.dll";//"http://testing.shippingapis.com/ShippingAPITest.dll";
            private WebClient web;
            private string _userid;
            #endregion
     
            #region Constructors
     
            /// <summary>
            /// Creates a new USPS Manager instance
            /// </summary>
            /// <param name="USPSWebtoolUserID">The UserID required by the USPS Web Tools</param>
            public USPSManager(string USPSWebtoolUserID)
            {
                web = new WebClient();
                _userid = USPSWebtoolUserID;
                _TestMode = false;
                
            }
            /// <summary>
            /// Creates a new USPS Manager instance
            /// </summary>
            /// <param name="USPSWebtoolUserID">The UserID required by the USPS Web Tools</param>
            /// <param name="testmode">If True, then the USPS Test URL will be used.</param>
            public USPSManager(string USPSWebtoolUserID, bool testmode)
            {
                _TestMode = testmode;
                web = new WebClient();
                _userid = USPSWebtoolUserID;
            }
     
            #endregion
     
            #region Properties
            private bool _TestMode;
            /// <summary>
            /// Determines if the Calls to the USPS server is made to the Test or Production server.
            /// </summary>
            public bool TestMode
            {
                get { return _TestMode; }
                set { _TestMode = value; }
            }
     
            #endregion
     
            #region Address Methods
            /// <summary>
            /// Validate a single address
            /// </summary>
            /// <param name="address">Address object to be validated</param>
            /// <returns>Validated Address</returns>
            public Address ValidateAddress(Address address)
            {
                try
                {
                    string validateUrl = "?API=Verify&XML=<AddressValidateRequest USERID=\"{0}\"><Address ID=\"{1}\"><Address1>{2}</Address1><Address2>{3}</Address2><City>{4}</City><State>{5}</State><Zip5>{6}</Zip5><Zip4>{7}</Zip4></Address></AddressValidateRequest>";
                    string url = GetURL() + validateUrl;
                    url = String.Format(url, _userid, address.ID.ToString(), address.Address1, address.Address2, address.City, address.State, address.Zip, address.ZipPlus4);
                    string addressxml = web.DownloadString(url);
                    if (addressxml.Contains("<Error>"))
                    {
                        int idx1 = addressxml.IndexOf("<Description>") + 13;
                        int idx2 = addressxml.IndexOf("</Description>");
                        int l = addressxml.Length;
                        string errDesc = addressxml.Substring(idx1, idx2 - idx1);
                        throw new USPSManagerException(errDesc);
                    }
                    
                    return Address.FromXml(addressxml);
                }
                catch(WebException ex)
                {
                    throw new USPSManagerException(ex);
                }
            }
            /// <summary>
            /// Get the zip code by providing an Address object with a city and state
            /// </summary>
            /// <param name="city">City</param>
            /// <param name="state">State</param>
            public Address GetZipcode(string city, string state)
            {
                Address a = new Address();
                a.City = city;
                a.State = state;
                return GetZipcode(a);
            }
     
            /// <summary>
            /// Get the zip code by providing an Address object with a city and state
            /// </summary>
            /// <param name="address">Address Object</param>
            /// <returns>Address Object</returns>
            public Address GetZipcode(Address address)
            {
                try
                {
                    //The address must contain a city and state
                    if (address.City == null || address.City.Length < 1 || address.State == null || address.State.Length < 1)
                        throw new USPSManagerException("You must supply a city and state for a zipcode lookup request.");
     
                    string zipcodeurl = "?API=ZipCodeLookup&XML=<ZipCodeLookupRequest USERID=\"{0}\"><Address ID=\"{1}\"><Address1>{2}</Address1><Address2>{3}</Address2><City>{4}</City><State>{5}</State></Address></ZipCodeLookupRequest>";
                    string url = GetURL() + zipcodeurl;
                    url = String.Format(url, _userid, address.ID.ToString(), address.Address1, address.Address2, address.City, address.State, address.Zip, address.ZipPlus4);
                    string addressxml = web.DownloadString(url);
                    if (addressxml.Contains("<Error>"))
                    {
                        int idx1 = addressxml.IndexOf("<Description>") + 13;
                        int idx2 = addressxml.IndexOf("</Description>");
                        int l = addressxml.Length;
                        string errDesc = addressxml.Substring(idx1, idx2 - idx1);
                        throw new USPSManagerException(errDesc);
                    }
     
                    return Address.FromXml(addressxml);
                }
                catch (WebException ex)
                {
                    throw new USPSManagerException(ex);
                }
            }
     
            /// <summary>
            /// Get the city and state by proving the zip code.
            /// </summary>
            /// <param name="zipcode">Zipcode</param>
            public Address GetCityState(string zipcode)
            {
                Address a = new Address();
                a.Zip = zipcode;
                return GetCityState(a);
            }
     
            /// <summary>
            /// Get the city and state by proving the zip code.
            /// </summary>
            /// <param name="address">Address object</param>
            /// <returns>Address Object</returns>
            public Address GetCityState(Address address)
            {
                try
                {
                    //The address must contain a city and state
                    if (address.Zip == null || address.Zip.Length < 1)
                        throw new USPSManagerException("You must supply a zipcode for a city/state lookup request.");
                    
                    string citystateurl = "?API=CityStateLookup&XML=<CityStateLookupRequest USERID=\"{0}\"><ZipCode ID= \"{1}\"><Zip5>{2}</Zip5></ZipCode></CityStateLookupRequest>";
                    string url = GetURL() + citystateurl;
                    url = String.Format(url, _userid, address.ID.ToString(), address.Zip);
                    string addressxml = web.DownloadString(url);
                    if (addressxml.Contains("<Error>"))
                    {
                        int idx1 = addressxml.IndexOf("<Description>") + 13;
                        int idx2 = addressxml.IndexOf("</Description>");
                        int l = addressxml.Length;
                        string errDesc = addressxml.Substring(idx1, idx2 - idx1);
                        throw new USPSManagerException(errDesc);
                    }
     
                    return Address.FromXml(addressxml);
                }
                catch (WebException ex)
                {
                    throw new USPSManagerException(ex);
                }
            }
     
            #endregion
     
            #region Tracking Methods
            public TrackingInfo GetTrackingInfo(string TrackingNumber)
            {
                try
                {
                    string trackurl = "?API=TrackV2&XML=<TrackRequest USERID=\"{0}\"><TrackID ID=\"{1}\"></TrackID></TrackRequest>";
                    string url = GetURL() + trackurl;
                    url = String.Format(url, _userid, TrackingNumber);
                    string xml = web.DownloadString(url);
                    if (xml.Contains("<Error>"))
                    {
                        int idx1 = xml.IndexOf("<Description>") + 13;
                        int idx2 = xml.IndexOf("</Description>");
                        int l = xml.Length;
                        string errDesc = xml.Substring(idx1, idx2 - idx1);
                        throw new USPSManagerException(errDesc);
                    }
     
                    return TrackingInfo.FromXml(xml);
                }
                catch (WebException ex)
                {
                    throw new USPSManagerException(ex);
                }
            }
            #endregion
     
            #region Label Methods
            /// <summary>
            /// Fills a package's ShippingLabel with a Byte{} containing the Image for the label
            /// </summary>
            /// <param name="package">Package with From and To addresses provided</param>
            /// <returns>The same package with the ShippingLabel</returns>
            public Package GetDeliveryConfirmationLabel(Package package)
            {
                string labeldate = package.ShipDate.ToShortDateString();
                if (package.ShipDate.ToShortDateString() == DateTime.Now.ToShortDateString())
                    labeldate = "";
                string url = "?API=DeliveryConfirmationV3&XML=<DeliveryConfirmationV3.0Request USERID=\"{0}\"><Option>{1}</Option><ImageParameters></ImageParameters><FromName>{2}</FromName><FromFirm>{3}</FromFirm><FromAddress1>{4}</FromAddress1><FromAddress2>{5}</FromAddress2><FromCity>{6}</FromCity><FromState>{7}</FromState><FromZip5>{8}</FromZip5><FromZip4>{9}</FromZip4><ToName>{10}</ToName><ToFirm>{11}</ToFirm><ToAddress1>{12}</ToAddress1><ToAddress2>{13}</ToAddress2><ToCity>{14}</ToCity><ToState>{15}</ToState><ToZip5>{16}</ToZip5><ToZip4>{17}</ToZip4><WeightInOunces>{18}</WeightInOunces><ServiceType>{19}</ServiceType><SeparateReceiptPage>{29}</SeparateReceiptPage><POZipCode>{20}</POZipCode><ImageType>{21}</ImageType><LabelDate>{22}</LabelDate><CustomerRefNo>{23}</CustomerRefNo><AddressServiceRequested>{24}</AddressServiceRequested><SenderName>{25}</SenderName><SenderEMail>{26}</SenderEMail><RecipientName>{27}</RecipientName><RecipientEMail>{28}</RecipientEMail></DeliveryConfirmationV3.0Request>";
                url = GetURL() + url;
                //url = String.Format(url,this._userid, (int)package.LabelType, package.FromAddress.Contact, package.FromAddress.FirmName, package.FromAddress.Address1, package.FromAddress.Address2, package.FromAddress.City, package.FromAddress.State, package.FromAddress.Zip, package.FromAddress.ZipPlus4, package.ToAddress.Contact, package.ToAddress.FirmName, package.ToAddress.Address1, package.ToAddress.Address2, package.ToAddress.City, package.ToAddress.State, package.ToAddress.Zip, package.ToAddress.ZipPlus4, package.WeightInOunces.ToString(), package.ServiceType.ToString().Replace("_", " "), package.OriginZipcode, package.LabelImageType.ToString(), labeldate, package.ReferenceNumber, package.AddressServiceRequested.ToString(),  package.FromAddress.Contact, package.FromAddress.ContactEmail, package.ToAddress.Contact, package.ToAddress.ContactEmail);
                url = String.Format(url, this._userid, (int)package.LabelType
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.Contact))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.FirmName))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.Address1))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.Address2))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.City))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.State))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.Zip))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.FromAddress.ZipPlus4))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.Contact))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.FirmName))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.Address1))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.Address2))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.City))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.State))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.Zip))
                    , HttpUtility.UrlEncode(SecurityElement.Escape(package.ToAddress.ZipPlus4))
                    , package.WeightInOunces.ToString()
                    , package.ServiceType.ToString().Replace("_", " ")
                    , HttpUtility.UrlEncode(package.OriginZipcode)
                    , package.LabelImageType.ToString()
                    , labeldate
                    , HttpUtility.UrlEncode(package.ReferenceNumber)
                    , package.AddressServiceRequested.ToString(), "", "", "", ""
                    , package.SeparateReceiptPage.ToString().ToUpper());
                string xml = web.DownloadString(url);
                if (xml.Contains("<Error>"))
                {
                    int idx1 = xml.IndexOf("<Description>") + 13;
                    int idx2 = xml.IndexOf("</Description>");
                    int l = xml.Length;
                    string errDesc = xml.Substring(idx1, idx2 - idx1);
                    throw new USPSManagerException(errDesc);
                }
                int i1 = xml.IndexOf("<DeliveryConfirmationLabel>") + 27;
                int i2 = xml.IndexOf("</DeliveryConfirmationLabel>");
                package.ShippingLabel = StringToUTF8ByteArray(xml.Substring(i1, i2 - i1));
     
                var doc = XDocument.Parse(xml);
     
                try
                {
                    package.DeliveryConfirmationNumber = doc.Element("DeliveryConfirmationV3.0Response").Element("DeliveryConfirmationNumber").Value;
                }
                catch (Exception ex)
                {
                    package.DeliveryConfirmationNumber = "Error retrieving conf #";
                }
     
                return package;
            }
     
            /// <summary>
            /// Fills a package's ShippingLabel with a Byte{} containing the Image for the label
            /// </summary>
            /// <param name="package">Package with From and To addresses provided</param>
            /// <returns>The same package with the ShippingLabel</returns>
            public Package GetSignatureConfirmationLabel(Package package)
            {
                string url = "?API=SignatureConfirmationV3&XML=<SignatureConfirmationV3.0Request USERID=\"{0}\"><Option>{1}</Option><ImageParameters></ImageParameters><FromName>{2}</FromName><FromFirm>{3}</FromFirm><FromAddress1>{4}</FromAddress1><FromAddress2>{5}</FromAddress2><FromCity>{6}</FromCity><FromState>{7}</FromState><FromZip5>{8}</FromZip5><FromZip4>{9}</FromZip4><ToName>{10}</ToName><ToFirm>{11}</ToFirm><ToAddress1>{12}</ToAddress1><ToAddress2>{13}</ToAddress2><ToCity>{14}</ToCity><ToState>{15}</ToState><ToZip5>{16}</ToZip5><ToZip4>{17}</ToZip4><WeightInOunces>{18}</WeightInOunces><ServiceType>{19}</ServiceType><POZipCode>{20}</POZipCode><ImageType>{21}</ImageType><LabelDate>{22}</LabelDate><CustomerRefNo>{23}</CustomerRefNo><AddressServiceRequested>{24}</AddressServiceRequested></SignatureConfirmationV3.0Request>";
                url = GetURL() + url;
                url = String.Format(url, this._userid, (int)package.LabelType, package.FromAddress.Contact, package.FromAddress.FirmName, package.FromAddress.Address1, package.FromAddress.Address2, package.FromAddress.City, package.FromAddress.State, package.FromAddress.Zip, package.FromAddress.ZipPlus4, package.ToAddress.Contact, package.ToAddress.FirmName, package.ToAddress.Address1, package.ToAddress.Address2, package.ToAddress.City, package.ToAddress.State, package.ToAddress.Zip, package.ToAddress.ZipPlus4, package.WeightInOunces.ToString(), package.ServiceType.ToString().Replace("_", " "), package.OriginZipcode, package.LabelImageType.ToString(), package.ShipDate.ToShortDateString(), package.ReferenceNumber, package.AddressServiceRequested.ToString(), package.FromAddress.Contact, package.FromAddress.ContactEmail, package.ToAddress.Contact, package.ToAddress.ContactEmail);
                string xml = web.DownloadString(url);
                if (xml.Contains("<Error>"))
                {
                    int idx1 = xml.IndexOf("<Description>") + 13;
                    int idx2 = xml.IndexOf("</Description>");
                    int l = xml.Length;
                    string errDesc = xml.Substring(idx1, idx2 - idx1);
                    throw new USPSManagerException(errDesc);
                }
                int i1 = xml.IndexOf("<SignatureConfirmationLabel>") + 28;
                int i2 = xml.IndexOf("</DeliveryConfirmationLabel>");
                package.ShippingLabel = StringToUTF8ByteArray(xml.Substring(i1, i2 - i1));
                return package;
            }
     
         
            #endregion
     
            #region Rates
     
            public RateResponse GetRate(Package package)
            {
                try
                {
                    string url = "?API=RateV2&XML=<RateV2Request USERID=\"{0}\"><Package ID=\"0\"><Service>{1}</Service><ZipOrigination>{2}</ZipOrigination><ZipDestination>{3}</ZipDestination><Pounds>{4}</Pounds><Ounces>{5}</Ounces><Container>{6}</Container><Size>{7}</Size></Package></RateV2Request>";
     
                    int lb = package.WeightInOunces / 16;
                    int oz = package.WeightInOunces % 16;
                    string container = package.PackageType.ToString().Replace("_", " ");
                    if (container == "None")
                        url = url.Replace("<Container>{6}</Container>", "");
                    string fromZip = package.FromAddress.Zip;
                    if (package.OriginZipcode != null && package.OriginZipcode.Length > 0)
                        fromZip = package.OriginZipcode;
     
                    
                    url = GetURL() + url;
                    url = String.Format(url, _userid, package.ServiceType.ToString(), fromZip, package.ToAddress.Zip, lb.ToString(), oz.ToString(), container, package.PackageSize.ToString().Replace("_", " "));
                    string xml = web.DownloadString(url);
                    if (xml.Contains("<Error>"))
                    {
                        int idx1 = xml.IndexOf("<Description>") + 13;
                        int idx2 = xml.IndexOf("</Description>");
                        int l = xml.Length;
                        string errDesc = xml.Substring(idx1, idx2 - idx1);
                        throw new USPSManagerException(errDesc);
                    }
     
                    return RateResponse.FromXml(xml);
                }
                catch (WebException ex)
                {
                    throw new USPSManagerException(ex);
                }
            }
            #endregion
     
            #region TextConversions
            /// <summary>
            /// To convert a Byte Array of Unicode values (UTF-8 encoded) to a complete String.
            /// </summary>
            /// <param name="characters">Unicode Byte Array to be converted to String</param>
            /// <returns>String converted from Unicode Byte Array</returns>
            private String UTF8ByteArrayToString(Byte[] characters)
            {
                UTF8Encoding encoding = new UTF8Encoding();
                String constructedString = encoding.GetString(characters);
                return (constructedString);
            }
     
            /// <summary>
            /// Converts the String to UTF8 Byte array and is used in De serialization
            /// </summary>
            /// <param name="pXmlString"></param>
            /// <returns></returns>
            private Byte[] StringToUTF8ByteArray(String pXmlString)
            {
                UTF8Encoding encoding = new UTF8Encoding();
                Byte[] byteArray = encoding.GetBytes(pXmlString);
                return byteArray;
            }
            #endregion
     
            #region Private methods
            private string GetURL()
            {
                string url = ProductionUrl;
                if (TestMode)
                    url = TestingUrl;
                return url;
            }
            #endregion
        }
    }
    Posted by admin on 3/25/2010 8:57 AM | Comments (0)

    So, you have an MVC form and you’re using the following:

    <%= Html.Hidden(“Command”, Model.Command) %>

    or

    <%= Html.HiddenFor(m => m.Command) %>

    You are doing postbacks to the form, and you’re changing the value of Command in the controller, but for some reason, the value being stored in the hidden field is the old value, not the changed value from said controller.  I have been pulling my hair out trying to figure out why.  I finally found the answer on stackoverflow.com.

    http://stackoverflow.com/questions/594600/possible-bug-in-asp-net-mvc-with-form-values-being-replaced

    It turns out this is by design, even though Haacked himself doesn’t necessarily like it.  The reason is that the helper methods will pull the values from the model state, not from your passed model.  This is the correct behavior when dealing with textboxes and other user-changeable fields, but it seems wrong to do this with hidden fields, and Phil sort of agrees in the post above.  It would be a breaking change, so I doubt it will ever be “corrected”.  You need to build up your own hidden field instead, as shown below and in the post above.

    <input type="hidden" name="the-name" value="<%= Html.AttributeEncode(Model.Value) %>" />
    Posted by Admin on 1/28/2010 8:32 PM | Comments (0)

    Some people I know are asking me about the iPad.  If you're interested, here's a small explanation that might give you a better idea of what it really is.

    Think iphone with no phone, with a 9.7 inch screen with four times the screen area, and a battery that can last for 10 hours of use. That's what it is in a nutshell. It can run iphone apps, but developers can also write apps that take advantage of the bigger screen. Because of the bigger screen, it can be a good ereader (it will have its own bookstore), and it is better than the iphone for productivity apps like documents and spreadsheets. It will be $499 for the 16gb version, $599 for the 32, and $699 for the 64. There will also be versions with AT&T wireless built in; they'll be $130 more, plus $29 a month.

    A huge downside IMO is that it can still only run one app at a time, just like the iphone. And you can't run the same apps you could run on a macbook or other laptop.  On a bigger device like this, you'd want to do more with it, and do those things simultaneously, so this is a serious limitation. Think about if your laptop could only run one app at a time.  When you consider you can get a netbook for $300 that can run multiple apps simultaneously, has a physical keyboard and even a bigger screen, it's a tough decision.

    Anyway, that's my $.02.  Hopefully this doesn't sound biased, because it's not meant to be.  I really thought I'd probably get one, and still might, but the limitations mentioned, plus the fact I already have a netbook, will probably cause me to hold off.

    Posted by admin on 8/25/2009 10:43 AM | Comments (0)

    I’m building apps on top of asp.net 4.0 ajax preview 4, and it’s pretty cool.  The name is a bit of a misnomer, since you can use all of the client-side ajax functionality without asp.net 4.0 at all.  In fact, I’m using the client-side templating abilities by calling in to an asp.net mvc app that returns json results.  Here’s a link to the preview bits.

    http://www.codeplex.com/aspnet/Wiki/View.aspx?title=AJAX&referringTitle=Home

    I’ll have more up in a bit, as soon as I can write a demo for it!

    Tags: , | Posted by admin on 8/25/2009 8:19 AM | Comments (0)

    MSDN help is the most frustrating creation ever.  Even when it really does answer the “what”, it almost never answers the “why”.  In this case, I’m working with Linq to SQL.  I’m using the repository pattern and passing in a read-only or read-write datacontext depending on the situation.  I’m making a datacontext read-only by setting ObjectTrackingEnabled=false.  Here’s the MSDN article on ObjectTrackingEnabled.

    http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.objecttrackingenabled.aspx

    Here is the key sentence:

    “If ObjectTrackingEnabled is false, DeferredLoadingEnabled is ignored and treated as false. In this case, the DataContext is read-only.”

    Ok.  I get that ObjectTrackingEnabled=false by necessity makes the context read-only.  After all, if you’re not tracking changes, how do you know what to update?  What I don’t understand is why DeferredLoadingEnabled gets set to false as well?  If I have the following object model:

    Order –> Status

    I want to be able to have status lazy load when I do something such as reference the status description, like so:

    Order.Status.Description <= This *should* lazy load the Status object

    Why, oh why, can’t I still get lazy loading with a read-only datacontext?  The Status object could simply be loaded with the same read-only context, couldn’t it?  Someone at MS made this decision.  But I have absolutely no idea how to find out the reasoning behind it.  What I do know is that retrieving the status description in the example above has to be written two different ways now depending on my scenario.  Ugly…

    Tags: , | Posted by Admin on 8/19/2009 7:20 PM | Comments (0)

    I’m a Microsoft Action Pack Subscriber.  Being in the partner program provides sales and training resources, and also provides internal-use licenses for Microsoft software.  It’s a great way to eat the Microsoft dogfood that you’re recommending to your customers.  It lets you tell the pros and cons of MS software from experience, not just from sales slicks.

    By passing a pretty easy online test, you can also get the software in the “Web Solutions Toolkit”.  This additional software includes Visual Studio Standard and the Expression suite.  Here’s where the frustration comes in.  I was going to create a prototype Windows Mobile app for a customer of mine.  I’ve written WM apps in Visual Studio 2005 Standard before with no issues.  I’ve since moved to VS2008, of course, specifically the standard version in the MAPS kit.  So, I fired up VS, went to the C# project types, and looked for the “Smart Device” projects.  Imagine my surprise when it wasn’t there.  I figured I was missing an SDK or something, so I downloaded and installed the latest CF 3.5 SDK and Emulators.  Still no dice.  It was only after doing some Binging that I came upon this thread on MS’s forums.  I couldn’t believe my eyes!  From VS2005 to VS2008, Microsoft actually made it *harder* to create WM apps.  I found another thread on the partner forum, and saw a response from a MS Partner representative, confirming all of this (here’s the link to the thread, but it’s secured).

    “Hi Tony,
    Thank you for the post. I am afarid we have only Visual Studio 2008 Standard Edition included in the Web Solutions Toolkit currently. However, we appreciate your valuable advice and I will forward your feedback to the concerning team for their reference. Hope Visual Studio 2008 Professional Edition can be made available in the future.

    I proceeded to add my response to the thread.

    “I have to add another +1 to this thread.  It's absolutely amazing that even partners can't use the provided software to develop Windows Mobile apps.  How can we convince our customers to extend their LOB apps to WM if we can't even create prototype apps to show them?  That's what I was about to start doing tonight for a customer (I have previously written WM apps with VS2005 with no issues) when I ran into this showstopper.  I'm not paying $500+ for an upgrade to Pro just to convince a customer of the merits of WM development.  The whole point of the MAPS was to give us the software we need for just this purpose.

    I'm very disappointed!

    John West”

    My response pretty much sums it up.  MAPS is all about giving us the tools to convince our customers that they should use MS software.  I’m a firm believer that MS does make the best software, and has the best overall software ecosystem, for businesses.  It’s not perfect, and doesn’t suit all scenarios, but it’s the best fit for most.  So when I’m not given the tools to prove this to the customers I serve, I get upset.  Come on, MS, help us out.  We *want* you to compete with the iphone by showing customers that WM also has a good app dev story.  We can’t do this when you make it so difficult.  Give us the tools we need, and let us help our customers, and, as a byproduct of wanting to help those customers, help you!

    Tags: | Posted by Admin on 7/23/2009 3:32 AM | Comments (0)

    I have fought with the issue below with Design 2, and anxiously downloaded Design 3, hoping it would be fixed.  No dice!

    The problem is when I’m exporting selected objects to jpg (or png, for that matter).  The dimensions of an object on the design surface will be different from the dimensions when exporting the selected object.  Why???  Why can't my dimensions when exporting be exactly the same as when designing them?  I thought maybe it was antialiasing, but unchecking that made no difference.  I also made sure my X and Y locations were integers, thinking there was some kind of rounding problem.  Still no difference.

    More...

    Tags: , , | Posted by Admin on 7/2/2009 3:59 AM | Comments (0)

    I’ve been thinking a lot about Windows Mobile 6.5 and 7.0.  Everyone is very focused on the user experience that Microsoft will provide.  Will they show the hexes or hide the hexes… Will it have Multitouch or not?  Zune integration?  Will it be more stable?  These are all great questions, but as a developer, none of them are the right question for me.  I mean, I want the Zune Marketplace on my WM device, no doubt about it.  However, there’s one thing I want more than any other as a developer.

    More...

    Tags: , | Posted by admin on 2/21/2009 3:11 AM | Comments (0)

    I have an AT&T Fuze with the original, stock Windows Mobile 6.1 installed.  I have tried repeatedly on two different machines to get Internet Sharing working over either bluetooth or usb.  No dice.  Finally, I stumbled on this blog: http://stevenharman.net/blog/archive/2008/10/03/windows-mobile-internet-connection-sharing-hack.aspx.  The important part wasn’t how to hack the registry.  What I needed was to know that the AT&T ISP connection needed to point to wap.cingular instead of isp.cingular.  I made that simple change and my connection worked flawlessly!  Hopefully this helps someone else.

    Tags: , , | Posted by Admin on 11/21/2008 3:10 AM | Comments (0)

    Note: The examples are for VB.NET.

    I have a separate assembly that hosts the layer containing my LINQ to SQL model. If you're going to use the L2S designer, you need to let L2S store the connectionstring in the settings.settings file, as shown here.

    image

    This is great, until you move your website to staging/production.  You need to be able to change this connectionstring by way of a web.config setting.  This method assumes that your L2Q data context has the following settings:

    Connection: ExampleConnectionString (MySettings)
    Connection String: xxx
    Application Settings: True
    Settings Property Name: ExampleConnectionString

    It turns out that it’s really simple.  Just add a connectionstring entry to override the one in the assembly containing the L2S connectionstring.  Here’s mine for the example above:

      <connectionStrings>
        <add name="App.My.MySettings.ExampleConnectionString"
          connectionString="server=svrprod; uid=secureuser;password=xxx; initial catalog=db"
          providerName="System.Data.SqlClient" />
      </connectionStrings>

    The key is the name.  You have to prefix your connection string’s name with “RootNamespaceForYourAssembly.My.MySettings.”.  That’s it.  Your web.config settings will now override the assembly’s settings when you view the website, and yet the designer will continue to use the settings in the assembly!

    John

    ps.  I do recommend you verify your changes before moving this to production to make sure your web.config connectionstring is being used.  To do this, simply make your production web.config connectionstring point to an invalid server and make sure you get the error on your website.  That way you’ll know for sure that it’s reading it correctly.