2014-12-03 6 views
6

신청서에는 CQRS가 있습니다 : IAsyncCommandIAsyncCommandHandler<IAsyncCommand>입니다.MVC5 비동기 ActionResult. 그게 가능하니?

보통 명령은 다음과 같이 중재자를 통해 처리됩니다 잘 작동

var mediator = //get mediator injected into MVC controller via constructor 
var asyncCommand = // construct AsyncCommand 
// mediator runs ICommandValidator and that returns a list of errors if any 
var errors = await mediator.ProcessCommand(asyncCommand); 

합니다. 이제 나는 컨트롤러 액션에서 반복적 인 코드를 많이 수행한다는 것을 알게되었습니다 :

public async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command) 
{ 
    if (!ModelState.IsValid) 
    { 
     return View(command); 
    } 

    var result = await mediator.ProcessCommandAsync(command); 

    if (!result.IsSuccess()) 
    { 
     AddErrorsToModelState(result); 
     return View(command); 
    } 
    return RedirectToAction(MVC.HomePage.Index()); 
} 

그리고이 패턴은 많은 컨트롤러에서 반복해서 반복됩니다. 그래서 단일 스레드 명령에 내가했던 단순화 :

public class ProcessCommandResult<T> : ActionResult where T : ICommand 
{ 
    private readonly T command; 
    private readonly ActionResult failure; 
    private readonly ActionResult success; 
    private readonly IMediator mediator; 


    public ProcessCommandResult(T command, ActionResult failure, ActionResult success) 
    { 
     this.command = command; 
     this.success = success; 
     this.failure = failure; 

     mediator = DependencyResolver.Current.GetService<IMediator>(); 
    } 

    public override void ExecuteResult(ControllerContext context) 
    { 
     if (!context.Controller.ViewData.ModelState.IsValid) 
     { 
      failure.ExecuteResult(context); 
      return; 
     } 

     var handlingResult = mediator.ProcessCommand(command); 

     if (handlingResult.ConainsErrors()) 
     { 
      AddErrorsToModelState(handlingResult); 
      failure.ExecuteResult(context); 
     } 

     success.ExecuteResult(context); 
    } 
    // plumbing code 
} 

그리고 일부 배관 공사 완료 후, 내 컨트롤러 액션은 다음과 같습니다

가 :

public virtual ActionResult Create(DoStuffCommand command) 
{ 
    return ProcessCommand(command, View(command), RedirectToAction(MVC.HomePage.Index())); 
} 

이 내가 돈 동기화-명령에 대해 잘 작동 ' 패턴을 async-await 할 필요가 없습니다. async 작업을 시도하자마자 MVC에 AsyncActionResult이 없기 때문에 컴파일되지 않으며 MVC 프레임 워크에서 비동기 작업을 void ExecuteResult(ControllerContext context)에서 사용할 수 없습니다.

그럼, 내가 어떻게 질문의 맨 위에 인용 컨트롤러 작업의 일반적인 구현을 만들 수있는 아이디어?

+5

'AsyncActionResult'라는 이름의 항목이 어디에 연관되는지 알 수 없습니다. 제네릭 메서드를 구현하는 경우 작업 또는 작업 만 반환하면됩니다. 비동기 작업은 항상 작업을 반환합니다. 'async void'는 비동기 이벤트 핸들러 (또는 메소드와 같은 핸들러)와 * nowhere * else에서만 사용되는 매우 특정한 구문입니다. 비동기적인'void' 메쏘드는'Task'를 리턴하는 함수입니다. 함수의 동등한 기능은'Task ' –

+0

@PanagiotisKanavos를 반환하는 함수입니다. 예, 저는'async void'를 사용해서는 안된다는 것을 잘 알고 있습니다. 그리고 Mediator를 실행하기 전에'ModelState'가 유효한 상태인지 확인해야하기 때문에 Task 를 반환 할 수 없습니다. MVC 파이프 라인을 통과하고 프레임 워크에서'ModelState'를 어떻게 든 꺼내야합니다. – trailmax

+0

액션 자체가 들어오는 요청과 예상되는 결과와 같은 다른 관심사가 섞여있는 것처럼 보입니다. 이 시점에서 ProcessCommandResult 클래스는 Controller처럼 보입니다. 유효성 검사, 바인딩 등을 무시하려면 MVC에 다른 메커니즘이 있습니다. 실제로 여기에있는 것은 CQRS를 위반합니다. ICommand를 구현하여 명령 자체처럼 응답 (ActionResult)을 사용하고 있습니다. –

답변

0

Action과 같은 것처럼 보이지만 ActionResult 대신 논리를 처리하는 것이 가장 좋습니다.

코드가 중복되는 경우 보호 된 도우미 메서드로 기본 클래스를 사용하는 것이 좋습니다 ...?

public class BaseCommandController : Controller 
{ 
    protected IMediator Mediator { get { return DependencyResolver.Current.GetService(typeof (IMediator)) as IMediator; } } 

    public async virtual Task<ActionResult> BaseDoStuff<TCommand>(TCommand command, Func<ActionResult> success, Func<ActionResult> failure) 
    { 
     if (!ModelState.IsValid) 
     { 
      return failure(); 
     } 

     var result = await Mediator.ProcessCommand(command); 

     if (!result.IsSuccess()) 
     { 
      AddErrorsToModelState(result); 
      return failure(); 
     } 

     return success(); 
    } 

    private void AddErrorsToModelState(IResponse result) 
    { 
    } 

} 
컨트롤러의 행동이 그 다음으로 렌더링됩니다

...

public class DefaultController : BaseCommandController 
{ 
    protected async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command) 
    { 
     return await BaseDoStuff(command,() => RedirectToAction("Index"),() => View(command)); 
    } 
} 
+0

우리는 정확히 그 사실을 알았지 만 여러 가지 이유로 우리에게는 적합하지 않습니다. – trailmax

1
귀하의 솔루션 (서비스 위치 및 기타 냄새를 모두 포함) 매우 냄새, 지나치게 복잡 보인다 시점을 놓칠 것 같다

무엇 ActionResults는 (명령 객체 그 자체입니다.)

실제로 이것은 The XY Problem의 좋은 예입니다. 행동 방법에서 일반적인 코드를 비동기 친화적 인 방식으로 리팩터링하는 실제 문제에 대해 묻는 대신 문제를 해결한다고 생각하는 지나치게 복잡한 솔루션을 생각해냅니다. 불행히도, 당신은 그것을 작동하게 만드는 방법을 알아낼 수 없으므로, 당신은 당신의 진짜 문제보다는 그 문제에 대해 물어볼 것입니다.

간단한 도우미 기능으로 원하는 것을 얻을 수 있습니다.

public async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command) 
{ 
    return await ControllerHelper.Helper(command, ModelState, _mediator, 
     RedirectToAction(MVC.HomePage.Index()), View(command), View(command)); 
} 

public static class ControllerHelper 
{ 
    // You may need to constrain this to where T : class, didn't test it 
    public static async Task<ActionResult> Helper<T>(T command, 
     ModelStateDictionary ModelState, IMediator mediator, ActionResult returnAction, 
     ActionResult successAction, ActionResult failureAction) 
    { 
     if (!ModelState.IsValid) 
     { 
      return failureResult; 
     } 

     var result = await mediator.ProcessCommandAsync(command); 

     if (!result.IsSuccess()) 
     { 
      ModelState.AddErrorsToModelState(result); 
      return successResult; 
     } 

     return returnAction; 
    } 

    public static void AddErrorsToModelState(this ModelStateDictionary ModelState, ...) 
    { 
     // add your errors to the ModelState 
    } 
} 

또는 상태 저장 객체로 만들고 생성자 삽입을 통해 계단식 종속성을 통해 조정자를 삽입 할 수 있습니다. ModelState를 삽입하는 것은 쉽지 않습니다. 불행히도 ModelState를 매개 변수로 전달해야합니다.

또한 ActionResults의 문자열을 전달할 수도 있지만 RedirectToActionResult 객체가 새로 추가되지 않으므로 RedirectToRoute 객체를 초기화하는 것이 엉망이며 ActionResult를 전달하기가 더 쉽습니다. 새로운 ViewResult를 직접 생성하는 것보다 Controller View() 함수를 사용하는 것이 훨씬 쉽습니다.

Sambo가 사용하는 Func<ActionResult> 접근법을 사용할 수도 있습니다.이 방법은 게으르게 평가되므로 필요할 때만 RedirectToAction 메서드를 호출합니다. RedirectToAction에 충분한 가치가있는 오버 헤드가 있다고 생각하지 않습니다.

+0

리팩토링에 대한 반복 작업을 충분히 마친 후 나 자신이 끝내었던 것과 거의 같습니다. – trailmax

+1

@ trailmax - 네, 질문이 몇 달 된 것을 알지 못했습니다. 삼보가 부딪 혔습니다. 다행히도 같은 결론에 도달했습니다 ... 나는베이스 컨트롤러를 싫어합니다. –