Skip to content

Commit 962742b

Browse files
committed
feat: introduced typed API responses for better result handling
1 parent aa8ae1c commit 962742b

5 files changed

Lines changed: 213 additions & 80 deletions

File tree

src/WebExpress.WebApp/Assets/js/webexpress.webapp.table.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ webexpress.webapp.TableCtrl = class extends webexpress.webui.TableCtrl {
4545

4646
// Placeholder columns and rows for loading state
4747
_previewColumns = [
48-
{ label: "<span class='placeholder col-6 placeholder-lg'></span>" },
49-
{ label: "<span class='placeholder col-6 placeholder-lg'></span>" },
50-
{ label: "<span class='placeholder col-6 placeholder-lg'></span>" }
48+
{ content: "<span class='placeholder col-6 placeholder-lg'></span>" },
49+
{ content: "<span class='placeholder col-6 placeholder-lg'></span>" },
50+
{ content: "<span class='placeholder col-6 placeholder-lg'></span>" }
5151
];
5252
_previewBody = [
53-
{ cells: [{ text: "<span class='placeholder col-7'></span>" }, { text: "<span class='placeholder col-5'></span>" }, { text: "<span class='placeholder col-6'></span>" }] },
54-
{ cells: [{ text: "<span class='placeholder col-6'></span>" }, { text: "<span class='placeholder col-7'></span>" }, { text: "<span class='placeholder col-5'></span>" }] },
55-
{ cells: [{ text: "<span class='placeholder col-6'></span>" }, { text: "<span class='placeholder col-6'></span>" }, { text: "<span class='placeholder col-7'></span>" }] }
53+
{ cells: [{ content: "<span class='placeholder col-7'></span>" }, { content: "<span class='placeholder col-5'></span>" }, { content: "<span class='placeholder col-6'></span>" }] },
54+
{ cells: [{ content: "<span class='placeholder col-6'></span>" }, { content: "<span class='placeholder col-7'></span>" }, { content: "<span class='placeholder col-5'></span>" }] },
55+
{ cells: [{ content: "<span class='placeholder col-6'></span>" }, { content: "<span class='placeholder col-6'></span>" }, { content: "<span class='placeholder col-7'></span>" }] }
5656
];
5757

5858
/**
@@ -136,9 +136,9 @@ webexpress.webapp.TableCtrl = class extends webexpress.webui.TableCtrl {
136136
})
137137
.then(response => {
138138
// Extract paging and data info with fallback defaults
139-
const page = response.page ?? 0;
140-
const pageSize = response.pageSize ?? 50;
141-
const total = response.total ?? 0;
139+
const page = response.pagination.page ?? 0;
140+
const pageSize = response.pagination.pageSize ?? 50;
141+
const total = response.pagination.total ?? 0;
142142
const totalPages = Math.ceil(total / pageSize);
143143
const startIndex = page * pageSize + 1;
144144
const endIndex = Math.min(startIndex + pageSize - 1, total);

src/WebExpress.WebApp/WWW/Api/1/RestPopupNotification.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using WebExpress.WebCore.WebComponent;
66
using WebExpress.WebCore.WebMessage;
77
using WebExpress.WebCore.WebRestApi;
8+
using WebExpress.WebCore.WebStatusPage;
89
using WebExpress.WebUI.WebNotification;
910

1011
namespace WebExpress.WebApp.WWW.Api._1
@@ -33,41 +34,51 @@ public RestPopupNotification(IComponentHub componentHub, IApplicationContext app
3334
/// Creates data based on the provided request.
3435
/// </summary>
3536
/// <param name="request">The request containing the data to create.</param>
36-
public void CreateData(Request request)
37+
/// <returns>The response containing the result of the operation.</returns>
38+
public Response CreateData(Request request)
3739
{
40+
return new ResponseBadRequest(new StatusMessage("Not implemented."));
3841
}
3942

4043
/// <summary>
4144
/// Retrieves data based on the provided request.
4245
/// </summary>
4346
/// <param name="request">The request containing the criteria for data retrieval.</param>
44-
/// <returns>A collection of notifications.</returns>
45-
public object GetData(Request request)
47+
/// <returns>The response containing the result of the operation.</returns>
48+
public Response GetData(Request request)
4649
{
47-
return _componentHub.GetComponentManager<NotificationManager>()?.GetNotifications
48-
(
49-
_applicationContext, request
50-
);
50+
return new ResponseOK()
51+
{
52+
Content = _componentHub.GetComponentManager<NotificationManager>()?.GetNotifications
53+
(
54+
_applicationContext, request
55+
)
56+
}.AddHeaderContentType("application/json");
5157
}
5258

5359
/// <summary>
5460
/// Updates data based on the provided request.
5561
/// </summary>
5662
/// <param name="request">The request containing the data to update.</param>
57-
public void UpdateData(Request request)
63+
/// <returns>The response containing the result of the operation.</returns>
64+
public Response UpdateData(Request request)
5865
{
66+
return new ResponseBadRequest(new StatusMessage("Not implemented."));
5967
}
6068

6169
/// <summary>
6270
/// Deletes data based on the provided request.
6371
/// </summary>
6472
/// <param name="request">The request containing the data to delete.</param>
65-
public void DeleteData(Request request)
73+
/// <returns>The response containing the result of the operation.</returns>
74+
public Response DeleteData(Request request)
6675
{
6776
if (Guid.TryParse(request.Uri.PathSegments.Last()?.ToString(), out Guid id))
6877
{
6978
_componentHub.GetComponentManager<NotificationManager>()?.RemoveNotifications(id);
7079
}
80+
81+
return new ResponseOK();
7182
}
7283
}
7384
}
Lines changed: 71 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,67 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Reflection;
54
using WebExpress.WebCore;
65
using WebExpress.WebCore.WebMessage;
76
using WebExpress.WebCore.WebRestApi;
7+
using WebExpress.WebCore.WebStatusPage;
88
using WebExpress.WebIndex;
9-
using WebExpress.WebIndex.WebAttribute;
109
using WebExpress.WebIndex.Wql;
1110

1211
namespace WebExpress.WebApp.WebRestApi
1312
{
14-
1513
/// <summary>
1614
/// Abstract class providing CRUD operations for REST API.
1715
/// </summary>
1816
/// <typeparam name="TIndexItem">Type of the index item.</typeparam>
1917
public abstract class RestApiCrud<TIndexItem> : IRestApi
2018
where TIndexItem : IIndexItem
2119
{
22-
/// <summary>
23-
/// Returns the lock object.
24-
/// </summary>
25-
protected object Guard { get; } = new object();
26-
2720
/// <summary>
2821
/// Processing of the resource that was called via the get request.
2922
/// </summary>
3023
/// <param name="request">The request.</param>
31-
/// <returns>An enumeration of which json serializer can be serialized.</returns>
32-
public virtual object GetData(Request request)
24+
/// <returns>The response containing the result of the operation.</returns>
25+
public virtual Response GetData(Request request)
3326
{
34-
var itemCount = 50;
35-
var search = request.HasParameter("search") ? request.GetParameter("search").Value : string.Empty;
36-
var wql = request.HasParameter("wql") ? request.GetParameter("wql").Value : null;
27+
var pageSize = 50;
28+
var filter = request.GetParameter("search")?.Value ?? string.Empty;
29+
var wql = request.GetParameter("wql")?.Value ?? null;
3730
var page = request.GetParameter("page");
38-
var pagenumber = !string.IsNullOrWhiteSpace(page?.Value) ? Convert.ToInt32(page?.Value) : 0;
31+
var pageNumber = !string.IsNullOrWhiteSpace(page?.Value) ? Convert.ToInt32(page?.Value) : 0;
3932

40-
lock (Guard)
33+
try
4134
{
42-
var wqlStatement = !string.IsNullOrWhiteSpace(search) || !string.IsNullOrWhiteSpace(wql)
43-
? WebEx.ComponentHub.GetComponentManager<WebIndex.IndexManager>()?.Retrieve<TIndexItem>(wql ?? $"{GetDefaultSearchAttribute()}='{search}*'")
44-
: WebEx.ComponentHub.GetComponentManager<WebIndex.IndexManager>()?.Retrieve<TIndexItem>("");
45-
var data = GetData(wqlStatement, request);
35+
IEnumerable<TIndexItem> data = [];
4636

47-
var count = data.Count();
48-
var totalpage = Math.Round(count / (double)itemCount, MidpointRounding.ToEven);
37+
if (!string.IsNullOrWhiteSpace(wql))
38+
{
39+
var wqlStatement = WebEx.ComponentHub.GetComponentManager<WebIndex.IndexManager>()?
40+
.Retrieve<TIndexItem>(wql);
4941

50-
if (page == null)
42+
data = GetData(wqlStatement, request);
43+
}
44+
else
5145
{
52-
return new { Data = data };
46+
data = GetData(filter, request);
5347
}
5448

55-
return new { data = data.Skip(itemCount * pagenumber).Take(itemCount), pagination = new { pagenumber, totalpage } };
49+
var result = new RestApiResult()
50+
{
51+
Data = data.Skip(pageSize * pageNumber).Take(pageSize),
52+
Pagination = new RestApiPaginationInfo()
53+
{
54+
PageNumber = pageNumber,
55+
PageSize = pageSize,
56+
TotalCount = data.Count()
57+
}
58+
};
59+
60+
return result.ToResponse();
61+
}
62+
catch (Exception ex)
63+
{
64+
return new ResponseBadRequest(new StatusMessage($"Error processing request.{ex}"));
5665
}
5766
}
5867

@@ -67,11 +76,23 @@ public virtual IEnumerable<TIndexItem> GetData(IWqlStatement<TIndexItem> wql, Re
6776
throw new NotImplementedException();
6877
}
6978

79+
/// <summary>
80+
/// Processing of the resource that was called via the get request.
81+
/// </summary>
82+
/// <param name="filter">The filtering and sorting options.</param>
83+
/// <param name="request">The request.</param>
84+
/// <returns>An enumeration of which json serializer can be serialized.</returns>
85+
public virtual IEnumerable<TIndexItem> GetData(string filter, Request request)
86+
{
87+
throw new NotImplementedException();
88+
}
89+
7090
/// <summary>
7191
/// Creates data.
7292
/// </summary>
7393
/// <param name="request">The request.</param>
74-
public virtual void CreateData(Request request)
94+
/// <returns>The response containing the result of the operation.</returns>
95+
public virtual Response CreateData(Request request)
7596
{
7697
throw new NotImplementedException();
7798
}
@@ -80,18 +101,41 @@ public virtual void CreateData(Request request)
80101
/// Updates data.
81102
/// </summary>
82103
/// <param name="request">The request.</param>
83-
public virtual void UpdateData(Request request)
104+
/// <returns>The response containing the result of the operation.</returns>
105+
public virtual Response UpdateData(Request request)
84106
{
107+
var id = request.GetParameter("id")?.Value;
108+
109+
var errors = UpdateData(id, request);
110+
111+
var result = new RestApiResult()
112+
.AddError(errors);
113+
114+
return result.ToResponse();
115+
}
116+
117+
/// <summary>
118+
/// Updates data.
119+
/// </summary>
120+
/// <param name="id">The id of the data to delete.</param>
121+
/// <param name="request">The request.</param>
122+
/// <returns>An enumeration of validation errors or null.</returns>
123+
public virtual IEnumerable<RestApiError> UpdateData(string id, Request request)
124+
{
125+
return [];
85126
}
86127

87128
/// <summary>
88129
/// Deletes data.
89130
/// </summary>
90131
/// <param name="request">The request.</param>
91-
public virtual void DeleteData(Request request)
132+
/// <returns>The response containing the result of the operation.</returns>
133+
public virtual Response DeleteData(Request request)
92134
{
93135
var id = request.GetParameter("id")?.Value;
94136
DeleteData(id, request);
137+
138+
return new ResponseOK();
95139
}
96140

97141
/// <summary>
@@ -103,18 +147,5 @@ public virtual void DeleteData(string id, Request request)
103147
{
104148
throw new NotImplementedException();
105149
}
106-
107-
/// <summary>
108-
/// Returns the attribute name that has been set as for search queries.
109-
/// </summary>
110-
/// <returns>The name of the default attribute.</returns>
111-
protected virtual string GetDefaultSearchAttribute()
112-
{
113-
return typeof(TIndexItem).GetProperties()
114-
.Where(x => x.GetCustomAttribute<IndexDefaultSearchAttribute>() != null)
115-
.Where(x => x.GetCustomAttribute<IndexIgnoreAttribute>() == null)
116-
.Select(x => x.Name)
117-
.FirstOrDefault();
118-
}
119150
}
120151
}

src/WebExpress.WebApp/WebRestApi/RestApiCrudTable.cs

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using WebExpress.WebCore.Internationalization;
88
using WebExpress.WebCore.WebAttribute;
99
using WebExpress.WebCore.WebMessage;
10+
using WebExpress.WebCore.WebRestApi;
11+
using WebExpress.WebCore.WebStatusPage;
1012
using WebExpress.WebIndex;
1113

1214
namespace WebExpress.WebApp.WebRestApi
@@ -71,42 +73,65 @@ public virtual IEnumerable<RestApiCrudTableRowOption> GetOptions(Request request
7173
/// Processing of the resource that was called via the get request.
7274
/// </summary>
7375
/// <param name="request">The request.</param>
74-
/// <returns>An enumeration of which json serializer can be serialized.</returns>
75-
public override object GetData(Request request)
76+
/// <returns>The response containing the result of the operation.</returns>
77+
public override Response GetData(Request request)
7678
{
77-
var page = Convert.ToInt32(request.GetParameter("page")?.Value ?? "0"); // current page number
79+
var pageNumber = Convert.ToInt32(request.GetParameter("page")?.Value ?? "0"); // current page number
7880
var pageSize = Convert.ToInt32(request.GetParameter("pageSize")?.Value ?? "50"); // number of items per page
81+
var filter = request.GetParameter("search")?.Value ?? string.Empty;
7982
var wql = request.GetParameter("wql")?.Value ?? null;
8083

81-
lock (Guard)
84+
try
8285
{
83-
var wqlStatement = !string.IsNullOrWhiteSpace(wql)
84-
? WebEx.ComponentHub.GetComponentManager<WebIndex.IndexManager>()?.Retrieve<TIndexItem>(wql)
85-
: WebEx.ComponentHub.GetComponentManager<WebIndex.IndexManager>()?.Retrieve<TIndexItem>("");
86+
IEnumerable<TIndexItem> data = [];
87+
88+
if (!string.IsNullOrWhiteSpace(wql))
89+
{
90+
var wqlStatement = WebEx.ComponentHub.GetComponentManager<WebIndex.IndexManager>()?
91+
.Retrieve<TIndexItem>(wql);
92+
93+
data = GetData(wqlStatement, request);
94+
}
95+
else
96+
{
97+
data = GetData(filter, request);
98+
}
99+
86100
var columns = _cachedColumns
87-
.Where(x => x.Value.Visible)
88-
.Select(x => x.Value);
89-
var data = GetData(wqlStatement, request);
90-
var count = data.Count();
101+
.Where(x => x.Value.Visible)
102+
.Select(x => x.Value);
91103

92-
return new
104+
var result = new RestApiTableResult()
93105
{
94-
title = I18N.Translate(request, Title),
95-
columns = columns,
96-
rows = data.Skip(page * pageSize).Take(pageSize).Select(row => new RestApiCrudTableRow
97-
{
98-
Id = row.Id.ToString(),
99-
Cells = _cachedColumns
106+
Title = I18N.Translate(request, Title),
107+
Columns = columns,
108+
Rows = data
109+
.Skip(pageNumber * pageSize)
110+
.Take(pageSize)
111+
.Select(row => new RestApiCrudTableRow
112+
{
113+
Id = row.Id.ToString(),
114+
Cells = _cachedColumns
100115
.Where(x => x.Value.Visible)
101116
.Select(x => new RestApiCrudTableCell
102117
{
103118
Text = x.Key.GetValue(row)?.ToString() ?? string.Empty
104119
}),
105-
Options = GetOptions(request, row)
106-
}),
107-
page = page, // current page number
108-
total = count // total number of entries
120+
Options = GetOptions(request, row)
121+
}),
122+
Pagination = new RestApiPaginationInfo()
123+
{
124+
PageNumber = pageNumber,
125+
PageSize = pageSize,
126+
TotalCount = data.Count()
127+
}
109128
};
129+
130+
return result.ToResponse();
131+
}
132+
catch (Exception ex)
133+
{
134+
return new ResponseBadRequest(new StatusMessage($"Error processing request.{ex}"));
110135
}
111136
}
112137
}

0 commit comments

Comments
 (0)