On the same day that ASP.Net 2.0 left beta,
my hosting company
made 2.0 hosting packages available. In my excitement to work with some
of the new functionality, I immediately upgraded my account. From
there, I downloaded and installed the new Time Tracker application.
I had been using the previous 1.1 version for some time but found it
had some shortcomings. I was hoping that the new version would be
somewhat improved. However, I found that in some instances, it is
actually less functional and the functionality that is there isn't
without its problems. So, this post is an
attempt to share some of the issues and solutions I encountered over
these past two days.
==================
Installation
==================
Installation was actually pretty easy. I downloaded the app from the
link on this site.
I made sure to change the extension from .VSI to .ZIP. I then extracted
the files. Then I uploaded the following files and folders to my hosted
web site.
/App_Code/
/TimeTracker/
Default.aspx
Default.aspx.vb
Global.asax
web.config
web.sitemap
Of course I needed to change the connection string to point to my hosted database so I updated the web.config file accordingly.
<connectionStrings>
<add
name="aspnet_staterKits_TimeTracker"
connectionString="server=SERVERNAME;uid=USERNAME;pwd=PASSWORD;database=DATABASE"
/>
<remove name="LocalSqlServer"/>
<add name="LocalSqlServer"
connectionString="server=SERVERNAME;uid=USERNAME;pwd=PASSWORD;database=DATABASE"
/>
</connectionStrings>
I then needed to get the database installed. In the downloaded files
was an SQL script in the App_Data folder, timetracker-add.sql. I ran
the script from Enterpise Manager and installed the database objects.
This script does not install all of the necessary tables though! This
version of the application makes use of some role management tables
that need to be installed seprately. To install these tables, you will
need to run the following configuration tool from your desktop:
C:\WINNT\Microsoft.Net\Framework\v2.0.50727\aspnet_regsql.exe. You
will need the information from your connection string to fill in
the prompts.
I was then ready to go.
==================
First Impressions
==================
I created a new user by pointing my browser to
TimeTracker\user_create.aspx. The first thing I noticed was that the
security was pretty strict. It requires a password with a length of 7
and at least one non-alphanumeric symbol. Plus, you have to have a
security question/answer. Because the creation of users, in my
scenario, will be centralized I didn't have any need for a security
question or non-alphanumeric symbols. I added the following to the
<system.web> section of the web.config file.
<membership>
<providers>
<remove name="AspNetSqlMembershipProvider" />
<add
name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
minRequiredPasswordLength="6"
minRequiredNonalphanumericCharacters="0"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
passwordAttemptWindow="10"
passwordStrengthRegularExpression="" />
</providers>
Much better. So, I then went through and created the rest of my users.
The first thing you will notice is that there is no name field, only a
username. I didn't think much about it at first until I created my
first project and the list of team members was done my username. Not
very friendly. Anyway, moving on.
==================
Create New Project
==================
Creating a project follows almost the same process and in the
ASP.NET 1.1 version of the application. The layout is different,
but the concepts are the same. You input general information about the
project, pick team members, and add categories of work to be done.
Pretty straight forward. However, I ran into a problem with the BLL
code from Projects.vb. It seems the split method has changed. You will
need to search this file for "split" (should occur twice) and change
the code to:
ProjectIds.Split(separator, StringSplitOptions.RemoveEmptyEntries)
==================
Edit Project
==================
In order to delete a Category from a project, you will need to
change the BLL file Category.VB. Apply the same logic to get
UpdateCategory to work, too.
OLD:
Public Shared Function DeleteCategory(ByVal id As Integer, ByVal originalId As Integer) As Boolean
If
originalId <= DefaultValues.GetCategoryIdMinValue() Then
Throw (New ArgumentOutOfRangeException("originalId"))
End If
Return DataAccessHelper.GetDataAccess().DeleteCategory(originalId)
End Function
NEW:
Public Shared Function DeleteCategory(ByVal id As Integer) As Boolean
If Id <= DefaultValues.GetCategoryIdMinValue() Then
Throw (New ArgumentOutOfRangeException("Id"))
End If
Return DataAccessHelper.GetDataAccess().DeleteCategory(Id)
End Function
==================
List Projects
==================My
users don't need to see the generated project ID. Our ID is embedded in
the names of the project so I wanted to remove the project ID from the
GridView.
OLD
<asp:BoundField DataField="Id" Visible=true />
NEW
<asp:BoundField DataField="Id" Visible="false" /> --- or you could delete this line completely like I did.
I then wanted to remove the time from the "Completion" column, especially since it is always midnight.
OLD
<asp:BoundField DataField="CompletionDate" HeaderText="Completion"
DataFormatString="{0:d}" SortExpression="CompletionDate" />
NEW
<asp:TemplateField HeaderText="Completion Date" SortExpression="CompletionDate">
<ItemTemplate><%# String.Format("{0:d}",
Convert.ToDateTime(Eval("CompletionDate")))
%></ItemTemplate>
</asp:TemplateField>
Next, I was unable to delete a project. Whenever the sub
ListAllProjects_RowDeleting() was called (that is, whenever you hit the
delete button) it would throw an error at the following line in the code-behind.:
e.Keys.Add("Id", ListAllProjects.Rows(e.RowIndex).Cells(0).Text)
It seems it was trying to add an ID to the row, but the ID was already there. I simply deleted this line.
Now the delete function worked fine (although I haven't checked the DB
to make sure all of the staff and hours were deleted, too). I then
added a confirmation dialogue box to the delete button.
OLD
<asp:CommandField ShowDeleteButton="True" HeaderText="Delete"
DeleteImageUrl="images/icon-delete.gif" ButtonType="Image" />
NEW
<asp:TemplateField HeaderText="Delete">
<ItemTemplate>
<asp:ImageButton
ID="btnDelete" ImageUrl="images/icon-delete.gif" OnClientClick="return
confirm('Are you sure you want to delete this project?');"
CommandName="Delete" Runat="server" />
</ItemTemplate>
</asp:TemplateField>
==================
Log (entering time)
==================
It is when I got to this page that I began to think I was losing my mind. At first
glance, this page looked the same as the previous version, but in
reality it acted totally different. Maybe some people might like the
new direction, but it didn't do what I needed at all. Now, the
following steps may have indeed bastardized the application and over
time I may find a better way to accomplish what I needed, but this is
what I came up with in the first 48 hours.
1. No default date value -- I like to enter as little data as possible.
So, I wanted the "Day" field to be pre-populated with today's date.
After all, if I'm a good little programmer, I will enter my time daily
so my project manager doesn't hassle me. So in TimeEntry.aspx.vb I made
the following change.
If (Not Page.IsPostBack) Then
WeekEnding2.text = DateTime.Now.ToString("MM/dd/yyyy")
' rest of the code
End If
I also changed the label "Day" to "Date" in the corresponding APSX page. Afterall, it is the whole date.
2. Why can I see all of the projects? Well it turns out because I am a
Project Administrator, I can see all of the projects, not just the ones
I am assigned to. Very confusing to the user; I don't want people
trying to bill time to a project they aren't assigned to. So in
TimeEntry.aspx.vb I made the following change.
If (Not Page.IsPostBack) Then
' I don't think people should see projects
they don't have a role on so I deleted the code here that is commented
out.
' If (Page.User.IsInRole("ProjectAdministrator") OrElse Page.User.IsInRole("ProjectManager")) Then
' UserList.DataSourceID = "ProjectMembers"
'
If
Page.User.IsInRole("ProjectAdministrator") Then
'
ProjectData.SortParameterName = "sortParameter"
'
ProjectData.SelectMethod = "GetAllProjects"
'
ElseIf
Page.User.IsInRole("ProjectManager") Then
''
ProjectData.SelectParameters.Add(New Parameter("userName",
TypeCode.String, Page.User.Identity.Name))
'
ProjectData.SortParameterName = "sortParameter"
'
ProjectData.SelectMethod = "GetProjectsByManagerUserName"
' End If
' Else
ProjectData.SelectParameters.Add(New Parameter("userName",
TypeCode.String, Page.User.Identity.Name))
ProjectData.SelectMethod = "GetProjectsByUserName"
UserList.Items.Add(Page.User.Identity.Name)
' End If
' rest of code
Now, I only get my own projects.
3. Why can I see other people's time entries? I didn't like this
feature in the previous version. If you are an admin of any type, you
can select a different team member and see their time. You can also
enter time for them! Dangerous waters to tread in. So in TimeEntry.aspx
I just commented out that dropdown list box.
<!-- <asp:DropDownList ID="UserList" runat="server" AutoPostBack="True" CssClass="username" /> -->
I also changed the text from "Time entries for:" to "Time Entries:" since my users can't change this anymore.
4. Why do my time entries change when I change projects? If you go
through and enter time on various projects, you will notice that the
entries that appear on the right side of the screen only appear for the
currently selected project. I don't find this very useful. I need to
see all of my hours for today to see what I have entered in already. I
don't want to have to flip from project to project. After a little
investigation I found that this GridView is populated by the
"GetTimeEntries" select method. This method takes two parameters:
projectId and userName. There is no regard for date. This is an
interesting approach. This new paradigm moves the focus from the
individual to the project. I needed something more closely related to
the previous version though. I changed the select method on the
GridView from "GetTimeEntries" to "GetTimeEntriesByUserNameAndDates."
Like its name implies, this takes three parameters: userName,
startingDate, and endDate. My ideas is to get only the entries I have
made for a certain date; specifically, the date that is selected from
the calendar and copied to the page. The new parameters become:
<SelectParameters>
<asp:ControlParameter Name="userName"
ControlID="UserList" PropertyName="SelectedValue" Type="String" />
<asp:ControlParameter Name="startingDate"
ControlID="WeekEnding2" PropertyName="Text" Type="DateTime" />
<asp:ControlParameter Name="endDate"
ControlID="WeekEnding2" PropertyName="Text" Type="DateTime" />
</SelectParameters>
5. Why do the time entries show the date I recorded the entry but not the day
I did the work? This is baffling to me. Why do I care when I entered my
time? Moreover, when was the work actually done? If you look in the
code-behind page you see that it works the way it was intended.
"DateTime.Now" is passed to the database. Well, maybe the selected date
is passed too and it just doesn't show? Nope. The current date is the
only date passed to the DB. I simply changed "DateTime.Now" to
"WeekEnding2.Text" in the code-behind.
Dim timeEntry As TimeEntry = New TimeEntry(Page.User.Identity.Name,
Convert.ToInt32(CategoryList.SelectedValue),
Convert.ToDecimal(Hours.Text), WeekEnding2.Text, UserList.SelectedValue)
Of course, now I don't need to see a time. Thus,
OLD:
<asp:Parameter Name="ReportedDate" Type="DateTime" />
becomes...
NEW:
<asp:TemplateField HeaderText="Date">
<ItemTemplate><%# String.Format("{0:d}",
Convert.ToDateTime(Eval("ReportedDate")))
%></ItemTemplate>
</asp:TemplateField>
I also deleted the iD column. Users don't care or need to see this.
NOTE: I did break something here and I don't know how. After these
changes, the BLL code for TimeEntry.vb began throwing an error. I had
to comment out the following lines:
' If duration <= DefaultValues.GetDurationMinValue() Then
' Throw New ArgumentOutOfRangeException("duration")
'End If
The best I can tell is that this checks to make sure there is more than
zero hours submitted. I'm not really concerned though because of the
validation done before submission. BTW, I had to turn the validation
ON! TimeEntry.apsx should read:
<asp:Button ID="AddEntry" runat="server" CssClass="submit" CausesValidation="
True" Text="Add Entry" ValidationGroup="newEntry" />
==================
Log (editing entries)
==================
I wanted a little bit more control over the editing, so I used Template
fields. It sill needs some work though. How would you add dropdowns for
the categories? Also, I can't get the date to validate; any thoughts
welcome.
OLD:
<Columns>
<asp:BoundField DataField="Id" HeaderText="Id" ReadOnly=true />
<asp:BoundField DataField="CategoryName" HeaderText="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:BoundField DataField="Duration" HeaderText="Duration" />
<asp:BoundField
DataField="ReportedDate" HeaderText="ReportedDate"
DataFormatString="{0:d}" />
<asp:CommandField
ShowEditButton="True" HeaderText="Edit" ButtonType="Image"
EditImageUrl="images/icon-edit.gif"
UpdateImageUrl="images/icon-save.gif"
CancelImageUrl="images/icon-cancel.gif" />
<asp:CommandField
ShowDeleteButton="True" HeaderText="Delete"
DeleteImageUrl="images/icon-delete.gif"
ButtonType="Image" />
</Columns>
NEW:
<Columns>
<asp:TemplateField HeaderText="Project">
<ItemTemplate><%# Eval("ProjectName")
%></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Category">
<ItemTemplate><%# Eval("CategoryName")
%></ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="Category2"
Text='<%# Bind("CategoryName") %>' runat="server" Visible="False"
/><%# Eval("CategoryName") %>
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Description">
<ItemTemplate><%# Eval("Description")
%></ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="Description2"
Text='<%# Bind("Description") %>' runat="server" Columns="30"
MaxLength="200" /><br />
<asp:CustomValidator ID="CustomValidator2" runat="server"
ControlToValidate="Description2" ErrorMessage="Description must be less
than 200 characters" />
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Hours">
<ItemTemplate><%# Eval("Duration")
%></ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="Hours2"
runat="server" Columns="4" MaxLength="5" Text='<%# Bind("Duration")
%>' /><br />
<asp:RequiredFieldValidator
ID="reqHours2" runat="server" ErrorMessage="Hours is a required value."
ControlToValidate="Hours2" Display="Dynamic" />
<asp:CompareValidator ID="CompareValidator2" runat="server"
ErrorMessage="Hours must be a decimal value."
ControlToValidate="Hours2" Type="Currency" Operator="DataTypeCheck"
Display="Dynamic" />
<asp:RangeValidator ID="RangeValidator2" runat="server"
ErrorMessage="Hours is out of range." ControlToValidate="Hours2"
MaximumValue="24" MinimumValue="0" Type="Double" />
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Date">
<ItemTemplate><%# String.Format("{0:d}",
Convert.ToDateTime(Eval("ReportedDate")))
%></ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="ReportedDate"
runat="server" Text='<%# String.Format("{0:d}",
Convert.ToDateTime(Eval("ReportedDate"))) %>' Visible="False"
Columns="8" MaxLength="10" /><%# String.Format("{0:d}",
Convert.ToDateTime(Eval("ReportedDate"))) %>
</EditItemTemplate>
</asp:TemplateField>
<asp:CommandField ShowEditButton="True" HeaderText="Edit"
ButtonType="Image" EditImageUrl="images/icon-edit.gif"
UpdateImageUrl="images/icon-save.gif"
CancelImageUrl="images/icon-cancel.gif" />
<asp:CommandField ShowDeleteButton="True" HeaderText="Delete"
DeleteImageUrl="images/icon-delete.gif" ButtonType="Image" />
</Columns>
=============================================
That's my first 48 hours with the app. A little frustrating but fun at
the same time. I think this is all of the changes I can do without touching
the database. I am heading there next though. Things I think need to be done
still include:
- adding FirstName and LastName fields to the user profiles,
- turn on the "Copy Categories" function when creating a new project,
- changing all of the user lists to show FirstName LastName (or LastName, FirstName) instead of username,
- add the functionality to update a user (the skeleton code seems to be there but it wasn't implemented?),
- the Resource Report list the users multiple times, it should only
list each person once; possible remove projects from the page entirely,
and
- change the layout of some pages.
Anyway, I hope this is helpful to someone. I will post my updates to
this topic over time. It will also become an article over at
my web site when I have time.
Later,
Chris W.