This example demonstrates how to extend Scheduler events with custom fields and display these values in UI.
ASP.NET Core Blazor
Windows Forms
- Inherit from the
Event
class and implement new properties. For details, see the following file: ExtendedEvent.cs. - Map new properties to appropriate data fields.
In an ASP.NET Core Blazor application, accessCustomFieldMappings
in theAddScheduler()
method. For details, see the following file: Startup.cs.
In a Windows Forms application, specifyCustomFieldMappings
. For details, see the following file: SchedulerCustomFieldMappingsController.cs. - Display field values in event cards of a Scheduler List View.
In an XAF ASP.NET Core Blazor application:- Create a Razor component. For implementation details, refer to the following file: CustomAppointmentTemplate.razor.
- Specify
VerticalAppointmentTemplate
andHorizontalAppointmentTemplate
properties of the Scheduler View. For implementation details, refer to the following file: SchedulerCustomFieldMappingsController.cs.
In an XAF Windows Forms application: - Handle the
InitAppointmentDisplayText
event. For implementation details, refer to the following file: SchedulerCustomFieldMappingsController.cs.
Files to Review
- ExtendedEvent.cs
- CustomAppointmentTemplate.razor (ASP.NET Core Blazor)
- SchedulerCustomFieldMappingsController.cs (ASP.NET Core Blazor)
- SchedulerCustomFieldMappingsController.cs (Windows Forms)
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ExtendedEvents.Module.BusinessObjects;
[DefaultClassOptions]
public class ExtendedEvent : Event {
public virtual string CustomSimpleTypeField { get; set; }
public virtual CustomReferenceTypeField CustomReferenceTypeField { get; set; }
}
public class CustomReferenceTypeField : BaseObject {
public virtual string Name { get; set; }
}
C#using DevExpress.ExpressApp.Security;
using DevExpress.ExpressApp.ApplicationBuilder;
using DevExpress.ExpressApp.Blazor.ApplicationBuilder;
using DevExpress.ExpressApp.Blazor.Services;
using DevExpress.Persistent.Base;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.EntityFrameworkCore;
using ExtendedEvents.Blazor.Server.Services;
using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
using DevExpress.Blazor;
using ExtendedEvents.Module.BusinessObjects;
namespace ExtendedEvents.Blazor.Server;
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton(typeof(Microsoft.AspNetCore.SignalR.HubConnectionHandler<>), typeof(ProxyHubConnectionHandler<>));
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpContextAccessor();
services.AddScoped<CircuitHandler, CircuitHandlerProxy>();
services.AddXaf(Configuration, builder => {
builder.UseApplication<ExtendedEventsBlazorApplication>();
builder.Modules
.AddConditionalAppearance()
.AddScheduler(options => {
options.Events.OnSchedulerDataStorageCreated += context => {
context.SchedulerDataStorage.AppointmentMappings.CustomFieldMappings = new[] {
new DxSchedulerCustomFieldMapping {
Name = "SimpleField",
Mapping = nameof(ExtendedEvent.CustomSimpleTypeField)
},
new DxSchedulerCustomFieldMapping {
Name = "ReferenceField",
Mapping = $"{nameof(ExtendedEvent.CustomReferenceTypeField)}.{nameof(CustomReferenceTypeField.Name)}"
}
};
};
})
.AddValidation(options => {
options.AllowValidationDetailsAccess = false;
})
.Add<ExtendedEvents.Module.ExtendedEventsModule>()
.Add<ExtendedEventsBlazorModule>();
builder.ObjectSpaceProviders
.AddSecuredEFCore(options => options.PreFetchReferenceProperties())
.WithDbContext<ExtendedEvents.Module.BusinessObjects.ExtendedEventsEFCoreDbContext>((serviceProvider, options) => {
// Uncomment this code to use an in-memory database. This database is recreated each time the server starts. With the in-memory database, you don't need to make a migration when the data model is changed.
// Do not use this code in production environment to avoid data loss.
// We recommend that you refer to the following help topic before you use an in-memory database: https://docs.microsoft.com/en-us/ef/core/testing/in-memory
//options.UseInMemoryDatabase("InMemory");
string connectionString = null;
if(Configuration.GetConnectionString("ConnectionString") != null) {
connectionString = Configuration.GetConnectionString("ConnectionString");
}
#if EASYTEST
if(Configuration.GetConnectionString("EasyTestConnectionString") != null) {
connectionString = Configuration.GetConnectionString("EasyTestConnectionString");
}
#endif
ArgumentNullException.ThrowIfNull(connectionString);
options.UseSqlServer(connectionString);
options.UseChangeTrackingProxies();
options.UseObjectSpaceLinkProxies();
options.UseLazyLoadingProxies();
})
.AddNonPersistent();
builder.Security
.UseIntegratedMode(options => {
options.RoleType = typeof(PermissionPolicyRole);
// ApplicationUser descends from PermissionPolicyUser and supports the OAuth authentication. For more information, refer to the following topic: https://docs.devexpress.com/eXpressAppFramework/402197
// If your application uses PermissionPolicyUser or a custom user type, set the UserType property as follows:
options.UserType = typeof(ExtendedEvents.Module.BusinessObjects.ApplicationUser);
// ApplicationUserLoginInfo is only necessary for applications that use the ApplicationUser user type.
// If you use PermissionPolicyUser or a custom user type, comment out the following line:
options.UserLoginInfoType = typeof(ExtendedEvents.Module.BusinessObjects.ApplicationUserLoginInfo);
options.Events.OnSecurityStrategyCreated += securityStrategy => {
// Use the 'PermissionsReloadMode.NoCache' option to load the most recent permissions from the database once
// for every DbContext instance when secured data is accessed through this instance for the first time.
// Use the 'PermissionsReloadMode.CacheOnFirstAccess' option to reduce the number of database queries.
// In this case, permission requests are loaded and cached when secured data is accessed for the first time
// and used until the current user logs out.
// See the following article for more details: https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.Security.SecurityStrategy.PermissionsReloadMode.
((SecurityStrategy)securityStrategy).PermissionsReloadMode = PermissionsReloadMode.NoCache;
};
})
.AddPasswordAuthentication(options => {
options.IsSupportChangePassword = true;
});
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.LoginPath = "/LoginPage";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if(env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
else {
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. To change this for production scenarios, see: https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRequestLocalization();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseXaf();
app.UseEndpoints(endpoints => {
endpoints.MapXafEndpoints();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapControllers();
});
}
}
C#using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Scheduler.Win;
using DevExpress.XtraScheduler;
using ExtendedEvents.Module.BusinessObjects;
namespace ExtendedEvents.Win.Controllers;
public class SchedulerCustomFieldMappingsController : ObjectViewController<ListView, ExtendedEvent> {
private SchedulerListEditor listEditor;
protected override void OnViewControlsCreated() {
base.OnViewControlsCreated();
if (View.Editor is SchedulerListEditor listEditor) {
listEditor.SchedulerControl.Storage.Appointments.CustomFieldMappings.AddRange(new[] {
new AppointmentCustomFieldMapping("SimpleField", nameof(ExtendedEvent.CustomSimpleTypeField)),
new AppointmentCustomFieldMapping("ReferenceField", nameof(ExtendedEvent.CustomReferenceTypeField))
});
SchedulerControl scheduler = listEditor.SchedulerControl;
scheduler.InitAppointmentDisplayText -= scheduler_InitAppointmentDisplayText;
scheduler.InitAppointmentDisplayText += scheduler_InitAppointmentDisplayText;
}
}
protected override void OnDeactivated() {
base.OnDeactivated();
if (listEditor != null && listEditor.SchedulerControl != null) {
SchedulerControl scheduler = listEditor.SchedulerControl;
scheduler.InitAppointmentDisplayText -= scheduler_InitAppointmentDisplayText;
}
}
private void scheduler_InitAppointmentDisplayText(object sender, AppointmentDisplayTextEventArgs e) {
Appointment appointment = e.Appointment;
if (appointment.IsRecurring)
appointment = e.Appointment.RecurrencePattern;
// Obtain source object if needed
//ExtendedEvent obj = (ExtendedEvent)listEditor.SourceObjectHelper.GetSourceObject(appointment);
var referencePropertyValue = ((CustomReferenceTypeField)appointment.CustomFields["ReferenceField"])?.Name;
e.Text = $"{appointment.CustomFields["SimpleField"]} - {referencePropertyValue}";
}
}
Razor<div class="card shadow-sm bg-white p-2" style="overflow: hidden; height: 100%;box-shadow: .125rem .25rem rgba(34,34,34,0.15)">
<div><span class="badge mb-1 @Context.Status.CssClass" style="white-space: pre-wrap;"></span></div>
<span class="text-dark ps-0 mb-1">@Context.Appointment.CustomFields["SimpleField"]</span>
<span class="text-dark ps-0 mb-1">@Context.Appointment.CustomFields["ReferenceField"]</span>
@*obtain source object if needed @Context.Appointment.SourceObject*@
</div>
@code {
public static RenderFragment<DxSchedulerAppointmentView> Create() {
return (DxSchedulerAppointmentView schedulerAppointmentView) =>
@<CustomAppointmentTemplate Context="@schedulerAppointmentView" />;
}
[Parameter]
public DxSchedulerAppointmentView Context { get; set; }
}
C#using DevExpress.Blazor;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Scheduler.Blazor.Editors;
using ExtendedEvents.Module.BusinessObjects;
namespace ExtendedEvents.Blazor.Server.Controllers;
public class SchedulerCustomFieldMappingsController : ObjectViewController<ListView, ExtendedEvent> {
protected override void OnViewControlsCreated() {
base.OnViewControlsCreated();
var schedulerAdapter = ((SchedulerListEditor)View.Editor).GetSchedulerAdapter();
schedulerAdapter.DayViewModel.VerticalAppointmentTemplate = CustomAppointmentTemplate.Create();
schedulerAdapter.DayViewModel.HorizontalAppointmentTemplate = CustomAppointmentTemplate.Create();
}
}