Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validation (where applicable) of fields to prevent unhandled exception #36

Open
jensbrak opened this issue Sep 18, 2020 · 4 comments
Labels
enhancement New feature or request

Comments

@jensbrak
Copy link

jensbrak commented Sep 18, 2020

Add some basic input validation to the templates. Would not only prevent Piranha from crashing if invalid input is made but also provide a good example of how to do it.

For instance, an invalid e-mail address of a comment will cause an exception that will crash the application. I assume this is the proper behavior of Piranha.Core but it would be both pedagogical and better if application/templates won't crash upon quite normal 'misbehavior' of a site visitor.

Steps to reproduce the exception:

  1. Create a new project from Razor Template
  2. Run the project and enter an invalid (ie 'ThisIsAnInvalidEmailAdress') e-mail address on any comment and submit it

Output of application:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
System.ComponentModel.DataAnnotations.ValidationException: The Email field is not a valid e-mail address.
   at System.ComponentModel.DataAnnotations.Validator.ValidationError.ThrowValidationException()
   at System.ComponentModel.DataAnnotations.Validator.ValidateObject(Object instance, ValidationContext validationContext, Boolean validateAllProperties)
   at Piranha.Services.PostService.SaveCommentAsync(Guid postId, Comment model, Boolean verify)
   at Piranha.AspNetCore.Models.SinglePostWithComments`1.OnPostSaveComment(Guid id, Boolean draft)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Piranha.AspNetCore.SitemapMiddleware.Invoke(HttpContext context, IApi api, IApplicationService service)
   at Piranha.AspNetCore.IntegratedMiddleware.Invoke(HttpContext context, IApi api, IApplicationService service)
   at Piranha.AspNetCore.Security.SecurityMiddleware.InvokeAsync(HttpContext ctx, IApplicationService service)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
@tidyui tidyui added the enhancement New feature or request label Sep 18, 2020
@tidyui
Copy link
Member

tidyui commented Sep 18, 2020

Sounds good. The core library is supposed to throw the exception if the email is malformed so I'm guessing it's only the validation parts missing in the templates.

@jensbrak
Copy link
Author

Sounds good. The core library is supposed to throw the exception if the email is malformed so I'm guessing it's only the validation parts missing in the templates.

I'll might try to go for a PR on a validation example (e-mail address of comments I believe is a good candidate) of the Razor Template - if not anyone beats me to it, that is.

@eric-wilson
Copy link

I'm struggling with this as well. How can do the following:

  1. Intercept the SaveComment to add my own validation
  2. Add some client-side validation

I'm using the pre-built templates e.g. dotnet new piranha.razor -o CMSRazorWebApp
The post.cshtml has @model SinglePostWithComments<StandardPost>

In the backing model, I'm trying to intercept the save and validation. The validation appears to be on the load and it's looking at the most recent comment in the list.

I'm sure I'm doing this wrong, but I can't seem to find a way to intercept and validate it first. At the very minimum, how can I trap the error and gracefully display a message?

[PostType(Title = "Standard Post")]
    public class StandardPost : Post<StandardPost>
    {

        public StandardPost() : base()
        {
            Intercepts();
        }

        public void Intercepts()
        {
            
            App.Hooks.Comments.RegisterOnValidate(comment =>
            {
                if (comment.Author.Trim().Length == 0) comment.Author = "guest";
                if (comment.Email.Trim().Length == 0) comment.Email = "guest@guest.com";
                                
            });

           App.Hooks.Comments.RegisterOnBeforeSave(comment =>
           {
               if (comment.Author.Trim().Length == 0) comment.Author = "guest";
               if (comment.Email.Trim().Length == 0) comment.Email = "guest@guest.com";
           });
        }

@eric-wilson
Copy link

eric-wilson commented Jan 10, 2021

I ended up coming up with this pattern. It works, but I'd like some feedback on how you would implement it.

[PostType(Title = "Standard Post")]
    public class StandardPost : Post<StandardPost>
    {
    }

public class PostModel: SinglePostWithComments<StandardPost>
    {
        public string Errors { get; set; }
        public PostModel(Piranha.IApi api, Piranha.AspNetCore.Services.IModelLoader loader): base(api, loader)
        {            
        }

        public override async Task<IActionResult> OnGet(Guid id, bool draft = false)
        {
            CheckForErrorMessages();
            return await base.OnGet(id, draft);
        }

        public override async Task<IActionResult> OnPostSaveComment(Guid id, bool draft = false)
        {
            try
            {               
                return await base.OnPostSaveComment(id, draft);
            }
            catch (Exception ex)
            {
                var uri = $"{HttpContext.Request.Headers["Referer"]}?error={ex.Message}&#comments"; 
                return Redirect(uri);               
            }
        }

        private void CheckForErrorMessages()
        {
            try
            {
                Request.Query.TryGetValue("error", out var error);
                Errors = error;
            }
            catch { }
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants