ActionResult extension doesn't work with Page() ActionResult method - c#

I have an extension for ActionResult that adds a toast to TempData when returning a page:
public static IActionResult WithMessage(this ActionResult result, InformMessage msg)
{
return new InformMessageResult(result, msg);
}
and this is InformMessageResult:
public class InformMessageResult : ActionResult
{
public ActionResult InnerResult { get; set; }
public InformMessage InformMessage { get; set; }
public InformMessageResult (ActionResult innerResult, InformMessage informMsg)
{
InnerResult = innerResult;
InformMessage = informMsg;
}
public override async Task ExecuteResultAsync(ActionContext context)
{
ITempDataDictionaryFactory factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
ITempDataDictionary tempData = factory.GetTempData(context.HttpContext);
tempData.Put("InformMessage", InformMessage);
await InnerResult.ExecuteResultAsync(context);
}
}
This works well with
return RedirectToPage(etc).WithMessage(etc)
and the like, but fails with
return Page().WithMessage(etc)
and the debugger highlights
await InnerResult.ExecuteResultAsync(context);
saying InnerResult not set to an instance of an object.
Is there a way I can make this work with Return Page()?
Edit for additional info:
I tested what was being sent in as the "InnerResult" and it looks like with Return Page(), everything is null (by design, I'd say, as I do nothing to it before that point):
with RedirectToPage():
With Page():

This is an older question, but I needed functionality like this myself, and dug deep to find the reason.
As you can see from your debugging, the Page method generates a completely blank PageResult. Being as every property is null, calling ExecuteResultAsync on it fails as it obviously can't do anything with all-null values.
The reason Page() otherwise works the rest of the time is due to behind-the-scenes magic in PageActionInvoker, specifically in its InvokeResultAsync method. It will detect that your ViewData and Page are blank and populate them before it, itself, calls the pageResult.ExecuteResultAsync method.
Hence, you can still get your InformMessageResult to work if you do the same work as PageActionInvoker does. Here's how:
public override async Task ExecuteResultAsync(ActionContext context)
{
/* temp data work goes here */
if (InnerResult is PageResult pageResult)
{
var pageContext = context as PageContext
?? throw new ArgumentException("context must be a PageContext if your InnerResult is a PageResult.", "context");
var pageFactoryProvider = pageContext.HttpContext.RequestServices.GetRequiredService<IPageFactoryProvider>();
var pageFactory = pageFactoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
var viewContext = new ViewContext(
pageContext,
NullView.Instance,
pageContext.ViewData,
tempData,
TextWriter.Null,
new Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelperOptions()
);
viewContext.ExecutingFilePath = pageContext.ActionDescriptor.RelativePath;
pageResult.ViewData = viewContext.ViewData;
pageResult.Page = (PageBase)pageFactory(pageContext, viewContext);
}
await InnerResult.ExecuteResultAsync(context);
}
private class NullView : IView
{
public static readonly NullView Instance = new NullView();
public string Path => string.Empty;
public Task RenderAsync(ViewContext context)
{
if (context == null) throw new ArgumentNullException("context");
return Task.CompletedTask;
}
}

The problem I suspect is that Page() and RedirectToPage() inherit from different base classes.
RedirectToPage() as per this documentation. It has the following Inheritance:
Object -> ActionResult -> RedirectToPageResult
This is exposed by some inheritance of the controller. So you're extension of ActionResult is available to be used.
However the Page() method is part of a RazorPages class as per this documentation. So it's inheritance is as follows:1
Object -> RazorPageBase -> PageBase -> Page
Now the Page() method of that class does return a PageResult which looks to inherit from ActionResult as defined here.
So with that in mind I'd suggest casting it to the base ActionResult first, and then using your extension method. Something like this perhaps:
var baseClass = (Page() as ActionResult);
return baseClass.WithMessage(etc);
1 You can see the base type in the second image the OP supplied.

Page() and RedirectToPage() are helper methods for PageResult and RedirectToPageResult respectively. So instead of newing up you call these methods.
When you call Page() it calls ExecuteResultAsync behind the scenes to render the PageResult. At that point, all the properties are null i.e. Page, Model, ViewData etc. As the result is already rendered, So you can't call another ExecuteResultAsync using the WithMessage extension method.
When you call RedirectToPage it issues a 301/302 and generates a new Request that will result in RedirectToPageResult. So the RedirectToPageResult is not rendered yet and you have the option to utilise your WithMessage extension method.
Having said that, I don't think it's possible to utilise the WithMessage when using Page() method, AFAIK.

Related

Create an IEnumerable<AbstractClass> of specific instances from different classes all inheriting from the abstract class

I have controllers which, for the sake of backwards compatibility, only have one action. The JSON request comes with an attribute "type" which determines what the action should do with it.
My idea for a clean solution was to build a set of action handlers. They all inherit from an abstract class called ActionHandler which has two methods
public abstract bool CanHandle(ClientRequest request);
and
public abstract object Handle(dynamic request)
And it has a property
public abstract string ActionForController { get; }
in which the specific actionhandlers just return the name of the controller they want to handle for. This is not very important, but may help clarify something later.
The controller is inserted with an ActionHandlerRegister which has an IEnumerable and a method "GetActionHandler". It returns the first specific ActionHandler that can handle the request.
public ActionHandler GetActionHandler(ClientRequest request)
{
foreach(var actionHandler in ActionHandlers)
{
if (actionHandler.CanHandle(request))
{
return actionHandler;
}
}
throw new BadRequestException(string.Format(CultureInfo.InvariantCulture, BadRequestExceptionTemplate, request.Type));
}
The controllers look like this:
public class LogController : ControllerBase
{
private readonly IActionHandlerRegister<LogController> logHandlers;
public LogController(IActionHandlerRegister<LogController> logHandlers)
{
this.logHandlers = logHandlers ?? throw new ArgumentNullException(nameof(logHandlers));
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] dynamic rawJson)
{
var jsonBody = ((JsonElement)rawJson).ToString();
if (string.IsNullOrEmpty(jsonBody))
{
return BadRequest(ActionHandler.BadRequestRequestNullOrTypeMissingError);
}
var clientRequest = JsonSerializer.Deserialize<ClientRequest>(jsonBody);
if (clientRequest == null || string.IsNullOrEmpty(clientRequest.Type))
{
return BadRequest(ActionHandler.BadRequestRequestNullOrTypeMissingError);
}
try
{
var handler = logHandlers.GetActionHandler(clientRequest);
var result = handler.Handle(rawJson);
return Ok(result);
}
catch (BadRequestException ex)
{
return BadRequest(ex.Message);
}
}
}
For people paying attention: yes, I'm passing the rawjson to handler.Handle. This is because "ClientRequest" is something generic (from which I can read the type) but the handler needs the specific request, so it's deserializing again to something more specific. Maybe there are better solutions for that. Feel free to tell me.
In startup.cs, the insertion of the ActionHandlerRegister into the controller looks like this:
public void RegisterActionHandlersAsSingleton(IServiceCollection services)
{
IEnumerable<ActionHandler> listOfActionHandlers =
from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
from actionHandlerType in domainAssembly.GetTypes()
where actionHandlerType.IsAssignableFrom(typeof(ActionHandler))
select (ActionHandler)Activator.CreateInstance(actionHandlerType);
services.AddSingleton<IActionHandlerRegister<LogController>>(new ActionHandlerRegister<LogController>(listOfActionHandlers.Where(a => a.ActionForController == nameof(LogController))));
// other controllers ...
}
You might be able to guess, this last piece of code crashes at runtime telling me it's unable to cast to ActionHandler.
System.InvalidCastException: Unable to cast object of type
'System.Object' to type
'TcServerModules.ActionHandlers.ActionHandler'.
I have been playing around with different solutions, but none of them scratch that itch. What would be a nice, true-to OO-design principle

Where is the ViewResult returned by ASP.NET Core Controller consumed?

In the definition of the ASP.NET Core Controller class, it defines the View() method as returning a ViewResult object.
namespace Microsoft.AspNetCore.Mvc
{
//...
public abstract class Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable
{
//...
public virtual ViewResult View();
//...
}
}
Where is it in the framework that invokes a controller method such as below and consumes the ViewResult returned by the call to the View() method?
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
The entire process is quite involved, and is managed internally by the ResourceInvoker class, but the main piece of plumbing responsible for handling a ViewResult is the ViewExecutor class (source code).
The primary entry point of the ViewExecutor is the ExecuteAsync() method, which accepts the ActionContext and your ViewResult. It then locates the appropriate view using the registered IViewEngine (typically the RazorViewEngine) to identify the corresponding view and return a ViewEngineResult instance. That ViewEngineResult is then used to execute and render the view.
Extending ViewExecutor
If you need to customize the logic, you can implement your own view executor class by implementing the IActionResultExecutor<> interface and then registering it as a singleton using ASP.NET Core's dependency injection container. When doing so, I recommend referencing the out-of-the-box ViewResultExecutor's source code.
This can be useful if you want to implement custom logic for locating the view. For instance, perhaps you want to account for state or context data such as route data, request headers, cookies, session, &c. when locating the view.
So, as a really simple example, let us say you want to allow a view to be optionally selected using a query string value (e.g., ?View=MyView). In that case, you might create a QueryStringViewResultExecutor. Here's a basic proof-of-concept:
public class QueryStringViewResultExecutor : ViewExecutor, IActionResultExecutor<ViewResult>
{
public QueryStringViewResultExecutor(
IOptions<MvcViewOptions> viewOptions,
IHttpResponseStreamWriterFactory writerFactory,
ICompositeViewEngine viewEngine,
ITempDataDictionaryFactory tempDataFactory,
DiagnosticListener diagnosticListener,
IModelMetadataProvider modelMetadataProvider
) : base(
viewOptions, writerFactory, viewEngine, tempDataFactory, diagnosticListener, modelMetadataProvider
)
{
}
public async Task ExecuteAsync(ActionContext context, ViewResult result)
{
var viewEngineResult = FindView(context, result); // See helper method below
viewEngineResult.EnsureSuccessful(originalLocations: null);
var view = viewEngineResult.View;
using (view as IDisposable)
{
await ExecuteAsync(
context,
view,
result.ViewData,
result.TempData,
result.ViewName,
result.StatusCode
).ConfigureAwait(false);
}
}
private ViewEngineResult FindView(ActionContext actionContext, ViewResult viewResult)
{
// Define variables
var view = (ViewEngineResult?)null;
var viewEngine = viewResult.ViewEngine?? ViewEngine;
var searchedPaths = new List<string>();
var requestContext = actionContext.HttpContext.Request;
// If defined, attempt to locate view based on query string variable
if (requestContext.Query.ContainsKey("View"))
{
var queryStringValue = requestContext.Query["View"].First<string>();
if (queryStringValue is not null)
{
view = viewEngine.FindView(actionContext, queryStringValue, isMainPage: true);
searchedPaths = searchedPaths.Union(view.SearchedLocations?? Array.Empty<string>()).ToList();
}
}
// If no view is found, fall back to the view defined on the viewResult
if (!view?.Success?? true)
{
view = viewEngine.FindView(actionContext, viewResult.ViewName, isMainPage: true);
searchedPaths = searchedPaths.Union(view.SearchedLocations ?? Array.Empty<string>()).ToList();
}
// Return view for processing by the razor engine
if (view is not null and { Success: true }) {
return view;
}
return ViewEngineResult.NotFound(viewResult.ViewName, searchedPaths);
}
}
You would then register this in your Startup.ConfigureServices() method as:
services.Services.TryAddSingleton<IActionResultExecutor<ViewResult>, QueryStringViewResultExecutor>();
Disclaimer: You may need to unregister the out-of-the-box ViewExecutor in order to avoid a conflict. Typically, however, you are registering an IActionResultExecutor<> with a custom ViewResult and, thus, that isn't necessary; see below.
Extending ViewResult
Often, you will want to pair this with a custom ViewResult. Why is this useful? Usually because you need to pass additional data to your ViewResultExecutor from your Controller.
So, as a contrived example, imagine that you have themes, and views can optionally be customized based on that theme. You might then add a Theme property to your ViewResult, thus allowing the ViewResultExecutor to first look for a view based on the theme, and otherwise fallback to the non-themed version.
public class ThemedViewResult : ViewResult
{
public string Theme { get; set; }
public override async Task ExecuteResultAsync(ActionContext context)
{
var executor = context.HttpContext.RequestServices.GetRequiredService<IActionResultExecutor<ThemedViewResult>>();
await executor.ExecuteAsync(context, this).ConfigureAwait(false);
}
}
Even if you don't need a custom ViewResult, it's worth pausing for a second to evaluate this code. The underlying ViewResult class implements IActionResult, which exposes a single method, ExecuteResultAsync(). This is called by the ResourceInvoker class mentioned at the top. This in turn locates the registered IActionResultExecutor<>—i.e., the type of component we created and registered in the previous section—and calls its ExecuteAsync() method.
Note: This is a contrived example because often a theme would be accessible by other context data, such as the RouteData, a custom ClaimsPrincipal, or an ISession implementation. But you can imagine other times where this information would be request-specific, and best relayed from the Controller via the ViewResult. For instance, perhaps a CMS where the theme can be selected on a per page basis.
As it's unclear whether you actually need to extend this functionality or are just curious how everything fits together, the above examples are only meant as a proof-of-concept; they aren't tested. Still, they should give you a basic idea of where and how the ViewResult is handled, as well as options for extending that behavior should you need.

MVC5 Async ActionResult. Is that possible?

In our application we have CQRS: we have IAsyncCommand with IAsyncCommandHandler<IAsyncCommand>.
Usually the command is processed via Mediator like this:
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);
That works fine. Now I noticed that I do a lot of repetitive code in controller actions:
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());
}
And this patterns repeats over and over in many-many controllers. So for single-threaded commands I've done simplification:
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
}
And after some plumbing done, my controller action looks like this:
public virtual ActionResult Create(DoStuffCommand command)
{
return ProcessCommand(command, View(command), RedirectToAction(MVC.HomePage.Index()));
}
This works well for sync-commands where I don't need to do async-await patterns. As soon as I try to do async operations, this does not compile, as there is no AsyncActionResult in MVC (or there is and I can't find it) and I can't make MVC framework use async operations on void ExecuteResult(ControllerContext context).
So, any ideas how I can make a generic implementation of the controller action I quoted on top of the question?
Your solution seems overly complex, highly smelly (contains both service location, and other smells) and seems to miss the point of what ActionResults are (command objects themselves, really).
In reality, this is a good example of The XY Problem. Rather than asking about your actual problem, which is refactoring common code in your action methods in an async friendly way, you have instead come up with an overly complex solution that you think solves your problem. Unfortunately, you can't figure out how to make it work, so you ask about THAT problem rather than your real problem.
You can achieve what you want with a simple helper function. Something like this:
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
}
}
Alternatively, you could make it a stateful object and inject the mediator through cascaded dependencies via constructor injection. It's not easy to inject ModelState, unfortunately, so that still needs to be passed as a parameter to the method.
You could also just pass the string for the ActionResults, but since there's no RedirectToActionResult object to new up, you'd have to mess with initializing a RedirectToRoute object and it's just easier to pass the ActionResult. It's also much easier to use the Controllers View() function than to construct a new ViewResult yourself.
You could also use the Func<ActionResult> approach that Sambo uses, which makes it lazy evaluate, so it only calls the RedirectToAction method when necessary. I don't think RedirectToAction has enough overhead to make it worth it.
Seems like an Action is still the best place to handle your logic instead of using an ActionResult.
If the code is duplicated, why not use a base class with a protected helper method...?
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)
{
}
}
Your controller's actions are then rendered as...
public class DefaultController : BaseCommandController
{
protected async virtual Task<ActionResult> DoStuff(DoStuffAsyncCommand command)
{
return await BaseDoStuff(command, () => RedirectToAction("Index"), () => View(command));
}
}

How do I return NotFound() IHttpActionResult with an error message or exception?

I am returning a NotFound IHttpActionResult, when something is not found in my WebApi GET action. Along with this response, I want to send a custom message and/or the exception message (if any). The current ApiController's NotFound() method does not provide an overload to pass a message.
Is there any way of doing this? or I will have to write my own custom IHttpActionResult?
Here's a one-liner for returning a IHttpActionResult NotFound with a simple message:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");
You'd need to write your own action result if you want to customize the response message shape.
We wanted to provide the most common response message shapes out of the box for things like simple empty 404s, but we also wanted to keep these results as simple as possible; one of the main advantages of using action results is that it makes your action method much easier to unit test. The more properties we put on action results, the more things your unit test needs to consider to make sure the action method is doing what you'd expect.
I often want the ability to provide a custom message as well, so feel free to log a bug for us to consider supporting that action result in a future release:
https://aspnetwebstack.codeplex.com/workitem/list/advanced
One nice thing about action results, though, is that you can always write your own fairly easily if you want to do something slightly different. Here's how you might do it in your case (assuming you want the error message in text/plain; if you want JSON, you'd do something slightly different with the content):
public class NotFoundTextPlainActionResult : IHttpActionResult
{
public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
if (request == null)
{
throw new ArgumentNullException("request");
}
Message = message;
Request = request;
}
public string Message { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Execute());
}
public HttpResponseMessage Execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
response.RequestMessage = Request;
return response;
}
}
public static class ApiControllerExtensions
{
public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
{
return new NotFoundTextPlainActionResult(message, controller.Request);
}
}
Then, in your action method, you can just do something like this:
public class TestController : ApiController
{
public IHttpActionResult Get()
{
return this.NotFound("These are not the droids you're looking for.");
}
}
If you used a custom controller base class (instead of directly inheriting from ApiController), you could also eliminate the "this." part (which is unfortunately required when calling an extension method):
public class CustomApiController : ApiController
{
protected NotFoundTextPlainActionResult NotFound(string message)
{
return new NotFoundTextPlainActionResult(message, Request);
}
}
public class TestController : CustomApiController
{
public IHttpActionResult Get()
{
return NotFound("These are not the droids you're looking for.");
}
}
You could use ResponseMessageResult if you like:
var myCustomMessage = "your custom message which would be sent as a content-negotiated response";
return ResponseMessage(
Request.CreateResponse(
HttpStatusCode.NotFound,
myCustomMessage
)
);
yeah, if you need much shorter versions, then I guess you need to implement your custom action result.
You may use ReasonPhrase property of HttpResponseMessage class
catch (Exception exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
ReasonPhrase = exception.Message
});
}
You can create a custom negotiated content result as d3m3t3er suggested. However I would inherit from. Also, if you need it only for returning NotFound, you don't need to initialize the http status from constructor.
public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
public NotFoundNegotiatedContentResult(T content, ApiController controller)
: base(HttpStatusCode.NotFound, content, controller)
{
}
public override Task<HttpResponseMessage> ExecuteAsync(
CancellationToken cancellationToken)
{
return base.ExecuteAsync(cancellationToken).ContinueWith(
task => task.Result, cancellationToken);
}
}
one line code in asp.net core:
Return StatusCode(404, "Not a valid request.");
I solved it by simply deriving from OkNegotiatedContentResult and overriding the HTTP code in the resulting response message. This class allows you to return the content body with any HTTP response code.
public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
public HttpStatusCode HttpStatusCode;
public CustomNegotiatedContentResult(
HttpStatusCode httpStatusCode, T content, ApiController controller)
: base(content, controller)
{
HttpStatusCode = httpStatusCode;
}
public override Task<HttpResponseMessage> ExecuteAsync(
CancellationToken cancellationToken)
{
return base.ExecuteAsync(cancellationToken).ContinueWith(
task => {
// override OK HTTP status code with our own
task.Result.StatusCode = HttpStatusCode;
return task.Result;
},
cancellationToken);
}
}
I was needing to create an IHttpActionResult instance in the body of an IExceptionHandler class, in order to set the ExceptionHandlerContext.Result property. However I also wanted to set a custom ReasonPhrase.
I found that a ResponseMessageResult could wrap a HttpResponseMessage (which allows ReasonPhrase to be set easily).
For Example:
public class MyExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var ex = context.Exception as IRecordNotFoundException;
if (ex != null)
{
context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
}
}
}
If you inherit from the base NegotitatedContentResult<T>, as mentioned, and you don't need to transform your content (e.g. you just want to return a string), then you don't need to override the ExecuteAsync method.
All you need to do is provide an appropriate type definition and a constructor that tells the base which HTTP Status Code to return. Everything else just works.
Here are examples for both NotFound and InternalServerError:
public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
public NotFoundNegotiatedContentResult(string content, ApiController controller)
: base(HttpStatusCode.NotFound, content, controller) { }
}
public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
: base(HttpStatusCode.InternalServerError, content, controller) { }
}
And then you can create corresponding extension methods for ApiController (or do it in a base class if you have one):
public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
return new NotFoundNegotiatedContentResult(message, controller);
}
public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
return new InternalServerErrorNegotiatedContentResult(message, controller);
}
And then they work just like the built-in methods. You can either call the existing NotFound() or you can call your new custom NotFound(myErrorMessage).
And of course, you can get rid of the "hard-coded" string types in the custom type definitions and leave it generic if you want, but then you may have to worry about the ExecuteAsync stuff, depending on what your <T> actually is.
You can look over the source code for NegotiatedContentResult<T> to see all it does. There isn't much to it.
Iknow PO asked with a message text, but another option to just return a 404 is making the method return a IHttpActionResult and use the StatusCode function
public async Task<IHttpActionResult> Get([FromUri]string id)
{
var item = await _service.GetItem(id);
if(item == null)
{
StatusCode(HttpStatusCode.NotFound);
}
return Ok(item);
}
Answers here are missing a little developer story problem. The ApiController class is still exposing a NotFound() method that developers may use. This would cause some 404 response to contain a uncontrolled result body.
I present here a few parts of code "better ApiController NotFound method" that will provide a less error-prone method that does not require developers to know "the better way of sending a 404".
create a class inheriting from ApiController called ApiController
I use this technique to prevent developers from using the original class
override its NotFound method to let devs use the first available api
if you want to discourage this, mark this as [Obsolete("Use overload instead")]
add an extra protected NotFoundResult NotFound(string message) that you want to encourage
problem: the result does not support responding with a body. solution: inherit and use NegotiatedContentResult. see attached better NotFoundResult class.
Another nice possibility is to use a different built-in result type: NotFoundObjectResult(message).
Needed to return the error message for 404 Not Found and I am using Dot Net 6.0.
This is the code
Problem(statusCode: 404, detail: "Put your detailed error message here");
Where Problem is a method present in ControllerBase class.

Why does my asp.net mvc controller not call it's base class' Initialize method during unit tests?

I am attempting to unit test my controllers, and I am using the default MVC 3 AccountController unit tests as a basis. So far I have my controller, which looks like:
public partial class HomeController : MyBaseController
{
public HomeController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; }
public virtual ActionResult Index()
{
return View();
}
public virtual ActionResult About()
{
return View();
}
}
MyBaseController has the following code:
public class MyBaseController : Controller
{
public MyJobLeadsBaseController()
{
CurrentUserId = 0;
}
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
// If the user is logged in, retrieve their login id
var user = MembershipService.GetUser();
if (user != null)
CurrentUserId = (int)user.ProviderUserKey;
}
public int CurrentUserId { get; set; }
public IMembershipService MembershipService { get; set; }
protected IUnitOfWork _unitOfWork;
}
Everything works correctly when I run the actual site, and breakpoints show that the Initialize() is correctly being triggered. However, the following unit test never runs the Initialize(RequestContext) method:
[TestMethod]
public void Index_Shows_When_Not_Logged_In()
{
// Setup
HomeController controller = new HomeController(_unitOfWork);
controller.MembershipService = new MockMembershipService(null);
SetupController(controller);
// Act
ActionResult result = controller.Index();
// Verify
Assert.IsNotNull(result, "Index returned a null action result");
Assert.IsInstanceOfType(result, typeof(ViewResult), "Index did not return a view result");
}
protected static void SetupController(Controller controller)
{
RequestContext requestContext = new RequestContext(new MockHttpContext(), new RouteData());
controller.Url = new UrlHelper(requestContext);
controller.ControllerContext = new ControllerContext
{
Controller = controller,
RequestContext = requestContext
};
}
Debugging through this unit test shows that at no point does the overridden MyBaseController.Initialize() get called at all. This causes issues where my CurrentUserId property is not being set in unit tests, but is being set on the live system.
What else do I have to do to trigger the Initialize() to be called?
When you make a request to a controller through a website the MVC framework picks up that request and runs it through a number of different steps. Somewhere in that pipeline of steps MVC knows that it must call the Initialize method so it finds the Initialize in your MyBaseController class and executes it. At this point all is well and everything works.
When you create a new instance of your HomeController in your test code you're simply creating a new instance of a class. You're not running that class through the MVC request pipeline and your test framework doesn’t know to execute the Initialize method so you get an error.
Personally I would look to test the Index action independently of the Initialize method. Your Index action above is empty so I can't see exactly what you're trying to do but if you are returning one view for logged in users and another for anonymous users I'd do something like this:
[TestMethod]
public void Index_Shows_When_Not_Logged_In(){
HomeController controller = new HomeController(_unitOfWork);
controller.CurrentUserId=0;
controller.Index();
//Check your view rendered in here
}
[TestMethod]
public void Some_Other_View_Shows_When_Logged_In(){
HomeController controller = new HomeController(_unitOfWork);
controller.CurrentUserId=12; //Could be any value
controller.Index();
//Check your view rendered in here
}
There are some pretty nifty test helpers in the MVC contrib project (http://mvccontrib.codeplex.com/) which allow you to do things like:
controller.Index().AssertViewRendered();
The constructor does not call Initialize. IIRC What calls initialize is the Execute/ExecuteCore method.
Here is the code from the MVC source:
protected virtual void Execute(RequestContext requestContext) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null) {
throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
}
VerifyExecuteCalledOnce();
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope()) {
ExecuteCore();
}
}
This is basically called from the MvcHandler in the BeginProcessRequest method.
Update:
RequestContext does not exist in a unit test, because you're not going through ASP.NET. If I remember correctly you'd need to mock it.
Another problem in using Initialize in a test is the membership provider, which I haven't used myself, but I would guess that AccountMembershipService would fail you in a test? It seems to me that this would create fragile tests. It would probably also slow you down if it has to contact a server, and might fail you on a CI server.
IMO,from a basic look around the Init method, it shouldn't be there. The easiest solution,without breaking anything, that comes to mind is using dependency injection to inject the CurrentUserId in the Controller ctor.
In the case where you "run the actual site" I suspect it calls the default constructor of your HomeController which in turn will call the base controller's correpsonding ctor.
Try modifying your custom ctor of public HomeController(IUnitOfWork unitOfWork) to
public HomeController(IUnitOfWork unitOfWork):base()
This will make sure to call the base controller's default ctor whenever it is called.

Categories

Resources