A little confused about where to put things in ASP.NET MVC
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?
Id
- I believe this is taken care of automatically by SQL-Server because I have all my Id fields set up asIDENTITY (1, 1)
. I haven't tested extensively, but this seems to "just work". Please confirm.InsertDateTime
- I want this to be set toDateTime.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?UpdateDateTime
- I want this to be set toDateTime.Now
for both Create and Edit actions, but again, where should I put the code that does this assignment?PhoneNumber
- Unlike the three fields above, this one is editable by the user, but it need to gets transformed from(888) 123-4567
to8881234567
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 addPhoneNumber
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:
User
table in the database - handles Id assignment. Could provide default values for InsertDateTime and UpdateDateTime.User
class - handles validation usingGetRuleViolations()
method.UserRepository
class - abstracts away data persistence functions (get, get all, add, delete, and save).UserController
class - handles user requests and post attempts (index, details, edit, posted edit, create, posted create, and delete).UserFormViewModel
class - provides strongly typed data to view (aUser
object plus backing data for drop-down menus).Views/User/Create.aspx
andViews/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
Should the
UpdateDateTime
andInsertDateTime
fields be assigned in theUserRepository
class or theUser
class?Does it makes sense to call phone number conversion methods (on a
PhoneNumberConverter
class) from theUserFormViewModel
?
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:
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
andUpdate
methods, or a singleSave
method as I've done.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 aPhoneNumberConverter
above is that you might want to have this expose both aValidate
andConvert
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 aPhoneNumber
member in theDataType
enumeration.Your
Save
method (orAdd
orUpdate
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 originalUser
object passed to it after the database transaction has succeeded. This should also answer your question #1 - if you have anIDENTITY
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.
精彩评论