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
User can log into the application using any provider supported by Microsoft Azure B2C.
Users can take surveys created by the application administrator.
Survey takers and visitors can view the tabulated results in pie charts.
Application administrators create and edit the surveys using the Survey Administration screen.
Set-Up
To set-up Blazor Simple Survey, the first step is to create a database and run the .sql scripts in !SQL directory.
Next, edit the appsettings.json to set the database connection (the DefaultConnection in the ConnectionStrings section).
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).
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
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.
A popup shows that allows them to give the Survey a name.
Clicking the plus button allows items to be added to the Survey.
A popup displays to allow items to be added or edited.
If the selected item type is Dropdown or Multi-Select Dropdown, the Edit Options button appears.
This displays a popup that allows the dropdown options to be added or edited.
Reorder buttons allow the position of the survey items to be adjusted.
The Survey page allows users to select, complete, and Submit a survey.
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
The application consists of six tables.
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:
<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" }))" />
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
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>
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>
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 inSelectedSurveyItem.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>}
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
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.
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);
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;
}}
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>
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 Azure B2C User And Group Management