of
";
469 |
470 | public bool preferCSSPageSize { get; set; } = false;
471 | public bool generateDocumentOutline { get; set; } = true;
472 |
473 | public string ToJson()
474 | {
475 | pageRanges = pageRanges ?? string.Empty;
476 | headerTemplate = headerTemplate ?? string.Empty;
477 | footerTemplate = footerTemplate ?? string.Empty;
478 |
479 | #if DEBUG
480 | var json = JsonConvert.SerializeObject(this, Formatting.Indented);
481 | Debug.WriteLine("PrintToPdf DevTools:\n" + json);
482 | return json;
483 | #endif
484 |
485 | return JsonConvert.SerializeObject(this, Formatting.None);
486 | }
487 | }
488 |
--------------------------------------------------------------------------------
/Westwind.WebView/HtmlToPdf/Enums.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Westwind.WebView.HtmlToPdf
8 | {
9 | public enum WebViewPrintColorModes
10 | {
11 | Color,
12 | Grayscale,
13 | }
14 |
15 | public enum WebViewPrintOrientations
16 | {
17 | Portrait,
18 | Landscape
19 | }
20 |
21 | public enum WebViewPrintCollations
22 | {
23 | Default,
24 | Collated,
25 | UnCollated
26 | }
27 |
28 | public enum WebViewPrintDuplexes
29 | {
30 | Default,
31 | OneSided,
32 | TwoSidedLongEdge,
33 | TwoSidedShortEdge
34 |
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Westwind.WebView/HtmlToPdf/HtmlToPdfDefaults.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Westwind.WebView.HtmlToPdf
9 | {
10 | public class HtmlToPdfDefaults
11 | {
12 | ///
13 | /// Specify the background color of the PDF frame which contains
14 | /// the margins of the document.
15 | ///
16 | /// Defaults to white, but if you use a non-white background for your
17 | /// document you'll likely want to match it to your document background.
18 | ///
19 | /// Also note that non-white colors may have to use custom HeaderTemplate and
20 | /// FooterTemplate to set the foregraound color of the text to match the background.
21 | ///
22 | public static string BackgroundHtmlColor { get; set; } = "#ffffff";
23 |
24 | ///
25 | /// If true uses WebView.PrintToPdfStreamAsync() rather than the DevTools version
26 | /// to generate PDF output. Use this for server operation as the DevTools printing
27 | /// is not supported in server environments like IIS.
28 | ///
29 | public static bool UseServerPdfGeneration { get; set; } = false;
30 |
31 |
32 | ///
33 | /// The default folder location for the WebView environment folder that is used when
34 | /// no explicit path is provided. This is a static value that is global to
35 | /// the Application, so it's best set during application startup to ensure
36 | /// that the same folder is used.
37 | ///
38 | /// Make sure this folder is writable by the application's user identity!
39 | ///
40 | public static string WebViewEnvironmentPath { get; set; } = Path.Combine(Path.GetTempPath(), "WebView2_Environment");
41 |
42 |
43 |
44 | ///
45 | /// Pre-initializes the Print Service which is necessary when running under
46 | /// server environment
47 | ///
48 | ///
49 | /// Provide a folder where the WebView Environment can be written to.
50 | ///
51 | /// *** IMPORTANT: ***
52 | /// This location has to be writeable using the server's identity so
53 | /// be sure to set folder permissions for limited user accounts.
54 | ///
55 | /// Defaults to the User Temp folder, but for server apps that folder may
56 | /// not be accessible, so it's best to explicitly set and configure this
57 | /// folder.
58 | ///
59 | public static void ServerPreInitialize(string defaultWebViewEnvironmentPath = null)
60 | {
61 | if (!string.IsNullOrEmpty(defaultWebViewEnvironmentPath))
62 | HtmlToPdfDefaults.WebViewEnvironmentPath = defaultWebViewEnvironmentPath;
63 |
64 | UseServerPdfGeneration = true;
65 |
66 | Task.Run(async () =>
67 | {
68 | try
69 | {
70 | var host = new HtmlToPdfHost();
71 | var result = await host.PrintToPdfStreamAsync("about:blank");
72 | }
73 | catch
74 | {
75 | }
76 | });
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Westwind.WebView/HtmlToPdf/PdfPrintOutputModes.cs:
--------------------------------------------------------------------------------
1 | namespace Westwind.WebView.HtmlToPdf
2 | {
3 | internal enum PdfPrintOutputModes
4 | {
5 | File,
6 | Stream
7 | }
8 | }
--------------------------------------------------------------------------------
/Westwind.WebView/HtmlToPdf/PdfPrintResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Westwind.WebView.HtmlToPdf
5 | {
6 |
7 | ///
8 | /// Result from a Print to PDF operation. ResultStream is set only
9 | /// on stream operations.
10 | ///
11 | public class PdfPrintResult
12 | {
13 | ///
14 | /// Notifies of sucess or failure of operation
15 | ///
16 | public bool IsSuccess { get; set; }
17 |
18 | ///
19 | /// If in stream mode, the resulting MemoryStream will be assigned
20 | /// to this property. You need to close/dispose of this stream when
21 | /// done with it.
22 | ///
23 | public Stream ResultStream { get; set; }
24 |
25 | ///
26 | /// A message related to the operation - use for error messages if
27 | /// an error occured.
28 | ///
29 | public string Message { get; set; }
30 |
31 | ///
32 | /// The exception that triggered a failed PDF conversion operation
33 | ///
34 | public Exception LastException { get; set; }
35 | }
36 | }
--------------------------------------------------------------------------------
/Westwind.WebView/HtmlToPdf/WebViewPrintSettings.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Web.WebView2.Core;
2 |
3 | namespace Westwind.WebView.HtmlToPdf
4 | {
5 |
6 | ///
7 | /// Proxy object of Core WebView settings options to avoid requiring
8 | /// a direct reference to the WebView control in the calling
9 | /// application/project.
10 | ///
11 | /// Settings map to these specific settings in the WebView:
12 | /// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
13 | ///
14 | public class WebViewPrintSettings
15 | {
16 | ///
17 | /// Scale Factor up to 2
18 | ///
19 | public float ScaleFactor
20 | {
21 | get => _scaleFactor;
22 | set
23 | {
24 | _scaleFactor = value;
25 | if (_scaleFactor > 2F)
26 | ScaleFactor = 2F;
27 | }
28 | }
29 | private float _scaleFactor = 1F;
30 |
31 |
32 | ///
33 | /// Portrait, Landscape
34 | ///
35 | public WebViewPrintOrientations Orientation { get; set; } = WebViewPrintOrientations.Portrait;
36 |
37 | ///
38 | /// Width in inches
39 | ///
40 | public double PageWidth { get; set; } = 8.5F;
41 |
42 | ///
43 | /// Height in inches
44 | ///
45 | public double PageHeight { get; set; } = 11F;
46 |
47 |
48 | ///
49 | /// Top Margin in inches
50 | ///
51 | public double MarginTop { get; set; } = 0.4F;
52 |
53 | ///
54 | /// Bottom Margin in inches
55 | ///
56 | public double MarginBottom { get; set; } = 0.30F;
57 |
58 | ///
59 | /// Left Margin in inches
60 | ///
61 | public double MarginLeft { get; set; } = 0.25F;
62 |
63 | ///
64 | /// Right Margin in inches
65 | ///
66 | public double MarginRight { get; set; } = 0.25F;
67 |
68 |
69 | ///
70 | /// Page ranges as specified 1,2,3,5-7
71 | ///
72 | public string PageRanges { get; set; } = string.Empty;
73 |
74 |
75 | ///
76 | /// Determines whether background colors are printed. Use to
77 | /// save ink on printing or for more legible in print/pdf scenarios
78 | ///
79 | public bool ShouldPrintBackgrounds { get; set; } = true;
80 |
81 |
82 | ///
83 | /// Color, Grayscale, Monochrome
84 | ///
85 | /// CURRENTLY DOESN'T WORK FOR PDF GENERATION
86 | ///
87 | public WebViewPrintColorModes ColorMode { get; set; } = WebViewPrintColorModes.Color;
88 |
89 |
90 | ///
91 | /// When true prints only the section of the document selected
92 | ///
93 | public bool ShouldPrintSelectionOnly { get; set; } = false;
94 |
95 | ///
96 | /// Determines whether headers and footers are printed
97 | ///
98 | public bool ShouldPrintHeaderAndFooter { get; set; } = false;
99 |
100 |
101 | public bool GenerateDocumentOutline { get; set; } = true;
102 |
103 |
104 | ///
105 | /// Html Template that renders the header.
106 | /// Refer to for embeddable styles and formatting:
107 | /// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
108 | ///
109 | public string HeaderTemplate { get; set; } = "
";
110 |
111 |
112 | ///
113 | /// Html template that renders the footer
114 | /// Refer to for embeddable styles and formatting:
115 | /// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
116 | ///
117 | public string FooterTemplate { get; set; } = "
of
";
118 |
119 |
120 |
121 | ///
122 | /// This a shortcut for the HeaderTemplate that sets the top of the page header. For more control
123 | /// set the HeaderTemplate directly.
124 | ///
125 | public string HeaderTitle
126 | {
127 | set
128 | {
129 | if (string.IsNullOrEmpty(value))
130 | HeaderTemplate = string.Empty;
131 | else
132 | HeaderTemplate = $"
{value}
";
133 | }
134 | }
135 |
136 | ///
137 | /// This a shortcut for the FooterTemplate that sets the bottom of the page footer. For more control
138 | /// set the FooterTemplate directly.
139 | ///
140 | public string FooterText
141 | {
142 | set
143 | {
144 | if (string.IsNullOrEmpty(value))
145 | FooterTemplate = string.Empty;
146 | else
147 | FooterTemplate = $"
{value}
";
148 | }
149 | }
150 |
151 | #region Print Settings - ignored for PDF
152 |
153 | ///
154 | /// Printer name when printing to a printer (not applicable for PDF)
155 | ///
156 | /// NO EFFECT ON PDF PRINTING
157 | ///
158 | public string PrinterName { get; set; }
159 |
160 | ///
161 | /// Number of Copies to print
162 | ///
163 | /// NO EFFECT ON PDF PRINTING
164 | ///
165 | public int Copies { get; set; } = 1;
166 |
167 | ///
168 | /// Default, OneSided, TwoSidedLongEdge, TwoSidedShortEdge
169 | ///
170 | /// NO EFFECT ON PDF PRINTING
171 | ///
172 | public WebViewPrintDuplexes Duplex { get; set; } = WebViewPrintDuplexes.Default;
173 |
174 | ///
175 | /// Default, Collated, Uncollated
176 | ///
177 | /// NO EFFECT OF PDF PRINTING
178 | ///
179 | public WebViewPrintCollations Collation { get; set; } = WebViewPrintCollations.Default;
180 |
181 | ///
182 | /// Allows multiple pages to be packed into a single page.
183 | ///
184 | /// NO EFFECT ON PDF PRINTING
185 | ///
186 | public int PagesPerSide { get; set; } = 1;
187 |
188 | #endregion
189 | }
190 | }
--------------------------------------------------------------------------------
/Westwind.WebView/Utilities/AsyncUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace Westwind.WebView.Utilities
9 | {
10 | ///
11 | /// Helper class to run async methods within a sync process.
12 | /// Source: https://www.ryadel.com/en/asyncutil-c-helper-class-async-method-sync-result-wait/
13 | ///
14 | public static class AsyncUtils
15 | {
16 | private static readonly TaskFactory _taskFactory = new
17 | TaskFactory(CancellationToken.None,
18 | TaskCreationOptions.None,
19 | TaskContinuationOptions.None,
20 | TaskScheduler.Default);
21 |
22 | ///
23 | /// Executes an async Task method which has a void return value synchronously
24 | /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
25 | ///
26 | ///
Task method to execute
27 | public static void RunSync(Func
task)
28 | => _taskFactory
29 | .StartNew(task)
30 | .Unwrap()
31 | .GetAwaiter()
32 | .GetResult();
33 |
34 | ///
35 | /// Executes an async Task method which has a void return value synchronously
36 | /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
37 | ///
38 | /// Task method to execute
39 | public static void RunSync(Func task,
40 | CancellationToken cancellationToken,
41 | TaskCreationOptions taskCreation = TaskCreationOptions.None,
42 | TaskContinuationOptions taskContinuation = TaskContinuationOptions.None,
43 | TaskScheduler taskScheduler = null)
44 | {
45 | if (taskScheduler == null)
46 | taskScheduler = TaskScheduler.Default;
47 |
48 | new TaskFactory(cancellationToken,
49 | taskCreation,
50 | taskContinuation,
51 | taskScheduler)
52 | .StartNew(task)
53 | .Unwrap()
54 | .GetAwaiter()
55 | .GetResult();
56 | }
57 |
58 | ///
59 | /// Executes an async Task<T> method which has a T return type synchronously
60 | /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
61 | ///
62 | /// Return Type
63 | /// Task<T> method to execute
64 | ///
65 | public static TResult RunSync(Func> task)
66 | => _taskFactory
67 | .StartNew(task)
68 | .Unwrap()
69 | .GetAwaiter()
70 | .GetResult();
71 |
72 |
73 | ///
74 | /// Executes an async Task<T> method which has a T return type synchronously
75 | /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
76 | ///
77 | /// Return Type
78 | /// Task<T> method to execute
79 | ///
80 | public static TResult RunSync(Func> func,
81 | CancellationToken cancellationToken,
82 | TaskCreationOptions taskCreation = TaskCreationOptions.None,
83 | TaskContinuationOptions taskContinuation = TaskContinuationOptions.None,
84 | TaskScheduler taskScheduler = null)
85 | {
86 | if (taskScheduler == null)
87 | taskScheduler = TaskScheduler.Default;
88 |
89 | return new TaskFactory(cancellationToken,
90 | taskCreation,
91 | taskContinuation,
92 | taskScheduler)
93 | .StartNew(func, cancellationToken)
94 | .Unwrap()
95 | .GetAwaiter()
96 | .GetResult();
97 | }
98 |
99 |
100 | ///
101 | /// Ensures safe operation of a task without await even if
102 | /// an execution fails with an exception. This forces the
103 | /// exception to be cleared unlike a non-continued task.
104 | ///
105 | /// Task Instance
106 | public static void FireAndForget(this Task t)
107 | {
108 | t.ContinueWith(tsk => tsk.Exception,
109 | TaskContinuationOptions.OnlyOnFaulted);
110 | }
111 |
112 |
113 | ///
114 | /// Ensures safe operation of a task without await even if
115 | /// an execution fails with an exception. This forces the
116 | /// exception to be cleared unlike a non-continued task.
117 | ///
118 | /// This version allows you to capture and respond to any
119 | /// exceptions caused by the Task code executing.
120 | ///
121 | ///
122 | /// Action delegate that receives an Exception parameter you can use to log or otherwise handle (or ignore) any exceptions
123 | public static void FireAndForget(this Task t, Action del)
124 | {
125 | t.ContinueWith( (tsk) => del?.Invoke(tsk.Exception), TaskContinuationOptions.OnlyOnFaulted);
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Westwind.WebView/Utilities/JsonSerializationUtils.cs:
--------------------------------------------------------------------------------
1 | #region License
2 | /*
3 | **************************************************************
4 | * Author: Rick Strahl
5 | * © West Wind Technologies, 2008 - 2009
6 | * http://www.west-wind.com/
7 | *
8 | * Created: 09/08/2008
9 | *
10 | * Permission is hereby granted, free of charge, to any person
11 | * obtaining a copy of this software and associated documentation
12 | * files (the "Software"), to deal in the Software without
13 | * restriction, including without limitation the rights to use,
14 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | * copies of the Software, and to permit persons to whom the
16 | * Software is furnished to do so, subject to the following
17 | * conditions:
18 | *
19 | * The above copyright notice and this permission notice shall be
20 | * included in all copies or substantial portions of the Software.
21 | *
22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29 | * OTHER DEALINGS IN THE SOFTWARE.
30 | **************************************************************
31 | */
32 | #endregion
33 |
34 | using System;
35 | using System.IO;
36 | using System.Text;
37 | using System.Diagnostics;
38 | using Newtonsoft.Json;
39 | using Newtonsoft.Json.Converters;
40 | using Newtonsoft.Json.Linq;
41 | using Newtonsoft.Json.Serialization;
42 |
43 | namespace Westwind.Utilities
44 | {
45 | ///
46 | /// JSON Serialization helper class that uses JSON.NET.
47 | /// This class serializes JSON to and from string and
48 | /// files on disk.
49 | ///
50 | ///
51 | /// JSON.NET is loaded dynamically at runtime to avoid hard
52 | /// linking the Newtonsoft.Json.dll to Westwind.Utilities.
53 | /// Just make sure that your project includes a reference
54 | /// to JSON.NET when using this class.
55 | ///
56 | internal static class JsonSerializationUtils
57 | {
58 | //capture reused type instances
59 | private static JsonSerializer JsonNet = null;
60 |
61 | private static object SyncLock = new Object();
62 |
63 |
64 |
65 | ///
66 | /// Serializes an object to an XML string. Unlike the other SerializeObject overloads
67 | /// this methods *returns a string* rather than a bool result!
68 | ///
69 | /// Value to serialize
70 | /// if true
71 | /// Determines if a failure throws or returns null
72 | ///
73 | /// null on error otherwise the Xml String.
74 | ///
75 | ///
76 | /// If null is passed in null is also returned so you might want
77 | /// to check for null before calling this method.
78 | ///
79 | public static string Serialize(object value, bool formatJsonOutput = false, bool camelCase = false, bool throwExceptions = false, bool forceNewInstance = false )
80 | {
81 | if (value is null) return "null";
82 |
83 | string jsonResult = null;
84 | Type type = value.GetType();
85 | JsonTextWriter writer = null;
86 | try
87 | {
88 | if (forceNewInstance)
89 | JsonNet = null;
90 | var json = CreateJsonNet(throwExceptions, camelCase);
91 |
92 | StringWriter sw = new StringWriter();
93 |
94 | writer = new JsonTextWriter(sw);
95 |
96 | if (formatJsonOutput)
97 | writer.Formatting = Formatting.Indented;
98 |
99 |
100 | writer.QuoteChar = '"';
101 | json.Serialize(writer, value);
102 |
103 | jsonResult = sw.ToString();
104 | writer.Close();
105 | }
106 | catch
107 | {
108 | if (throwExceptions)
109 | throw;
110 |
111 | jsonResult = null;
112 | }
113 | finally
114 | {
115 | if (writer != null)
116 | writer.Close();
117 | }
118 |
119 | return jsonResult;
120 | }
121 |
122 | ///
123 | /// Serializes an object instance to a JSON file.
124 | ///
125 | /// the value to serialize
126 | /// Full path to the file to write out with JSON.
127 | /// Determines whether exceptions are thrown or false is returned
128 | /// if true pretty-formats the JSON with line breaks
129 | /// true or false
130 | public static bool SerializeToFile(object value, string fileName, bool throwExceptions = false, bool formatJsonOutput = false, bool camelCase = false)
131 | {
132 | try
133 | {
134 | Type type = value.GetType();
135 |
136 | var json = CreateJsonNet(throwExceptions, camelCase);
137 | if (json == null)
138 | return false;
139 |
140 |
141 | using (FileStream fs = new FileStream(fileName, FileMode.Create))
142 | {
143 | using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
144 | {
145 | using (var writer = new JsonTextWriter(sw))
146 | {
147 | if (formatJsonOutput)
148 | writer.Formatting = Formatting.Indented;
149 |
150 | writer.QuoteChar = '"';
151 | json.Serialize(writer, value);
152 | }
153 | }
154 | }
155 | }
156 | catch (Exception ex)
157 | {
158 | Debug.WriteLine("JsonSerializer Serialize error: " + ex.Message);
159 | if (throwExceptions)
160 | throw;
161 | return false;
162 | }
163 |
164 | return true;
165 | }
166 |
167 | ///
168 | /// Deserializes an object, array or value from JSON string to an object or value
169 | ///
170 | ///
171 | ///
172 | ///
173 | ///
174 | public static object Deserialize(string jsonText, Type type, bool throwExceptions = false)
175 | {
176 | var json = CreateJsonNet(throwExceptions);
177 | if (json == null)
178 | return null;
179 |
180 | object result = null;
181 | JsonTextReader reader = null;
182 | try
183 | {
184 | StringReader sr = new StringReader(jsonText);
185 | reader = new JsonTextReader(sr);
186 | result = json.Deserialize(reader, type);
187 | reader.Close();
188 | }
189 | catch (Exception ex)
190 | {
191 | Debug.WriteLine("JsonSerializer Deserialize error: " + ex.Message);
192 | if (throwExceptions)
193 | throw;
194 |
195 | return null;
196 | }
197 | finally
198 | {
199 | if (reader != null)
200 | reader.Close();
201 | }
202 |
203 | return result;
204 | }
205 |
206 | ///
207 | /// Deserializes an object, array or value from JSON string to an object or value
208 | ///
209 | ///
210 | ///
211 | ///
212 | public static T Deserialize(string jsonText, bool throwExceptions = false)
213 | {
214 | var res = Deserialize(jsonText, typeof(T), throwExceptions);
215 | if (res == null)
216 | return default(T);
217 |
218 | return (T)res;
219 | }
220 |
221 | ///
222 | /// Deserializes an object from file and returns a reference.
223 | ///
224 | /// name of the file to serialize to
225 | /// The Type of the object. Use typeof(yourobject class)
226 | /// determines whether we use Xml or Binary serialization
227 | /// determines whether failure will throw rather than return null on failure
228 | /// Instance of the deserialized object or null. Must be cast to your object type
229 | public static object DeserializeFromFile(string fileName, Type objectType, bool throwExceptions = false)
230 | {
231 | var json = CreateJsonNet(throwExceptions);
232 | if (json == null)
233 | return null;
234 |
235 | object result;
236 | JsonTextReader reader;
237 | FileStream fs;
238 |
239 | try
240 | {
241 | using (fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
242 | {
243 | using (var sr = new StreamReader(fs, Encoding.UTF8))
244 | {
245 | using (reader = new JsonTextReader(sr))
246 | {
247 | result = json.Deserialize(reader, objectType);
248 | }
249 | }
250 | }
251 | }
252 | catch (Exception ex)
253 | {
254 | Debug.WriteLine("JsonNetSerialization Deserialization Error: " + ex.Message);
255 | if (throwExceptions)
256 | throw;
257 |
258 | return null;
259 | }
260 |
261 | return result;
262 | }
263 |
264 | ///
265 | /// Deserializes an object from file and returns a reference.
266 | ///
267 | /// name of the file to serialize to
268 | /// determines whether we use Xml or Binary serialization
269 | /// determines whether failure will throw rather than return null on failure
270 | /// Instance of the deserialized object or null. Must be cast to your object type
271 | public static T DeserializeFromFile(string fileName, bool throwExceptions = false)
272 | {
273 | var res = DeserializeFromFile(fileName, typeof(T), throwExceptions);
274 | if (res == null)
275 | return default(T);
276 | return (T)res;
277 | }
278 |
279 | ///
280 | /// Takes a single line JSON string and pretty formats
281 | /// it using indented formatting.
282 | ///
283 | ///
284 | ///
285 | public static string FormatJsonString(string json)
286 | {
287 | return JToken.Parse(json).ToString(Formatting.Indented) as string;
288 | }
289 |
290 | ///
291 | /// Dynamically creates an instance of JSON.NET
292 | ///
293 | /// If true throws exceptions otherwise returns null
294 | /// Dynamic JsonSerializer instance
295 | public static JsonSerializer CreateJsonNet(bool throwExceptions = true, bool camelCase = false)
296 | {
297 | if (JsonNet != null)
298 | return JsonNet;
299 |
300 | lock (SyncLock)
301 | {
302 | if (JsonNet != null)
303 | return JsonNet;
304 |
305 | // Try to create instance
306 | JsonSerializer json;
307 | try
308 | {
309 | json = new JsonSerializer();
310 | }
311 | catch
312 | {
313 | if (throwExceptions)
314 | throw;
315 | return null;
316 | }
317 |
318 | if (json == null)
319 | return null;
320 |
321 | json.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
322 |
323 | // Enums as strings in JSON
324 | var enumConverter = new StringEnumConverter();
325 | json.Converters.Add(enumConverter);
326 |
327 | if (camelCase)
328 | json.ContractResolver = new CamelCasePropertyNamesContractResolver();
329 |
330 | JsonNet = json;
331 | }
332 |
333 | return JsonNet;
334 | }
335 | }
336 | }
--------------------------------------------------------------------------------
/Westwind.WebView/Utilities/StreamExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace System.IO
4 | {
5 | ///
6 | /// MemoryStream Extension Methods that provide conversions to and from strings
7 | ///
8 | internal static class MemoryStreamExtensions
9 | {
10 | ///
11 | /// Returns the content of the stream as a string
12 | ///
13 | /// Memory stream
14 | /// Encoding to use - defaults to Unicode
15 | ///
16 | public static string AsString(this MemoryStream ms, Encoding encoding = null)
17 | {
18 | if (encoding == null)
19 | encoding = Encoding.Unicode;
20 |
21 | return encoding.GetString(ms.ToArray());
22 | }
23 |
24 | ///
25 | /// Writes the specified string into the memory stream
26 | ///
27 | ///
28 | ///
29 | ///
30 | public static void FromString(this MemoryStream ms, string inputString, Encoding encoding = null)
31 | {
32 | if (encoding == null)
33 | encoding = Encoding.Unicode;
34 |
35 | byte[] buffer = encoding.GetBytes(inputString);
36 | ms.Write(buffer, 0, buffer.Length);
37 | ms.Position = 0;
38 | }
39 | }
40 |
41 | ///
42 | /// Stream Extensions
43 | ///
44 | internal static class StreamExtensions
45 | {
46 | ///
47 | /// Converts a stream by copying it to a memory stream and returning
48 | /// as a string with encoding.
49 | ///
50 | /// stream to turn into a string
51 | /// Encoding of the stream. Defaults to Unicode
52 | /// string
53 | public static string AsString(this Stream s, Encoding encoding = null)
54 | {
55 | using (var ms = new MemoryStream())
56 | {
57 | s.CopyTo(ms);
58 | s.Position = 0;
59 | return ms.AsString(encoding);
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/Westwind.WebView/Utilities/StringUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace Westwind.WebView.HtmlToPdf.Utilities
7 | {
8 | internal static class StringUtils
9 | {
10 | ///
11 | /// A helper to generate a JSON string from a string value
12 | ///
13 | /// Use this to avoid bringing in a full JSON Serializer for
14 | /// scenarios of string serialization.
15 | ///
16 | /// Note: Function includes surrounding quotes!
17 | ///
18 | ///
19 | /// JSON encoded string ("text"), empty ("") or "null".
20 | internal static string ToJson(this string text, bool noQuotes = false)
21 | {
22 | if (text is null)
23 | return "null";
24 |
25 | var sb = new StringBuilder(text.Length);
26 |
27 | if (!noQuotes)
28 | sb.Append("\"");
29 |
30 | var ct = text.Length;
31 |
32 | for (int x = 0; x < ct; x++)
33 | {
34 | var c = text[x];
35 |
36 | switch (c)
37 | {
38 | case '\"':
39 | sb.Append("\\\"");
40 | break;
41 | case '\\':
42 | sb.Append("\\\\");
43 | break;
44 | case '\b':
45 | sb.Append("\\b");
46 | break;
47 | case '\f':
48 | sb.Append("\\f");
49 | break;
50 | case '\n':
51 | sb.Append("\\n");
52 | break;
53 | case '\r':
54 | sb.Append("\\r");
55 | break;
56 | case '\t':
57 | sb.Append("\\t");
58 | break;
59 | default:
60 | uint i = c;
61 | if (i < 32) // || i > 255
62 | sb.Append($"\\u{i:x4}");
63 | else
64 | sb.Append(c);
65 | break;
66 | }
67 | }
68 | if (!noQuotes)
69 | sb.Append("\"");
70 |
71 | return sb.ToString();
72 | }
73 |
74 | internal static string ToJson(this double value, int maxDecimals = 2)
75 | {
76 | if (maxDecimals > -1)
77 | value = Math.Round(value, maxDecimals);
78 |
79 | return value.ToString(CultureInfo.InvariantCulture);
80 | }
81 | internal static string ToJson(this bool value)
82 | {
83 | return value ? "true" : "false";
84 | }
85 |
86 | internal static string ExtractString(string source,
87 | string beginDelim,
88 | string endDelim,
89 | bool caseSensitive = false,
90 | bool allowMissingEndDelimiter = false,
91 | bool returnDelimiters = false)
92 | {
93 | int at1, at2;
94 |
95 | if (string.IsNullOrEmpty(source))
96 | return string.Empty;
97 |
98 | if (caseSensitive)
99 | {
100 | at1 = source.IndexOf(beginDelim);
101 | if (at1 == -1)
102 | return string.Empty;
103 |
104 | at2 = source.IndexOf(endDelim, at1 + beginDelim.Length);
105 | }
106 | else
107 | {
108 | //string Lower = source.ToLower();
109 | at1 = source.IndexOf(beginDelim, 0, source.Length, StringComparison.OrdinalIgnoreCase);
110 | if (at1 == -1)
111 | return string.Empty;
112 |
113 | at2 = source.IndexOf(endDelim, at1 + beginDelim.Length, StringComparison.OrdinalIgnoreCase);
114 | }
115 |
116 | if (allowMissingEndDelimiter && at2 < 0)
117 | {
118 | if (!returnDelimiters)
119 | return source.Substring(at1 + beginDelim.Length);
120 |
121 | return source.Substring(at1);
122 | }
123 |
124 | if (at1 > -1 && at2 > 1)
125 | {
126 | if (!returnDelimiters)
127 | return source.Substring(at1 + beginDelim.Length, at2 - at1 - beginDelim.Length);
128 |
129 | return source.Substring(at1, at2 - at1 + endDelim.Length);
130 | }
131 |
132 | return string.Empty;
133 | }
134 |
135 | internal static string LogStringDefaultFile { get; set; } = "d:\\temp\\_LogOutput.txt"; // Path.Combine(Path.GetTempPath(), "_LogOutput.txt");
136 |
137 | ///
138 | /// Simple Logging method that allows quickly writing a string to a file
139 | ///
140 | ///
141 | ///
142 | /// if not specified used UTF-8
143 | internal static void LogString(string output, string filename=null, Encoding encoding = null)
144 | {
145 | if (encoding == null)
146 | encoding = Encoding.UTF8;
147 |
148 | if (string.IsNullOrEmpty(filename))
149 | filename = LogStringDefaultFile;
150 |
151 | lock (_logLock)
152 | {
153 | var writer = new StreamWriter(filename, true, encoding);
154 | writer.WriteLine(DateTime.Now + " - " + output);
155 | writer.Close();
156 | }
157 | }
158 |
159 | private static object _logLock = new object();
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/Westwind.WebView/Westwind.WebView.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net472;net9.0-windows;net8.0-windows;
4 | true
5 | true
6 | Latest
7 | 0.3.3
8 | Rick Strahl
9 | disable
10 | false
11 | en-US
12 | Westwind.WebView
13 | West Wind WebView Interop Helpers
14 | en-US
15 | Westwind.WebView
16 | Westwind.WebView
17 | West Wind WebView Interop Helpers
18 | A .NET library to aid WebView2 control hosting, .NET/JavaScript interop and Html to Pdf Conversion
19 | A .NET library to aid WebView2 control hosting, .NET/JavaScript interop and Html to Pdf Conversion.
20 | Rick Strahl, West Wind Technologies 2023-2024
21 | WebView Westwind
22 |
23 | http://github.com/rickstrahl/westwind.webview
24 | icon.png
25 | LICENSE.md
26 | true
27 | Rick Strahl, West Wind Technologies, 2023-2024
28 | Github
29 | West Wind Technologies
30 | https://github.com/RickStrahl/Westwind.WebView
31 | git
32 |
33 |
34 |
35 | embedded
36 | $(NoWarn);CS1591;CS1572;CS1573
37 | true
38 | True
39 | ./nupkg
40 | true
41 | RELEASE
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/Westwind.WebView/Wpf/BaseJavaScriptInterop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using Microsoft.Web.WebView2.Wpf;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Serialization;
8 |
9 | namespace Westwind.WebView.Wpf
10 | {
11 |
12 | ///
13 | /// Helper class that simplifies calling JavaScript functions
14 | /// in a WebView document. Helps with formatting calls using
15 | /// ExecuteScriptAsync() and properly formatting/encoding parameters.
16 | /// It provides an easy way to Invoke methods on a global
17 | /// object that you specify (ie `window` or some other object) using
18 | /// a Reflection like Invocation interface.
19 | ///
20 | /// For generic Window function invocation you can use this class
21 | /// as is.
22 | ///
23 | /// But we recommend that you subclass this class for your application
24 | /// and then implement wrapper methods around each interop call
25 | /// you make into the JavaScript document rather than making the
26 | /// interop calls in your application code.
27 | ///
28 | /// Operations are applied to the `BaseInvocationTarget` which is
29 | /// the base object that operations are run on. This can be the
30 | /// `window.` or any globally accessibly object ie. `window.textEditor.`.
31 | ///
32 | /// You can use Invoke, Set, Get to access object properties, or you can
33 | /// use `ExecuteScriptAsync` to fire raw requests.
34 | ///
35 | /// Parameterize helps with encoding parameters when calling methods
36 | /// and turning them into string parseable values using JSON.
37 | ///
38 | /// *** IMPORTANT ***
39 | /// When subclassing make sure you create the class with
40 | /// ***the same constructor signature as this class***
41 | /// and preferrably pre-set the `baseInvocationTarget` parameter
42 | /// (`window` or `window.someObject`)
43 | ///
44 | public class BaseJavaScriptInterop
45 | {
46 | ///
47 | /// A string that is used as the object for object invocation
48 | /// By default this is empty which effectively invokes objects
49 | /// in the root namespace (window.). This string should reflect
50 | /// a base that supports appending a method or property acces,
51 | /// meaning it usually will end in a `.` (except for root/empty)
52 | /// such as `object.property.`
53 | ///
54 | /// For other components this might be an root object. In the MM
55 | /// editor for example it's:
56 | ///
57 | /// `window.textEditor.`
58 | ///
59 | public string BaseInvocationTargetString { get; set; }
60 |
61 | ///
62 | /// WebBrowser instance that the interop object operates on
63 | ///
64 | public WebView2 WebBrowser { get; }
65 |
66 |
67 | ///
68 | /// Creates an instance of this interop object to call JavaScript functions
69 | /// in the loaded DOM document.
70 | ///
71 | ///
72 | /// The base 'object' to execute
73 | /// commands on. The default is the global `window.` object. Set with the
74 | /// `.` at the end.
75 | public BaseJavaScriptInterop(WebView2 webBrowser, string baseInvocationTarget = "window")
76 | {
77 | WebBrowser = webBrowser;
78 | if (string.IsNullOrEmpty(baseInvocationTarget))
79 | baseInvocationTarget = "window.";
80 | if (!baseInvocationTarget.TrimEnd().EndsWith("."))
81 | baseInvocationTarget += ".";
82 |
83 | BaseInvocationTargetString = baseInvocationTarget;
84 | }
85 |
86 |
87 | #region Serialization
88 |
89 | static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings()
90 | {
91 | ContractResolver = new CamelCasePropertyNamesContractResolver(),
92 | };
93 |
94 |
95 | ///
96 | /// Helper method that consistently serializes JavaScript with Camelcase
97 | ///
98 | ///
99 | ///
100 | public static string SerializeObject(object data)
101 | {
102 | return JsonConvert.SerializeObject(data, _serializerSettings);
103 | }
104 |
105 | ///
106 | /// Helper method to deserialize json content
107 | ///
108 | ///
109 | ///
110 | public static TResult DeserializeObject(string json)
111 | {
112 | return JsonConvert.DeserializeObject(json, _serializerSettings);
113 | }
114 |
115 | #endregion
116 |
117 |
118 | #region Async Invocation Utilities
119 |
120 | ///
121 | /// Calls a method with simple or no parameters: string, boolean, numbers
122 | ///
123 | /// Method to call
124 | /// Parameters to path or none
125 | /// object result as specified by TResult type
126 | public async Task Invoke(string method, params object[] parameters)
127 | {
128 | StringBuilder sb = new StringBuilder();
129 | sb.Append(BaseInvocationTargetString + method + "(");
130 |
131 | if (parameters != null)
132 | {
133 | for (var index = 0; index < parameters.Length; index++)
134 | {
135 | object parm = parameters[index];
136 | var jsonParm = SerializeObject(parm);
137 | sb.Append(jsonParm);
138 | if (index < parameters.Length - 1)
139 | sb.Append(",");
140 | }
141 | }
142 | sb.Append(")");
143 |
144 | var cmd = sb.ToString();
145 | string result = await WebBrowser.CoreWebView2.ExecuteScriptAsync(cmd);
146 |
147 | Type resultType = typeof(TResult);
148 | return (TResult) JsonConvert.DeserializeObject(result, resultType);
149 | }
150 |
151 | ///
152 | /// Calls a method with simple parameters: String, number, boolean
153 | /// This version returns no results.
154 | ///
155 | ///
156 | ///
157 | ///
158 | public async Task Invoke(string method, params object[] parameters)
159 | {
160 | StringBuilder sb = new StringBuilder();
161 | sb.Append(BaseInvocationTargetString + method + "(");
162 |
163 | if (parameters != null)
164 | {
165 | for (var index = 0; index < parameters.Length; index++)
166 | {
167 | object parm = parameters[index];
168 | var jsonParm = SerializeObject(parm);
169 | sb.Append(jsonParm);
170 | if (index < parameters.Length - 1)
171 | sb.Append(",");
172 | }
173 | }
174 | sb.Append(")");
175 |
176 | await WebBrowser.CoreWebView2.ExecuteScriptAsync(sb.ToString());
177 | }
178 |
179 | ///
180 | /// Parameterizes a set of value parameters into string
181 | /// form that can be used in `ExecuteScriptAsync()` calls.
182 | /// Parameters are turned into a string using JSON values
183 | /// that are literal representations of values passed.
184 | ///
185 | /// You can wrap the result into a method call like this:
186 | ///
187 | /// ```csharp
188 | /// var parmData = js.Parameterize( new [] { 'parm1', pos } );
189 | /// "method(" + parmData + ")"
190 | /// ```
191 | ///
192 | ///
193 | ///
194 | public string Parameterize(object[] parameters)
195 | {
196 | StringBuilder sb = new StringBuilder();
197 | if (parameters != null)
198 | {
199 | for (var index = 0; index < parameters.Length; index++)
200 | {
201 | object parm = parameters[index];
202 | var jsonParm = SerializeObject(parm);
203 | sb.Append(jsonParm);
204 | if (index < parameters.Length - 1)
205 | sb.Append(",");
206 | }
207 | }
208 |
209 | return sb.ToString();
210 | }
211 |
212 |
213 | ///
214 | /// Sets a property on the editor by name
215 | ///
216 | /// Single property or hierarchical property off window.textEditor
217 | /// Value to set - should be simple value
218 | public async Task Set(string propertyName, object value)
219 | {
220 |
221 | var cmd = BaseInvocationTargetString + propertyName + " = " +
222 | SerializeObject(value) + ";";
223 |
224 | await WebBrowser.CoreWebView2.ExecuteScriptAsync(cmd);
225 | }
226 |
227 |
228 | ///
229 | /// Gets a property from the window.textEditor object
230 | ///
231 | ///
232 | public async Task Get(string propertyName)
233 | {
234 | var cmd = "return " + BaseInvocationTargetString + propertyName + ";";
235 | string result = await WebBrowser.CoreWebView2.ExecuteScriptAsync(cmd);
236 |
237 | Type resultType = typeof(TResult);
238 | return DeserializeObject(result);
239 | }
240 |
241 | ///
242 | /// Calls a method on the TextEditor in JavaScript a single JSON encoded
243 | /// value or object. The receiving function should expect a JSON object and parse it.
244 | ///
245 | /// This version returns no result value.
246 | ///
247 | public async Task CallMethodWithJson(string method, object parameter = null)
248 | {
249 | string cmd = method;
250 |
251 | if (parameter != null)
252 | {
253 | var jsonParm = SerializeObject(parameter);
254 | cmd += "(" + jsonParm + ")";
255 | }
256 |
257 | await WebBrowser.CoreWebView2.ExecuteScriptAsync(cmd);
258 | }
259 |
260 | ///
261 | /// Calls a method on the TextEditor in JavaScript a single JSON encoded
262 | /// value or object. The receiving function should expect a JSON object and parse it.
263 | ///
264 | ///
265 | ///
266 | ///
267 | public async Task CallMethodWithJson(string method, object parameter = null)
268 | {
269 | string cmd = method;
270 |
271 | if (parameter != null)
272 | {
273 | var jsonParm = SerializeObject(parameter);
274 | cmd += "(" + jsonParm + ")";
275 | }
276 |
277 | string result = await WebBrowser.CoreWebView2.ExecuteScriptAsync(cmd);
278 | return DeserializeObject(result);
279 | }
280 |
281 | ///
282 | /// Calls a method on the TextEditor in JavaScript a single JSON encoded
283 | /// value or object. The receiving function should expect a JSON object and parse it.
284 | ///
285 | public async Task ExecuteScriptAsync(string script)
286 | {
287 | await WebBrowser.CoreWebView2.ExecuteScriptAsync(script);
288 | }
289 |
290 | ///
291 | /// Calls a method on the TextEditor in JavaScript a single JSON encoded
292 | /// value or object.
293 | ///
294 | public async Task ExecuteScriptAsyncWithResult(string script)
295 | {
296 | var result = await WebBrowser.CoreWebView2.ExecuteScriptAsync(script);
297 | if (result == null)
298 | return default(TResult);
299 |
300 | return DeserializeObject(result);
301 | }
302 |
303 | #endregion
304 | }
305 |
306 | }
307 |
--------------------------------------------------------------------------------
/Westwind.WebView/Wpf/CachedWebViewEnvironment.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Web.WebView2.Core;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.IO;
7 | using System.Threading.Tasks;
8 | using Microsoft.Web.WebView2.Wpf;
9 | using System.Reflection;
10 | using System.Threading;
11 |
12 | namespace Westwind.WebView.Wpf
13 | {
14 |
15 |
16 | ///
17 | /// A wrapper class that provide a single point of initialization for the WebView2
18 | /// environment to ensure that only a single instance of the environment is created
19 | /// and used. This is important to avoid failures loading the environment with different
20 | /// environment settings is not actively supported by the WebView - IOW, it only
21 | /// supports a single environment per process and the first one determines the settings used,
22 | /// and unfortunately repeated instantiations with different settings can and often fail.
23 | ///
24 | /// This cached environment ensures that only a single instance of the environment is created
25 | /// and used and provides a single point of initialization for the environment.
26 | ///
27 | public class CachedWebViewEnvironment
28 | {
29 | ///
30 | /// Cached instance used by default
31 | ///
32 | public static CachedWebViewEnvironment Current { get; set; } = new CachedWebViewEnvironment();
33 |
34 | ///
35 | /// The Cached WebView Environment - one copy per running process
36 | ///
37 | public CoreWebView2Environment Environment { get; set; }
38 |
39 | ///
40 | /// The global setting for the folder where the WebView2 environment is stored.
41 | /// Defaults to the Temp folder of the current user. If not set. We recommend
42 | /// you set this folder **early** in your application startup.
43 | ///
44 | public string EnvironmentFolderName { get; set; }
45 |
46 | ///
47 | /// Optional WebView Environment options that can be set before the environment is created.
48 | /// Like the folder we recommend you set this early in your application startup.
49 | ///
50 | public CoreWebView2EnvironmentOptions EnvironmentOptions { get; set; }
51 |
52 |
53 | ///
54 | /// Ensure only one instance initializes the environment at a time to avoid
55 | /// multiple environment versions. Only applies to environment load, not waiting
56 | /// for the initialization to complete which can take a long time.
57 | ///
58 | private static SemaphoreSlim _EnvironmentLoadLock = new SemaphoreSlim(1, 1);
59 |
60 |
61 | ///
62 | /// This method provides a single point of initialization for the WebView2 environment
63 | /// to ensure that only a single instance of the environment is created. This is important
64 | /// to avoid failures loading the environment with different settings.
65 | ///
66 | /// Once the environment is created it's cached in the Environment property and reused
67 | /// on subsequent calls. While possible to override the environment **we don't recommend it**.
68 | ///
69 | ///
70 | /// This method does not complete until the WebView is UI activated. If not visible
71 | /// an `await` call will not complete until it becomes visible
72 | /// (due to internal `WebView2.EnsureCoreWebView2Async()` behavior)
73 | ///
74 | /// WebBrowser instance to set the environment on
75 | /// Optionally pass in an existing configured environment
76 | ///
77 | ///
78 | public async Task InitializeWebViewEnvironment(WebView2 webBrowser, CoreWebView2Environment environment = null, string webViewEnvironemntPath = null)
79 | {
80 | try
81 | {
82 |
83 | if (environment == null)
84 | environment = Environment;
85 |
86 | if (environment == null)
87 | {
88 | // lock
89 | await _EnvironmentLoadLock.WaitAsync();
90 |
91 | if (environment == null)
92 | {
93 | var envPath = webViewEnvironemntPath ?? Current.EnvironmentFolderName;
94 | if (string.IsNullOrEmpty(envPath))
95 | Current.EnvironmentFolderName = Path.Combine(Path.GetTempPath(),
96 | Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location) + "_WebView");
97 |
98 | // must create a data folder if running out of a secured folder that can't write like Program Files
99 | environment = await CoreWebView2Environment.CreateAsync(userDataFolder: EnvironmentFolderName,
100 | options: EnvironmentOptions);
101 |
102 | Environment = environment;
103 | }
104 |
105 | _EnvironmentLoadLock.Release();
106 | }
107 | await webBrowser.EnsureCoreWebView2Async(environment);
108 | }
109 | catch (Exception ex)
110 | {
111 | throw new WebViewInitializationException($"WebView EnsureCoreWebView2AsyncCall failed.\nFolder: {EnvironmentFolderName}", ex);
112 | }
113 |
114 | }
115 |
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Westwind.WebView/Wpf/WebView2Extensions.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Threading.Tasks;
4 |
5 | namespace Microsoft.Web.WebView2.Wpf
6 | {
7 | public static class WebView2Extensions
8 | {
9 | public static async Task NavigateToStringSafe(this WebView2 webView, string html)
10 | {
11 | webView.Source = new Uri("about:blank");
12 |
13 | string encodedHtml = JsonConvert.SerializeObject(html);
14 | string script = "window.document.write(" + encodedHtml + ")";
15 |
16 | await webView.EnsureCoreWebView2Async(); // make sure WebView is ready
17 | await webView.ExecuteScriptAsync(script);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Westwind.WebView/Wpf/WebViewInitializationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Westwind.WebView.Wpf
4 | {
5 | public class WebViewInitializationException : Exception
6 | {
7 | public WebViewInitializationException()
8 | { }
9 | public WebViewInitializationException(string message) : base(message)
10 | { }
11 | public WebViewInitializationException(string message, Exception innerException) : base(message, innerException)
12 | { }
13 | }
14 | }
--------------------------------------------------------------------------------
/Westwind.WebView/Wpf/WebViewUtilities.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Web.WebView2.Core;
2 | using Microsoft.Win32;
3 | using System;
4 | using System.IO;
5 | using System.Linq;
6 | using Westwind.WebView.Utilities;
7 |
8 | namespace Westwind.WebView.Wpf
9 | {
10 | ///
11 | /// Installation and environment helpers for the WebView2
12 | /// control
13 | ///
14 | public class WebViewUtilities
15 | {
16 |
17 | ///
18 | /// This method checks to see if the WebView runtime is installed. It doesn't
19 | /// check for a specific version, just whether the runtime is installed at all.
20 | /// For a specific version check use IsWebViewVersionInstalled()
21 | ///
22 | /// HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}
23 | /// HKEY_CURRENT_USER\Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5
24 | ///
25 | ///
26 | public static bool IsWebViewRuntimeInstalled()
27 | {
28 |
29 | string regKey = @"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients";
30 | using (RegistryKey edgeKey = Registry.LocalMachine.OpenSubKey(regKey))
31 | {
32 | if (edgeKey != null)
33 | {
34 | string[] productKeys = edgeKey.GetSubKeyNames();
35 | if (productKeys.Any())
36 | {
37 | return true;
38 | }
39 | }
40 | }
41 |
42 | return false;
43 | }
44 |
45 |
46 | ///
47 | /// This method checks to see if the WebView runtime is installed and whether it's
48 | /// equal or greater than the WebView .NET component.
49 | ///
50 | /// This ensures that you use a version of the WebView Runtime that supports the features
51 | /// of the .NET Component.
52 | ///
53 | /// Should be called during app startup to ensure the WebView Runtime is available.
54 | ///
55 | public static bool IsWebViewVersionInstalled()
56 | {
57 | try
58 | {
59 | var versionNo = CoreWebView2Environment.GetAvailableBrowserVersionString(null);
60 |
61 | // strip off 'canary' or 'stable' version
62 | var at = versionNo.IndexOf(" ");
63 | if (at < 1) return false;
64 | versionNo = versionNo.Substring(0, at -1);
65 |
66 | //versionNo = StringUtils.ExtractString(versionNo, "", " ", allowMissingEndDelimiter: true)?.Trim();
67 | var ver = new Version(versionNo);
68 |
69 | var asmVersion = typeof(CoreWebView2Environment).Assembly.GetName().Version;
70 |
71 | if (ver.Build >= asmVersion.Build)
72 | return true;
73 | }
74 | catch
75 | {
76 | // ignored
77 | }
78 |
79 | return false;
80 | }
81 |
82 |
83 | ///
84 | /// Removes the applications local WebView Environment
85 | ///
86 | ///
87 | /// true if the directory exists and was successfully deleted.
88 | /// false if directory doesn't exist, or the directory deletion fails.
89 | ///
90 | public static bool RemoveEnvironmentFolder(string folder)
91 | {
92 | if (string.IsNullOrEmpty(folder))
93 | return false;
94 |
95 | var checkPath = Path.Combine(folder, "EbWebView");
96 | if (Directory.Exists(checkPath))
97 | {
98 | Directory.Delete(folder);
99 | if (!Directory.Exists(checkPath))
100 | return true;
101 | }
102 |
103 | return false;
104 | }
105 |
106 | ///
107 | /// Returns the WebView SDK
108 | ///
109 | ///
110 | ///
111 | public static string GetWebViewRuntimeVersion(bool returnSdk = false)
112 | {
113 | try
114 | {
115 | if (!returnSdk)
116 | return CoreWebView2Environment.GetAvailableBrowserVersionString(null);
117 |
118 | return typeof(CoreWebView2Environment).Assembly.GetName().Version.ToString();
119 | }
120 | catch
121 | {
122 | return null;
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Westwind.WebView/publish-nuget.ps1:
--------------------------------------------------------------------------------
1 | if (test-path ./nupkg) {
2 | remove-item ./nupkg -Force -Recurse
3 | }
4 |
5 | dotnet build -c Release
6 |
7 | $filename = gci "./nupkg/*.nupkg" | sort LastWriteTime | select -last 1 | select -ExpandProperty "Name"
8 | Write-host $filename
9 | $len = $filename.length
10 |
11 | if ($len -gt 0) {
12 | Write-Host "signing... $filename"
13 | nuget sign ".\nupkg\$filename" -CertificateSubject "West Wind Technologies" -timestamper " http://timestamp.digicert.com"
14 | nuget push ".\nupkg\$filename" -source "https://nuget.org"
15 |
16 | Write-Host "Done."
17 | }
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RickStrahl/Westwind.WebView/cbf7323a89ba99a91ce1a395f80dcf29a6899d12/icon.png
--------------------------------------------------------------------------------