Get View Name where ViewResult.ViewName is empty s

2019-04-30 18:10发布

问题:

I have written an extension method on ActionResult for use in unit testing which will assert whether the ViewName returned is what was expected. This is the code I have so far:

public static void AssertViewWasReturned(this ActionResult result, string viewName)
{
    string actualViewName;

    if (result is ViewResult)
        actualViewName = (result as ViewResult).ViewName;
    else if (result is PartialViewResult)
        actualViewName = (result as PartialViewResult).ViewName;
    else
        throw new InvalidOperationException("Results of type " + result.GetType() + " don't have a ViewName");

    Assert.AreEqual(viewName, actualViewName, string.Format("Expected a View named{0}, got a View named {1}", viewName, actualViewName));
}

This works fine except where the controller returns a View without specifying a name - in this case result.ViewName is an empty string.

So, my question is - is there any well of telling from a ViewResult object what the name of the View was where ViewName is an empty string?

回答1:

If your controller-method is not called through the MVC pipeline, additional information are not added to the Controller.ViewData dictionary (which I assumed would somehow provide an "action"-key, but couldn't confirm). But since you using your controller "outside" the context of the routing-framework etc. there is no way it knows about the called method.

So the answer is simply "no". If the name of the view was not specified, you cannot determine it from the ViewResult returned by the action. At least not in the way your controller is being tested (which is totally fine by the way).



回答2:

For what it's worth - I've revised my test my extension method like this (incorporating J. Tihon's feedback):

public static void AssertViewWasReturned(this ActionResult result, string viewName, string defaultViewName)
{    
    Assert.IsInstanceOf<ViewResultBase>(result, "Result is not an instance of ViewResultBase");
    var viewResult = (ViewResultBase)result;

    var actualViewName = viewResult.ViewName;

    if (actualViewName == "")
        actualViewName = defaultViewName;

    Assert.AreEqual(viewName, actualViewName, string.Format("Expected a View named{0}, got a View named {1}", viewName, actualViewName));
}

This means that my unit tests can contain code like this:

var result = controller.MyAction();
result.AssertViewWasReturned("ExpectedViewName","MyAction")

It's not as good as I'd hoped, as I need to specify the 'defaultViewName' (i.e. the action name) but is a reasonable comprimise.



回答3:

You could update your controllers to always pass the view you want.

Instead of:

public ActionResult Index()
{
    return View();
}

Do this instead:

public ActionResult Index()
{
    return View("Index");
}

Either that or change your unit test code.



回答4:

Not sure I am understanding your question, but if the View() method is called with no parameters, MVC will look for a View with the name of the calling action method, in a directory named as the Controller (without the "Controller" suffix added onto the name).

For instance, this Action:

public class UserController : Controller
{
    public ActionResult SomeAction()
    {
        // some code here
        return View();
    }
}

Because this is an empty View() call, MVC will look for the View with the path and filename of ~\Views\User\SomeAction.cshtml.

Does that answer your question?



回答5:

You have to call your view name while returning.

Ex:

Public ActionResult Index(){

// your code

Return View("YourViewName", DataToSendForView);
}


回答6:

You can override the controller. The OnResultExecuted Method is the point in the controller lifecycle where there is a view available.

protected override void OnResultExecuted(ResultExecutedContext ctx)
{
    base.OnResultExecuted(ctx);
    String ViewPath = ((System.Web.Mvc.BuildManagerCompiledView)((System.Web.Mvc.ViewResultBase)ctx.Result).View).ViewPath.ToString();
}