in
Forums
Blogs
DevExpress.com
Client Center
Support Center
DevExpress Channel

Paul Kimmel's Blog

Using Custom Objects with ASPxScheduler

I was curious and surprised to discover that some employers are literally listing “Developer Express controls experience” as a pre-requisite for some programming employment. For some employers it is not enough to be a good developer, a good ASP.NET programmer, a good database guy, and a good JavaScript guy (or gal). Some employers want to know that a potential candidate has real experience with Developer Express controls. The reason for this has quite simply got to be that one need a tremendous amount of experience to use what Developer Express has to offer. The reason for this is not that our controls are inordinately difficult to use, rather it is that there is a tremendous amount of pre-existing work available for the picking if one knows where to look. Experience is how one knows where to look.

I have spent the better part of 1,500 hours this year working on Professional DevExpress ASP.NET Controls—the book—and am in awe of how much material is added regularly to our product line. The ASP.NET controls is a big part of our product line—by no means all—but there is a lot there. In retrospect I think we could have written an ASPxGridView book and easily covered 400 pages. It was actually hard to keep the grid coverage at 100 pages. The ASPxScheduler is another big control. In fact, the ASPxScheduler is more of a sub-system than a control. With a little bit of code and a couple of data source you could create an MS Outlook (tm) style interface, a project management tool, or just about any scheduler you could imagine (I suspect). And, you could probably create a substantial amount of functionality without writing hardly any code at all. The caveat being that you know how to use the control.

One of my objectives as evangelist is to give you the information you need to leverage as much of the code we have already written for you without you having to re-write from scratch what more than likely already exists—if you just know where to look. To that end, me, Mehul, Oliver, Gary, Mark and many others write blogs, tweets, articles (and a book) now to point you in the right direction. I use custom objects a lot for development, and this blog post shows you how to use the ASPxScheduler with custom objects. (You can of course use a database, but that’s another blog post and its covered in the book.) Here is an overview of what it takes to use custom objects with the ASPxScheduler:

  • The ASPxScheduler normalizes appointment item as the appointment (as well as recurring appointment detail) from one source and resources from another
  • The ASPxScheduler separates presentation from Storage with pre-determined fields for appointments and resources
  • You simply map your appointment fields to our appointment fields and your resource fields to our resource fields, specify how the CRUD happens—SQL or whatever—and the scheduler is fully operational

So for custom objects with the ASPxScheduler you need the custom objects, a BindingList<T>, and the equivalent of an object data source class, and if your custom object fields use dissimilar names then you need to map your properties to the scheduler’s properties.  it sounds like a lot of code, but the example can actually be coded—custom objects, lists, and data source—with just under 200 lines of code, most of which is represented by your custom objects. And, if you use CodeRush or automatic properties this can be done very quickly.

Reviewing the Appointment and Resources Schemas

The “schemas” for the Appointment and Resources objects as the ASPxScheduler sees the world are represented by Figures 1 and 2 respectively. the closer your objects reflect these schemas the easier the mappings are. for example, if your appointment class has a StartDate named StartDate then it is a straight forward map. If you are using the mapping wizard it will guess. the closer the names and data types are the better the guesses will be.

image
Figure 1: The ASPxScheduler’s view of an appointment.

image
Figure 2:  The ASPxScheduler’s view of the resources world.

For resources the demo simply uses an array of random names and the index as the ResourceId and ResourceName. You can get away with this because it isn’t necessary to map all of the fields. You can just specify the ones you need. The Appointments will be mapped to the custom object DentalAppointment (see Listing 1). DentalAppointment is defined using automatic properties and has a one-to-one mapping between its properties and the fields an Appointment uses in the ASPxScheduler (except for one extra field Price, which goes unused).

Listing 1: The DentalAppointment class has almost the identical set of properties and types as the Appointments used by the ASPxScheduler, making mapping pretty easy.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

[Serializable]
public class DentalAppointment
{
  public object Id { get; set; }
  public DateTime StartTime{ get; set; }
  public DateTime EndTime{ get; set; }
  public string Subject{ get; set; }
  public int Status{ get; set; }
  public string Description{ get; set; }
  public long Label{ get; set; }
  public string Location{ get; set; }
  public bool AllDay{ get; set; }
  public int EventType{ get; set; }
  public string RecurrenceInfo{ get; set; }
  public string ReminderInfo{ get; set; }
  public object OwnerId{ get; set; }
  public double Price{ get; set; }
  public string ContactInfo{ get; set; }
}

 

Implementing the Custom BindingList<T>

The most common in-memory holder for custom types is some kind of collection or collection<T>. The BindingList<T> is a good candidate for custom collections because it is a generic collection and it supports data binding.  Because BindingList<T> is a generic collection it can be instantiated with any type for the teplate parameter T. The BindingList<T> supports data binding because it has methods and events that are typical of databound operations, including AddNew, CancelNew, InsertItem, RemoveAt, OnAddingNew, etc. (You can check the help for the complete list of operations.) Listing 2 contains the DentalAppointmentList class, that inherits from BindingList<DentalAppointment>, for the sample.

Listing 2: The generic data-bindable collection.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Web;
using System.ComponentModel;

[Serializable]
public class DentalAppointmentList : BindingList<DentalAppointment>
{
  public void AddRange(DentalAppointmentList events)
  {
    Array.ForEach(events.ToArray(), e => this.Add(e));
  }
  public int GetEventIndex(object eventId)
  {
    var result = this.Where(item => item.Id == eventId).First();
    if (result != null)
      return this.IndexOf(result);

    return -1;
  }

}

 

Generally you can just instantiate Bindinglist<T>, but I use inheritance to create a place to add a few additional business behaviors. In our example AddRange was added to supported appending a list of items, and GetEventindex finds an object by its Id instead of by the object itself. AddRange and GetEventsIndex both use one of the newer features in .NET Lambda expressions--e=>this.Add(e) and item=>item.Id == eventId, respectively. A Lambda expressions is an even more condensed version of an anonymous method. The left side is like the function header and parameters and the right side is the method body. The funny operator => (referred to as “goes to” or a “gosinta” by me) is borrowed from the original mathematical expression language.

You could use a plain vanilla for loop for both methods, but where is the fun in that.

Implementing the Custom DataSource

The custom datasource plays the role of negotiating CRUD behaviors between the client control—the ASPxScheduler in this case—and an ObjectDataSource component. The custom datasource—DentalAppointmentDataSource in Listing 3—will be the TypeName property of an ObjectDataSource. The ObjectDataSource component will be assigned to the ASPxScheduler’s AppointmentDataSource property. (Remember that the ASPxScheduler has an AppointmentDataSource and ResourceDataSource.)

The key to a custom datasource is to define methods that are assigned to the ObjectDataSource’s Selectmethod, UpdateMethod,, InsertMethod and DeleteMeethod properties. The ObjectDataSource will forward these operations to the business object’s methods defined by ObjectDataSource.TypeName that perform these functions. Referring to Listing 3 it is evident by name which behaviors implement the required operations.

Listing 3: A custom business object that  performs CRUD operations for a custom ObjectDataSource.

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Web;

public class DentalAppointmentDataSource
{
  private DentalAppointmentList events;
  public DentalAppointmentList Events
  {
    get { return events; }
    set { events = value; }
  }

    public DentalAppointmentDataSource(DentalAppointmentList events)
    {
    this.events = events;
    }

  public DentalAppointmentDataSource()
    : this(new DentalAppointmentList())
  {}

  #region ObjectDataSource methods
  public void InsertMethodHandler(DentalAppointment appointment)
  {
    Events.Add(appointment);
  }

  public void DeleteMethodHandler(DentalAppointment appointment)
  {
    Events.Remove(appointment);
  }

  public void UpdateMethodHandler(DentalAppointment appointment)
  {
    int eventIndex = Events.GetEventIndex(appointment.Id);
    if (eventIndex >= 0)
    {
      Events.RemoveAt(eventIndex);
      Events.Insert(eventIndex, appointment);
    }
  }

  public IEnumerable SelectMethodHandler()
  {
    DentalAppointmentList result = new DentalAppointmentList();
    result.AddRange(Events);
    return result;
  }
  #endregion
}

Defining the GUI

The presentation for the demo is an ASPxScheduler bound to an ObjectDataSource who’s behaviors are supported by the custom business object DentalAppointmentDataSource (in Listing 3). To implement the GUI—shown at runtime in Figure 3—follow these steps:

  1. Place an ASPxScheduler on a Web form
  2. Right click the Smart tags menu, select Appointment Data Source|New data source
  3. In the Data Source Configuration Wizard select Object as the data source type
  4. Click OK
  5. Choose the DentalAppointmentDataSource as the business object
  6. Click Next
  7. In the Define Data methods step pick the relevant method for each of the SELECT, UPDATE, INSERT, and DELETE operations (see Figure 4)
  8. Click Finish
  9. Clear the ASPxScheduler’s AppointDataSourceID from the properties window because in this example we will be setting the DataSource and data binding in the code behind

 

image
Figure 3: The running demo application with its default appearance and one appointment added by me. 

 

image
Figure 4: Define the data source’s various CRUD operations.

The numbered steps above will generate (minus a few bits we still have to do) the ASPX shown in Listing 4. With just a few lines of code more you will have a functioning appointment/scheduling sub-system.

Listing 4: The ASPX generated by adding the ASPxScheduler and the ObjectDataSource.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register assembly="DevExpress.Web.ASPxScheduler.v9.1, Version=9.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxScheduler" tagprefix="dxwschs" %>
<%@ Register assembly="DevExpress.XtraScheduler.v9.1.Core, Version=9.1.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.XtraScheduler" tagprefix="dxschsc" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <dxwschs:ASPxScheduler ID="ASPxScheduler1" runat="server"
            onappointmentinserting="ASPxScheduler1_AppointmentInserting"
            Start="2009-06-30">
<Views>
<DayView><TimeRulers>
<dxschsc:TimeRuler></dxschsc:TimeRuler>
</TimeRulers>
</DayView>

<WorkWeekView><TimeRulers>
<dxschsc:TimeRuler></dxschsc:TimeRuler>
</TimeRulers>
</WorkWeekView>
</Views>
        </dxwschs:ASPxScheduler>
        <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
            DataObjectTypeName="DentalAppointment" DeleteMethod="DeleteMethodHandler"
            InsertMethod="InsertMethodHandler"
            SelectMethod="SelectMethodHandler"
            TypeName="DentalAppointmentDataSource" UpdateMethod="UpdateMethodHandler"
            onobjectcreated="ObjectDataSource1_ObjectCreated">
        </asp:ObjectDataSource>
    </div>
    </form>
</body>
</html>

Writing the Code-Behind

The code behind performs five general tasks: data binding, populating the ASPxScheduler’s Resources, storing the DentalAppointmentList in Session, mapping the DentalAppointment properties to the ASPxScheduler appointment fields, and creating a unique appointment id when an appointment item is inserted in the scheduler. Listing 5 contains the code-behind—which is less than 100 lines of code—for the Web page, followed by a brief synopsis of the tasks being performed.

Listing 5: The code-behind for the  sample application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using DevExpress.Web.ASPxScheduler;
using DevExpress.XtraScheduler;
using DevExpress.Data;
using System.Diagnostics;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
      MapCustomObjectsToAppointmentFields();
      PopulateResourcesData(Storage, 5);
      ASPxScheduler1.AppointmentDataSource = ObjectDataSource1;
      ASPxScheduler1.DataBind();

    }

    ASPxSchedulerStorage Storage { get { return ASPxScheduler1.Storage; } }

    private void MapCustomObjectsToAppointmentFields()
    {
      ASPxAppointmentMappingInfo mappings = Storage.Appointments.Mappings;
      Storage.BeginUpdate();
      try
      {
        mappings.AllDay = "AllDay";
        mappings.AppointmentId = "Id";
        mappings.Description = "Description";
        mappings.End = "EndTime";
        mappings.Label = "Label";
        mappings.Location = "Location";
        mappings.RecurrenceInfo = "RecurrenceInfo";
        mappings.ReminderInfo = "ReminderInfo";
        mappings.ResourceId = "OwnerId";
        mappings.Start = "StartTime";
        mappings.Status = "Status";
        mappings.Subject = "Subject";
        mappings.Type = "EventType";
      }
      finally
      {
        Storage.EndUpdate();
      }
    }

    public static string[] Users = new string[] { "Peter Dolan", "Ryan Fischer", "Andrew Miller", "Tom Hamlett",
                        "Jerry Campbell", "Carl Lucas", "Mark Hamilton", "Steve Lee" };
    private void PopulateResourcesData(ASPxSchedulerStorage storage, int count)
    {
      ResourceCollection resources = storage.Resources.Items;
      storage.BeginUpdate();
      try
      {
        int c = Math.Min(count, Users.Length);
        for (int i = 1; i <= c; i++)
          resources.Add(new Resource(i, Users[i - 1]));
      }
      finally
      {
        storage.EndUpdate();
      }
    }
    private DentalAppointmentList GetDentalAppointments()
    {
      DentalAppointmentList events =
        Session["ListBoundModeObjects"] as DentalAppointmentList;
      if (events == null)
      {
        events = GenerateDentalAppointmentEventList();
        Session["ListBoundModeObjects"] = events;
      }
      return events;
    }

    private DentalAppointmentList GenerateDentalAppointmentEventList()
    {
      return new DentalAppointmentList();
    }

    protected void ASPxScheduler1_AppointmentInserting(object sender, PersistentObjectCancelEventArgs e)
    {
      ASPxSchedulerStorage storage = (ASPxSchedulerStorage)sender;
      Appointment appointment = (Appointment)e.Object;
      storage.SetAppointmentId(appointment, "d" + appointment.GetHashCode());

    }
    protected void ObjectDataSource1_ObjectCreated(object sender, ObjectDataSourceEventArgs e)
    {
      e.ObjectInstance = new DentalAppointmentDataSource(GetDentalAppointments());
    }
}

The Page_Load method performs initialization by performing calling the mapping step, adding resources, and data binding. The Storage property is a simplifcation property that simply surfaces the underlying ASPxScheduker’s Storage property. MapCustomObjectsToAppointmentFields does exactly what the method name indicates: it tells the ASPxScheduler what names in the custom objects refer to which fields in the scheduler. (The name-strings are case sensitive.) PopulateResourcesData creates some resources, in this case, random names playing the role of Dentist. GetDentalAppointmentList manages creating the events list and storing it in Session. GenerateDentalAppointmentList is really just a placeholder. In a real business application this method would ultimately construct appointments from a data source like a Web service, XML file, or database. AppointmentInserting is event that needs to be created on the ASPxScheduler.This method sets the AppointmentId, cleverly using an object’s guaranteed to be unique hash code. Finally, ObjectCreated.ObjectCreated is associating the custom datasource with the ObjectDataSource.

    protected void ASPxScheduler1_AppointmentInserting(object sender, PersistentObjectCancelEventArgs e)
    {
      ASPxSchedulerStorage storage = (ASPxSchedulerStorage)sender;
      Appointment appointment = (Appointment)e.Object;
      storage.SetAppointmentId(appointment, "d" + appointment.GetHashCode());

    }
    protected void ObjectDataSource1_ObjectCreated(object sender, ObjectDataSourceEventArgs e)
    {
      e.ObjectInstance = new DentalAppointmentDataSource(GetDentalAppointments());
    }
}

The ASPxScheduler peforms a large number of housekeeping tasks, including adding user controls for editing appointments, searching and recurring appointments. While it takes some work to get the scheduler up and running, the work—represented by this article—is infinitesimally smaller than the amount of code needed to implementing a custom scheduler control.

Thanks for playing.

Published Jul 01 2009, 09:25 PM by Paul Kimmel (Developer Express)

Comments

 

Pawel said:

Is there more books that cover use of DevExpress components?

July 9, 2009 3:42 AM
 

Paul Kimmel (Developer Express) said:

None that I am aware of. Of course, perhaps if more customers express an interest there will be. Thanks for asking.

July 9, 2009 4:10 AM
 

elizabeth vue said:

so is there anyway that I can customize the side header so that it searches for an event/schedule by doing a side name search

December 16, 2009 3:24 PM
 

Paul Kimmel (Developer Express) said:

Elizabeth:

Can you elaborate or send me a mini project or screenshot of what you are trying to accomplish?

paulk@devexpress.com

Paul

December 17, 2009 11:26 AM

Leave a Comment

(required)  
(optional)
(required)  
Verification code: Required
   
Add
Copyright © 1998-2010 Developer Express Inc.
ALL RIGHTS RESERVED