日期参数为空致API报错:非可空System.DateTime类型Fromdate参数缺失
请求无效错误排查:Fromdate参数空值问题
错误信息
{"Message":"请求无效","MessageDetail":"参数字典中,'shenoyapi.Controllers.ReportController'内的CustomerWiseQuotationHistory方法的非可空类型System.DateTime参数'Fromdate'存在空条目。可选参数必须是引用类型、可空类型或声明为可选参数。"}
相关代码
Component.ts 验证逻辑
validate(): boolean { this.appErrors = []; if (this.objProductQuotationHistory.ProductName=='') { this.appErrors.push({ Title: 'Select Product Name.' }); } if (this.objProductQuotationHistory.FromDate > this.objProductQuotationHistory.ToDate) { this.appErrors.push({ Title: 'From Date date cannot be greater than To Date.' }); } if (this.appErrors.length > 0) { return false; } else { return true; } } print(type: string ): void { if (!this.validate()) { const dialogRef = this.dialog.open(AlertdialogComponent, { data: this.appErrors }); return; } // 此处应包含调用getProductHistory的逻辑 }
Services.ts 请求方法
getProductHistory(productId: number,FromDate: Date,ToDate:Date): Observable<number>{ let f = moment(FromDate); let fromdate = f.format('YYYY-MM-DD'); let t = moment(ToDate); let todate = t.format('YYYY-MM-DD'); return this.http.get<number>(AppConfig.BASE_API_URL +'/api/report/productwisequotationhistoryreport/' + productId + '/' + fromdate + '/' + todate); }
API 接口代码(C#)
[Route("api/report/productwisequotationhistoryreport/{id}/{type}/{Fromdate}/{ToDate}")] [HttpGet] public HttpResponseMessage ProductrWiseQuotationHistory(long id, string type, DateTime Fromdate, DateTime ToDate) { try { rptViewer.ProcessingMode = ProcessingMode.Local; rptViewer.LocalReport.ReportEmbeddedResource = "shenoyapi.Reports.ProductWiseQuotationHistoryReport.rdlc"; var dsHeaderData = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings["AppDatabaseConnection"].ToString(), CommandType.StoredProcedure, 0, "GetReportHeader"); var dtHeader = new ProductWiseQuotationHistoryDataSource.GetReportHeaderDataTable(); foreach (DataRow item in dsHeaderData.Tables[0].Rows) { dtHeader.AddGetReportHeaderRow( Convert.ToInt64(item[0]), item[1].ToString(), item[2].ToString(), item[3].ToString(), item[4].ToString(), item[5].ToString(), item[6].ToString(), item[7].ToString(), item[8].ToString() ); } var dsHeader = new ReportDataSource("dsHeader", (DataTable)dtHeader); rptViewer.LocalReport.DataSources.Add(dsHeader); var dsProductWiseQuotationHistoryData = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings["AppDatabaseConnection"].ToString(), CommandType.StoredProcedure, 0, "GetProductWiseQuotationHistory", new SqlParameter("@ProductId", id), new SqlParameter("@Fromdate", Fromdate), new SqlParameter("@ToDate", ToDate)); var dtProductWiseQuotationHistoryHeader = new ProductWiseQuotationHistoryDataSource.GetProductWiseQuotationHistoryDataTable(); foreach (DataRow item in dsProductWiseQuotationHistoryData.Tables[0].Rows) { dtProductWiseQuotationHistoryHeader.AddGetProductWiseQuotationHistoryRow( Convert.ToInt64(item[0]), Convert.ToDateTime(item[1]), Convert.ToInt32(item[2]), Convert.ToInt32(item[3]), item[4].ToString(), Convert.ToDateTime(item[5]), item[6].ToString(), item[7].ToString(), Convert.ToDecimal(item[8]), Convert.ToDecimal(item[9]), Convert.ToDecimal(item[10]), Convert.ToDecimal(item[11]), Convert.ToDecimal(item[12]), item[13].ToString(), item[14].ToString(), item[15].ToString(), item[16].ToString(), item[17].ToString(), item[18].ToString(), item[19].ToString(), item[20].ToString(), item[21].ToString(), item[22].ToString(), item[23].ToString(), item[24].ToString(), item[25].ToString(), item[26].ToString(), item[27].ToString(), Convert.ToDecimal(item[28]), Convert.ToDecimal(item[29]), Convert.ToDecimal(item[30]), Convert.ToInt32(item[31]), item[32].ToString(), item[33].ToString(), item[34].ToString(), item[35].ToString(), item[36].ToString(), item[37].ToString(), (byte[])item[38], item[39].ToString(), Convert.ToDecimal(item[40]), Convert.ToDecimal(item[41]), Convert.ToDecimal(item[42]) ); } var dsProductWiseQuotationHistoryHeader = new ReportDataSource("dsReport", (DataTable)dtProductWiseQuotationHistoryHeader); rptViewer.LocalReport.DataSources.Add(dsProductWiseQuotationHistoryHeader); byte[] fileBytes = new byte[0]; if (type == "Excel") { fileBytes = rptViewer.LocalReport.Render("Excel", null, out mimeType, out encoding, out extension, out streamIds, out warnings); } else if (type == "PDF") { fileBytes = rptViewer.LocalReport.Render("PDF", null, out mimeType, out encoding, out extension, out streamIds, out warnings); } HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); response.Content = new ByteArrayContent(fileBytes); response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); if (type == "Excel") { response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/xls"); response.Content.Headers.ContentDisposition.FileName = "QuotationReport_" + DateTime.Now.Ticks.ToString() + ".xls"; } else if (type == "PDF") { response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); response.Content.Headers.ContentDisposition.FileName = "QuotationReport_" + DateTime.Now.Ticks.ToString() + ".pdf"; } return response; } catch (Exception ee) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotAcceptable, ee.Message)); } }
问题分析
我仔细梳理了你的代码,发现几个核心问题:
- API路由与请求URL不匹配:API路由定义是
{id}/{type}/{Fromdate}/{ToDate},但前端请求URL只拼接了productId + '/' + fromdate + '/' + todate,完全漏掉了type参数!这导致后端接收的参数顺序完全错乱:原本type的位置被fromdate占据,Fromdate的位置被todate占据,最后ToDate的位置没有值,直接触发非可空参数空值错误。 - 前端验证遗漏:当前
validate()方法只验证了ProductName和日期大小,没有检查FromDate和ToDate是否为空,如果用户未选择日期,空值传到后端就会报错。 - 方法名拼写/混淆问题:错误信息提到的是
CustomerWiseQuotationHistory方法,但你的API代码里是ProductrWiseQuotationHistory(注意多了一个r),要么是代码粘贴错误,要么是项目中存在同名方法导致混淆。
解决方案
按优先级依次处理:
1. 修复请求URL与路由的匹配问题
修改Services.ts的getProductHistory方法,补充type参数并按路由顺序拼接URL:
getProductHistory(productId: number, type: string, FromDate: Date, ToDate:Date): Observable<number>{ let f = moment(FromDate); let fromdate = f.format('YYYY-MM-DD'); let t = moment(ToDate); let todate = t.format('YYYY-MM-DD'); // 按路由顺序拼接参数:id/type/fromdate/todate return this.http.get<number>(`${AppConfig.BASE_API_URL}/api/report/productwisequotationhistoryreport/${productId}/${type}/${fromdate}/${todate}`); }
同时在Component.ts的print方法中调用时传入type参数:
print(type: string ): void { if (!this.validate()) { const dialogRef = this.dialog.open(AlertdialogComponent, { data: this.appErrors }); return; } // 调用时传入type参数 this.yourServiceName.getProductHistory(this.objProductQuotationHistory.ProductId, type, this.objProductQuotationHistory.FromDate, this.objProductQuotationHistory.ToDate) .subscribe(res => { /* 后续处理逻辑 */ }); }
2. 完善前端验证逻辑
在validate()方法中添加日期非空验证:
validate(): boolean { this.appErrors = []; if (!this.objProductQuotationHistory.ProductName) { this.appErrors.push({ Title: 'Select Product Name.' }); } // 验证FromDate非空 if (!this.objProductQuotationHistory.FromDate) { this.appErrors.push({ Title: 'Please select From Date.' }); } // 验证ToDate非空 if (!this.objProductQuotationHistory.ToDate) { this.appErrors.push({ Title: 'Please select To Date.' }); } // 先判断日期存在再比较大小,避免空值报错 if (this.objProductQuotationHistory.FromDate && this.objProductQuotationHistory.ToDate && this.objProductQuotationHistory.FromDate > this.objProductQuotationHistory.ToDate) { this.appErrors.push({ Title: 'From Date cannot be greater than To Date.' }); } return this.appErrors.length === 0; }
3. 修正API方法名拼写错误
将API中的ProductrWiseQuotationHistory改为ProductWiseQuotationHistory(去掉多余的r),同时确认项目中是否存在CustomerWiseQuotationHistory方法,避免混淆。
4. 后端参数容错处理(可选)
为了防止前端验证失效,可将API的DateTime参数改为可空类型,内部做非空判断:
public HttpResponseMessage ProductWiseQuotationHistory(long id, string type, DateTime? Fromdate, DateTime? ToDate) { // 先校验参数非空 if (!Fromdate.HasValue || !ToDate.HasValue) { return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "From Date and To Date are required."); } // 后续逻辑使用Fromdate.Value和ToDate.Value ... }
完成以上修改后,应该就能解决这个请求无效的问题了。
内容的提问来源于stack exchange,提问作者Keep Watching Madani Channel
相关产品推荐
相关产品推荐

