ASP.NET eCommerce Solutions?

by zack.moore January 05, 2009 12:41

I had a long break after Christmas that I used to work on some projects. Sometimes it is hard to get any free time and it was a well enjoyed break.

I didn't get as much done on my XNA project as I would like but I've been studying the book pretty thoroughly.

My main project that I worked on was to build a web page for my dad's business. He has an old brink-and-mortar style business that was started by my grandfather. My dad is pretty tech savvy but he never got around to building a web page for his business so I thought it would be a good project.

I got a lot done on that and I'm talking to my sister and my wife to help me finish writing content for the site.

For my dad's web page I decided to investigate what it would take to add an eCommerce section and see if he was interested in selling a few items over the 'net as well as in person. So I started looking for a good ASP.NET based free eCommerce solution preferably that had built in support for PayPal's free (mostly) services. I figured there had to be lots of them out there but I didn't know one off the top of my head so I decided to look.

Turns out I was wrong, or at least I couldn't find as many as I thought. osCommerce seems to dominate the PHP world with several other strong competitors, but I didn't find as many stand-out ASP.NET kits.

However I did find 2 that seemed to be the best.

Microsoft used to have an eCommerce starter kit that you could download for ASP.NET. I don't know the details of how this happened but the kit has now been taken over by someone else and is called dashCommerce. The code is published under a free open source license and a commercial license with the main difference being a support agreement and the requirement that the free version must always display a message at the bottom of the page that reads "Powered by dashCommerce".

The other good product I found, which is also listed on Microsoft's ASP.NET web page, is DotShoppingCart. This product has as far as I can tell the same general options as dashCommerce, that you can use a free open source version as long as you leave the "Powered by DotShoppingCart" message at the bottom of every page or you can buy a commercial license with a support agreement.

I downloaded both products and tried them out using PayPal's sandbox. (btw, PayPal's sandbox is a pain to use.) I liked both. They are both similar in the features that they offer. They both have basic CMS tools and product catalogs. Both allow products to have configurable attributes like Size or Color or whatever you need. dashCommerce gives each product a different SKU per attribute while DotShoppingCart seems to give a product one SKU no matter the attributes. Both require SQL Server 2005 Express (or greater) with Advanced Services. I believe the main reason for this is that both use the Full Text searching. I don't know if either uses any of the other Advanced Features. Both have source code. dashCommerce uses SubSonic and log4net while DotShoppingCart uses the MS Enterprise Library. Both appear to have IPN support but I didn't test it.

I would also like to point out that ComponentOne also offers a free control library for dealing with PayPal. I haven't downloaded it or looked at it but it might be worth checking out and kudos to ComponentOne for offering it for free.

What do most people use? Either of the items I've listed or do you roll your own? Or is there another free or low cost kit or product that I have missed?

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

ASP.NET | eCommerce

Make programmatic navigation easier in ASP.NET 2.0

by zack.moore November 26, 2007 21:52

All code published in this article is published under the Microsoft Public License. See source code download for a copy of the license.

Copyright 2007 Zack Moore

This is a small project I built earlier this year that I thought I would share.
ASP.NET web pages usually consist of several pages and users transition from page to page through a few different mechanisms. The simplest method is through clicking a link. Links can appear on menus or could be placed anywhere on a page. This is an example of a self guided transition.
Users can also be transitioned to different pages programmatically. Usually, this happens when a users performs an action like clicking a command button. When this happens, the page posts back to the server and the command button event handler runs. The event handler runs some code, perhaps making a decision, and then calls Response.Redirect() or perhaps Server.Transfer().
Most ASP.NET applications probably contain this same sequence many times. The code to perform the redirect should look something like this:

string url = string.Format("~/Output.aspx?ID={0}&StartDT={1}", 
    Server.UrlEncode(id), Server.UrlEncode(StartDate.ToShortDateString())); 
Response.Redirect(this.ResolveUrl(url));

There are several problems with this code. While this is short at only 2 lines it is overly complex and repetitive. Second, the developer has to remember to URL Encode each query string parameter. The developer must also correctly type in the URL. Lastly, if a resource moves then every page that does a redirect to that resource must be edited and retested.
That's a lot for just a couple of lines of code. Wouldn't it be nicer if we could wrap some of the repetitive parts of this together and only have one place to change the URL if the page or resource moved?
As it turns out, ASP.NET 2.0 has already given us the base on which to build something to do just that. The ASP.NET Site Navigation system is very nice and it is now standard on most of the new web projects that I start. This system allows you to define the navigation of your web site and bind that information to either a standard databinding control or a specialized navigation control. You can define hierarchy and even limit which user roles can see which navigation nodes. This is a powerful framework, and we can extend it a little further.
What we want, is a system that will automatically perform URL Encoding for us, and which will allow us to redirect or transfer users to different pages without having to put the URL in each page.


The ASP.NET Site Navigation by default uses an XML file, usually with the file extension ".sitemap", to define the page locations and hierarchy. The Site Navigation system uses the provider model, so there are other providers that retrieve navigation information from other sources such as a database, however in this article we are focusing on the XmlSiteMapProvider.
Here is an example sitemap file:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="~/Default.aspx" title="Home">
        <siteMapNode url="~/Details.aspx" title="Details" />
        <siteMapNode url="~/ContactUs.aspx" title="Contact Us"/>
    </siteMapNode>
</siteMap>

This sitemap defines a Default page as the root of a hierarchy containing a ‘Details’ and ‘Contact Us’ page below it. The sitemap defines a title for each page and the URL for each page. This is a minimum set of information, but each node can have more information associated with it depending on your needs.

When a menu or other control binds to this sitemap, the nodes for the correct level will be displayed. That means that you typically wouldn’t put nodes in the sitemap that you wouldn’t want displayed on a menu. Consequently, there could be many pages not represented in the sitemap, but that you may wish to be able to navigate to through code.

In order to create a solution for this, I am going to add two new attributes to the siteMapNode. The first new attribute I am going to add is a ‘visible’ attribute which can be either true or false. This will be used to tell the site map provider whether or not this node should be shown on a menu.

In order to be able to direct our navigation to a particular node, we need to be able to uniquely identify a particular node. The site map provider assigns a key for each node already, but the XmlSiteMapProvider uses the URL as the key. This doesn’t help us, so I am going to define a new attribute which I will call ‘navigatorId’. This can be any string which will be easy for you and your developers to use as an identifier for a page or resource. I could have used the already existing ‘title’, but there may be a scenario where you need more than one node to have the same title.

If you are observant, you may have realized that siteMap defines a namespace and adding new attributes to siteMapNode would violate that schema. Luckily for use, the XmlSiteMapProvider doesn’t use a validating XML Reader. However if it did, we could attempt to define a different schema which extended the default schema.

Our new site sitemap file looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
    <siteMapNode url="~/Default.aspx" title="Home" navigatorId="Default">
        <siteMapNode url="~/Details.aspx" title="Details" navigatorId="Details"/>
        <siteMapNode url="~/ContactUs.aspx" title="Contact Us" navigatorId="ContactUs"/>
        <siteMapNode url="~/ContactUsEmailSent.aspx" title="Contact Us" navigatorId="ContactUsEmailSent" visible="false"/>
    </siteMapNode>
</siteMap>

The first change is adding a ‘navigatorId’ attribute to each node. Notice that I also added a new node which has its ‘visible’ attribute set to false.

Just adding these attributes isn’t enough. In order to take advantage of this new data, we need to extend the XmlSiteMapProvider and configure our new provider in the web.config file.

In order to accomplish what we want, I could write our own site map provider and add all the features we want. But with only a few new features, we can get by simply extending the existing provider and this saves us the trouble of having to re-implement all the parts that we want to keep.

Site map providers implement a method named IsAccessibleToUser() to determine if a node should be shown. This is typically used with security trimming, but we can extend this method along with our ‘visible’ attribute to hide nodes that we don’t want to show up on a menu. Take a look at the code below.

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

namespace ZacksFiasco.Web.Navigation
{
    public class NavigatorSiteMapProvider : XmlSiteMapProvider
    {
        public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
        {
            bool isVisible = true;
            bool rc = false;

            return rc;
        }        
    }
}

IsAccessibleToUser() takes two parameters: the current HttpContext and the SiteMapNode to test. SiteMapNode has an indexer that enumerates all of the attributes  of the node, including the custom attributes that we added. If you check the 'visible' attribute, you can determine if the node should be visible to the user or not.  You should be sure to test if the node that was passed to you is not null, that the attribute exists, and that the attribute value can be parsed into a boolean. For this implementation because this is a new attribute, if the attribute does not exists, then we are going to assume that the node is visible.

if (node != null && node["visible"] != null && bool.TryParse(node["visible"], out isVisible))
{
}

If the visible attribute exists and is true, you should not return true. For a correct implementation you should call down to the base class and return the base class's result. This allows the base class to have the final say and apply any logic that the base class implements. If the attribute does not exist, you should call down to the base class.  The only time you should return a value different than the base class is if the attribute exists, but is false. In that case, we definitely do not want to show the node and so we want to return false.

The provider code ends up looking like this

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

namespace ZacksFiasco.Web.Navigation
{
    public class NavigatorSiteMapProvider : XmlSiteMapProvider
    {
        public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
        {
            bool isVisible = true;
            bool rc = false;

            if (node != null && node["visible"] != null && bool.TryParse(node["visible"], out isVisible))
            {
                if (isVisible)
                {
                    rc = base.IsAccessibleToUser(context, node);
                }
                else
                {
                    rc = false;
                }
            }
            else
            {
                rc = base.IsAccessibleToUser(context, node);
            }

            return rc;
        }        
    }
}

This is all we need to complete our site map provider. Just add this to your web application by inserting the following into your web.config. Just keep in mind that the value of type is "<namespace>.<type name>, <assembly>". In this example, I also turned on security trimming. This is a good example of why we call down to the base class in our custom site map provider. If we returned true without calling the base class, then the base class would not have a chance to apply security trimming and possibly limit visibility to this node.

<siteMap defaultProvider="NavSiteMapProvider" enabled="true">
    <providers>
        <add name="NavSiteMapProvider"
             description="Custom SiteMap provider."
             type="MyNavigation.MySiteMapProvider, MyNavigation"
             siteMapFile="Web.sitemap"
             securityTrimmingEnabled="true"/>
    </providers>
</siteMap>

Now that our site map provider is in place, we want to take advantage of this new functionality. We can now navigate and perform Redirects by navigatorId by searching the site map provider for the correct node and building our URL. This is very useful, but still repetitive. We can improve this by removing manual string manipulation and wrapping the code that performs the URL Encoding.

To begin, create a new class. I named my class Navigator. First, I gave Navigator a single private static method.

static string GetUrlFromKey(string navigatorId)

This method takes as a parameter a navigatorId string. Using this id, we can search all nodes in the site map provider until we find a node that matches and return that node's URL. We can search the nodes using a foreach loop like the following:

foreach (SiteMapNode node in SiteMap.Providers["AspNetXmlSiteMapProvider"].RootNode.GetAllNodes())

This loop statement uses the AspNetXmlSiteMapProvider. In the XML I provider for the web.conig I did not clear the provider list before adding our custom provider. There are reasons why we are not searching our own custom site map provider. First,since the name of the site map provider is specified in the web.config, I can't know at compile time what that name is. But I do know what the default name of the XmlSiteMapProvider is. Secondly, I want to be able to call IsAccessibleToUser() to check whether we should return this URL. If I call our custom provider, it will check the 'visible' flag which we don't want, since we want to be able to navigate to nodes that aren't visible on a menu. The solution is to use a provider that doesn't check the 'visible' flag when it evaluates accessibility. So in your loop, if the navigatorId matches and the node is accessible, then retrieve the URL from the node and return it.

We can improve this by adding to our configuration file a list of the site map providers that we wish to search. This give us the flexibility to search any and as many providers as we wish and only the providers that we wish.

static string GetUrlFromKey(string navigatorId)
{
    SiteMapNode rcNode = null;
    string rc = string.Empty;

    NavigatorSection ns = ConfigurationManager.GetSection("ZacksFiasco.Web.Navigation") as NavigatorSection;

    if (ns != null)
    {
        foreach (ProviderElement pe in ns.SiteMapProvidersToSearch)
        {
            foreach (SiteMapNode node in SiteMap.Providers[pe.SiteMapProviderName].RootNode.GetAllNodes())
            {
                if (node.IsAccessibleToUser(HttpContext.Current) && node["navigatorId"] == navigatorId)
                {
                    rcNode = node;
                    break;
                }
            }

            if (rcNode != null)
            {
                break;
            }
        }
    }
    else
    {
        foreach (SiteMapNode node in SiteMap.Providers["AspNetXmlSiteMapProvider"].RootNode.GetAllNodes())
        {
            if (node.IsAccessibleToUser(HttpContext.Current) && node["navigatorId"] == navigatorId)
            {
                rcNode = node;
                break;
            }
        }
    }

    if (rcNode != null)
    {
        rc = rcNode.Url;
    }

    return rc;
}

Implementing this configuration is outside the scope of this article, but the resulting web.config ends up looking like this.

<configuration>
    <configSections>
        <section name="ZacksFiasco.Web.Navigation"
                 type="ZacksFiasco.Web.Navigation.Configuration.NavigatorSection, ZacksFiasco.Web.Navigation"/>
    </configSections>
    <ZacksFiasco.Web.Navigation>
        <siteMapProvidersToSearch> 
            <add siteMapProviderName="AspNetXmlSiteMapProvider" />          
        </siteMapProvidersToSearch>       
    </ZacksFiasco.Web.Navigation>
    <system.web>
        <siteMap defaultProvider="NavSiteMapProvider" enabled="true">
            <providers>
                <add name="NavSiteMapProvider"
                     description="Custom SiteMap provider."
                     type="ZacksFiasco.Web.Navigation.NavigatorSiteMapProvider, ZacksFiasco.Web.Navigation"
                     siteMapFile="Web.sitemap"
                     securityTrimmingEnabled="true"/>
            </providers>
        </siteMap>
    </system.web>
</configuration>

Next, add to your class a member dictionary that takes a string as the key and a string as the value. Add a property get that wraps this dictionary. I called my property Parameters.

Now add a string member named navigatorId and add a property with a get and set that wraps this member.

string navigatorId;

public string NavigatorId
{
    get { return navigatorId; }
    set { navigatorId = value; }
}

Dictionary<string, string> parameters;

public Dictionary<string, string> Parameters
{
    get { return parameters; }
}

Override the ToString() member. In ToString(), perform the string concatenation necessary to build the URL. To retrieve the base URL, call the static method GetUrlFromKey() and pass the navigatorId member. To add the querystring parameters, perform a loop on the parameters dictionary and URL Encode each parameter.

Now add two final methods. Redirect() and Transfer(). Redirect() performs a Response Redirect() and Transfer performs a server-side Transfer. To implement each of these, just get the current HttpContext and call to the appropriate context object. Use the ToString() method to build your URL and that is all you need.

public void Transfer()
{
    HttpContext.Current.Server.Transfer(ToString());
}

public void Redirect()
{
    HttpContext.Current.Response.Redirect(ToString());
}

To use what you have written, the code to perform a programmatic navigation looks like the following:

protected void btHidden1_Click(object sender, EventArgs e)
{
    Navigator n = new Navigator("HiddenOne");
    n.Parameters["text"] = txParam.Text;
    n.Parameters["time"] = DateTime.Now.ToLongTimeString();

    n.Redirect();
}

Visit the ZacksFiasco.Web.Navigation Codeplex site to download the source code or download the assembly and begin using it in your projects right away.

kick it on DotNetKicks.com

Currently rated 3.3 by 4 people

  • Currently 3.25/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Web Navigation | ASP.NET | C# | SiteMapProvider

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen | Modified by Mooglegiant


I've been doing software development since I was little and my dad first brough home an ATARI 800. I picked up PILOT and BASIC. Now I mostly write C# and ASP.NET, and about a dozen other languages and platforms. I also enjoy a bunch of outdoor sports including running and mountain biking. I am very happily married. I am currently working for a great Software and IT consulting company named SPINEN where I am a Senior Developer.


Copyright Zack Moore

TextBox