I have a project mvc 4 c# I'm using resource files for localization. I have action to change the culture
public ActionResult ChangeCulture(string lang, string returnUrl)
{
if (lang.Equals("he-IL"))
{
CultureInfo ci = new CultureInfo(lang);
ci.DateTimeFormat.ShortDatePattern = "dd/MM/yy";
ci.DateTimeFormat.LongDatePattern = "dd/MM/yy";
ci.DateTimeFormat.LongTimePattern = "HH:mm";
ci.DateTimeFormat.ShortTimePattern = "HH:mm";
ci = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
else if (lang.Equals("en-US"))
{
CultureInfo ci = new CultureInfo(lang);
ci = new CultureInfo(lang);
Thread.CurrentThread.CurrentUICulture = ci;
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(ci.Name);
}
else
{
return new HttpStatusCodeResult(404);
}
return Redirect(returnUrl);
}
in the view I get the text from resource and it's working good.
<div>#(ViewRes.GlobalResource.Hello)</div>
but when I return text from SignalR hub it's always the same language. why?
SignalR does not localize your text within it's stack, you have to select the correctly localized text and return that string yourself. This should be as easy as requesting your resource from a ResourceManager and letting it select the correctly localized text for you. A really simple example of that looks something like this:
public string YourHubMethod()
{
ResourceManager resourceManager = new ResourceManager("YourNamespace.YourResourcesName",
typeof(YourHubType).Assembly);
return resourceManager.GetString("SomeResourceName");
}
Update:
Now that you've provided some more information about how you remember the current culture between requests for your web application in the comments (you use a cookie) I can give you more information on how to make this transfer over to SignalR.
You'll first want to override your Hub's OnConnected property and there you can extract the cookie value and store it in SignalR connection state like so:
public override Task OnConnected()
{
Client.Culture = Context.Request.Cookies["YourCultureCookieName"];
return base.OnConnected();
}
Once it's stored in the client state, you can now build a HubPipelineModule that looks for that state and sets the culture for each logical SignalR request:
public class CallerCulturePipelineModule : HubPipelineModule
{
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
// Use the value we stored in the Culture property of the caller's state when they connected
CultureInfo callerCultureInfo = new CultureInfo(context.Hub.Context.Caller.Culture);
Thread.CurrentThread.CurrentUICulture = callerCultureInfo;
Thread.CurrentThread.CurrentCulture = callerCultureInfo;
return base.OnBeforeIncoming(context);
}
}
You then need to make sure to register the pipeline module as part of your Application_Start:
GlobalHost.HubPipeline.AddModule(new CallerCulturePipelineModule());
Finally, if the user changes their culture while they're already connected you would need to update their connection state with the newly selected value (since they're not going to reconnect).There's actually no way to modify connection state outside the hub itself, so you would need to actually get the IHubContext for your hub and update the client through an explicit callback to the client inside of your existing MVC controller's ChangeCulture method. To do this I would suggest you pass the connection ID to ChangeCulture as an optional query string variable (since the user might not be connected to your hub all the time?) and then use that to call back to the client:
string connectionId = <get connectionId from request here>;
IHubContext yourHubContext GlobalHost.GetHubContext<YourHub>();
yourHubContext.Client(connectionId).UpdateCultureState(lang);
And then on the client side you just update the state in JavaScript:
yourHubProxy.client.updateCultureState = function(lang)
{
yourHubProxy.state.culture = lang;
}
Related
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;
}
}
Lets stay I store content for a single user of my application at:
C:\Files{GUID}\
Those files may be something like:
C:\Files{GUID}\bunny.jpg
C:\Files{GUID}\unicorn.html
The end user should be presented with a "friendly" url.
http:// domain.com/files/{GUID}/bunny.jpg
That url somehow must pass through a controller or httpmodule or thingIdontknow to be authorized to view that file. These permissions may change day to day so files need to be checked for permissions often.
From what I've been reading this is entirely possible but I'm not sure what to code up next or if anybody has any insight here. HttpModule or Controller? I'm confused as to what needs to happen.
That url somehow must pass through a controller or httpmodule or thingIdontknow to be authorized to view that file. These permissions may change day to day so files need to be checked for permissions often.
The thing you don't know has a name. It's called an authorization action filter.
First let's suppose that you have registered a custom route for serving those files:
routes.MapRoute(
"MyImagesRoute",
"files/{id}/{name}",
new { controller = "Files", action = "Index" }
// TODO: you could constrain the id parameter to be a GUID.
// Just Google for a Regex that will match a GUID pattern and put here
// as route constraint
);
and then of course a corresponding controller to serve them:
public class FilesController: Controller
{
public ActionResult Index(Guid guid, string name)
{
var path = #"C:\files";
var file = Path.Combine(path, guid.ToString(), name);
file = Path.GetFullPath(file);
if (!file.StartsWith(path))
{
// someone tried to be smart and send
// files/{Guid}/..\..\creditcard.pdf as parameter
throw new HttpException(403, "Forbidden");
}
// TODO: adjust the mime type based on the extension
return File(file, "image/png");
}
}
Unfortunately at this stage there's nothing preventing a user ALPHA from requesting the file of user BETA, right? That's the scenario you would like to handle, aren't you?
So let's write a custom Authorize attribute to protect this controller action:
public class MyAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authenticated or doesn't have
// permissions to access this controller action
return false;
}
// at this stage we know that there's some user authenticated
// Let's get the Guid now from our route:
var routeData = httpContext.Request.RequestContext.RouteData;
var id = routeData.Values["id"] as string;
Guid guid;
if (!Guid.TryParse(id, out guid))
{
// invalid Guid => no need to continue any further, just deny access
return false;
}
// Now we've got the GUID that this user is requesting
// Let's see who this user is:
string username = httpContext.User.Identity.Name;
// and finally ensure that this user
// is actually the owner of the folder
return IsAuthorized(username, guid);
}
private bool IsAuthorized(string username, Guid guid)
{
// You know what to do here: hit your data store to verify
// that the currently authenticated username is actually
// the owner of this GUID
throw new NotImplementedException();
}
}
and then let's decorate our controller action with this authorization attribute:
public class FilesController: Controller
{
[MyAuthorize]
public ActionResult Index(Guid guid, string name)
{
// at this stage we know that the currently authenticated user
// is authorized to access the file.
var path = #"C:\files";
var file = Path.Combine(path, guid.ToString(), name);
file = Path.GetFullPath(file);
if (!file.StartsWith(path))
{
// someone tried to be smart and send
// files/{Guid}/..\..\creditcard.pdf as parameter
throw new HttpException(403, "Forbidden");
}
var file = Path.Combine(#"c:\files", guid.ToString(), name);
// TODO: adjust the mime type based on the extension
return File(file, "image/png");
}
}
I'm trying to serve an iCalendar file (.ics) in my MVC application.
So far it's working fine. I have an iPhone subscribing to the URL for the calendar but now I need to serve a personalised calendar to each user.
When subscribing to the calendar on the iPhone I can enter a username and password, but I don't know how to access these in my MVC app.
Where can I find details of how the authentication works, and how to implement it?
It turns out that Basic Authentication is what is required. I half had it working but my IIS configuration got in the way. So, simply returning a 401 response when there is no Authorization header causes the client (e.g. iPhone) to require a username/password to subscribe to the calendar.
On the authorization of the request where there is an Authorization request header, the basic authentication can be processed, retrieving the username and password from the base 64 encoded string.
Here's some useful code for MVC:
public class BasicAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var auth = filterContext.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var encodedDataAsBytes = Convert.FromBase64String(auth.Replace("Basic ", ""));
var value = Encoding.ASCII.GetString(encodedDataAsBytes);
var username = value.Substring(0, value.IndexOf(':'));
var password = value.Substring(value.IndexOf(':') + 1);
if (MembershipService.ValidateUser(username, password))
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(username), null);
}
else
{
filterContext.Result = new HttpStatusCodeResult(401);
}
}
else
{
if (AuthorizeCore(filterContext.HttpContext))
{
var cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
else
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Calendar\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
}
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, my controller action looks like this:
[BasicAuthorize]
public ActionResult Calendar()
{
var userName = HttpContext.User.Identity.Name;
var appointments = GetAppointments(userName);
return new CalendarResult(appointments, "Appointments.ics");
}
I found this really helpful, but i hit a few problems during the development and i thought i would share some of them to help save other people some time.
I was looking to get data from my web application into the calendar for an android device and i was using discountasp as a hosting service.
The first problem i hit was that the validation did not work when uploaded to the server, stangely enough it was accepting my control panel login for discountasp but not my forms login.
The answer to this was to turn off Basic Authentication in IIS manager. This resolved the issue.
Secondly, the app i used to sync the calendar to the android device was called iCalSync2 - its a nice app and works well. But i found that it only worked properly when the file was delivered as a .ics (duh for some reason i put it as a .ical.. it must have been late) and i also had to choose the webcal option
Lastly i found i had to add webcal:// to the start of my url instead of http://
Also be careful as the code posted above ignores the roles input variable and always passes nothing so you might need to do some role based checks inside your calendar routine or modify the code above to process the roles variable.
How can I (in ASP .NET MVC) get the CultureInfo of the current visitor (based on his/her browser languages)?
I have no idea where to start. I tried looking into the "Accept-Languages" header sent by the browser. But is that the best way of doing it?
Request.UserLanguages is the property you're looking for. Just keep in mind that this array may contain arbitrary (even non-exsitent) languages as set by request headers.
UPDATE
Example:
// Get Browser languages.
var userLanguages = Request.UserLanguages;
CultureInfo ci;
if (userLanguages.Count() > 0)
{
try
{
ci = new CultureInfo(userLanguages[0]);
}
catch(CultureNotFoundException)
{
ci = CultureInfo.InvariantCulture;
}
}
else
{
ci = CultureInfo.InvariantCulture;
}
// Here CultureInfo should already be set to either user's prefereable language
// or to InvariantCulture if user transmitted invalid culture ID
Asp.Net Core version: using RequestLocalization ie the culture is retrieved form the HTTP Request.
in Startup.cs - Configure
app.UseRequestLocalization();
Then in your Controller/Razor Page.cs
var locale = Request.HttpContext.Features.Get<IRequestCultureFeature>();
var BrowserCulture = locale.RequestCulture.UICulture.ToString();
You can use code similar to the following to get various details from your user (including languages):
MembershipUser user = Membership.GetUser(model.UserName);
string browser = HttpContext.Request.Browser.Browser;
string version = HttpContext.Request.Browser.Version;
string type = HttpContext.Request.Browser.Type;
string platform = HttpContext.Request.Browser.Platform;
string userAgent = HttpContext.Request.UserAgent;
string[] userLang = HttpContext.Request.UserLanguages
It appears Request.UserLanguages is not available in later mvc versions (Asp.net core mvc 2.0.2 didn't have it.)
I made an extension method for HTTPRequest. Use it as follows:
var requestedLanguages = Request.GetAcceptLanguageCultures();
The method will give you the cultures from the Accept-Language header in order of preference (a.k.a. "quality").
public static class HttpRequestExtensions
{
public static IList<CultureInfo> GetAcceptLanguageCultures(this HttpRequest request)
{
var requestedLanguages = request.Headers["Accept-Language"];
if (StringValues.IsNullOrEmpty(requestedLanguages) || requestedLanguages.Count == 0)
{
return null;
}
var preferredCultures = requestedLanguages.ToString().Split(',')
// Parse the header values
.Select(s => new StringSegment(s))
.Select(StringWithQualityHeaderValue.Parse)
// Ignore the "any language" rule
.Where(sv => sv.Value != "*")
// Remove duplicate rules with a lower value
.GroupBy(sv => sv.Value).Select(svg => svg.OrderByDescending(sv => sv.Quality.GetValueOrDefault(1)).First())
// Sort by preference level
.OrderByDescending(sv => sv.Quality.GetValueOrDefault(1))
.Select(sv => new CultureInfo(sv.Value.ToString()))
.ToList();
return preferredCultures;
}
}
Tested with ASP.NET Core MVC 2.0.2
It's similar to #mare's answer, but a bit more up-to-date and the q (quality) is not ignored. Also, you may want to append the CultureInfo.InvariantCulture to the end of the list, depending on your usage.
I am marking this question for myself with a star and sharing here some code that essentially turns the Request.UserLanguages into an array of CultureInfo instances for further use in your application. It is also more flexible to work with CultureInfo than just the ISO codes, because with CultureInfo you get access to all the properties of a culture (like Name, Two character language name, Native name, ...):
// Create array of CultureInfo objects
string locale = string.Empty;
CultureInfo[] cultures = new CultureInfo[Request.UserLanguages.Length + 1];
for (int ctr = Request.UserLanguages.GetLowerBound(0); ctr <= Request.UserLanguages.GetUpperBound(0);
ctr++)
{
locale = Request.UserLanguages[ctr];
if (!string.IsNullOrEmpty(locale))
{
// Remove quality specifier, if present.
if (locale.Contains(";"))
locale = locale.Substring(0, locale.IndexOf(';'));
try
{
cultures[ctr] = new CultureInfo(locale, false);
}
catch (Exception) { continue; }
}
else
{
cultures[ctr] = CultureInfo.CurrentCulture;
}
}
cultures[Request.UserLanguages.Length] = CultureInfo.InvariantCulture;
HTH
var userLanguage = CultureInfo.CurrentUICulture;
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);
}
}