When we organize large events such as conferences or hackathons, we often need to manage speaker data, sessions or presentations within the event, sponsors, or general event data. For this purpose, we may create an administrative web portal that allows us to handle all this data.
With these considerations in mind, in this article, we'll learn in a practical way how to design a dashboard to handle data for an in-person or virtual event. This portal can help us a lot in terms of time to manage the information in a database and subsequently manage all this data in another web portal for the users/participants of the event (this second portal we will analyze in a second article).
The web portal for managing events will be done ASP.NET 5 with the MVVM pattern (Model, View, Model View) with DotVVM. Where,
- The model. - will be responsible for all application data and related business logic.
- View. - shall correspond to representations for the end-user of the application model. The view will be responsible for displaying the data to the user and for allowing manipulation of application data.
- Model-View or View-Model. - one or more per view; the view-model will be responsible for implementing view behavior to respond to user actions and exposing model data easily.
The final result of the web portal will be as follows,
Note
The source code for this project can be found in the following GitHub repository: Event Admin.
Basic functionalities of the administrative web portal
For the management of event data, the base options and functionality that are considered within the dashboard are described below,
- Organizers, which indicates the name of the communities and/or institutions that are organizing the event. Here you can put the social networks of the organizing group and also a description.
- Speakers, to have a list of those people who will present a session within the event.
- Sessions, where the event presentations are recorded, with their respective associated speakers. Here you can also specify the type of the session (e.g. Keynote, workshop, talk, etc.), the start date, the end date, a description, and the level of difficulty of the session (basic, intermediate, advanced).
- Sponsors, since most events can be held thanks to their sponsors, each collaborator can be registered here by specifying the company name, website, logo, and description.
Now that we are aware of the options that will be included, let's start building our web portal.
Resources and tools needed
As initially mentioned, for the construction of the portal we will use ASP.NET 5 & DotVVM. In this sense, the resources and tools needed to establish our work environment are as follows,
The database schema
Considering the base functionalities of the administrative web portal, the dashboard has been considered a SQL database, with entities: Organizer, Sponsor, Event, Speaker, Session, Speaker_has_Session, SessionLevel, and SessionType.
For this case, the database manager to be used is SQL Server, so we could initially build the dashboard on-premises, or consider some cloud resource such as Azure SQL Database, to work with the database.
The SQL script for this database is as follows,
-- -----------------------------------------------------
CREATE TABLE Event (
IdEvent INT NOT NULL IDENTITY(1,1),
Name VARCHAR(45) NOT NULL,
Description VARCHAR(500) NULL,
Icon VARCHAR(45) NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL,
RegistrationLink VARCHAR(45) NULL,
StreamingLink VARCHAR(45) NULL,
PRIMARY KEY (IdEvent))
;
-- -----------------------------------------------------
CREATE TABLE Speaker (
IdSpeaker INT NOT NULL IDENTITY(1,1),
FirstName VARCHAR(45) NOT NULL,
SecondName VARCHAR(45) NULL,
FirstLastName VARCHAR(45) NOT NULL,
SecondLastName VARCHAR(45) NULL,
PhotoLink VARCHAR(45) NULL,
TwitterLink VARCHAR(45) NULL,
LinkedInLink VARCHAR(45) NULL,
IdEvent INT NOT NULL,
PRIMARY KEY (IdSpeaker),
INDEX fk_Speaker_Event_idx (IdEvent ASC),
CONSTRAINT fk_Speaker_Event
FOREIGN KEY (IdEvent)
REFERENCES Event (IdEvent)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
;
-- -----------------------------------------------------
CREATE TABLE SessionType (
IdSessionType INT NOT NULL IDENTITY(1,1),
Name VARCHAR(45) NOT NULL,
Description VARCHAR(45) NULL,
PRIMARY KEY (IdSessionType))
;
-- -----------------------------------------------------
CREATE TABLE SessionLevel (
IdSessionLevel INT NOT NULL IDENTITY(1,1),
Name VARCHAR(45) NULL,
Description VARCHAR(45) NULL,
PRIMARY KEY (IdSessionLevel))
;
-- -----------------------------------------------------
CREATE TABLE Session (
IdSession INT NOT NULL IDENTITY(1,1),
Name VARCHAR(45) NOT NULL,
Description VARCHAR(500) NULL,
StartDate DATETIME NULL,
EndDate DATETIME NULL,
IconLink VARCHAR(45) NULL,
IdSessionType INT NOT NULL,
IdSessionLevel INT NOT NULL,
PRIMARY KEY (IdSession),
INDEX fk_Session_SessionType1_idx (IdSessionType ASC),
INDEX fk_Session_SessionLevel1_idx (IdSessionLevel ASC),
CONSTRAINT fk_Session_SessionType1
FOREIGN KEY (IdSessionType)
REFERENCES SessionType (IdSessionType)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_Session_SessionLevel1
FOREIGN KEY (IdSessionLevel)
REFERENCES SessionLevel (IdSessionLevel)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
;
-- -----------------------------------------------------
CREATE TABLE Speaker_has_Session (
IdSpeaker INT NOT NULL,
IdSession INT NOT NULL,
INDEX fk_Speaker_has_Session_Session1_idx (IdSession ASC),
INDEX fk_Speaker_has_Session_Speaker1_idx (IdSpeaker ASC),
CONSTRAINT fk_Speaker_has_Session_Speaker1
FOREIGN KEY (IdSpeaker)
REFERENCES Speaker (IdSpeaker)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT fk_Speaker_has_Session_Session1
FOREIGN KEY (IdSession)
REFERENCES Session (IdSession)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
;
-- -----------------------------------------------------
CREATE TABLE Organizer (
IdOrganizer INT NOT NULL IDENTITY(1,1),
Name VARCHAR(45) NOT NULL,
LogoLink VARCHAR(45) NULL,
WebPage VARCHAR(45) NULL,
Email VARCHAR(45) NULL,
Description VARCHAR(45) NULL,
FacebookLink VARCHAR(45) NULL,
TwitterLink VARCHAR(45) NULL,
InstagramLink VARCHAR(45) NULL,
IdEvent INT NOT NULL,
PRIMARY KEY (IdOrganizer),
INDEX fk_Organizer_Event1_idx (IdEvent ASC),
CONSTRAINT fk_Organizer_Event1
FOREIGN KEY (IdEvent)
REFERENCES Event (IdEvent)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
;
-- -----------------------------------------------------
CREATE TABLE Sponsor (
IdSponsor INT NOT NULL IDENTITY(1,1),
Name VARCHAR(45) NOT NULL,
LogoLink VARCHAR(45) NULL,
Description VARCHAR(45) NULL,
WebPage VARCHAR(45) NULL,
IdEvent INT NOT NULL,
PRIMARY KEY (IdSponsor),
INDEX fk_Sponsor_Event1_idx (IdEvent ASC),
CONSTRAINT fk_Sponsor_Event1
FOREIGN KEY (IdEvent)
REFERENCES Event (IdEvent)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
;
Project solution environment
Within the solution in Visual Studio 2019, we'll have three projects,
- Data Access Layer (DAL) - a class library to handle connection and access to the database.
- BL (Business Layer) - another class library for service management and application domain logic.
- PL - Presentation Layer. This section is where we have Views and Viewmodels for designing web pages with DotVVM.
All right, with this in mind, now let's look at the steps we need to take in these three projects for the development of our web portal.
DAL - Data Access Layer
This first project corresponds to a library of classes. In this sense, as a first point, we will relate our project to the database in SQL Server. To do this, we can use Entity Framework, an object-relational mapping framework (ORM) designed to create data access applications by programming in a conceptual application model, rather than directly programming a relational storage schema.
In the Entity Framework, there are two approaches, the first Code-First, which allows us to generate the database through classes, and the second, Database-First, which allows us to generate feature classes from an existing database. As expected, in this case, we will use the Database-First approach. To meet this goal, you will need to install three Nuget packages,
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.EntityFrameworkCore.SqlServer
Then we will need to insert a command from the package manager console. This console can be activated from the Options Menu -> View -> Other Windows -> the Package Management Console.
In this console we will insert the following command,
Scaffold-DbContext "Server=YOUR_SERVER; Database=DATABASE_NAME; Username=YOUR_USERNAME; Password=YOUR_PASSWORD Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir DAL/Entities
When we enter the command on this console, we'll have something like this,
With these steps, we already have ready the connection and configurations needed to work with the SQL Server database in ASP.NET with the help of Entity Framework.
BL - Business Logic Layer
Now it's up to us to define the models and create the services to handle the logic of our application. In this case, we will create a second class library to have a general list of entities (Event, Organizer, Session, Speaker, Sponsor) and the specific information of each of them.
In the solution within Visual Studio 2019 to the final we will have something like this,
Here are two important things to mention. As a first point, the EventId class can handle the ID of the event we want to work with. In this case, the database is designed so that we can work with multiple events at once, so in this section, we can specify the ID of the event we want to reference (in the future we could handle this identifier at the web interface level).
public class EventId
{
private static EventId instance { get; set; } = null;
public int Id { get; set; } = 1;
private EventId() { }
public static EventId GetInstance()
{
if (instance == null) {
instance = new EventId();
}
return instance;
}
}
A second aspect to consider is that most services implement CRUD operations (Create, Read, Update, Delete) to handle the data for each case. For example, for Organizers, the methods are as follows,
- GetAllOrganizersAsync().
- GetOrganizerByIdAsync(int IdOrganizer).
- InsertOrganizerAsync(OrganizerDetailModel Organizer).
- UpdateOrganizerAsync(OrganizerDetailModel Organizer).
- DeleteOrganizerAsync(int IdOrganizer).
With this mentioned, now let's look at the design of our dashboard with DotVVM.
PL - Presentation layer
For the design of the web portal, this is where DotVVM comes into action. Each page in DotVVM consists of two files,
- A View, which is based on HTML syntax and describes what the page will look like.
- A ViewModel, which is a class in CTM that describes the state of the page (for example, values in form fields) and handles user interactions (for example, button clicks).
For our case, we will have four Views and four main ViewModels for the sections in the dashboard,
- Default: it will be the main page, in this case only the options menu will be displayed.
- Create: A page made up of a form to create a new record.
- Detail: to view a record in detail.
- Edit: To modify or delete record information.
- List: to display the list of records for a particular case.
Considering the Views and Viewmodels files, in Visual Studio 2019 we'll see something like this for our entities,
Next, let's look at some of the main components of this administrative portal.
A. Masterpage
In DotVVM, master pages or master pages are known as Masterpage, whose files have a .dotmaster extension. In this case, this page will be useful to set our HTML skeleton, import our CSS & JavaScript files, and define the contents that will be visible on all child pages (lists, forms, records).
The header of our HTML will look like this,
<head>
<meta charset="utf-8" />
<title>Event Admin</title>
<meta content='width=device-width, initial-scale=1.0, shrink-to-fit=no' name='viewport' />
<!-- Fonts and icons -->
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Slab:400,700|Material+Icons" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
<!-- CSS Files -->
<dot:RequiredResource Name="Styles" />
</head>
A particular tag is the one found under the statement <dot:RequiredResource NameTM"Styles" />. In DotVVM, within the configuration class called DotvvmStartUp, under the ConfigureResources method, we can specify the location of the CSS files in our administrative portal so that they are organized in this section,
private void ConfigureResources(DotvvmConfiguration config, string applicationPath)
{
config.Resources.Register("Styles", new StylesheetResource()
{
Location = new UrlResourceLocation("~/assets/css/material-dashboard.css?v=2.1.2")
});
}
The MasterPage with its HTML body will be displayed as follows,
Where,
- Section 1, is the HTML header where you will find the page title, CSS fonts and referenced JavaScript files, and other specifications.
- Section 2, corresponds to the logo of the page, in other words, the title within it.
- Section 3, is the portal options menu.
- Section 4, is the section where all the contents of the daughter pages (list of speakers, sponsors, sessions, organizers; and the creation and modification forms for each of these) will be displayed.
In the portal, visually the results according to the numbering of the previously specified sections are as follows,
For the analysis of the child pages, let's look at two example cases, the list of organizers and a registration form for those organizers.
B. List - Organizers
Up to this point, we have been able to analyze the established structure that our portal will have, now let's look at one of the child pages or also called content pages.
The purpose of this first page aims to visualize through a table the organizers that are registered in the database. In this sense, in the first part of this page we find the ViewModel,
public class ListViewModel : MasterPageViewModel
{
public string Title { get; set; }
public string Subtitle { get; set; }
private readonly OrganizerService OrganizerService;
[Bind(Direction.ServerToClient)]
public List<OrganizerListModel> Organizers { get; set; }
public int ContUsers { get; set; }
public ListViewModel(OrganizerService OrganizerService)
{
Title = "Organizer";
Subtitle = "In this section you can see the list of organizers registered in the database.";
this.OrganizerService = OrganizerService;
}
public override async Task PreRender()
{
Organizers = await OrganizerService.GetAllOrganizersAsync();
await base.PreRender();
}
}
In this ViewModel class, we define the title and subtitle that the child page will have. We will also have an instance of OrganizerService, which will allow us to access the methods to retrieve a list of registered organizers from the database through the Organizer service (implemented in the BL).
In this same class, we can locate the definition List<OrganizerListModel> Organizers of type OrganizerListModel (defined in the classes of the models in the BL), which will have the list of organizers (IdOrganizer, and Name) to load them into a table on the main page of the organizer's section. Other interesting statement is [Bind(Direction.ServerToClient)]. These types of properties allow you to specify what information will be transferred from the server to the client or from the client to the server when using Binding Directions. Considering the list of organizers, in many cases, it is not necessary to transfer the entire view model in both directions. The server in view will suffice in this case.
Finally, in the Organizer's List ViewModel, we have the PreRender() method, which allows us to perform certain types of operations that will be performed at the time of loading the page. In this case, the database will be queried through the call of one of the service methods, in this case, OrganizerService.GetAllOrganizersAsync() to retrieve the list of organizers in an Organizers collection of type OrganizerListModel.
The second part corresponds to the View on this page.
In the first part, we find the statement <dot:Content ContentPlaceHolderIDTM"MainContent">, which specifies the id of this content page, which is being referenced from the MasterPage in turn.
In the second part, we have a navbar where the title and subtitle of this home page are displayed, and in turn, a button that will redirect you to a form to create a new organizer,
Finally, in a third section, we have a counter of the number of registered organizers and the table where these records are listed.
Only one counter in the Organizers collection set in the ViewModel is displayed in the counter section,
<p class="card-category"><b>Number of registered organizers</b></p>
<h2 class="card-title">
{{value: Organizers.Count}}
</h2>
The organizer table can be displayed as follows,
This table is designed through a GridView: <dot:GridView ... >, a DotVVM control that allows us to create a table or crew to display a certain list of information. In HTML we would be talking about the <table> tag. One of its attributes is the DataSource: DataSource'"-value: Organizers-", which allows you to specify the data source, in this case, we refer to the list of organizers: Organizers, which was defined in the ViewModel as we saw earlier.
<dot:GridView DataSource="{value: Organizers}" class="table">
<Columns>
<dot:GridViewTextColumn ValueBinding="{value: IdOrganizer}" HeaderText="ID" HeaderCssClass="text-primary" CssClass="text-primary" />
<dot:GridViewTextColumn ValueBinding="{value: Name}" HeaderText="Name" HeaderCssClass="text-primary" />
<dot:GridViewTemplateColumn>
<dot:RouteLink Text="" RouteName="DetailOrganizer" Param-IdOrganizer="{{value: IdOrganizer}}" class="btn btn-primary btn-link btn-sm">
<i class="material-icons">visibility</i> Detail
</dot:RouteLink>
</dot:GridViewTemplateColumn>
</Columns>
</dot:GridView>
Continuing with our analysis, in the GridView we have the columns IdOrganizer, and Name of the organizers, but additionally, we can also add columns to perform operations on some specific record. In this case, with RouteLink, we can define a hyperlink that constructs a URL from route names and parameter values to redirect us to other pages or perform additional operations, for example, see in detail the registration of a particular user according to their Id,
<dot:RouteLink RouteName="DetailOrganizer" Param-Id="{{value: IdOrganizer}}" />
These paths and their corresponding parameters must be defined in the DotvvmStartup.cs file in the ConfigureRoutes method as follows,
config.RouteTable.Add("DetailOrganizer", "DetailOrganizer/{IdOrganizer}", "Views/Organizers/Detail.dothtml");
C. Registration Form - Organizers
Within the portal, there are other options for creating, modifying, and deleting a record from a particular organizer. In this case, to generalize, let's analyze the Create Organizer page. Like the List, the Create page is one that will be the daughter of the MasterPage.
As the name implies, this page aims to create a new organizer through a form within the web portal. The result is as follows,
In this case, in the form, all components correspond to text boxes for data entry. Let's look at one of them, for example, the Name field in the View will be as follows,
<div class="col-md-6">
<div class="form-group">
<label class="">Name</label>
<dot:TextBox Text="{value: Organizer.Name}" class="form-control" />
</div>
</div>
Here we can comment on two things. The first, that for text boxes we can use a DotVVM component that allows us to associate a TextBox with an attribute in the ViewModel.
public OrganizerDetailModel Organizer { get; set; } = new OrganizerDetailModel();
We can also accompany this TextBox with a control called Validator, which allows us to validate certain characteristics, for example, that the field cannot be registered as null. In these two components, we can add additional specifications, such as the corresponding styles for each case.
The send button will follow the same logic,
<dot:Button Text="Insert Organizer" Click="{command: AddOrganizer()}" class="btn btn-primary pull-right" />
This button, set through a DotVVM component, is calling a function set in the ViewModel to add a user based on the data inserted into the form. This function is structured as follows,
public async Task AddOrganizer()
{
await OrganizerService.InsertOrganizerAsync(Organizer);
Context.RedirectToRoute("Organizer");
}
In this case, by inserting the record successfully, from the ViewModel, we can redirect to the List page (page with the list of registered organizers) and view the new organizer inserted into the database and displayed in the general list.
As mentioned earlier, the detail and editing pages follow the same design logic in this case; similarly for the Speakers, Sessions, and Sponsors sections.
Below are some additional screenshots for the Speakers, Sessions and Sponsors registration forms respectively.
What's next?
With this tutorial article, we have learned in general how to control the data of our events, whether for conferences or hackathons, for virtual meetings, or in person, through ASP.NET 5 & DotVVM.
The code in this tutorial can be found in the following repository on GitHub: Event Admin.
In the next article, we can learn how to use the same data stored in the database with SQL Server, but this time to represent it in a web portal where users or attendees can learn about sessions, speakers, and everything else. In the case of the list of organizers, in this second portal we could have something like this,
Additional resources
Today there are many applications that we can build in the .NET ecosystem, and even more specifically in the area of web page development. In this sense, below are some additional resources to further acquire knowledge in this field,
Thank you very much for reading, I hope this demo can be of use to you. If you have any questions or ideas that you need to discuss, it will be nice to be able to collaborate and together exchange knowledge with each other.
See you on Twitter! Or if you like you can also write me on Telegram. :)