views:

390

answers:

4

I am designing a product management system. I am wondering the best way to handle a large amount of variation in each Action/View in my application. The app handles 20 categories and 12 Target Markets, each of which affect the data that needs to be collected for each product. For example, the "QuickAdd" action takes in the core data like Product Name and SKU, plus a few other key pieces of information based on the combo of the Category and Target Market that the product is being added to (examples below). The Category and Target Market are not configurable attributes of the product, the user using the system can only work under a particular combo, for example Toys/USA. The reason for mentioning that is I can't design the form to have sections of attributes for each Category/Market combo, it needs to work like the form is made for just that Category/Market - the user has no knowledge of other combos.

Some examples to hopefully clarify the possible situations:

If I am adding a product to category Toys with the Target Market USA I need to ask for the "Age range" and "Did it pass the safety inspection".

If I am adding a product to category Toys with Target Market Mexico, I just need to ask for "Age range".

If I am adding a product to the category Clothing with the Target Market USA I need to ask for the "Style" and "Material"

If I am adding a product to the category Clothing with the Target Market Canada I need to ask for the "Style" and "Material" and "USA Price"

We have 20 categories and 12 Target Markets, plus there are 10 forms that need to behave in this fashion, so in theory there are 2400 distinct Actions/Views/Models

So the question is, in ASP.NET MVC, what is the best way to handle displaying all these dynamic forms and handling the variations of data that gets sent to the action?

EDIT
A clarification on how the attributes of the product are determined: They are based on the hierarchy of the product belonging to a Category in a Market. For example, it is not the addition of all Toy attributes and USA attributes we'd ask for, it is the attributes of a product that is a Toy sold in the market USA. A Toy sold in the USA needs the "safety inspection" information, but Clothing in the USA does not. A Toy in Mexico also does not need the "safety inspection" information, so that attribute is not inherent to all Toys or all USA products, but rather the fact that is a combo of both Category and Market.

+2  A: 

I would create some domain models for the attribute types.

public enum AttributeTypeEnum
{
    Currency,
    Range,
    List,
    Number,
    Text,
    Boolean
}

public interface class IAttribute
{
    int Id { get; set; }
    string Name { get; set; } 
    AttributeTypeEnum AttType { get; set; }
}

public abstract class BaseAttribute
{
    int Id { get;set;}
    string Name { get;set;}
    AttributeTypeEnum AttType { get; set; }
}

public class RangeAttribute<T> : BaseAttribute
{
   T StartValue { get;set; }
   T EndValue { get; set; }
}

Then associate each attribute to one or more Categories

public class CategoryAttribute
{
    int Id { get; set; }
    IAttribute Attribute { get; set; }
}

You can then have a list of attributes against each category

public class CategoryAttributeService()
{
    public IList<CategoryAttributes> GetAttributes(int CategoryId)
    {
        return new IList<CategoryAttributes>();
    }
}

Your controller can then return a list of these attributes in the ViewData.Model.

// controller action
public class CategoryAttributeController : Controller
{
    public ActionResult CategoryAttributes(int categoryId)
    {
        CategoryAttributeService cas = new CategoryAttributeServices();
        ViewData.Model = new CategoryAttributeViewData(categoryId)
        {
            Attributes = cas.GetAttributes(categoryId);
        };
        return View();
    }
}

and let your view handle the type of each item and alter the form controls/display of each item accordingly ie (a range with have a start and end value) a boolean will have a checkbox, Material might be a listbox etc. you have a number of choices on how to handle the rendering, you could create a separate .ascx control for each attribute type to generate the form controls, or as below create an html helper method

<%@ Page Title="" Language="C#" Inherits="ViewPage<CategoryAttributeViewData>" %>

<% foreach(CategoryAttribute attribute in ViewData.Model.Attributes) { %>
<%= Html.RenderAttribute(attribute) %>
<% } %>

and the helper method like

public static string RenderAttribute(this HtmlHelper, ICategoryAttribute att)
{
    StringWriter stringWriter = new StringWriter();
    using (HtmlTextWriter writer = new HtmlTextWriter(stringWriter))
    {
        switch(att.AttributeType)
        {
            case AttributeDataType.Boolean:
                CreateCheckBox(writer, att);
                break;
            case AttributeDataType.List:
                CreateListBox(writer, att);
                break;
            // Other types                
        }
    }
    stringWriter.ToString();
}

EDIT: I kind of left Markets out the above so if I understand this correctly, Each Market has a number of categories (one to many) Say USA and Clothing. The category Clothing can appear in many markets. Each category has a number of attributes (one to many) (Clothing: color, size) and each attribute can have many Markets (one to Many)

  • A list of Markets
  • A list of categories
  • A list of MarketCategories
  • A list of CategoryAttributes
  • A list of Attributes
  • A list of AttributeMarkets

Markets > MarketCategories > CategoryAttributes > Attributes > AttributeMarkets

Is that correct?

Mac.

Mac
I'd be curious to see how you would handle the data after it is sent to the action.
jayrdub
I like the general approach in terms of View handling. Couple points: wouldn't the CategoryAttributeService also have to take a TargetMarketId? Also I think the action signature would look more like `public ActionResult QuickAdd(int categoryId, int targetMarketId, string productName, string sku, IEnumerable<Attribute> attributes)`, wherein `attributes` should be defined with a model binder. Actually, a bunch of the parameters could be rolled into a Product parameter using model binding.
gWiz
You would need the targetMarketId but not the categoryId, just add to model binder, also agree it would make sense to use model binding to shorten the method signature and keep it clean.
Mac
A: 
Danny Varod
A: 

Set up a table in your database that looks something like this:

Category nvarchar(*)
Market nvarchar(*)
AttributeName nvarchar(*)
AttributeType nvarchar(*)

Then store each combination of attributes that you need in that table (obviously, some refactoring can be done, like having an "Attributes" table that stores whether or not an attribute is required for the quick insert and would allow Category/Market combos to share attributes).

Then in your view, read the User's Category and Market combo and dynamically build the form:

In your Page.Load for the form, instantiate the form parts you need, give them meaningful IDs, then in your postback handler, read all the data from the Request.Form object.

A short example:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 Dim stuff As New System.Web.UI.WebControls.TextBox()
 stuff.ID = "WOW64"
 Page.Form.Controls.Add(stuff)
End Sub

Protected Sub Submit_Click(ByVal sender As Object, ByVal e As EventArgs)
 Dim str As String = Request.Form("WOW64")
 str = str ' do something with the string'
End Sub
Evan Larkin
A: 

Ended up using a pretty standard implementation of EAV

jayrdub