Fixes all the logic in the CodeFileController, propery null checking, proper handling of persisting everything including renames, removes code duplication

This commit is contained in:
Shannon
2017-03-21 18:39:45 +11:00
parent dfa58183c6
commit 27ee192c8f

View File

@@ -1,4 +1,5 @@
using AutoMapper;
using System;
using AutoMapper;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -18,9 +19,11 @@ using Umbraco.Web.Trees;
namespace Umbraco.Web.Editors
{
//TODO: Put some exception filters in our webapi to return 404 instead of 500 when we throw ArgumentNullException
// ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/
[PluginController("UmbracoApi")]
[PrefixlessBodyModelValidator]
[UmbracoApplicationAuthorizeAttribute(Core.Constants.Applications.Settings)]
[UmbracoApplicationAuthorize(Core.Constants.Applications.Settings)]
public class CodeFileController : BackOfficeNotificationsController
{
@@ -33,6 +36,9 @@ namespace Umbraco.Web.Editors
[ValidationFilter]
public HttpResponseMessage PostCreate(string type, CodeFileDisplay display)
{
if (display == null) throw new ArgumentNullException("display");
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
switch (type)
{
case Core.Constants.Trees.PartialViews:
@@ -67,10 +73,9 @@ namespace Umbraco.Web.Editors
[HttpPost]
public CodeFileDisplay PostCreateContainer(string type, string parentId, string name)
{
if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(name))
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId");
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
// if the parentId is root (-1) then we just need an empty string as we are
// creating the path below and we don't wan't -1 in the path
@@ -120,10 +125,8 @@ namespace Umbraco.Web.Editors
/// <returns>The file and its contents from the virtualPath</returns>
public CodeFileDisplay GetByPath(string type, string virtualPath)
{
if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(virtualPath))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath");
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
@@ -139,7 +142,7 @@ namespace Umbraco.Web.Editors
display.Id = System.Web.HttpUtility.UrlEncode(view.Path);
return display;
}
return null;
throw new HttpResponseException(HttpStatusCode.NotFound);
case Core.Constants.Trees.PartialViewMacros:
var viewMacro = Services.FileService.GetPartialViewMacro(virtualPath);
@@ -151,7 +154,7 @@ namespace Umbraco.Web.Editors
display.Id = System.Web.HttpUtility.UrlEncode(viewMacro.Path);
return display;
}
return null;
throw new HttpResponseException(HttpStatusCode.NotFound);
case Core.Constants.Trees.Scripts:
var script = Services.FileService.GetScriptByName(virtualPath);
@@ -163,7 +166,7 @@ namespace Umbraco.Web.Editors
display.Id = System.Web.HttpUtility.UrlEncode(script.Path);
return display;
}
return null;
throw new HttpResponseException(HttpStatusCode.NotFound);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
@@ -176,10 +179,7 @@ namespace Umbraco.Web.Editors
/// <returns>Returns a list of <see cref="SnippetDisplay"/> if a correct type is sent</returns>
public IEnumerable<SnippetDisplay> GetSnippets(string type)
{
if (string.IsNullOrWhiteSpace(type))
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
IEnumerable<string> snippets;
switch (type)
@@ -209,15 +209,10 @@ namespace Umbraco.Web.Editors
/// <param name="id"></param>
/// <param name="snippetName"></param>
/// <returns></returns>
public CodeFileDisplay GetScaffold(string type, string id = null, string snippetName = null)
public CodeFileDisplay GetScaffold(string type, string id, string snippetName = null)
{
if (string.IsNullOrWhiteSpace(type))
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
if (id.IsNullOrWhiteSpace())
id = string.Empty;
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("Value cannot be null or whitespace.", "id");
CodeFileDisplay codeFileDisplay;
@@ -246,15 +241,15 @@ namespace Umbraco.Web.Editors
// Make sure that the root virtual path ends with '/'
codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.EnsureEndsWith("/");
if (id.IsNullOrWhiteSpace() == false && id != Core.Constants.System.Root.ToInvariantString())
if (id != Core.Constants.System.Root.ToInvariantString())
{
codeFileDisplay.VirtualPath += id.TrimStart("/").EnsureEndsWith("/");
//if it's not new then it will have a path, otherwise it won't
codeFileDisplay.Path = Url.GetTreePathFromFilePath(id);
}
codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~");
codeFileDisplay.Path = Url.GetTreePathFromFilePath(id);
codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.TrimStart("~");
codeFileDisplay.FileType = type;
return codeFileDisplay;
}
@@ -268,52 +263,52 @@ namespace Umbraco.Web.Editors
[HttpPost]
public HttpResponseMessage Delete(string type, string virtualPath)
{
if (string.IsNullOrWhiteSpace(type) == false && string.IsNullOrWhiteSpace(virtualPath) == false)
if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type");
if (string.IsNullOrWhiteSpace(virtualPath)) throw new ArgumentException("Value cannot be null or whitespace.", "virtualPath");
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
switch (type)
{
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
case Core.Constants.Trees.PartialViews:
if (IsDirectory(virtualPath, SystemDirectories.PartialViews))
{
Services.FileService.DeletePartialViewFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path");
switch (type)
{
case Core.Constants.Trees.PartialViews:
if (IsDirectory(virtualPath, SystemDirectories.PartialViews))
{
Services.FileService.DeletePartialViewFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View or folder found with the specified path");
case Core.Constants.Trees.PartialViewMacros:
if (IsDirectory(virtualPath, SystemDirectories.MacroPartials))
{
Services.FileService.DeletePartialViewMacroFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path");
case Core.Constants.Trees.PartialViewMacros:
if (IsDirectory(virtualPath, SystemDirectories.MacroPartials))
{
Services.FileService.DeletePartialViewMacroFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro or folder found with the specified path");
case Core.Constants.Trees.Scripts:
if (IsDirectory(virtualPath, SystemDirectories.Scripts))
{
Services.FileService.DeleteScriptFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.GetScriptByName(virtualPath) != null)
{
Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id);
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path");
case Core.Constants.Trees.Scripts:
if (IsDirectory(virtualPath, SystemDirectories.Scripts))
{
Services.FileService.DeleteScriptFolder(virtualPath);
return Request.CreateResponse(HttpStatusCode.OK);
}
if (Services.FileService.GetScriptByName(virtualPath) != null)
{
Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id);
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script or folder found with the specified path");
default:
return Request.CreateResponse(HttpStatusCode.NotFound);
}
default:
return Request.CreateResponse(HttpStatusCode.NotFound);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
@@ -326,16 +321,13 @@ namespace Umbraco.Web.Editors
/// <returns>The updated CodeFileDisplay model</returns>
public CodeFileDisplay PostSave(CodeFileDisplay display)
{
if (display == null) throw new ArgumentNullException("display");
if (ModelState.IsValid == false)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
if (display == null || string.IsNullOrWhiteSpace(display.FileType))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
switch (display.FileType)
{
case Core.Constants.Trees.PartialViews:
@@ -370,25 +362,21 @@ namespace Umbraco.Web.Editors
case Core.Constants.Trees.Scripts:
var virtualPath = display.VirtualPath ?? string.Empty;
// this is all weird, should be using relative paths everywhere!
var relPath = FileSystemProviderManager.Current.ScriptsFileSystem.GetRelativePath(virtualPath);
var path = relPath;
if (path.EndsWith(".js") == false)
path += ".js";
var script = Services.FileService.GetScriptByName(path) ?? new Script(path);
script.Path = path;
script.Content = display.Content;
Services.FileService.SaveScript(script);
display = Mapper.Map(script, display);
display.Path = Url.GetTreePathFromFilePath(script.Path);
display.Id = System.Web.HttpUtility.UrlEncode(script.Path);
var scriptResult = CreateOrUpdateScript(display);
display = Mapper.Map(scriptResult, display);
display.Path = Url.GetTreePathFromFilePath(scriptResult.Path);
display.Id = System.Web.HttpUtility.UrlEncode(scriptResult.Path);
return display;
//display.AddErrorNotification(
// Services.TextService.Localize("speechBubbles/partialViewErrorHeader"),
// Services.TextService.Localize("speechBubbles/partialViewErrorText"));
break;
default:
throw new HttpResponseException(HttpStatusCode.NotFound);
}
@@ -396,11 +384,86 @@ namespace Umbraco.Web.Editors
return display;
}
/// <summary>
/// Create or Update a Script
/// </summary>
/// <param name="display"></param>
/// <returns></returns>
/// <remarks>
/// It's important to note that Scripts are DIFFERENT from cshtml files since scripts use IFileSystem and cshtml files
/// use a normal file system because they must exist on a real file system for ASP.NET to work.
/// </remarks>
private Script CreateOrUpdateScript(CodeFileDisplay display)
{
//must always end with the correct extension
display.Name = EnsureCorrectFileExtension(display.Name, ".js");
var virtualPath = display.VirtualPath ?? string.Empty;
// this is all weird, should be using relative paths everywhere!
var relPath = FileSystemProviderManager.Current.ScriptsFileSystem.GetRelativePath(virtualPath);
if (relPath.EndsWith(".js") == false)
{
//this would typically mean it's new
relPath = relPath.IsNullOrWhiteSpace()
? relPath + display.Name
: relPath.EnsureEndsWith('/') + display.Name;
}
var script = Services.FileService.GetScriptByName(relPath);
if (script != null)
{
// might need to find the path
var orgPath = script.OriginalPath.Substring(0, script.OriginalPath.IndexOf(script.Name));
script.Path = orgPath + display.Name;
script.Content = display.Content;
//try/catch? since this doesn't return an Attempt?
Services.FileService.SaveScript(script, Security.CurrentUser.Id);
}
else
{
script = new Script(relPath);
script.Content = display.Content;
Services.FileService.SaveScript(script, Security.CurrentUser.Id);
}
return script;
}
private Attempt<IPartialView> CreateOrUpdatePartialView(CodeFileDisplay display)
{
return CreateOrUpdatePartialView(display, SystemDirectories.MacroPartials,
Services.FileService.GetPartialView, Services.FileService.SavePartialView, Services.FileService.CreatePartialView);
}
private Attempt<IPartialView> CreateOrUpdatePartialViewMacro(CodeFileDisplay display)
{
return CreateOrUpdatePartialView(display, SystemDirectories.MacroPartials,
Services.FileService.GetPartialViewMacro, Services.FileService.SavePartialViewMacro, Services.FileService.CreatePartialViewMacro);
}
/// <summary>
/// Helper method to take care of persisting partial views or partial view macros - so we're not duplicating the same logic
/// </summary>
/// <param name="display"></param>
/// <param name="systemDirectory"></param>
/// <param name="getView"></param>
/// <param name="saveView"></param>
/// <param name="createView"></param>
/// <returns></returns>
private Attempt<IPartialView> CreateOrUpdatePartialView(
CodeFileDisplay display, string systemDirectory,
Func<string, IPartialView> getView,
Func<IPartialView, int, Attempt<IPartialView>> saveView,
Func<IPartialView, string, int, Attempt<IPartialView>> createView)
{
//must always end with the correct extension
display.Name = EnsureCorrectFileExtension(display.Name, ".cshtml");
Attempt<IPartialView> partialViewResult;
string virtualPath = NormalizeVirtualPath(display.VirtualPath, SystemDirectories.PartialViews);
var view = Services.FileService.GetPartialView(virtualPath);
var virtualPath = NormalizeVirtualPath(display.VirtualPath, systemDirectory);
var view = getView(virtualPath);
if (view != null)
{
// might need to find the path
@@ -408,14 +471,13 @@ namespace Umbraco.Web.Editors
view.Path = orgPath + display.Name;
view.Content = display.Content;
partialViewResult = Services.FileService.SavePartialView(view, Security.CurrentUser.Id);
partialViewResult = saveView(view, Security.CurrentUser.Id);
}
else
{
var fileName = EnsurePartialViewExtension(display.Name, ".cshtml");
view = new PartialView(virtualPath + fileName);
view = new PartialView(virtualPath + display.Name);
view.Content = display.Content;
partialViewResult = Services.FileService.CreatePartialView(view, display.Snippet, Security.CurrentUser.Id);
partialViewResult = createView(view, display.Snippet, Security.CurrentUser.Id);
}
return partialViewResult;
@@ -435,29 +497,7 @@ namespace Umbraco.Web.Editors
return virtualPath;
}
private Attempt<IPartialView> CreateOrUpdatePartialViewMacro(CodeFileDisplay display)
{
Attempt<IPartialView> partialViewMacroResult;
var virtualPath = display.VirtualPath ?? string.Empty;
var viewMacro = Services.FileService.GetPartialViewMacro(virtualPath);
if (viewMacro != null)
{
viewMacro.Content = display.Content;
viewMacro.Path = display.Name;
partialViewMacroResult = Services.FileService.SavePartialViewMacro(viewMacro, Security.CurrentUser.Id);
}
else
{
var fileName = EnsurePartialViewExtension(display.Name, ".cshtml");
viewMacro = new PartialView(virtualPath + fileName);
viewMacro.Content = display.Content;
partialViewMacroResult = Services.FileService.CreatePartialViewMacro(viewMacro, display.Snippet, Security.CurrentUser.Id);
}
return partialViewMacroResult;
}
private string EnsurePartialViewExtension(string value, string extension)
private string EnsureCorrectFileExtension(string value, string extension)
{
if (value.EndsWith(extension) == false)
value += extension;