Friday, October 30, 2009

Calculating UPS Shipping Rate in C#

Some useful links for doing UPS shipping rate calculations.

  • UPS Rates & Service Selection
  • https://www.ups.com/ups.app/xml/Rate
    The XML service for retrieving the rate data
  • dtk_RateXML_V1.zip
    Look for
    /Developers Guide/RSS_Tool_06_10_09.pdf
    in the zip.
  • Use the Xml Schemas/DataTypes support utility (xsd.exe) from the Visual Studio command prompt to generate classes that you can pass through the System.Xml.Serialization.XmlSerializer class straight to a HttpWebRequest RequestStream or from the corresponding ResponseStream.
    • xsd.exe RateRequest_external.xsd /classes /l:cs /n:FishOfPrey.SampleNamespace
    • xsd.exe RateResponse_external.xsd /classes /l:cs /n:FishOfPrey.SampleNamespace

Friday, October 23, 2009

SOQL Pagination for Salesforce API queries with the S4S Data Connector

At the start of the month I asked a question of Stack Overflow about how to efficiently page through SOQL results. See Stack Overflow : SOQL Pagination for Salesforce API queries. As at today I haven't received any answers. There is Salesforce Ideas : SOQL Request: allow greater-than, less-than WHERE conditions for Id fields, but it requires starting a new query for each page.

I've taken a different approach with the Sitecore for Salesforce Data Connector (DC). Using the QueryResultPager I can jump to any page in the query results with minimal overhead.

The following is an example of paging over 10,000 Salesforce Leads from C#. In real world ASP.NET examples the QueryResultPager would be persisted between requests. The performance is a respectable 10 seconds in the debug build, which I think is pretty good considering I'm jumping all over the place with the page index and page size. More linear paging performs better.

using Microsoft.VisualStudio.TestTools.UnitTesting; //Only used for the Assert in the example
using FuseIT.Sitecore.SalesforceConnector.Entities;
using FuseIT.Sitecore.SalesforceConnector.Services;

//...

[TestMethod]
public void LeadsPagination()
{
    SalesforceSession salesforceSession = new SalesforceSession(
        new LoginDetails("username@example.com", "salesforcePassword"));
    LeadDataSource leadDataSource = new LeadDataSource(salesforceSession);
    string[] requiredLeadFields = new string[] { "Id", "Name", "Email", "Description" };
    QueryResultPager pager = leadDataSource.GetPager(requiredLeadFields);

    Assert.IsTrue(pager.TotalRecordCount > 10000, "The total number of records");

    var firstPage = pager.GetPage(salesforceSession, 0, 10); //Records 0 to 9
    List leads = leadDataSource.EntitysFromQueryResult(firstPage);

    var randomPage = pager.GetPage(salesforceSession, 30, 100); //Records 3000 to 3099
    leads = leadDataSource.EntitysFromQueryResult(randomPage);

    randomPage = pager.GetPage(salesforceSession, 1, 150); //Records 150 to 299
    leads = leadDataSource.EntitysFromQueryResult(randomPage);

    randomPage = pager.GetPage(salesforceSession, 17, 15); //Records 255 to 269
    leads = leadDataSource.EntitysFromQueryResult(randomPage);

    randomPage = pager.GetRecords(salesforceSession, pager.TotalRecordCount - 101, pager.TotalRecordCount - 1); //The last 100 leads
    leads = leadDataSource.EntitysFromQueryResult(randomPage);
}

Behind the scenes this is sending the SOQL query select Id, Name, Email, Description from Lead and then utilizing the QueryLocator.

For Salesforce Organizations with customizations, like fields, you would use the T4 generated classes that match your org rather than those in FuseIT.Sitecore.SalesforceConnector.Entities.
NB: These code snippets are applicable to the current 1.0.5 release.

Finding an existing Salesforce Account using the S4S Data Connector

The following is an example of creating a new Salesforce Account from C# using the Sitecore for Salesforce Data Connector (DC).

using Microsoft.VisualStudio.TestTools.UnitTesting; //Only used for the Assert in the example
using FuseIT.Sitecore.SalesforceConnector.Entities;
using FuseIT.Sitecore.SalesforceConnector.Services;

//...

[TestMethod]
public void CreateAccount()
{
    SalesforceSession salesforceSession = new SalesforceSession(
        new LoginDetails("username@example.com", "salesforcePassword"));
    AccountService accountService = new AccountService(salesforceSession);
    string accountNameToFind = "FuseIT";
    Account matchingAccount = accountService.GetSingleByFieldEquals("Name", accountNameToFind);
    Assert.IsNotNull(matchingAccount);
    Assert.AreEqual(accountNameToFind, matchingAccount.Name);
}

For Salesforce Organizations with customizations, like fields, you would use the T4 generated classes that match your org rather than those in FuseIT.Sitecore.SalesforceConnector.Entities.
NB: These code snippets are applicable to the current 1.0.5 release.

Creating a new Salesforce Account using the S4S Data Connector

The following is an example of creating a new Salesforce Account from C# using the Sitecore for Salesforce Data Connector (DC).
NB: These code snippets are applicable to the current 1.0.5 release.

For Salesforce Organizations with customizations, like fields, you would use the T4 generated classes that match your org rather than those in FuseIT.Sitecore.SalesforceConnector.Entities.

using Microsoft.VisualStudio.TestTools.UnitTesting; //Only used for the Assert in the example
using FuseIT.Sitecore.SalesforceConnector.Entities;
using FuseIT.Sitecore.SalesforceConnector.Services;

//...

[TestMethod]
public void CreateAccount()
{
    Account account = new Account();
    account.Name = "FuseIT";

    SalesforceSession salesforceSession = new SalesforceSession(
        new LoginDetails("username@example.com", "salesforcePassword"));
    AccountService accountService = new AccountService(salesforceSession);

    var saveResult = accountService.Insert(account);
    string newAccountId = null;
    if (saveResult.success)
    {
         newAccountId = account.Id;
    }
    Assert.IsTrue(accountService.ValidEntityId(newAccountId));
}

Thursday, October 22, 2009

Monday, October 19, 2009

Sitecore CMS 6 - Pagination from the shell User Manager with a custom Membership Provider

When I connected a custom Membership Provider to Sitecore.NET 6.1.0 (rev. 090821) I noticed the performance was degrading badly as the number of custom members increased. Some investigation into the calls coming out of Sitecore showed that pageIndex was always 0 and the pageSize always int.MaxValue regardless of the actual page required. I.e.:

//Sitecore requests the first 15 records - expected pageIndex = 0, pageSize = 15
CustomMembershipProvider.GetAllUsers(int pageIndex = 0, int pageSize = int.MaxValue, out int totalRecords);

//Sitecore requests the second 15 records - expected pageIndex = 1, pageSize = 15
CustomMembershipProvider.GetAllUsers(int pageIndex = 0, int pageSize = int.MaxValue, out int totalRecords); 

//Sitecore requests the third 15 records  - expected pageIndex = 2, pageSize = 15
CustomMembershipProvider.GetAllUsers(int pageIndex = 0, int pageSize = int.MaxValue, out int totalRecords); 

//...

I checked with Sitecore and this is a known issue that will be resolved in an upcoming release. In the short term it can be patched by adding the ManualPaging="true" attribute to the ComponentArt Grid in \sitecore\shell\Applications\Security\UserManager\UserManager.aspx.

Friday, October 16, 2009

ASP.NET Enumerate profile properties

    var propertyNames = new Dictionary<string, string>();

    var profileSection = (System.Web.Configuration.ProfileSection)ConfigurationManager.GetSection("system.web/profile");
    foreach (ProfilePropertySettings propertyDefinition in profileSection.PropertySettings)
    {
        var customProviderData = propertyDefinition.CustomProviderData;
        //...
    }

Sys.WebForms.PageRequestManagerParserErrorException and HttpContext Trace

Recently I started getting the following error from UpdatePanels in a ASP.NET site.

Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, HttpModules, or server trace is enabled.
Details: Error parsing near '[initialvalue']|""|<div id="__asptrace"'.

Happily the error message pretty much points me straight at the issue: "server trace is enabled" and "__asptrace".

Turning the ASP.NET server trace off resolves the issue.

<trace enabled="true" pageOutput="false" requestLimit="40" traceMode="SortByTime" localOnly="true"/>

Monday, October 12, 2009

Accessing Session values for a HTTP Handler (ASHX)

When converting from a ASPX to ASHX for returning image data I ran into an issue with the HttpContext session being null. Turns out I just needed to add the IReadOnlySessionState interface to my IHttpHandler implementation.

An HTTP handler that needs to access session-state values must implement the IRequiresSessionState interface. Like INamingContainer, it's a marker interface and requires no method implementation. Note that the IRequiresSessionState interface indicates that the HTTP handler requires read and write access to the session state. If read-only access is needed, use the IReadOnlySessionState interface instead.
MSDN: Writing HTTP Handlers

Scott Hanselman: Getting Session State in HttpHandlers (ASHX files)

Thursday, October 8, 2009

Blog Disclaimer

After reading Computer World - Opinion: Website disclaimers – yes, they do work I thought it might be sensible to add a disclaimer that states users should "make their own, independent inquiries before acting on any information" [on this site] and that "all information is of a general nature only and must not be taken as advice.". A quick Google however turned up a much more thorough definition. Full credit goes to Herche’s Blog Disclaimer


http://www.fishofprey.com (hereafter “this website”) will abide by, adhere to, accept responsibility for, endure under and act with respect toward the following weblog disclaimer:

By accessing this website, a web browser (hereafter user) is consents that s/he is familiar with, understands and absolutely accepts the following weblog disclaimer:

The views expressed by the authors on this website do not necessarily reflect the views of this website, those who link to this website, the author's mother, father, sister, brother, uncle, aunt, grandparents, cousins, step relations, any other blood relative and the author himself, this website’s web host, template designer, or any other organization, service, motto, logo, insignia or avatar in any way connected with this website.

Comments on this website are the sole responsibility of their writers and the writer will take full responsibility, liability, and blame for any libel or litigation that results from something written in or as a direct result of something written in a comment. The accuracy, completeness, veracity, honesty, exactitude, factuality and politeness of comments are not guaranteed.

All trademarks, service marks, collective marks, design rights, personality rights, copyrights, registered names, mottos, logos, avatars, insignias and marks used or cited by this website are the property of their respective owners and this website in no way accepts any responsibility for an infringement on one of the above.

Although it may claim otherwise, this website does not offer legal, medical, psychiatric, veterinary, gynecological, archaeological, astronomical, astrological, ontological, paleontological, philosophical, axiological, audiological, bacteriological, mineralogical, criminological, terminological, dermatological, ecclesiastical, campanological, phrenological, phonological, technological, hematological, campanological, neurological, psychobiological, urological, ufological, typological,, mythological, hydrological, xylological, zoological, logical or any other kind of professional advice. Nothing on this website should be construed as professional advice including, but not limited to, the above list.

The information provided on this website is of a general, broad, sweeping, large, wide-ranging, wide-reaching and wide-spread nature and cannot substitute for the advice of a licensed professional or chiropractor. A competent authority with specialized knowledge is the only one who can address the specific circumstances of your predicament. We can try, but this disclaimer frees us of any liability if negative consequences result from our efforts.

Please contact your local bar association, law society, neighborhood association of jurists, medical board, county hospital, phone book, online directory, local emergency number in your jurisdiction, mother or Google to find a or obtain a referral to a competent professional. If you do not have reasonable means of contacting an attorney-at-law, lawyer, civil law notary, barrister, solicitor, medical professional, coroner or any other professional in the area of your inquiry, meaning you are an orphaned, computer-illiterate social hazard, please exit this window and get your life in order.

This website has no control over the information you access via outbound link(s) in the post text, sidebar, header, footer or comment sections. This website does not endorse linked website(s), cannot guarantee the accuracy of any information found by following said links or the correctness of any analysis found therein and should not be held responsible for it or the consequences of a user’s use of that information. If you are curious about the veracity of something you find, please follow the directions in the above paragraph and consult the appropriate experts.

This website may inadvertently link to content that is obscene, prurient, useless, hate-filled, poisonous, pornographic, frivolous, empty, rotten, bad, disgusting, hostile, repulsive, virulent, infectious, malignant, antagonistic, irritating, obnoxious, harsh, embittered, rancorous, resentful, acrimonious, pestilential, baneful, noxious, toxic, venomous, pernicious or repetitive. This website in no way condones, endorses or takes responsibility for such content.

This website publishes content regularly and said content is maintained in reference to the protections afforded it under local, provincial, state, martial, federal, international and mafia law. Publication of information found on this website may be in violation of the laws of the city, county, state, country or other jurisdiction from where you are viewing this website’s content and laws in your jurisdiction may not protect or allow the same kinds of speech or distribution. In the case that the laws of the jurisdiction where this website's content is maintained and those of yours conflict, this website does not encourage, condone, facilitate, recommend or protect the violation of any laws and cannot be responsible for any violations of such laws.

Because the World Wide Web is an integrated net of communication, discussion and litigation, this website encourages the distribution of its content. Cross, reciprocal or just plain friendly hyperlinking is consistent with this information sharing and this disclaimer should not be construed as a condemnation of any linking practices. That said, any reproduction of this website’s content must credit the website by name and Uniform Resource Locator (URL). Should you link to this domain or use, reproduce, republish, regurgitate, repeat, reiterate, rebound, reecho, reverberate, mimic, imitate, parrot or duplicate the information contained on this website, you alone are responsible for that action and should, under threat of litigation, credit this website by name and URL.

This website is not recommended for inmates, ingrates or anyone professing an irrational fear of cats or any other mammal, those who have a penchant for time wasting, illiterates and lawyers. Women who are pregnant or may become pregnant or are nursing are advised to consult their husband and physician before reading this website. Eating before reading may result in unhealthy indigestion. Not recommended for people over the age of 120.