Base View Model with Culture/Language via BeginExecuteCore - asp.net-mvc

I was wondering whether any people could help me please? I'm trying to create a site where the user logs in, it retrieves their chosen language from the database, and it uses that when setting the culture. There are also a number of settings about the user that would be retrieved at the same time as the user's language.
The culture/translations are handled via a base controller below (it's still a test version, but you will get the idea).
public abstract class BaseController : Controller
{
//public UserRegistrationInformation UserSession;
//public void GetUserInfo()
//{
// WebUsersEntities db = new WebUsersEntities();
// UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == WebSecurity.CurrentUserId).FirstOrDefault();
//}
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
//GetUserInfo();
string cultureName = null;
// Change this to read from the user settings rather than a cookie
/// Attempt to read the culture cookie from Request
//HttpCookie cultureCookie = Request.Cookies["_culture"];
//if (cultureCookie != null)
// cultureName = cultureCookie.Value;
//else
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
//cultureName = "es-es";
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
}
This was largely taken from http://afana.me/post/aspnet-mvc-internationalization-part-2.aspx
I've been searching for how to pass the user's settings to the _layout rather than just the view. I found an interesting post here Pass data to layout that are common to all pages that works for me, I've created a base ViewModel, and any other ViewModels are inheriting it.
public abstract class ViewModelBase
{
public string BrandName { get; set; }
public UserRegistrationInformation UserSession;
public void GetUserInfo()
{
WebUsersEntities db = new WebUsersEntities();
UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == WebSecurity.CurrentUserId).FirstOrDefault();
}
}
To test with I've altered the existing change password model and control to:
public class LocalPasswordModel : ViewModelBase
{..........}
and
public ActionResult Manage(ManageMessageId? message)
{
//ViewModelAccounts vma = new ViewModelAccounts();
//vma.GetUserInfo();
LocalPasswordModel l = new LocalPasswordModel();
l.GetUserInfo();
l.BrandName = "blue";
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
: "";
ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
ViewBag.ReturnUrl = Url.Action("Manage");
return View(l);
}
Again this works perfectly, however I only want to retrieve the user's information the once. Currently I can do what I want by calling it in the BeginExecuteCore, and then again in the controller as above. How can I call this the once to be used everywhere? i.e. populate the BaseViewModel.
Thanks for any help or pointers you may be able to give!

Ok. I've finally solved this.
I'm, creating a base model that all of my other view-models are going to inherit from. It can also be called directly in case any view doesn't require its own view-model.
public class ViewModelBase
{
public UserSettings ProfileSettings;
// Create a new instance, so we don't need to every time its called.
public ViewModelBase()
{
ProfileSettings = new UserSettings();
}
}
public class UserSettings // UserSettings is only used here and consumed by ViewModelBase, its the name there that is used throughout the application
{
public string BrandName { get; set; }
public UserRegistrationInformation UserSession;
}
This is being generated in the basecontroller.
public abstract class BaseController : Controller
{
public ViewModelBase vmb = new ViewModelBase();
protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
string cultureName = null;
int userid = 0;
if (System.Web.Security.Membership.GetUser() != null)
{
//logged in
userid = (int)System.Web.Security.Membership.GetUser().ProviderUserKey;
WebUsersEntities db = new WebUsersEntities();
vmb.ProfileSettings.UserSession = db.UserRegistrationInformations.Where(r => r.uri_UserID == userid).FirstOrDefault();
vmb.ProfileSettings.BrandName = "test";
cultureName = "es-es";
}
else
{
// not logged in
cultureName = Request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
return base.BeginExecuteCore(callback, state);
}
}
The other controllers all inherit from this controller. If any screen has a dedicated view-model it can retrieve the information from the model populated in the controller like this:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
LoginModel v = new LoginModel();
v.ProfileSettings = vmb.ProfileSettings;
ViewBag.ReturnUrl = returnUrl;
return View(v);
}
I hope that helps someone in the future.

Related

MVC 5 Session Variable ModelBinder null on Action Method

I am doing an MVC APP. I have a View that Inherit from Model call UserModel that have 2 properties. UserName and Password. I want to save those values in Session variables, so I am using ModelBinder.
My class definition is like this.
public class UserModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
My model binder is like this.
public class UserDetailModelBinder : IModelBinder
{
#region Constants
private const string SessionKey = "User";
#endregion
#region Public Methods and Operators
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
UserModel user = (controllerContext.HttpContext.Session != null) ? (controllerContext.HttpContext.Session[SessionKey] as UserModel) : null;
if (user == null)
{
user = new UserDetail();
controllerContext.HttpContext.Session[SessionKey] = user;
}
return user;
}
#endregion
}
And I have defined Properly in My global.asax
The problem I found is that my Action Method that receives a UserModel instance from the View is null. It reads what already has my Session instead of Reading the View, and then Save it in the Session.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(UserModel model)
{
}
I suppose it is because it's the same Model I defined to save in the BinderModel
So, my question would be, How can I save in Session, a Model inherit from the View using BinderModel?
You're setting the null value to UserModel and returned. You should be read the values from request and return it.
var request = controllerContext.HttpContext.Request;
if (user == null)
{
user = new UserModel() {
UserName= request.Form.Get("UserName").ToString(),
Password = request.Form.Get("Password").ToString()
};
controllerContext.HttpContext.Session["User"] = user;
}
Instead of using the model binder you could directly storing the user model to session in your login method. I'm not sure why you're choosing model binder.
public async Task<ActionResult> Login(UserModel model)
{
//Session["User"] = model
}

A second operation started on this context before a previous asynchronous operation completed error in a synchronous asp.net identity call

I am getting
"A second operation started on this context before a previous
asynchronous operation completed"
error intermittently in an MVC project that uses asp.net identity 2.1. Basically I have a base controller where every other controller derives from, and this controller has a property that returns an ApplicationUser. (The extended class of asp.net identity user). The base controller has the following. Note that UserManager is retrieved from the OwinContext (and yes, it is created by using
CreatePerOwinContext()
method, just like in the templates.
protected ApplicationUser ApplicationUser
{
get
{
var userId = User.Identity.GetUserId();
var applicationUser = UserManager.FindById(userId);
if (applicationUser == null)
{
return null;
}
return applicationUser;
}
}
protected ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
set
{
_userManager = value;
}
}
Then in my other controllers, I freely call this property to get a reference to the ApplicationUser, an example being as follows
public ActionResult Index()
{
if (Request.IsAuthenticated)
{
var model = new OrganizationListViewModel(false)
{
OrganizationDetailViewModels = ApplicationUser.Affiliations
.Select(e => new OrganizationDetailViewModel(
e.Organization, ApplicationUser)).ToList()
};
return View(model);
}
else
{
return RedirectToAction("Search", "Search", new { type = "organization" });
}
}
This sometimes works, and sometimes it gives the error I mentioned. I am not calling an async operation in the property or the controller method. So I am puzzled. Any ideas?
I suspect this line:
OrganizationDetailViewModels = ApplicationUser.Affiliations
.Select(e => new OrganizationDetailViewModel(
e.Organization, ApplicationUser)).ToList()
Here you do a request for ApplicationUser, then lazy-load Affiliations, then for every select in Affiliations you do a request for ApplicationUser again.
Do local caching for the ApplicationUser to avoid multiple requests to DB where only one is required:
private ApplicationUser _cachedUser;
protected ApplicationUser ApplicationUser
{
get
{
var userId = User.Identity.GetUserId();
if (_cachedUser == null)
{
var applicationUser = UserManager.FindById(userId);
return applicationUser;
}
return _cachedUser;
}
}

EF Code First Intercept OWIN User.Identity To All Queries

I am using owin authentification on my MVC project.
I have Orders And Users(ApplicationUser) tables with navigation properties.
Every time I am querieng orders for authorized user i need to check user:
public class TestController : Controller
{
public ActionResult Index()
{
using (var ctx = new TestContext())
{
ctx.Orders.Where(o => o.User == User.Identity.Name).Where(...).ToList();
}
}
}
If i forget to check it somewhere, user will get access to all orders.
I want something like or some better way to do it:
[InterceptQuery]
public Expression<Func<Orders, Boolean>> FilterUser(){
return o => o.Where(o => o.User == HttpContext.User.Identity.Name);
}
And it will always fire when i am quering Orders table.
Create a repository around your context and let your code uses the repository so you will add the filtering in the repository method, and by doing this every call to the repository method will get the filtered data, so yku don't repeat yourself and filter everywhere, just in the repository.
EDIT :
You can implement the repository like this:
// an interface around the current user, so you can mock this later
// and test your repository with your own username.
public interface ICurrentUser
{
string UserName { get; }
}
// the implementation of the current user which get the username from the current identity.
public class CurrentUser : ICurrentUser
{
public string UserName
{
get
{
return HttpContext.Current.User.Identity.Name;
}
}
}
// a simple repository to get the orders filtered by the current user.
public interface IOrderRespositroy
{
List<Order> GetAllOrders();
}
// the implementation of the orderrepository interface,
// this get the dbcontext, which get the orders from the data base,
// but you will filter all orders by the username
public class OrderRepository : IOrderRespositroy
{
private readonly TestContext _context;
public OrderRepository(TestContext context, ICurrentUser currentUser)
{
_context = context;
}
public List<Order> GetAllOrders()
{
return _context.Orders.Where(o=> o.User == currentUser.UserName).Where()
}
}
// your new controller which depends on IOrderRepository, and your code will not be polluted
// by the code to filter the orders by the current user.
public class TestController : Controller
{
private readonly IOrderRespositroy _repository;
public TestController(IOrderRespositroy repository)
{
_repository = repository;
}
public ActionResult Index()
{
var orders = _repository.GetAllOrders();
// .
// .
// .
}
}
Now you can setup your dependencies using an IoC container like Autofac, but by using the above pattern you can easily change the logic to filter the orders if you for example decided that all orders should be filtered by userName and userAccess (for example).
Hope that helps.
I would suggest you to overwrite the AuthorizeAttribute and have it applied on Actions and/or Controllers as needed.
public class OrdersAuthorizeAttribute : AuthorizeAttribute
{
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
{
base.AuthorizeCore(httpContext);
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
return ctx.Orders.FirstOrDefault(o => o.User == user.Identity.Name) != null;
}
}

MVC Role Authorization

I am trying to implement a role authorization mechanism which checks the roles of the current logged in user, if the user is in the right role, he/she is allowed, else display error view.
The problem is that when the user tries to access the below method in the controller, he does get into the RoleAuthorizationAttribute class and gets verfied but then the method in the controller is not executed.
Note : the user has the Client role
Controller method
[RoleAuthorization(Roles = "Client, Adminsitrator")]
public ActionResult addToCart(int ProductID, string Quantity)
{
tempShoppingCart t = new tempShoppingCart();
t.ProductID = ProductID;
t.Username = User.Identity.Name;
t.Quantity = Convert.ToInt16(Quantity);
new OrdersService.OrdersClient().addToCart(t);
ViewData["numberOfItemsInShoppingCart"] = new OrdersService.OrdersClient().getNoOfItemsInShoppingCart(User.Identity.Name);
ViewData["totalPriceInSC"] = new OrdersService.OrdersClient().getTotalPriceOfItemsInSC(User.Identity.Name);
return PartialView("quickShoppingCart", "Orders");
}
Role Authentication class
[System.AttributeUsage(System.AttributeTargets.All,AllowMultiple = false, Inherited = true)]
public sealed class RoleAuthorizationAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
List<String> requiredRoles = Roles.Split(Convert.ToChar(",")).ToList();
List<Role> allRoles = new UsersService.UsersClient().GetUserRoles(filterContext.HttpContext.User.Identity.Name).ToList();
bool Match = false;
foreach (String s in requiredRoles)
{
foreach (Role r in allRoles)
{
string rName = r.RoleName.Trim().ToString();
string sName = s.Trim();
if (rName == sName)
{
Match = true;
}
}
}
if (!Match)
{
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
base.OnAuthorization(filterContext);
}
}
Could you please tell me what I am doing wrong
Since I had the roles of the users in the database I had to check against the database so I included this method in the global.asax
protected void Application_AuthenticateRequest(object sender, EventArgs args)
{
if (Context.User != null)
{
IEnumerable<Role> roles = new UsersService.UsersClient().GetUserRoles(
Context.User.Identity.Name);
string[] rolesArray = new string[roles.Count()];
for (int i = 0; i < roles.Count(); i++)
{
rolesArray[i] = roles.ElementAt(i).RoleName;
}
GenericPrincipal gp = new GenericPrincipal(Context.User.Identity, rolesArray);
Context.User = gp;
}
}
Then I could use the normal
[Authorize(Roles = "Client, Administrator")]
On top of the actionResult methods in the controllers
This worked.
Your original code was close, but the problem lies here:
base.OnAuthorization(filterContext);
Unconditionally calling the base class means you are requiring the decorated roles to be found in BOTH the UsersService and the built-in Role provider. If the role provider isn't configured to return the same set of roles (which they wouldn't if the default AuthorizeAttribute isn't sufficient for you) then this will obviously result in the Authorization test always returning false.
Instead you could add a separate property to the derived Attribute such as
public string RemoteRoles { get; set; }
and replace
List<String> requiredRoles = Roles.Split(Convert.ToChar(",")).ToList();
with:
List<String> requiredRoles = RemoteRoles.Split(Convert.ToChar(",")).ToList();
And decorate your controller like such:
[RoleAuthorization (RemoteRoles = "Client, Administrator")]
If you're using MVC 5 you have to enable lazy loading in your DbContext by putting the following line in your DbContext initialisation.
this.Configuration.LazyLoadingEnabled = true;
In MVC 5 default project you'll add it to ApplicationDbContext.cs file.
I'm not sure if this is particular to MVC 5, to Identity 2.0, or affect other versions. I'm using this setup and enabling lazy loading make all the default role schema works. See https://stackoverflow.com/a/20433316/2401947 for more info.
Additionally, if you're using ASP.NET Identity 2.0 default permission schema, you don't have to implement Application_AuthenticateRequest as Darren mentioned. But if you're using custom authorisation tables, then you have to implement it as well.

Custom Attribute redirect causes "Child actions are not allowed to perform redirect actions" error

My MVC 3 webapp has different parts which require to check whether the user is a user or an admin, they all get access to the same pages, except some pages have controllers (buttons and textboxes) that only admins can see. I do that check by putting the user's access level into my viewmodel and doing a check:
#if (Model.UserAccess != "Viewer")
{
[do something]
}
I check in my action what access the logged in user has. If the session were to timeout I redirect them to the logon page.
My action is called from a Project page view and loaded as a partial:
#{Html.RenderAction("CategoryList", "Home", new { categoryId = Model.CategoryId });}
And my Controller:
public PartialViewResult CategoryList(int categoryid = 0)
{
var useraccess = GetUseraccess();
[blablabla stuff done here]
var model = new CategoryViewModel()
{
CategoryList = categorylist
UserAccess = useraccess
};
return PartialView(model);
}
public string GetUseraccess()
{
var useraccess = "viewer"; //default
var check = SessionCheck();
if (check == "expired")
{
ModelState.AddModelError("", "Session expired. Please login again.");
Response.Redirect("/Account/LogOn");
}
else if (check == "admin")
{
useraccess = "admin";
}
return useraccess;
}
public string SessionCheck()
{
if (Session["UserAccess"] == null)
{
return "expired";
}
else if ((string)Session["UserAccess"] == "admin")
{
return "admin";
}
else // viewer
{
return "viewer";
}
}
Now that works fine. However I've been trying to implement a custom attribute that would check the session's expiration before the controller is fired:
public class CheckUserAccessSessionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var useraccess = filterContext.HttpContext.Session["UserAccess"];
if ((string)useraccess == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Account", action = "LogOn" }));
}
}
}
[CheckUserAccessSession]
public PartialViewResult CategoryList(int categoryid = 0)
{
[same stuff as above here]
}
I get the error
Exception Details: System.InvalidOperationException: Child actions are not allowed to perform redirect actions.
I understand why the error happens. But I haven't found how to go around it. Similarly I'd like to send to my action some data from another custom attribute but that also isn't working because of the RedirectToRouteResult.
Your issue here that is you're returning a PartialViewResult on that action method, and by definition the output of that action method is going to be only a portion of the full request served by IIS. You should be doing permissions checking on the action method that serves the full view in which the partial view is incorporated.
Technically when you're calling Response.Redirect in your initial implementation you're breaking far away from the ASP.NET MVC design conventions; even though it works, it's bad design.
Use this piece of code. It will work.
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
PartialViewResult result = new PartialViewResult();
result.ViewName = "noaccess";
filterContext.Result = result;
}

Resources