MediatR CQRS - How to deal with unexisting resources (asp.net core web api)











up vote
2
down vote

favorite












So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.



If we take DELETE for example, here is my controller action:



[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});

return NoContent();
}


The Command:



public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}


The Command Handler:



public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;

public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}

public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);


if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}

return Unit.Value;
}
}


As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.










share|improve this question
























  • What is Unit?
    – Kirk Larkin
    Nov 22 at 16:00










  • @KirkLarkin It's defined in the MediatR library, but I'm still trying to figure that one out myself haha. A lot of the Command examples I've seen return Task<Unit>.
    – rejy11
    Nov 22 at 16:22















up vote
2
down vote

favorite












So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.



If we take DELETE for example, here is my controller action:



[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});

return NoContent();
}


The Command:



public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}


The Command Handler:



public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;

public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}

public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);


if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}

return Unit.Value;
}
}


As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.










share|improve this question
























  • What is Unit?
    – Kirk Larkin
    Nov 22 at 16:00










  • @KirkLarkin It's defined in the MediatR library, but I'm still trying to figure that one out myself haha. A lot of the Command examples I've seen return Task<Unit>.
    – rejy11
    Nov 22 at 16:22













up vote
2
down vote

favorite









up vote
2
down vote

favorite











So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.



If we take DELETE for example, here is my controller action:



[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});

return NoContent();
}


The Command:



public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}


The Command Handler:



public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;

public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}

public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);


if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}

return Unit.Value;
}
}


As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.










share|improve this question















So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.



If we take DELETE for example, here is my controller action:



[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});

return NoContent();
}


The Command:



public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}


The Command Handler:



public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;

public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}

public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);


if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}

return Unit.Value;
}
}


As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.







c# asp.net-core mediatr






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 22 at 16:12









Kirk Larkin

18.5k33654




18.5k33654










asked Nov 22 at 15:15









rejy11

13410




13410












  • What is Unit?
    – Kirk Larkin
    Nov 22 at 16:00










  • @KirkLarkin It's defined in the MediatR library, but I'm still trying to figure that one out myself haha. A lot of the Command examples I've seen return Task<Unit>.
    – rejy11
    Nov 22 at 16:22


















  • What is Unit?
    – Kirk Larkin
    Nov 22 at 16:00










  • @KirkLarkin It's defined in the MediatR library, but I'm still trying to figure that one out myself haha. A lot of the Command examples I've seen return Task<Unit>.
    – rejy11
    Nov 22 at 16:22
















What is Unit?
– Kirk Larkin
Nov 22 at 16:00




What is Unit?
– Kirk Larkin
Nov 22 at 16:00












@KirkLarkin It's defined in the MediatR library, but I'm still trying to figure that one out myself haha. A lot of the Command examples I've seen return Task<Unit>.
– rejy11
Nov 22 at 16:22




@KirkLarkin It's defined in the MediatR library, but I'm still trying to figure that one out myself haha. A lot of the Command examples I've seen return Task<Unit>.
– rejy11
Nov 22 at 16:22












2 Answers
2






active

oldest

votes

















up vote
1
down vote



accepted










MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:



public class DeleteCourseCommand : IRequest<bool>
...


In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.



Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:



public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
...

public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = ...

if (course == null)
return false; // Simple example, where false means it wasn't found.

...

return true;
}
}


The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).



Finally, you'll need to update your Delete action to use the new response type, like this:



public async Task<IActionResult> Delete(int id)
{
var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});

if (!courseWasFound)
return NotFound();

return NoContent();
}





share|improve this answer




























    up vote
    0
    down vote













    I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:



    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
    {
    public override void OnException(ExceptionContext context)
    {
    if (context.Exception is ValidationException)
    {
    context.HttpContext.Response.ContentType = "application/json";
    context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
    context.Result = new JsonResult(
    ((ValidationException)context.Exception).Failures);

    return;
    }

    var code = HttpStatusCode.InternalServerError;

    if (context.Exception is NotFoundException)
    {
    code = HttpStatusCode.NotFound;
    }

    context.HttpContext.Response.ContentType = "application/json";
    context.HttpContext.Response.StatusCode = (int)code;
    context.Result = new JsonResult(new
    {
    error = new { context.Exception.Message }
    });
    }
    }


    Startup Class:



    services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));


    Custom Exception:



    public class NotFoundException : Exception
    {
    public NotFoundException(string entityName, int key)
    : base($"Entity {entityName} with primary key {key} was not found.")
    {
    }
    }


    Then in the Handle method:



    if (course != null)
    {
    _context.Courses.Remove(course);
    var saveResult = await _context.SaveChangesAsync(cancellationToken);
    if (saveResult <= 0)
    {
    throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
    }
    }
    else
    {
    throw new NotFoundException(nameof(Course), request.Id);
    }

    return Unit.Value;


    This seems to do the trick, if anyone can see any potential issues with this please let me know!






    share|improve this answer





















    • @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
      – rejy11
      Nov 22 at 16:18










    • Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
      – Kirk Larkin
      Nov 22 at 16:22










    • @KirkLarkin I guess it depends on how much of a purist you are haha.
      – rejy11
      Nov 22 at 16:27











    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53433910%2fmediatr-cqrs-how-to-deal-with-unexisting-resources-asp-net-core-web-api%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote



    accepted










    MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:



    public class DeleteCourseCommand : IRequest<bool>
    ...


    In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.



    Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:



    public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
    {
    ...

    public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
    {
    var course = ...

    if (course == null)
    return false; // Simple example, where false means it wasn't found.

    ...

    return true;
    }
    }


    The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).



    Finally, you'll need to update your Delete action to use the new response type, like this:



    public async Task<IActionResult> Delete(int id)
    {
    var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});

    if (!courseWasFound)
    return NotFound();

    return NoContent();
    }





    share|improve this answer

























      up vote
      1
      down vote



      accepted










      MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:



      public class DeleteCourseCommand : IRequest<bool>
      ...


      In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.



      Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:



      public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
      {
      ...

      public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
      {
      var course = ...

      if (course == null)
      return false; // Simple example, where false means it wasn't found.

      ...

      return true;
      }
      }


      The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).



      Finally, you'll need to update your Delete action to use the new response type, like this:



      public async Task<IActionResult> Delete(int id)
      {
      var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});

      if (!courseWasFound)
      return NotFound();

      return NoContent();
      }





      share|improve this answer























        up vote
        1
        down vote



        accepted







        up vote
        1
        down vote



        accepted






        MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:



        public class DeleteCourseCommand : IRequest<bool>
        ...


        In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.



        Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:



        public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
        {
        ...

        public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
        {
        var course = ...

        if (course == null)
        return false; // Simple example, where false means it wasn't found.

        ...

        return true;
        }
        }


        The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).



        Finally, you'll need to update your Delete action to use the new response type, like this:



        public async Task<IActionResult> Delete(int id)
        {
        var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});

        if (!courseWasFound)
        return NotFound();

        return NoContent();
        }





        share|improve this answer












        MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:



        public class DeleteCourseCommand : IRequest<bool>
        ...


        In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.



        Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:



        public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
        {
        ...

        public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
        {
        var course = ...

        if (course == null)
        return false; // Simple example, where false means it wasn't found.

        ...

        return true;
        }
        }


        The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).



        Finally, you'll need to update your Delete action to use the new response type, like this:



        public async Task<IActionResult> Delete(int id)
        {
        var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});

        if (!courseWasFound)
        return NotFound();

        return NoContent();
        }






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 22 at 16:12









        Kirk Larkin

        18.5k33654




        18.5k33654
























            up vote
            0
            down vote













            I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:



            [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
            public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
            {
            public override void OnException(ExceptionContext context)
            {
            if (context.Exception is ValidationException)
            {
            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            context.Result = new JsonResult(
            ((ValidationException)context.Exception).Failures);

            return;
            }

            var code = HttpStatusCode.InternalServerError;

            if (context.Exception is NotFoundException)
            {
            code = HttpStatusCode.NotFound;
            }

            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)code;
            context.Result = new JsonResult(new
            {
            error = new { context.Exception.Message }
            });
            }
            }


            Startup Class:



            services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));


            Custom Exception:



            public class NotFoundException : Exception
            {
            public NotFoundException(string entityName, int key)
            : base($"Entity {entityName} with primary key {key} was not found.")
            {
            }
            }


            Then in the Handle method:



            if (course != null)
            {
            _context.Courses.Remove(course);
            var saveResult = await _context.SaveChangesAsync(cancellationToken);
            if (saveResult <= 0)
            {
            throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
            }
            }
            else
            {
            throw new NotFoundException(nameof(Course), request.Id);
            }

            return Unit.Value;


            This seems to do the trick, if anyone can see any potential issues with this please let me know!






            share|improve this answer





















            • @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
              – rejy11
              Nov 22 at 16:18










            • Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
              – Kirk Larkin
              Nov 22 at 16:22










            • @KirkLarkin I guess it depends on how much of a purist you are haha.
              – rejy11
              Nov 22 at 16:27















            up vote
            0
            down vote













            I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:



            [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
            public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
            {
            public override void OnException(ExceptionContext context)
            {
            if (context.Exception is ValidationException)
            {
            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            context.Result = new JsonResult(
            ((ValidationException)context.Exception).Failures);

            return;
            }

            var code = HttpStatusCode.InternalServerError;

            if (context.Exception is NotFoundException)
            {
            code = HttpStatusCode.NotFound;
            }

            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)code;
            context.Result = new JsonResult(new
            {
            error = new { context.Exception.Message }
            });
            }
            }


            Startup Class:



            services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));


            Custom Exception:



            public class NotFoundException : Exception
            {
            public NotFoundException(string entityName, int key)
            : base($"Entity {entityName} with primary key {key} was not found.")
            {
            }
            }


            Then in the Handle method:



            if (course != null)
            {
            _context.Courses.Remove(course);
            var saveResult = await _context.SaveChangesAsync(cancellationToken);
            if (saveResult <= 0)
            {
            throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
            }
            }
            else
            {
            throw new NotFoundException(nameof(Course), request.Id);
            }

            return Unit.Value;


            This seems to do the trick, if anyone can see any potential issues with this please let me know!






            share|improve this answer





















            • @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
              – rejy11
              Nov 22 at 16:18










            • Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
              – Kirk Larkin
              Nov 22 at 16:22










            • @KirkLarkin I guess it depends on how much of a purist you are haha.
              – rejy11
              Nov 22 at 16:27













            up vote
            0
            down vote










            up vote
            0
            down vote









            I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:



            [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
            public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
            {
            public override void OnException(ExceptionContext context)
            {
            if (context.Exception is ValidationException)
            {
            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            context.Result = new JsonResult(
            ((ValidationException)context.Exception).Failures);

            return;
            }

            var code = HttpStatusCode.InternalServerError;

            if (context.Exception is NotFoundException)
            {
            code = HttpStatusCode.NotFound;
            }

            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)code;
            context.Result = new JsonResult(new
            {
            error = new { context.Exception.Message }
            });
            }
            }


            Startup Class:



            services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));


            Custom Exception:



            public class NotFoundException : Exception
            {
            public NotFoundException(string entityName, int key)
            : base($"Entity {entityName} with primary key {key} was not found.")
            {
            }
            }


            Then in the Handle method:



            if (course != null)
            {
            _context.Courses.Remove(course);
            var saveResult = await _context.SaveChangesAsync(cancellationToken);
            if (saveResult <= 0)
            {
            throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
            }
            }
            else
            {
            throw new NotFoundException(nameof(Course), request.Id);
            }

            return Unit.Value;


            This seems to do the trick, if anyone can see any potential issues with this please let me know!






            share|improve this answer












            I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:



            [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
            public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
            {
            public override void OnException(ExceptionContext context)
            {
            if (context.Exception is ValidationException)
            {
            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            context.Result = new JsonResult(
            ((ValidationException)context.Exception).Failures);

            return;
            }

            var code = HttpStatusCode.InternalServerError;

            if (context.Exception is NotFoundException)
            {
            code = HttpStatusCode.NotFound;
            }

            context.HttpContext.Response.ContentType = "application/json";
            context.HttpContext.Response.StatusCode = (int)code;
            context.Result = new JsonResult(new
            {
            error = new { context.Exception.Message }
            });
            }
            }


            Startup Class:



            services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));


            Custom Exception:



            public class NotFoundException : Exception
            {
            public NotFoundException(string entityName, int key)
            : base($"Entity {entityName} with primary key {key} was not found.")
            {
            }
            }


            Then in the Handle method:



            if (course != null)
            {
            _context.Courses.Remove(course);
            var saveResult = await _context.SaveChangesAsync(cancellationToken);
            if (saveResult <= 0)
            {
            throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
            }
            }
            else
            {
            throw new NotFoundException(nameof(Course), request.Id);
            }

            return Unit.Value;


            This seems to do the trick, if anyone can see any potential issues with this please let me know!







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 22 at 16:12









            rejy11

            13410




            13410












            • @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
              – rejy11
              Nov 22 at 16:18










            • Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
              – Kirk Larkin
              Nov 22 at 16:22










            • @KirkLarkin I guess it depends on how much of a purist you are haha.
              – rejy11
              Nov 22 at 16:27


















            • @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
              – rejy11
              Nov 22 at 16:18










            • Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
              – Kirk Larkin
              Nov 22 at 16:22










            • @KirkLarkin I guess it depends on how much of a purist you are haha.
              – rejy11
              Nov 22 at 16:27
















            @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
            – rejy11
            Nov 22 at 16:18




            @KirkLarkin Indeed! I like your solution. However, I was under the impression that since DELETE is a command, it should not return anything.
            – rejy11
            Nov 22 at 16:18












            Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
            – Kirk Larkin
            Nov 22 at 16:22




            Yeah, I understand where you're coming from with CQS there but this is really just a simple status code that's coming back (a bool here) and I think that's fine (although it's somewhat opinion-based). The main point here is that it's not a query in the strict sense.
            – Kirk Larkin
            Nov 22 at 16:22












            @KirkLarkin I guess it depends on how much of a purist you are haha.
            – rejy11
            Nov 22 at 16:27




            @KirkLarkin I guess it depends on how much of a purist you are haha.
            – rejy11
            Nov 22 at 16:27


















            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53433910%2fmediatr-cqrs-how-to-deal-with-unexisting-resources-asp-net-core-web-api%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Trompette piccolo

            Slow SSRS Report in dynamic grouping and multiple parameters

            Simon Yates (cyclisme)