带有参数和 OnInitialisedAsync 的服务器端 Blazor 页面问题
Server-side Blazor page issues with parameters and OnInitialisedAsync
我对 Blazor 比较陌生,我遇到了一些我认为相当简单的问题 - 将整数作为路由参数传递。作为参考,传递参数本身非常有效(即它出现在地址栏中)。
但是,问题是当我尝试在新页面上使用该参数时 - 想法是父页面是一个充满各种操作项目的手风琴,如果您单击项目上的编辑操作,您可以在新标签页中对其进行编辑。
如果我这样声明页面:
@page "/news/edit/{newsID}"
我收到了从字符串转换为整数的相关错误 - 这是我所预料的。当我通过这样声明来“修复”该错误时:
@page "/news/edit/{newsID:int}"
代码进入 OnInitialisedAsync 方法后我收到一个错误 - 这是页面 BuildRenderTree 上的空引用错误。作为参考,这是我的 OnInitialisedAsync 方法:
protected override async Task OnInitializedAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
categories = await procedures.spGetNewsCategoriesAsync();
users = await procedures.spGetUsersAsync();
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
itemModel = new SaveNewsItemModel(newsItem.headline, newsItem.category, newsItem.content, newsItem.id);
if (newsItem.requestedStartDate != null)
{
itemModel.RequestDates(newsItem.requestedStartDate.Value, newsItem.requestedFinishDate.Value);
}
if (newsItem.requester != null)
{
itemModel.AssignRequester(newsItem.requester.GetValueOrDefault());
}
var fileList = await procedures.spGetNewsItemFilesAsync(newsID);
if (fileList.Count > 0)
{
foreach (var file in fileList)
{
itemModel.AddFile(file.fileID, file.newsItem, file.id, file.fileName, file.mimeType, context);
}
}
MaxSize = 15 * 1024 * 1024;
}
我有什么方法可以根据该参数设置页面 - 还是我必须等待页面在客户端绘制后才能使用该参数?
编辑 - Task.Delay 只是我在尝试我在搜索中发现的潜在修复方法。
根据要求提供更多信息:
就是这个例外。我尝试切换到 OnParameterSetAsync 而不是 OnInitialisedAsync 但没有成功
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=FIS2withSyncfusion
StackTrace:
at FIS2withSyncfusion.Pages.News.EditNews.BuildRenderTree(RenderTreeBuilder __builder)
完整文件为:
@page "/news/edit/{newsID:int}"
@inject NavigationManager NavManager;
@inject FACT_FranklinContext context;
@inject IFileService fileService;
@inject IHttpClientFactory ClientFactory;
@inject IJSRuntime js;
@using FIS2withSyncfusion.Models;
@using FIS2withSyncfusion.Utility;
@using Syncfusion.Blazor.RichTextEditor;
@using System.Collections.Generic;
@using System.Threading.Tasks;
@using Newtonsoft.Json;
<div class="card">
<div class="card-header bg-secondary p-2">
<h6 class="text-white m-0">Edit a News Item</h6>
</div>
<div class="card-body">
<div class="form-group row">
<label for="title" class="col-sm-2 col-form-label">Title/Heading</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="title" @bind-value="@(itemModel.Title)" placeholder="Enter Title/Heading" maxlength="100" />
</div>
</div>
<div class="form-group row">
<label for="category" class="col-sm-2 col-form-label">Category</label>
<div class="col-sm-10">
<SfDropDownList TItem="spGetNewsCategoriesResult" TValue="int" Placeholder="Select a category" @ref="sfDropDown" DataSource="categories" @bind-Value="@(itemModel.Category)">
<DropDownListFieldSettings Text="name" Value="id" />
</SfDropDownList>
</div>
</div>
<div class="form-group row">
<label for="category" class="col-sm-2 col-form-label">On Behalf Of</label>
<div class="col-sm-10">
<SfDropDownList TItem="spGetUsersResult" TValue="int" Placeholder="Select a user" DataSource="users" @ref="userDropDown" @bind-Value="@(itemModel.requester)">
<DropDownListFieldSettings Text="userName" Value="id" />
</SfDropDownList>
</div>
</div>
<div class="form-group row">
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" aria-label="Request Specific Dates?" id="requestDates" @bind-value="isChecked" />
</div>
</div>
<label class="form-check-label" for="requestDates">Suggest Dates This Should Be Active?</label>
</div>
</div>
@if (isChecked)
{
<div class="row">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
Active From:
</span>
</div>
<SfDateTimePicker TValue="DateTime" Min="DateTime.Now" @bind-Value="activeFrom"></SfDateTimePicker>
</div>
</div>
<div class="row">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
Active Until:
</span>
</div>
<SfDateTimePicker TValue="DateTime" Min="DateTime.Now" @bind-Value="activeTo"></SfDateTimePicker>
</div>
</div>
}
<div class="form-group row">
<SfUploader @ref="uploader" MaxFileSize="@MaxSize" Files="uploadedFiles" AllowedExtensions=".doc, .docx, .pdf, .xls, .xlsx, .ppt, .pptx, .jpg, .jpeg, .bmp, .png" AllowMultiple="true" AutoUpload="true">
<UploaderEvents ValueChange="OnFileUpload" OnRemove="OnFileRemove" OnClear="OnClearFiles" />
</SfUploader>
</div>
<div class="form-group row" style="height:fit-content;">
<SfRichTextEditor @bind-Value="@(itemModel.Content)" Height="200px" EnableResize="true">
<RichTextEditorPasteCleanupSettings KeepFormat="true" DeniedAttributes="@DeniedAttributes.ToArray()" />
<RichTextEditorToolbarSettings Items="Tools" Type="ToolbarType.Expand" />
</SfRichTextEditor>
</div>
</div>
<div class="card-footer">
<div class="btn-cls">
<button type="button" class="btn btn-primary" @onclick="OnSave">Save</button>
<button type="button" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
@if (ShowDialog)
{
<Dialog Title="Edit A News Item" message="@Message" OKText="@OKText" cancelText="@CancelText" OnClose="OnDialogClose">
</Dialog>
}
@code {
[Parameter]
public int newsID { get; set; }
[CascadingParameter]
Task<AuthenticationState> authenticationStateTask { get; set; }
public string userName { get; set; }
private int MaxSize { get; set; }
int count { get; set; }
private bool ShowDialog { get; set; } = false;
private string Message { get; set; } = "";
private string OKText { get; set; } = "";
private string CancelText { get; set; } = "";
public DateTime activeTo { get; set; }
public DateTime activeFrom { get; set; }
private bool isChecked { get; set; }
List<string> DeniedAttributes = new List<string>() {
"id", "title", "style"
};
List<UploaderUploadedFiles> uploadedFiles = new List<UploaderUploadedFiles>();
List<spGetNewsCategoriesResult> categories = new List<spGetNewsCategoriesResult>();
List<spGetUsersResult> users = new List<spGetUsersResult>();
SfDropDownList<int, spGetNewsCategoriesResult> sfDropDown;
SfDropDownList<int, spGetUsersResult> userDropDown;
spGetNewsItemResult newsItem = new spGetNewsItemResult();
SaveNewsItemModel itemModel;
List<Syncfusion.Blazor.Inputs.FileInfo> Files = new List<Syncfusion.Blazor.Inputs.FileInfo>();
SfUploader uploader;
List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();
protected override async Task OnInitializedAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
categories = await procedures.spGetNewsCategoriesAsync();
users = await procedures.spGetUsersAsync();
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
itemModel = new SaveNewsItemModel(newsItem.headline, newsItem.category, newsItem.content, newsItem.id);
if (newsItem.requestedStartDate != null)
{
itemModel.RequestDates(newsItem.requestedStartDate.Value, newsItem.requestedFinishDate.Value);
}
if (newsItem.requester != null)
{
itemModel.AssignRequester(newsItem.requester.GetValueOrDefault());
}
var fileList = await procedures.spGetNewsItemFilesAsync(newsID);
if (fileList.Count > 0)
{
foreach (var file in fileList)
{
itemModel.AddFile(file.fileID, file.newsItem, file.id, file.fileName, file.mimeType, context);
}
}
MaxSize = 15 * 1024 * 1024;
}
private List<ToolbarItemModel> Tools = new List<ToolbarItemModel>() {
new ToolbarItemModel()
{
Command = ToolbarCommand.Bold
},
new ToolbarItemModel()
{
Command = ToolbarCommand.Italic
},
new ToolbarItemModel()
{
Command= ToolbarCommand.Underline
},
new ToolbarItemModel()
{
Command= ToolbarCommand.Separator
},
new ToolbarItemModel()
{
Command = ToolbarCommand.Undo
},
new ToolbarItemModel()
{
Command = ToolbarCommand.Redo
},
new ToolbarItemModel()
{
Command= ToolbarCommand.Separator
},
new ToolbarItemModel()
{
Command = ToolbarCommand.OrderedList
},
new ToolbarItemModel()
{
Command = ToolbarCommand.UnorderedList
}
};
private async Task OnFileUpload(UploadChangeEventArgs args)
{
foreach (var file in args.Files)
{
var fileName = file.FileInfo.Name;
using (var ms = file.Stream)
{
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
files.Add(fileInfo);
var bytes = ms.ToArray();
await fileService.SaveFileToTempAsync(bytes, fileName);
itemModel.AddFile(fileName, fileInfo.Extension, context);
}
}
}
private async Task OnClearFiles(ClearingEventArgs args)
{
foreach (var file in args.FilesData)
{
var fileName = file.Name;
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
itemModel.RemoveFile(fileName, fileInfo.Extension, context);
fileService.DeleteTempFile(fileName);
}
}
private async Task OnFileRemove(RemovingEventArgs args)
{
foreach (var file in args.FilesData)
{
var fileName = file.Name;
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
itemModel.RemoveFile(fileName, fileInfo.Extension, context);
fileService.DeleteTempFile(fileName);
}
}
private async Task OnSave()
{
if (isChecked)
{
itemModel.RequestDates(activeFrom, activeTo);
}
else
{
itemModel.RevokeDates();
}
var procedures = context.Procedures;
var addedFiles = await procedures.spEditNewsItemAsync(JsonConvert.SerializeObject(itemModel), userName);
if (addedFiles.Count > 0)
{
foreach (var file in addedFiles)
{
await fileService.MoveTempToNewsAsync(file.fileName, file.newsID, file.fileID);
}
}
Message = "This has been successfully saved and is now pending review; pressing OK will refresh the page.";
OKText = "OK";
ShowDialog = true;
}
private async Task OnDialogClose(bool r)
{
ShowDialog = false;
NavManager.NavigateTo(NavManager.Uri, true);
}
}
如果到了紧要关头,我可以将它呈现为一个对话框,但我仍然希望将它作为一个页面进行排序,以便我知道如何让它工作以供将来参考。
进一步编辑:
根据 Nicola 的建议,我将 OnInitialisedAsync 方法更改为以下内容:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
categories = await procedures.spGetNewsCategoriesAsync();
users = await procedures.spGetUsersAsync();
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
itemModel = new SaveNewsItemModel(newsItem.headline, newsItem.category, newsItem.content, newsItem.id);
if (newsItem.requestedStartDate != null)
{
itemModel.RequestDates(newsItem.requestedStartDate.Value, newsItem.requestedFinishDate.Value);
}
if (newsItem.requester != null)
{
itemModel.AssignRequester(newsItem.requester.GetValueOrDefault());
}
var fileList = await procedures.spGetNewsItemFilesAsync(newsID);
if (fileList.Count > 0)
{
foreach (var file in fileList)
{
itemModel.AddFile(file.fileID, file.newsItem, file.id, file.fileName, file.mimeType, context);
}
}
MaxSize = 15 * 1024 * 1024;
StateHasChanged();
}
}
这已经起作用了,提供了围绕 HTML 块的保护以防止正确的变量。
提供进一步的背景;这是调用此页面的手风琴组件的代码 - 本质上,这个想法是让用户能够编辑手风琴项目,我认为在新选项卡中进行编辑比用一个大对话框:
@page "/news/approvals"
@inject NavigationManager NavManager;
@inject FACT_FranklinContext context;
@inject IFileService fileService;
@inject IHttpClientFactory ClientFactory;
@inject IJSRuntime js;
@using FIS2withSyncfusion.Models;
@using FIS2withSyncfusion.Utility;
@using Syncfusion.Blazor.RichTextEditor;
@using System.Collections.Generic;
@using System.Threading.Tasks;
@using Newtonsoft.Json;
<div class="card">
<div class="card-header bg-secondary p-2">
<h6 class="text-white m-0">News Items Pending Approval</h6>
</div>
<div class="card-body">
<SfAccordion @bind-ExpandedIndices="expandedIndicesArray">
<AccordionItems>
@foreach (var category in categories)
{
<AccordionItem>
<HeaderTemplate>
<h4>@(category)</h4>
</HeaderTemplate>
<ContentTemplate>
<SfAccordion>
<AccordionItems>
@foreach (var item in newsResults.Where(x => x.category == category))
{
<AccordionItem>
<HeaderTemplate>
<h5>@(item.headline) -- Created by @(item.createdBy)</h5>
</HeaderTemplate>
<ContentTemplate>
<div>
@((MarkupString)item.content)
</div>
@if (item.files != null && item.files > 0)
{
<h5>Files:</h5>
<ul>
@foreach (var file in newsFiles)
{
if (file.newsItem == item.id)
{
<li>
<a id="@GetIDForFile(file.newsItem, file.fileID)" @onclick="eventArgs => HandleFileClick(eventArgs, file.newsItem, file.fileID, file.mimeType, file.fileName)">@(file.fileName)</a>
</li>
}
}
</ul>
}
<div class="row">
<button type="button" class="btn btn-primary" @onclick="eventArgs => OpenApproveDialog(eventArgs, item.id)">Approve</button>
<button type="button" class="btn btn-secondary" @onclick="eventArgs => OnEditClick(eventArgs, item.id)">Edit</button>
<button type="button" class="btn btn-secondary" @onclick="eventArgs => OnDeleteClick(eventArgs, item.id)">Delete</button>
</div>
</ContentTemplate>
</AccordionItem>
}
</AccordionItems>
</SfAccordion>
</ContentTemplate>
</AccordionItem>
}
</AccordionItems>
</SfAccordion>
</div>
</div>
@if (showDateDlg)
{
<NewsApproveForm OnClose="ApproveNewsItem">
</NewsApproveForm>
}
@if (showDeleteDlg)
{
<Dialog Title="Delete News Item" message="@Message" OKText="@OKText" cancelText="@CancelText" OnClose="OnDialogClose">
</Dialog>
}
@code {
[CascadingParameter]
Task<AuthenticationState> authenticationStateTask { get; set; }
public string userName { get; set; }
private bool showDateDlg { get; set; } = false;
private bool showDeleteDlg { get; set; } = false;
private int selectedNewsID { get; set; }
List<spGetUnnapprovedNewsResult> newsResults = new List<spGetUnnapprovedNewsResult>();
List<string> categories = new List<string>();
List<int> expandedIndices = new List<int>();
int[] expandedIndicesArray;
List<spGetNewsFilesResult> newsFiles = new List<spGetNewsFilesResult>();
private string Message { get; set; } = "Are you sure you wish to delete this news item? Once deleted the page will refresh.";
private string OKText { get; set; } = "OK";
private string CancelText { get; set; } = "Cancel";
private string GetIDForFile(int newsItem, Guid FileID)
{
return $"{newsItem}-{FileID}";
}
private void OpenApproveDialog(MouseEventArgs eventArgs, int newsID)
{
showDateDlg = true;
selectedNewsID = newsID;
}
private async Task OnEditClick(MouseEventArgs eventArgs, int newsID)
{
await js.InvokeAsync<object>("open", $"/news/edit/{newsID}", "_blank");
}
private async Task OnDeleteClick(MouseEventArgs eventArgs, int newsID)
{
showDeleteDlg = true;
selectedNewsID = newsID;
}
private async Task ApproveNewsItem(ApproveNewsModel newsModel)
{
if (newsModel.IsApproved)
{
FACT_FranklinContextProcedures procedures = context.Procedures;
await procedures.spApproveNewsItemAsync(selectedNewsID, newsModel.expiryDate, newsModel.startDate, userName);
}
selectedNewsID = -1;
showDateDlg = false;
NavManager.NavigateTo(NavManager.Uri, true);
}
protected async Task HandleFileClick(MouseEventArgs eventArgs, int newsID, Guid fileID, string mimeType, string name)
{
var content = await fileService.GetNewsFileAsync(newsID, fileID);
DownloadUtility.SaveAs(js, name, content, mimeType);
}
protected override async Task OnInitializedAsync()
{
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
newsResults = await procedures.spGetUnnapprovedNewsAsync();
newsFiles = await procedures.spGetNewsFilesAsync(false);
int count = 0;
foreach (var item in newsResults)
{
if (!categories.Contains(item.category))
{
categories.Add(item.category);
expandedIndices.Add(count);
count++;
}
}
categories.Sort();
expandedIndicesArray = expandedIndices.ToArray();
}
private async Task OnDialogClose(bool r)
{
if (r)
{
var procedures = context.Procedures;
await procedures.spDeleteNewsItemAsync(selectedNewsID, userName);
}
selectedNewsID = -1;
showDeleteDlg = false;
NavManager.NavigateTo(NavManager.Uri, true);
}
}
参数尚未设置。将您的代码放在 OnParameterSetAsync()
中。
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
我认为你的行 .First()
导致了这个错误,因为 newsID 是 0。
我认为问题在于 itemModel
(假设还有其他 properties/variables)在您尝试访问它时未声明。
您可以通过这样的检查来保护您的代码:
@if (itemModel == null)
{
<div>loading data...</div>
}
else
{
<div class="card">
...
}
提示:请记住,只加载一次数据是一个好习惯。
如果您在 OnInitialized
方法中加载数据,对后端的调用将完成两次。
第一次用于预渲染(你可以禁用它,但它是 Blazor 中的标准),第二次用于最终渲染。
所以最好加一个
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadDataAsync();
StateHasChanged();
}
}
并创建一个名为 LoadDataAsync
的方法,并调用所有方法。
我对 Blazor 比较陌生,我遇到了一些我认为相当简单的问题 - 将整数作为路由参数传递。作为参考,传递参数本身非常有效(即它出现在地址栏中)。
但是,问题是当我尝试在新页面上使用该参数时 - 想法是父页面是一个充满各种操作项目的手风琴,如果您单击项目上的编辑操作,您可以在新标签页中对其进行编辑。
如果我这样声明页面:
@page "/news/edit/{newsID}"
我收到了从字符串转换为整数的相关错误 - 这是我所预料的。当我通过这样声明来“修复”该错误时:
@page "/news/edit/{newsID:int}"
代码进入 OnInitialisedAsync 方法后我收到一个错误 - 这是页面 BuildRenderTree 上的空引用错误。作为参考,这是我的 OnInitialisedAsync 方法:
protected override async Task OnInitializedAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
categories = await procedures.spGetNewsCategoriesAsync();
users = await procedures.spGetUsersAsync();
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
itemModel = new SaveNewsItemModel(newsItem.headline, newsItem.category, newsItem.content, newsItem.id);
if (newsItem.requestedStartDate != null)
{
itemModel.RequestDates(newsItem.requestedStartDate.Value, newsItem.requestedFinishDate.Value);
}
if (newsItem.requester != null)
{
itemModel.AssignRequester(newsItem.requester.GetValueOrDefault());
}
var fileList = await procedures.spGetNewsItemFilesAsync(newsID);
if (fileList.Count > 0)
{
foreach (var file in fileList)
{
itemModel.AddFile(file.fileID, file.newsItem, file.id, file.fileName, file.mimeType, context);
}
}
MaxSize = 15 * 1024 * 1024;
}
我有什么方法可以根据该参数设置页面 - 还是我必须等待页面在客户端绘制后才能使用该参数?
编辑 - Task.Delay 只是我在尝试我在搜索中发现的潜在修复方法。
根据要求提供更多信息:
就是这个例外。我尝试切换到 OnParameterSetAsync 而不是 OnInitialisedAsync 但没有成功
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=FIS2withSyncfusion
StackTrace:
at FIS2withSyncfusion.Pages.News.EditNews.BuildRenderTree(RenderTreeBuilder __builder)
完整文件为:
@page "/news/edit/{newsID:int}"
@inject NavigationManager NavManager;
@inject FACT_FranklinContext context;
@inject IFileService fileService;
@inject IHttpClientFactory ClientFactory;
@inject IJSRuntime js;
@using FIS2withSyncfusion.Models;
@using FIS2withSyncfusion.Utility;
@using Syncfusion.Blazor.RichTextEditor;
@using System.Collections.Generic;
@using System.Threading.Tasks;
@using Newtonsoft.Json;
<div class="card">
<div class="card-header bg-secondary p-2">
<h6 class="text-white m-0">Edit a News Item</h6>
</div>
<div class="card-body">
<div class="form-group row">
<label for="title" class="col-sm-2 col-form-label">Title/Heading</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="title" @bind-value="@(itemModel.Title)" placeholder="Enter Title/Heading" maxlength="100" />
</div>
</div>
<div class="form-group row">
<label for="category" class="col-sm-2 col-form-label">Category</label>
<div class="col-sm-10">
<SfDropDownList TItem="spGetNewsCategoriesResult" TValue="int" Placeholder="Select a category" @ref="sfDropDown" DataSource="categories" @bind-Value="@(itemModel.Category)">
<DropDownListFieldSettings Text="name" Value="id" />
</SfDropDownList>
</div>
</div>
<div class="form-group row">
<label for="category" class="col-sm-2 col-form-label">On Behalf Of</label>
<div class="col-sm-10">
<SfDropDownList TItem="spGetUsersResult" TValue="int" Placeholder="Select a user" DataSource="users" @ref="userDropDown" @bind-Value="@(itemModel.requester)">
<DropDownListFieldSettings Text="userName" Value="id" />
</SfDropDownList>
</div>
</div>
<div class="form-group row">
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" aria-label="Request Specific Dates?" id="requestDates" @bind-value="isChecked" />
</div>
</div>
<label class="form-check-label" for="requestDates">Suggest Dates This Should Be Active?</label>
</div>
</div>
@if (isChecked)
{
<div class="row">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
Active From:
</span>
</div>
<SfDateTimePicker TValue="DateTime" Min="DateTime.Now" @bind-Value="activeFrom"></SfDateTimePicker>
</div>
</div>
<div class="row">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">
Active Until:
</span>
</div>
<SfDateTimePicker TValue="DateTime" Min="DateTime.Now" @bind-Value="activeTo"></SfDateTimePicker>
</div>
</div>
}
<div class="form-group row">
<SfUploader @ref="uploader" MaxFileSize="@MaxSize" Files="uploadedFiles" AllowedExtensions=".doc, .docx, .pdf, .xls, .xlsx, .ppt, .pptx, .jpg, .jpeg, .bmp, .png" AllowMultiple="true" AutoUpload="true">
<UploaderEvents ValueChange="OnFileUpload" OnRemove="OnFileRemove" OnClear="OnClearFiles" />
</SfUploader>
</div>
<div class="form-group row" style="height:fit-content;">
<SfRichTextEditor @bind-Value="@(itemModel.Content)" Height="200px" EnableResize="true">
<RichTextEditorPasteCleanupSettings KeepFormat="true" DeniedAttributes="@DeniedAttributes.ToArray()" />
<RichTextEditorToolbarSettings Items="Tools" Type="ToolbarType.Expand" />
</SfRichTextEditor>
</div>
</div>
<div class="card-footer">
<div class="btn-cls">
<button type="button" class="btn btn-primary" @onclick="OnSave">Save</button>
<button type="button" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
@if (ShowDialog)
{
<Dialog Title="Edit A News Item" message="@Message" OKText="@OKText" cancelText="@CancelText" OnClose="OnDialogClose">
</Dialog>
}
@code {
[Parameter]
public int newsID { get; set; }
[CascadingParameter]
Task<AuthenticationState> authenticationStateTask { get; set; }
public string userName { get; set; }
private int MaxSize { get; set; }
int count { get; set; }
private bool ShowDialog { get; set; } = false;
private string Message { get; set; } = "";
private string OKText { get; set; } = "";
private string CancelText { get; set; } = "";
public DateTime activeTo { get; set; }
public DateTime activeFrom { get; set; }
private bool isChecked { get; set; }
List<string> DeniedAttributes = new List<string>() {
"id", "title", "style"
};
List<UploaderUploadedFiles> uploadedFiles = new List<UploaderUploadedFiles>();
List<spGetNewsCategoriesResult> categories = new List<spGetNewsCategoriesResult>();
List<spGetUsersResult> users = new List<spGetUsersResult>();
SfDropDownList<int, spGetNewsCategoriesResult> sfDropDown;
SfDropDownList<int, spGetUsersResult> userDropDown;
spGetNewsItemResult newsItem = new spGetNewsItemResult();
SaveNewsItemModel itemModel;
List<Syncfusion.Blazor.Inputs.FileInfo> Files = new List<Syncfusion.Blazor.Inputs.FileInfo>();
SfUploader uploader;
List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();
protected override async Task OnInitializedAsync()
{
await Task.Delay(TimeSpan.FromSeconds(10));
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
categories = await procedures.spGetNewsCategoriesAsync();
users = await procedures.spGetUsersAsync();
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
itemModel = new SaveNewsItemModel(newsItem.headline, newsItem.category, newsItem.content, newsItem.id);
if (newsItem.requestedStartDate != null)
{
itemModel.RequestDates(newsItem.requestedStartDate.Value, newsItem.requestedFinishDate.Value);
}
if (newsItem.requester != null)
{
itemModel.AssignRequester(newsItem.requester.GetValueOrDefault());
}
var fileList = await procedures.spGetNewsItemFilesAsync(newsID);
if (fileList.Count > 0)
{
foreach (var file in fileList)
{
itemModel.AddFile(file.fileID, file.newsItem, file.id, file.fileName, file.mimeType, context);
}
}
MaxSize = 15 * 1024 * 1024;
}
private List<ToolbarItemModel> Tools = new List<ToolbarItemModel>() {
new ToolbarItemModel()
{
Command = ToolbarCommand.Bold
},
new ToolbarItemModel()
{
Command = ToolbarCommand.Italic
},
new ToolbarItemModel()
{
Command= ToolbarCommand.Underline
},
new ToolbarItemModel()
{
Command= ToolbarCommand.Separator
},
new ToolbarItemModel()
{
Command = ToolbarCommand.Undo
},
new ToolbarItemModel()
{
Command = ToolbarCommand.Redo
},
new ToolbarItemModel()
{
Command= ToolbarCommand.Separator
},
new ToolbarItemModel()
{
Command = ToolbarCommand.OrderedList
},
new ToolbarItemModel()
{
Command = ToolbarCommand.UnorderedList
}
};
private async Task OnFileUpload(UploadChangeEventArgs args)
{
foreach (var file in args.Files)
{
var fileName = file.FileInfo.Name;
using (var ms = file.Stream)
{
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
files.Add(fileInfo);
var bytes = ms.ToArray();
await fileService.SaveFileToTempAsync(bytes, fileName);
itemModel.AddFile(fileName, fileInfo.Extension, context);
}
}
}
private async Task OnClearFiles(ClearingEventArgs args)
{
foreach (var file in args.FilesData)
{
var fileName = file.Name;
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
itemModel.RemoveFile(fileName, fileInfo.Extension, context);
fileService.DeleteTempFile(fileName);
}
}
private async Task OnFileRemove(RemovingEventArgs args)
{
foreach (var file in args.FilesData)
{
var fileName = file.Name;
System.IO.FileInfo fileInfo = new System.IO.FileInfo(fileName);
itemModel.RemoveFile(fileName, fileInfo.Extension, context);
fileService.DeleteTempFile(fileName);
}
}
private async Task OnSave()
{
if (isChecked)
{
itemModel.RequestDates(activeFrom, activeTo);
}
else
{
itemModel.RevokeDates();
}
var procedures = context.Procedures;
var addedFiles = await procedures.spEditNewsItemAsync(JsonConvert.SerializeObject(itemModel), userName);
if (addedFiles.Count > 0)
{
foreach (var file in addedFiles)
{
await fileService.MoveTempToNewsAsync(file.fileName, file.newsID, file.fileID);
}
}
Message = "This has been successfully saved and is now pending review; pressing OK will refresh the page.";
OKText = "OK";
ShowDialog = true;
}
private async Task OnDialogClose(bool r)
{
ShowDialog = false;
NavManager.NavigateTo(NavManager.Uri, true);
}
}
如果到了紧要关头,我可以将它呈现为一个对话框,但我仍然希望将它作为一个页面进行排序,以便我知道如何让它工作以供将来参考。
进一步编辑:
根据 Nicola 的建议,我将 OnInitialisedAsync 方法更改为以下内容:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
categories = await procedures.spGetNewsCategoriesAsync();
users = await procedures.spGetUsersAsync();
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
itemModel = new SaveNewsItemModel(newsItem.headline, newsItem.category, newsItem.content, newsItem.id);
if (newsItem.requestedStartDate != null)
{
itemModel.RequestDates(newsItem.requestedStartDate.Value, newsItem.requestedFinishDate.Value);
}
if (newsItem.requester != null)
{
itemModel.AssignRequester(newsItem.requester.GetValueOrDefault());
}
var fileList = await procedures.spGetNewsItemFilesAsync(newsID);
if (fileList.Count > 0)
{
foreach (var file in fileList)
{
itemModel.AddFile(file.fileID, file.newsItem, file.id, file.fileName, file.mimeType, context);
}
}
MaxSize = 15 * 1024 * 1024;
StateHasChanged();
}
}
这已经起作用了,提供了围绕 HTML 块的保护以防止正确的变量。
提供进一步的背景;这是调用此页面的手风琴组件的代码 - 本质上,这个想法是让用户能够编辑手风琴项目,我认为在新选项卡中进行编辑比用一个大对话框:
@page "/news/approvals"
@inject NavigationManager NavManager;
@inject FACT_FranklinContext context;
@inject IFileService fileService;
@inject IHttpClientFactory ClientFactory;
@inject IJSRuntime js;
@using FIS2withSyncfusion.Models;
@using FIS2withSyncfusion.Utility;
@using Syncfusion.Blazor.RichTextEditor;
@using System.Collections.Generic;
@using System.Threading.Tasks;
@using Newtonsoft.Json;
<div class="card">
<div class="card-header bg-secondary p-2">
<h6 class="text-white m-0">News Items Pending Approval</h6>
</div>
<div class="card-body">
<SfAccordion @bind-ExpandedIndices="expandedIndicesArray">
<AccordionItems>
@foreach (var category in categories)
{
<AccordionItem>
<HeaderTemplate>
<h4>@(category)</h4>
</HeaderTemplate>
<ContentTemplate>
<SfAccordion>
<AccordionItems>
@foreach (var item in newsResults.Where(x => x.category == category))
{
<AccordionItem>
<HeaderTemplate>
<h5>@(item.headline) -- Created by @(item.createdBy)</h5>
</HeaderTemplate>
<ContentTemplate>
<div>
@((MarkupString)item.content)
</div>
@if (item.files != null && item.files > 0)
{
<h5>Files:</h5>
<ul>
@foreach (var file in newsFiles)
{
if (file.newsItem == item.id)
{
<li>
<a id="@GetIDForFile(file.newsItem, file.fileID)" @onclick="eventArgs => HandleFileClick(eventArgs, file.newsItem, file.fileID, file.mimeType, file.fileName)">@(file.fileName)</a>
</li>
}
}
</ul>
}
<div class="row">
<button type="button" class="btn btn-primary" @onclick="eventArgs => OpenApproveDialog(eventArgs, item.id)">Approve</button>
<button type="button" class="btn btn-secondary" @onclick="eventArgs => OnEditClick(eventArgs, item.id)">Edit</button>
<button type="button" class="btn btn-secondary" @onclick="eventArgs => OnDeleteClick(eventArgs, item.id)">Delete</button>
</div>
</ContentTemplate>
</AccordionItem>
}
</AccordionItems>
</SfAccordion>
</ContentTemplate>
</AccordionItem>
}
</AccordionItems>
</SfAccordion>
</div>
</div>
@if (showDateDlg)
{
<NewsApproveForm OnClose="ApproveNewsItem">
</NewsApproveForm>
}
@if (showDeleteDlg)
{
<Dialog Title="Delete News Item" message="@Message" OKText="@OKText" cancelText="@CancelText" OnClose="OnDialogClose">
</Dialog>
}
@code {
[CascadingParameter]
Task<AuthenticationState> authenticationStateTask { get; set; }
public string userName { get; set; }
private bool showDateDlg { get; set; } = false;
private bool showDeleteDlg { get; set; } = false;
private int selectedNewsID { get; set; }
List<spGetUnnapprovedNewsResult> newsResults = new List<spGetUnnapprovedNewsResult>();
List<string> categories = new List<string>();
List<int> expandedIndices = new List<int>();
int[] expandedIndicesArray;
List<spGetNewsFilesResult> newsFiles = new List<spGetNewsFilesResult>();
private string Message { get; set; } = "Are you sure you wish to delete this news item? Once deleted the page will refresh.";
private string OKText { get; set; } = "OK";
private string CancelText { get; set; } = "Cancel";
private string GetIDForFile(int newsItem, Guid FileID)
{
return $"{newsItem}-{FileID}";
}
private void OpenApproveDialog(MouseEventArgs eventArgs, int newsID)
{
showDateDlg = true;
selectedNewsID = newsID;
}
private async Task OnEditClick(MouseEventArgs eventArgs, int newsID)
{
await js.InvokeAsync<object>("open", $"/news/edit/{newsID}", "_blank");
}
private async Task OnDeleteClick(MouseEventArgs eventArgs, int newsID)
{
showDeleteDlg = true;
selectedNewsID = newsID;
}
private async Task ApproveNewsItem(ApproveNewsModel newsModel)
{
if (newsModel.IsApproved)
{
FACT_FranklinContextProcedures procedures = context.Procedures;
await procedures.spApproveNewsItemAsync(selectedNewsID, newsModel.expiryDate, newsModel.startDate, userName);
}
selectedNewsID = -1;
showDateDlg = false;
NavManager.NavigateTo(NavManager.Uri, true);
}
protected async Task HandleFileClick(MouseEventArgs eventArgs, int newsID, Guid fileID, string mimeType, string name)
{
var content = await fileService.GetNewsFileAsync(newsID, fileID);
DownloadUtility.SaveAs(js, name, content, mimeType);
}
protected override async Task OnInitializedAsync()
{
var authState = await authenticationStateTask;
var user = authState.User;
userName = user.Identity.Name;
FACT_FranklinContextProcedures procedures = context.Procedures;
newsResults = await procedures.spGetUnnapprovedNewsAsync();
newsFiles = await procedures.spGetNewsFilesAsync(false);
int count = 0;
foreach (var item in newsResults)
{
if (!categories.Contains(item.category))
{
categories.Add(item.category);
expandedIndices.Add(count);
count++;
}
}
categories.Sort();
expandedIndicesArray = expandedIndices.ToArray();
}
private async Task OnDialogClose(bool r)
{
if (r)
{
var procedures = context.Procedures;
await procedures.spDeleteNewsItemAsync(selectedNewsID, userName);
}
selectedNewsID = -1;
showDeleteDlg = false;
NavManager.NavigateTo(NavManager.Uri, true);
}
}
参数尚未设置。将您的代码放在 OnParameterSetAsync()
中。
List<spGetNewsItemResult> results = await procedures.spGetNewsItemAsync(newsID);
newsItem = results.First();
我认为你的行 .First()
导致了这个错误,因为 newsID 是 0。
我认为问题在于 itemModel
(假设还有其他 properties/variables)在您尝试访问它时未声明。
您可以通过这样的检查来保护您的代码:
@if (itemModel == null)
{
<div>loading data...</div>
}
else
{
<div class="card">
...
}
提示:请记住,只加载一次数据是一个好习惯。
如果您在 OnInitialized
方法中加载数据,对后端的调用将完成两次。
第一次用于预渲染(你可以禁用它,但它是 Blazor 中的标准),第二次用于最终渲染。
所以最好加一个
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadDataAsync();
StateHasChanged();
}
}
并创建一个名为 LoadDataAsync
的方法,并调用所有方法。