IDE = Doing Enterprise Software Development ? Visual Studio : Microsoft Web Matrix

by scott.rudy on 08-03-2010 09:22 PM - last edited on 08-03-2010 09:35 PM

Microsoft recently announced their WebMatrix product. The application is meant to enable hobbyist web site owners to easily create dynamic web pages that have the ability to interact with data. WebMatrix is not meant to compete with Microsoft Visual Studio, which is targeted towards enterprise application development, rather it is intended to provide a development environment for a Windows alternative to the LAMP stack.

 

An intro to one of the productivity enhancements was covered on David Fowler's blog. The post dealt with a new .Net data access wrapper library from Microsoft, aptly named Microsoft.Data.dll (note this is not part of the .Net framework). Unfortunately for the author, the intended hobbyist audience turned out to not be the group actually subscribing to the blog. Instead, a backlash of comments came in from the professional development pundits thrashing the example posted on the blog, like this one.

 

The example was intended to be simple to show the power of the library and the reduction in complexity for the hobbyist when dealing with data access. Now Microsoft is in the business of getting people onto their platform to generate revenue and they often do over simplify code examples in order to illustrate a point. Also, to be fair, without the right education a hobbyist can get into trouble when they aren't advised of potential security risks.As such, the new code probably should have looked a litte more like this:

 

using (var db = Database.OpenFile("Northwind")) {
  foreach (var product in db.Query(
    "select * from products where UnitsInStock < @0", 20)) {
    Response.Write(string.Format({0} {1}<br />), 
      Server.HtmlEncode(product.ProductName), product.UnitsInStock);
  }
}

Sure it's deceivingly simple, but if all a person (note I didn't say developer) needs to do is get out a quick and dirty, read-only report of items with low inventory then I would argue that this provides more short term value than over-engineering a complex solution, in the event the client *might* need to refactor the report into an N-tier solution someday. If you need to do that in WebMatrix then your page would probably look more like this:

<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Configuration" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Page Language="C#" %>
<script runat="server">
// WebApp
protected void Page_Load(object sender, EventArgs e)
{
  InventoryServiceClient client = new InventoryServiceClient();
  // assume global error handling since this is all one layer
  List<ClientProduct> productList = client.GetProductsForReorder();
  foreach (ClientProduct product in productList)
  {
    Response.Write(string.Format("{0} {1}<br />",
      Server.HtmlEncode(product.ProductName), product.UnitsInStock));
  }
}

// ServiceClient
internal class InventoryServiceClient {
  public List<ClientProduct> GetProductsForReorder() {
    List<ClientProduct> productList = new List<ClientProduct>();
    IInventoryDataService inventorySvc = new InventoryDataService();
    List<ProductDataContract> serviceProductList =
      inventorySvc.GetProductsForReorder();

    foreach (ProductDataContract serverProduct in serviceProductList) {
      productList.Add(new ClientProduct() {
          ProductName = serverProduct.ProductName,
          UnitsInStock = serverProduct.UnitsInStock
        });
    }
    return productList;
  }
}
 
internal class ClientProduct {
  public string ProductName { get; set; }
  public int UnitsInStock { get; set; }
}

// ServiceContracts
public interface IInventoryDataService {
    List<ProductDataContract> GetProductsForReorder();
}
public interface IInventoryBusinessService {
  int GetReorderPoint();
}
public interface IProductRepository {
    List<ProductDataContract> GetProductsForReorder(int reorderPoint);
}
public class ProductDataContract {
  public string ProductName { get; set; }
  public int UnitsInStock { get; set; }
}

// BusinessAccess
public sealed class InventoryBusinessService : IInventoryBusinessService {
  private const string REORDER_POINT = "ReorderPoint";
  public int GetReorderPoint() {
      return int.Parse(ConfigurationManager.AppSettings[REORDER_POINT]);
  }
}

// DataAccess
public sealed class InventoryDataService : IInventoryDataService {
    public List<ProductDataContract> GetProductsForReorder() {
    IInventoryBusinessService inventoryBusiness = new InventoryBusinessService();
    IProductRepository productRepository = new SqlProductRepository();
    return productRepository.GetProductsForReorder(
        inventoryBusiness.GetReorderPoint());
  }
}
 
internal sealed class SqlProductRepository : IProductRepository {
  private string _connectionString =
    ConfigurationManager.ConnectionStrings["DataAccess"].ConnectionString;

  public List<ProductDataContract> GetProductsForReorder(int reorderPoint)
  {
    ProductDataAccess productData = new ProductDataAccess();
    List<ProductDataContract> productList = new List<ProductDataContract>();
    using (SqlConnection connection = new SqlConnection(_connectionString)) {
      connection.Open();
      productList = productData.GetProductsForReorder(connection, reorderPoint);
    }
    return productList;
  }
}

internal sealed class ProductDataAccess {
  private const string LOW_INVENTORY_SQL =
    "select ProductName, UnitsInStock from products where UnitsInStock < @reorderPoint";
  private const int PRODUCT_NAME_POSITION = 0;
  private const int UNITS_IN_STOCK_POSITION = 1;

  public List<ProductDataContract> GetProductsForReorder(
    SqlConnection connection, int reorderPoint)
  {
    List<ProductDataContract> productList = new List<ProductDataContract>();

    using (SqlCommand command = new SqlCommand(LOW_INVENTORY_SQL, connection)) {
      command.Parameters.Add(new SqlParameter() { ParameterName = "@reorderPoint",
          DbType = System.Data.DbType.String, Value = reorderPoint
      });

      using (SqlDataReader reader = command.ExecuteReader()) {
        while (reader.Read()) {
          productList.Add(new ProductDataContract() {
              ProductName = reader.IsDBNull(PRODUCT_NAME_POSITION) ?
                null : reader.GetString(PRODUCT_NAME_POSITION).TrimEnd(),
              UnitsInStock = reader.GetInt32(UNITS_IN_STOCK_POSITION)
          });}}}
    return productList;
  }
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"><title>Low Inventory Report</title></head>
<body><form id="form1" runat="server"><div></div></form></body>
</html>

We encourage you to share your comments on this post. Comments are moderated and will be reviewed and posted as promptly as possible during regular business hours.

To ensure your comment is published, please follow our community guidelines.

Comments
by Simon MacArthur(anon) on 08-03-2010 10:52 PM

Bravo, like your style!

 

You proved my point on the bad blog about how "developers"  over value code!!

 

The "right way" has far more  code, which in my book means more to go wrong, more reliance on stable infrastructure which ultimately will be outside your control, took me 3 times longer to read and digest what's going on,  yet provides not one bit of added business benefit!!!

 

Great stuff!

 

Maybe you should release it on codeplex so you can get people using it before it makes it into the officially supported pattern so we can really shoot ourselves in the foot

 

:smileyhappy:

 

by Steve Gentile(anon) on 08-04-2010 12:14 AM

"Sure it's deceivingly simple, but if all a person (note I didn't say developer) needs to do is get out a quick and dirty, read-only report of items with low inventory"

 

And they just did a select * on a table that just locked out the other 300 users because they have no idea the implications of the query ???(you said they weren't a developer so it's easy to assume they don't).

 

Imagine all 10 managers at this place unknowingly doing this ?

 

How about they talk to the dev team and work on getting some reporting services running in a control environment ?  

by scott.rudy on 08-04-2010 02:25 AM

@Steve Gentile - While I agree that select * isn't needed here since the caller is only interested in two columns, I am not sure what you mean by "locked out". Are you implying that a database would issue a lock on the table because someone issued a "select *" from a table? If so, what database are you using? This select statement on the Northwind database will execute in subsecond response time. If you look at the Northwind Traders database you will find there are only 45 records in the product table and the column lengths are fairly small. This query could easily run in a system that only supports 300 users.

by infocyde(anon) on 08-04-2010 04:21 PM

God bless you :smileyhappy:  I don't think the point could be made any better. 

by infocyde(anon) on 08-04-2010 04:23 PM

Select * blocking out a table?  Huh? Is this a CE 4 thing? Definitely not SQL Server full thing.

by Set(anon) on 08-05-2010 07:21 AM

>Select * blocking out a table?  Huh? Is this a CE 4 thing? Definitely not SQL Server full thing.

 

 

Try using something different than  POC databases with 100 rows at top. It's really easy to lock a table or the majority of a table with simple selects.
by Mitch Labrador(anon) on 08-10-2010 03:24 PM

LOL!!! I love the passive aggressive way you used to make your point.

 

Pitty you did not have more space on Ayende's blog comment form to do it there.

 

Priceless!!!

by Set(anon) on 08-10-2010 07:33 PM

If the comment was for me, go check how locks are working in SQL Server.... but yes, I will agree that with(nolock) your problems are solved....

by scott.rudy on 08-11-2010 02:59 PM

My understanding is that SQL Server will do a clustered index scan, based on the PK_products index, on this table (which will lock the index for updates, but allow simultaneous reads) whether or not you use the "*" notation. I would be more than happy to be educated if I am wrong on this point.

 

Furthermore, using the "NOLOCK" hint could actually introduce dirty reads and cause the business to put out an order for products causing a surplus. I suppose they could have a sale to get rid of them, but using this hint loosely could also get someone fired from their job.

 

Here is a link to the download page for the Northwind Database.

Post a Comment
Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.

Find HP in Social Media

Facebook Twitter YouTube SlideShare Flickr
About the Author
Labels