Due to the nature of the way the website I am working on was designed I have an issue I now must resolve.
I will try to write this question as language-independent but the site is done in ASP.Net with C#.
Overview
The structure of our site looks like this for any given "object":
ASPX Page
Multiple UserControls (ascx pages)
Textboxes, Comboboxes, Labels,
Buttons, etc.
What this means is, if we have a User page and that page might have 3 UserControls. One for User Information (Name, email, etc.), one for Address (City, State, Zip, etc.) and one for Regional Settings (Time zone, language, etc.)
All of these fields on the 3 UserControls are all stored in the same table in the database.
Now, we are using DataBinding and EntityFrameworks to fill in the data and this means that when we save an object, we are actually calling save 3 times (1 per UserControl).
(Language-Agnostic: We don't actually
assign values like User.Name =
"Bob", it is handled by a Framework)
Because the UserControl only knows about the fields it contains, when the Databinding saves it "thinks" the other fields are now blank. (i.e. Address control saves and now "Email" and "Name" are null values). To "fix" this there is a method called "MapOldToNew" which goes out and grabs the old record and fills in the values on the New object that is about to be saved. This is generic, which means we don't have a method on the Address Usercontrol that says "go get the Name and Email fields and fill them in", instead it loops through all the properties of the Entity(object) and if a value is NULL on the New object but NOT NULL on the Old object (the one currently in the database about to be overwritten) it will make the New value equal to the Old value.
The Problem
Dates. We allow NULL dates in our database.
If a User fills in "Birthday" and saves, they now have a Birthday value in the field in the database.
If they go back and clear out the Birthday field on the web page and save, the MapOldToNew method grabs the old record, sees that Birthday is not null there and is Null on the New object about to be saved, it will override the New value (NULL) with the Old value ('7/23/1981' for example).
Solutions?
Rework our system to not use
UserControls but instead have all
controls on the same Page, thereby
reducing the number of times we need
to Save and not requiring
MapOldToNew. For now, let's
assume this is an impossible
solution (even though it is the one
I feel like should be done for
plenty of reasons).
Store all
dates as Strings and do conversion
on Load and Save. (Strings don't
have the same problem because once a
string has been modified it is now
an empty string, and not NULL)
Do not allow NULL dates in the
database and always store
DateTime.MinValue and do not
display it On Load.
Those are the ideas I have come up with off the top of my head. For arguments sake, let's assume #1 is not possible (because I have a feel management won't like the time it will take to accomplish).
How would you "fix" this?
Another Explanation
Here is the problem broken down more.
User Record has 2 fields. Name and Birthdate.
Assume that there is 1 ASPX Page with 2 UserControls (ascx).
UserControl1 has a single DatePicker DataBound to "BirthDate".
UserControl2 has a single TextBox DataBound to "Name".
When UserControl1 calls Update on the ObjectContext the User object it sends looks like this:
{ Name = NULL, BirthDate = 8/13/1980 }
MapOldToNew looks at the Database Record and sees that Name should be "Bob" so it fills the value in and the record saves.
UserControl2 calls Update now and the User object looks like this:
{ Name = "John", BirthDate = NULL }
MapOldToNew sees that BirthDate is NULL so it overwrites it with 8/13/1980 from the database and the record saves and the database has the correct values.
Now assume the user goes back in and deletes the BirthDate value. When UserControl2 calls MapOldToNew, it sees that BirthDate is NULL (which the user wants) and overwrites it from the Database.
If it DIDN'T overwrite it from the database, it would always save it as NULL and the user would never be able to set the BirthDate.
I would refactor MapOldToNew to understand nullable types. Im assuming you have a nullable date (for ex. datetime? or Nullable and if so - do not make the assumption of overwriting if null.
Also in the future consider MVC or for WebForms consider Dynamic Data because of the mapping of types to models.
http://aspnet.codeplex.com/wikipage?title=Dynamic%20Data
Related
I've built a number sites using classic ASP type security and have continued using the same methods in ASP.NET. However, I now am looking for recommendations for a better or best practice way of doing it. I've read a few resource online but have not seen anything comprehensive enough that's applicable to what I'm trying to do. What I'm trying to do is apply user specific security that determines that user's access to specific pages, sections on that page, and fields in each section. It also needs to restrict access to various records as well and determine whether it's read or write privileges.
For those interested, here's how I've done it so far:
Because I lacked the know-how, here's how I did it using the old ASP classic way...
First, on the database side I have 4 relevant tables: tblUsers, tblRoles, tblPages, tblRecords (haven't gotten to sections and fields yet). Each user can belong to a "role" and the role then determines what pages they can access as well as what records they can access in various tables (there are a number of other tables in the db as well e.g. tblCustomers, tblSales, etc...). Each table includes these fields:
tblUsers: UserID, UserName, UserPwd
tblRoles: RoleID, RoleName, UserID
tblPages: PageID, PageName, RoleID
tblRecords: RecordID, RecordTable, RoleID
Now on the application side here's what I've done:
First, my login page is based on 1) looking up the user name and password in the tblUsers table and 2) if found, setting a session variable named "UserLoggedIn" = true. Then on every page load event I check if the UserLoggedIn session is set to true, if so, continue... if not clear all session variables (log out) and send the user back to the login page. The old classic ASP way of doing it.
Second, with the login set up, to control page access, when the user is logged in I created another session variable that holds a pipe delimited string of all the pages that user can access (e.g. Session("PageAccess") = "{1|3|10|8}"). Then in each page's load event I've explicitly added a variable/constant that uniquely identifies that page (e.g. pageone.aspx has Dim PageID As String = 1). Then I check to see if the PageID matches an ID stored in the PageAccess session. If it does, continue... If it doesn't I send them to the home page.
Third/Last, for the records access, I did the same thing. When the user is logged in I created a session variable that hold a pipe delimited string of all the records the user could access along with the table it's related to (e.g. Session("RecordAccess") = "{tblCustomrs||1|5|7}" and checked it and applied it basically the same way as the page session variable.
My Solution is :(it worked for my projects)
tables : tblUser , tblRole ,
tblUserInRole : userid,roleid,username,password (this design help you can assign more than one role to a user)
tblrole, tbrules(your Access Controls for example pages)
tblRulesInRole : roleid , ruleid,Editable,Viewable,printable,Deletable
For Implement Access Control in every request and response ,you should Create HttpModule......
I have an object with multiple collections that is retrieved from a WCF service and stored in the session.
The collections are bound to ListViews on the page, with a final submit button at the bottom.
I want to be able to make changes to this object in the session (add/edit items in the collections), without persisting the changes until the final submit button is clicked.
The problem I'm having is that the ids are created in the db, so all newly added items will have an id of 0. I don't see a way to uniquely identify the collection items unless I add something like a clientID field to the datacontract. I feel like I'm missing something really obvious here.
If these are auto-generated identity values, you can insert additionally guids for each entry.
I have a classic ObjectDataSource and a ListView in my page. The List view just displays some data and when switched to edit template it allows the user to change the values. I want the user to edit just some values -- so I bind just these ones in the edit template.
The problem is that the other values suddenly turn to nulls or 0. I tried to bind all of the values at once and it works fine, but I cannot understand why the old/original values just disappear. Is there any way how to bind the old values?
Thanks for help.
The problem is, that only the data that is included into a round-trip to the server will be available in the postback. That includes all that that is bound to BoundFields, TemplateFields or if the Propertyname is included in the DataKey (or DataKeyNames, don't know right now).
The best approach to fix this, and to keep the overhead to a minimum is to add your primary key to the DataKeyNames collection. This allows you to have access to your custom object that contains an unique identifier and all properties that have just changes.
In your Update Method of the ODS (in your custom class) you now need to retrieve the old object by its unique identifier, manually assign the new values and saves your object back to the database
Let's say I have a class Person, with a string[] nickNames, where Person can have 0 or more nicknames stored. I want to create an asp.net page where a user can go and add/edit/delete nicknames.
Question is- how to i persist the Person object between postbacks? I query the DB and create the object then display it on a form, but then the user has the option to edit/delete fields of that object.. once the page is displayed with the fields of Person, how do I update that object with the changes the user made, to store to db?
Thanks!
Well if your Person Object is serializable you could store it in ViewState and if not, you could stick it in Session, but it sounds like you might have a general lack of understanding about Data Persistance in general
Depending on your implementation, and whether you're coding this all by hand or using the built in DataSource/DataAdapter controls, theres a bunch of ways to do it.
You could have a look at some basic ASP.NET/ADO.NET Tutorials to point you in the right direction
http://aspnet101.com/aspnet101/tutorials.aspx?id=17
Query the object it again (you could store it in a session variable but that doesn't scale), gather and apply changes from user upon postback.
I have a web application which has a Sql Server database on the backend. As the site will be rolled out to different clients (or different virtual directories in IIS), each client derivative of the site will use the same backend.
There is an admin page where text on the site can be changed (enter text in a listview, and choose the page to select where that text will show up, and also you can see company-specific details in the other listviews. As this is a shared database, that means a client can see each other's data.
What I am trying to do is store the accountId (a guid returned from the database from login_authenticate), and stick this into session. I then retrieve this on the admin page, and I want to use this value (But it's always 0000-0000 etc), to limit the records returned in the listview.
Is there an example of this? Also, how can I set the default value (this is in the where clause of SqlDataSource), to programatically what the account id is (so I can give me all records = what the current accountid is, or perhaps, what the login is - this is stored in the account table).
Thanks
This is what I tried.
What I am confused about, though, is whether the where clause, when using a session object, is getting an object that I have written the code to retrieve from the session, or an object I have only added but not retrieved. I get the accountID when logging in (verified via stepping in - obviously - or the login will fail).
I will try again with storing the object in session # the login page when I have just retrieved the accountid variable, and then retrieve it on another page.
For some reason I keep getting 0s so I will look at this in my application.
It sounds like your method should be working. I would follow a debugging process:
Check that you are getting the accountID value from the database. Print it on screen immediately after retrieving the value for the first time.
If this is working, store the value in the Session and immediately retrieve it, and check that you are getting the value back.
Create 2 test pages, one where you set the Session variable and another where you retrieve it.
I know this seems really basic, but the failure is being introduced somewhere in the above 3 places. If you can find which step fails, you will be able to fix it.