A little confused about where to put things in ASP.NET MVC

By : devuxer
Source: Stackoverflow.com
Question!

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?

By : devuxer


Answers
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.



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.



This video can help you solving your question :)
By: admin