Modify _layout for different users - c#

In my App I have navbar with menu. In menu I have 3 dropdowns.
public
for normal users
for admins
Access is restricted but menus are visible for all. I want to hide unnecessary element for normal and public(anynymous) users.
for identyfication I'm using windows login names
To get a user role I'm asking database and query returns if user is normal user or admin.
My solution:
public bool CheckIfAdmin(string login)
{
bool admin = false;
EquipmentEntities db = new EquipmentEntities();
Tuple<string, string> credentials = GetName(login);
int RoleId = db.Users.Where(w => w.Name == credentials.Item1).Where(w => w.Surname == credentials.Item2).Select(s => s.RoleId).FirstOrDefault();
if(RoleId==1)
{
admin = true;
}
return admin;
}
and nearly same code for for checking if User
in methods:
if(CheckIfAdmin(login)){
ViewBag.Role=1;
}
else if(CheckIfUser(login)){
ViewBag.Role=2;
}
and finally in layout:
#if (ViewBag.Role==1)
{
<li class="dropdown">
Admin<b class="caret"></b>
<ul class="dropdown-menu">
//MEnu
</ul>
</li>
}
and nearly same code for second dropdown I want to hide.
This is working but I at this moment I need to put checks for role in each method. Its large amount of redundant code. Can anyone suggest me how to make it better?

I see your roles is static, because you check if(RoleId==1) so that user is admin. I think you can define roles like enum.
public enum UserRole
{
User = 1,
Manager = 2,
Admin = 3,
//SuperAdmin...etc.
}
Create base controller, add CurrentUser property. And when action execute take current user.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
CurrentUser = db.GetLoggedUserFromDatabase(); // to use in controller
ViewBag.CurrentUser = CurrentUser; // to use in views
}
public User CurrentUser { get; set; }
}
Finally your controller implements BaseController:
public class AnyController : BaseController
{
//in every action you have current user's details.
//Already you know current users role. you can use it. for example:
public ActionResult AnyAction()
{
if(CurrentUser != null) //if user logged
{
if (CurrentUser.Role == (int)UserRole.Admin)
{
//user is admin
}
}
}
}
In views you can use ViewBag.CurrentUser. Cast it first then check role as in controller.

Related

ASP.NET Core 3.1 Web Api authorization on model property level

I have a web api with basic jwt authentication and role based authorization. Now I want to restrict certain fields from being edited by users that are in the role user, because the route based authorization is not enough.
class Account {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
public bool Enabled {get; set;} // <- this field should only be editable by an admin or manager
public int RoleId {get; set;} // <- this field should only be editable by an admin
}
When the user is in the role user he is only allowed to change his email address and his password, but only for his account. When he is in the role manager he should be able to edit the fields email, password and enabled but only for accounts that are in the user role. An admin can edit every field from every user.
Is there anything that would solve my problem, for example something like this:
class Account {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
[Authorize(Roles = "Admin,Manager")]
public bool Enabled {get; set;} // <- this field should only be editable by an admin or manager
[Authorize(Roles = "Admin")]
public int RoleId {get; set;} // <- this field should only be editable by an admin
}
More infos about my project:
- ASP.NET Core 3.1
- I use Entity Framework Core with a Postgres database
- For authentication I use basic jwt bearer authentication
So, I think you has incorrect understanding of Authtorize working.
This attribute uses for Controllers. You can create multiple controllers and set for each method different ROLES to specify what Roles can call this method.
It's not correct to specify it on Dto (Data Transfer Objects) classes.
But you can make some interesting solution with 2 controllers and inheritance.
//Account dto for edit
class AccountEditDto {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
}
//Controller to edit account
[Route("all/account_controller")]
public class AccountController : Controller
{
public ActionResult EditAccount(AccountEditDto accountDto)
{
//do something
}
}
Then for create manager roles setup something like this :
//Account dto for edit
class AccountManagerEditDto : AccountEditDto {
public bool Enabled {get; set;}
}
//Controller admin to edit account
[Area("Manager")]
[Route("manager/account_controller")]
public class AccountManagerController : AccountController
{
[Authorize(Roles = "Manager")]
public ActionResult EditAccount(AccountManagerEditDto accountDto)
{
//Do something
}
}
Then for create admin roles setup something like this :
//Account dto for edit
class AccountAdminEditDto : AccountManagerEditDto {
public int RoleId {get; set;}
}
//Controller admin to edit account
[Area("Admin")]
[Route("admin/account_controller")]
public class AccountAdminController : AccountController
{
[Authorize(Roles = "Admin")]
public ActionResult EditAccount(AccountAdminEditDto accountDtp)
{
//Do something
}
}
Then you can use than pattern of URL for call controller methods:
http://localhost/{role}/accont_controller/edit
jwt is token based, meaning that every user has it's unique token which he uses in order to access your system.
The easiest way for you to achieve this is to decode the token and check for the roles, thus enabling you to set role specific action for users.
You will not be able to do this via attributes because jwt does not support them.
There is a good guide to walk you through it as it will be too long for an answer here:
Role based JWT tokens
It requires basic understanding of how tokens work and the guide provides brief explanation.
Although I'm not really Fan of this answer but you might you need this answer duo to a internal policy on your workplace.
Disclaimer:: this question is treated as business rule validation because there is many ways technically to sperate them in a way that make since.
For me I like the First answer which made a very good segregation of the Apis....
Although this is the think you are looking for you MUST keep them in one api: :
[System.AttributeUsage(System.AttributeTargets.Property, Inherited = true)]
public class AuthorizeEdit : ValidationAttribute
{
private readonly string _role;
private IHttpContextAccessor _httpContextAccessor;
public AuthorizeEdit(string role) : base()
{
_role = role;
}
public override bool IsValid(object value)
{
return _httpContextAccessor.HttpContext.User?.IsInRole(_role) ?? false;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
_httpContextAccessor = (IHttpContextAccessor)validationContext
.GetService(typeof(IHttpContextAccessor));
return base.IsValid(value, validationContext);
}
}
and you can use it like this :
class Account {
public int Id {get; set;}
public string Email {get; set;}
public string Password {get; set;}
public bool Enabled {get; set;} // <- this field should only be editable by an admin or manager
[Authorize(Roles = "Admin")]
public int RoleId {get; set;} // <- this field should only be editable by an admin
}
THE DOWNSIDE : Any one whos not in role in the validator will not be able to change any failed what so ever
My recommendation: separate your Apis like the first answer suggest then add this filter to the admin api
Note you can change the filter to take as roles as u want
Unfortunately, this is not possible as it isn't something built-in to ASP.NET Core 3.1.
However, why not carry out your logic in the handler?
You can either create multiple endpoints for users (which I wouldn't recommend) or as per common convention, use 1 route and just validate the data based on the user's role before processing the data.
Get the current account, check to see what has changed and if the user has changed a property which they should have no permission to change, return HTTP 403 Forbidden without further processing their request.
If they have the right role for the action, continue as normal.
You can inherit AuthorizeAttribute and write your own like so:
public class myAuthorizationAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// do any stuff here
// it will be invoked when the decorated method is called
if (CheckAuthorization(actionContext))
return true; // authorized
else
return false; // not authorized
}
private bool CheckAuthorization(HttpActionContext actionContext)
{
bool isAuthorized = ... // determine here if user is authorized
if (!isAuthorized) return false;
var controller = (myController)actionContext.ControllerContext;
// add those boolean properties to myController
controller.isEnabledReadOnly = ... // determine here if user has the role
controller.isRoleIdReadOnly = ... // determine here if user has the role
return true;
}
}
In the function CheckAuthorization you can check roles available and then set flags in your code to decide whether the related fields should be allowed written to or not.
It can be simply used on any method like:
[myAuthorization]
public HttpResponseMessage Post(string id)
{
// ... your code goes here
response = new HttpResponseMessage(HttpStatusCode.OK); // return OK status
return response;
}
Since you've added the properties isEnabledReadOnly and isRoleIdReadOnly to your controller, you can directly access them in any (Get, Post, ...) method.
Note: You can use the actionContext to access Request, Response, ControllerContext etc (see here).
More information about the internals (how this works) can be found here.
You can do in an action. You sould code like this.
public class AccountController : Controller
{
[HttpPost("EditProfile")]
public async Task<IActionResult> EditProfile(User user)
{
var fields = new List<string>();
fields.Add("Id");
fields.Add("Email");
fields.Add("Password");
if (User.IsInRole("Admin")){
fields.Add("RoleId");
fields.Add("Enabled ");
}else if(User.IsInRole("Manager"))){
fields.Add("Enabled ");
}
var updateUser = context.Entry(user);
foreach (var field in fields)
{
updateUser.Property(field).IsModified = true;
}
await context.SaveChangesAsync();
}
}
Well, I would personally suggest using #TemaTre's answer but since you ask. I could mention another possibility other than using a custom serializer. Which is to use a custom model binder.
You need a few basic steps
Define a custom attribute
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class AuthorizePropertyAttribute : Attribute
{
readonly string roles;
public AuthorizePropertyAttribute(string roles)
{
this.roles = roles;
}
public string Roles
{
get { return roles; }
}
}
Annotate your model with that attribute
public class Account
{
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
[AuthorizeProperty("Admin,Manager")]
public bool Enabled { get; set; } // <- this field should only be editable by an admin or manager
[AuthorizeProperty("Admin")]
public int RoleId { get; set; } // <- this field should only be editable by an admin
}
Define a custom ModelBinder
public class AuthorizedModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using var reader = new StreamReader(bindingContext.HttpContext.Request.Body);
var body = await reader.ReadToEndAsync();
var jObject = JsonConvert.DeserializeObject(body, bindingContext.ModelType); // get the posted object
var modelType = bindingContext.ModelType;
var newObject = Activator.CreateInstance(modelType); // this is for demo purpose you can get it in a way you want (like reading it from db)
var properties = modelType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
var user = bindingContext.HttpContext.User; // this is also for demo purpose only
foreach (var prop in properties)
{
var auth = prop
.GetCustomAttributes(typeof(AuthorizePropertyAttribute), true)
.OfType<AuthorizePropertyAttribute>().FirstOrDefault(); // check the property has that auth attribute
if (auth == null)
{
prop.SetValue(newObject, prop.GetValue(jObject)); // if not assign that property
}
else
{
var isInRole = auth.Roles.Split(",", StringSplitOptions.RemoveEmptyEntries).Any(user.IsInRole);
if (isInRole) // this guy has access
{
prop.SetValue(newObject, prop.GetValue(jObject));
}
}
}
}
}
And finally, if you want every Account object to go through this model binder you can annotate your class like this :
[ModelBinder(typeof(AuthorizedModelBinder))]
public class Account ...
Or you can specify it on the Action you want to use like this :
public IActionResult Sup([ModelBinder(typeof(AuthorizedModelBinder))]Account ACC)...

ASP.NET MVC 5 create global variable from current user email

My current site uses two databases, one that stores the users directly and another one that stores additional data using the user email. I need to extract data (an identification number) from this other table, so I have created a model that operates like this:
ZivoyAccount.cs
namespace ZivoyPublicaciones.Models
{
public class ZivoyAccount
{
//Database for additional data
private PPublicEntities db = new PPublicEntities();
//Database with user roles
private zivoypostEntities userdb = new zivoypostEntities();
public Int64 UserIdentification {
get {
//Get the User Identity
String UserIdentity = HttpContext.Current.User.Identity.GetUserId();
var UserData = userdb.AspNetUsers.Find(UserIdentity);
//Get the User Email
var UserEmail = UserData.Email;
//Get the Data from Other Database
var ZivoyUserData = db.Users.Find(UserEmail);
return ZivoyUserData.user_gkey;
}
}
}
}
But I have been reading that this isn't the right behaviour since this variables get set for all users, and I need them to be set for each user. How could I accomplish this?
I'm calling my model on a father controller so that I can use this variable on all my child controller
AuthorizationController.cs
namespace ZivoyPublicaciones.Controllers
{
[Authorize]
public class AuthorizationController : Controller
{
protected ZivoyAccount UserProfiler = new ZivoyAccount();
}
}

How to hide links from users that aren't part of AD Group

I am using VS 2013 and working on a MVC Web Application. It uses Windows Authentication. I wrote a bool method that checks for users in a specific AD group.
string user = User.Identity.Name;
PrincipalContext context = new PrincipalContext(ContextType.Domain, "DOMAINNAME");
if(user.IsMemberOf(ctx, IdentityType.SamAccountName, "GroupName")){
return true;
}
else
return false;
However, I am not able to use this method from my View since its not an extension method.
What I want to do is, check for user is in group and then hide the specific link if he/she isn't in that group.
Any suggestions?
Why not have a property in your model:
public bool IsAuthorized { get; set; }
In the controller, set the property in your action method before returning the model:
YourModel model = ...
string user = User.Identity.Name;
PrincipalContext context = new PrincipalContext(ContextType.Domain, "DOMAINNAME");
model.IsAuthorized = user.IsMemberOf(ctx, IdentityType.SamAccountName, "GroupName");
return View(model);
Then in the view, simply hide the link if it's not authorized
#if (model.IsAuthorized)
{
#Html.Action(...) // or whatever your link is
}

Custom page access security with MVC 4

I have a system where all pages (views) and all controls (buttons, links, menu itens ...) have security roles applied to them.
So I have an admin interface where all pages and controls are registered. And each user has a set of individual permissions.
So, for example:
I have a View EditCar, with 3 buttons: "New", "Delete" and "Back".
So the user X have permission to see View EditCar, and only the button "Back"
So each new view must be registered, and the users associated with. There is no roles, because each user is 100% configurable.
So, I have a FilterAttribute:
public class CustomAuthorize : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
var userPermissions = repository.GetAll().Where(x => x.Name.Equals(User.Identity.Name);
// if (!userPermissions.Pages.Any(x => x.NamePage.Contains(???))))
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
So my question is :
- What should I keep in database to identify each View(Action) ? Maybe 3 values? Area-Controller-Action?
Is it the best option? Any other idea about that solution?
Thanks
I have same scenario in my web-application and it is working in the following way:
we have in database:
Permission contains View, Add, Edit, Delete
Feature contains all the feature which can be set over role
FeaturePermission bind the feature with permission like which feature has what permisssion
UserRole has the role of a user
RoleFeaturePermission shows that which role has what permission to allowed
Now in code I do when a user authenticate I generate the list of permission assigned to it with features then I defined an Enum like:
public enum FeatureValue
{
Custom = 1,
Schedule = 2,
Export=3
}
public enum PermissionValue
{
View = 1,
Add = 2,
Edit = 3,
Delete = 4
}
and the UserPermission static class to get authorization:
public static bool VerifyPermission(FeatureValue feature, PermissionValue permission, int id) {
return getFeaturePermissionsForReport(feature, permission, id);
}
private static bool getFeaturePermissionsForReport(FeatureValue feature, PermissionValue permission, int id) {
SessionHelper sessionHelper = new SessionHelper(null);
UserModel userModel = sessionHelper .getUser()//get user from session.
if (userModel != null && userModel.IsAuthorized == false) return false;
UserProfile userProfile = sessionHelper.Get<UserProfile> ();
if (userProfile != null && userProfile.AssignedRoleList != null) {
List<Core.Entities.FeaturePermission> featurePermission = userProfile.AssignedRoleList.SelectMany(b => b.RoleFeaturePermission).ToList();
if (featurePermission != null) {
if (featurePermission.Count(f = > f.Feature.Id == (int) feature && f.Permission.Id == (int) permission) > 0) {
bool isAllowed= false;
int featurePermissionId = featurePermission.Where(f = > f.Feature.Id == (int) feature && f.Permission.Id == (int) permission).Select(i = > i.Id).FirstOrDefault();
isAllowed = (reports.Count(r = > (r.FeaturePermissionId == featurePermissionId && r.Id == id)) > 0) ? true : false;
return isAllowed;
}
}
}
return false;
}
and now one each link, button or action use:
#if (UserPermission.VerifyPermission(FeatureValue.Custom, PermissionValue.Edit))
{
//action link to edit custom view
}
and for action custom attribute is:
[AttributeUsage(AttributeTargets.All,AllowMultiple=true)]
public class CustomFeaturePermissionAttribute : ActionFilterAttribute
{
private FeatureValue[] feature;
private PermissionValue[] permission;
private bool excludeParamId;
/// <summary>
/// Set values of featurelist and permission list
/// </summary>
/// <param name="featureList"></param>
/// <param name="permissionList"></param>
public CustomFeaturePermissionAttribute(object featureList,object permissionList, int excludeParamId)
{
FeatureList = (FeatureValue[])featureList;
PermissionList = (PermissionValue[])permissionList;
ExcludeParamId = excludeParamId;
}
public FeatureValue[] FeatureList
{
get
{
return feature;
}
set
{
feature = value;
}
}
public bool ExcludeParamId
{
get
{
return excludeParamId;
}
set
{
excludeParamId = value;
}
}
public PermissionValue[] PermissionList
{
get
{
return permission;
}
set
{
permission = value;
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
bool isAccessAllowed = false;
FeatureValue feature;
PermissionValue permission;
for (int i = 0; i < FeatureList.Count(); i++)
{
feature = FeatureList[i];
permission = PermissionList[i];
isAccessAllowed = UserPermission.VerifyPermission(feature, permission, Convert.ToInt16(ExcludeParamId));
if (isAccessAllowed)
break;
}
if (!isAccessAllowed)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "UnauthorizedAccess", controller = "Security" }));
}
}
}
and on actions allow role having view permission over Custom and Export:
[CustomFeaturePermission(new FeatureValue[] { FeatureValue.Custom, FeatureValue.Export }, new PermissionValue[] { PermissionValue.View, PermissionValue.View},pageId)]
public ActionResult Custom()
{
//action body
}
I would create an abstract way of defining each permission, such as an enum. For example:
public enum UserPermissions
{
ViewCars,
EditCars,
DeleteCars,
ViewUsers,
EditUsers,
DeleteUsers
}
You could create these in the database in a table called Permissions, then create a many-to-many mapping where each user can be assigned to any number of permissions.
Then you would create a custom authorization attribute by deriving from AuthorizeAttribute and override the OnAuthorization method to load the user from the database. This is exactly what you have done in your question except the key part is that you want to add some property where you can define the permission(s) needed for an action, like so:
public class UserPermissionsAttribute : AuthorizeAttribute
{
public IEnumerable<UserPermissions> PermissionsRequired { get; set; }
public UserPermissionsAttribute()
{
}
public UserPermissionsAttribute(params UserPermissions[] permissionsRequired)
{
PermissionsRequired = permissionsRequired;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = filterContext.HttpContext.User; // get user from DB
if (PermissionsRequired.All(x => user.Permissions.Any(y => x == y)))
{
// all permissions are met
base.OnAuthorization(filterContext);
}
else
{
throw new UnauthorizedAccessException();
}
base.OnAuthorization(filterContext);
}
}
Now you can decorate each action or controller with a permission or list of permissions:
[UserPermissions(UserPermissions.ViewCars, UserPermissions.EditCars)]
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
This way you separate your permission system from MVC controller/action logic.
Although I'd advise against this method of storing each permission on an individual basis. The role system keeps things much simpler and will improve performance. I really think you could do this with a number of fine-grained roles instead of fine-grained permissions.
Note that Authorizing users to see specific page elements differs from Authorizing for CRUD or other database operations, unless the elements point to operational Actions in Controller. Consider that you may have some elements that there's no need to be saw by a specific user, and don't have specific database operation. Till now we conclude that we need the following permissions :
Permission to See
Permission to Command
I believe that you can use Microsoft Role Provider for both parts. According to MSDN Documentation Considering that :
The Authorize attribute lets you indicate that authorization is
restricted to predefined roles or to individual users. This gives you
a high degree of control over who is authorized to view any page on
the site.
In The next step/question is how to do that?
I think 3 ways are available to meet our purpose:
Solution 1: Creating separate Views with specific page elements due to forwarding each user to related View. In this scenario we must
create separate controller actions too. we have to check user types
before each action like [Authorise(Roles="Administrator")]. We
forced to have static (Pre-defined) Roles and Accessibility. And in
one sentence Not a good solution because of redundancy and
instability.
Solution 2: Creating pages Dynamically simply by adding some if conditions for each access restricted element in One Page(for
example Edit Page). That is like employing #if
(User.IsInRole("Admin")) to authorize specific users and show
related page elements like buttons. In Controller side we can use
if conditions (not as FilterAttribute due to add dynamic
functionality based on generated/added new roles) and control valid
transactions against database. Although FilterAttributes add some great functionalists (like performance optimization). In one sentence A moderate solution.
Solution 3: Act like solution 2, just fix Controller problem by
creating our own custom FilterAttribute for authorization. That will
inherited from AuthorizeAttribute and overrides the OnAuthorize
method to do what you need only for Operations.
For Example :
public class TableAuthorizeAttribute : AuthorizeAttribute
{
public enum TableAction
{
Read,
Create,
Update,
Delete
}
public TableAction Action { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
//do custom authorizization using Action and getting TableEntryID
//from filterContext.HttpContext.Request.QueryString or
//filterContext.HttpContext.Request.Form
}
}
And its usage will be like this :
[TableAuthorize(Action=TableAuthorizeAttribute.TableAction.Update)]
Here is complete example about above concept. Here is complete example for creating dynamic AuthorizeAttribute for authorizing new roles added to application.
Solution 3 in one sentence A perfect but Complex Solution.
Note that by using FilterAttribute before Actions we have limited our application to static/predefined roles. No need to use another Data Structure or generate tables in Database.
I've seen a similar implementation in the past which utilized a token concept.
Each Action method is represented by a token. A selection of tokens define a role. A role is assigned to a user.
I used a simple console application to reflect my MVC application and look for all Controllers and determine every action method within them.
Store these "Tokens" in your database along with your roles.
The implementation kept it simple and just used the fully qualified name with namespaces etc to identify them. This way the data has to be specific to your application which can increase security
I would take Trevor's approach, but It wouldn't use an attribute.
I would create a common action permission enum like :
[Flags]
internal enum PermissionsEnum
{
listbutton = 1,
editbutton = 2,
deletebutton = 4,
savebutton = 8,
createbutton = 16,
action03 = 32,
action04 = 64,
action05 = 128,
action06 = 256,
action07 = 512,
action08 = 1024,
action09 = 2048,
action10 = 4096,
action11 = 8192,
action12 = 16384,
action13 = 32768
}
Such a permission object I store for every area/controller and user in the database like with some additional constraints
permission value -1 not allowed to call the action and permission value 0 to call the action but no other permissions:
Controller/Action UserId Permission
================= ====== =========
cars/delete User0001 -1
cars/edit User0001 8
cars/index User0001 0
cars/list User0001 16
cars/show User0001 2
The apply the permissions I would create a base controller. When ever an action is called, the base controller retrieves the permissions for the called controller:
var currentController = this.Url.RouteData["controller"];
var currentAction = this.Url.RouteData["action"];
var currentUserPermissons = GetUserPermissonForController(string.Format("{0}/{1}",currentController,currentAction), userId);
if( 0 > currentUserPermissons ) RedirectToAction("PermissonDenied","Error");
ViewBag.UserPermissons = (PermissionsEnum)currentUserPermissons;
In each view I would check the ViewBag.UserPermissons before create a protected item like:
#{ if((ViewBag.UserPermissons & PermissionsEnum.listbutton) == PermissionsEnum.listbutton)
{
#Html.ActionLink("Listitems","List")
}
}

Automatically binding a model property if it is present

I'm not sure i know the exact terminology for what i want to do, so i'll just describe what i want, and hopefully someone will give me the answer or direct me to the right place...
I want some (maybe all) of the models on my site to inherit from IGeneralSettings -
public interface IGeneralSettings
{
User CurrentUser { get; set; }
UserSettings PersonalSettings { get; set; }
}
and if the current user is authenticated/logged in, I will check if the current controller's model is of a type that implements IGeneralSettings, and if it is, I will fill the data with the current user data.
I am using Windsor Castle as an IoC container.
I don't want to use the property bag for this.
I don't want to call a method that will do this in each action in each controller, I want this to be injected automatically each request - meaning this code will be written once.
Is this possible ?
I would recommend adding your CurrentUser and PersonalSettings objects to a base controller and populating it each request (if they are authenticated). Then you can access them in a custom model binder because you have access to the controller context.
Example of one way to do it:
Base controller that your controllers inherit
public class BaseController : Controller
{
public BaseController()
{
//You can load the CurrentUser and Settings here
CurrentUser = new User
{
Id = 1
};
PersonalSettings = new UserSettings
{
Id = 1
};
}
public User CurrentUser { get; set; }
public UserSettings PersonalSettings { get; set; }
}
ModelBinder
public class ThingModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var model = (Thing)base.BindModel(controllerContext, bindingContext);
var baseController = controllerContext.Controller as BaseController;
if (baseController != null)
{
model.CurrentUser = baseController.CurrentUser;
model.PersonalSettings = baseController.PersonalSettings;
}
return model;
}
}
Add ModelBinder in Application_Start of Global.asax
ModelBinders.Binders.Add(typeof(Thing), new ThingModelBinder());
Controller
public class HomeController : BaseController
{
[HttpPost]
public ActionResult Save(Thing model)
{
//model has CurrentUser and PersonalSettings set
//without any values being posted.
//You can also access the values on the BaseController
//if you don't want to automatically bind it.
return View();
}
}
Another way I found in my last project:
Define a extension method for Controller, like:
public static Models.User GetUser(this Controller controller)
{
string name = controller.User.Identity.Name;
string alias = name.Substring(name.LastIndexOf('\\') + 1);
string domain = name.Substring(0, name.IndexOf('\\') - 1);
User user = User.ByAlias(alias);
if (user == null)
{
user = new User();
user.Name = controller.User.Identity.Name;
user.Alias = alias;
user.Domain = domain;
user.Description = "Automatically registered";
Guid userId = Models.User.Create(user, Guid.Empty);
user = User.ByAlias(alias);
}
return user;
}
That would not break your current class structure.

Categories

Resources