MVC data annotation layer: where to put the set CurrentUICulture statement? - asp.net-mvc

I am getting crazy with the localization of an MVC application.
After a recent question of mine I have followed this approach:
The language is stored in Session["lang"]
Each controller inherits from my own BaseController, which overrides OnActionExecuting, and in this method reads the Session and sets CurrentCulture and CurrentUICulture
This works great, until the Data Annotation Layer comes in. It seems like it gets called BEFORE the action itself is executed, and therefore it always gets the error messages in the default language!
The fields declarations go like this:
[Required(ErrorMessageResourceName = "validazioneRichiesto", ErrorMessageResourceType = typeof(Resources.Resources))]
public string nome { get; set; }
So, is there any reasonable place where I can put the call?
I initialize the Data Annotation Model Binder in my Controller constructor.
public CardController() : base() {
ModelBinders.Binders.DefaultBinder =
new Microsoft.Web.Mvc.DataAnnotations.DataAnnotationsModelBinder();
}
So, since Session is always null in the controller's constructor, and the action override is called AFTER the data annotation has validated the fields, where can I possibly set the CurrentCulture and CurrentUICulture to get localized errors?
I tried putting the CurrentCulture and CurrentUiCulture in Application_* (e.g. Application_AcquireRequestState or Application_PreRequestHandlerExecute) seems to have no effect...

As the culture is a global user setting, I am using it in the global.asax.cs file in the Application_BeginRequest method.
It does the work for me (using cookies) but the Session is also available there.
EDIT:
/by Brock Allen:
http://www.velocityreviews.com/forums/t282576-aspnet-20-session-availability-in-globalasax.html/
Session is available in PreRequesthandlerExecute.
The problem is that your code is being executed for every request into the server, and some requests (like ones for WebResourxe.axd) don't utlilize
Session (because the handler doesn't implement IRequireSessionState). So change your code to only access Session if that request has access to it.
Change your code to do this:
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
SetCulture();
}
Anyway, not sure if it works with mvc

After reading your question more carefully, I think that your problem is more in the way the resources are compiled.
Check in the Resource.resx properties, find Build Action and set it to Embedded Resource
Also change Custom Tool to PublicResXFileCodeGenerator
alt text http://img208.imageshack.us/img208/2126/captuream.png
I have tested a mini solution and works perfectly.
If you have more problem, I can send the example to you.

Another approach you can use is to put the lang in the URL, with this benefits:
The site is spidered by search engines in different languages
The user can send a URL to a friend in the selected language
To do this, use the Application_BeginRequest method in Global.asax
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
Dim lang As String
If HttpContext.Current.Request.Path.Contains("/en/") Then
lang = "en"
Else
lang = "es"
End If
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(lang)
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang)
End Sub
See this question for more info on how to implement it

The OP posted the final solution as the following, thanks to the accepted answer by twk:
void Application_PreRequestHandlerExecute(object sender, EventArgs e) {
if (Context.Handler is IRequiresSessionState ||
Context.Handler is IReadOnlySessionState) {
if (Session["lang"] == null) {
Session["lang"] = "it";
}
if (Request.QueryString["lang"] == "it" || Request.QueryString["lang"] == "en") {
Session["lang"] = Request.QueryString["lang"];
}
string lang1 = Session["lang"].ToString();
string lang2 = Session["lang"].ToString().ToUpper();
if (lang2 == "EN")
lang2 = "GB";
System.Threading.Thread.CurrentThread.CurrentCulture =
System.Globalization.CultureInfo.CreateSpecificCulture(lang1 + "-" + lang2);
System.Threading.Thread.CurrentThread.CurrentUICulture =
System.Globalization.CultureInfo.CreateSpecificCulture(lang1 + "-" + lang2);
}
}

Related

Dynamically extract information from FluentSecurity configuration

I'm working on an ASP.NET MVC Website that uses FluentSecurity to configure authorizations. Now we need a custom ActionLink Helper which will be displayed only if the current user has access to the targeted action.
I want to know if there is a way to know dynamically, from FluentSecurity Configuration (using SecurityConfiguration class for example), if the current logged user has access to an action given her name (string) and her controller name (string). I spend a lot of time looking in the source code of FluentSecurity https://github.com/kristofferahl/FluentSecurity but with no success.
For example:
public bool HasAccess(string controllerName, string actionName) {
//code I'm looking for goes here
}
Finally, i will answer myself may this will help another one.
I just emulate the OnAuthorization method of the HandleSecurityAttribute class. this code works well:
public static bool HasAccess(string fullControllerName, string actionName)
{
ISecurityContext contx = SecurityContext.Current;
contx.Data.RouteValues = new RouteValueDictionary();
var handler = new SecurityHandler();
try
{
var result = handler.HandleSecurityFor(fullControllerName, actionName, contx);
return (result == null);
} catch (PolicyViolationException)
{
return false;
}
}

ASP.net MVC Logging Page Views

Is there an easy way to log every page hit by any user.
I am thinking this will sit within the global.asax.cs file, so that I can write to a db table the URL of the page being hit.
I've found a way to complete this problem which seems to fit my purpose.
I use the PostAuthenticateRequestHandler method, as it is called for every page hit.
I ignore any empty path and just "/" as these are not actual pages hit.
//in global.asax.cs file
private void PostAuthenticateRequestHandler(object sender, EventArgs e)
{
///...///
string extension = this.Context.Request.CurrentExecutionFilePathExtension;
string path = this.Context.Request.CurrentExecutionFilePath;
if (extension == string.Empty && path != "/")
{
PageVisitedLogModel pageVisitedLogModel = new PageVisitedLogModel
{
DateVisited = DateTime.Now,
IPAddress = this.Context.Request.UserHostAddress,
PageURL = this.Context.Request.RawUrl,
Username = this.Context.User.Identity.Name
};
//then writes to log
DataHelper.UpdatePageVisitedLog(pageVisitedLogModel);
}
}
One way to do this would be to use a global action filter, as in this example. This allows you to run some code for any action in your solution.

MVC3 using routes or using controller logic?

I'm relatively new with MVC3, but I'm using it, C# and EF4 to create an application website. The routing that I'm using is the same as in the default Microsoft project created when I selected MVC3 pattern, nothing special:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new[] { "MySite.Controllers" }
);
}
And everything is working fine there. We're using the default Membership Provider, and users also get an INT value that identifies their account. This lets them see their profile pretty easily with a simple routing like:
www.mysite.com/profile/4
...for example. However, the client has asked that a lot of accounts be pre-generated and distributed to selected users. I've worked up a way to run that through SQL Server and it works fine, all the accounts got created (about a thousand). Additionally, I've add a bit field ('Claimed') that can help identify whether one of these pre-generated accounts has been 'activated' by these users.
My question is, when a user is given a link to come visit their (un-activated) account, should I use a test when doing the initial routing on that page to identify their account as un-claimed and send them somewhere else to finish entering details into their account? Or should I let them go to the same page as everyone else, and have something in the controller logic that identifies this record as un-claimed, and then send them to another page to finish entering details etc.? Is there a good reason for doing one over the other?
And what about people who make up (or have a typographical error) in their Id value, like:
www.mysite.com/profile/40000000000
(and the site only has a thousand users so far), should that be handled similarly, or through different means entirely? (I.e., in one scenario we're identifying an existing account that is not yet claimed, and in another scenario we're having to figure out that the account doesn't even exist.)
Any help would be greatly appreciated.
EDIT:
I'm trying to implement Soliah's suggested solution, and got stuck a bit on the fact that the if (id != 0) didn't like that the id might not be in an INT. I'm past that now, and attempting to figure out a way to do the check if valid portion, but possibly I have not solved the problem with the id not being treated as an INT? Something is definitely not right, even though I'm trying to convert it again during my database test for validity. Any ideas on why I'm getting the error below? What am I missing?
public class ValidProfileIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var id = (Convert.ToInt32(filterContext.ActionParameters["Id"]));
if (id != 0)
{
// Check if valid and behave accordingly here.
Profile profile = db.Profiles.Where(q => q.ProfileId == (Convert.ToInt32(id))).FirstOrDefault();
}
base.OnActionExecuting(filterContext);
}
}
Cannot implicitly convert type 'System.Linq.IQueryable<Mysite.Models.Profile>' to 'Mysite.Models.Profile'. An explicit conversion exists (are you missing a cast?)
EDIT #2:
I'm working on Robert's suggestion, and have made partial progress. My code currently looks like this:
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
bool isActivated = // some code to get this state
return isActivated;
}
}
which I got to after reading the blog entry, and (believe it or not) this posting: http://pastebin.com/Ea09Gf4B
I needed to change ActionSelectorAttribute to ActionMethodSelectorAttribute in order to get things moving again.
However, what I don't see how to do is to get the Id value into the bool isActivated test. My database has a view ('Claimed') which can give back a true/false value, depending on the user's profile Id that it is handed, but I don't see where to add the Id. Would something like what Soliah edited work?
if (int.TryParse(filterContext.ActionParameters["Id"], id) && id != 0) {
bool isActivated = db.Claimed.Where(c => c.ProfileId == id).FirstOrDefault();
EDIT #3:
Here is my current state of the code:
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
// get profile id first
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
bool isActivated = profile;// some code to get this state
return isActivated;
}
}
For me, I had to change things to int.Parse((string)controllerContext.RouteData.Values to get them to work, which they seem to do (to that point.) I discovered that formatting here: Bind a routevalue to a property of an object that is part of viewmodel
The line
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
errors on the db. section, with error message as follows:
Cannot access a non-static member of outer type 'MySite.Controllers.HomeController' via nested type 'MySite.Controllers.HomeController.UserAccountActivatedAttribute'
...which is something that I have diligently tried to figure out with MSDN and Stack, only to come up empty. Does this ring any bells?
Others have suggested many things already, but let me bring something else to the table here.
Action Method Selector
In order to keep your controller actions clean, you can write an action method selector attribute to create two simple actions:
[ActionName("Index")]
public ActionResult IndexNonActivated(int id)
{
...
}
[ActionName("Index")]
[UserAccountActivated]
public ActionResult IndexActivated(int id)
{
...
}
This way you don't deal with checking code in your actions keeping them really thin. Selector filter will make sure that correct action will get executed related to user account activation state.
You can read more about action selector attributes in my blog post but basically you'd have to write something similar to this:
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
// get profile id first
int id = int.Parse(controllerContext.RouteData.Values["id"] ?? -1);
bool isActivated = // some code to get this state
return isActivated;
}
}
And that's basically it.
This will make it possible for users to access their profile regardless whether their account has been activated or not. Or maybe even deactivated at some later time... And it will work seamlessly in the background.
One important advantage
If you'd have two actions with different names (as Juraj suggests), one for active profiles and other for activation, you'd have to do the checking in both, because even active users would be able to access activation action:
profile/4 > for active profiles
profile/activate/4 > for inactive profiles
Both actions should be checking state and redirect to each other in case that state doesn't "fit". This also means that each time a redirection would occur, profile will get checked twice. In each action.
Action method selector will check profiles only once. No matter what state user profile is in.
I'd prefer to keep my controller thin and place this in an action filter that you can annotate on the Index action of the Profile controller.
public class ValidProfileIdAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActinExecutingContext filterContext) {
int id;
if (int.TryParse(filterContext.ActionParameters["Id"], id) && id != 0) {
// Check if valid and behave accordingly here.
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
}
base.OnActionExecuting(filterContext);
}
}
The OnActionExecuting method will be called before your controller's action.
In your controller:
[ValidProfileId]
public ActionResult Index(int id) {
...
}
I would suggest to have that logic in the controller, as once he/she is activated, they may be able to use the same link to access their profile.
Checking whether an account is activated or not is a part of application logic and should be implemented inside the controller (or deeper). Within the controller, you can redirect un-activated users to any other controller/action to finish the activation. URL routing mechanism should route simply according to a shape of an incoming URL and shouldn't contact the database.

Session is null in RouteHandler

I have spent quite some time going through similar questions here and have not found any that answer my question - apologies if this is a duplicate however I'm pretty sure it's not..
I have an website where the aim is for visitors to complete a form. I am interested in testing different type of forms to ascertain which get filled out more consistently. My idea is that each form has it's own controller and when the user first requests the url it is picked up by a custom route handler which picks 1 form at random and set the relevant controller in RouteData. The chosen formid is then stored in the Session so on subsequnt requests instead of a form being picked at random it will just use the one from the session.
The probem is that I cannot seem to access the Session data in the routehandler - requestContext.Httpcontext.Session is always null. Is this because it is too early in the pipeline? if so how could I achieve this approach?
The first code I tried looked like this:
int FormID = 0;
string FormName = "";
RepositoryManager mgr = new RepositoryManager();
if (requestContext.HttpContext.Session["Form_ID"] != null && requestContext.HttpContext.Session["Form_Name"] != null)
{
int.TryParse(requestContext.HttpContext.Session["Form_ID"].ToString(), out FormID);
FormName = requestContext.HttpContext.Session["Form_Name"].ToString();
}
if (FormID == 0)
{
List<Form> forms = mgr.FormRepository.Get(f => f.FormType.Code == "").ToList();
int rnd = new Random().Next(0, forms.Count - 1);
FormID = forms[rnd].ID;
FormName = forms[rnd].FormName;
requestContext.HttpContext.Session["Form_ID"] = FormID;
requestContext.HttpContext.Session["Form_Name"].ToString();
}
requestContext.RouteData.Values["controller"] = FormName;
return new MvcHandler(requestContext);
This always errored as requestContext.HttpContext.Session is null
I have tried with a custom routehandler then passing off to a custom http handler as follows:
Routehandler
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
IHttpHandler handler = new FormMvcHandler(requestContext);
return handler;
FormMVCHandler
public class FormMvcHandler : MvcHandler, IRequiresSessionState
{
public FormMvcHandler(RequestContext requestContext)
: base(requestContext)
{
}
protected override void ProcessRequest(HttpContext httpContext)
{
//for testing setting form manually - session will be used here as in original routehandler
RequestContext.RouteData.Values["controller"] = "1Stage";
base.ProcessRequest(httpContext);
}
}
In this second approach changing the controller name has no effect. I have tried changing the controller name in the constructor of the HTTPHandler which does have an effect however If I try and access the session from there using RequestContext.HttpContext.Session it is still null. I have tried setting a breakpoint in ProcessRequest however it is never hit.
Edit 2
This now works by overriding both ProcessRequest(HttpContext httpContext) and BeginProcessRequest(HttpContext httpContext) in the HttpHandler - even when not using an async controller BeginProcessRequest is called by the framework (v3)
In your RouteHandler you have an function GetHttpHandler which return an IHttpHandler. That custom HttpHandler must use IRequiresSessionState and then you can access the Session in the ProcessRequest function in the HttpHandler.
Look into this post:
IRequiresSessionState - how do I use it?
I think you need to use IRequiresSessionState interface
It's too early to using Session in router hander.
you can achieve what you want by using action filter.
Create a Controller named FormController, an action named FormPickerAttribute. In the ActionExecuting of attribute, you can check cookie or session, where your set form id. let's say the form id is "Form1"(create one if null), then you change the action methods to "Form1".
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary{
{ "controller", "Form" },
{ "action", "Form1" },
{ "area", ""}, #your area name
{ "parameter", "parameter value"} #passing any parameter to the action
}
);
you can also create a controller for each form, just updated the
{"controller", "FormIdController"}
to the correct one.

Asp.Net MVC - Best approach for "dynamic" routing

I am trying to come up with an approach to create "dynamic" routing. What I mean, exactly, is that I want to be able to assign the controller and action of a route for each hit rather than having it mapped directly.
For example, a route may look like this "path/{object}" and when that path is hit, a lookup is performed providing the appropriate controller / action to call.
I've tried discovering the mechanisms for creating a custom route handler, but the documentation / discoverability is a bit shady at the moment (I know, its beta - I wouldn't expect any more). Although, I'm not sure if thats even the best approach and perhaps a controller factory or even a default controller/action that performs all of the mappings may be the best route (no pun intended) to go.
Any advice would be appreciated.
You can always use a catch all syntax ( I have no idea if the name is proper).
Route:
routeTable.MapRoute(
"Path",
"{*path}",
new { controller = "Pages", action = "Path" });
Controller action is defined as:
public ActionResult Path(string path)
In the action for controller you will have a path, so just have to spilt it and analyse.
To call another controller you can use a RedirectToAction ( I think this is more proper way). With redirection you can set up a permanent redirectionfor it.
Or use a something like that:
internal class MVCTransferResult : RedirectResult
{
public MVCTransferResult(string url) : base(url)
{
}
public MVCTransferResult(object routeValues)
: base(GetRouteURL(routeValues))
{
}
private static string GetRouteURL(object routeValues)
{
UrlHelper url = new UrlHelper(
new RequestContext(
new HttpContextWrapper(HttpContext.Current),
new RouteData()),
RouteTable.Routes);
return url.RouteUrl(routeValues);
}
public override void ExecuteResult(ControllerContext context)
{
var httpContext = HttpContext.Current;
// ASP.NET MVC 3.0
if (context.Controller.TempData != null &&
context.Controller.TempData.Count() > 0)
{
throw new ApplicationException(
"TempData won't work with Server.TransferRequest!");
}
// change to false to pass query string parameters
// if you have already processed them
httpContext.Server.TransferRequest(Url, true);
// ASP.NET MVC 2.0
//httpContext.RewritePath(Url, false);
//IHttpHandler httpHandler = new MvcHttpHandler();
//httpHandler.ProcessRequest(HttpContext.Current);
}
}
However this method require to run on IIS or a IIS Expres Casinni is not supporting a Server.Transfer method

Resources