10/17/2020 Admin

Blazor Simple Survey: Creating Dynamic Surveys


You can create dynamic pages that will capture input from users using controls such as Text Box, Text Area, Date, Date Time, Dropdown, and Multi-Select Dropdown. This is demonstrated in the Server Side Blazor application Simple Survey available at https://github.com/ADefWebserver/BlazorSimpleSurvey.

Walk-Thru

image

User can log into the application using any provider supported by Microsoft Azure B2C.

image

Users can take surveys created by the application administrator.

image

Survey takers and visitors can view the tabulated results in pie charts.

image

Application administrators create and edit the surveys using the Survey Administration screen.

Set-Up

image

To set-up Blazor Simple Survey, the first step is to create a database and run the .sql scripts in !SQL directory.

image

Next, edit the appsettings.json to set the database connection (the DefaultConnection in the ConnectionStrings section).

image
Blazor Simple Survey requires Azure B2C (even when running on your local development machine).

To setup Azure B2C, follow the directions at: Azure AD B2C Quickstart with Visual Studio & Blazor, and then copy the settings to the appsettings.json file (in the AzureAdB2C section).

image

To create surveys, a user account needs to be in the Blazor Simple Survey Administrators group.

This is done using the Azure B2C user administration page (See: Blazor Azure B2C User And Group Management for full directions).

Create Surveys

image

When a user in the configured Blazor Simple Survey Administrators group logs in, they will see the Survey Administration link.

Clicking on this link takes them to the page that allows them to click the button to create a new Survey.

image

A popup shows that allows them to give the Survey a name.

image

Clicking the plus button allows items to be added to the Survey.

image

A popup displays to allow items to be added or edited.

image

If the selected item type is Dropdown or Multi-Select Dropdown, the Edit Options button appears.

image

This displays a popup that allows the dropdown options to be added or edited.

image

Reorder buttons allow the position of the survey items to be adjusted.

image

The Survey page allows users to select, complete, and Submit a survey.

image

The Responses tab allows users to see the tabulated results, of all responses, for all the survey controls except the Text Area control.

Data Layer

image

The application consists of six tables.

image

EF Core Power Tools was used to create the SimpleSurveyContext Data Context.

The SimpleSurveyService uses the Data Context to connect to the database and consists of methods that allow the remaining code to perform data functions.

Creating And Editing Survey

The following button opens the EditSurvey control using the Radzen Dialog:

image

								
									<
									RadzenButton
									ButtonStyle="ButtonStyle.Success"
								
									Size="Radzen.ButtonSize.Medium"
								
									MouseEnter="@(args =>
									
								
									ShowTooltip(args, new TooltipOptions()
								
									{ Text= "New
									Survey" }))"
								
									Icon="addchart"
									Click="@(args =>
									
								
									dialogService.Open<EditSurvey>($"New
									Survey",
								
									new
									Dictionary<string,
									object>() {
								
									{
								
									"SelectedSurvey",
									new
									Survey() {
									Id
									=
									-1
									}
								
									}
								
									},
								
									new
									DialogOptions() {
									Width
									=
									"500px",
									Height
									=
									"280px"
									}))"
									/>
								

image

The following markup displays the form that allows you to create or edit a survey.

The delete button will only display if the survey is not a new survey:

								
									<
									RadzenCard
									Style="margin-bottom: 20px;"
									>
								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-12"
									>
								
									<
									div
									>Survey Name:</
									div
									>
								
									<
									RadzenTextBox
									@bind-Value="SelectedSurvey.SurveyName"
								
									Style="width: 400px"
									/>
								
									<
									br
									/>
								
									</
									div
									>
								
									</
									div
									>
								
									</
									RadzenCard
									>
								
									<
									RadzenButton
									Click="UpdateSurvey"
								
									Text="Save"
									ButtonStyle="ButtonStyle.Success"
								
									Style="margin-bottom: 10px; width: 150px"
									/>
								
@if (SelectedSurvey.Id > 0)
								
{
								
									<
									RadzenButton
									Click="DeleteSurveyDialog"
								
									ButtonStyle="ButtonStyle.Danger"
								
									Text="Delete"
								
									Style="margin-bottom: 10px; width: 150px"
									/>
								
}

When the survey is updated, the following code is used to call the Data Context:

								
									async Task UpdateSurvey()
								
									{
								
									SelectedSurvey.UserId = UserId;
								

								
									try
								
									{
								
									if
									(SelectedSurvey.Id == -1)
								
									{
								
									SelectedSurvey =
								
									await @Service.CreateSurveyAsync(SelectedSurvey);
								
									}
								
									else
								
									{
								
									SelectedSurvey =
								
									await @Service.UpdateSurveyAsync(SelectedSurvey);
								
									}
								

								
									dialogService.Close(SelectedSurvey);
								
									}
								
									catch
									(Exception ex)
								
									{
								
									strError = ex.GetBaseException().Message;
								
									}
								
									}

Survey Items

image

The following markup displays the form that allows survey items to be defined:

								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-12"
									>
								
									<
									div
									>
									<
									b
									>Name</
									b
									>
									</
									div
									>
								
									<
									RadzenTextBox
									@bind-Value="SelectedSurveyItem.ItemLabel"
								
									Style="width: 400px"
									/>
								
									<
									br
									/>
								
									</
									div
									>
								
									</
									div
									>
								
									<
									br
									/>
								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-6"
									>
								
									<
									div
									>
									<
									b
									>Type</
									b
									>
									</
									div
									>
								
									<
									RadzenDropDown
									AllowClear="true"
									TValue="string"
								
									Data="@FormTypes"
								
									@bind-Value="SelectedSurveyItem.ItemType"
									/>
								
									</
									div
									>
								
									<
									div
									class="col-md-6"
									>
								
									<
									div
									>
									<
									b
									>Required</
									b
									>
									</
									div
									>
								
									<
									RadzenCheckBox
									@bind-Value="boolRequired"
								
									Style="margin-bottom: 20px"
									TValue="bool"
									/>
								

								
									</
									div
									>
								
									</
									div
									>
								

image

If a survey administrator selects a Dropdown or a Multi-Select Dropdown a button to edit options is displayed:

								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-12"
									>
								
									@if ((SelectedSurveyItem.ItemType == "Dropdown")
								
									|| (SelectedSurveyItem.ItemType == "Multi-Select Dropdown"))
								
									{
								
									<
									RadzenButton
									Click="OpenPopup"
								
									Text="Edit Options"
								
									Size="Radzen.ButtonSize.Small"
								
									ButtonStyle="ButtonStyle.Info"
									/>
								
									}
								
									</
									div
									>
								
									</
									div
									>
								

image

This opens a Popup that displays a modal form to allow the options to be edited:

								
@if (ShowPopup)
								
{
								
									<
									div
									class="modal"
									tabindex="-1"
								
									style="display:block;background-color:gainsboro"
									role="dialog"
									>
								
									<
									div
									class="modal-dialog"
									>
								
									<
									div
									class="modal-content"
									>
								
									<
									div
									class="modal-header"
									>
								
									<
									h3
									class="modal-title"
									>Edit Options</
									h3
									>
								
									</
									div
									>
								
									<
									div
									class="modal-body"
									>
								
									<
									ul
									style="list-style-type:none;"
									>
								
									@foreach (var option in
								
									SelectedSurveyItem.SurveyItemOption.OrderBy(x => x.Id))
								
									{
								
									<
									li
									>
								
									<
									div
									Style="margin-bottom: 10px; width: 150px"
									>
								
									<
									RadzenButton
									Click="(() => RemoveOption(option))"
								
									Icon="delete"
								
									Size="Radzen.ButtonSize.Small"
								
									ButtonStyle="ButtonStyle.Danger"
									/>
								
									<
									b
									>@option.OptionLabel</
									b
									>
								
									</
									div
									>
								
									</
									li
									>
								
									}
								
									</
									ul
									>
								
									<
									RadzenTextBox
									@bind-Value="newOption"
								
									Style="width: 200px"
									/>
								
									<
									RadzenButton
									Click="AddOption"
								
									Text="Add"
								
									Size="Radzen.ButtonSize.Small"
								
									ButtonStyle="ButtonStyle.Success"
									/>
								
									<
									RadzenButton
									Click="ClosePopup"
								
									Text="Close"
								
									Size="Radzen.ButtonSize.Small"
								
									ButtonStyle="ButtonStyle.Secondary"
									/>
								
									</
									div
									>
								
									</
									div
									>
								
									</
									div
									>
								
									</
									div
									>
								
}

image

If a survey item is selected for deletion, the Radzen dialog allows the conformation to be handled in a single method:

								
									async Task DeleteSurveyItemDialog() =>
								
									await dialogService.OpenAsync("Delete Survey Item", ds =>
								
									@<
									RadzenCard
									Style="padding: 20px;"
									>
								
									<
									p
									Style="margin-bottom: 10px;"
									>Confirm?</
									p
									>
								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-12"
									>
								
									<
									RadzenButton
									Text="Yes"
									Click="DeleteSurveyItem"
								
									Style="margin-bottom: 10px; width: 150px"
									/>
								
									<
									RadzenButton
									Text="No"
									Click="()=> ds.Close(false)"
								
									ButtonStyle="ButtonStyle.Secondary"
								
									Style="margin-bottom: 10px; width: 150px"
									/>
								
									</
									div
									>
								
									</
									div
									>
								
									</
									RadzenCard
									>);

Display Survey

image

When displaying a survey, a class is used (DTOSurvey) that contains fields to display the survey as well as fields that can be bound to controls to capture the responses to the survey items.

image

The following markup displays a dropdown to allow the visitor to select a survey:

								
									<
									RadzenDropDown
									TValue="DTOSurvey"
								
									Data="@colSurveys"
								
									TextProperty="SurveyName"
								
									@bind-Value="SelectedSurvey"
								
									Change="@(args => SelectedSurveyChange(args))"
									/>
								

This calls the following method that loads the selected survey, and then also calls the LoadSurveyResultsData method, to load the tabulated results for the selected survey.

Before it does, it creates default Radzen.LoadDataArgs that provide data filtering:

								
									async Task SelectedSurveyChange(object
									value)
								
									{
								
									ShowSurveyComplete =
									false;
								
									colSurveys =
									new
									List<DTOSurvey>();
								
									await RefreshSurveys(SelectedSurvey.Id);
								

								
									// Refresh results
								
									LoadDataArgs args =
									new
									LoadDataArgs();
								
									args.Filter =
									null;
								
									args.OrderBy =
									null;
								
									args.Skip = 0;
								
									args.Top = 1;
								

								
									await LoadSurveyResultsData(args);
								
									}

The LoadSurveyResultsData method uses the Radzen.LoadDataArgs to filter and sort the data.

The reason this is constructed this way, is that in the remaining code, the RadzenDataList, that is used to allow the visitor to page through the pie charts, is also bound to this method, and it will construct and pass Radzen.LoadDataArgs to this method.

								
									// Get Survey Items
								
									// Don't include "Text Area"
								

								
									var query = dbContext.SurveyItem
								
									.Where(x => x.Survey == SelectedSurvey.Id)
								
									.Where(x => x.ItemType != "Text Area")
								
									.Include(x => x.SurveyItemOption)
								
									.OrderBy(x => x.Position).AsQueryable();
								

								
									if
									(!string.IsNullOrEmpty(args.Filter))
								
									{
								
									query = query.Where(args.Filter);
								
									}
								

								
									if
									(!string.IsNullOrEmpty(args.OrderBy))
								
									{
								
									query = query.OrderBy(args.OrderBy);
								
									}
								

								
									var Results = query.Skip(args.Skip.Value)
								
									.Take(args.Top.Value).ToList();

The survey items are iterated and added to the collection in the DTOSurvey object.

The tabulated results, used to display in the pie charts, is constructed using the following code:

								
									ColAnswerResponse = dbContext.SurveyAnswer
								
									.Where(x => x.SurveyItemId == SurveyItem.Id)
								
									.GroupBy(n => n.AnswerValue)
								
									.Select(n =>
									new
									AnswerResponse
								
									{
								
									OptionLabel = n.Key,
								
									Responses = n.Count()
								
									}
								
									).OrderBy(n => n.OptionLabel).ToList();
								
									}
								

								
									if(ColAnswerResponse.Count > 10)
								
									{
								
									// Only take top 10
									
								
									NewDTOSurveyItem.AnswerResponses = ColAnswerResponse
								
									.OrderByDescending(x => x.Responses)
								
									.Take(10).ToList();
								

								
									// Put remaining items in "Other"
								
									var ColOtherItems =
								
									ColAnswerResponse.OrderByDescending(x => x.Responses)
								
									.Skip(10)
								
									.ToList();
								

								
									var SumOfOther =
								
									ColOtherItems.Sum(x => x.Responses);
								

								
									NewDTOSurveyItem.AnswerResponses.Add(new
									AnswerResponse()
								
									{
								
									OptionLabel = "Other", Responses = SumOfOther
								
									});
								
									}
								
									else
								
									{
								
									NewDTOSurveyItem.AnswerResponses = ColAnswerResponse;
								
									}
								

								
									SurveyResultsCollection.Add(NewDTOSurveyItem);

image

This calls the following method when the survey is submitted:

								

								
									async Task SaveSurvey()
								
									{
								
									try
								
									{
								
									var result =
								
									await @Service.CreateSurveyAnswersAsync(SelectedSurvey);
								

								
									CompleteSurvey();
								
									}
								
									catch
									(Exception ex)
								
									{
								
									strError = ex.GetBaseException().Message;
								
									}
								
									}

That calls the following method to save the submission to the database:

								
									public
									Task<bool> CreateSurveyAnswersAsync(DTOSurvey paramDTOSurvey)
								
{
								
									try
								
									{
								
									List<SurveyAnswer> SurveyAnswers =
									new
									List<SurveyAnswer>();
								

								
									foreach
									(var SurveyItem
									in
									paramDTOSurvey.SurveyItem)
								
									{
								
									// Delete possible existing answer
								
									var ExistingAnswers = _context.SurveyAnswer
								
									.Where(x => x.SurveyItemId == SurveyItem.Id)
								
									.Where(x => x.UserId == paramDTOSurvey.UserId)
								
									.ToList();
								

								
									if(ExistingAnswers !=
									null)
								
									{
								
									_context.SurveyAnswer.RemoveRange(ExistingAnswers);
								
									_context.SaveChanges();
								
									}
								

								
									// Save Answer
								

								
									if
									(SurveyItem.ItemType != "Multi-Select Dropdown")
								
									{
								
									SurveyAnswer NewSurveyAnswer =
									new
									SurveyAnswer();
								

								
									NewSurveyAnswer.AnswerValue =
								
									SurveyItem.AnswerValueString;
								

								
									NewSurveyAnswer.AnswerValueDateTime =
								
									SurveyItem.AnswerValueDateTime;
								

								
									NewSurveyAnswer.SurveyItemId =
								
									SurveyItem.Id;
								

								
									NewSurveyAnswer.UserId =
								
									paramDTOSurvey.UserId;
								

								
									_context.SurveyAnswer.Add(NewSurveyAnswer);
								
									_context.SaveChanges();
								
									}
								

								
									if
									(SurveyItem.AnswerValueList !=
									null)
								
									{
								
									foreach
									(var item
									in
									SurveyItem.AnswerValueList)
								
									{
								
									SurveyAnswer NewSurveyAnswerValueList =
								
									new
									SurveyAnswer();
								

								
									NewSurveyAnswerValueList.AnswerValue =
								
									item;
								

								
									NewSurveyAnswerValueList.SurveyItemId =
								
									SurveyItem.Id;
								

								
									NewSurveyAnswerValueList.UserId =
								
									paramDTOSurvey.UserId;
								

								
									_context.SurveyAnswer
								
									.Add(NewSurveyAnswerValueList);
								

								
									_context.SaveChanges();
								
									}
								
									}
								
									}
								

								
									return
									Task.FromResult(true);
								
									}
								
									catch
								
									{
								
									DetachAllEntities();
								
									throw;
								
									}
								
}

image

The markup to display the survey is as follows:

								
									<
									RadzenTemplateForm
									TItem="DTOSurvey"
									Data="@SelectedSurvey"
									Submit="@SaveSurvey"
									>
								
									<
									div
									>
								
									@foreach (var SurveyItem in SelectedSurvey.SurveyItem.OrderBy(x => x.Position))
								
									{
								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-9"
									style="text-align: left;margin-bottom: 20px"
									>
								
									<
									b
									>@SurveyItem.ItemLabel</
									b
									>
								
									<
									br
									/>
								
									@if (SurveyItem.ItemType == "Text Box")
								
									{
								
									<
									RadzenTextBox
									MaxLength="4000"
									Name="@SurveyItem.Id.ToString()"
								
									@bind-Value="@SurveyItem.AnswerValueString"
									/>
								
									}
								
									@if (SurveyItem.ItemType == "Text Area")
								
									{
								
									<
									RadzenTextArea
									MaxLength="4000"
									Name="@SurveyItem.Id.ToString()"
								
									@bind-Value="@SurveyItem.AnswerValueString"
									/>
								
									}
								
									@if (SurveyItem.ItemType == "Date")
								
									{
								
									<
									RadzenDatePicker
									DateFormat="d"
									Name="@SurveyItem.Id.ToString()"
								
									@bind-Value="@SurveyItem.AnswerValueDateTime"
								
									TValue="DateTime?"
									ShowTime="false"
									/>
								
									}
								
									@if (SurveyItem.ItemType == "Date Time")
								
									{
								
									<
									RadzenDatePicker
									TValue="DateTime?"
									Name="@SurveyItem.Id.ToString()"
								
									@bind-Value="@SurveyItem.AnswerValueDateTime"
								
									ShowTime="true"
									/>
								
									}
								
									@if (SurveyItem.ItemType == "Dropdown")
								
									{
								
									<
									RadzenDropDown
									AllowClear="true"
									TValue="string"
								
									Name="@SurveyItem.Id.ToString()"
								
									Data="@SurveyItem.SurveyItemOption.OrderBy(x => x.Id)"
								
									TextProperty="OptionLabel"
									ValueProperty="OptionLabel"
								
									@bind-Value="@SurveyItem.AnswerValueString"
								
									Style="width:300px;"
									/>
								
									}
								
									@if (SurveyItem.ItemType == "Multi-Select Dropdown")
								
									{
								
									<
									RadzenDropDown
									Name="@SurveyItem.Id.ToString()"
								
									TValue="IEnumerable<string>"
								
									Multiple="true"
								
									AllowClear="true"
								
									AllowFiltering="true"
								
									@bind-Value="SurveyItem.AnswerValueList"
								
									Placeholder="Select..."
								
									Data="@SurveyItem.SurveyItemOption"
								
									TextProperty="OptionLabel"
								
									ValueProperty="OptionLabel"
								
									Style="width:300px;"
									/>
								
									}
								
									@if (SurveyItem.Required == 1)
								
									{
								
									<
									br
									/>
									<
									RadzenRequiredValidator
									Component="@SurveyItem.Id.ToString()"
								
									Text="Required"
									Popup="false"
								
									Style="position: absolute"
									/>
								
									}
								
									</
									div
									>
								
									</
									div
									>
								
									}
								
									</
									div
									>
								
									@if (isAuthenticated)
								
									{
								
									<
									div
									class="row"
									>
								
									<
									RadzenButton
									ButtonType="ButtonType.Submit"
									Text="Submit"
									>
									</
									RadzenButton
									>
								
									</
									div
									>
								
									}
								
									else
								
									{
								
									<
									h4
									>You must
									<
									a
									href="AzureADB2C/Account/SignIn"
									>Log in</
									a
									>
									to take a Survey</
									h4
									>
								
									}
								
									</
									RadzenTemplateForm
									>
								

image

The markup used to display the survey results is as follows:

								
									<
									RadzenDataList
									Count="@SurveyResultsCount"
								
									Data="@SurveyResults"
								
									LoadData="@LoadSurveyResultsData"
								
									PageSize="1"
									WrapItems="true"
									AllowPaging="true"
								
									TItem="DTOSurveyItem"
									>
								
									<
									Template
									Context="item"
									>
								
									<
									RadzenCard
									Style="height:300px"
									>
								
									<
									div
									class="row"
									>
								
									<
									div
									class="col-md-12"
									>
								
									<
									RadzenChart
									>
								
									<
									RadzenDonutSeries
									Data="@item.AnswerResponses"
								
									CategoryProperty="OptionLabel"
								
									ValueProperty="Responses"
									>
								
									<
									TitleTemplate
									>
								
									<
									div
									class="rz-donut-content"
									>
								
									<
									div
									>@item.ItemLabel</
									div
									>
								
									</
									div
									>
								
									</
									TitleTemplate
									>
								
									</
									RadzenDonutSeries
									>
								
									</
									RadzenChart
									>
								
									</
									div
									>
								
									</
									div
									>
								
									</
									RadzenCard
									>
								
									</
									Template
									>
								
									</
									RadzenDataList
									>
								

Links

Blazor Simple Survey (GitHub)

Blazor Multi-Tenant Azure B2C

Blazor Azure B2C User And Group Management

Free Blazor Components | 50+ controls by Radzen

An error has occurred. This application may no longer respond until reloaded. Reload 🗙