I would like to display the current user in a custom view, which most of my ContentPage's contain. I currently store the current user in the App instance as a property, after login. Attempting to just update the Label in the constructor is too early in the lifecycle.
Is there a way with Xamarin Forms to bind to this object or otherwise get the current user to update a Label in my custom view? I am using Xamarin.Forms 3.5 with the standard MVVM.
There are a multiple approaches you could take, but the short answer is that you need something sitting between the global (static) variable and the views in order to make things work smoothly. The property on your view model must be a non-static property.
However it can have a custom implementation so that the getter retrieves the value from some global location, and in your case, you may not need a setter. The only other piece you need is to tell the view model to fire a PropertyChanged event when the user information is available, then you can use standard Binding from the Label to the view model.
Assuming you have some CurrentUser static class that has members like:
public static class CurrentUser
{
public event Action OnLogin; // login code needs to fire this on login
public string Username { get; set; }
// etc.
}
Then view models would hook up to that by doing something like:
class UserViewModel : INotifyPropertyChanged
{
public UserViewModel()
{
CurrentUser.OnLogin += CurrentUser_Login;
}
private void CurrentUser_Login()
{
PropertyChanged?.Invoke(nameof(Username));
}
public string Username {
get {
return CurrentUser.Username;
}
}
// etc.
}
Then the view would use <Label Text="{Binding Username}" . . .> and then when OnLogin is fired, all views would be automatically updated with the new username.
Related
I am working on asp.net mvc 5. I want to assign permissions of controllers' action methods to roles dynamically without hard conding the roles in Authorize attribute.
Here is the scenario -
In my project, I have four roles - Student, Teacher, Program Officer and Admin.
I want that admin can change the accessability of each role whenever he wishes. I do not want to hard code the authorize attribute with role names before every action name of controller because admin will not then be able to change the permissions of each role.
I want to create a page where every action method of controller will be listed as checkbox and admin can select action checkboxes for a role. Then that role users will get accessability of those action methods.
Here I want to make the UI as following -
Can anyone please help me to do this by giving any suggestion or code or link?
Imagine you have service which returns array of roles based on controller and action name like this:
public class RoleProvider
{
public string[] Get(string controller, string action)
{
// get your roles based on the controller and the action name
// wherever you want such as db
// I hardcoded for the sake of simplicity
return new string[]{"Student", "Teacher"};
}
}
Now you can write your own authorization attribute something like this:
public class DynamicRoleAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var controller = httpContext.Request.RequestContext
.RouteData.GetRequiredString("controller");
var action = httpContext.Request.RequestContext
.RouteData.GetRequiredString("action");
// feed the roles here
Roles = string.Join("," ,_rolesProvider.Get(controller, action));
return base.AuthorizeCore(httpContext);
}
}
Now use your custom authorization attribute instead of older one like this:
[DynamicRoleAuthorize]
public ActionResult MyAction()
{
}
I think the only way is to implement your own Authorize Attribute where you can implement your own logic for authorization.
And in your case you should have a table where associate roles and controllers action and check this table in your custom Authorize Attribute.
While this does not give you the dynamics web page assignment you're looking for, If you are flexible in your approach... you can set up an Enum list of Roles Admin, Editor editor etc, and pass them as a parameter object (ENUM) as a param, so that the DynamicRoleAuthorize can use it load the roles that are allowed
from vivians blog The constructor accepts parameters of type object, that is the little trick. If you use parameters of type Enum, you will get the same error message as above. We can do that because an Enum is an object.
To ensure that we are passing parameters of type Enum, we check the type of every roles. If one role is not of type Enum, the constructor will throw an ArgumentException.
Then we set the standard Roles property with the name of our roles with the string.Join method.
using System;
using System.Linq;
using System.Web.Mvc;
namespace MvcApplication.HowTo.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeEnumAttribute : AuthorizeAttribute
{
public AuthorizeEnumAttribute(params object[] roles)
{
if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
throw new ArgumentException("roles");
this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
}
}
}
Is there a possibility to hand over the Result of the Authorize-Attribute to the View?
Let's assume I want to hide 5 links in my Index view based on the memberships of a User.
[Authorize(Roles = "Admin")]
public ActionResult Index(){
....
}
The code above will prevent all users that are not part of the Admin-Group from visiting the Index page.
#{
if(User.IsInRole("Admin"){
Some link to be hidden
}
}
This code will hide the link if the User is not part of the Admin role. This is basically what I want BUT using this method I have to change the role name on every hidden link if the role would change.
Isn't there something like a combination of both? (Schema see below)
[Authorize(Roles = "Admin")] //This will pass true to the View if the User is a member of the group "Admin"
public ActionResult Index(){
....
}
#{
if(User.IsAuthenticated){ //This will read the "Token" and if it's true the if statement will get executed.
Some link to be hidden
}
}
So - if the User is in Role "Admin" the link will be shown. Is this possible?
You could use ViewBag and ViewData among other things, but I'd suggest passing a model back to the view with properties indicating whether to display the links or not.
public class YourViewModel()
{
public bool ShowHiddenLinks { get; set; }
// ... whatever other properties
}
In your controller you'd then do:
[Authorize(Roles = "Admin")]
public ActionResult Index()
{
var yourVm = new YourViewModel();
yourVm.ShowHiddenLinks = true;
return View(yourVm);
}
And your view becomes:
#model YourViewModel
/* ShowHiddenLinks is true & this view is meant for admins only,
so show admin-related links */
#if (Model.ShowHiddenLinks)
{
Some link to be hidden
}
I've named the viewmodel property ShowHiddenLinks on purpose, so that it becomes re-usable for views meant for other users as well. You can of course extend the viewmodel to feature properties for other roles (e.g. a view which is accessible by admins and moderators, each with their own distinct set of hidden links), or create one viewmodel per roleāit all depends on the scenario.
I am aware simple role provider in which if i need to restrict particular action i have to simply write Authorize(Roles = "Admin") or if i need to restrict particular part of view i nned to write #if(User.IsInRole("Admin")).
But my question is that what if my roles are not fixed and it is stored in database and my super admin can able to edit and delete them.
My requirement is that superadmin can add,update,delete roles and also create different users and maintain the roles of those users.
I have done lot of googling and found something as follows
[AttributeUsage (AttributeTargets.Method|AttributeTargets.Class,Inherited = true,AllowMultiple=true) ]
public class CustomRole : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase context)
{
Respository db = new Respository();
if (db.UserMasters.Where(x => x.user_name == context.User.Identity.Name).Count() > 0)
{
return true;
}
else { return false; }
}
}
Here i can use this code to authorize action method as follows
[CustomRole]
public ActionResult Details(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
Here my this action method is protected but what if i want to protect some part of view by this custom method. How to use this functionality to achieve functionality as User.IsInRole("Admin")?
your requirement will get in 3 steps
1- Create all default roles, store it in database.i.e- roleid,rolename
2- When creating new user map userid with roleid.
3- also make one table for all permission which you have to give.
4- make seperate ui for admin to change the roles of each user.
database will be like below image.
and ui will be like this.
try this yousrelf..
Fully answering your question might be out of scope for StackOverflow, since it would basically require writing most of an application for you, but here's the general idea.
Write a helper class similar to this:
public class ModuleHelper
{
public static bool UserCanAccessModule(string moduleIdentifier)
{
bool canAccess = false;
/*
Call into your service with current User.Name and module identifier (integer, GUID, whatever).
Return result of whether user has the required role for the specified module
*/
try
{
canAccess = service.CanUserAccessModule(User.Identity.Name, moduleIdentifier);
}
catch
{
// catching all exceptions, since this is a UI helper
}
return canAccess;
}
// etcetera...
}
I'd suggest wrapping it in the root namespace of your application; otherwise, add a reference to this class's namespace in the system.web.webPages.razor section of the web.config in the Views folder. Then, you can do something like:
<div class="col-sm-3 col-md-2 sidebar">
#if (ModuleHelper.UserCanAccessModule("moduleXYZ"))
{
#Html.Action("moduleXYZ")
}
</div>
This obviously assumes a lot, but the idea isn't new or all that complicated in practice. The logic of the service is relatively simple:
Look up the user
Look up the "action" or "module"
Look for intersection (if any) between the roles assigned to each.
No intersection means user doesn't have the required role.
Tieson T. has a great answer to your question already, so what I'll provide here is an alternative method if you wanted to keep all of your authorization steps all in controllers.
Consider separating the different aspects (or restricted parts) of your main view into a partial view (or views) that perform the restricted functionality. Then, instead of using: #Html.RenderPartial("ViewName", Model) you can set up your partials to be returned from controller actions decorated with the ChildActionOnly Attribute by using the RenderAction Html Helper.
For example:
<div class="col-sm-3 col-md-2 sidebar">
#Html.RenderAction("RestrictedContent")
</div>
Then in your controller class
public class RestrictedController : Controller {
public RestrictedController() : base() {
}
[ChildActionOnly()]
[CustomRole()]
public ActionResult RestrictedContent() {
return PartialView("RestrictedPartial");
} // end action RestrictedContent
} // end class
The only consideration with this approach will be in your custom attribute to interrogate the the IsChildAction property to avoid rendering a redirect or whatever your attribute does in the case the user is not authorized since you'll probably want to just not render anything.
For Example (in your custom attribute class):
public override void OnAuthorization(AuthorizationContext filterContext) {
if(filterContext.IsChildAction) {
filterContext.Result = new EmptyResult(); // return an empty result instead of performing a redirect.
} else {
base.OnAuthorization(filterContext); // continue with custom authorization if it is not a child action
} // end if/else
} // end method OnAuthorization
Phil Haack has an article describing the usage of the RenderAction method here: http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx/
Also, see here for an interesting discussion on the differences between Action and RenderAction. The difference between Html.Action and Html.RenderAction
I come from "regular" asp.net so i'm a bit (totally) lost with MVC.
What I was doing with my own asp.net programmation pattern :
I have one custom class objet which represent the "page" and its properties (like mypage.loadJquery, mypage.isLogged, mypage.Title, custom cache logic, etc.)
This class is instanciate once on top of each ASHX page, I then manipulate a stringbuilder to produce HTML and spit it right at the browser at the end.
Having only one request on the ASHX page, i can use my page object instanciated at the top till the end when calling final response.write
Now i'm trying to go for MVC. I "kind of" understood the M/V/C model and the routing concept. I would like to keep my custom "page" object but I lost my page life cycle and I definitely don't know how to instanciate my page object ONCE in at the top of every call.
I need this instanciated ONCE shared object across every models, controllers, views, partial views, htmlhelper...
I realize MVC pattern might be confusing for me at this moment, bu how could I try to reproduce my need ?
(Very concrete exemple : On every request i need to check if the user is logged via his cookies. If it is I round trip the database to get user infos. Then I DO NEED THESE INFOS ON PRATICALLY EVERY model / controller / view of the app, but of course don't want to round back each time to security check and database querying, how can i have these info on the whole mvc cyle ?)
In my project I create interface IViewModel that contains all fields that I need in my layout/masterpage and set is as model of it so I can easily use them:
IViewModel.cs
public interface IViewModel
{
string Title { get; set; }
User User { get; set; }
}
Layout.cshtml
#model IViewModel
<html>
<head>
<title>#Model.Title</title>
</head>
<body>
#if (Model.User.IsAuthenticated) {
You are logged as #Model.User.Name
}
</body>
</html>
All my models implement that interface (or inherit from ViewModelBase that is default implementation of that class). Additionally I have custom action filter that check if returned ActionResult is (Partial)ViewResult and if Model of it implement my IViewModel interface and fill data in that interface.
public FillViewModelAttribute : ActionFilterAttribute
{
public override OnActionExecuted(ActionExecutedContext context)
{
var viewResult = context.Result as ViewResult;
if (viewResult != null && viewResult.Model is IViewModel)
{
var viewModel = (IViewModel)viewResult.Model;
// fill data
}
}
}
I created many projects like this. Basically, you can create a base controller class where all the other controllers inherit from it.
[Authorize]
public class BaseController : Controller
{
private Instructor _G_USER = null;
protected Instructor G_USER
{
get
{
if (_G_USER == null)
{
_G_USER = Your DB query here
ViewData["G_USER"] = _G_USER;
}
return _G_USER;
}
}
}
Then in your every child class, you can do
[Authorize]
public class YourController : BaseController
{
public ActionResult Index()
{
if(!G_USER.CAN_DO_THIS) throw new NoPermissionException();
return View();
}
}
To use the User in the view, create an extension method.
public static class ExtentionMethods
{
public static USER G_USER(this ViewPage page)
{
return (USER)page.ViewData["G_USER"];
}
}
Then use in the page like this
<%=this.G_USER().....%>
Hi I have a question about model binding. Imagine you have an existing database entity displayed in a form and you'd like to edit some details, some properties eg createddate etc are not bound to the form, during model binding, these properties are not assigned to the model as they are not on the http post data or querystrong etc, hence their properties are null. In my controller method for update , Id just like to do
public ActionResult Update( Entity ent)
{
//Save changes to db
}
but as some properties are null in ent, they override the existing database fields which are not part of the form post data, What is the correct way to handle this? Ive tried hidden fields to hold the data, but model binding does not seem to assign hidden fields to the model. Any suggestions would be appreciated
You shouldn't be sending your entity to your view, you should send a slim version of it called a DTO (data transfer object) or ViewModel.
Only send the properties to the view (and hence the form that gets posted back) that you want the user to update with that action.
In your POST handler you can then validate and copy the properties across to your entity.
an AutoMapper can help if you have lots of properties
something like:
public class User
{
int id;
string name;
string email;
}
public class EditUserEmailDto
{
string email;
}
// get
public ActionResult EditEmail(int id,)
{
return View("EditEmail", new EditUserEmailDto());
}
// post
public ActionResult EditEmail(int id, EditUserEmailDto dto)
{
if(!ModelState.IsValid)
return View("EditEmail", dto);
var user = userRepo.Get(id);
user.email = dto.email;
userRepo.Save(user);
return;
}