diff --git a/.gitignore b/.gitignore index 53321ba..c1312c6 100644 --- a/.gitignore +++ b/.gitignore @@ -129,7 +129,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -199,3 +199,4 @@ FakesAssemblies/ License.config /Blomsterringen.Web/imagecache maildrop +.idea diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe2dea..d443551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,7 @@ All notable changes to this project will be documented in this file. ## [11.0.0] - Upgraded to CMS 11 - - Upgraded to framework version 4.6.1 \ No newline at end of file + - Upgraded to framework version 4.6.1 + + ## [11.0.4.6] + - Adding query string capability to filter data by column and exact value \ No newline at end of file diff --git a/README.md b/README.md index 716ae7c..a34b13b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Where can I get it? 1. Add [EPiServer Nuget feed](http://nuget.episerver.com/en/Feed/) in Visual Studio Package Manager options (Tools ->Options -> Package Manager -> Package Sources). 1. Install DDSAdmin via NuGet in Visual Studio. Ensure that you also install the required dependencies. 1. Rebuild your solution. -1. Start editing your data in EPiServer DDS storages. It will be available in CMS menu: Geta->DDS Admin. +1. Start editing your data in EPiServer DDS storage's. It will be available in CMS menu: Geta->DDS Admin. Where can I get more info? ------------------------------ diff --git a/src/Dds/Interfaces/ICrudService.cs b/src/Dds/Interfaces/ICrudService.cs index efd5fc5..c7129be 100644 --- a/src/Dds/Interfaces/ICrudService.cs +++ b/src/Dds/Interfaces/ICrudService.cs @@ -7,7 +7,7 @@ public interface ICrudService { StringResponse Create(string storeName, Dictionary values); StringResponse Delete(string storeName, string identityId); - ReadResponse Read(string storeName, int start, int pageSize, string search, int sortByColumn, string sortDirection); + ReadResponse Read(string storeName, int start, int pageSize, string search, int sortByColumn, string sortDirection, string filterByColumn, string filterValue); StringResponse Update(string storeName, int columnId, string value, string id, string columnName); } } diff --git a/src/Dds/Services/CrudService.cs b/src/Dds/Services/CrudService.cs index 5a36243..0c8a0c3 100644 --- a/src/Dds/Services/CrudService.cs +++ b/src/Dds/Services/CrudService.cs @@ -74,7 +74,7 @@ public StringResponse Delete(string storeName, string identityId) return response; } - public ReadResponse Read(string storeName, int start, int pageSize, string search, int sortByColumn, string sortDirection) + public ReadResponse Read(string storeName, int start, int pageSize, string search, int sortByColumn, string sortDirection, string filterByColumn, string filterValue) { var response = new ReadResponse { Success = false }; logger.Debug("Read started"); @@ -83,22 +83,38 @@ public ReadResponse Read(string storeName, int start, int pageSize, string searc var store = DynamicDataStoreFactory.Instance.GetStore(storeName); var storeMetadata = store.Metadata(); - // TODO: we cannot order here due to fact that this is PropertyBag, if we could it would be great performance boost - // var orderBy = sortByColumn == 0 ? "Id" : StoreMetadata.Columns.ToList()[sortByColumn - 1].PropertyName; - var query = store.ItemsAsPropertyBag(); // .OrderBy(orderBy); + List data; + int count; - var data = sortByColumn == 0 && string.IsNullOrEmpty(search) - ? (sortDirection == "asc" - ? query.OrderBy(r => r.Id).Skip(start).Take(pageSize).ToList() - : query.OrderByDescending(r => r.Id).Skip(start).Take(pageSize).ToList()) - : query.ToList(); + var preSorted = false; + + if (string.IsNullOrWhiteSpace(filterByColumn)) + { + // TODO: we cannot order here due to fact that this is PropertyBag, if we could it would be great performance boost + // var orderBy = sortByColumn == 0 ? "Id" : StoreMetadata.Columns.ToList()[sortByColumn - 1].PropertyName; + var query = store.ItemsAsPropertyBag(); // .OrderBy(orderBy); + + preSorted = sortByColumn == 0 && string.IsNullOrEmpty(search); + + data = preSorted + ? (sortDirection == "asc" + ? query.OrderBy(r => r.Id).Skip(start).Take(pageSize).ToList() + : query.OrderByDescending(r => r.Id).Skip(start).Take(pageSize).ToList()) + : query.ToList(); + count = query.Count(); + } + else + { + data = store.FindAsPropertyBag(filterByColumn, filterValue).ToList(); + count = data.Count; + } List> stringData; - if (sortByColumn == 0 && string.IsNullOrEmpty(search)) + if (preSorted) { // no sorting and no filtering, use fast code then stringData = FormatData(storeMetadata, data); - totalCount = query.Count(); + totalCount = count; } else { diff --git a/src/Dds/Services/StoreService.cs b/src/Dds/Services/StoreService.cs index a308dfc..e4dc0d5 100644 --- a/src/Dds/Services/StoreService.cs +++ b/src/Dds/Services/StoreService.cs @@ -80,12 +80,24 @@ public bool Delete(string storeName, Identity id) } } - public bool Flush(string storeName) + public bool Flush(string storeName, string filterColumnName, string filter) { try { var store = DynamicDataStoreFactory.Instance.GetStore(storeName); - store.DeleteAll(); + if (!string.IsNullOrWhiteSpace(filterColumnName)) + { + var itemsToBeDeleted = store.FindAsPropertyBag(filterColumnName, filter).ToList(); + foreach (var itemToBeDeleted in itemsToBeDeleted) + { + store.Delete(itemToBeDeleted.Id); + } + } + else + { + store.DeleteAll(); + } + return true; } catch (Exception ex) diff --git a/src/modules/_protected/Geta.DdsAdmin/Admin/Constants.cs b/src/modules/_protected/Geta.DdsAdmin/Admin/Constants.cs index c7459a5..38507f9 100644 Binary files a/src/modules/_protected/Geta.DdsAdmin/Admin/Constants.cs and b/src/modules/_protected/Geta.DdsAdmin/Admin/Constants.cs differ diff --git a/src/modules/_protected/Geta.DdsAdmin/Admin/Data.ashx.cs b/src/modules/_protected/Geta.DdsAdmin/Admin/Data.ashx.cs index 8fa1b2b..d0bdf9f 100644 --- a/src/modules/_protected/Geta.DdsAdmin/Admin/Data.ashx.cs +++ b/src/modules/_protected/Geta.DdsAdmin/Admin/Data.ashx.cs @@ -73,7 +73,7 @@ private BaseResponse Delete(HttpContext context, string storeName) return SerializeResponse(context, deleteResponse); } - private BaseResponse Read(HttpContext context, string storeName) + private BaseResponse Read(HttpContext context, string storeName, string filterColumnName, string filter) { int start = Convert.ToInt32(context.Request.QueryString["iDisplayStart"]); int pageSize = Convert.ToInt32(context.Request.QueryString["iDisplayLength"]); @@ -82,7 +82,7 @@ private BaseResponse Read(HttpContext context, string storeName) int sortByColumn = Convert.ToInt32(context.Request.QueryString["iSortCol_0"]); string sortDirection = context.Request.QueryString["sSortDir_0"]; - var readResponse = this.crudService.Read(storeName, start, pageSize, search, sortByColumn, sortDirection); + var readResponse = this.crudService.Read(storeName, start, pageSize, search, sortByColumn, sortDirection, filterColumnName, filter); var result = new { @@ -113,6 +113,10 @@ private void WriteResponse(HttpContext context) logger.InfoFormat("Operation:{0}", operation); string storeName = context.Request.QueryString[Constants.StoreKey]; logger.InfoFormat("Store:{0}", operation); + string filterColumnName = context.Request.QueryString[Constants.FilterColumnNameKey]; + logger.InfoFormat("Filter column name:{0}", filterColumnName); + string filter = context.Request.QueryString[Constants.FilterKey]; + logger.InfoFormat("Filter:{0}", filter); context.Response.Clear(); context.Response.ClearContent(); @@ -123,7 +127,7 @@ private void WriteResponse(HttpContext context) { case "read": { - response = Read(context, storeName); + response = Read(context, storeName, filterColumnName, filter); } break; case "update": diff --git a/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx b/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx index 7fa02f3..8e97b0b 100644 --- a/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx +++ b/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx @@ -5,7 +5,7 @@ - <%# CurrentStoreName %> + <%# CurrentStoreName %> <%# CurrentFilterMessage%> " + Text = "" }); Page.Header.Controls.Add(new Literal { - Text = "" + Text = "" }); } @@ -76,6 +88,12 @@ protected override void OnLoad(EventArgs e) } GetQueryStringParameters(); + if (ExportToXlsx) + { + RenderXlsx(CurrentStoreName, CurrentFilterColumnName, CurrentFilter); + return; + } + Store = _storeService.GetMetadata(CurrentStoreName); LoadAndDisplayData(); @@ -87,9 +105,30 @@ protected string SetItem(RepeaterItem repeaterItem) return string.Empty; } + protected string GetParameters() + { + // ReSharper disable once UseObjectOrCollectionInitializer + var builder = new UrlBuilder("http://localhost"); + builder.QueryCollection[Constants.StoreKey] = CurrentStoreName; + if (CurrentFilterMessage != null) + { + builder.QueryCollection[Constants.FilterColumnNameKey] = CurrentFilterColumnName; + builder.QueryCollection[Constants.FilterKey] = CurrentFilter; + } + + return builder.Query.TrimStart('?'); + } + private void GetQueryStringParameters() { CurrentStoreName = HttpUtility.HtmlEncode(Request.QueryString[Constants.StoreKey]); + CurrentFilterColumnName = HttpUtility.HtmlEncode(Request.QueryString[Constants.FilterColumnNameKey]); + CurrentFilter = HttpUtility.HtmlEncode(Request.QueryString[Constants.FilterKey]); + ExportToXlsx = HttpUtility.HtmlEncode(Request.QueryString[Constants.ExportToXlsxKey]) != null; + CurrentFilterMessage = + !string.IsNullOrWhiteSpace(CurrentFilterColumnName) && !string.IsNullOrWhiteSpace(CurrentFilter) + ? $"filtered by {CurrentFilterColumnName} = {CurrentFilter}" + : null; CustomHeading = HttpUtility.HtmlEncode(Request.QueryString[Constants.HeadingKey]); CustomMessage = HttpUtility.HtmlEncode(Request.QueryString[Constants.MessageKey]); @@ -101,10 +140,10 @@ private void GetQueryStringParameters() } HiddenColumns = hiddenColumns.Split(new[] - { - "," - }, - StringSplitOptions.RemoveEmptyEntries).Select(item => Convert.ToInt32(item)).ToArray(); + { + "," + }, + StringSplitOptions.RemoveEmptyEntries).Select(item => Convert.ToInt32(item)).ToArray(); } private void LoadAndDisplayData() @@ -127,20 +166,59 @@ private void LoadAndDisplayData() repForm.DataSource = Store.Columns; repColumnsHeader.DataBind(); repForm.DataBind(); + + Flush.Text = string.IsNullOrWhiteSpace(CurrentFilterMessage) ? "Delete all data" : "Delete filtered data"; + Flush.OnClientClick = string.IsNullOrWhiteSpace(CurrentFilterMessage) + ? "return confirm('Do you really want to delete all data from this table?')" + : "return confirm('Do you really want to delete filtered data from this table?')"; + Export.Text = string.IsNullOrWhiteSpace(CurrentFilterMessage) + ? "Export to Excel" + : "Export filtered data to Excel"; } - protected void FlushStore(object sender, EventArgs e) + protected void FlushStoreClick(object sender, EventArgs e) { var storeName = Request.Form["CurrentStoreName"]; - _storeService.Flush(storeName); + var filterColumnName = Request.Form["CurrentFilterColumnName"]; + var filter = Request.Form["CurrentFilter"]; + + _storeService.Flush(storeName, filterColumnName, filter); Response.Redirect(Request.RawUrl); } - protected void ExportStore(object sender, EventArgs e) + protected void FilterClick(object sender, EventArgs e) + { + var filterColumnName = Request.Form["CurrentFilterColumnName"]; + var filter = Request.Form["CurrentFilter"]; + + var builder = new UrlBuilder(Request.Url); + if (string.IsNullOrWhiteSpace(filterColumnName)) + { + builder.QueryCollection.Remove(Constants.FilterColumnNameKey); + builder.QueryCollection.Remove(Constants.FilterKey); + } + else + { + builder.QueryCollection[Constants.FilterColumnNameKey] = filterColumnName; + builder.QueryCollection[Constants.FilterKey] = filter; + } + + Response.Redirect(builder.ToString()); + } + + protected void ExportStoreClick(object sender, EventArgs e) { var storeName = Request.Form["CurrentStoreName"]; - var ddsDataSet = GetDdsStoreAsDataSet(storeName); + var filterColumnName = Request.Form["CurrentFilterColumnName"]; + var filter = Request.Form["CurrentFilter"]; + + RenderXlsx(storeName, filterColumnName, filter); + } + + private void RenderXlsx(string storeName, string filterColumnName, string filter) + { + var ddsDataSet = GetDdsStoreAsDataSet(storeName, filterColumnName, filter); using (var wb = new XLWorkbook()) { @@ -150,7 +228,19 @@ protected void ExportStore(object sender, EventArgs e) Response.Buffer = true; Response.Charset = ""; Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - Response.AddHeader("content-disposition", $"attachment;filename={storeName}.xlsx"); + if (!string.IsNullOrWhiteSpace(filterColumnName)) + { + var fileName = $"{storeName}.{filterColumnName}.{filter}.xlsx"; + Path.GetInvalidFileNameChars() + .Aggregate(fileName, (current, c) => current.Replace(c, '_')); + Response.AddHeader("content-disposition", + $"attachment;filename={fileName.Substring(0, Math.Min(fileName.Length, 200))}"); + } + else + { + Response.AddHeader("content-disposition", $"attachment;filename={storeName}.xlsx"); + } + using (var MyMemoryStream = new MemoryStream()) { wb.SaveAs(MyMemoryStream); @@ -161,7 +251,7 @@ protected void ExportStore(object sender, EventArgs e) } } - private DataSet GetDdsStoreAsDataSet(string storeName) + private DataSet GetDdsStoreAsDataSet(string storeName, string filterColumnName, string filter) { var dataTable = new DataTable("record"); @@ -169,12 +259,12 @@ private DataSet GetDdsStoreAsDataSet(string storeName) foreach (var column in columns.Columns) { - dataTable.Columns.Add(column.PropertyName, typeof (string)); + dataTable.Columns.Add(column.PropertyName, typeof(string)); } - var allRecords = _crudService.Read(storeName, 0, int.MaxValue, null, 0, null); + var allRecords = _crudService.Read(storeName, 0, int.MaxValue, null, 0, null, filterColumnName, filter); - if (allRecords == null || !allRecords.Success || allRecords.TotalCount == 0) + if (allRecords == null || !allRecords.Success) { return null; } @@ -203,4 +293,4 @@ private DataSet GetDdsStoreAsDataSet(string storeName) }; } } -} +} \ No newline at end of file diff --git a/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx.designer.cs b/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx.designer.cs index 946160b..96eab3a 100644 --- a/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx.designer.cs +++ b/src/modules/_protected/Geta.DdsAdmin/Admin/DdsAdmin.aspx.designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // @@ -17,7 +17,7 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.HtmlControls.HtmlHead head1; @@ -26,7 +26,7 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Panel hdivNoStoreTypeSelected; @@ -35,7 +35,7 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Panel hdivStoreTypeDoesntExist; @@ -44,7 +44,7 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Panel hdivStoreTypeSelected; @@ -53,16 +53,25 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Repeater repForm; + /// + /// Filter control. + /// + /// + /// Auto-generated field. + /// To modify, move the field declaration from the designer file to a code-behind file. + /// + protected global::System.Web.UI.WebControls.Button Filter; + /// /// Flush control. /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Button Flush; @@ -71,7 +80,7 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Button Export; @@ -80,7 +89,7 @@ public partial class DdsAdmin { /// /// /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. + /// To modify, move the field declaration from the designer file to a code-behind file. /// protected global::System.Web.UI.WebControls.Repeater repColumnsHeader; }