//******************************************************************************************************
// WebExtensions.cs - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/17/2016 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using GSF.Collections;
using GSF.IO;
using GSF.Web.Model;
using RazorEngine.Templating;
using HtmlHelper = System.Web.Mvc.HtmlHelper;
namespace GSF.Web
{
///
/// Defines extension methods useful for web applications.
///
public static class WebExtensions
{
// Nested Types
// Defines a multi-part stream provider that will parse out files and form data
private class PostDataStreamProvider : MultipartStreamProvider
{
#region [ Members ]
// Fields
private readonly PostData m_postData;
private readonly List m_isFormData;
#endregion
#region [ Constructors ]
public PostDataStreamProvider()
{
m_postData = new PostData();
m_isFormData = new List();
}
#endregion
#region [ Properties ]
// Gets any data passed as part of the multi-part form data
public PostData PostData => m_postData;
#endregion
#region [ Methods ]
// Gets the stream where to write the body part to. This method is called when a MIME multi-part body part has been parsed
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
// For form data, Content-Disposition header is a requirement
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
if ((object)contentDisposition == null)
throw new InvalidOperationException("Expected \"Content-Disposition\" header field not found in MIME multi-part body.");
// Post process this body part as form data if no file name is specified
m_isFormData.Add(string.IsNullOrEmpty(contentDisposition.FileName));
return new MemoryStream();
}
// Executes the post processing operation reading the non-file contents as form data
public override async Task ExecutePostProcessingAsync()
{
for (int i = 0; i < Contents.Count; i++)
{
if (m_isFormData[i])
{
HttpContent formContent = Contents[i];
ContentDispositionHeaderValue contentDisposition = formContent.Headers.ContentDisposition;
string fieldName = UnquoteToken(contentDisposition.Name) ?? "";
// Read out any form data
string fieldValue = await formContent.ReadAsStringAsync();
m_postData.FormData.Add(fieldName, fieldValue);
}
else
{
m_postData.FileData.Add(Contents[i]);
}
}
}
#endregion
#region [ Static ]
// Static Methods
private static string UnquoteToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
return token;
if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
return token.Substring(1, token.Length - 2);
return token;
}
#endregion
}
// Defines a GetMemberBinder implementation to get DynamicViewBag properties by name
private class MemberBinder : GetMemberBinder
{
public MemberBinder(string name) : base(name, false)
{
}
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) => null;
}
// Static Fields
private static readonly HashSet s_executingAssemblyResources;
private static readonly HashSet s_callingAssemblyResources;
private static readonly HashSet s_entryAssemblyResources;
// Static Constructor
static WebExtensions()
{
s_executingAssemblyResources = new HashSet(Assembly.GetExecutingAssembly().GetManifestResourceNames(), StringComparer.Ordinal);
s_callingAssemblyResources = new HashSet(Assembly.GetCallingAssembly().GetManifestResourceNames(), StringComparer.Ordinal);
s_entryAssemblyResources = new HashSet(Assembly.GetEntryAssembly()?.GetManifestResourceNames() ?? new[] { "" }, StringComparer.Ordinal);
}
// Static Methods
///
/// Performs JavaScript encoding on given string.
///
/// The string to be encoded.
public static string JavaScriptEncode(this string text)
{
return HttpUtility.JavaScriptStringEncode(text.ToNonNullString());
}
///
/// Performs HTML encoding on the given string.
///
/// The string to be encoded.
public static string HtmlEncode(this string text)
{
return HttpUtility.HtmlEncode(text.ToNonNullString());
}
///
/// Performs URL encoding on the given string.
///
/// The string to be encoded.
public static string UrlEncode(this string text)
{
return HttpUtility.UrlEncode(text.ToNonNullString());
}
///
/// Corrects script alignment with a desired number of forward spaces.
///
/// Script text.
/// Desired forward spaces.
/// Script with corrected forward alignment.
public static string FixForwardSpacing(this string script, int spaces = 4)
{
Tuple[] linesAndLengths = script
.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Select(line => new Tuple(line, line.Length - line.TrimStart(' ').Length))
.ToArray();
int minLength = linesAndLengths
.Select(lineAndLength => (int?)lineAndLength.Item2)
.Where(length => length.GetValueOrDefault() > 0)
.Min() ?? 0;
string forwardSpacing = new string(' ', spaces);
return linesAndLengths
.Select(lineAndLength => lineAndLength.Item2 > 0 ?
$"{forwardSpacing}{(lineAndLength.Item2 > minLength ? lineAndLength.Item1.Substring(minLength) : lineAndLength.Item1)}" :
lineAndLength.Item1.ToNonNullNorEmptyString())
.ToDelimitedString(Environment.NewLine);
}
///
/// Gets query parameters for current request message
///
///
///
public static Dictionary QueryParameters(this HttpRequestMessage request)
{
return request.GetQueryNameValuePairs().ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
}
///
/// Gets a collection of uploaded post data from an .
///
/// request data that contains form data and/or uploaded files.
/// Parsed post data.
public static PostData GetPostData(this HttpRequestMessage request)
{
// Providing non-async version to simplify processing within Razor script
Task getPostDataTask = GetPostDataAsync(request);
getPostDataTask.ContinueWith(task =>
{
if ((object)task.Exception != null)
throw task.Exception;
},
TaskContinuationOptions.OnlyOnFaulted);
return getPostDataTask.Result;
}
///
/// Asynchronously gets a collection of uploaded post data from an .
///
/// request data that contains form data and/or uploaded files.
/// Parsed post data.
public static async Task GetPostDataAsync(this HttpRequestMessage request)
{
PostDataStreamProvider provider = await request.Content.ReadAsMultipartAsync(new PostDataStreamProvider());
return provider.PostData;
}
///
/// Converts relative URL to an absolute URL.
///
/// MVC HTML helper instance.
/// Relative URL.
/// Absolute URL.
public static string ToAbsoluteUrl(this HtmlHelper helper, string url)
{
if (string.IsNullOrEmpty(url))
return "";
if (HttpContext.Current == null)
return "";
if (url.StartsWith("/"))
url = url.Insert(0, "~");
if (!url.StartsWith("~/"))
url = url.Insert(0, "~/");
Uri uri = HttpContext.Current.Request.Url;
string port = uri.Port == 80 ? "" : ":" + uri.Port;
return $"{uri.Scheme}://{uri.Host}{port}{VirtualPathUtility.ToAbsolute(url)}";
}
///
/// Includes an HTTP resource directly into an MVC view page.
///
/// MVC HTML helper instance.
/// URL of resource to include.
/// Resolved URL resource as an MvcHtmlString.
public static MvcHtmlString IncludeUrl(this HtmlHelper helper, string url)
{
if (string.IsNullOrEmpty(url))
return new MvcHtmlString("");
HttpWebRequest request = WebRequest.CreateHttp(url);
request.Credentials = CredentialCache.DefaultCredentials;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
using (Stream stream = response?.GetResponseStream())
{
if ((object)stream == null)
return new MvcHtmlString($"");
using (StreamReader reader = new StreamReader(stream))
return new MvcHtmlString(reader.ReadToEnd());
}
}
///
/// Includes an embedded resource directly into an MVC view page.
///
/// MVC HTML helper instance.
/// Resource to include.
/// Resource as an MvcHtmlString.
public static MvcHtmlString IncludeResouce(this HtmlHelper helper, string resourceName)
{
if (string.IsNullOrEmpty(resourceName))
return new MvcHtmlString("");
Stream stream = OpenEmbeddedResourceStream(resourceName);
if ((object)stream == null)
return new MvcHtmlString("");
using (StreamReader reader = new StreamReader(stream))
return new MvcHtmlString(reader.ReadToEnd());
}
///
/// Renders a Razor view from an embedded resource.
///
/// MVC HTML helper instance.
/// Embedded resource name.
/// Type of model.
/// Model instance.
/// Rendered resource as an MvcHtmlString.
///
/// If needed by view, define ViewBag.DataContext before rendering resource.
///
public static MvcHtmlString RenderResource(this HtmlHelper helper, string resourceName, Type modelType = null, object model = null)
{
MvcHtmlString result;
DynamicViewBag viewBag = new DynamicViewBag(helper.ViewData);
bool isPost = HttpContext.Current.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase);
viewBag.AddValue("IsPost", isPost);
viewBag.AddValue("Request", HttpContext.Current.Items["MS_HttpRequestMessage"] ?? new HttpRequestMessage(isPost ? HttpMethod.Post : HttpMethod.Get, HttpContext.Current.Request.Url));
if (FilePath.GetExtension(resourceName).Equals(".vbhtml", StringComparison.OrdinalIgnoreCase))
result = new MvcHtmlString(RazorEngine.Default.RunCompile(resourceName, modelType, model, viewBag));
else
result = new MvcHtmlString(RazorEngine.Default.RunCompile(resourceName, modelType, model, viewBag));
// Copy any new or updated view bag elements back to MVC view data dictionary
foreach (string memberName in viewBag.GetDynamicMemberNames())
{
object value;
viewBag.TryGetMember(new MemberBinder(memberName), out value);
if (value != null)
helper.ViewData[memberName] = value;
}
return result;
}
///
/// Renders a Razor view from an embedded resource.
///
/// Type of model.
/// MVC HTML helper instance.
/// Embedded resource name.
/// Model instance.
/// Rendered resource as an MvcHtmlString.
///
/// If needed by view, define ViewBag.DataContext before rendering resource.
///
public static MvcHtmlString RenderResource(this HtmlHelper helper, string resourceName, T model)
{
return RenderResource(helper, resourceName, typeof(T), model);
}
///
/// Determines if specified embedded resource exists.
///
/// Fully qualified name of resource to check.
/// true if resource exists; otherwise, false.
public static bool EmbeddedResourceExists(string resourceName)
{
return
s_executingAssemblyResources.Contains(resourceName) ||
s_callingAssemblyResources.Contains(resourceName) ||
s_entryAssemblyResources.Contains(resourceName);
}
///
/// Opens a stream to an embedded resource.
///
/// Fully qualified name of resource to open.
/// Stream to embedded resource if found; otherwise, null.
public static Stream OpenEmbeddedResourceStream(string resourceName)
{
if (s_executingAssemblyResources.Contains(resourceName))
return Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName);
if (s_callingAssemblyResources.Contains(resourceName))
return Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
if (s_entryAssemblyResources.Contains(resourceName))
return Assembly.GetEntryAssembly().GetManifestResourceStream(resourceName);
return null;
}
}
}