How do I feed the language culture thread into the helper class - asp.net

With asp.net MVC 2, I have been trying to get Matt Hawley's Localization helper to work in my web application, but I am getting stuck feeding a null into the Language string variable. I can't figure out why I am doing this.
namespace MvcLocalization
{
public abstract class LocalizedControllerBase : Controller
{
public String LanguageCode { get; private set; }
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
LanguageCode = requestContext.RouteData.Values["languageCode"].ToString();
if ( !AppConfig.SupportedLanguages.Contains(LanguageCode) )
LanguageCode = AppConfig.DefaultLanguageCode;
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture(LanguageCode);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
base.Execute(requestContext);
}
}
}
What could I be doing wrong?

I figured out how to swap cultures using MIKEBLOG's code in his/her awesome blog post http://helios.ca/2009/05/27/aspnet-mvc-and-localization/
thanks
Paul

Related

ASP.NET : How to Manage State in Class Library?

I have an ASP.NET MVC 6 application with a few class libraries (.NET 4.6.1). Now I want to pass the values between the asp.net application and the class libraries. For example I want to access UserId (that is inside a session) from the class library. I don't want to use parameters to pass the value, because UserId is a global variable in my class library and I don't have a reference from web application in the class library. What is the best way to solve this?
Use Sessions in a class library?
Use Shared Memory ?
Use Web Service ?
Use Dtabase ?
... ?
Update :
https://stackoverflow.com/a/2040623/2455393 says that we can use this :
using System.Web;
var currentSession = HttpContext.Current.Session;
var myValue = currentSession["myKey"];
in .NET 4.6.1 (MVC 6) it does not work. but in .NET 4.0 it works well. this is my problem.
I don't have a reference from web application in the class library.
What is the best way to solve this?
Ideally, class library should never have access to HttpContext (unless it is related to presentation layer). Instead, you just pass UserId as a parameter to methods.
Otherwise, it will be hard to unit test the class library.
How about Presentation Layer
If you want to access userId inside controller, you want to inject it, instead of accessing it from HttpContext directly.
For example,
public interface IUserSession
{
int Id { get; }
string FirstName { get; }
string LastName { get; }
string UserName { get; }
bool IsInRole(string roleName);
}
public interface IWebUserSession : IUserSession
{
Uri RequestUri { get; }
string HttpRequestMethod { get; }
}
public class UserSession : IWebUserSession
{
public int Id => Convert.ToInt32(((ClaimsPrincipal) HttpContext.Current.User)?.FindFirst(ClaimTypes.Sid)?.Value);
public string FirstName => ((ClaimsPrincipal)HttpContext.Current.User)?.FindFirst(ClaimTypes.GivenName)?.Value;
public string LastName => ((ClaimsPrincipal) HttpContext.Current.User)?.FindFirst(ClaimTypes.Surname)?.Value;
public string UserName => ((ClaimsPrincipal)HttpContext.Current.User)?.FindFirst(ClaimTypes.Name)?.Value;
public bool IsInRole(string roleName) => HttpContext.Current.User.IsInRole(roleName);
public Uri RequestUri => HttpContext.Current.Request.Url;
public string HttpRequestMethod => HttpContext.Current.Request.HttpMethod;
}
Usage
public class MyController : Controller
{
private readonly IWebUserSession _webUserSession;
public MyController(IWebUserSession webUserSession)
{
_webUserSession = webUserSession;
}
}

How to control the language in which model validation errors are displayed

In an ASP.NET MVC application, changing the Thread.CurrentThread.CurrentCulture[UI] can change the way MVC picks messages from resources. In the example application I've created to present the problem, there are two resource file - Res.resx for the English messages and Res.es.resx for the Spanish messages.
However, error messages resulting from the model validation always display in English.
My question is, how can I control the language in which the model validation error messages are displayed?
Below are parts of an example application I've wrote (based on the default ASP.NET MVC application) to demonstrate this problem.
Screenshots of how it looks in the browser:
https://dl.dropboxusercontent.com/u/4453002/SO_LanguageOfValidation.png
ViewModel and Controller - HomeController.cs :
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Threading;
using System.Web.Mvc;
namespace SO_ValidationMessageInEnglish.Controllers {
/// <summary>
/// A very basic view model.
/// </summary>
public class ViewModel {
[Display(Name = "Message", ResourceType = typeof(Res))]
[DisplayName("Message")]
[Required(AllowEmptyStrings = false, ErrorMessageResourceName = "MessageRequired", ErrorMessageResourceType = typeof(Res))]
public string Message { get; set; }
public string Language { get; set; }
}
public class HomeController : Controller {
public ActionResult Index(string language = "en") {
Thread.CurrentThread.CurrentCulture = new CultureInfo(language);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(language);
return View();
}
[HttpPost]
[ActionName("Index")]
public ActionResult IndexPost(ViewModel foo) {
Thread.CurrentThread.CurrentCulture = new CultureInfo(foo.Language ?? "en");
Thread.CurrentThread.CurrentUICulture = new CultureInfo(foo.Language ?? "en");
return View(foo);
}
}
}
View - Index.cshtml :
#model SO_ValidationMessageInEnglish.Controllers.ViewModel
#using SO_ValidationMessageInEnglish
#{ ViewBag.Title = Res.Title; }
#Res.CurrentMessage:<br />
<h2>#((Model != null) ? Model.Message : Res.Default)</h2>
<p />
#using (Html.BeginForm("Index", "Home", FormMethod.Post, null)) {
#Html.LabelFor(m => m.Message)
#Html.TextBoxFor(m => m.Message)
#Html.HiddenFor(m => m.Language)
#Html.ValidationMessageFor(m => m.Message)
<input type="submit" value="#Res.Submit" />
}
I also run into the same problem. When the model binder has invalid data it runs before the ActionFilter(s).
I didn't like the proposed solutions because messing with the routing was not my preferred solution. Listen for Application_AcquireRequestState is problematic because this event fire for each and every request, not just for requests that will be routed into MVC controllers.
I've end up writing a custom implementation of IControllerFactory that use DefaultControllerFactory internally and execute the localization code inside CreateController method.
This is not ideal either, hope it helps.
public class PluggableControllerFactory : IControllerFactory {
private readonly IControllerFactory innerControllerFactory;
public PluggableControllerFactory() {
innerControllerFactory = new DefaultControllerFactory();
}
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) {
// Run your culture localization here
return innerControllerFactory.CreateController(requestContext, controllerName);
}
public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(System.Web.Routing.RequestContext requestContext, string controllerName) {
return innerControllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
}
public void ReleaseController(IController controller) {
innerControllerFactory.ReleaseController(controller);
}
}
}
.NET uses full culture code to pick up the resource file as first choice. Rename Res.es.resx to Res.es-ES.resx and see if that works.
I guess, one way to do that is to use your own model binder (although that might cause other problems in some situations). That will execute only when there is a model on the action, thus you might have to set the culture in two places (one for all actions and this for the model validation in particular):
public class MyModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var un = HttpContext.Current.User.Identity.Name;
if (!string.IsNullOrWhiteSpace(un))
{
var userLanguageCode = .......
var culture = CultureInfo.GetCultureInfo(userLanguageCode ?? "en-US");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
I found a different and in my opinion better solution that requires less work. In the Global.asax file:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
// Run your culture localization here
}

JSON.NET Serializer for WCF REST Services

I am trying to use the NETFx Json.NET MediaTypeFormatter nuget package to swap out the default DataContractJsonSerializer in my WCF REST service (4.0 framework). I downloaded the package in my project and added the following lines of code in the Global.asax file.
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
// Create Json.Net formatter serializing DateTime using the ISO 8601 format
var serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
var config = HttpHostConfiguration.Create();
config.Configuration.OperationHandlerFactory.Formatters.Clear();
config.Configuration.OperationHandlerFactory.Formatters.Insert(0, new JsonNetMediaTypeFormatter(serializerSettings));
}
But when I run the service it still uses the DataContractJsonSerilizer for serialization. Below is the class I am returning from my service.
[DataContract]
public class SampleItem
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string StringValue { get; set; }
[DataMember]
public DateTime DateTime { get; set; }
}
Below is the response from the service in Fiddler.
You can see that the DateTime is not in ISO format which I have specified in serializerSettings in the above code. This tells me that the JSON.NET serializer is not used for serializing the objects.
Would appreciate any help.
I feel dumb after I figured the answer. Happens at times :). I had to add the config to the RouteTable. Below is the code in Global.asax
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes();
}
private void RegisterRoutes()
{
// Create Json.Net formatter serializing DateTime using the ISO 8601 format
var serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
var config = HttpHostConfiguration.Create().Configuration;
config.OperationHandlerFactory.Formatters.Clear();
config.OperationHandlerFactory.Formatters.Insert(0, new JsonNetMediaTypeFormatter(serializerSettings));
var httpServiceFactory = new HttpServiceHostFactory
{
OperationHandlerFactory = config.OperationHandlerFactory,
MessageHandlerFactory = config.MessageHandlerFactory
};
RouteTable.Routes.Add(new ServiceRoute("Service1", httpServiceFactory, typeof(Service1)));
}
}
Hope it will help somebody if they happen to run into the same scenario.

C# Optional Object Action MVC Parameter

Is it possible to specify an object as a parameter in MVC with default values in some way?
E.g.
public virtual ViewResult Index(RequirementFilters requirementFilters)
I'd like to initialize the values of a couple of parameters on RequirementFilters?
At the moment I am doing
public virtual ViewResult Index(int status=1, bool required =false)
I wanted to create a Filter Object so I could re-use it but I can't figure out way of setting defaults for the object in the Action Parameters.
Thanks
Graeme
You could create a custom ActionFilter attribute and create an instance of your Filter Object there. You can provide some properties through the custom attribute.
Here's an example:
public class DefaultQuerySettingsAttribute : ActionFilterAttribute
{
public string ParameterName { get; set; }
public Type SettingsType { get; set; }
public int Rows { get; set; }
public string SortColumn { get; set; }
public string SortOrder { get; set; }
public bool PagingEnabled { get; set; }
public DefaultQuerySettingsAttribute()
{
this.ParameterName = "settings";
var defaultSettings = new QuerySettings();
this.Rows = defaultSettings.Rows;
this.SortColumn = defaultSettings.SortColumn;
this.SortOrder = defaultSettings.SortOrder;
this.PagingEnabled = defaultSettings.PagingEnabled;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (filterContext.ActionParameters.ContainsKey(this.ParameterName))
{
var querySettings = filterContext.ActionParameters[this.ParameterName] as QuerySettings;
if (querySettings == null || string.IsNullOrWhiteSpace(querySettings.SortColumn))
filterContext.ActionParameters[this.ParameterName] = this.GetQuerySettings();
}
}
private QuerySettings GetQuerySettings()
{
var querySettings = (QuerySettings)Activator.CreateInstance(SettingsType ?? typeof(QuerySettings));
querySettings.Rows = Rows;
querySettings.SortColumn = SortColumn;
querySettings.SortOrder = SortOrder;
querySettings.PagingEnabled = PagingEnabled;
return querySettings;
}
}
ParameterName is the name of the argument in the action method (requirementFilters in your case).
You can also specify actual type that will be instantiated by providing SettingsType.
Users sometimes prefer to see the defaults on screen, rather than allowing the system to hide the defaults internally.
A better way of having defaults will be to actually show the defaults on int UI, in the HTML by rendering it with together with the defaults. That way when someone posts the page, the defaults which you pre-rendered is also posted and binded to the model.
So try and see if you can render with defaults whatever for you are rendering and posted to the Index action.
Finally, if you can't do it that way, what is preventing you from initializing the properties with default values in the no-arg constructor while creating the object?
EDIT
Or you can use the C# language feature the null coalescent operator to implement defaults. Look here to read about it.
As long as you don't need to change the defaults per action, you can set them in the default constructor of the Model.

ASP.NET MVC - Set custom IIdentity or IPrincipal

I need to do something fairly simple: in my ASP.NET MVC application, I want to set a custom IIdentity / IPrincipal. Whichever is easier / more suitable. I want to extend the default so that I can call something like User.Identity.Id and User.Identity.Role. Nothing fancy, just some extra properties.
I've read tons of articles and questions but I feel like I'm making it harder than it actually is. I thought it would be easy. If a user logs on, I want to set a custom IIdentity. So I thought, I will implement Application_PostAuthenticateRequest in my global.asax. However, that is called on every request, and I don't want to do a call to the database on every request which would request all the data from the database and put in a custom IPrincipal object. That also seems very unnecessary, slow, and in the wrong place (doing database calls there) but I could be wrong. Or where else would that data come from?
So I thought, whenever a user logs in, I can add some necessary variables in my session, which I add to the custom IIdentity in the Application_PostAuthenticateRequest event handler. However, my Context.Session is null there, so that is also not the way to go.
I've been working on this for a day now and I feel I'm missing something. This shouldn't be too hard to do, right? I'm also a bit confused by all the (semi)related stuff that comes with this. MembershipProvider, MembershipUser, RoleProvider, ProfileProvider, IPrincipal, IIdentity, FormsAuthentication.... Am I the only one who finds all this very confusing?
If someone could tell me a simple, elegant, and efficient solution to store some extra data on a IIdentity without all the extra fuzz.. that would be great! I know there are similar questions on SO but if the answer I need is in there, I must've overlooked.
Here's how I do it.
I decided to use IPrincipal instead of IIdentity because it means I don't have to implement both IIdentity and IPrincipal.
Create the interface
interface ICustomPrincipal : IPrincipal
{
int Id { get; set; }
string FirstName { get; set; }
string LastName { get; set; }
}
CustomPrincipal
public class CustomPrincipal : ICustomPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string email)
{
this.Identity = new GenericIdentity(email);
}
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
CustomPrincipalSerializeModel - for serializing custom information into userdata field in FormsAuthenticationTicket object.
public class CustomPrincipalSerializeModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
LogIn method - setting up a cookie with custom information
if (Membership.ValidateUser(viewModel.Email, viewModel.Password))
{
var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();
CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
serializeModel.Id = user.Id;
serializeModel.FirstName = user.FirstName;
serializeModel.LastName = user.LastName;
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
viewModel.Email,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
return RedirectToAction("Index", "Home");
}
Global.asax.cs - Reading cookie and replacing HttpContext.User object, this is done by overriding PostAuthenticateRequest
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);
CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
newUser.Id = serializeModel.Id;
newUser.FirstName = serializeModel.FirstName;
newUser.LastName = serializeModel.LastName;
HttpContext.Current.User = newUser;
}
}
Access in Razor views
#((User as CustomPrincipal).Id)
#((User as CustomPrincipal).FirstName)
#((User as CustomPrincipal).LastName)
and in code:
(User as CustomPrincipal).Id
(User as CustomPrincipal).FirstName
(User as CustomPrincipal).LastName
I think the code is self-explanatory. If it isn't, let me know.
Additionally to make the access even easier you can create a base controller and override the returned User object (HttpContext.User):
public class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
and then, for each controller:
public class AccountController : BaseController
{
// ...
}
which will allow you to access custom fields in code like this:
User.Id
User.FirstName
User.LastName
But this will not work inside views. For that you would need to create a custom WebViewPage implementation:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
Make it a default page type in Views/web.config:
<pages pageBaseType="Your.Namespace.BaseViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
and in views, you can access it like this:
#User.FirstName
#User.LastName
I can't speak directly for ASP.NET MVC, but for ASP.NET Web Forms, the trick is to create a FormsAuthenticationTicket and encrypt it into a cookie once the user has been authenticated. This way, you only have to call the database once (or AD or whatever you are using to perform your authentication), and each subsequent request will authenticate based on the ticket stored in the cookie.
A good article on this: http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (broken link)
Edit:
Since the link above is broken, I would recommend LukeP's solution in his answer above: https://stackoverflow.com/a/10524305 - I would also suggest that the accepted answer be changed to that one.
Edit 2:
An alternative for the broken link: https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
Here is an example to get the job done. bool isValid is set by looking at some data store (lets say your user data base). UserID is just an ID i am maintaining. You can add aditional information like email address to user data.
protected void btnLogin_Click(object sender, EventArgs e)
{
//Hard Coded for the moment
bool isValid=true;
if (isValid)
{
string userData = String.Empty;
userData = userData + "UserID=" + userID;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
//And send the user where they were heading
string redirectUrl = FormsAuthentication.GetRedirectUrl(username, false);
Response.Redirect(redirectUrl);
}
}
in the golbal asax add the following code to retrive your information
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
// Create an Identity object
//CustomIdentity implements System.Web.Security.IIdentity
CustomIdentity id = GetUserIdentity(authTicket.Name);
//CustomPrincipal implements System.Web.Security.IPrincipal
CustomPrincipal newUser = new CustomPrincipal();
Context.User = newUser;
}
}
When you are going to use the information later, you can access your custom principal as follows.
(CustomPrincipal)this.User
or
(CustomPrincipal)this.Context.User
this will allow you to access custom user information.
MVC provides you with the OnAuthorize method that hangs from your controller classes. Or, you could use a custom action filter to perform authorization. MVC makes it pretty easy to do. I posted a blog post about this here. http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0
Here is a solution if you need to hook up some methods to #User for use in your views. No solution for any serious membership customization, but if the original question was needed for views alone then this perhaps would be enough. The below was used for checking a variable returned from a authorizefilter, used to verify if some links wehere to be presented or not(not for any kind of authorization logic or access granting).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal;
namespace SomeSite.Web.Helpers
{
public static class UserHelpers
{
public static bool IsEditor(this IPrincipal user)
{
return null; //Do some stuff
}
}
}
Then just add a reference in the areas web.config, and call it like below in the view.
#User.IsEditor()
Based on LukeP's answer, and add some methods to setup timeout and requireSSL cooperated with Web.config.
The references links
MSDN, Explained: Forms Authentication in ASP.NET 2.0
MSDN, FormsAuthentication Class
SO, .net Access Forms authentication “timeout” value in code
Modified Codes of LukeP
1, Set timeout based on Web.Config. The FormsAuthentication.Timeout will get the timeout value, which is defined in web.config. I wrapped the followings to be a function, which return a ticket back.
int version = 1;
DateTime now = DateTime.Now;
// respect to the `timeout` in Web.config.
TimeSpan timeout = FormsAuthentication.Timeout;
DateTime expire = now.Add(timeout);
bool isPersist = false;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
version,
name,
now,
expire,
isPersist,
userData);
2, Configure the cookie to be secure or not, based on the RequireSSL configuration.
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;
As an addition to LukeP code for Web Forms users (not MVC) if you want to simplify the access in the code behind of your pages, just add the code below to a base page and derive the base page in all your pages:
Public Overridable Shadows ReadOnly Property User() As CustomPrincipal
Get
Return DirectCast(MyBase.User, CustomPrincipal)
End Get
End Property
So in your code behind you can simply access:
User.FirstName or User.LastName
What I'm missing in a Web Form scenario, is how to obtain the same behaviour in code not tied to the page, for example in httpmodules should I always add a cast in each class or is there a smarter way to obtain this?
Thanks for your answers and thank to LukeP since I used your examples as a base for my custom user (which now has User.Roles, User.Tasks, User.HasPath(int) , User.Settings.Timeout and many other nice things)
All right, so i'm a serious cryptkeeper here by dragging up this very old question, but there is a much simpler approach to this, which was touched on by #Baserz above. And that is to use a combination of C# Extension methods and caching (Do NOT use session).
In fact, Microsoft has already provided a number of such extensions in the Microsoft.AspNet.Identity.IdentityExtensions namespace. For instance, GetUserId() is an extension method that returns the user Id. There is also GetUserName() and FindFirstValue(), which returns claims based on the IPrincipal.
So you need only include the namespace, and then call User.Identity.GetUserName() to get the users name as configured by ASP.NET Identity.
I'm not certain if this is cached, since the older ASP.NET Identity is not open sourced, and I haven't bothered to reverse engineer it. However, if it's not then you can write your own extension method, that will cache this result for a specific amount of time.
I tried the solution suggested by LukeP and found that it doesn't support the Authorize attribute. So, I modified it a bit.
public class UserExBusinessInfo
{
public int BusinessID { get; set; }
public string Name { get; set; }
}
public class UserExInfo
{
public IEnumerable<UserExBusinessInfo> BusinessInfo { get; set; }
public int? CurrentBusinessID { get; set; }
}
public class PrincipalEx : ClaimsPrincipal
{
private readonly UserExInfo userExInfo;
public UserExInfo UserExInfo => userExInfo;
public PrincipalEx(IPrincipal baseModel, UserExInfo userExInfo)
: base(baseModel)
{
this.userExInfo = userExInfo;
}
}
public class PrincipalExSerializeModel
{
public UserExInfo UserExInfo { get; set; }
}
public static class IPrincipalHelpers
{
public static UserExInfo ExInfo(this IPrincipal #this) => (#this as PrincipalEx)?.UserExInfo;
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginModel details, string returnUrl)
{
if (ModelState.IsValid)
{
AppUser user = await UserManager.FindAsync(details.Name, details.Password);
if (user == null)
{
ModelState.AddModelError("", "Invalid name or password.");
}
else
{
ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignOut();
AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);
user.LastLoginDate = DateTime.UtcNow;
await UserManager.UpdateAsync(user);
PrincipalExSerializeModel serializeModel = new PrincipalExSerializeModel();
serializeModel.UserExInfo = new UserExInfo()
{
BusinessInfo = await
db.Businesses
.Where(b => user.Id.Equals(b.AspNetUserID))
.Select(b => new UserExBusinessInfo { BusinessID = b.BusinessID, Name = b.Name })
.ToListAsync()
};
JavaScriptSerializer serializer = new JavaScriptSerializer();
string userData = serializer.Serialize(serializeModel);
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
details.Name,
DateTime.Now,
DateTime.Now.AddMinutes(15),
false,
userData);
string encTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
return RedirectToLocal(returnUrl);
}
}
return View(details);
}
And finally in Global.asax.cs
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
JavaScriptSerializer serializer = new JavaScriptSerializer();
PrincipalExSerializeModel serializeModel = serializer.Deserialize<PrincipalExSerializeModel>(authTicket.UserData);
PrincipalEx newUser = new PrincipalEx(HttpContext.Current.User, serializeModel.UserExInfo);
HttpContext.Current.User = newUser;
}
}
Now I can access the data in views and controllers simply by calling
User.ExInfo()
To log out I just call
AuthManager.SignOut();
where AuthManager is
HttpContext.GetOwinContext().Authentication

Resources