views:

136

answers:

2

I'm working on my first ASP.NET MVC app, and I'm running into a little confusion about creating/updating certain data.

I have a database table User, a LinqToSql-generated partial class User, and my own custom partial class User.

I'm using [Bind(Exclude = "Id, InsertDateTime, UpdateDateTime")] on my version of User because I don't want users to edit these fields.

I also have a PhoneNumber field, which I do want users to edit, but it requires transformation. I store this in the database as 10 numbers, but when I display it to users via the view, I convert it to a readable phone number in the view like this:

string.Format("{0:(###) ###-####}", Convert.ToInt64(Model.User.PhoneNumber)).

The problem is, when the user clicks Save, the phone number will always be in the wrong format. Somewhere, I need to strip out all the non-numeric characters (parentheses, dashes, slashes, and spaces).

Questions

For each of the fields listed below, how do I handle Create and Edit actions?

  1. Id - I believe this is taken care of automatically by SQL-Server because I have all my Id fields set up as IDENTITY (1, 1). I haven't tested extensively, but this seems to "just work". Please confirm.

  2. InsertDateTime - I want this to be set to DateTime.Now only for Create actions, not for Edit actions. So, where would be the appropriate place to set this value: User, UserController, UserFormViewModel, or something else?

  3. UpdateDateTime - I want this to be set to DateTime.Now for both Create and Edit actions, but again, where should I put the code that does this assignment?

  4. PhoneNumber - Unlike the three fields above, this one is editable by the user, but it need to gets transformed from (888) 123-4567 to 8881234567 before the update can occur. Couple question here: (1) Where is the appropriate place for this transformation? I convert the phone number to "user readable" format in the view, where should I convert it back to "database storage" format? (2) Should I add PhoneNumber to my [Bind(Exclude...)] attribute?

Update

From the answers so far, I think clarify a few things, at least for myself.

First of all, here's a list of locations where stuff happens with User data:

  1. User table in the database - handles Id assignment. Could provide default values for InsertDateTime and UpdateDateTime.

  2. User class - handles validation using GetRuleViolations() method.

  3. UserRepository class - abstracts away data persistence functions (get, get all, add, delete, and save).

  4. UserController class - handles user requests and post attempts (index, details, edit, posted edit, create, posted create, and delete).

  5. UserFormViewModel class - provides strongly typed data to view (a User object plus backing data for drop-down menus).

  6. Views/User/Create.aspx and Views/User/Edit.aspx - generates the html to display a UI to the user by combining static data with dynamic data (which is stored in the view model).

My current thinking is that the responsibility for setting Id, UpdateDateTime, and InsertDateTime conceptually lies with the model. The database is definitely responsible for setting the Id on insert, but it's still a little unclear to me where the date-time fields should be set. There seem to be two choices: the repository (as suggested by @Aaronaught) or the User class (which already handles validation).

As for the issue converting the PhoneNumber between ########## and (###) ###-####, this seem conceptually more like a "view" function. I like @Aaronaught's idea of having a dedicated PhoneNumberConverter class, and I will probably go with that, but there is still a question of who calls the methods on this class. For this, I'm leaning toward my UserFormViewModel class.

This leads me to two followup questions...

Followup Questions

  1. Should the UpdateDateTime and InsertDateTime fields be assigned in the UserRepository class or the User class?

  2. Does it makes sense to call phone number conversion methods (on a PhoneNumberConverter class) from the UserFormViewModel?

+1  A: 

here is my answer for that:

1- Yes you'r right it is done using SQL server Identity specification column "Auto incremental column"

2- You can set a default value for this field in DB to the getdate() sql function so that it takes that value for the first time it will be inseterted in the db, and it takes the server datetime value.

3- this also can be the same for default value, but in the function you save data in ex. neer the line you call submit changes, set this value to Datetime.Now.

4- first part I think the approperiate place will be on the [Post] method version, and i don't think you should exclude it.

Mustafa Magdy
Mustafa, thanks for your answer. I added `DEFAULT GETDATE()` to my column definitions for `InsertDateTime` and `UpdateDateTime` in my database. It sounds like you are saying the `UserController` class is the right place to set the `UpdateDateTime` and transform the `PhoneNumber`. For `UpdateDateTime`, I think this makes sense, but for the `PhoneNumber`, I would need to do this in two places (`[Post]Edit` and `[Post]Create`). This violates DRY (don't repeat yourself)...that's why I was wondering if maybe one of my model classes (or a ViewModel class) might be better.
DanM
+1  A: 

Normally you'll have a business-logic layer, which MVCers call a repository - something that goes between your controller and DAL. That is often the ideal place to handle timestamps and data transformations.

public class UserRepository : IUserRepository
{
    private IDatabase db;

    public UserRepository(IDatabase db)
    {
        if (db == null)
        {
            throw new ArgumentNullException("db");
        }
        this.db = db;
    }

    public void SaveUser(User user)
    {
        int userID = user.ID;
        DateTime createDate = user.CreatedOn;
        DateTime updateDate = DateTime.Now;
        long phoneNumber = PhoneNumberConverter.Parse(user.PhoneNumber);
        using (TransactionScope tsc = new TransactionScope())
        {
            if (user.ID == 0)
            {
                createDate = updateDate;
                userID = db.InsertUser(user.Name, phoneNumber, createDate,
                    updateDate);
            }
            else
            {
                db.UpdateUser(user.ID, user.Name, phoneNumber, updateDate);
            }
            tsc.Complete();
        }
        user.ID = userID;
        user.CreatedOn = createDate;
        user.LastModified = updateDate;
    }
}

Note that I'm making a bunch of "assumptions" here like the use of TransactionScope and some sort of thin CRUD layer type called IDatabase. These aren't important, they're just there to illustrate the workflow:

  1. Have some type of "repository" class that handles the "business logic" - i.e. all of the stuff that happens between when the user hits "save" and when it actually goes into the database. You can implement separate Add and Update methods, or a single Save method as I've done.

  2. Do any data conversion you need to do inside the Add/Update/Save methods. This does not replace the need for for validation at the UI level; the reason I made reference to a PhoneNumberConverter above is that you might want to have this expose both a Validate and Convert method, so that way both your repository and your UI can rely on the same central validation/conversion logic. Of course in MVC 2.0 you can just use Data Annotations for this - there's actually a PhoneNumber member in the DataType enumeration.

  3. Your Save method (or Add or Update method) takes an unattached entity and saves it to the database. This method checks for an ID and chooses whether to insert or update based on that. Either way, it updates the original User object passed to it after the database transaction has succeeded. This should also answer your question #1 - if you have an IDENTITY column in your database, then your database is responsible for generating the IDs; you do not generate them in your app.

An alternative approach is for the Save method to return a brand-new User instances initialized from whatever actually got stored in the database. In other words, it does a SELECT after the INSERT or UPDATE. While this does tend to be a bit more reliable, there's a significant performance tradeoff.

Technically a repository is part of your model, which should answer the "where" question at a fundamental level, although I tend to think of it as being a separate layer altogether.

I'm not sure how well this answers your question - it's a little tricky to answer "design" questions like this - but I find this design to work pretty well for most purposes, it follows the DRY principle and is pretty easy to maintain.

Aaronaught
Hi Aaron, thanks for your thoughtful answer. I updated my question based in part on your answer. I wonder if you could comment.
DanM
I suppose it depends what `UpdateDateTime` and `InsertDateTime` really mean to you. If they are intended to refer to when it was *actually saved to the database*, then I think you should do it in the repository (BLL or DAL). On the other hand, if your domain objects have a long lifespan, maybe you should be populating/updating those fields as soon as the `User` instance is created (in the constructor) or whenever any property is modified (this will be tedious). I would lean toward the first, personally, as it's likely what most people will assume it to mean.
Aaronaught
And as for your second follow-up question - yes, you should be able to reference something like a `PhoneNumberConverter` from anywhere in the app, that's why you have one. ;)
Aaronaught
Okay, I took a shot at setting the `UpdateDateTime` and `InsertDateTime` fields in my repository. Seems pretty clean, and it will be easy to apply this to all my repositories via my repository base class. Now, though, you have me wondering if validation code should also go in my repository :) It seems that the LinqToSql classes are designed for validation, so I should probably leave it alone, but it seems like validation of business rules should be "data source independent", which would suggest the repository is a better choice. Love to hear your thoughts on that.
DanM
For validation I actually use xVal with DataAnnotations, which means (roughly) that the validation "rules" go in the model (as attributes) but the actual validation is a few lines of code in the Controller. I believe other validation frameworks (i.e. Castle) work in a similar way. Repository is technically part of the Model but I wouldn't put validation there, at least not exclusively and not for simple types of validation. Keep your classes single-purpose if you can; a repository handles loading and saving, validation is something that happens before saving.
Aaronaught
I think what you're describing is very similar to what I'm currently doing, so I'll stick with what I have. Thanks for all your help!
DanM