Telerik blogs

Packaging code into reusable objects can save you time—if you make the right design decisions. Here’s a case study for creating tools that work with the Telerik Scheduler for Blazor.

After writing a ton of code, you realize that you could package it up as a reusable object and never have to write that code again. Here’s some advice on doing that well, with a case study for creating tools for working with the Progress Telerik UI for Blazor Scheduler.

In previous posts about working with recurring events in the Progress Telerik UI for Blazor Scheduler, I’ve sketched out the kind of code that lets you build an app with Scheduler that enables users to modify schedules, override scheduled events, add new events and even add new schedules (here’s the first post in that series).

Of course, the next step for any developer would be to bundle that code into a set of reusable objects that would simplify working with the Scheduler in the next application. The goal would be to be able to configure all of Scheduler’s functionality like this:

<TelerikScheduler
  Data="DataList"
  RecurrenceRuleField="RecurrenceRuleString"
  
  AllowUpdate="true"
  AllowCreate="true"
  AllowDelete="true"

  OnDelete="@DeleteSchedule"
  OnUpdate="@UpdateSchedule"
  OnCreate="@CreateSchedule">

  …other settings…
</TelerikScheduler>

And then support that all that functionality with something like these five lines of code:

List<RecurringEvent> DataList;
SchedulerManager<RecurringEvent> sm = new() { AddSchedule = true };

void DeleteSchedule(SchedulerDeleteEventArgs e) { sm.DeleteSchedException(e); }
void UpdateSchedule(SchedulerUpdateEventArgs e) { sm.UpdateExceptionSched(e); }
void CreateSchedule(SchedulerCreateEventArgs e) { sm.CreateExceptionSched(e); }

And that’s all very doable—there are links to the objects I’ve used here (RecurringEvent and SchedulerManager) in this post to prove that. But that project could easily turn into a time-consuming example of “developer’s disease”: If it can be coded, then it must be coded.

No, it doesn’t.

In fact, if there’s one mantra to use as guidance in creating reusable objects, it’s this: You’re creating reusable objects to make your and your team more productive in the future by handling the “typical tasks” in applications you might build for your organization in the future. “Typical tasks,” in this case, being any task that would otherwise require repeating code you’ve already written in the next application.

What you’re not doing is creating a commercial product to support any developer creating any application in any organization.

So, my goal in this post is to look at the kind of design decisions you might make in creating reusable objects, not to provide a set of universal support objects for Scheduler. The code, while I’m pretty sure works, is here as an example of what the results of those decisions look like.

The RecurringEvent Class

For example, Scheduler needs a List of event objects to bind to, and working with Scheduler consists of manipulating that List of event objects. In creating a set of reusable code, I don’t want to support working with any possible object—that would be far too big a task. My first design decision, therefore, is to design the only object that my code will work with.

In this case, I created a class called RecurringEvent that I could tweak as needed to support the code I would write. To simplify that class even further, I had it inherit from the Telerik abstract Appointment class, which has all the properties that Scheduler needs.

Now, having created that class, I can also extend it as necessary to both support the reusable code I’ll write and handle any typical tasks. As an example, I added these members to my RecurringEvent class:

  • A default constructor: This constructor helps set every RecurringEvent object’s Id property to a unique GUID. This eliminates the need to set the Id property when generating event objects.

  • RecurrenceRuleString: This is a string representation of the schedule (in RFC5545 format) and is required by Scheduler. My implementation of this property not only gives Scheduler what it wants but also sets the base Appointment class’s RecurrenceRule property (used when generating future events) to update whenever RecurrenceRuleString is changed so that the two properties are always in sync.

  • RecurrenceExceptionsString: Scheduler suppresses regularly scheduled occurrences for any events added to the schedule object’s RecurrenceExceptions property—I’ll need to save that list of exceptions in any database that holds my schedule. To support that, the RecurrenceExceptionsString property returns the RecurrenceExceptions array as a comma-delimited string for easy storage. Similarly, when retrieving a schedule object from my database, I’ll want to reload my RecurrenceExceptions property from that string, and my RecurrenceExceptionsString property supports doing that.

  • GenerateSchedule: To share a schedule with other applications, I’ll need to store the regularly scheduled events in a database. This method holds the code to return a List of RecurringEvent objects for every generated event between the schedule’s Start and the schedule’s OriginalEnd date

And that’s great … but it’s reasonable to assume that any future application that uses Scheduler is probably going to require some additional, application-specific properties that RecurringEvent doesn’t have. Rather than extend my solution to work with any class in the world, I took advantage of the DataItem property that RecurringEvent inherits from the Telerik Appointment class.

The DataItem property is declared as type object so DataItem will hold any .NET object (anything from a string to a collection). I just need to set up my code so that whatever is in a schedule object’s DataItem property is passed to any related objects that SchedulerManager creates.

Now, when I come to use RecurringEvent in some other application, I can store any additional information any application needs in the DataItem property, confident that SchedulerManager will propagate that property appropriately.

And here’s the code for the RecurringEvent class.

SchedulerManager

Having defined the only object that my code has to work with, the next step is to write the code that uses that object—initially only supporting the typical cases.

So, for example, my SchedulerManager exposes the three methods required to support the parts of Scheduler’s functionality that I’m interested in: adding, updating and deleting scheduled events (I covered those methods in my earlier series). Now, for the typical case, I can just wire up those methods to Scheduler’s OnCreate, OnUpdate and OnDelete methods as I showed at the start of this post (recognizing that if I wire up both OnCreate and OnUpdate, the Blazor UI may throw an exception when the user double-clicks on a scheduled event and modifies it).

If you’re interested, here’s the code for SchedulerManager and SchedulerDataManager.

Supporting Customization

Once you’re set up to support the typical cases, you can consider extending your reusable objects to support some minimal set of expected customizations. Or not—supporting the typical case is a perfectly good place to stop and move on to the next application. Having said that, some customizations are easier to support than others.

For example, comprehensive reusable objects like SchedulerManager will sometimes be doing too much for you—there will be cases where you’d prefer to exercise more control than the reusable object provides.

To support that case, I separated my code into two classes: While SchedulerManager supports all the functionality that I want in Scheduler, it does that by calling methods in another class called SchedulerDataManager. SchedulerDataManager provides only the minimal set of functionality for managing Scheduler’s List of events. So, if I find that SchedulerManager is doing too much, I can ignore SchedulerManager and build my application using SchedulerDataManager.

The alternative customization scenario is when your reusable object isn’t doing enough. You can support that case by having your reusable object fire events at critical moments in its processing—that enables you to add additional code to the reusable object’s default processing when necessary. As an example, I’ve set up SchedulerManager to fire events whenever an event object is added, updated or deleted (and cleverly called those events OnEventAdded, On EventUpdated and OnEventDeleted).

As an example of when these events might be useful, SchedulerManager’s default processing assumes that I’ll update my database after the user is finished interacting with my application and clicks a Save button. For example, SchedulerManager stores the event objects it deletes in a DeletedItems list that I can use to remove data from my database when the user clicks my application’s Save button.

Personally, I like a “Save button strategy” because it also supports having a Cancel button that allows the user to experiment with their schedule without having to save it.

However, there might be a case where I need to update my database as my user makes changes (some application where changes need to be seen in real time, for example). Should that case come up, I could (presumably) handle that by inserting custom database update code into SchedulerManager’s events to extend SchedulerManager’s default processing.

In addition, all three of the events are passed an event parameter that includes a Cancel property that, when set to true, stops SchedulerManager from doing its “normal” processing. This allows me to not only extend SchedulerManager’s processing but also to replace it: I add some code in an event and then set the Cancel property to true to prevent SchedulerManager’s normal processing from happening.

Not Supporting Customization

You’ll need to decide what level of customization you’re willing to support (remembering that “none” is a perfectly good option). There’s also a middle ground open to you: Structure your object to support some potential customization but defer writing the required code to when (and if) you actually need it.

For example, as I discussed in the last part of my series on supporting Scheduler, allowing the user to add multiple schedules can create a problem. It’s a niche case, though, and only occurs if you allow the user to add both new events and multiple schedules to Scheduler. By default, SchedulerManager avoids that problem by preventing the user from adding multiple schedules.

But, rather than cut off the option for multiple schedules completely, I added a property called SupportMultipleSchedules to SchedulerManager. When the property is set to true, SchedulerManager will let the user add multiple schedules. That was all easy to do because all I did was include an empty code block where I would, someday and if I need to, write the more difficult code that handles multiple schedules. Should I ever actually create an application that needs multiple schedules, I’ll fill in that empty code block then.

Another example: While the various events that are fired by SchedulerManager can be cancelled, they don’t prevent the user from deleting a regularly scheduled occurrence (Scheduler handles that pretty much internally, making it difficult to interfere with the process). I didn’t even try supporting that. If I ever write an application where I need to prevent a user from deleting an occurrence from within SchedulerManager, I’ll figure out a way to do it then … and I recognize that may not involve adding code to SchedulerManager at all.

Ignoring a problem isn’t a viable solution for a commercial product. However, I don’t care because my job is to deliver applications to my client, not commercial objects. I’m only interested in reusability to the extent that it enables me (and my client’s IT shop) to deliver subsequent applications faster. And, as much as I like driving up my billable hours, solving problems neither I nor my client currently have isn’t part of my job description. It’s not part of yours, either.


Dive into this yourself: Try Telerik UI for Blazor free for 30 days.


Try Now


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is the author of the Coding Azure series, providing full-stack consulting from UX design through object modeling to database design. Peter holds multiple Azure certifications in Azure administration, architecture, development and security. He is also a Microsoft Certified Trainer.

Related Posts

Comments

Comments are disabled in preview mode.