ardalis / Result

A result abstraction that can be mapped to HTTP response codes if needed.
MIT License
866 stars 107 forks source link

Razor Pages Architecture question #113

Closed VictorioBerra closed 1 year ago

VictorioBerra commented 1 year ago

Hello, thanks for the useful library! I know this might not be the best place for questions like this but I wanted to ask about best practices with architecture when layering an app using Ardalis.Result and things like RazorPages and FluentValidation.

Take the following example from the homepage, where we have a service that updates a BlogCategory, and leverages FluentValidation and Ardalis.Result:

BlogService (from README.md)

// ... Presumably somewhere in BlogCategoryService.cs
public async Task<Result<BlogCategory>> UpdateAsync(BlogCategory blogCategory)
{
    if (Guid.Empty == blogCategory.BlogCategoryId) return Result<BlogCategory>.NotFound();

    var validator = new BlogCategoryValidator();
    var validation = await validator.ValidateAsync(blogCategory);
    if (!validation.IsValid)
    {
        return Result<BlogCategory>.Invalid(validation.AsErrors());
    }

    var itemToUpdate = (await GetByIdAsync(blogCategory.BlogCategoryId)).Value;
    if (itemToUpdate == null)
    {
        return Result<BlogCategory>.NotFound();
    }

    itemToUpdate.Update(blogCategory.Name, blogCategory.ParentId);

    return Result<BlogCategory>.Success(await _blogCategoryRepository.UpdateAsync(itemToUpdate));
}

Razor Page

Html

@page
@model MyApp.Pages.EditBlogCategoryModel

<form method="post">
    <input type="text" asp-for="BlogCategoryEditViewModel.BlogCategoryName" />
    <input type="text" asp-for="BlogCategoryEditViewModel.BlogCategoryParentId" />
    <button type="submit">Submit</button>
</form>

Code Behind

using Ardalis.Result;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace SlackBotIron.Pages
{
    public class BlogCategoryEditViewModel
    {
        public string BlogCategoryName { get; set; }

        public int BlogCategoryParentId { get; set; }
    }

    public class EditBlogCategoryModel : PageModel
    {
        private readonly BlogCategoryService blogCategoryService;

        public EditBlogCategoryModel(BlogCategoryService blogCategoryService)
        {
            this.blogCategoryService = blogCategoryService;
        }

        [BindProperty]
        public BlogCategoryEditViewModel BlogCategoryEditViewModel { get; set; }

        public IActionResult OnGet(int blogCategoryId)
        {
            var editBlogCategoryRequest = new BlogCategory
            {
                BlogCategoryId = blogCategoryId,
                ParentId = BlogCategoryParentId,
                Name = BlogCategoryName,
            };

            var updateResult = this.blogCategoryService.UpdateAsync(editBlogCategoryRequest);

            if (updateResult.ValidationErrors.Any())
            {
                foreach (var error in updateResult.ValidationErrors)
                {
                    modelState.AddModelError(error.Identifier, error.ErrorMessage);
                }
            }

            if (updateResult.Status == ResultStatus.NotFound)
            {
                return this.NotFound();
            }
            else if (updateResult.Status == ResultStatus.Ok)
            {
                return this.RedirectToPage("/Blog/Category/Index");
            }
            else
            {
                throw new NotImplementedException($"Unexpected result status from {nameof(this.blogCategoryService)}.{nameof(this.blogCategoryService.UpdateAsync)}");
            }
        }
    }
}

My questions are:

VictorioBerra commented 1 year ago

I created a project that illustrates the above, would you change anything? Mainly look at https://github.com/VictorioBerra/ArdalisResult-Architecture-Example/blob/main/ArdalisResultArch/Pages/Blog/Edit.cshtml.cs#L42

I validate the model, and I also validate in the app service (class that returns Result<>). So I validate twice.

VictorioBerra commented 1 year ago

Closing as I dont think this is the best place for this type of discussion but I would love to find a better place to talk about .NET architecture. There seems to be a lot of libraries, sample apps, Pluralsight videos and talks about Clean Code, Onion Arch, Hexigonal Arch, but very few places to actually get feedback and discuss.