From 303d1a8ff44af537cdd599a668d57fb049bc03c5 Mon Sep 17 00:00:00 2001 From: FastReports-bot Date: Mon, 11 Sep 2023 12:13:11 +0300 Subject: [PATCH] * sync 9/11/2023 version: 2023.3.0 --- FastReport.Base/Base.cs | 34 +- .../Export/Html/HTMLExportLayers.cs | 6 +- FastReport.Base/HtmlObject.cs | 17 - FastReport.Base/ReportPage.cs | 9 +- FastReport.Base/Utils/Config.cs | 9 - .../Utils/FRPrivateFontCollection.cs | 104 +++- .../Utils/Json/Serialization/JsonConverter.cs | 5 + .../Json/Serialization/JsonDeserializer.cs | 7 + FastReport.Base/Utils/Res.cs | 10 +- .../Application/Cache/CacheOptions.cs | 25 +- .../Application/Cache/WebReportCache.cs | 16 +- .../Cache/WebReportDistributedCache.cs | 27 +- .../Application/Cache/WebReportLegacyCache.cs | 33 +- .../Application/Cache/WebReportMemoryCache.cs | 4 +- .../Application/ExportMenuSettings.cs | 7 - .../Application/ExportsHelper.cs | 4 +- .../FastReportBuilderExtensions.cs | 46 -- .../Application/FastReportMiddleware.cs | 68 --- ...portServiceCollectionExtensions.Backend.cs | 66 --- .../Infrastructure/ControllerBuilder.cs | 541 ++++++++++++++++++ .../Infrastructure/ControllerExecutor.cs | 74 +++ .../FastReportBuilderExtensions.cs | 111 ++++ .../{ => Infrastructure}/FastReportGlobal.cs | 14 +- .../Infrastructure/FastReportMiddleware.cs | 44 ++ .../{ => Infrastructure}/FastReportOptions.cs | 18 +- ...portServiceCollectionExtensions.Backend.cs | 68 +++ .../FastReportServiceCollectionExtensions.cs | 2 +- .../Application/Infrastructure/IResult.cs | 83 +++ .../Application/LinkerFlags.cs | 107 ++++ .../Application/Toolbar/ToolbarButton.cs | 33 ++ .../Application/Toolbar/ToolbarElement.cs | 156 +++++ .../Application/Toolbar/ToolbarInput.cs | 47 ++ .../Application/Toolbar/ToolbarSelect.cs | 85 +++ .../Application/ToolbarSettings.cs | 40 +- .../Application/WebReport.Backend.cs | 15 +- FastReport.Core.Web/Application/WebReport.cs | 19 +- .../Application/WebReportHtml.Backend.cs | 1 + FastReport.Core.Web/Application/WebUtils.cs | 5 +- .../Controllers/ApiControllerBase.cs | 10 +- .../Designer/ConnectionsController.cs | 99 ++-- .../Designer/DesignerReportController.cs | 120 ++-- .../Controllers/Designer/UtilsController.cs | 135 ++--- .../Controllers/Legacy/BaseController.cs | 128 ----- .../Controllers/Legacy/DesignerController.cs | 267 --------- .../Controllers/Legacy/ReportController.cs | 190 ------ .../Controllers/Legacy/ResourceController.cs | 33 -- .../Controllers/Preview/DialogController.cs | 33 +- .../Preview/ExportReportController.cs | 82 ++- .../Preview/GetPictureController.cs | 40 +- .../Preview/GetReportController.cs | 75 ++- .../Preview/PrintReportController.cs | 45 +- .../Controllers/Preview/ServiceController.cs | 48 +- .../Resources/ResourcesController.cs | 42 +- .../FastReport.Web.Shared.props | 2 + .../Resources/default-custom-button.svg | 3 + .../Services/Abstract/IConnectionsService.cs | 2 +- .../Services/Abstract/IReportService.cs | 8 + .../Implementation/ConnectionService.cs | 30 +- .../Implementation/DesignerUtilsService.cs | 2 - .../Services/Implementation/ExportService.cs | 2 - .../Implementation/InternalResourceLoader.cs | 12 +- .../Services/Implementation/PrintService.cs | 3 +- .../Implementation/ReportDesignerService.cs | 32 -- .../Services/Implementation/ReportService.cs | 61 +- .../Implementation/TextEditService.cs | 1 - .../Services/ServicesParamsModels.cs | 26 +- FastReport.Core.Web/Templates/main.cs | 4 +- FastReport.Core.Web/Templates/outline.cs | 2 +- FastReport.Core.Web/Templates/script.cs | 23 + FastReport.Core.Web/Templates/style.cs | 9 + FastReport.Core.Web/Templates/toolbar.cs | 21 +- .../FRPrivateFontCollection.OpenSource.cs | 6 +- UsedPackages.version | 24 +- 73 files changed, 2069 insertions(+), 1511 deletions(-) delete mode 100644 FastReport.Core.Web/Application/FastReportBuilderExtensions.cs delete mode 100644 FastReport.Core.Web/Application/FastReportMiddleware.cs delete mode 100644 FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.Backend.cs create mode 100644 FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs create mode 100644 FastReport.Core.Web/Application/Infrastructure/ControllerExecutor.cs create mode 100644 FastReport.Core.Web/Application/Infrastructure/FastReportBuilderExtensions.cs rename FastReport.Core.Web/Application/{ => Infrastructure}/FastReportGlobal.cs (66%) create mode 100644 FastReport.Core.Web/Application/Infrastructure/FastReportMiddleware.cs rename FastReport.Core.Web/Application/{ => Infrastructure}/FastReportOptions.cs (77%) create mode 100644 FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.Backend.cs rename FastReport.Core.Web/Application/{ => Infrastructure}/FastReportServiceCollectionExtensions.cs (97%) create mode 100644 FastReport.Core.Web/Application/Infrastructure/IResult.cs create mode 100644 FastReport.Core.Web/Application/LinkerFlags.cs create mode 100644 FastReport.Core.Web/Application/Toolbar/ToolbarButton.cs create mode 100644 FastReport.Core.Web/Application/Toolbar/ToolbarElement.cs create mode 100644 FastReport.Core.Web/Application/Toolbar/ToolbarInput.cs create mode 100644 FastReport.Core.Web/Application/Toolbar/ToolbarSelect.cs delete mode 100644 FastReport.Core.Web/Controllers/Legacy/BaseController.cs delete mode 100644 FastReport.Core.Web/Controllers/Legacy/DesignerController.cs delete mode 100644 FastReport.Core.Web/Controllers/Legacy/ReportController.cs delete mode 100644 FastReport.Core.Web/Controllers/Legacy/ResourceController.cs create mode 100644 FastReport.Core.Web/Resources/default-custom-button.svg diff --git a/FastReport.Base/Base.cs b/FastReport.Base/Base.cs index ed85814b..98b67f7a 100644 --- a/FastReport.Base/Base.cs +++ b/FastReport.Base/Base.cs @@ -148,10 +148,11 @@ public enum Flags internal enum ObjectState : byte { None = 0, - IsAncestor = 0x01, - IsDesigning = 0x02, - IsPrinting = 0x04, - IsRunning = 0x08, + IsAncestor = 1, + IsDesigning = 2, + IsPrinting = 4, + IsRunning = 8, + IsDeserializing = 16, } /// @@ -449,6 +450,17 @@ public bool IsRunning get { return GetObjectState(ObjectState.IsRunning); } } + /// + /// Gets a value indicating whether the object is currently processed by the report engine. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + internal bool IsDeserializing + { + get { return GetObjectState(ObjectState.IsDeserializing); } + set { SetObjectState(ObjectState.IsDeserializing, value); } + } + + /// /// Gets an original component for this object. /// @@ -867,10 +879,18 @@ public virtual void Serialize(FRWriter writer) /// Reader object. public virtual void Deserialize(FRReader reader) { - reader.ReadProperties(this); - while (reader.NextItem()) + try + { + IsDeserializing = true; + reader.ReadProperties(this); + while (reader.NextItem()) + { + DeserializeSubItems(reader); + } + } + finally { - DeserializeSubItems(reader); + IsDeserializing = false; } } diff --git a/FastReport.Base/Export/Html/HTMLExportLayers.cs b/FastReport.Base/Export/Html/HTMLExportLayers.cs index ab15906e..7ddf3a28 100644 --- a/FastReport.Base/Export/Html/HTMLExportLayers.cs +++ b/FastReport.Base/Export/Html/HTMLExportLayers.cs @@ -143,7 +143,7 @@ private string GetHref(ReportComponentBase obj) EncodeURL(obj.Hyperlink.ReportParameter), EncodeURL(obj.Hyperlink.Value)); string onClick = String.Format(OnClickTemplate, ReportID, "detailed_report", url); - href = String.Format("", hrefStyle, onClick); + href = String.Format("", hrefStyle, onClick); } else if (obj.Hyperlink.Kind == HyperlinkKind.DetailPage) { @@ -152,7 +152,7 @@ private string GetHref(ReportComponentBase obj) EncodeURL(obj.Hyperlink.ReportParameter), EncodeURL(obj.Hyperlink.Value)); string onClick = String.Format(OnClickTemplate, ReportID, "detailed_page", url); - href = String.Format("", hrefStyle, onClick); + href = String.Format("", hrefStyle, onClick); } else if (SinglePage) { @@ -170,7 +170,7 @@ private string GetHref(ReportComponentBase obj) onClick = String.Format(OnClickTemplate, ReportID, "goto", url); if (onClick != String.Empty) - href = String.Format("", hrefStyle, onClick); + href = String.Format("", hrefStyle, onClick); } } return href; diff --git a/FastReport.Base/HtmlObject.cs b/FastReport.Base/HtmlObject.cs index f52cc692..1a0678c2 100644 --- a/FastReport.Base/HtmlObject.cs +++ b/FastReport.Base/HtmlObject.cs @@ -130,23 +130,6 @@ public override void Draw(FRPaintEventArgs e) DrawDesign(e); } - /// - public override void ApplyStyle(Style style) - { - base.ApplyStyle(style); - } - - /// - public override void SaveStyle() - { - base.SaveStyle(); - } - - /// - public override void RestoreStyle() - { - base.RestoreStyle(); - } /// public override void Serialize(FRWriter writer) diff --git a/FastReport.Base/ReportPage.cs b/FastReport.Base/ReportPage.cs index 15df8d5a..70f447e9 100644 --- a/FastReport.Base/ReportPage.cs +++ b/FastReport.Base/ReportPage.cs @@ -153,7 +153,7 @@ public float PaperHeight /// Gets or sets the raw index of a paper size. /// /// - /// This property stores the RawKind value of a selected papersize. It is used to distiguish + /// This property stores the RawKind value of a selected papersize. It is used to distinguish /// between several papers with the same size (for ex. "A3" and "A3 with no margins") used in some /// printer drivers. /// It is not obligatory to set this property. FastReport will select the @@ -281,6 +281,12 @@ public bool Landscape { if (landscape != value) { + landscape = value; + if (IsDeserializing) + { + return; + } + float e = paperWidth; paperWidth = paperHeight; paperHeight = e; @@ -305,7 +311,6 @@ public bool Landscape bottomMargin = m2; } } - landscape = value; } } diff --git a/FastReport.Base/Utils/Config.cs b/FastReport.Base/Utils/Config.cs index 21f53eb8..fd76b4b8 100644 --- a/FastReport.Base/Utils/Config.cs +++ b/FastReport.Base/Utils/Config.cs @@ -28,7 +28,6 @@ public static partial class Config #endif #region Private Fields - private static readonly CultureInfo engCultureInfo = new CultureInfo("en-US"); private static readonly XmlDocument FDoc = new XmlDocument(); private static readonly string version = typeof(Report).Assembly.GetName().Version.ToString(3); @@ -117,14 +116,6 @@ public static string ApplicationFolder } } - /// - /// Gets an english culture information for localization purposes - /// - public static CultureInfo EngCultureInfo - { - get { return engCultureInfo; } - } - /// /// Gets or sets the path used to load/save the configuration file. /// diff --git a/FastReport.Base/Utils/FRPrivateFontCollection.cs b/FastReport.Base/Utils/FRPrivateFontCollection.cs index 042b776e..b1424749 100644 --- a/FastReport.Base/Utils/FRPrivateFontCollection.cs +++ b/FastReport.Base/Utils/FRPrivateFontCollection.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Drawing.Text; using System.Drawing; +using System.Linq; namespace FastReport.Utils { @@ -13,8 +14,9 @@ namespace FastReport.Utils public partial class FRPrivateFontCollection { private readonly PrivateFontCollection collection = TypeConverters.FontConverter.PrivateFontCollection; - private Dictionary FontFiles = new Dictionary(); - private Dictionary MemoryFonts = new Dictionary(); + + private readonly Dictionary _fonts = new Dictionary(); + internal PrivateFontCollection Collection { get { return collection; } } @@ -30,7 +32,7 @@ public partial class FRPrivateFontCollection /// true if the font is contained in this collection. public bool HasFont(string fontName) { - return FontFiles.ContainsKey(fontName) || MemoryFonts.ContainsKey(fontName); + return _fonts.ContainsKey(fontName); } /// @@ -40,18 +42,11 @@ public bool HasFont(string fontName) /// Either FileStream or MemoryStream containing font data. public Stream GetFontStream(string fontName) { - if (FontFiles.ContainsKey(fontName)) - { - return new FileStream(FontFiles[fontName], FileMode.Open, FileAccess.Read); - } - else if (MemoryFonts.ContainsKey(fontName)) + if (_fonts.TryGetValue(fontName, out var font)) { - MemoryFont font = MemoryFonts[fontName]; - byte[] buffer = new byte[font.Length]; - Marshal.Copy(font.Memory, buffer, 0, font.Length); - return new MemoryStream(buffer); + return font.GetFontStream(); } - + return null; } @@ -65,7 +60,8 @@ public bool AddFontFile(string filename) bool success = false; if(File.Exists(filename)) { - if (!FontFiles.ContainsValue(filename)) + // if (!FontFiles.ContainsValue(filename)) + if (!_fonts.Values.OfType().Any(fontFile => fontFile._filepath == filename)) { collection.AddFontFile(filename); RegisterFontInternal(filename); @@ -91,6 +87,24 @@ public bool AddFontFile(string filename) public void AddFontFromStream(Stream stream) { collection.AddFont(stream); + stream.Position = 0; + + var fontFamily = Families[Families.Length - 1]; + string fontName = fontFamily.Name; + + var isBold = fontFamily.IsStyleAvailable(FontStyle.Bold); + // every time is false + //var isItalic = fontFamily.IsStyleAvailable(FontStyle.Italic); + + fontName = fontName + (isBold ? "-B" : "") /*+ (isItalic ? "-I" : "")*/; + + if (!_fonts.ContainsKey(fontName)) + { + var ms = new MemoryStream(); + stream.CopyTo(ms); + ms.Position = 0; + _fonts.Add(fontName, new FontFromStream(ms)); + } } #endif @@ -103,21 +117,71 @@ public void AddMemoryFont(IntPtr memory, int length) { collection.AddMemoryFont(memory, length); string fontName = Families[Families.Length - 1].Name; - if (!FontFiles.ContainsKey(fontName)) - MemoryFonts.Add(fontName, new MemoryFont(memory, length)); + if (!_fonts.ContainsKey(fontName)) + _fonts.Add(fontName, new MemoryFont(memory, length)); } - private struct MemoryFont + private abstract class DictionaryFont { - public readonly IntPtr Memory; - public readonly int Length; + public abstract Stream GetFontStream(); + } + + private sealed class FontFromFile : DictionaryFont + { + internal readonly string _filepath; + + public FontFromFile(string filepath) + { + _filepath = filepath; + } + + public override Stream GetFontStream() + { + return new FileStream(_filepath, FileMode.Open, FileAccess.Read); + } + + public override string ToString() + { + return _filepath; + } + } + + private sealed class FontFromStream : DictionaryFont + { + private readonly Stream _stream; + + public FontFromStream(Stream stream) + { + _stream = stream; + } + + public override Stream GetFontStream() + { + var newStream = new MemoryStream(); + _stream.CopyTo(newStream); + _stream.Position = 0; + newStream.Position = 0; + return newStream; + } + } + + private sealed class MemoryFont : DictionaryFont + { + private readonly IntPtr Memory; + private readonly int Length; public MemoryFont(IntPtr memory, int length) { Memory = memory; Length = length; } - } + public override Stream GetFontStream() + { + byte[] buffer = new byte[Length]; + Marshal.Copy(Memory, buffer, 0, Length); + return new MemoryStream(buffer); + } + } } } diff --git a/FastReport.Base/Utils/Json/Serialization/JsonConverter.cs b/FastReport.Base/Utils/Json/Serialization/JsonConverter.cs index 211b998d..47ff594c 100644 --- a/FastReport.Base/Utils/Json/Serialization/JsonConverter.cs +++ b/FastReport.Base/Utils/Json/Serialization/JsonConverter.cs @@ -15,6 +15,11 @@ public static T Deserialize(string json) return JsonDeserializer.Deserialize(json); } + public static object Deserialize(string json, Type type) + { + return JsonDeserializer.Deserialize(json, type); + } + public static string Serialize(T instance) { return JsonSerializer.Serialize(instance); diff --git a/FastReport.Base/Utils/Json/Serialization/JsonDeserializer.cs b/FastReport.Base/Utils/Json/Serialization/JsonDeserializer.cs index a6c9c446..576cd5f2 100644 --- a/FastReport.Base/Utils/Json/Serialization/JsonDeserializer.cs +++ b/FastReport.Base/Utils/Json/Serialization/JsonDeserializer.cs @@ -24,6 +24,13 @@ public static T Deserialize(string json) return instance; } + public static object Deserialize(string json, Type type) + { + var instance = CreateInstance(type); + DeserializeProperties(instance, json); + return instance; + } + private static object Deserialize(JsonBase jsonBase, Type type) { var instance = CreateInstance(type); diff --git a/FastReport.Base/Utils/Res.cs b/FastReport.Base/Utils/Res.cs index 0abcefc5..e7e0566f 100644 --- a/FastReport.Base/Utils/Res.cs +++ b/FastReport.Base/Utils/Res.cs @@ -85,7 +85,15 @@ private static void LoadBuiltinLocale() using (Stream stream = ResourceLoader.GetStream("en.xml")) { FLocale.Load(stream); - CultureInfo enCulture = CultureInfo.GetCultureInfo("en"); + CultureInfo enCulture; + try + { + enCulture = CultureInfo.GetCultureInfo("en"); + } + catch // InvariantGlobalization mod fix (#939) + { + enCulture = CultureInfo.InvariantCulture; + } CurrentCulture = enCulture; if (!LocalesCache.ContainsKey(enCulture)) LocalesCache.Add(enCulture, FLocale); diff --git a/FastReport.Core.Web/Application/Cache/CacheOptions.cs b/FastReport.Core.Web/Application/Cache/CacheOptions.cs index 21224d0f..a1394b00 100644 --- a/FastReport.Core.Web/Application/Cache/CacheOptions.cs +++ b/FastReport.Core.Web/Application/Cache/CacheOptions.cs @@ -8,26 +8,13 @@ namespace FastReport.Web.Cache { public class CacheOptions { - private bool _useLegacyWebReportCache = true; - private TimeSpan _cacheDuration = TimeSpan.FromMinutes(DEFAULT_TIMER_MINUTES); - - internal bool IsChangedByUser { get; private set; } = false; - private const int DEFAULT_TIMER_MINUTES = 15; /// /// Use legacy WebReport cache instead of . /// Default value: true /// - public bool UseLegacyWebReportCache - { - get => _useLegacyWebReportCache; - set - { - _useLegacyWebReportCache = value; - IsChangedByUser = true; - } - } + public bool UseLegacyWebReportCache { get; set; } = true; //public bool UseDistributedCache { get; set; } = false; @@ -40,14 +27,6 @@ public bool UseLegacyWebReportCache /// If report is not used the specified time and there is no references to the object, it will be deleted from cache. /// Default value: "15". /// - public TimeSpan CacheDuration - { - get => _cacheDuration; - set - { - _cacheDuration = value; - IsChangedByUser = true; - } - } + public TimeSpan CacheDuration { get; set; } = TimeSpan.FromMinutes(DEFAULT_TIMER_MINUTES); } } diff --git a/FastReport.Core.Web/Application/Cache/WebReportCache.cs b/FastReport.Core.Web/Application/Cache/WebReportCache.cs index 595bce70..94936ab9 100644 --- a/FastReport.Core.Web/Application/Cache/WebReportCache.cs +++ b/FastReport.Core.Web/Application/Cache/WebReportCache.cs @@ -3,7 +3,7 @@ namespace FastReport.Web.Cache { /// - /// For backward compability + /// For backward compatibility /// internal static class WebReportCache { @@ -13,18 +13,16 @@ public static IWebReportCache Instance { get { - if (_instance == null) - { - if (FastReportGlobal.FastReportOptions.CacheOptions.UseLegacyWebReportCache) - _instance = new WebReportLegacyCache(); - else - _instance = new WebReportMemoryCache(new MemoryCache(new MemoryCacheOptions())); - - } return _instance; } + internal set + { + _instance = value; + } } + internal static MemoryCache GetDefaultMemoryCache() => new MemoryCache(new MemoryCacheOptions()); + } } diff --git a/FastReport.Core.Web/Application/Cache/WebReportDistributedCache.cs b/FastReport.Core.Web/Application/Cache/WebReportDistributedCache.cs index 2258ee9c..38593fb0 100644 --- a/FastReport.Core.Web/Application/Cache/WebReportDistributedCache.cs +++ b/FastReport.Core.Web/Application/Cache/WebReportDistributedCache.cs @@ -19,14 +19,14 @@ internal class WebReportDistributedCache : IWebReportCache private readonly IDistributedCache _cache; private readonly DistributedCacheEntryOptions _cacheEntryOptions; - private static readonly IFormatter _bf = new BinaryFormatter(); + //private static readonly IFormatter _bf = new BinaryFormatter(); - public WebReportDistributedCache(IDistributedCache cache) + public WebReportDistributedCache(IDistributedCache cache, CacheOptions cacheOptions) { _cache = cache; _cacheEntryOptions = new DistributedCacheEntryOptions() { - SlidingExpiration = FastReportGlobal.FastReportOptions.CacheOptions.CacheDuration + SlidingExpiration = cacheOptions.CacheDuration }; } @@ -66,19 +66,22 @@ public void Dispose() private static byte[] WebReportToBytes(WebReport value) { - var ms = new MemoryStream(); - _bf.Serialize(ms, value); - return ms.ToArray(); + throw new NotImplementedException(); + + //var ms = new MemoryStream(); + //_bf.Serialize(ms, value); + //return ms.ToArray(); } private static WebReport BytesToWebReport(byte[] value) { - using (var ms = new MemoryStream(value, false)) - { - var obj = _bf.Deserialize(ms); - var webReport = obj as WebReport; - return webReport; - } + throw new NotImplementedException(); + //using (var ms = new MemoryStream(value, false)) + //{ + // var obj = _bf.Deserialize(ms); + // var webReport = obj as WebReport; + // return webReport; + //} } } diff --git a/FastReport.Core.Web/Application/Cache/WebReportLegacyCache.cs b/FastReport.Core.Web/Application/Cache/WebReportLegacyCache.cs index 02be6139..21becbc9 100644 --- a/FastReport.Core.Web/Application/Cache/WebReportLegacyCache.cs +++ b/FastReport.Core.Web/Application/Cache/WebReportLegacyCache.cs @@ -12,17 +12,30 @@ namespace FastReport.Web.Cache /// internal sealed class WebReportLegacyCache : IWebReportCache { + public WebReportLegacyCache(CacheOptions cacheOptions) + { + _cacheOptions = cacheOptions; + } sealed class CacheItem : IDisposable { - internal Timer Timer; + internal readonly Timer Timer; internal WebReport WebReport; internal readonly WeakReference WeakReference; - public CacheItem(WebReport webReport) + public CacheItem(WebReport webReport, TimeSpan dueTime) { WebReport = webReport; WeakReference = new WeakReference(webReport); + Timer = new Timer(TimerAction, this, dueTime, Timeout.InfiniteTimeSpan); + } + + private static void TimerAction(object state) + { + // clear reference to object + var _item = (CacheItem)state; + Debug.WriteLine($"Timer action! {_item.WebReport.ID} is null now!"); + _item.WebReport = null; } public void Dispose() @@ -33,7 +46,8 @@ public void Dispose() } } - readonly List cache = new List(); + private readonly List cache = new List(); + private readonly CacheOptions _cacheOptions; public void Add(WebReport webReport) { @@ -49,16 +63,7 @@ public void Add(WebReport webReport) return; } - var item = new CacheItem(webReport); - - item.Timer = new Timer(state => - { - // clear reference to object - var _item = (CacheItem)state; - Debug.WriteLine($"Timer action! {_item.WebReport.ID} is null now!"); - _item.WebReport = null; - }, - item, FastReportGlobal.FastReportOptions.CacheOptions.CacheDuration, Timeout.InfiniteTimeSpan); + var item = new CacheItem(webReport, _cacheOptions.CacheDuration); cache.Add(item); } @@ -85,7 +90,7 @@ private CacheItem FindWithRefresh(string id) { // refresh item cacheItem.WebReport = target; - cacheItem.Timer.Change(FastReportGlobal.FastReportOptions.CacheOptions.CacheDuration, Timeout.InfiniteTimeSpan); + cacheItem.Timer.Change(_cacheOptions.CacheDuration, Timeout.InfiniteTimeSpan); } } return cacheItem; diff --git a/FastReport.Core.Web/Application/Cache/WebReportMemoryCache.cs b/FastReport.Core.Web/Application/Cache/WebReportMemoryCache.cs index eb6d8e58..751a42fa 100644 --- a/FastReport.Core.Web/Application/Cache/WebReportMemoryCache.cs +++ b/FastReport.Core.Web/Application/Cache/WebReportMemoryCache.cs @@ -17,7 +17,7 @@ internal sealed class WebReportMemoryCache : IWebReportCache private readonly MemoryCacheEntryOptions _memoryCacheEntryOptions; - public WebReportMemoryCache(IMemoryCache cache) + public WebReportMemoryCache(IMemoryCache cache, CacheOptions cacheOptions) { _cache = cache; @@ -27,7 +27,7 @@ public WebReportMemoryCache(IMemoryCache cache) }; _memoryCacheEntryOptions = new MemoryCacheEntryOptions { - SlidingExpiration = FastReportGlobal.FastReportOptions.CacheOptions.CacheDuration, + SlidingExpiration = cacheOptions.CacheDuration, }; _memoryCacheEntryOptions.PostEvictionCallbacks.Add(callback); } diff --git a/FastReport.Core.Web/Application/ExportMenuSettings.cs b/FastReport.Core.Web/Application/ExportMenuSettings.cs index 0db0d6c5..2003bd32 100644 --- a/FastReport.Core.Web/Application/ExportMenuSettings.cs +++ b/FastReport.Core.Web/Application/ExportMenuSettings.cs @@ -6,17 +6,10 @@ namespace FastReport.Web public class ExportMenuSettings { -#if WASM - /// - /// Show Exports menu. Not supported in Wasm at the moment - /// - public bool Show { get => false; set => throw new NotSupportedException("Not supported in Wasm at the moment"); } -#else /// /// Show Exports menu /// public bool Show { get; set; } = true; -#endif /// /// Used to set exports in toolbar. diff --git a/FastReport.Core.Web/Application/ExportsHelper.cs b/FastReport.Core.Web/Application/ExportsHelper.cs index a0e69959..4dba7219 100644 --- a/FastReport.Core.Web/Application/ExportsHelper.cs +++ b/FastReport.Core.Web/Application/ExportsHelper.cs @@ -5,6 +5,7 @@ using FastReport.Export.Html; using FastReport.Export.Image; using System.Reflection; +using System.Diagnostics.CodeAnalysis; #if !OPENSOURCE using FastReport.Export.Csv; using FastReport.Export.OoXML; @@ -138,6 +139,7 @@ internal readonly struct ExportInfo { public readonly string Extension; public readonly Exports Export; + [DynamicallyAccessedMembers(LinkerFlags.ExportTypeMembers)] public readonly Type ExportType; public readonly bool HaveSettings; @@ -154,7 +156,7 @@ public ExportBase CreateExport() return null; } - public ExportInfo(string extension, Exports export, Type exportType, bool haveSettings) + public ExportInfo(string extension, Exports export, [DynamicallyAccessedMembers(LinkerFlags.ExportTypeMembers)] Type exportType, bool haveSettings) { Extension = extension; ExportType = exportType; diff --git a/FastReport.Core.Web/Application/FastReportBuilderExtensions.cs b/FastReport.Core.Web/Application/FastReportBuilderExtensions.cs deleted file mode 100644 index 2350ff8c..00000000 --- a/FastReport.Core.Web/Application/FastReportBuilderExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -#if !WASM -using System; -using FastReport.Web; -using Microsoft.AspNetCore.Routing; -using System.Net.Http; -using Microsoft.AspNetCore.Hosting; -using System.Diagnostics; - -namespace Microsoft.AspNetCore.Builder -{ - public static class FastReportBuilderExtensions - { - public static IApplicationBuilder UseFastReport(this IApplicationBuilder app, Action setupAction = null) - { - var options = new FastReportOptions(); - setupAction?.Invoke(options); - - FastReportGlobal.FastReportOptions = options; - if (FastReportGlobal.InternalCacheOptions != null && FastReportGlobal.InternalCacheOptions.IsChangedByUser) - { - FastReportGlobal.FastReportOptions.CacheOptions = FastReportGlobal.InternalCacheOptions; - } - - if (FastReportOptions.UseNewControllers) - { - Debug.WriteLine("Application is using new controllers"); - return app; // Do nothing - } - // fallback - - - FastReport.Utils.Config.WebMode = true; - - // TODO: find better way to share global objects -#if BLAZOR - FastReportGlobal.HostingEnvironment = (IWebHostEnvironment)app.ApplicationServices.GetService(typeof(IWebHostEnvironment)); -#else - FastReportGlobal.HostingEnvironment = (IHostingEnvironment)app.ApplicationServices.GetService(typeof(IHostingEnvironment)); -#endif - - - return app.UseMiddleware(); - } - } -} -#endif \ No newline at end of file diff --git a/FastReport.Core.Web/Application/FastReportMiddleware.cs b/FastReport.Core.Web/Application/FastReportMiddleware.cs deleted file mode 100644 index d18a6d69..00000000 --- a/FastReport.Core.Web/Application/FastReportMiddleware.cs +++ /dev/null @@ -1,68 +0,0 @@ -#if !WASM -using FastReport.Web.Controllers; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Routing; -using System; -using System.Threading.Tasks; - -namespace FastReport.Web -{ - class FastReportMiddleware - { - private readonly RequestDelegate next; - - Func> ExecuteFunc; - - public FastReportMiddleware(RequestDelegate next) - { - this.next = next; - ExecuteFunc = RequestFastReportControllerStart; - } - - public async Task Invoke(HttpContext httpContext) - { - if (!(await ExecuteFunc(httpContext))) - await next(httpContext); - } - - private Task RequestFastReportControllerStart(HttpContext httpContext) - { - FastReportGlobal.FastReportOptions.RoutePathBaseRoot = httpContext.Request.PathBase; - ExecuteFunc = RequestFastReportController; - return RequestFastReportController(httpContext); - } - - private async Task RequestFastReportController(HttpContext httpContext) - { - if (!httpContext.Request.Path.StartsWithSegments(FastReportGlobal.FastReportOptions.RouteBasePath)) - return false; - - if (!(FastReportGlobal.FastReportOptions.Authorize?.Invoke(httpContext.Request) ?? true)) - { - await new UnauthorizedResult().ExecuteResultAsync(new ActionContext(httpContext, new RouteData(), new ActionDescriptor())); - return true; - } - - // create new controllers on each request - var controllers = new BaseController[] - { - new ResourceController(), - new ReportController(), -#if DESIGNER - new DesignerController(), -#endif - }; - - foreach (var controller in controllers) - { - if (await controller.OnRequest(httpContext)) - return true; - } - - return false; - } - } -} -#endif \ No newline at end of file diff --git a/FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.Backend.cs b/FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.Backend.cs deleted file mode 100644 index 0e850376..00000000 --- a/FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.Backend.cs +++ /dev/null @@ -1,66 +0,0 @@ -using FastReport.Code; -using FastReport.Web; -using FastReport.Web.Services; - -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.Extensions.DependencyInjection.Extensions; - -using System; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static partial class FastReportServiceCollectionExtensions - { - - private static void ConfigureApplicationPart(IServiceCollection services, WebReportOptions options) - { - var manager = GetServiceFromCollection(services); - if (manager != null) - { - var curAssembly = typeof(FastReportServiceCollectionExtensions).Assembly; - var curAssemblyName = curAssembly.GetName().Name; - - var frAppPart = manager.ApplicationParts.FirstOrDefault(apPart => apPart.Name == curAssemblyName); - if (frAppPart == null) - { - // Register this assembly as part of user application - var assemblyPart = new AssemblyPart(curAssembly); - manager.ApplicationParts.Add(assemblyPart); - } - - -#if DEBUG // TODO: fix - // Then we can use new controllers - FastReportOptions.UseNewControllers = true; -#endif - - AddFastReportWebServices(services, options); - } - - FastReportGlobal.InternalCacheOptions = options.CacheOptions; - } - - private static void AddFastReportWebServices(IServiceCollection services, WebReportOptions options) - { - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); -#if DESIGNER - services.TryAddSingleton(); -#endif - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - } - - // possible memory leaks, be careful! - private static T GetServiceFromCollection(IServiceCollection services) - { - return (T)(GetServiceDescriptorFromCollection(services)?.ImplementationInstance); - } - } -} diff --git a/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs b/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs new file mode 100644 index 00000000..9cb77e9f --- /dev/null +++ b/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs @@ -0,0 +1,541 @@ +#if !WASM +using System; +using Microsoft.AspNetCore.Http; +using System.Reflection; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Routing; +using System.Globalization; +using System.Diagnostics; +using Microsoft.AspNetCore.Routing.Template; +using Microsoft.AspNetCore.Routing; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace FastReport.Web.Infrastructure +{ + internal static partial class ControllerBuilder + { + private static ControllerExecutor _executor; + + public static MethodInfo[] GetControllerMethods() + { + return GetControllerMethods(typeof(Controllers.Controllers)); + } + + internal static MethodInfo[] GetControllerMethods(Type fromType) + { + return fromType.GetMethods(BindingFlags.Public | BindingFlags.Static); + } + + + public static Delegate BuildMinimalAPIDelegate(MethodInfo method) + { + var methodParams = method.GetParameters().Select(param => param.ParameterType).ToList(); + var returnType = method.ReturnType; + + Type delegateType; + switch (methodParams.Count + 1) // plus returnType + { + case 1: + if (returnType == typeof(void)) + delegateType = typeof(Action); + else + delegateType = typeof(Func<>); + break; + + case 2: + delegateType = typeof(Func<,>); + break; + + case 3: + delegateType = typeof(Func<,,>); + break; + + case 4: + delegateType = typeof(Func<,,,>); + break; + + case 5: + delegateType = typeof(Func<,,,,>); + break; + + case 6: + delegateType = typeof(Func<,,,,,>); + break; + + case 7: + delegateType = typeof(Func<,,,,,,>); + break; + + case 8: + delegateType = typeof(Func<,,,,,,,>); + break; + + case 9: + delegateType = typeof(Func<,,,,,,,,>); + break; + + case 10: + delegateType = typeof(Func<,,,,,,,,,>); + break; + + default: + throw new NotImplementedException(); + } + + if (methodParams.Count > 0) + { + methodParams.Add(returnType); + delegateType = delegateType.MakeGenericType(methodParams.ToArray()); + } + + return Delegate.CreateDelegate(delegateType, method); + } + + public static ControllerExecutor Executor => _executor; + + public static void InitializeControllers() + { + var methods = GetControllerMethods(); + + InitializeControllers(methods); + } + + internal static void InitializeControllers(MethodInfo[] methods) + { + var endpoints = BuildEndpoints(methods); + _executor = new ControllerExecutor(endpoints); + } + + + internal static EndpointInfo[] BuildEndpoints(IReadOnlyCollection methods) + { + //var availableServices = GetFastReportServices(); + + List endpoints = new List(); + foreach (var method in methods) + { + var endpoint = BuildEndpoint(method); + endpoints.AddRange(endpoint); + } + + return endpoints.ToArray(); + } + + + internal static IEnumerable BuildEndpoint(MethodInfo method) + { + var httpMethodAttribute = method.GetCustomAttribute(); + string routeTemplate = httpMethodAttribute?.Template ?? method.GetCustomAttribute()?.Template; + + if (routeTemplate == null) + throw new Exception($"Method '{method.Name}' doesn't have a route template. Please, use RouteAttribute or Http[Get|Post|...]Attribute with route template"); + + var parameters = new List>(); + var parameterInfos = method.GetParameters(); + for (int i = 0; i < parameterInfos.Length; i++) + { + var parameterInfo = parameterInfos[i]; + + var paramType = parameterInfo.ParameterType; + + // if FromQuery/FromRoute/FromBody etc. + if (TryAttributesSearching(parameters, parameterInfo, paramType)) + continue; + + // if knownTypes: + if (TryKnownTypesSearching(parameters, parameterInfo.Name, paramType)) + continue; + + // if exist in serviceProvider + parameters.Add((httpContext) => httpContext.RequestServices.GetService(paramType)); + continue; + } + + var resultHandler = BuildResultHandler(method.ReturnType); + + var templateMatcher = new TemplateMatcher(TemplateParser.Parse(WebUtils.ToUrl(FastReportGlobal.FastReportOptions.RouteBasePath, routeTemplate).TrimStart('/')), new RouteValueDictionary()); + + if(httpMethodAttribute != null) + foreach(var httpMethod in httpMethodAttribute.HttpMethods) + { + yield return new EndpointInfo(httpMethod, + templateMatcher, + method, + parameters.ToArray(), + resultHandler); + } + else + { + // if HttpMethodAttribute doesn't exists => it's GET + yield return new EndpointInfo("GET", + templateMatcher, + method, + parameters.ToArray(), + resultHandler); + } + } + + private static Func BuildResultHandler(Type returnType) + { + PropertyInfo resultProperty; + if(returnType.IsGenericType) + { + var generticType = returnType.GetGenericTypeDefinition(); + if (generticType == typeof(Task<>)) + { + resultProperty = returnType.GetProperty(nameof(Task.Result)); + return async (httpContext, methodResult) => + { + await (Task)methodResult; + var result = resultProperty.GetValue(methodResult); + await BuilderTemplates.HandleResult(httpContext, result); + }; + } + else if (generticType == typeof(ValueTask<>)) + { + resultProperty = returnType.GetProperty(nameof(ValueTask.Result)); + return async (httpContext, methodResult) => + { + // TODO: NOT WORKING + await (ValueTask)methodResult; + var result = resultProperty.GetValue(methodResult); + await BuilderTemplates.HandleResult(httpContext, result); + }; + } + } + else if (returnType == typeof(Task)) + { + return BuilderTemplates.Task_AwaitAndHandleResult; + } + else if (returnType == typeof(ValueTask)) + { + return BuilderTemplates.ValueTask_AwaitAndHandleResult; + } + + return BuilderTemplates.HandleResult; + } + + internal static class BuilderTemplates + { + private static readonly IResult _SuccessResult = Results.Ok(); + + public static async Task Task_AwaitAndHandleResult(HttpContext httpContext, object methodResult) + { + await(Task)methodResult; + await HandleResult(httpContext, null); + } + + public static async Task ValueTask_AwaitAndHandleResult(HttpContext httpContext, object methodResult) + { + await (ValueTask)methodResult; + await HandleResult(httpContext, null); + } + + public static Task HandleResult(HttpContext httpContext, object result) + { + if (result == null) + { + return _SuccessResult.ExecuteAsync(httpContext); + } + + if (result is IResult iResult) + { + return iResult.ExecuteAsync(httpContext); + } + else + { + Debug.Assert(result.GetType() != typeof(IActionResult)); + +#if NETCOREAPP3_1_OR_GREATER + string content = System.Text.Json.JsonSerializer.Serialize(result); +#else + string content = result.ToString(); +#endif + var contentResult = Results.Content(content, "text/json"); + return contentResult.ExecuteAsync(httpContext); + } + } + } + + + + + private static bool TryKnownTypesSearching(List> parameters, string paramName, Type paramType) + { + if (paramType == typeof(HttpContext)) + { + parameters.Add(static (httpContext) => httpContext); + return true; + } + else if (paramType == typeof(HttpRequest)) + { + parameters.Add(static (httpContext) => httpContext.Request); + return true; + } + else if (paramType == typeof(HttpResponse)) + { + parameters.Add(static (httpContext) => httpContext.Response); + return true; + } + else if (paramType == typeof(CancellationToken)) + { + parameters.Add(static (httpContext) => httpContext.RequestAborted); + return true; + } + else if (paramType == typeof(IServiceProvider)) + { + parameters.Add(static (httpContext) => httpContext.RequestServices); + return true; + } + // Special case: we check primitive types + else if (paramType.IsSimpleType()) + { + parameters.Add((httpContext) => + { + var request = httpContext.Request; + if (request.Query.TryGetValue(paramName, out var queryValue)) + return ParseToType(queryValue, paramType); + + if (request.Headers.TryGetValue(paramName, out var headerValue)) + return ParseToType(headerValue, paramType); + +#if NETCOREAPP3_1_OR_GREATER + if (request.RouteValues.TryGetValue(paramName, out var routeValue)) + return ParseToType(routeValue, paramType); +#endif + + if (request.Form.TryGetValue(paramName, out var formValue)) + return ParseToType(formValue, paramType); + + return default; + }); + return true; + } + + return false; + } + + private static bool TryAttributesSearching(List> parameters, ParameterInfo parameterInfo, Type paramType) + { + var attributes = parameterInfo.GetCustomAttributes(false); + foreach (var attribute in attributes) + { + Func func; + if (attribute is FromQueryAttribute fromQuery) + { + string name = fromQuery.Name ?? parameterInfo.Name; + if (paramType.IsSimpleType()) + func = (httpContext) => + { + var queryValue = httpContext.Request.Query[name].ToString(); + return ParseToType(queryValue, paramType); + }; + else + func = (httpContext) => + { + return DeserializeFromCollection(httpContext.Request.Query, paramType); + }; + + parameters.Add(func); + return true; + } + else if (attribute is FromHeaderAttribute fromHeader) + { + string name = fromHeader.Name ?? parameterInfo.Name; + if (paramType.IsSimpleType()) + func = (httpContext) => + { + var queryValue = httpContext.Request.Headers[name].ToString(); + return ParseToType(queryValue, paramType); + }; + else + func = (httpContext) => + { + return DeserializeFromCollection(httpContext.Request.Headers, paramType); + }; + + parameters.Add(func); + return true; + } +#if NETCOREAPP3_1_OR_GREATER // not available in .Net Core 2 + else if (attribute is FromRouteAttribute fromRoute) + { + string name = fromRoute.Name ?? parameterInfo.Name; + if (paramType.IsSimpleType()) + func = (httpContext) => + { + var queryValue = httpContext.Request.RouteValues[name].ToString(); + return ParseToType(queryValue, paramType); + }; + else + func = (httpContext) => + { + return DeserializeFromCollection(httpContext.Request.RouteValues, paramType); + }; + + parameters.Add(func); + return true; + } +#endif + else if (attribute is FromServicesAttribute) + { + parameters.Add((httpContext) => + { + return httpContext.RequestServices.GetService(paramType); + }); + return true; + } + } + return false; + } + + private static object DeserializeFromCollection(IDictionary dictionary, Type paramType) + { + var result = Activator.CreateInstance(paramType); + var properties = paramType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); + foreach (var property in properties) + { + if (dictionary.TryGetValue(property.Name, out var value)) + { + var parsedValue = ParseToType(value.ToString(), property.PropertyType); + property.SetValue(result, parsedValue); + } + } + + return result; + } + + private static object DeserializeFromCollection(IQueryCollection dictionary, Type paramType) + { + var result = Activator.CreateInstance(paramType); + // TODO: test with init-only fields + var properties = paramType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); + foreach (var property in properties) + { + if (dictionary.TryGetValue(property.Name, out var value)) + { + var parsedValue = ParseToType(value.ToString(), property.PropertyType); + property.SetValue(result, parsedValue); + } + } + + return result; + } + + private static object DeserializeFromCollection(IHeaderDictionary dictionary, Type paramType) + { + var result = Activator.CreateInstance(paramType); + var properties = paramType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); + foreach (var property in properties) + { + if (dictionary.TryGetValue(property.Name, out var value)) + { + var parsedValue = ParseToType(value.ToString(), property.PropertyType); + property.SetValue(result, parsedValue); + } + } + + return result; + } + + private static object ParseToType(string value, Type type) + { + object result; + if (type.IsPrimitive || type == typeof(string)) + { + result = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + else // if (type.IsEnum) + { + result = Enum.Parse(type, value); + } + + return result; + } + + private static object ParseToType(object value, Type type) + { + object result; + if (type.IsPrimitive || type == typeof(string)) + { + result = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + else // if (type.IsEnum) + { + result = Enum.Parse(type, value.ToString()); + } + + return result; + } + + [DebuggerDisplay("{Method.Name} [{Parameters.Length}], {HttpMethod} {Route.Template}")] + internal readonly struct EndpointInfo + { + public readonly string HttpMethod; + + public readonly TemplateMatcher Route; + + public readonly Func[] Parameters; + + public readonly MethodInfo Method; + + public readonly Func Handler; + + public EndpointInfo(string httpMethod, TemplateMatcher route, MethodInfo method, + Func[] parameters, + Func handler) + { + HttpMethod = httpMethod; + Route = route; + Method = method; + Parameters = parameters; + Handler = handler; + } + } + + [DebuggerDisplay("{Method.Name} [{Parameters.Length}], {HttpMethod} {Route.Template}")] + internal readonly struct BuiltEndpointInfo + { + public readonly string HttpMethod; + + public readonly TemplateMatcher Route; + + public readonly Func Method; + + + public BuiltEndpointInfo(string httpMethod, TemplateMatcher route, Func method) + { + HttpMethod = httpMethod; + Route = route; + Method = method; + } + } + + + //private static Type[] GetFastReportServices() + //{ + // var types = typeof(ControllerBuilder).Assembly.GetTypes(); + // var services = new List(); + // foreach (var type in types) + // { + // if(type.IsInterface && type.IsPublic) + // { + // services.Add(type); + // } + // } + // return services.ToArray(); + //} + } + + internal static class TypeExtensions + { + public static bool IsSimpleType(this Type type) + => type.IsPrimitive || type.IsEnum || type == typeof(string); + } +} +#endif diff --git a/FastReport.Core.Web/Application/Infrastructure/ControllerExecutor.cs b/FastReport.Core.Web/Application/Infrastructure/ControllerExecutor.cs new file mode 100644 index 00000000..87931b4d --- /dev/null +++ b/FastReport.Core.Web/Application/Infrastructure/ControllerExecutor.cs @@ -0,0 +1,74 @@ +#if !WASM +using System; +using Microsoft.AspNetCore.Http; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using static FastReport.Web.Infrastructure.ControllerBuilder; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; + +namespace FastReport.Web.Infrastructure +{ + internal sealed class ControllerExecutor + { + private readonly EndpointInfo[] _endpoints; + + public ControllerExecutor(EndpointInfo[] endpoints) + { + _endpoints = endpoints; + } + + public async ValueTask ExecuteAsync(HttpContext httpContext) + { + bool success = false; + var endpoint = FindEndpoint(httpContext.Request); + + if (endpoint.Method != null) // Found + { + await ExecuteEndpoint(endpoint, httpContext); + success = true; + } + + return success; + } + + internal static async Task ExecuteEndpoint(EndpointInfo endpoint, HttpContext httpContext) + { + // TODO: create scope ? + //using var scope = httpContext.RequestServices.CreateScope(); + + object[] arguments = ResolveDependencies(endpoint, httpContext); + + var returnValue = endpoint.Method.Invoke(null, arguments); + + await endpoint.Handler(httpContext, returnValue); + } + + private EndpointInfo FindEndpoint(HttpRequest httpRequest) + { + var path = httpRequest.Path; + + var routeValues = new RouteValueDictionary(); + var endpoints = _endpoints.Where(endpoint => + endpoint.HttpMethod == httpRequest.Method && + endpoint.Route.TryMatch(path, routeValues) + ); + + return endpoints.FirstOrDefault(); + } + + private static object[] ResolveDependencies(EndpointInfo endpoint, HttpContext httpContext) + { + object[] arguments = new object[endpoint.Parameters.Length]; + for (int i = 0; i < arguments.Length; i++) + { + var param = endpoint.Parameters[i]; + arguments[i] = param.Invoke(httpContext); + } + return arguments; + } + } + +} +#endif diff --git a/FastReport.Core.Web/Application/Infrastructure/FastReportBuilderExtensions.cs b/FastReport.Core.Web/Application/Infrastructure/FastReportBuilderExtensions.cs new file mode 100644 index 00000000..c775ed92 --- /dev/null +++ b/FastReport.Core.Web/Application/Infrastructure/FastReportBuilderExtensions.cs @@ -0,0 +1,111 @@ +#if !WASM +using System; +using System.Diagnostics; +using FastReport.Web; +using FastReport.Web.Cache; +using FastReport.Web.Services; +using FastReport.Web.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Hosting; +using System.Reflection; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Builder +{ + public static class FastReportBuilderExtensions + { + public static IApplicationBuilder UseFastReport(this IApplicationBuilder app, Action setupAction = null) + { + var options = SetupFastReport(setupAction, app.ApplicationServices); + + ControllerBuilder.InitializeControllers(); + + return app.UseMiddleware(); + } + + private static FastReportOptions SetupFastReport(Action setupAction, IServiceProvider serviceProvider) + { + FastReportServicesCheck(serviceProvider); + + var options = new FastReportOptions(); + setupAction?.Invoke(options); + + FastReport.Utils.Config.WebMode = true; + + // because WebReport..ctor adds WebReport instances to WebReportCache without DI + WebReportCache.Instance = serviceProvider.GetService(); + + // TODO: find better way to share global objects +#if BLAZOR + FastReportGlobal.HostingEnvironment = serviceProvider.GetService(); +#else + FastReportGlobal.HostingEnvironment = serviceProvider.GetService(); +#endif + WebReport.ResourceLoader = serviceProvider.GetService(); + + return options; + } + +#if false + public static WebApplication UseFastReport(this WebApplication app, Action setupAction = null) + { + var options = SetupFastReport(setupAction, app.Services); + + return UseMinimalApiRouting(app, options); + } + + private static WebApplication UseMinimalApiRouting(WebApplication app, FastReportOptions options) + { + var methods = ControllerBuilder.GetControllerMethods(); + foreach (var method in methods) + { + var @delegate = ControllerBuilder.BuildMinimalAPIDelegate(method); + + var httpMethod = method.GetCustomAttribute(); + + if (httpMethod == null) + throw new Exception($"There isn't any 'HttpMethodAttribute' in this method {method.Name}"); + + var path = WebUtils.ToUrl(options.RouteBasePath, httpMethod.Template); + + RouteHandlerBuilder builder; + if (httpMethod is HttpGetAttribute) + { + builder = app.MapGet(path, @delegate); + } + else if (httpMethod is HttpPostAttribute) + { + builder = app.MapPost(path, @delegate); + } + else if (httpMethod is HttpPutAttribute) + { + builder = app.MapPut(path, @delegate); + } + else if (httpMethod is HttpDeleteAttribute) + { + builder = app.MapDelete(path, @delegate); + } + else + throw new NotSupportedException(); + + builder.ExcludeFromDescription() // exclude from Swagger (optional) + .AllowAnonymous(); // disable user authorization for this endpoint + } + return app; + } +#endif + + private static void FastReportServicesCheck(IServiceProvider serviceProvider) + { + const string HAVE_TO_REGISTER_SERVICES = "Please, register FastReport services in DI container. Use services.AddFastReport()"; + + var _ = serviceProvider.GetService() ?? throw new Exception(HAVE_TO_REGISTER_SERVICES); + } + + } +} +#endif diff --git a/FastReport.Core.Web/Application/FastReportGlobal.cs b/FastReport.Core.Web/Application/Infrastructure/FastReportGlobal.cs similarity index 66% rename from FastReport.Core.Web/Application/FastReportGlobal.cs rename to FastReport.Core.Web/Application/Infrastructure/FastReportGlobal.cs index 3347f89e..67caea4b 100644 --- a/FastReport.Core.Web/Application/FastReportGlobal.cs +++ b/FastReport.Core.Web/Application/Infrastructure/FastReportGlobal.cs @@ -2,25 +2,19 @@ using FastReport.Web.Cache; using Microsoft.AspNetCore.Hosting; -#endif using System; -namespace FastReport.Web +namespace FastReport.Web.Infrastructure { - class FastReportGlobal + internal static class FastReportGlobal { -#if WASM - // .. -#elif BLAZOR +#if BLAZOR internal static IWebHostEnvironment HostingEnvironment = null; #else internal static IHostingEnvironment HostingEnvironment = null; #endif internal static FastReportOptions FastReportOptions = new FastReportOptions(); -#if !WASM - internal static CacheOptions InternalCacheOptions { get; set; } -#endif - } } +#endif diff --git a/FastReport.Core.Web/Application/Infrastructure/FastReportMiddleware.cs b/FastReport.Core.Web/Application/Infrastructure/FastReportMiddleware.cs new file mode 100644 index 00000000..d949724a --- /dev/null +++ b/FastReport.Core.Web/Application/Infrastructure/FastReportMiddleware.cs @@ -0,0 +1,44 @@ +#if !WASM +using Microsoft.AspNetCore.Http; +using System; +using System.Threading.Tasks; + +namespace FastReport.Web.Infrastructure +{ + sealed class FastReportMiddleware + { + private static readonly ValueTask _falseResult = new ValueTask(false); + + private readonly RequestDelegate next; + + Func> ExecuteFunc; + + public FastReportMiddleware(RequestDelegate next) + { + this.next = next; + ExecuteFunc = RequestFastReportControllerStart; + } + + public async Task Invoke(HttpContext httpContext) + { + if (!(await ExecuteFunc(httpContext))) + await next(httpContext); + } + + private ValueTask RequestFastReportControllerStart(HttpContext httpContext) + { + FastReportGlobal.FastReportOptions.RoutePathBaseRoot = httpContext.Request.PathBase; + ExecuteFunc = RequestFastReportController; + return RequestFastReportController(httpContext); + } + + private ValueTask RequestFastReportController(HttpContext httpContext) + { + if (!httpContext.Request.Path.StartsWithSegments(FastReportGlobal.FastReportOptions.RouteBasePath)) + return _falseResult; + + return ControllerBuilder.Executor.ExecuteAsync(httpContext); + } + } +} +#endif \ No newline at end of file diff --git a/FastReport.Core.Web/Application/FastReportOptions.cs b/FastReport.Core.Web/Application/Infrastructure/FastReportOptions.cs similarity index 77% rename from FastReport.Core.Web/Application/FastReportOptions.cs rename to FastReport.Core.Web/Application/Infrastructure/FastReportOptions.cs index 1c7c8137..9f13e5a9 100644 --- a/FastReport.Core.Web/Application/FastReportOptions.cs +++ b/FastReport.Core.Web/Application/Infrastructure/FastReportOptions.cs @@ -1,7 +1,6 @@ #if !WASM using FastReport.Web.Cache; using Microsoft.AspNetCore.Http; -#endif using System; using System.Collections.Generic; using System.ComponentModel; @@ -11,13 +10,21 @@ namespace FastReport.Web { public class FastReportOptions { + private const string CACHEOPTIONS_OBSOLETE_MESSAGE = "Please, use services.AddFastReport(options => options.CacheOptions)"; + /// /// Request.PathBase url for API. /// Default value: "". /// internal string RoutePathBaseRoot { get; set; } = ""; - internal static bool UseNewControllers { get; set; } = false; + internal bool CheckAuthorization(HttpRequest request) + { + if (Authorize != null) + return Authorize.Invoke(request); + + return true; + } /// /// Request.Path part of url for API. @@ -25,7 +32,6 @@ public class FastReportOptions /// public string RouteBasePath { get; set; } = "/_fr"; -#if !WASM /// /// A function that determines who can access API. /// It should return true when the request client has access, false for a 401 to be returned. @@ -33,6 +39,8 @@ public class FastReportOptions /// public Func Authorize { get; set; } = null; + [Obsolete(CACHEOPTIONS_OBSOLETE_MESSAGE, true)] + [EditorBrowsable(EditorBrowsableState.Never)] public CacheOptions CacheOptions { get; set; } = new CacheOptions(); /// @@ -40,11 +48,10 @@ public class FastReportOptions /// If report is not used the specified time and there is no references to the object, it will be deleted from cache. /// Default value: "15". /// - [Obsolete("Please, use CacheOptions.CacheDuration")] + [Obsolete(CACHEOPTIONS_OBSOLETE_MESSAGE, true)] [EditorBrowsable(EditorBrowsableState.Never)] public int CacheDuration { get => CacheOptions.CacheDuration.Minutes; set => CacheOptions.CacheDuration = TimeSpan.FromMinutes(value); } -#endif /// /// Enable or disable the multiple instances environment. @@ -53,3 +60,4 @@ public class FastReportOptions //public bool CloudEnvironment { get; set; } = false; } } +#endif diff --git a/FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.Backend.cs b/FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.Backend.cs new file mode 100644 index 00000000..d0cdde36 --- /dev/null +++ b/FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.Backend.cs @@ -0,0 +1,68 @@ +using FastReport.Code; +using FastReport.Web; +using FastReport.Web.Cache; +using FastReport.Web.Infrastructure; +using FastReport.Web.Services; + +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection.Extensions; + +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static partial class FastReportServiceCollectionExtensions + { + + private static void AddServices(IServiceCollection services, WebReportOptions options) + { + AddFastReportWebServices(services, options); + } + + private static void AddFastReportWebServices(IServiceCollection services, WebReportOptions options) + { + services.TryAddSingleton(options.CacheOptions); + if (options.CacheOptions.UseLegacyWebReportCache) + { + services.TryAddSingleton(); + } + else // MemoryCache + { + // check for registered IMemoryCache + var memoryCacheDescriptor = GetServiceDescriptorFromCollection(services); + + if(memoryCacheDescriptor != null) + { + services.TryAddSingleton(); + } + else + { + services.TryAddSingleton( + static serviceProvider => + { + var memoryCache = serviceProvider.GetService() + ?? WebReportCache.GetDefaultMemoryCache(); + + var cacheOptions = serviceProvider.GetService(); + return new WebReportMemoryCache(memoryCache, cacheOptions); + }); + } + } + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); +#if DESIGNER + services.TryAddSingleton(); +#endif + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + } + + } +} diff --git a/FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.cs b/FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.cs similarity index 97% rename from FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.cs rename to FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.cs index 827a9fba..d4e661e3 100644 --- a/FastReport.Core.Web/Application/FastReportServiceCollectionExtensions.cs +++ b/FastReport.Core.Web/Application/Infrastructure/FastReportServiceCollectionExtensions.cs @@ -57,7 +57,7 @@ private static void AddFastReportPrivate(IServiceCollection services, WebReportO FastReport.Utils.Config.WebMode = true; #if !WASM - ConfigureApplicationPart(services, options); + AddServices(services, options); #else AddWasmServices(services, options); #endif diff --git a/FastReport.Core.Web/Application/Infrastructure/IResult.cs b/FastReport.Core.Web/Application/Infrastructure/IResult.cs new file mode 100644 index 00000000..9ee6a1a6 --- /dev/null +++ b/FastReport.Core.Web/Application/Infrastructure/IResult.cs @@ -0,0 +1,83 @@ +#if !NET6_0_OR_GREATER +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing; + +using System; +using System.Net; +using System.Threading.Tasks; + +namespace FastReport.Web.Infrastructure +{ + internal interface IResult + { + Task ExecuteAsync(HttpContext httpContext); + } + + internal sealed class ResultHandler : IResult + { + private readonly IActionResult _actionResult; + + public static IResult Parse(IActionResult actionResult) + { + return new ResultHandler(actionResult); + } + + public Task ExecuteAsync(HttpContext httpContext) + { + var actionDescriptor = new ActionDescriptor(); + var context = new ActionContext(httpContext, new RouteData() /*httpContext.GetRouteData()*/, actionDescriptor); + return _actionResult.ExecuteResultAsync(context); + } + + private ResultHandler(IActionResult actionResult) + { + _actionResult = actionResult; + } + } + + internal static class Results + { + internal static IResult Content(string content, string contentType) => ResultHandler.Parse(new ContentResult() + { + Content = content, + ContentType = contentType, + StatusCode = StatusCodes.Status200OK + }); + + internal static IResult BadRequest(string content) => ResultHandler.Parse(new BadRequestObjectResult(content)); + + internal static IResult File( + byte[] fileContents, + string contentType = null, + string fileDownloadName = null, + bool enableRangeProcessing = false, + DateTimeOffset? lastModified = null) + => ResultHandler.Parse(new FileContentResult(fileContents, contentType) + { + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + LastModified = lastModified + }); + + internal static IResult NotFound() => ResultHandler.Parse(new NotFoundResult()); + + internal static IResult Ok() => ResultHandler.Parse(new OkResult()); + + internal static IResult Ok(object value) + { + IActionResult result; + if (value == null) + result = new OkResult(); + else + result = new OkObjectResult(value); + return ResultHandler.Parse(result); + } + + internal static IResult StatusCode(int code) => ResultHandler.Parse(new StatusCodeResult(code)); + + internal static IResult Unauthorized() => ResultHandler.Parse(new UnauthorizedResult()); + } +} +#endif \ No newline at end of file diff --git a/FastReport.Core.Web/Application/LinkerFlags.cs b/FastReport.Core.Web/Application/LinkerFlags.cs new file mode 100644 index 00000000..aaaa0583 --- /dev/null +++ b/FastReport.Core.Web/Application/LinkerFlags.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace FastReport.Web +{ + internal static class LinkerFlags + { + internal const DynamicallyAccessedMemberTypes ExportTypeMembers = DynamicallyAccessedMemberTypes.PublicParameterlessConstructor + | DynamicallyAccessedMemberTypes.PublicProperties; + + internal const DynamicallyAccessedMemberTypes All = DynamicallyAccessedMemberTypes.All; + } + +#if !NET5_0_OR_GREATER + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + } + } + + // + // Summary: + // Specifies the types of members that are dynamically accessed. This enumeration + // has a System.FlagsAttribute attribute that allows a bitwise combination of its + // member values. + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + // + // Summary: + // Specifies all members. + All = -1, + // + // Summary: + // Specifies no members. + None = 0, + // + // Summary: + // Specifies the default, parameterless public constructor. + PublicParameterlessConstructor = 1, + // + // Summary: + // Specifies all public constructors. + PublicConstructors = 3, + // + // Summary: + // Specifies all non-public constructors. + NonPublicConstructors = 4, + // + // Summary: + // Specifies all public methods. + PublicMethods = 8, + // + // Summary: + // Specifies all non-public methods. + NonPublicMethods = 16, + // + // Summary: + // Specifies all public fields. + PublicFields = 32, + // + // Summary: + // Specifies all non-public fields. + NonPublicFields = 64, + // + // Summary: + // Specifies all public nested types. + PublicNestedTypes = 128, + // + // Summary: + // Specifies all non-public nested types. + NonPublicNestedTypes = 256, + // + // Summary: + // Specifies all public properties. + PublicProperties = 512, + // + // Summary: + // Specifies all non-public properties. + NonPublicProperties = 1024, + // + // Summary: + // Specifies all public events. + PublicEvents = 2048, + // + // Summary: + // Specifies all non-public events. + NonPublicEvents = 4096, + // + // Summary: + // Specifies all interfaces implemented by the type. + Interfaces = 8192 + } +#endif +} diff --git a/FastReport.Core.Web/Application/Toolbar/ToolbarButton.cs b/FastReport.Core.Web/Application/Toolbar/ToolbarButton.cs new file mode 100644 index 00000000..24174dcd --- /dev/null +++ b/FastReport.Core.Web/Application/Toolbar/ToolbarButton.cs @@ -0,0 +1,33 @@ +using System; + +namespace FastReport.Web.Toolbar +{ + /// + /// Button for the toolbar + /// + public class ToolbarButton : ToolbarElement + { + /// + /// The image of the button that appears in the toolbar + /// + public ToolbarElementImage Image { get; set; } = new ToolbarElementImage(); + + /// + /// Action that is triggered when the button is clicked + /// + public IClickAction OnClickAction { get; set; } + + internal override string Render(string template_FR) + { + if (!Enabled) return default; + + var action = OnClickAction is ElementScript scriptButton + ? scriptButton.Script + : $"{template_FR}.customMethodInvoke('{ID}', this.value)"; + + return $@"
+ +
"; + } + } +} \ No newline at end of file diff --git a/FastReport.Core.Web/Application/Toolbar/ToolbarElement.cs b/FastReport.Core.Web/Application/Toolbar/ToolbarElement.cs new file mode 100644 index 00000000..ba516cca --- /dev/null +++ b/FastReport.Core.Web/Application/Toolbar/ToolbarElement.cs @@ -0,0 +1,156 @@ +using FastReport.Utils; +using System; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using static System.String; + +namespace FastReport.Web.Toolbar +{ + public abstract class ToolbarElement + { + public ToolbarElement() + { + Name = ID.ToString(); + } + + internal Guid ID { get; } = Guid.NewGuid(); + + /// + /// Name of button required to interact with the element list + /// + public string Name { get; set; } + + /// + /// Defines the visibility of the button on the toolbar + /// + public bool Enabled { get; set; } = true; + + /// + /// Element styles to be specified in the style tag + /// + public string ElementCustomStyle { get; set; } = Empty; + + /// + /// Classes to be specified in the class tag for the element + /// + public string ElementClasses { get; set; } = Empty; + + /// + /// Text to be displayed when the element is hovered over + /// + public string Title { get; set; } = Empty; + + /// + /// Property by which the buttons in the toolbar will be sorted + /// + public int Position { get; set; } = -1; + + internal abstract string Render(string template_FR); + } + + + /// + /// Action that is triggered when the element is clicked + /// + public interface IClickAction + { + } + + /// + /// Action that is triggered when the element is changed + /// + public interface IChangeAction + { + } + + /// + /// A script that is called when you click on element + /// + public class ElementScript : IClickAction, IChangeAction + { + /// + /// A script that is called when you click on element + /// + public string Script { get; set; } + } + + /// + /// Action that is called when you click on an item + /// + public class ElementClickAction : IClickAction + { + /// + /// Action that is called when you click on an item. WebReport - WebReport for which the button is made + /// + public Func OnClickAction { get; set; } + } + + /// + /// Action that is called when item changed + /// + public class ElementChangeAction : IChangeAction + { + /// + /// Action that is called when item changed. WebReport - WebReport for which the button is mad + /// + public Func OnChangeAction { get; set; } + } + + /// + /// The image that will be displayed in the toolbar + /// + public class ToolbarElementImage + { + private string defaultButtonImage; + + private string base64; + + internal string RenderedImage => IsNullOrEmpty(Url) ? $"data:image/svg+xml;base64,{Base64}" : Url; + + + /// + /// Toolbar element image in Base64 format + /// + public string Base64 + { + get + { + if (!IsNullOrWhiteSpace(base64)) + return base64; + + if (IsNullOrWhiteSpace(defaultButtonImage)) + LoadDefaultImage(); + + return defaultButtonImage; + } + + set => base64 = value; + } + + /// + /// Link to the image of the toolbar element + /// + public string Url { get; set; } + + private void LoadDefaultImage() + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceStream = + assembly.GetManifestResourceStream($"{assembly.GetName().Name}.Resources.default-custom-button.svg"); + string resource; + + using (var reader = new StreamReader(resourceStream, Encoding.UTF8)) + { + resource = reader.ReadToEnd(); + } + + var svgBytes = System.Text.Encoding.UTF8.GetBytes(resource); + var base64String = Convert.ToBase64String(svgBytes); + + defaultButtonImage = base64String; + } + } +} \ No newline at end of file diff --git a/FastReport.Core.Web/Application/Toolbar/ToolbarInput.cs b/FastReport.Core.Web/Application/Toolbar/ToolbarInput.cs new file mode 100644 index 00000000..2d024663 --- /dev/null +++ b/FastReport.Core.Web/Application/Toolbar/ToolbarInput.cs @@ -0,0 +1,47 @@ +using FastReport.Web; +using System; + +namespace FastReport.Web.Toolbar +{ + /// + /// Input field for the toolbar + /// + public class ToolbarInput : ToolbarElement + { + /// + /// Type of input, which is specified in the type tag + /// + public string InputType { get; set; } + + /// + /// Styles that are specified in the style tag for input + /// + public string InputCustomStyle { get; set; } + + /// + /// Standard value for input + /// + public string InputDefaultValue { get; set; } + + /// + /// Action to be triggered when user changes input value + /// + /// Can be either ElementScript or ElementChangeAction + /// + public IChangeAction OnChangeAction { get; set; } + + internal override string Render(string template_FR) + { + if (!Enabled) return default; + + var action = OnChangeAction is ElementScript elementScript + ? elementScript.Script + : $"{template_FR}.customMethodInvoke('{ID}', this.value)"; + + return + $@"
+ +
"; + } + } +} \ No newline at end of file diff --git a/FastReport.Core.Web/Application/Toolbar/ToolbarSelect.cs b/FastReport.Core.Web/Application/Toolbar/ToolbarSelect.cs new file mode 100644 index 00000000..ef14d5fc --- /dev/null +++ b/FastReport.Core.Web/Application/Toolbar/ToolbarSelect.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FastReport.Web.Toolbar +{ + /// + /// Element that opens a drop-down list when you hover over it + /// + public class ToolbarSelect : ToolbarElement + { + /// + /// The image of the button that appears in the toolbar + /// + public ToolbarElementImage Image { get; set; } = new ToolbarElementImage(); + + /// + /// Contains items that will be displayed in the drop-down list + /// + /// List of ToolbarSelectItems + /// + public List Items { get; set; } = new List(); + + internal override string Render(string template_FR) + { + if (!Enabled) return default; + + var sb = new StringBuilder(); + + sb.Append( + $@"
+ +
"); + + foreach (var item in Items.Where(item => item.Enabled)) + sb.Append(item.Render(template_FR)); + + sb.Append("
"); + return sb.ToString(); + } + } + + /// + /// The element that appears in the drop-down list + /// + public class ToolbarSelectItem + { + public ToolbarSelectItem() + { + Name = ID.ToString(); + } + + internal Guid ID { get; } = Guid.NewGuid(); + + /// + /// Name of toolbar item required to interact with the items list + /// + public string Name { get; set; } + + /// + /// The inscription displayed in the drop-down list + /// + public string Title { get; set; } + + /// + /// Defines the visibility of the item in the drop-down list + /// + public bool Enabled { get; set; } = true; + + /// + /// Action that is triggered when the item is clicked + /// + public IClickAction OnClickAction { get; set; } + + internal string Render(string template_FR) + { + var action = OnClickAction is ElementScript scriptButton + ? scriptButton.Script + : $"{template_FR}.customMethodInvoke('{ID}', this.value)"; + + return $@"{Title}"; + } + } +} \ No newline at end of file diff --git a/FastReport.Core.Web/Application/ToolbarSettings.cs b/FastReport.Core.Web/Application/ToolbarSettings.cs index 593a3dd0..e700c6df 100644 --- a/FastReport.Core.Web/Application/ToolbarSettings.cs +++ b/FastReport.Core.Web/Application/ToolbarSettings.cs @@ -1,11 +1,8 @@ -using System; +using FastReport.Web.Toolbar; +using System; using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using System.Linq; using System.Drawing; -using System.Globalization; +using System.Linq; namespace FastReport.Web { @@ -47,12 +44,16 @@ public bool ShowBottomToolbar public bool ShowLastButton { get; set; } = true; public bool ShowRefreshButton { get; set; } = true; public bool ShowZoomButton { get; set; } = true; + #if WASM /// /// Show Print menu. Not supported in Wasm at the moment /// public bool ShowPrint { get => false; set => throw new NotSupportedException("Not supported in Wasm at the moment"); } #else + /// + /// Show Print menu + /// public bool ShowPrint { get; set; } = true; #endif @@ -116,6 +117,33 @@ public bool ShowBottomToolbar public int Height { get; set; } = 40; + internal List Elements { get; set; } = new List(); + + /// + /// Adds an item to the toolbar + /// + /// Any inheritor of ToolbarElement. Can be a TolbarButton, ToolbarSelect, or ToolbarInput + public void InsertToolbarElement(ToolbarElement element) + { + if (Elements.Any(x => x.Name == element.Name)) return; + + var index = Elements.FindIndex(x => x.Position > element.Position); + + if (index < 0 || element.Position == -1) + { + if (Elements.Count != 0) + element.Position = Elements.Max(x => x.Position) + 1; + else + element.Position = 0; + + Elements.Add(element); + } + else + { + Elements.Insert(index, element); + } + } + internal int ToolbarSlash { get diff --git a/FastReport.Core.Web/Application/WebReport.Backend.cs b/FastReport.Core.Web/Application/WebReport.Backend.cs index be528d5f..77152d3c 100644 --- a/FastReport.Core.Web/Application/WebReport.Backend.cs +++ b/FastReport.Core.Web/Application/WebReport.Backend.cs @@ -5,14 +5,16 @@ using Microsoft.AspNetCore.Html; using System.Threading.Tasks; using System.Linq; -using FastReport.Web.Application; -using System.Drawing; +using FastReport.Web.Cache; +using FastReport.Web.Services; namespace FastReport.Web { public partial class WebReport { + internal static IResourceLoader ResourceLoader { get; set; } + public HtmlString RenderSync() { @@ -45,5 +47,14 @@ internal HtmlString Render(bool renderBody) throw new Exception($"Unknown mode: {Mode}"); } } + + /// + /// Force report to be removed from internal cache + /// + public void RemoveFromCache() + { + WebReportCache.Instance?.Remove(this); + } + } } diff --git a/FastReport.Core.Web/Application/WebReport.cs b/FastReport.Core.Web/Application/WebReport.cs index 36c6d92f..0628cef2 100644 --- a/FastReport.Core.Web/Application/WebReport.cs +++ b/FastReport.Core.Web/Application/WebReport.cs @@ -106,7 +106,7 @@ public bool ReportPrepared public int TotalPages => Report?.PreparedPages?.Count ?? 0; /// - /// Switches beetwen Preview and Designer modes + /// Switches between Preview and Designer modes /// public WebReportMode Mode { get; set; } = WebReportMode.Preview; @@ -206,11 +206,6 @@ public bool ReportPrepared #region Non-public - // TODO - private string ReportFile { get; set; } = null; - private string ReportPath { get; set; } = null; - internal string ReportResourceString { get; set; } = null; - internal readonly Dictionary PictureCache = new Dictionary(); internal string InlineStyle @@ -230,7 +225,7 @@ public WebReport() string path = WebUtils.MapPath(LocalizationFile); Res.LoadLocale(path); #if !WASM - WebReportCache.Instance.Add(this); + WebReportCache.Instance?.Add(this); #endif #if DIALOGS Dialog = new Dialog(this); @@ -269,16 +264,6 @@ internal void InternalDispose() Res.Dispose(); } - /// - /// Force report to be removed from internal cache - /// - public void RemoveFromCache() - { -#if !WASM - WebReportCache.Instance.Remove(this); -#endif - } - // TODO // void ReportLoad() // void RegisterData() diff --git a/FastReport.Core.Web/Application/WebReportHtml.Backend.cs b/FastReport.Core.Web/Application/WebReportHtml.Backend.cs index fc5335c4..cf0afad7 100644 --- a/FastReport.Core.Web/Application/WebReportHtml.Backend.cs +++ b/FastReport.Core.Web/Application/WebReportHtml.Backend.cs @@ -10,6 +10,7 @@ using System.Globalization; using FastReport.Web.Services; using FastReport.Export.Html; +using FastReport.Web.Infrastructure; namespace FastReport.Web { diff --git a/FastReport.Core.Web/Application/WebUtils.cs b/FastReport.Core.Web/Application/WebUtils.cs index 29159a1d..879d0742 100644 --- a/FastReport.Core.Web/Application/WebUtils.cs +++ b/FastReport.Core.Web/Application/WebUtils.cs @@ -1,4 +1,7 @@ -using System; +#if !WASM +using FastReport.Web.Infrastructure; +#endif +using System; using System.Collections.Generic; using System.IO; using System.Net; diff --git a/FastReport.Core.Web/Controllers/ApiControllerBase.cs b/FastReport.Core.Web/Controllers/ApiControllerBase.cs index aaedcf7f..e69e8ba5 100644 --- a/FastReport.Core.Web/Controllers/ApiControllerBase.cs +++ b/FastReport.Core.Web/Controllers/ApiControllerBase.cs @@ -1,14 +1,12 @@ -using Microsoft.AspNetCore.Mvc; +using FastReport.Web.Infrastructure; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; namespace FastReport.Web.Controllers { - [ApiController] - public abstract class ApiControllerBase : ControllerBase + internal static partial class Controllers { + private static bool IsAuthorized(HttpRequest request) => FastReportGlobal.FastReportOptions.CheckAuthorization(request); } } diff --git a/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs b/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs index 8fc5ea32..e490b003 100644 --- a/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs +++ b/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs @@ -10,97 +10,66 @@ using System.Net; using System.Text; using System.Text.Encodings.Web; +using FastReport.Web.Infrastructure; +using Microsoft.AspNetCore.Http; namespace FastReport.Web.Controllers { - public sealed class ConnectionsController : ApiControllerBase + static partial class Controllers { - private readonly IConnectionsService _connectionsService; - - public ConnectionsController(IConnectionsService connectionsService) - { - _connectionsService = connectionsService; - } - public sealed class ConnectionsParams { public string ConnectionType { get; set; } public string ConnectionString { get; set; } } - [HttpGet] - [Route("/_fr/designer.getConnectionTypes")] - public IActionResult GetConnectionTypes() + [HttpGet("/designer.getConnectionTypes")] + public static IResult GetConnectionTypes(IConnectionsService connectionsService) { - var response = _connectionsService.GetConnectionTypes(); + var response = connectionsService.GetConnectionTypes(); + var content = "{" + string.Join(",", response.ToArray()) + "}"; - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/json", - Content = "{" + String.Join(",", response.ToArray()) + "}", - }; + return Results.Content(content, "application/json"); } - [HttpGet] - [Route("/_fr/designer.getConnectionTables")] - public IActionResult GetConnectionTables([FromQuery] ConnectionsParams query) + [HttpGet("/designer.getConnectionTables")] + public static IResult GetConnectionTables([FromQuery] ConnectionsParams query, + IConnectionsService connectionsService) { - var response = _connectionsService.GetConnectionTables(query.ConnectionType, query.ConnectionString, out bool isError); - - return isError ? new ContentResult() + try { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Content = response, + var response = connectionsService.GetConnectionTables(query.ConnectionType, query.ConnectionString); + + return Results.Content(response, "application/xml"); } - : new ContentResult() + catch (Exception ex) { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; + return Results.BadRequest(ex.Message); + } } - [HttpPost] - [Route("/_fr/designer.makeConnectionString")] - public IActionResult MakeConnectionString(string connectionType) + [HttpPost("/designer.makeConnectionString")] + public static IResult MakeConnectionString(string connectionType, + IConnectionsService connectionsService, + HttpRequest request) { - var form = Request.Form; - var response = _connectionsService.CreateConnectionStringJSON(connectionType, form, out bool isError); + var form = request.Form; + var response = connectionsService.CreateConnectionStringJSON(connectionType, form, out var isError); - return isError ? new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Content = response, - } - : new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; + return isError + ? Results.Content(response, "text/plain") + : Results.Content(response, "application/xml"); } - [HttpGet] - [Route("/_fr/designer.getConnectionStringProperties")] - public IActionResult GetConnectionStringProperties([FromQuery] ConnectionsParams query) + [HttpGet("/designer.getConnectionStringProperties")] + public static IResult GetConnectionStringProperties([FromQuery] ConnectionsParams query, + IConnectionsService connectionsService) { - var response = _connectionsService.GetConnectionStringPropertiesJSON(query.ConnectionType, query.ConnectionString, out bool isError); + var response = connectionsService.GetConnectionStringPropertiesJSON(query.ConnectionType, query.ConnectionString, out var isError); - return isError ? new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Content = response, - } - : new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; + return isError + ? Results.Content(response, "text/plain") + : Results.Content(response, "application/xml"); } } } diff --git a/FastReport.Core.Web/Controllers/Designer/DesignerReportController.cs b/FastReport.Core.Web/Controllers/Designer/DesignerReportController.cs index 8de9de0d..490680ab 100644 --- a/FastReport.Core.Web/Controllers/Designer/DesignerReportController.cs +++ b/FastReport.Core.Web/Controllers/Designer/DesignerReportController.cs @@ -1,118 +1,80 @@ #if DESIGNER using FastReport.Web.Services; -using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Net; -using System.Text; using System.Threading.Tasks; +using FastReport.Web.Infrastructure; +using Microsoft.AspNetCore.Http; +using System.Net.Mime; namespace FastReport.Web.Controllers { - public sealed class DesignerReportController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - private readonly IReportDesignerService _reportDesignerService; - - public DesignerReportController(IReportService reportService, IReportDesignerService reportDesignerService) + [HttpGet("/designer.getReport")] + public static IResult GetReport(string reportId, + HttpRequest request, + IReportDesignerService reportDesignerService, + IReportService reportService) { - _reportService = reportService; - _reportDesignerService = reportDesignerService; - } + if (!IsAuthorized(request)) + return Results.Unauthorized(); - [HttpGet] - [Route("/_fr/designer.getReport")] - public IActionResult GetReport(string reportId) - { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!reportService.TryFindWebReport(reportId, out WebReport webReport)) + return Results.NotFound(); - var report = _reportDesignerService.GetDesignerReport(webReport); + var report = reportDesignerService.GetDesignerReport(webReport); - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = report, - }; + return Results.Content(report, "text/html"); } - [HttpPost] - [Route("/_fr/designer.saveReport")] - public async Task SaveReport(string reportId) + [HttpPost("/designer.saveReport")] + public static async Task SaveReport(string reportId, HttpRequest request, IReportService reportService, IReportDesignerService reportDesignerService) { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!IsAuthorized(request)) + return Results.Unauthorized(); - string contentType = "text/html"; + if (!reportService.TryFindWebReport(reportId, out var webReport)) + return Results.NotFound(); - var saveReportParams = SaveReportServiceParams.ParseRequest(Request); - - var result = await _reportDesignerService.SaveReportAsync(webReport, saveReportParams); + const string contentType = "text/html"; + var saveReportParams = SaveReportServiceParams.ParseRequest(request); + var result = await reportDesignerService.SaveReportAsync(webReport, saveReportParams); if (webReport.Designer.SaveMethod == null) { - if (result.Msg.IsNullOrEmpty()) - { - return new ContentResult() - { - ContentType = contentType, - StatusCode = result.Code, - }; - } - else - { - return new ContentResult() - { - ContentType = contentType, - StatusCode = result.Code, - Content = result.Msg - }; - } - } - else - { - return new ContentResult() - { - StatusCode = result.Code, - ContentType = "text/html", - Content = result.Msg - }; + return result.Msg.IsNullOrEmpty() + ? Results.Ok() + : Results.Content(result.Msg, contentType); } + + return Results.Content(result.Msg, contentType); } - [HttpPost] - [Route("/_fr/designer.previewReport")] - public async Task GetPreviewReport(string reportId) + [HttpPost("/designer.previewReport")] + public static async Task GetPreviewReport(string reportId, HttpRequest request, IReportService reportService, IReportDesignerService reportDesignerService) { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!IsAuthorized(request)) + return Results.Unauthorized(); + + if (!reportService.TryFindWebReport(reportId, out var webReport)) + return Results.NotFound(); - string receivedReportString = await _reportDesignerService.GetPOSTReportAsync(Request.Body); + var receivedReportString = await reportDesignerService.GetPOSTReportAsync(request.Body); string response; try { - response = await _reportDesignerService.DesignerMakePreviewAsync(webReport, receivedReportString); + response = await reportDesignerService.DesignerMakePreviewAsync(webReport, receivedReportString); } catch (Exception ex) { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/html", - Content = ex.Message - }; + return Results.BadRequest(ex.Message); } - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = response, - }; + return Results.Content(response, MediaTypeNames.Text.Html); } } } diff --git a/FastReport.Core.Web/Controllers/Designer/UtilsController.cs b/FastReport.Core.Web/Controllers/Designer/UtilsController.cs index 43757a6c..9be31986 100644 --- a/FastReport.Core.Web/Controllers/Designer/UtilsController.cs +++ b/FastReport.Core.Web/Controllers/Designer/UtilsController.cs @@ -1,135 +1,94 @@ #if DESIGNER using FastReport.Web.Services; + using Microsoft.AspNetCore.Mvc; using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; using System.Net; -using System.Reflection; -using System.Text; using System.Threading.Tasks; +using FastReport.Web.Infrastructure; +using Microsoft.AspNetCore.Http; +using System.Net.Mime; namespace FastReport.Web.Controllers { - public sealed class UtilsController : ApiControllerBase + static partial class Controllers { - private readonly IDesignerUtilsService _designerUtilsService; - private readonly IReportDesignerService _reportDesignerService; - private readonly IReportService _reportService; - - public UtilsController(IDesignerUtilsService designerUtilsService, IReportService reportService, - IReportDesignerService reportDesignerService) - { - _designerUtilsService = designerUtilsService; - _reportService = reportService; - _reportDesignerService = reportDesignerService; - } - - #region Routes - [HttpGet] - [Route("/_fr/designer.objects/mschart/template")] - public IActionResult GetMSChartTemplate(string name) + [HttpGet("/designer.objects/mschart/template")] + public static IResult GetMSChartTemplate(string name, IDesignerUtilsService designerUtilsService) { string response; try { - response = _designerUtilsService.GetMSChartTemplateXML(name); + response = designerUtilsService.GetMSChartTemplateXML(name); } catch (Exception ex) { - return new NotFoundResult(); + return Results.NotFound(); } - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; + return Results.Content(response, "application/xml"); } - [HttpGet] - [Route("/_fr/designer.getComponentProperties")] - public IActionResult GetComponentProperties(string name) + [HttpGet("/designer.getComponentProperties")] + public static IResult GetComponentProperties(string name, IDesignerUtilsService designerUtilsService) { - string response = _designerUtilsService.GetPropertiesJSON(name); - - if (response.IsNullOrEmpty()) - return new NotFoundResult(); - else - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/json", - Content = response - }; + var response = designerUtilsService.GetPropertiesJSON(name); + + return response.IsNullOrEmpty() + ? Results.NotFound() + : Results.Content(response, "application/json"); } - [HttpGet] - [Route("/_fr/designer.getConfig")] - public IActionResult GetConfig(string reportId) + [HttpGet("/designer.getConfig")] + public static IResult GetConfig(string reportId, IReportService reportService) { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!reportService.TryFindWebReport(reportId, out var webReport)) + return Results.NotFound(); - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/json", - Content = webReport.Designer.Config.IsNullOrWhiteSpace() ? "{}" : webReport.Designer.Config, - }; + var content = webReport.Designer.Config.IsNullOrWhiteSpace() ? "{}" : webReport.Designer.Config; + + return Results.Content(content, "application/json"); } - [HttpGet] - [Route("/_fr/designer.getFunctions")] - public IActionResult GetFunctions(string reportId) + [HttpGet("/designer.getFunctions")] + public static IResult GetFunctions(string reportId, + IReportService reportService, + IDesignerUtilsService designerUtilsService) { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!reportService.TryFindWebReport(reportId, out var webReport)) + return Results.NotFound(); - var buff = _designerUtilsService.GetFunctions(webReport.Report); + var buff = designerUtilsService.GetFunctions(webReport.Report); - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = buff, - }; + return Results.Content(buff, "application/xml"); } - [HttpPost] - [Route("/_fr/designer.objects/preview")] - public async Task GetDesignerObjectPreview(string reportId) + [HttpPost("/designer.objects/preview")] + public static async Task GetDesignerObjectPreview(string reportId, + IReportService reportService, + IReportDesignerService reportDesignerService, + IDesignerUtilsService designerUtilsService, + HttpRequest request) { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!reportService.TryFindWebReport(reportId, out var webReport)) + return Results.NotFound(); try { - var reportObj = await _reportDesignerService.GetPOSTReportAsync(Request.Body); - - var response = _designerUtilsService.DesignerObjectPreview(webReport, reportObj); - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = response - }; + var reportObj = await reportDesignerService.GetPOSTReportAsync(request.Body); + var response = designerUtilsService.DesignerObjectPreview(webReport, reportObj); + + return Results.Content(response, MediaTypeNames.Text.Html); } catch (Exception ex) { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/html", - Content = webReport.Debug ? ex.Message : "", - }; + var content = webReport.Debug ? ex.Message : ""; + + return Results.BadRequest(content); } } - #endregion } } #endif \ No newline at end of file diff --git a/FastReport.Core.Web/Controllers/Legacy/BaseController.cs b/FastReport.Core.Web/Controllers/Legacy/BaseController.cs deleted file mode 100644 index 5febc8ef..00000000 --- a/FastReport.Core.Web/Controllers/Legacy/BaseController.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Template; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -namespace FastReport.Web.Controllers -{ - abstract class BaseController - { - internal readonly struct Handler - { - //public HttpMethod Method; - public readonly TemplateMatcher RouteTemplate; - public readonly Func ActionSync; - public readonly Func> ActionAsync; - - public Handler(TemplateMatcher routeTemplate, - Func actionSync, - Func> actionAsync) - { - RouteTemplate = routeTemplate; - ActionSync = actionSync; - ActionAsync = actionAsync; - } - } - - //public readonly string RouteBasePath; - - protected HttpContext Context { get; private set; } - protected HttpRequest Request => Context.Request; - protected HttpResponse Response => Context.Response; - //protected RouteValueDictionary RouteValues { get; private set; } - - private readonly List handlers = new List(); - - public BaseController(/*string routeBasePath*/) - { - //RouteBasePath = routeBasePath; - } - - protected void RegisterHandler(/*HttpMethod method, */string routeTemplate, Func action) - { - RegisterHandler(/*method, */routeTemplate, new RouteValueDictionary(), action); - } - - protected void RegisterHandler(/*HttpMethod method, */string routeTemplate, RouteValueDictionary routeDefaults, Func action) - { - handlers.Add(new Handler( - //Method = method, - routeTemplate: new TemplateMatcher(TemplateParser.Parse(WebUtils.ToUrl(FastReportGlobal.FastReportOptions.RouteBasePath, /*RouteBasePath, */routeTemplate).TrimStart('/')), routeDefaults), - actionSync: action, - actionAsync: null - )); - } - - protected void RegisterHandler(/*HttpMethod method, */string routeTemplate, Func> action) - { - RegisterHandler(/*method, */routeTemplate, new RouteValueDictionary(), action); - } - - protected void RegisterHandler(/*HttpMethod method, */string routeTemplate, RouteValueDictionary routeDefaults, Func> action) - { - handlers.Add(new Handler( - //Method = method, - routeTemplate: new TemplateMatcher(TemplateParser.Parse(WebUtils.ToUrl(FastReportGlobal.FastReportOptions.RouteBasePath, /*RouteBasePath, */routeTemplate).TrimStart('/')), routeDefaults), - actionAsync: action, - actionSync: null - )); - } - - public async Task OnRequest(HttpContext httpContext) - { - //if (!httpContext.Request.Path.StartsWithSegments(WebUtils.ToUrl(FastReportGlobal.FastReportOptions.RouteBasePath, RouteBasePath))) - // return false; - - foreach (var handler in handlers) - { - //if (httpContext.Request.Method != handler.Method.Method) - // continue; - - var routeValues = new RouteValueDictionary(); - - if (!handler.RouteTemplate.TryMatch(httpContext.Request.Path, routeValues)) - continue; - - // setup values for action - Context = httpContext; - //RouteValues = routeValues; - - IActionResult actionResult = null; - - //try - //{ - - if (handler.ActionAsync != null) - actionResult = await handler.ActionAsync.Invoke(); - if (actionResult == null && handler.ActionSync != null) - actionResult = await Task.Run(() => handler.ActionSync.Invoke()); - - //} - //catch (Exception e) - //{ - // actionResult = new ContentResult() - // { - // StatusCode = (int)HttpStatusCode.InternalServerError, - // ContentType = "text/html", - // Content = e.ToString(), - // }; - //} - - await actionResult.ExecuteResultAsync(new ActionContext(httpContext, new RouteData(), new ActionDescriptor())); - - // clear values after action - Context = null; - //RouteValues = null; - - return true; - } - - return false; - } - } -} diff --git a/FastReport.Core.Web/Controllers/Legacy/DesignerController.cs b/FastReport.Core.Web/Controllers/Legacy/DesignerController.cs deleted file mode 100644 index 104e65a7..00000000 --- a/FastReport.Core.Web/Controllers/Legacy/DesignerController.cs +++ /dev/null @@ -1,267 +0,0 @@ -#if DESIGNER -using FastReport.Web.Services; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Diagnostics; -using System.Net; - -namespace FastReport.Web.Controllers -{ - class DesignerController : BaseController - { - #region Routes - - public DesignerController() : base() - { - RegisterHandler("/designer.getReport", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var report = ReportDesignerService.Instance.GetDesignerReport(webReport); - - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = report, - }; - }); - - RegisterHandler("/designer.saveReport", async () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - string contentType = "text/html"; - - var saveReportParams = SaveReportServiceParams.ParseRequest(Request); - - var result = await ReportDesignerService.Instance.SaveReportAsync(webReport, saveReportParams); - - if (webReport.Designer.SaveMethod == null) - { - if (result.Msg.IsNullOrEmpty()) - { - return new ContentResult() - { - ContentType = contentType, - StatusCode = result.Code, - }; - } - else - { - return new ContentResult() - { - ContentType = contentType, - StatusCode = result.Code, - Content = result.Msg - }; - } - } - else - { - return new ContentResult() - { - StatusCode = result.Code, - ContentType = "text/html", - Content = result.Msg - }; - } - }); - - RegisterHandler("/designer.previewReport", async () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - string receivedReportString = await ReportDesignerService.Instance.GetPOSTReportAsync(Request.Body); - string response = default; - - try - { - response = await ReportDesignerService.Instance.DesignerMakePreviewAsync(webReport, receivedReportString); - } - catch (Exception ex) - { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/html", - Content = ex.Message - }; - } - - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = response, - }; - }); - - RegisterHandler("/designer.objects/preview", async () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - try - { - var reportObj = await ReportDesignerService.Instance.GetPOSTReportAsync(Request.Body); - - var response = DesignerUtilsService.Instance.DesignerObjectPreview(webReport, reportObj); - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = response - }; - } - catch (Exception ex) - { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/html", - Content = webReport.Debug ? ex.Message : "", - }; - } - }); - - RegisterHandler("/designer.getConfig", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/json", - Content = webReport.Designer.Config.IsNullOrWhiteSpace() ? "{}" : webReport.Designer.Config, - }; - }); - - RegisterHandler("/designer.getFunctions", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var buff = DesignerUtilsService.Instance.GetFunctions(webReport.Report); - - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = buff, - }; - }); - - RegisterHandler("/designer.getConnectionTypes", () => - { - var names = ConnectionService.Instance.GetConnectionTypes(); - - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/json", - Content = "{" + String.Join(",", names.ToArray()) + "}", - }; - }); - - RegisterHandler("/designer.getConnectionTables", () => - { - var connectionType = Request.Query["connectionType"].ToString(); - var connectionString = Request.Query["connectionString"].ToString(); - var response = ConnectionService.Instance.GetConnectionTables(connectionType, connectionString, out bool isError); - - return isError ? new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Content = response, - } - : new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; - }); - - RegisterHandler("/designer.getConnectionStringProperties", () => - { - var connectionType = Request.Query["connectionType"].ToString(); - var connectionString = Request.Query["connectionString"].ToString(); - var response = ConnectionService.Instance.GetConnectionStringPropertiesJSON(connectionType, connectionString, out bool isError); - - return isError ? new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Content = response, - } - : new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; - }); - - RegisterHandler("/designer.makeConnectionString", () => - { - var connectionType = Request.Query["connectionType"].ToString(); - var form = Request.Form; - var response = ConnectionService.Instance.CreateConnectionStringJSON(connectionType, form, out bool isError); - - return isError ? new ContentResult() - { - StatusCode = (int)HttpStatusCode.InternalServerError, - ContentType = "text/plain", - Content = response, - } - : new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; - }); - - RegisterHandler("/designer.objects/mschart/template", () => - { - var resourceName = Request.Query["name"].ToString(); - var response = DesignerUtilsService.Instance.GetMSChartTemplateXML(resourceName); - - if (response.IsNullOrEmpty()) - return new NotFoundResult(); - else - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/xml", - Content = response - }; - }); - - RegisterHandler("/designer.getComponentProperties", () => - { - var componentName = Request.Query["name"].ToString(); - string responseJson = DesignerUtilsService.Instance.GetPropertiesJSON(componentName); - - if (responseJson.IsNullOrEmpty()) - return new NotFoundResult(); - else - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "application/json", - Content = responseJson - }; - - }); - } - } -} - #endregion -#endif \ No newline at end of file diff --git a/FastReport.Core.Web/Controllers/Legacy/ReportController.cs b/FastReport.Core.Web/Controllers/Legacy/ReportController.cs deleted file mode 100644 index 2682c7e1..00000000 --- a/FastReport.Core.Web/Controllers/Legacy/ReportController.cs +++ /dev/null @@ -1,190 +0,0 @@ -using FastReport.Export; -using FastReport.Export.Html; -using FastReport.Export.Image; -using Microsoft.AspNetCore.Mvc; -using System; -using System.IO; -using System.Linq; -using System.Net; -using System.Web; -using System.Reflection; -using System.Globalization; -using FastReport.Web.Cache; -using FastReport.Web.Services; -using System.Collections.Generic; - -namespace FastReport.Web.Controllers -{ - class ReportController : BaseController - { - #region Routes - - public ReportController() - { - - RegisterHandler("/preview.getReport", async () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var getReportParams = new GetReportServiceParams - { - SkipPrepare = Request.Query["skipPrepare"].ToString(), - ForceRefresh = Request.Query["forceRefresh"].ToString(), - RenderBody = Request.Query["renderBody"].ToString(), - }; - - getReportParams.ParseRequest(Request); - - var render = ReportService.Instance.GetReport(webReport, getReportParams); - - if (render.IsNullOrEmpty()) - return new OkResult(); - - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = render, - }; - }); - - RegisterHandler("/preview.touchReport", () => - { - var reportId = Request.Query["reportId"].ToString(); - ReportService.Instance.Touch(reportId); - return new OkResult(); - }); - - RegisterHandler("/preview.getPicture", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var pictureId = Request.Query["pictureId"].ToString().TrimStart('='); - if (webReport.PictureCache.TryGetValue(pictureId, out byte[] value)) - { - string imgType = WebUtils.IsPng(value) ? "image/png" : "image/svg+xml"; - return new FileContentResult(value, imgType); - } - - return new NotFoundResult(); - }); - - RegisterHandler("/preview.printReport", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var printMode = Request.Query["printMode"].ToString().ToLower(); - - var response = PrintService.Instance.PrintReport(webReport, printMode); - - if (!(response is null)) - { - switch (printMode) - { - case "html": - return new FileContentResult(response, "text/html"); -#if !OPENSOURCE - case "pdf": - return new FileContentResult(response, "application/pdf"); -#endif - } - } - - return new UnsupportedMediaTypeResult(); - }); - - RegisterHandler("/preview.exportReport", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var exportFormat = Request.Query["exportFormat"].ToString().ToLower(); - - // skip extra key/value pairs - var exportParams = Request.Query.Where(pair => pair.Key != "exportFormat" && pair.Key != "reportId") - .Select(item => new KeyValuePair(item.Key, item.Value)).ToArray(); - - byte[] file; - string filename; - - try - { - file = ExportService.Instance.ExportReport(webReport, exportParams, exportFormat, out filename); - } - catch (Exception ex) - { - return new UnsupportedMediaTypeResult(); - } - - return new FileContentResult(file, "application/octet-stream") - { - FileDownloadName = $"{filename}.{exportFormat}" - }; - }); - -#if DIALOGS - RegisterHandler("/dialog", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var dialogParams = new DialogParams(); - dialogParams.ParseRequest(Request); - - webReport.Dialogs(dialogParams); - - return new OkResult(); - }); -#endif - - RegisterHandler("/preview.textEditForm", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var result = TextEditService.Instance.GetTemplateTextEditForm(Request.Query["click"].ToString(), webReport); - - if (result != null) - { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = result, - }; - } - else - { - return new NotFoundResult(); - } - }); - - RegisterHandler("/exportsettings.getSettings", () => - { - if (!ReportService.Instance.TryFindWebReport(Request.Query["reportId"].ToString(), out WebReport webReport)) - return new NotFoundResult(); - - var format = Request.Query["format"]; - - var msg = ExportService.Instance.GetExportSettings(webReport, format); - - if (msg != null) - { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = msg, - }; - } - return new NotFoundResult(); - }); - - } - -#endregion - } -} \ No newline at end of file diff --git a/FastReport.Core.Web/Controllers/Legacy/ResourceController.cs b/FastReport.Core.Web/Controllers/Legacy/ResourceController.cs deleted file mode 100644 index 10c5f1fe..00000000 --- a/FastReport.Core.Web/Controllers/Legacy/ResourceController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Text; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Http; -using FastReport.Web.Services; - -namespace FastReport.Web.Controllers -{ - class ResourceController : BaseController - { - public ResourceController() : base() - { - RegisterHandler("/resources.getResource", async () => - { - var resourceName = Request.Query["resourceName"].ToString(); - var resource = await InternalResourceLoader.Instance.GetBytesAsync(resourceName); - if (resource == null) - return new NotFoundResult(); - - var contentType = Request.Query["contentType"].ToString(); - if (contentType.IsNullOrWhiteSpace()) - contentType = "application/octet-stream"; - - return new FileContentResult(resource, contentType); - }); - } - } -} diff --git a/FastReport.Core.Web/Controllers/Preview/DialogController.cs b/FastReport.Core.Web/Controllers/Preview/DialogController.cs index 8bdb705e..c695de0d 100644 --- a/FastReport.Core.Web/Controllers/Preview/DialogController.cs +++ b/FastReport.Core.Web/Controllers/Preview/DialogController.cs @@ -1,37 +1,32 @@ #if DIALOGS -using FastReport.Web.Cache; +using FastReport.Web.Infrastructure; using FastReport.Web.Services; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; namespace FastReport.Web.Controllers { - public sealed class DialogController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - public DialogController(IReportService reportService) + [HttpPost("/dialog")] + public static IResult TouchDialog([FromQuery] string reportId, + IReportService reportService, + HttpRequest request) { - _reportService = reportService; - } + if (!IsAuthorized(request)) + return Results.Unauthorized(); - [HttpPost] - [Route("/_fr/dialog")] - public IActionResult TouchDialog([FromQuery] string reportId) - { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + if (!reportService.TryFindWebReport(reportId, out WebReport webReport)) + return Results.NotFound(); var dialogParams = new DialogParams(); - dialogParams.ParseRequest(Request); + dialogParams.ParseRequest(request); webReport.Dialogs(dialogParams); - return Ok(); + return Results.Ok(); } } } diff --git a/FastReport.Core.Web/Controllers/Preview/ExportReportController.cs b/FastReport.Core.Web/Controllers/Preview/ExportReportController.cs index 3efb32d3..b56548c0 100644 --- a/FastReport.Core.Web/Controllers/Preview/ExportReportController.cs +++ b/FastReport.Core.Web/Controllers/Preview/ExportReportController.cs @@ -1,92 +1,84 @@ -using FastReport.Web.Cache; +using FastReport.Web.Infrastructure; using FastReport.Web.Services; + +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; -using System.Runtime.InteropServices.ComTypes; -using System.Text; +using System.Net.Mime; namespace FastReport.Web.Controllers { - public sealed class ExportReportController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - private readonly IExportsService _exportsService; - - public ExportReportController(IReportService reportService, IExportsService exportsService) - { - _reportService = reportService; - _exportsService = exportsService; - } - - public sealed class ExportReportParams + internal sealed class ExportReportParams { public string ReportId { get; set; } public string ExportFormat { get; set; } } - [HttpGet] - [Route("/_fr/preview.exportReport")] - public IActionResult ExportReport([FromQuery] ExportReportParams query) + //[Authorize] + [HttpGet("/preview.exportReport")] + public static IResult ExportReport([FromQuery] ExportReportParams query, + IReportService reportService, + IExportsService exportsService, + HttpRequest request) { - if (!_reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) - return new NotFoundResult(); + if (!IsAuthorized(request)) + return Results.Unauthorized(); + + if (!reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) + return Results.NotFound(); // TODO: // skip extra key/value pairs var exportFormat = query.ExportFormat.ToLower(); - var exportParams = Request.Query.Where(pair => pair.Key != "exportFormat" && pair.Key != "reportId") + var exportParams = request.Query.Where(pair => pair.Key != "exportFormat" && pair.Key != "reportId") .Select(item => new KeyValuePair(item.Key, item.Value)).ToArray(); byte[] file; string filename; try { - file = _exportsService.ExportReport(webReport, exportParams, exportFormat, out filename); + file = exportsService.ExportReport(webReport, exportParams, exportFormat, out filename); } - catch(Exception ex) + catch (Exception) { - return new UnsupportedMediaTypeResult(); + return Results.StatusCode((int)HttpStatusCode.UnsupportedMediaType); } - - return new FileContentResult(file, "application/octet-stream") - { - FileDownloadName = $"{filename}.{exportFormat}" - }; + + return Results.File(file, + contentType: MediaTypeNames.Application.Octet, + fileDownloadName: $"{filename}.{exportFormat}"); } - public sealed class ExportSettingsParams + + internal sealed class ExportSettingsParams { public string ReportId { get; set; } public string Format { get; set; } } - - [HttpPost] - [Route("/_fr/exportsettings.getSettings")] - public IActionResult GetExportSettings([FromQuery] ExportSettingsParams query) + [HttpPost("/exportsettings.getSettings")] + public static IResult GetExportSettings([FromQuery] ExportSettingsParams query, + IReportService reportService, + IExportsService exportsService) { - if (!_reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) - return new NotFoundResult(); - - var msg = _exportsService.GetExportSettings(webReport, query.Format); + if (!reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) + return Results.NotFound(); + + var msg = exportsService.GetExportSettings(webReport, query.Format); if (msg != null) { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = msg, - }; + return Results.Content(msg, MediaTypeNames.Text.Html); } - return new NotFoundResult(); + return Results.NotFound(); } } } diff --git a/FastReport.Core.Web/Controllers/Preview/GetPictureController.cs b/FastReport.Core.Web/Controllers/Preview/GetPictureController.cs index 4f74d0b5..b1b8cdfe 100644 --- a/FastReport.Core.Web/Controllers/Preview/GetPictureController.cs +++ b/FastReport.Core.Web/Controllers/Preview/GetPictureController.cs @@ -1,46 +1,40 @@ -using FastReport.Web.Cache; +using FastReport.Web.Infrastructure; using FastReport.Web.Services; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; namespace FastReport.Web.Controllers { - [Route("/_fr/preview.getPicture")] - public sealed class GetPictureController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - - public GetPictureController(IReportService reportService) - { - _reportService = reportService; - } - - public sealed class GetPictureParams + internal sealed class GetPictureParams { public string ReportId { get; set; } public string PictureId { get; set; } - } - [HttpGet] - public IActionResult GetPicture([FromQuery] GetPictureParams query) + + [HttpGet("/preview.getPicture")] + public static IResult GetPicture([FromQuery] GetPictureParams query, + IReportService reportService, + HttpRequest request) { - if (!_reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) - return new NotFoundResult(); + if (!IsAuthorized(request)) + return Results.Unauthorized(); + + if (!reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) + return Results.NotFound(); var pictureId = query.PictureId.TrimStart('='); if (webReport.PictureCache.TryGetValue(pictureId, out byte[] value)) { string imgType = WebUtils.IsPng(value) ? "image/png" : "image/svg+xml"; - return new FileContentResult(value, imgType); + return Results.File(value, imgType); } - return new NotFoundResult(); + return Results.NotFound(); } } } diff --git a/FastReport.Core.Web/Controllers/Preview/GetReportController.cs b/FastReport.Core.Web/Controllers/Preview/GetReportController.cs index fd63859e..cf03f4de 100644 --- a/FastReport.Core.Web/Controllers/Preview/GetReportController.cs +++ b/FastReport.Core.Web/Controllers/Preview/GetReportController.cs @@ -1,66 +1,59 @@ -using FastReport.Web.Cache; +using FastReport.Web.Infrastructure; using FastReport.Web.Services; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using static System.Net.Mime.MediaTypeNames; +using System.Net.Mime; +using System.Threading.Tasks; namespace FastReport.Web.Controllers { - public sealed class GetReportController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - public GetReportController(IReportService reportService) + [HttpPost("/preview.getReport")] + public static IResult GetReport([FromQuery] string reportId, + IReportService reportService, + HttpRequest request) { - _reportService = reportService; - } - - public sealed class GetReportParams - { - public string ReportId { get; set; } + if (!IsAuthorized(request)) + return Results.Unauthorized(); - public string RenderBody { get; set; } + if (!reportService.TryFindWebReport(reportId, out WebReport webReport)) + return Results.NotFound(); - //public bool RenderBody { get; set; } = false; + var query = GetReportServiceParams.ParseRequest(request); - public string SkipPrepare { get; set; } + string render = reportService.GetReport(webReport, query); - public string ForceRefresh { get; set; } + if (render.IsNullOrEmpty()) + return Results.Ok(); + return Results.Content(render, + contentType: MediaTypeNames.Text.Html); } - [HttpPost] - [Route("/_fr/preview.getReport")] - public IActionResult GetReport(string reportId, [FromQuery] GetReportServiceParams query) + + [HttpPost("/preview.touchReport")] + public static IResult TouchReport(string reportId, IReportService reportService) { - if (!_reportService.TryFindWebReport(reportId, out WebReport webReport)) - return new NotFoundResult(); + reportService.Touch(reportId); + return Results.Ok(); + } - query.ParseRequest(Request); - string render = _reportService.GetReport(webReport, query); + [HttpPost("/preview.toolbarElementClick")] + public static async Task GetReportAfterElementClick(string reportId, string elementId, IReportService reportService, + string inputValue = default) + { + if (!reportService.TryFindWebReport(reportId, out WebReport webReport)) + return Results.NotFound(); - if (render.IsNullOrEmpty()) - return new OkResult(); + var updatedWebreport = await reportService.InvokeCustomElementAction(webReport, elementId, inputValue); - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = render, - }; + return Results.Content(updatedWebreport, + contentType: MediaTypeNames.Text.Html); } - [HttpPost] - [Route("/_fr/preview.touchReport")] - public IActionResult TouchReport(string reportId) - { - _reportService.Touch(reportId); - return Ok(); - } } } diff --git a/FastReport.Core.Web/Controllers/Preview/PrintReportController.cs b/FastReport.Core.Web/Controllers/Preview/PrintReportController.cs index fcdfc766..721fa274 100644 --- a/FastReport.Core.Web/Controllers/Preview/PrintReportController.cs +++ b/FastReport.Core.Web/Controllers/Preview/PrintReportController.cs @@ -1,50 +1,55 @@ -using FastReport.Web.Services; +using FastReport.Web.Infrastructure; +using FastReport.Web.Services; + +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System.Net; + namespace FastReport.Web.Controllers { - [Route("/_fr/preview.printReport")] - public sealed class PrintReportController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - private readonly IPrintService _printService; - public PrintReportController(IReportService reportService, IPrintService printService) - { - _printService = printService; - _reportService = reportService; - } - - public sealed class PrintReportParams + internal sealed class PrintReportParams { public string ReportId { get; set; } public string PrintMode { get; set; } } - [HttpGet] - public IActionResult PrintReport([FromQuery] PrintReportParams query) + + [HttpGet("/preview.printReport")] + public static IResult PrintReport([FromQuery] PrintReportParams query, + IReportService reportService, + IPrintService printService, + HttpRequest request) { - if (!_reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) - return new NotFoundResult(); + if (!IsAuthorized(request)) + return Results.Unauthorized(); + + if (!reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) + return Results.NotFound(); var printMode = query.PrintMode.ToLower(); - var response = _printService.PrintReport(webReport, printMode); + var response = printService.PrintReport(webReport, printMode); if (!(response is null)) { switch (printMode) { case "html": - return new FileContentResult(response, "text/html"); + return Results.File(response, + contentType: "text/html"); #if !OPENSOURCE case "pdf": - return new FileContentResult(response, "application/pdf"); + return Results.File(response, + contentType: "application/pdf"); #endif } } - return new UnsupportedMediaTypeResult(); + return Results.StatusCode((int)HttpStatusCode.UnsupportedMediaType); } } } diff --git a/FastReport.Core.Web/Controllers/Preview/ServiceController.cs b/FastReport.Core.Web/Controllers/Preview/ServiceController.cs index 98997c8f..fec12905 100644 --- a/FastReport.Core.Web/Controllers/Preview/ServiceController.cs +++ b/FastReport.Core.Web/Controllers/Preview/ServiceController.cs @@ -1,56 +1,44 @@ -using FastReport.Web.Cache; +using FastReport.Web.Infrastructure; using FastReport.Web.Services; + +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; +using System.Net.Mime; namespace FastReport.Web.Controllers { - public sealed class ServiceController : ApiControllerBase + static partial class Controllers { - private readonly IReportService _reportService; - private readonly ITextEditService _textEditService; - - public ServiceController(IReportService reportService, ITextEditService textEditService) - { - _reportService = reportService; - _textEditService = textEditService; - } - public sealed class PrintReportParams + internal sealed class TextEditParams { public string ReportId { get; set; } public string Click { get; set; } } - [HttpGet] - [Route("/_fr/preview.textEditForm")] - public IActionResult TextEditForm([FromQuery] PrintReportParams query) + + [HttpGet("/preview.textEditForm")] + public static IResult TextEditForm([FromQuery] TextEditParams query, + IReportService _reportService, + ITextEditService _textEditService, + HttpRequest request) { + if (!IsAuthorized(request)) + return Results.Unauthorized(); + if (!_reportService.TryFindWebReport(query.ReportId, out WebReport webReport)) - return new NotFoundResult(); + return Results.NotFound(); var result = _textEditService.GetTemplateTextEditForm(query.Click, webReport); if (result != null) { - return new ContentResult() - { - StatusCode = (int)HttpStatusCode.OK, - ContentType = "text/html", - Content = result, - }; + return Results.Content(result, MediaTypeNames.Text.Html); } else - { - return new NotFoundResult(); - } + return Results.NotFound(); } } } diff --git a/FastReport.Core.Web/Controllers/Resources/ResourcesController.cs b/FastReport.Core.Web/Controllers/Resources/ResourcesController.cs index b943fb27..e4c50ba3 100644 --- a/FastReport.Core.Web/Controllers/Resources/ResourcesController.cs +++ b/FastReport.Core.Web/Controllers/Resources/ResourcesController.cs @@ -1,42 +1,36 @@ -using FastReport.Web.Services; +using FastReport.Web.Infrastructure; +using FastReport.Web.Services; + +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; +using System.Threading; using System.Threading.Tasks; namespace FastReport.Web.Controllers { - [Route("/_fr/resources.getResource")] - public sealed class ResourcesController : ApiControllerBase + static partial class Controllers { - private readonly IResourceLoader _resourceLoader; - - public ResourcesController(IResourceLoader resourceLoader) - { - _resourceLoader = resourceLoader; - } - - public sealed class GetResourceParams + internal sealed class GetResourceParams { - public string resourceName { get; set; } + public string ResourceName { get; set; } - public string contentType { get; set; } + public string ContentType { get; set; } } - [HttpGet] - public async Task GetResource([FromQuery] GetResourceParams query) + [HttpGet("/resources.getResource")] + public static async Task GetResource([FromQuery] GetResourceParams query, + IResourceLoader resourceLoader, + CancellationToken cancellationToken) { - var resource = await _resourceLoader.GetBytesAsync(query.resourceName); + var resource = await resourceLoader.GetBytesAsync(query.ResourceName, cancellationToken); if (resource == null) - return new NotFoundResult(); + return Results.NotFound(); - if (query.contentType.IsNullOrWhiteSpace()) - query.contentType = "application/octet-stream"; + if (query.ContentType.IsNullOrWhiteSpace()) + query.ContentType = "application/octet-stream"; - return new FileContentResult(resource, query.contentType); + return Results.File(resource, query.ContentType); } } diff --git a/FastReport.Core.Web/FastReport.Web.Shared.props b/FastReport.Core.Web/FastReport.Web.Shared.props index dcffee02..dad063af 100644 --- a/FastReport.Core.Web/FastReport.Web.Shared.props +++ b/FastReport.Core.Web/FastReport.Web.Shared.props @@ -8,6 +8,8 @@ Fast Reports Inc. Fast Reports Inc. + 9.0 + true frlogo192.png https://www.fast-report.com/en/product/fast-report-net diff --git a/FastReport.Core.Web/Resources/default-custom-button.svg b/FastReport.Core.Web/Resources/default-custom-button.svg new file mode 100644 index 00000000..7d4a1d19 --- /dev/null +++ b/FastReport.Core.Web/Resources/default-custom-button.svg @@ -0,0 +1,3 @@ + + + diff --git a/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs b/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs index aa2fe69a..66a8827e 100644 --- a/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs +++ b/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs @@ -32,7 +32,7 @@ public interface IConnectionsService ///
/// Returns a bool variable which means whether the error is returned or not /// Returns JSON with connected tables - string GetConnectionTables(string connectionType, string connectionString, out bool isError); + string GetConnectionTables(string connectionType, string connectionString); /// /// Returns the list of connection types diff --git a/FastReport.Core.Web/Services/Abstract/IReportService.cs b/FastReport.Core.Web/Services/Abstract/IReportService.cs index 071c3681..4dc45d44 100644 --- a/FastReport.Core.Web/Services/Abstract/IReportService.cs +++ b/FastReport.Core.Web/Services/Abstract/IReportService.cs @@ -44,5 +44,13 @@ public interface IReportService /// /// Report ID void Touch(string reportId); + + /// + /// Returns the report after clicking on an element with id = elementId + /// + /// Report to which the action applies + /// ID of the clicked item + /// Updated WebReport + Task InvokeCustomElementAction(WebReport webReport, string elementId, string inputValue); } } diff --git a/FastReport.Core.Web/Services/Implementation/ConnectionService.cs b/FastReport.Core.Web/Services/Implementation/ConnectionService.cs index 8aebbe5a..a3ed50cd 100644 --- a/FastReport.Core.Web/Services/Implementation/ConnectionService.cs +++ b/FastReport.Core.Web/Services/Implementation/ConnectionService.cs @@ -13,8 +13,6 @@ namespace FastReport.Web.Services { internal sealed class ConnectionService : IConnectionsService { - [Obsolete] - internal static ConnectionService Instance { get; } = new ConnectionService(); public string GetConnectionStringPropertiesJSON(string connectionType, string connectionString, out bool isError) { @@ -162,8 +160,13 @@ public string CreateConnectionStringJSON(string connectionType, IFormCollection } } - public string GetConnectionTables(string connectionType, string connectionString, out bool isError) + public string GetConnectionTables(string connectionType, string connectionString) { + if (!IsConnectionStringValid(connectionString, out var errorMsg)) + { + throw new Exception(errorMsg); + } + var objects = new List(); RegisteredObjects.DataConnections.EnumItems(objects); Type connType = null; @@ -198,23 +201,30 @@ public string GetConnectionTables(string connectionType, string connectionString writer.Save(ms); ms.Position = 0; - isError = false; return Encoding.UTF8.GetString(ms.ToArray()); } } } - catch (Exception ex) + catch { - isError = true; - return ex.ToString(); + throw new Exception("Error in creating tables. Please verify your connection string."); } } - else + + throw new Exception("Connection type not found"); + + } + + private static bool IsConnectionStringValid(string connectionString, out string errorMsg) + { + if (string.IsNullOrEmpty(connectionString)) { - isError = true; - return "Connection type not found"; + errorMsg = "Connection string is null or empty"; + return false; } + errorMsg = string.Empty; + return true; } public List GetConnectionTypes() diff --git a/FastReport.Core.Web/Services/Implementation/DesignerUtilsService.cs b/FastReport.Core.Web/Services/Implementation/DesignerUtilsService.cs index 2813e872..e01d4ad7 100644 --- a/FastReport.Core.Web/Services/Implementation/DesignerUtilsService.cs +++ b/FastReport.Core.Web/Services/Implementation/DesignerUtilsService.cs @@ -17,8 +17,6 @@ namespace FastReport.Web.Services { internal sealed class DesignerUtilsService : IDesignerUtilsService { - [Obsolete] - internal static DesignerUtilsService Instance { get; } = new DesignerUtilsService(); public string GetMSChartTemplateXML(string templateName) { diff --git a/FastReport.Core.Web/Services/Implementation/ExportService.cs b/FastReport.Core.Web/Services/Implementation/ExportService.cs index 97b1f87a..64d3fa41 100644 --- a/FastReport.Core.Web/Services/Implementation/ExportService.cs +++ b/FastReport.Core.Web/Services/Implementation/ExportService.cs @@ -9,8 +9,6 @@ namespace FastReport.Web.Services { internal sealed class ExportService : IExportsService { - [Obsolete] - internal static ExportService Instance { get; } = new ExportService(); public byte[] ExportReport(WebReport webReport, KeyValuePair[] exportParams, string exportFormat, out string filename) { diff --git a/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs b/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs index b1e93aa1..cba49fef 100644 --- a/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs +++ b/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs @@ -1,5 +1,4 @@ -using FastReport.Web.Services; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -16,10 +15,6 @@ namespace FastReport.Web.Services /// internal sealed class InternalResourceLoader : IResourceLoader { - #region Singletone - - [Obsolete] - public static readonly InternalResourceLoader Instance = new InternalResourceLoader(); static readonly string AssemblyName; static readonly Assembly _assembly; @@ -30,11 +25,6 @@ static InternalResourceLoader() AssemblyName = _assembly.GetName().Name; } - public InternalResourceLoader() - { - } - - #endregion readonly ConcurrentDictionary cache1 = new ConcurrentDictionary(); readonly ConcurrentDictionary cache2 = new ConcurrentDictionary(); diff --git a/FastReport.Core.Web/Services/Implementation/PrintService.cs b/FastReport.Core.Web/Services/Implementation/PrintService.cs index 68b2d504..e8fd0c59 100644 --- a/FastReport.Core.Web/Services/Implementation/PrintService.cs +++ b/FastReport.Core.Web/Services/Implementation/PrintService.cs @@ -1,4 +1,5 @@ using FastReport.Export.Html; +using FastReport.Web.Infrastructure; #if !OPENSOURCE using FastReport.Export.Pdf; #endif @@ -11,8 +12,6 @@ namespace FastReport.Web.Services { internal sealed class PrintService : IPrintService { - [Obsolete] - internal static PrintService Instance { get; } = new PrintService(); public byte[] PrintReport(WebReport webReport, string printMode) { diff --git a/FastReport.Core.Web/Services/Implementation/ReportDesignerService.cs b/FastReport.Core.Web/Services/Implementation/ReportDesignerService.cs index fb52261d..0094c63d 100644 --- a/FastReport.Core.Web/Services/Implementation/ReportDesignerService.cs +++ b/FastReport.Core.Web/Services/Implementation/ReportDesignerService.cs @@ -15,8 +15,6 @@ namespace FastReport.Web.Services { internal sealed class ReportDesignerService : IReportDesignerService { - [Obsolete] - internal static ReportDesignerService Instance { get; } = new ReportDesignerService(); public async Task GetPOSTReportAsync(Stream requestBody, CancellationToken cancellationToken = default) { @@ -57,8 +55,6 @@ public async Task SaveReportAsync(WebReport webReport, // save by using a Func string report = await GetPOSTReportAsync(@params.RequestBody); - report = FixLandscapeProperty(report); - result.Msg = string.Empty; result.Code = 200; try { @@ -94,7 +90,6 @@ private async Task DesignerSaveReport(WebReport webRepo { // paste restricted back in report before save string restrictedReport = PasteRestricted(webReport, reportString); - restrictedReport = FixLandscapeProperty(restrictedReport); webReport.Report.LoadFromString(restrictedReport); webReport.OnSaveDesignedReport(); @@ -256,10 +251,8 @@ public async Task DesignerMakePreviewAsync(WebReport webReport, string r //previewReport.Toolbar.EnableFit = true; //previewReport.Layers = true; string reportString = PasteRestricted(webReport, receivedReportString); - reportString = FixLandscapeProperty(reportString); previewReport.Report.ReportResourceString = reportString; // TODO //previewReport.ReportFile = String.Empty; - previewReport.ReportResourceString = reportString; // TODO previewReport.Mode = WebReportMode.Preview; previewReport.Report.PreparedPages?.Clear(); @@ -478,31 +471,6 @@ string PasteRestricted(WebReport webReport, string xmlString) return xmlString; } - private static string FixLandscapeProperty(string reportString) - { - int indexOfLandscape = reportString.IndexOf(nameof(ReportPage.Landscape)); - if (indexOfLandscape != -1) - { - // Landscape="~" - int lastIndexOfLandscapeValue = - reportString.IndexOf('"', indexOfLandscape + nameof(ReportPage.Landscape).Length + 2, 10); - - var indexOfPage = reportString.IndexOf(nameof(ReportPage), 0, indexOfLandscape); - int startposition = indexOfPage + nameof(ReportPage).Length + 1; - if (indexOfLandscape == startposition) - return reportString; - - StringBuilder sb = new StringBuilder(reportString); - var property = reportString.Substring(indexOfLandscape, lastIndexOfLandscapeValue - indexOfLandscape + 2); - - sb.Remove(indexOfLandscape, property.Length); - - sb.Insert(startposition, property); - reportString = sb.ToString(); - } - return reportString; - } - private static void CopyCookies(HttpWebRequest request, SaveReportServiceParams @params) { CookieContainer cookieContainer = new CookieContainer(); diff --git a/FastReport.Core.Web/Services/Implementation/ReportService.cs b/FastReport.Core.Web/Services/Implementation/ReportService.cs index 133fe88d..d4c3c199 100644 --- a/FastReport.Core.Web/Services/Implementation/ReportService.cs +++ b/FastReport.Core.Web/Services/Implementation/ReportService.cs @@ -6,13 +6,20 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Linq; +using FastReport.Web.Toolbar; namespace FastReport.Web.Services { internal sealed class ReportService : IReportService { - [Obsolete] - internal static ReportService Instance { get; } = new ReportService(); + + private readonly IWebReportCache _cache; + + public ReportService(IWebReportCache webReportCache) + { + _cache = webReportCache; + } public string GetReport(WebReport webReport, GetReportServiceParams @params) { @@ -47,20 +54,62 @@ public string GetReport(WebReport webReport, GetReportServiceParams @params) return webReport.Render(renderBodyBool).ToString(); } - public async Task GetReportAsync(WebReport webReport, GetReportServiceParams @params, CancellationToken cancellationToken = default) + public Task GetReportAsync(WebReport webReport, GetReportServiceParams @params, CancellationToken cancellationToken = default) + { + return Task.FromResult(GetReport(webReport, @params)); + } + + public async Task InvokeCustomElementAction(WebReport webReport, string elementId, string inputValue) { - return await Task.FromResult(GetReport(webReport, @params)); + var element = webReport.Toolbar.Elements.FirstOrDefault(e => + { + switch (e) + { + case ToolbarButton button: + return button.ID.ToString() == elementId; + case ToolbarInput input: + return input.ID.ToString() == elementId; + case ToolbarSelect select: + return select.Items.Any(i => i.ID.ToString() == elementId); + default: + return false; + } + }); + + switch (element) + { + case ToolbarSelect toolbarSelect: + { + var item = toolbarSelect.Items.FirstOrDefault(i => i.ID.ToString() == elementId); + + if (item?.OnClickAction is ElementClickAction elementAction) + await elementAction.OnClickAction(webReport); + break; + } + case ToolbarButton button: + { + if (button.OnClickAction is ElementClickAction elementAction && elementAction.OnClickAction != null) + await elementAction.OnClickAction(webReport); + break; + } + case ToolbarInput input when input.OnChangeAction is ElementChangeAction elementAction: + await elementAction.OnChangeAction(webReport, inputValue); + break; + } + + return webReport.Render(true).ToString(); } + public bool TryFindWebReport(string reportId, out WebReport webReport) { - webReport = WebReportCache.Instance.Find(reportId); + webReport = _cache.Find(reportId); return webReport != null; } public void Touch(string reportId) { - WebReportCache.Instance.Touch(reportId); + _cache.Touch(reportId); } } } diff --git a/FastReport.Core.Web/Services/Implementation/TextEditService.cs b/FastReport.Core.Web/Services/Implementation/TextEditService.cs index c9564efa..f235b921 100644 --- a/FastReport.Core.Web/Services/Implementation/TextEditService.cs +++ b/FastReport.Core.Web/Services/Implementation/TextEditService.cs @@ -8,7 +8,6 @@ namespace FastReport.Web.Services { internal sealed class TextEditService : ITextEditService { - internal static TextEditService Instance { get; } = new TextEditService(); public string GetTemplateTextEditForm(string click, WebReport webReport) { diff --git a/FastReport.Core.Web/Services/ServicesParamsModels.cs b/FastReport.Core.Web/Services/ServicesParamsModels.cs index 6a011ea1..90437ebe 100644 --- a/FastReport.Core.Web/Services/ServicesParamsModels.cs +++ b/FastReport.Core.Web/Services/ServicesParamsModels.cs @@ -50,13 +50,20 @@ public class GetReportServiceParams public string ForceRefresh { get; set; } public string Zoom { get; set; } - public void ParseRequest(HttpRequest request) + public static GetReportServiceParams ParseRequest(HttpRequest request) { - Zoom = request.Query["zoom"].ToString(); + var reportServiceParams = new GetReportServiceParams + { + SkipPrepare = request.Query["skipPrepare"].ToString(), + ForceRefresh = request.Query["forceRefresh"].ToString(), + RenderBody = request.Query["renderBody"].ToString(), + }; + + reportServiceParams.Zoom = request.Query["zoom"].ToString(); - DialogParams = new DialogParams(); + reportServiceParams.DialogParams = new DialogParams(); - ReportPageParams = new ReportPageParams + reportServiceParams.ReportPageParams = new ReportPageParams { GoTo = request.Query["goto"].ToString(), Bookmark = request.Query["bookmark"].ToString(), @@ -64,13 +71,13 @@ public void ParseRequest(HttpRequest request) DetailedPage = request.Query["detailed_page"].ToString() }; - ReportTabParams = new ReportTabParams + reportServiceParams.ReportTabParams = new ReportTabParams { SetTab = request.Query["settab"].ToString(), CloseTab = request.Query["closetab"].ToString() }; - ClickParams = new ClickParams + reportServiceParams.ClickParams = new ClickParams { Click = request.Query["click"].ToString(), CheckBoxClick = request.Query["checkbox_click"].ToString(), @@ -78,10 +85,11 @@ public void ParseRequest(HttpRequest request) AdvMatrixClick = request.Query["advmatrix_click"].ToString() }; - if (!ClickParams.TextEdit.IsNullOrEmpty()) - ClickParams.Text = request.Form["text"].ToString(); + if (!reportServiceParams.ClickParams.TextEdit.IsNullOrEmpty()) + reportServiceParams.ClickParams.Text = request.Form["text"].ToString(); - DialogParams.ParseRequest(request); + reportServiceParams.DialogParams.ParseRequest(request); + return reportServiceParams; } } diff --git a/FastReport.Core.Web/Templates/main.cs b/FastReport.Core.Web/Templates/main.cs index 8edb9af3..8ab02fa9 100644 --- a/FastReport.Core.Web/Templates/main.cs +++ b/FastReport.Core.Web/Templates/main.cs @@ -1,4 +1,6 @@ -using System.Net; +using FastReport.Web.Infrastructure; + +using System.Net; namespace FastReport.Web { diff --git a/FastReport.Core.Web/Templates/outline.cs b/FastReport.Core.Web/Templates/outline.cs index 8badaa4c..3b2d5b1e 100644 --- a/FastReport.Core.Web/Templates/outline.cs +++ b/FastReport.Core.Web/Templates/outline.cs @@ -29,7 +29,7 @@ string template_outline() "; diff --git a/FastReport.Core.Web/Templates/script.cs b/FastReport.Core.Web/Templates/script.cs index 6b232450..c7490c9e 100644 --- a/FastReport.Core.Web/Templates/script.cs +++ b/FastReport.Core.Web/Templates/script.cs @@ -136,6 +136,29 @@ string template_script() => $@" this._reload('&skipPrepare=yes&' + kind + '=' + value); }}, + customMethodInvoke: function(elementId, inputValue){{ + var that = this; + var body = this._findBody(); + var container = this._findContainer(); + + this._fetch({{ + method: 'POST', + url: '{template_ROUTE_BASE_PATH}/preview.toolbarElementClick?reportId={ID}&elementId=' + elementId + '&inputValue=' + inputValue, + onSend: function () {{ + that._activateSpinner(); + }}, + onSuccess: function (xhr) {{ + container.outerHTML = xhr.responseText; + that._execScripts(); + }}, + onError: function (xhr) {{ + that._placeError(xhr, body); + that._deactivateSpinner(); + }} + }}); + }}, + + settab: function (tab) {{ this._reload('&skipPrepare=yes&settab=' + tab); }}, diff --git a/FastReport.Core.Web/Templates/style.cs b/FastReport.Core.Web/Templates/style.cs index 5e6613a4..01663dcf 100644 --- a/FastReport.Core.Web/Templates/style.cs +++ b/FastReport.Core.Web/Templates/style.cs @@ -28,6 +28,7 @@ string template_style() => $@" overflow: hidden; width: 100%; height: 100%; + margin-top: 20px; }} .{template_FR}-report {{ @@ -38,6 +39,10 @@ string template_style() => $@" align-items: flex-start; }} +.{template_FR}-spinner[style*=""display:none""] ~ .{template_FR}-toolbar ~ .{template_FR}-body {{ + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25); +}} + /*********** SPLIT ************/ @@ -120,6 +125,10 @@ string template_style() => $@" opacity: 1; }} +.{template_FR}-toolbar-image{{ + width: calc({Toolbar.Height}px * 0.5); +}} + /********************** TOOLBAR DROPDOWN ***********************/ diff --git a/FastReport.Core.Web/Templates/toolbar.cs b/FastReport.Core.Web/Templates/toolbar.cs index 5cd62028..6adb4368 100644 --- a/FastReport.Core.Web/Templates/toolbar.cs +++ b/FastReport.Core.Web/Templates/toolbar.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace FastReport.Web @@ -20,7 +21,7 @@ string template_toolbar(bool renderBody) var exports = Toolbar.Exports; var toolbarExportItem = $@"
-
" +
" + (exports.ShowPreparedReport ? $@"{localization.preparedTxt}" : "") #if !OPENSOURCE + (exports.ShowPdfExport ? $@"{localization.pdfTxt}":"") @@ -32,7 +33,7 @@ string template_toolbar(bool renderBody) + (exports.ShowPowerPoint2007Export ? $@"{localization.powerPoint2007Txt}" : "") + (exports.EnableSettings && exports.ShowPowerPoint2007Export ? $@"" : "") + (exports.ShowTextExport ? $@"{localization.textTxt}" : "") - + (exports.ShowRtfExport ? $@"{localization.rtfTxt}" : "") + + (exports.ShowRtfExport ? $@"{localization.rtfTxt}" : "") + (exports.EnableSettings && exports.ShowRtfExport ? $@"" : "") + (exports.ShowXpsExport ? $@"{localization.xpsTxt}" : "") + (exports.ShowOdsExport ? $@"{localization.odsTxt}" : "") @@ -79,6 +80,8 @@ string template_toolbar(bool renderBody) var selectedZoom2 = $@"
"; var isFirstPage = CurrentPageIndex == 0; var isLastPage = CurrentPageIndex >= TotalPages - 1; + var isSinglePage = SinglePage || TotalPages < 2; + var customButtons = string.Join("", Toolbar.Elements.Select(x => x.Render(template_FR))); string templateToolbar = $@"
@@ -104,10 +107,7 @@ string template_toolbar(bool renderBody) {(currentZoom == 50 ? selectedZoom1 : selectedZoom2)}50%
{(currentZoom == 25 ? selectedZoom1 : selectedZoom2)}25%
-
" : "")} - - -{((SinglePage || TotalPages < 2) ? "" : $@" +
" : "")}" + $@" {(Toolbar.ShowFirstButton ? $@"
" : "")} @@ -117,15 +117,15 @@ string template_toolbar(bool renderBody) " : "")}
- TotalPages ? TotalPages : (CurrentPageIndex + 1))}"" onchange=""{template_FR}.goto(document.getElementsByClassName('{template_FR}-current-page-input')[0].value);"" title=""{localization.currentPageTxt}""> + TotalPages ? TotalPages : (CurrentPageIndex + 1))}"" onchange=""{template_FR}.goto(document.getElementsByClassName('{template_FR}-current-page-input')[0].value);"" title=""{localization.currentPageTxt}"">
-
+
- +
{(Toolbar.ShowNextButton ? $@"
@@ -135,8 +135,7 @@ string template_toolbar(bool renderBody) {(Toolbar.ShowLastButton ? $@"
" : "")} - -")} +{customButtons}
{template_tabs()} diff --git a/FastReport.OpenSource/Utils/FRPrivateFontCollection.OpenSource.cs b/FastReport.OpenSource/Utils/FRPrivateFontCollection.OpenSource.cs index 4f271ffe..e0de5e60 100644 --- a/FastReport.OpenSource/Utils/FRPrivateFontCollection.OpenSource.cs +++ b/FastReport.OpenSource/Utils/FRPrivateFontCollection.OpenSource.cs @@ -12,11 +12,11 @@ partial class FRPrivateFontCollection private void RegisterFontInternal(string filename) { string fontName = Families[Families.Length - 1].Name; - if (!FontFiles.ContainsKey(fontName)) - FontFiles.Add(fontName, filename); + if (!_fonts.ContainsKey(fontName)) + _fonts.Add(fontName, new FontFromFile(filename)); #if DEBUG else - Console.WriteLine("Font \"{0}\" already present in collection.\n Files:\n {1}\n {2}\n", fontName, FontFiles[fontName], filename); + Console.WriteLine("Font \"{0}\" already present in collection.\n Files:\n {1}\n {2}\n", fontName, _fonts[fontName], filename); #endif } diff --git a/UsedPackages.version b/UsedPackages.version index 411b4ffe..3b9f53f8 100644 --- a/UsedPackages.version +++ b/UsedPackages.version @@ -1,22 +1,22 @@  - + - 2022.3.2 + [2023.3.0] - [2023.1.8] - [2023.2.0] + [2023.3.0] + [2023.3.0] - [2023.1.0] - [2023.2.0] + [2023.3.0] + [2023.3.0] - - 2023.1.0 - 2023.1.0 - 2023.1.0 - 2023.1.0 - 2023.1.0 + + 2023.3.0 + 2023.3.0 + 2023.2.1 + 2023.3.0 + 2023.3.0 [4.7.0,)