I have an owin culture middle ware running very nice.
It just changes the culture according to the url. This works in 4.5.* perfectly. Now when the runtiome is changed to 4.6.1, the culture isn't preserved anymore and as a result it just doesn't work.
I can reproduce it in a very simple solution which only has this middleware simulating the culture change
public class CultureMiddleware : OwinMiddleware
{
public CultureMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
var culture = new CultureInfo("es-ES");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
await Next.Invoke(context);
}
}
I attach the middleware to the pipeline it gets execute but when I'm calling an action the controller doesn't have the culture (like it had in .net 4.5.1)
I already posted here but the support is really slow. One Answer every two weeks and then it seems like they haven't tried what they write :-(
https://connect.microsoft.com/VisualStudio/feedback/details/2455357
I got a response from microsoft which works for me.
you can try set the following element in your web.config file. This
element has to be child to the <appSettings> element.
<add key="appContext.SetSwitch:Switch.System.Globalization.NoAsyncCurrentCulture" value="true" />
I also tried to fix it with OwinMiddleware but failed.
My solution was to create an ActionFilterAttribute and register this at startup:
public partial class Startup : UmbracoDefaultOwinStartup
{
public override void Configuration(IAppBuilder app)
{
GlobalFilters.Filters.Add(new CultureCookieFilter());
base.Configuration(app);
}
}
public class CultureCookieFilter : ActionFilterAttribute
{
private const string CULTURE_KEY = "X-Culture";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.Cookies[CULTURE_KEY] != null)
{
var langCookie = filterContext.HttpContext.Request.Cookies[CULTURE_KEY];
if (langCookie != null)
{
var lang = langCookie.Value;
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(lang);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
}
}
base.OnActionExecuting(filterContext);
}
}
Related
I'm trying to pass a URL for a background image to my _Layout.cshtml,
public HomeController()
{
this.ViewData["BackgroundImage"] = "1920w/Stipula_fountain_pen.jpg";
}
and
<body style="background-image: url(#(string.Format("assets/images/{0}", ViewData["BackgroundImage"])))">
...
</body>
but ViewData is always empty inside _Layout.cshtml. Is that working as intended? I'd rather not go down the BaseViewModel/BaseController route as that feels like overkill.
EDIT: It seems as if ViewData set in the constructor isn't actually used, because once an action is executing the collection is empty. If I set ViewData inside the action then that data is passed on to _Layout.cshtml - feels like a bug to me.
You can use an action filter to set ViewData for all controller actions:
public class SetBackgroundUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var result = filterContext.Result as ViewResult;
if (result != null)
{
result.ViewData["BackgroundImage"] = "1920w/Stipula_fountain_pen.jpg";
}
}
}
[SetBackgroundUrl]
public HomeController()
{
}
Or just override OnActionExecuted method of the controller:
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
var result = context.Result as ViewResult;
if (result != null)
{
result.ViewData["BackgroundImage"] = "1920w/Stipula_fountain_pen.jpg";
}
}
Expanding on adem caglin's answer I went with this filter attribute, which can take an arbitrary URL:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple = false)]
public class SetBackgroundUrlAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (!string.IsNullOrWhiteSpace(this.Url))
{
var result = filterContext.Result as ViewResult;
if (result != null)
result.ViewData["BackgroundImage"] = this.Url;
}
}
public string Url { get; set; }
}
and is used like so:
[SetBackgroundUrl(Url = "1920w/Stipula_fountain_pen.jpg")]
public class HomeController : Controller
{
...
}
I've got this code.
public ActionResult Index()
{
ReceiptModel model = new ReceiptModel();
try
{
model = new ReceiptModel(context);
}
catch (BussinessException bex)
{
ModelState.AddModelError("Index", bex.MessageToDisplay);
return View("Index");
}
return View(model);
}
BussinesException ir returned from database and then displayed for user. I have to put on every controller method try-catch statement, which is a bit tedious. Is there any easier way how to handle these exceptions?
P.S. All other exceptions are handled with HandleExceptionAttribute
UPDATE:
I used Floradu88 approach. So Now i have something like this.
public sealed class HandleBussinessExceptionAttribute : HandleErrorAttribute, IExceptionFilter
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.Controller.TempData["UnhandledException"] = filterContext.Exception;
filterContext.ExceptionHandled = true;
((Controller)filterContext.Controller).ModelState.AddModelError(
((BussinessException)filterContext.Exception).Code.ToString(),
((BussinessException)filterContext.Exception).MessageToDisplay
);
filterContext.Result = new ViewResult
{
ViewName = this.View,
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData,
};
}
}
and on Controller action i put
[HandleBussinessExceptionAttribute(Order = 2, ExceptionType = typeof(BussinessException), View = "Login")]
i also tried in exception handler:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(filterContext.RouteData));
and then handle error in action with ModelState.IsValid but values to action comes null. So for now i use first approach. When i have a bit more time i'll try to fix second approach.
Please read the documentation on this part:
http://msdn.microsoft.com/en-us/library/gg416513%28v=vs.98%29.aspx
http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/understanding-action-filters-cs
Too much content to be posted here:
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
}
}
}
And your controller:
public class ProductsController : ApiController
{
[NotImplExceptionFilter]
public Contact GetContact(int id)
{
throw new NotImplementedException("This method is not implemented");
}
}
According to this post, Create a custom binder and put the model in TempData:
public class AppBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
controllerContext.Controller.TempData["model"] = bindingContext.Model;
}
}
Register it in global.asax:
ModelBinders.Binders.DefaultBinder = new AppBinder();
Create a BaseController and override the OnException:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
this.ModelState.AddModelError(string.Empty, filterContext.Exception.Message);
filterContext.Result = new ViewResult
{
ViewName = filterContext.RouteData.Values["action"].ToString(),
TempData = filterContext.Controller.TempData,
ViewData = filterContext.Controller.ViewData
};
base.OnException(filterContext);
}
All Actions int The Controllers which inherited from this base controller will show their unhandled exceptions their own view in validation summery (remember to have
#Html.ValidationSummary(true)
in page). it works for me, hope works for you too!
I have set up a global filter for all my controller actions in which I open and close NHibernate sessions. 95% of these action need some database access, but 5% don't. Is there any easy way to disable this global filter for those 5%. I could go the other way round and decorate only the actions that need the database, but that would be far more work.
You could write a marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute
{
}
and then in your global action filter test for the presence of this marker on the action:
public class MyGlobalActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.ActionDescriptor.GetCustomAttributes(typeof(SkipMyGlobalActionFilterAttribute), false).Any())
{
return;
}
// here do whatever you were intending to do
}
}
and then if you want to exclude some action from the global filter simply decorate it with the marker attribute:
[SkipMyGlobalActionFilter]
public ActionResult Index()
{
return View();
}
Though, the accepted answer by Darin Dimitrov is fine and working well but, for me, the simplest and most efficient answer found here.
You just need to add a boolean property to your attribute and check against it, just before your logic begins:
public class DataAccessAttribute: ActionFilterAttribute
{
public bool Disable { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Disable) return;
// Your original logic for your 95% actions goes here.
}
}
Then at your 5% actions just use it like this:
[DataAccessAttribute(Disable=true)]
public ActionResult Index()
{
return View();
}
In AspNetCore, the accepted answer by #darin-dimitrov can be adapted to work as follows:
First, implement IFilterMetadata on the marker attribute:
public class SkipMyGlobalActionFilterAttribute : Attribute, IFilterMetadata
{
}
Then search the Filters property for this attribute on the ActionExecutingContext:
public class MyGlobalActionFilter : IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.Filters.OfType<SkipMyGlobalActionFilterAttribute>().Any())
{
return;
}
// etc
}
}
At least nowadays, this is quite easy: to exclude all action filters from an action, just add the OverrideActionFiltersAttribute.
There are similar attributes for other filters: OverrideAuthenticationAttribute, OverrideAuthorizationAttribute and OverrideExceptionAttribute.
See also https://www.strathweb.com/2013/06/overriding-filters-in-asp-net-web-api-vnext/
Create a custom Filter Provider. Write a class which will implement IFilterProvider. This IFilterProvider interface has a method GetFilters which returns Filters which needs to be executed.
public class MyFilterProvider : IFilterProvider
{
private readonly List<Func<ControllerContext, object>> filterconditions = new List<Func<ControllerContext, object>>();
public void Add(Func<ControllerContext, object> mycondition)
{
filterconditions.Add(mycondition);
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return from filtercondition in filterconditions
select filtercondition(controllerContext) into ctrlContext
where ctrlContext!= null
select new Filter(ctrlContext, FilterScope.Global);
}
}
=============================================================================
In Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
MyFilterProvider provider = new MyFilterProvider();
provider.Add(d => d.RouteData.Values["action"].ToString() != "SkipFilterAction1 " ? new NHibernateActionFilter() : null);
FilterProviders.Providers.Add(provider);
}
protected void Application_Start()
{
RegisterGlobalFilters(GlobalFilters.Filters);
}
Well, I think I got it working for ASP.NET Core.
Here's the code:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Prepare the audit
_parameters = context.ActionArguments;
await next();
if (IsExcluded(context))
{
return;
}
var routeData = context.RouteData;
var controllerName = (string)routeData.Values["controller"];
var actionName = (string)routeData.Values["action"];
// Log action data
var auditEntry = new AuditEntry
{
ActionName = actionName,
EntityType = controllerName,
EntityID = GetEntityId(),
PerformedAt = DateTime.Now,
PersonID = context.HttpContext.Session.GetCurrentUser()?.PersonId.ToString()
};
_auditHandler.DbContext.Audits.Add(auditEntry);
await _auditHandler.DbContext.SaveChangesAsync();
}
private bool IsExcluded(ActionContext context)
{
var controllerActionDescriptor = (Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor;
return controllerActionDescriptor.ControllerTypeInfo.IsDefined(typeof(ExcludeFromAuditing), false) ||
controllerActionDescriptor.MethodInfo.IsDefined(typeof(ExcludeFromAuditing), false);
}
The relevant code is in the 'IsExcluded' method.
You can change your filter code like this:
public class NHibernateActionFilter : ActionFilterAttribute
{
public IEnumerable<string> ActionsToSkip { get; set; }
public NHibernateActionFilter(params string[] actionsToSkip)
{
ActionsToSkip = actionsToSkip;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (null != ActionsToSkip && ActionsToSkip.Any(a =>
String.Compare(a, filterContext.ActionDescriptor.ActionName, true) == 0))
{
return;
}
//here you code
}
}
And use it:
[NHibernateActionFilter(new[] { "SkipFilterAction1 ", "Action2"})]
I know we could simply use an app_offline.htm file to do this.
But I want to be able access the website if my IP is 1.2.3.4 (for example), so that I can do a final testing.
if( IpAddress != "1.2.3.4" )
{
return Redirect( offlinePageUrl );
}
How can we implement this in ASP.NET MVC 3?
You can use a catch-all route with a RouteConstraint with the IP check:
Make sure you put the offline route first.
routes.MapRoute("Offline", "{controller}/{action}/{id}",
new
{
action = "Offline",
controller = "Home",
id = UrlParameter.Optional
},
new { constraint = new OfflineRouteConstraint() });
and the constraint code:
public class OfflineRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return IpAddress != "1.2.3.4";
}
}
Per Max's suggestion here is an actual implementation.
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CheckForDownPage());
}
//the rest of your global asax
//....
}
public sealed class CheckForDownPage : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/Down.htm");
if (System.IO.File.Exists(path) && IpAddress != "1.2.3.4")
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("~/Down.htm");
return;
}
base.OnActionExecuting(filterContext);
}
}
You can define a global filter that stop all the requests if they don't come from your IP. you can enable the filter by configuration.
I got an infinite loop on colemn615's solution, so I added a check for the offline page.
Also, for later versions of ASP.NET this is split into a FilterConfig.cs file in the App_Start folder.
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CheckForDownPage());
}
public sealed class CheckForDownPage : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (HttpContext.Current.Request.RawUrl.Contains("Down.htm"))
{
return;
}
var path = System.Web.Hosting.HostingEnvironment.MapPath("~/Down.htm");
if (System.IO.File.Exists(path) && IpAddress != "1.2.3.4")
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("~/Down.htm");
return;
}
base.OnActionExecuting(filterContext);
}
}
I add an AppSetting in the Web.Config file:
<add key="MaintenanceMsg" value="We are currently performing some routine maintenance. We expect to be back up at around 01:00." />
I then update the global.asax file's Application_BeginRequest method to check if the appsetting has a value, if it does, I redirect everything to the maintenance page:
private void Application_BeginRequest(object sender, EventArgs e)
{
if(!string.IsNullOrWhiteSpace(ConfigurationManager.AppSettings["MaintenanceMsg"]) && !Request.Url.ToString().Contains("UndergoingMaintenance"))
Response.Redirect("~/Home/UndergoingMaintenance");
** Do whatever you do when you don't want to show your maintenance page here**
}
Finally, create your view and controller action.
If you use Azure you can simply add and delete the value for the AppSetting in the portal so you can suspend your site in a matter of minutes without a deployment.
I'm trying to build custom AuthorizeAttribute, so in my Core project (a class library) I have this code:
using System;
using System.Web;
using System.Web.Mvc;
using IVC.Core.Web;
using System.Linq;
namespace IVC.Core.Attributes
{
public class TimeShareAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if(!httpContext.Request.IsAuthenticated)
return false;
var rolesProvider = System.Web.Security.Roles.Providers["TimeShareRoleProvider"];
string[] roles = rolesProvider.GetRolesForUser(httpContext.User.Identity.Name);
if(roles.Contains(Website.Roles.RegisteredClient, StringComparer.OrdinalIgnoreCase))
{
return true;
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult("/TimeShare/Account/LogOn");
base.HandleUnauthorizedRequest(filterContext);
}
}
}
When I try to build the thing I get this error:
Error 2 'IVC.Core.Attributes.TimeShareAuthorizeAttribute.AuthorizeCore(System.Web.HttpContextBase)': no suitable method found to override ...
Am I missing something here? I've searched all over but every site I can find just tells me to do exactly what I did here. I'm using mvc2 btw.
Edited to add: if I move the class to the mvc project in the same solution there's no compiler error.
Yeah, I fumbled with that one for a while too and figured it out from the Object browser. It certainly is NOT clear from the MSDN docs unless you scroll all the way down to the user comments on the HttpContextBase class. And, of course, lots of examples on the web, but nobody ever shows the full class file! :)
Try adding a reference to System.Web.Abstractions to your project.
UPDATE: Just noticed from the MSDN that under v3.5, it is under System.Web.Abstractions, but under v4, it's under System.Web.
Here is AuthorizationAttribute.cs
/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This software is subject to the Microsoft Public License (Ms-PL).
* A copy of the license can be found in the license.htm file included
* in this distribution.
*
* You must not remove this notice, or any other, from this software.
*
* ***************************************************************************/
namespace System.Web.Mvc {
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Security.Principal;
using System.Web;
[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes",
Justification = "Unsealed so that subclassed types can set properties in the default constructor or override our behavior.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter {
private string _roles;
private string[] _rolesSplit = new string[0];
private string _users;
private string[] _usersSplit = new string[0];
public string Roles {
get {
return _roles ?? String.Empty;
}
set {
_roles = value;
_rolesSplit = SplitString(value);
}
}
public string Users {
get {
return _users ?? String.Empty;
}
set {
_users = value;
_usersSplit = SplitString(value);
}
}
// This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method.
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
return false;
}
return true;
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus) {
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
public virtual void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext)) {
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
}
// This method must be thread-safe since it is called by the caching module.
protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
bool isAuthorized = AuthorizeCore(httpContext);
return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
}
internal static string[] SplitString(string original) {
if (String.IsNullOrEmpty(original)) {
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
}
Also, since I've seen this happen before, be careful with letting Visual Studio auto add usings for you. If you're not paying attention you may end up adding System.Web.Http instead of System.Web.Mvc.
When I copied my old codes to my new project, the same problem happened.
It turned out that there are 2 AuthrizeAttributes. One is in System.Web.Mvc, and another in System.Web.Http.
The Mvc one has the AuthrizeCore, while the Http one has not.
You might need to add reference to System.Web.Mvc to access the right one.
I had this error too. It turned out that Visual Studio had added to my ActionFilter by default:
using System.Web.Http;
Even when I added System.Web.Mvc I still got the error. Commenting out System.Web.Http and just using System.Web.Mvc seems to solve the problem for me.
Hoping this might help someone!
Use System.Web.Mvc namespace.
Sample code:
using System.Web.Mvc;
public class CustomAuthorize : AuthorizeAttribute
{
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
var service = new AuthService();
return service.GetCurrentUser() != null;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
}
}
Under fw v4.5 it now under System.Web.Mvc apparently.