Allowing anonymous access to MVC4 actions - asp.net

I am trying to allow anonymous access to the root of my site. If I make a request to site.com/home it allows anonymous access. However, if I request site.com/ I am presented with a login page. So far I have done the following:
In the web.config I authorized "Home" for all users:
<location path="Home">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
In FilterConfig.cs I added the following AuthorizeAttribute:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}
My Home Index controller action looks like this:
[AllowAnonymous]
public ActionResult Index()
{
return View();
}
My routes looks like this:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Zoom",
url: "zoom/{id}",
defaults: new { controller = "Zoom", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Is this accomplished with a route? Am I completely missing something?

You have to implement the logic in the Attribute code to filter it. In other words, you have to check and see if the method/class is annotated with the attribute and then skip authorization if it is (or handle accordingly for your scenario).
Here's an example:
/// <summary>
/// This class is used to ensure that a user has been authenticated before allowing a given method
/// to be called.
/// </summary>
/// <remarks>
/// This class extends the <see cref="AuthorizeAttribute"/> class.
/// </remarks>
public sealed class LoginAuthorize : AuthorizeAttribute
{
/// <summary>
/// The logger used for logging.
/// </summary>
private static readonly ILog Logger = LogManager.GetLogger(typeof(LoginAuthorize));
/// <summary>
/// Handles the authentication check to ensure user has been authenticated before allowing a method
/// to be called.
/// </summary>
/// <param name="filterContext">The authorization context object.</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
DateTime methodEntryTime = DateTime.Now;
Helper.LogMethodEntry(Logger, MethodBase.GetCurrentMethod(), filterContext);
try
{
// determine if the called method has the AllowAnonymousAttribute, which means we can skip
// authorization
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (!skipAuthorization)
{
base.OnAuthorization(filterContext);
// make sure required session data is still present
if (string.IsNullOrWhiteSpace(filterContext.HttpContext.Session[Helper.ROLE_NAME] as string))
{
HandleUnauthorizedRequest(filterContext);
}
}
Helper.LogMethodExit(Logger, MethodBase.GetCurrentMethod(), methodEntryTime);
}
catch (Exception e)
{
Helper.LogException(Logger, MethodBase.GetCurrentMethod(), e);
throw;
}
}
/// <summary>
/// Handles unauthorized requests. Redirects user to login page.
/// </summary>
/// <param name="filterContext">The authorization context object.</param>
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
DateTime methodEntryTime = DateTime.Now;
Helper.LogMethodEntry(Logger, MethodBase.GetCurrentMethod(), filterContext);
try
{
base.HandleUnauthorizedRequest(filterContext);
// redirect user to login page
filterContext.Result = new RedirectResult("~/Login");
Helper.LogMethodExit(Logger, MethodBase.GetCurrentMethod(), methodEntryTime);
}
catch (Exception e)
{
Helper.LogException(Logger, MethodBase.GetCurrentMethod(), e);
throw;
}
}
}
}
Then, in Global.asax you would add this LoginAuthorize class, like this:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LoginAuthorize());
filters.Add(new HandleErrorAttribute());
}

First of all, you should NOT use the Webforms way of authorization with the web.config. Get rid of that first.
Second, by adding the Authorize attribute as global filter, you're basically applying the Authorize attribute to all controllers and actions, is that really what you want?
It is more common to decorate actions methods, or the complete controller. If the controller has the authorize attribute, you can still allow anonymous access for action methods by adding the AllowAnonymous attribute, just like you already did.
Using this approach should just work fine, the routes look good.

Related

ASP.NET MVC Custom controller not working

I have a Project called Project.Web which contains a folder Mvc which includes a base controller called BaseController.cs.
namespace ProsecVHIPHelper.Web.Mvc
{
/// <summary>
/// A base controller for all controllers
/// </summary>
public abstract class BaseController : Controller
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="applicationContext"></param>
protected BaseController(ApplicationContext applicationContext)
{
if (applicationContext == null) throw new ArgumentNullException("applicationContext");
ApplicationContext = applicationContext;
}
/// <summary>
/// Default constructor
/// </summary>
protected BaseController() : this(ApplicationContext.Current) { }
/// <summary>
/// The application context
/// </summary>
public ApplicationContext ApplicationContext
{
get;
private set;
}
/// <summary>
/// Get the services singleton
/// </summary>
public ServiceContext Services
{
get { return ApplicationContext.Services; }
}
}
}
This base controller will be used by all of my other controllers.
In my second project called Project.Web.UI I have a route config like:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Ftp", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "Project.Web.Mvc" }
);
}
}
Where I include the base controller namespace.
But when I create new controllers using my base controller I always get a 404 not found exception.
When I use the default Controller class then it works.
First my controllers where located in the Project.Web project in a folder called Controllers. When I add this namespace also in the route config it doesn't work.
Then I moved these controllers ( not the base controller ) to my other project Project.Web.UI, but still no luck.
The Project.Web project is a class library and the Project.Web.UI is a web project.
Any ideas?
Ok I have found the solution. The problem was that I have added the System.Web.Mvc using the add reference. But this had a different version from the System.Web.Mvc in my web project.
After removing this reference and added a reference to the dll in the web project, everything worked like a charm!

How to enable CORS for asp.net mvc web site?

I want to enable CORS for an action in my asp.net mvc 4 web site.
This is possible for web api
http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api
Is there a similar solution for mvc web site?
I also want to be able to restrict this for origin web sites I want.
Thanks
i think you need to create your Custom authorization Like this:
/// <summary>
/// Referrer Url Custom Authorize
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ReferrerUrlAuthorize : AuthorizeAttribute
{
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
IList<string> webSitesAllowed = new List<string>() { "http://mywebsite.com", "http://customersite.com" };
string urlReferrer = System.Web.HttpContext.Current.Request.UrlReferrer.AbsoluteUri;
//checking if the referrer url exists in your list of sites allowed
if (!webSitesAllowed.Contains(urlReferrer))
return false; //access denied
return true;
}
}
and in your controller class must be set the ReferrerUrlAuthorize
[ReferrerUrlAuthorize]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
}

Testing ASP.NET Web API POST and Parameterless GET Routes with WebApiContrib.Testing

I am trying to set up some route tests using the WebApiContrib.Testing library. My get tests (like this) work fine...
[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
{
"~/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>(c => c.Get("sjkfhiuehfkshjksdfh"));
}
I am rather lost on the post test - I currently have the following which fails with a NotImplementedException...
[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsAPost()
{
"~/auth".ShouldMapTo<AuthController>(c => c.Post(new AuthenticationCredentialsModel()), "POST");
}
Here's the setup and teardown for completeness...
[SetUp]
public void SetUpTest()
{
RouteConfig.RegisterRoutes(RouteTable.Routes);
WebApiConfig.Register(GlobalConfiguration.Configuration);
}
[TearDown]
public void TearDownTest()
{
RouteTable.Routes.Clear();
GlobalConfiguration.Configuration.Routes.Clear();
}
The route I am trying to test is the default POST route, which maps to this method call...
[AllowAnonymous]
public HttpResponseMessage Post([FromBody] AuthenticationCredentialsModel model)
{ *** Some code here that doesn't really matter *** }
I am also getting a failure on this test that tests the standard GET route without parameters returns all of the items...
[Test]
[Category("VersionInfo Api Tests")]
public void TheVersionInfoControllerAcceptsAMultipleItemGetRouteForAllItems()
{
"~/versioninfo".ShouldMapTo<VersionInfoController>(c => c.Get());
}
Which is testing this method...
public HttpResponseMessage Get()
{ *** Some code here that doesn't really matter *** }
This library was recommended by several articles I read, but I'm not sure now if I'm doing something wrong or if it's just quite limited and I'm better off rolling my own.
There seems to be some nasty issues here - after debugging through the WebApiContrib.Testing library, I've found the following...
In the route mapping code used for the libraries own tests, the route mappings look like this...
// GET /api/{resource}
routes.MapHttpRoute(
name: "Web API Get All",
routeTemplate: "api/{controller}",
defaults: new {action = "Get"},
constraints: new {httpMethod = new HttpMethodConstraint("GET")}
);
My route mapping looks like this for essentially the same route...
config.Routes.MapHttpRoute("DefaultApiGet", "{controller}",
new { action = "Get" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
Notice the different syntax for constraint building. My route mapping code won't allow strings. Maybe this is something that's changed in the WebAPI version since the library was written, I'm not sure, but it seems that any route that I'm trying to test with an HttpMethodConstraint will fail.
I'm continuing to investigate this one; I now have a 'why', but not a 'how to fix' yet.
UPDATE: The WebAPI routing takes two forms of constraint, one of which is just a string (more details here - http://forums.asp.net/p/1777747/4904556.aspx/1?Re+System+Web+Http+Routing+IHttpRouteConstraint+vs+System+Web+Routing+IRouteConstraint). I tried changing the routing table to see if that would make any difference, but it didn't - just to let you know!
I fixed this in the end by writing my own, after reading a post by whyleee on another question here - Testing route configuration in ASP.NET WebApi (WebApiContrib.Testing didn't seem to work for me)
I merged his post with some of the elements I liked syntactically from the WebApiContrib.Testing library to generate the following helper class.
This allows me to write really lightweight tests like this...
[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
{
"http://api.siansplan.com/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>("Get", "hash");
}
[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsAPost()
{
"http://api.siansplan.com/auth".ShouldMapTo<AuthController>("Post", HttpMethod.Post);
}
The helper class looks like this...
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Hosting;
using System.Web.Http.Routing;
namespace SiansPlan.Api.Tests.Helpers
{
public static class RoutingTestHelper
{
/// <summary>
/// Routes the request.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="request">The request.</param>
/// <returns>Inbformation about the route.</returns>
public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)
{
// create context
var controllerContext = new HttpControllerContext(config, new Mock<IHttpRouteData>().Object, request);
// get route data
var routeData = config.Routes.GetRouteData(request);
RemoveOptionalRoutingParameters(routeData.Values);
request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
controllerContext.RouteData = routeData;
// get controller type
var controllerDescriptor = new DefaultHttpControllerSelector(config).SelectController(request);
controllerContext.ControllerDescriptor = controllerDescriptor;
// get action name
var actionMapping = new ApiControllerActionSelector().SelectAction(controllerContext);
var info = new RouteInfo(controllerDescriptor.ControllerType, actionMapping.ActionName);
foreach (var param in actionMapping.GetParameters())
{
info.Parameters.Add(param.ParameterName);
}
return info;
}
#region | Extensions |
/// <summary>
/// Determines that a URL maps to a specified controller.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
/// <param name="fullDummyUrl">The full dummy URL.</param>
/// <param name="action">The action.</param>
/// <param name="parameterNames">The parameter names.</param>
/// <returns></returns>
public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, params string[] parameterNames)
{
return ShouldMapTo<TController>(fullDummyUrl, action, HttpMethod.Get, parameterNames);
}
/// <summary>
/// Determines that a URL maps to a specified controller.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
/// <param name="fullDummyUrl">The full dummy URL.</param>
/// <param name="action">The action.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <param name="parameterNames">The parameter names.</param>
/// <returns></returns>
/// <exception cref="System.Exception"></exception>
public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, HttpMethod httpMethod, params string[] parameterNames)
{
var request = new HttpRequestMessage(httpMethod, fullDummyUrl);
var config = new HttpConfiguration();
WebApiConfig.Register(config);
var route = RouteRequest(config, request);
var controllerName = typeof(TController).Name;
if (route.Controller.Name != controllerName)
throw new Exception(String.Format("The specified route '{0}' does not match the expected controller '{1}'", fullDummyUrl, controllerName));
if (route.Action.ToLowerInvariant() != action.ToLowerInvariant())
throw new Exception(String.Format("The specified route '{0}' does not match the expected action '{1}'", fullDummyUrl, action));
if (parameterNames.Any())
{
if (route.Parameters.Count != parameterNames.Count())
throw new Exception(
String.Format(
"The specified route '{0}' does not have the expected number of parameters - expected '{1}' but was '{2}'",
fullDummyUrl, parameterNames.Count(), route.Parameters.Count));
foreach (var param in parameterNames)
{
if (!route.Parameters.Contains(param))
throw new Exception(
String.Format("The specified route '{0}' does not contain the expected parameter '{1}'",
fullDummyUrl, param));
}
}
return true;
}
#endregion
#region | Private Methods |
/// <summary>
/// Removes the optional routing parameters.
/// </summary>
/// <param name="routeValues">The route values.</param>
private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValues)
{
var optionalParams = routeValues
.Where(x => x.Value == RouteParameter.Optional)
.Select(x => x.Key)
.ToList();
foreach (var key in optionalParams)
{
routeValues.Remove(key);
}
}
#endregion
}
/// <summary>
/// Route information
/// </summary>
public class RouteInfo
{
#region | Construction |
/// <summary>
/// Initializes a new instance of the <see cref="RouteInfo"/> class.
/// </summary>
/// <param name="controller">The controller.</param>
/// <param name="action">The action.</param>
public RouteInfo(Type controller, string action)
{
Controller = controller;
Action = action;
Parameters = new List<string>();
}
#endregion
public Type Controller { get; private set; }
public string Action { get; private set; }
public List<string> Parameters { get; private set; }
}
}

Hosting site on http and https

I want to host my ASP.NET MVC website with both http and https bindings.
But only few paths should be available via http, where as all paths should be available via https.
e.g.
My application exposes following urls:
https://server/v1/setup
https://server/v1/exchange
https://server/v1/time
I want time url to be available via http as well
http://server/v1/time
I do not want to set any rules in IIS. Is there any way I can control urls available via http in code?
I also had loook at RequiresHttps attribute, but there is some redirection issue with it.
If http request is made for not allowed paths, response should be 404 (not found).
You could make an an actionfilter to check for https.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class HttpsOnlyAttribute : ActionFilterAttribute
{
/// <summary>
/// Called by the MVC framework before the action method executes.
/// </summary>
/// <param name="filterContext">The filter context.</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsSecureConnection)
{
throw new HttpException(404, "HTTP/1.1 404 Not Found");
}
}
}
Just place the attribute on top of the controllers you want to be https only
[HttpsOnly]
public class SecureController : Controller
{
// your actions here
}
You can even target just actions
public class SampleController : Controller
{
[HttpsOnly]
public ActionResult SecureAction()
{
return View();
}
}
The RequireHttpsAttribute can still be used in this case.
Decorating your Controllers of Actions with this will Redirect GET requests to the Secure version, and throw errors for all other methods.
If you extend from this method, you can override the handling to either always return a 404, or to use the default handling.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsExtendedAttribute : RequireHttpsAttribute
{
public RequireHttpsExtendedAttribute(bool throwNotFound = false)
{
ThrowNotFound = throwNotFound;
}
private bool ThrowNotFound { get; set; }
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
if (ThrowNotFound)
throw new HttpException(404, "HTTP/1.1 404 Not Found");
base.HandleNonHttpsRequest(filterContext);
}
}
Thanks for the responses!! I came up with solution of using custom route constraints.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Test1_default",
"Test1/CurrentTime",
new { action = "CurrentTime", controller = "Default1" },
new { https = new HttpsConstraint() });
}
}
public class HttpsConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.IsSecureConnection;
}
}

What is the proper way to send an HTTP 404 response from an ASP.NET MVC action?

If given the route:
{FeedName}/{ItemPermalink}
ex: /Blog/Hello-World
If the item doesn't exist, I want to return a 404. What is the right way to do this in ASP.NET MVC?
Shooting from the hip (cowboy coding ;-)), I'd suggest something like this:
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return new HttpNotFoundResult("This doesn't exist");
}
}
HttpNotFoundResult:
using System;
using System.Net;
using System.Web;
using System.Web.Mvc;
namespace YourNamespaceHere
{
/// <summary>An implementation of <see cref="ActionResult" /> that throws an <see cref="HttpException" />.</summary>
public class HttpNotFoundResult : ActionResult
{
/// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with the specified <paramref name="message"/>.</summary>
/// <param name="message"></param>
public HttpNotFoundResult(String message)
{
this.Message = message;
}
/// <summary>Initializes a new instance of <see cref="HttpNotFoundResult" /> with an empty message.</summary>
public HttpNotFoundResult()
: this(String.Empty) { }
/// <summary>Gets or sets the message that will be passed to the thrown <see cref="HttpException" />.</summary>
public String Message { get; set; }
/// <summary>Overrides the base <see cref="ActionResult.ExecuteResult" /> functionality to throw an <see cref="HttpException" />.</summary>
public override void ExecuteResult(ControllerContext context)
{
throw new HttpException((Int32)HttpStatusCode.NotFound, this.Message);
}
}
}
// By Erik van Brakel, with edits from Daniel Schaffer :)
Using this approach you comply to the framework standards. There already is a HttpUnauthorizedResult in there, so this would simply extend the framework in the eyes of another developer maintaining your code later on (you know, the psycho who knows where you live).
You could use reflector to take a look into the assembly to see how the HttpUnauthorizedResult is achieved, because I don't know if this approach misses anything (it seems too simple almost).
I did use reflector to take a look at the HttpUnauthorizedResult just now. Seems they're setting the StatusCode on the response to 0x191 (401). Although this works for 401, using 404 as the new value I seem to be getting just a blank page in Firefox. Internet Explorer shows a default 404 though (not the ASP.NET version). Using the webdeveloper toolbar I inspected the headers in FF, which DO show a 404 Not Found response. Could be simply something I misconfigured in FF.
This being said, I think Jeff's approach is a fine example of KISS. If you don't really need the verbosity in this sample, his method works fine as well.
We do it like so; this code is found in BaseController
/// <summary>
/// returns our standard page not found view
/// </summary>
protected ViewResult PageNotFound()
{
Response.StatusCode = 404;
return View("PageNotFound");
}
called like so
public ActionResult ShowUserDetails(int? id)
{
// make sure we have a valid ID
if (!id.HasValue) return PageNotFound();
throw new HttpException(404, "Are you sure you're in the right place?");
The HttpNotFoundResult is a great first step to what I am using. Returning an HttpNotFoundResult is good. Then the question is, what's next?
I created an action filter called HandleNotFoundAttribute that then shows a 404 error page. Since it returns a view, you can create a special 404 view per controller, or let is use a default shared 404 view. This will even be called when a controller doesn't have the specified action present, because the framework throws an HttpException with a status code of 404.
public class HandleNotFoundAttribute : ActionFilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
var httpException = filterContext.Exception.GetBaseException() as HttpException;
if (httpException != null && httpException.GetHttpCode() == (int)HttpStatusCode.NotFound)
{
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; // Prevents IIS from intercepting the error and displaying its own content.
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.StatusCode = (int) HttpStatusCode.NotFound;
filterContext.Result = new ViewResult
{
ViewName = "404",
ViewData = filterContext.Controller.ViewData,
TempData = filterContext.Controller.TempData
};
}
}
}
Note that as of MVC3, you can just use HttpStatusCodeResult.
Using ActionFilter is hard to maintain because whenever we throw an error the filter need to be set in the attribute. What if we forget to set it? One way is deriving OnException on base controller. You need to define a BaseController derived from Controller and all your controllers must derive from BaseController. It is a best practise to have a base controller.
Note if using Exception the response status code is 500, so we need to change it to 404 for Not Found and 401 for Unauthorized. Just like I mention above, use OnException overrides on BaseController to avoid using filter attribute.
The new MVC 3 also make more troublesome by returning an empty view to browser. The best solution after some research is based on my answer here How to return a view for HttpNotFound() in ASP.Net MVC 3?
To make more convinience I paste it here:
After some study. The workaround for MVC 3 here is to derive all HttpNotFoundResult, HttpUnauthorizedResult, HttpStatusCodeResult classes and implement new (overriding it) HttpNotFound() method in BaseController.
It is best practise to use base Controller so you have 'control' over all derived Controllers.
I create new HttpStatusCodeResult class, not to derive from ActionResult but from ViewResult to render the view or any View you want by specifying the ViewName property. I follow the original HttpStatusCodeResult to set the HttpContext.Response.StatusCode and HttpContext.Response.StatusDescription but then base.ExecuteResult(context) will render the suitable view because again I derive from ViewResult. Simple enough is it? Hope this will be implemented in the MVC core.
See my BaseController bellow:
using System.Web;
using System.Web.Mvc;
namespace YourNamespace.Controllers
{
public class BaseController : Controller
{
public BaseController()
{
ViewBag.MetaDescription = Settings.metaDescription;
ViewBag.MetaKeywords = Settings.metaKeywords;
}
protected new HttpNotFoundResult HttpNotFound(string statusDescription = null)
{
return new HttpNotFoundResult(statusDescription);
}
protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null)
{
return new HttpUnauthorizedResult(statusDescription);
}
protected class HttpNotFoundResult : HttpStatusCodeResult
{
public HttpNotFoundResult() : this(null) { }
public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { }
}
protected class HttpUnauthorizedResult : HttpStatusCodeResult
{
public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { }
}
protected class HttpStatusCodeResult : ViewResult
{
public int StatusCode { get; private set; }
public string StatusDescription { get; private set; }
public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { }
public HttpStatusCodeResult(int statusCode, string statusDescription)
{
this.StatusCode = statusCode;
this.StatusDescription = statusDescription;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
context.HttpContext.Response.StatusCode = this.StatusCode;
if (this.StatusDescription != null)
{
context.HttpContext.Response.StatusDescription = this.StatusDescription;
}
// 1. Uncomment this to use the existing Error.ascx / Error.cshtml to view as an error or
// 2. Uncomment this and change to any custom view and set the name here or simply
// 3. (Recommended) Let it commented and the ViewName will be the current controller view action and on your view (or layout view even better) show the #ViewBag.Message to produce an inline message that tell the Not Found or Unauthorized
//this.ViewName = "Error";
this.ViewBag.Message = context.HttpContext.Response.StatusDescription;
base.ExecuteResult(context);
}
}
}
}
To use in your action like this:
public ActionResult Index()
{
// Some processing
if (...)
return HttpNotFound();
// Other processing
}
And in _Layout.cshtml (like master page)
<div class="content">
#if (ViewBag.Message != null)
{
<div class="inlineMsg"><p>#ViewBag.Message</p></div>
}
#RenderBody()
</div>
Additionally you can use a custom view like Error.shtml or create new NotFound.cshtml like I commented in the code and you may define a view model for the status description and other explanations.

Resources