Implementing logic to provide role based viewdata - c#

I have been thinking about this for the past few days and was wondering what everyone else's take is on it. I want to implement some content that is based on what role the user belongs to. Our applications uses HTTP headers to receive role information. In MVC should I have the controller handle the role detection, and then pass a boolean value into the viewmodel.
Viewmodel:
public class ProductViewModel
{
public Product MyProduct { get; set; }
public bool IsAdmin { get; set; }
public bool IsSuperAdmin { get; set; }
}
Controller:
public class ProductController : Controller
{
public ActionResult Info()
{
//get product information here
ProductViewModel pvm = new pvm();
pvm.Product = theProduct;
pvm.IsAdmin = checkAdminAccess(); //This would return a bool if they had access
pvm.IsSuperAdmin = checkSuperAdminAccess();//This would return a bool if they had access
return View(pvm);
}
}
View:
#if(Model.IsAdmin == true || Model.IsSuperAdmin == true)
{
//Render Content for admin (Either directly or via Partial View)
}
#if(Model.IsSuperAdmin == true)
{
//Render Superadmin content (Either directly or via Partial View)
}
//Implement other Product Stuff Here
Or would it be better to create a partial view and handle the logic in the view. This seems like there is less work involved.
#if (Request.Headers.AllKeys.Contains("role")){
ViewBag.Role = Request.Headers["role"].ToString();
}
...
#if(ViewBag.Role == "Admin" || ViewBag.Role == "SuperAdmin")
{
#Html.Partial("AdminPartial")//Or Render Code Directly?
if(ViewBag.Role == "SuperAdmin")
{
#Html.Partial("SuperAdminPartial")//Or Render Code Directly?
}
}
//Implement other Product Stuff Here
Is there any added benefits to doing it one way or another, should I be rendering the content directly or using a partial view or does it not matter? Or am I missing some other way that this should be done?

You can use the AuthorizeAttribute to restrict access/use of controller actions:
[Authorize(Roles = "Admin, Customer")]
public class ProductController : Controller
{
public ActionResult Info()
{
//get product information here
ProductViewModel pvm = new pvm();
return View(pvm);
}
[Authorize(Roles = "Admin")]
public ActionResult EditInfo()
{
//get product information here
EditProductViewModel epvm = new epvm();
return View(epvm);
}
}
This is the most basic way to restrict access by callers to an action method. However, with a large application with many roles/controllers/actions, annotating and keeping track of all attributes can become difficult.
There is product called Fluent Security that allows security to be specified in a central place, and gets rid of the issue mentioned previously.
There is also the issue of the single responsibility principle, look at Darin's answer to this question - he explains it quite well.

Related

Writing ModelState valiidation code as it's own method in a controller so it can be called by multiple ActionResults

Good morning,
I would consider myself a little better than a beginner with ASP.NET, but I have a problem that I need help with that I feel should be fairly simple, but cannot get to work. I'm currently building a website and am looking to implement some validation rules when submitting a form. I know that I can write the validation directly for each page I'm creating within it's corresponding ActionResult method, but I would much rather write the validation code once as it's own method within the controller and call the method within each ActionResult (for example, my ActionResult Create, ActionResult Edit, ActionResult Review) in the controller. I want to do this to keep the code more simple and readable, especially because the validation code that I'm writing is about 500 lines long.
Here's just a portion of the code that I'm using:
if (ModelState.IsValidField("Name") && customerDatabase.Name== null)
{
ModelState.AddModelError("Name", "The customer's name is required.");
}
if (ModelState.IsValidField("AccountNumber") && customerDatabase.AccountNumber.Length != 10)
{
ModelState.AddModelError("AccountNumber", "The customer's account number must be 10 digits long.");
}
if (ModelState.IsValidField("Address") && customerDatabase.Address == null)
{
ModelState.AddModelError("Address", "The customer's address is required.");
}
Thank you in advance for your help!
Consider using validation attributes for the simple validations on the model
In MVC, validation happens on both the client and server.
Fortunately, .NET has abstracted validation into validation attributes. These attributes contain validation code, thereby reducing the amount of code you must write.
for example
public class CustomerDatabase {
[Required]
public string Name { get; set; }
[StringLength(10)]
public string AccountNumber { get; set; }
[Required]
public string Address { get; set; }
//...other properties
}
This will also allow client side validation in a MVC View
It will reduce the amount of repeated validation needed on the server
if(ModelState.IsValid) {
//...
}
return View(customerDatabase);
Another alternative could be to create a custom action filter to apply validation checks
public class ValidateCustomerModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext context) {
var ModelState = context.Controller.ViewData.ModelState;
var customerDatabase = (MyModel)ModelState.Value; //cast to expected model type
if (ModelState.IsValidField("Name") && customerDatabase.Name== null) {
ModelState.AddModelError("Name", "The customer's name is required.");
}
if (ModelState.IsValidField("AccountNumber") && customerDatabase.AccountNumber.Length != 10) {
ModelState.AddModelError("AccountNumber", "The customer's account number must be 10 digits long.");
}
if (ModelState.IsValidField("Address") && customerDatabase.Address == null) {
ModelState.AddModelError("Address", "The customer's address is required.");
}
//...
}
}
and use it on the desired actions
[ValidateCustomerModel]
[HttpPost]
public ActionResult Create(MyModel customerDatabase) {
if(ModelState.IsValid) {
//...
}
return View(customerDatabase);
}
Reference Model validation in ASP.NET Core MVC

NETCORE MVC - How to work with nested, multi-parameterized routes

Looking for best practices when working with nested routes in .NET Core MVC.
Let's say CampusController.cs works with a base model:
[Route("api/campus/")]
public class CampusController : Controller
{
...
[HttpGet]
[Route("{campusId}")]
public IActionResult GetCampusInfo ([FromQuery]int campusId) { ... }
}
And BuildingController.cs works with a child model:
[Route("api/campus/{campusId}/building")]
public class BuildingController : Controller
{
...
[HttpGet]
[Route("{buildingId}")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
[Route("{buildingId}/")]
public IActionResult GetBuilding ([FromQuery]int buildingId) { ... }
....
(more Action Methods)
}
If buildingId maps directly to the database it could retrieved even if the provided campusId isn't the parent. To keep the URL clean when calling /api/campus/{campusId}/building/{buildingId} I'd like to validate {campusId} and return a 4xx coded IActionResult if it's invalid. There has to be a better way than including validation logic in every Action Method inside BuildingController.
Is there a way to cascade multiple Action methods on different controllers? So that a validation method on CampusController would be called first and in turn call a method onBuildingController?
Is there a way to have a controller-level verification of campusId that could short circuit and return a ActionResult if validation fails?
EDIT: When I refer to validation logic I mean API signals; not the business-logic that actually determines if campusId is/isn't valid.
Thanks in advance!
If using placeholder in the route prefix you would also need to include it in the action itself
[Route("api/campus/{campusId:int}/building")]
public class BuildingController : Controller {
//...
[HttpGet]
[Route("{buildingId:int}")] // Matches GET api/campus/123/building/456
public IActionResult GetBuilding ([FromRoute]int campusId, [FromRoute]int buildingId) {
//... validate campus id along with building id
}
}
If concerned about repeated code for validation then create a base controller for campus related request and have a shared validation method.
Another option is to have a service/repository that can be used to verify campus id and its relation to the provided building id if needed.
It sounds like you want your users to provide a campusId when talking to the BuildingController, and your BuildingController to validate campusId in a DRY kind of way.
If that's the case, you can create an input model for your BuildingController methods:
public class BuildingIdInput
{
[Required]
public int? CampusId { get; set; }
[Required]
public int? BuildingId { get; set; }
}
Then you can let MVC bind user input to this model.
[Route("api/campus")]
public class BuildingController : Controller
{
[HttpGet]
[Route("{campusId}/building/{buildingId}")]
public IActionResult GetBuilding (BuildingIdInput input)
{
if (ModelState.IsValid)
{...}
}
}

How to use asynchronous ViewModel in every controller action?

I'm new to asp.net mvc (5) and I am facing an issue on my website.
Basically, all aspnet_users are linked to my specific user table via the guid.
I have a BaseController class with a UnitOfWork and a ViewModelBase :
public class BaseController : Controller
{
protected UnitOfWork UnitOfWork { get; private set; }
public ViewModelBase ViewModel { get; set; }
}
(Extract of) the ViewModelBase class, containing the information needed in the layout page :
public class ViewModelBase
{
public User User { get; set; }
public bool IsAuthentified { get; set; }
public bool Loaded { get; set; }
}
And the layout which uses it :
#model Project.ViewModels.ViewModelBase
<html>
<body>
Some very cool layout stuff related to the User in the ViewModelBase
</body>
</html>
Here is an example of use with the HomeController :
public class HomeController : BaseController
{
private new HomeViewModel ViewModel
{
get { return (HomeViewModel)base.ViewModel; }
}
public HomeController()
{
ViewModel = new HomeViewModel();
}
public ActionResult Index()
{
// I need to get the MembershipUser here to retrieve the related User and set it in the ViewModel
return View(ViewModel);
}
}
The HomeViewModel :
public class HomeViewModel : ViewModelBase
{
public string HomeSpecificProperty { get; set; }
}
And the view :
#model Project.ViewModels.HomeViewModel
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/Layout.cshtml";
}
Welcome #Model.User.UserName !
<br />
#Model.HomeSpecificProperty
And here is my problem. The thing is all the pages have a viewmodel inherited from ViewModelBase, since it is used in the layout, but I don't know where nor how to set the User property in it.
Since Membership is used to retrieve the User it has to be in an Action, I can't do this in the ViewModelBase constructor.
Thus I added this code in the BaseController, which sets the ViewModelBase properties on the first get :
private ViewModelBase viewModel;
protected ViewModelBase ViewModel
{
get
{
if (!viewModel.Loaded)
LoadBaseContext();
return viewModel;
}
set { viewModel = value; }
}
private void LoadBaseContext()
{
viewModel.IsAuthentified = HttpContext.User.Identity.IsAuthenticated;
if (viewModel.IsAuthentified)
{
MembershipUser user = Membership.GetUser();
viewModel.Player = UnitOfWork.UserRepo.Get((Guid)user.ProviderUserKey);
}
viewModel.Loaded = true;
}
Might not be very beautiful, but works. However, since there is some database acesses in it (notably to get the User), I thought I should put the LoadBaseContext function async.
I tried, and since all actions use ViewModelBase I put every action async too. But then #Html.ActionLink doesn't work anymore since the action called is async.
Finally, my question is : Is this ViewModelBase and User property the right way to do ? If it is, should the LoadBaseContext be async ? Then, how to make it work with the actions ?
Thanks a lot.
UPDATE
Extract of the layout :
#if (!Model.IsAuthentified)
{
#Html.Action("Index", "Authentification", null) // display login partial view
}
else
{
#Html.Action("Index", "UserInfos", null) // display userinfos partial view
}
Authentification controller Index's action :
public ActionResult Index()
{
ViewModel = new AuthentificationViewModel();
// loads the ViewModelBase properties here
return PartialView("~/Views/Shared/Partials/Login.cshtml", ViewModel);
}
If you have something that you always want in all of your views, you could either ensure that every view takes a view model that includes or extends that core set of data, or you could simply put the data in the ViewBag.
To do the latter, you could override OnActionExecuting in your base controller, and set the ViewBag contents there.
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
this.ViewBag["IsAuthenticated"] = this.Request.IsAuthenticated;
// ...
}
If you want async behaviour, then you might be able to adapt this post to your needs.
To do this without overriding OnActionExecuting, you could put an async method in your controller base class that sets the data in the ViewBag:
protected async virtual Task PopulateViewBag()
{
this.ViewBag["foo"] = await MyAsyncMethod();
// ...
}
... and then simply call this from each and every one of your controller actions:
public async Task<ActionResult> MyAction()
{
await this.PopulateViewBag();
// ... more code
}
It would be a bit tedious, though.
One final word: depending on how many users you expect to have etc. it may be easier to get the user information once, and then cache it (e.g. in the Session), rather than repeatedly get it during every request. Presumably most of the user info isn't going to change between requests...

Make a complex typed property required in an MVC4 form

I can't figure out how to "customize" the rules for the [Required] attribute when I stick it to a custom typed property. Code looks like this:
public class MyProp
{
public Guid Id {get;set;}
public string Target {get;set;}
}
public class MyType : IValidatableObject
{
public string Name {get;set;}
public MyProp Value {get;set;}
private MyType()
{
this.Name = string.Empty;
this.Value = new MyProp { Id = Guid.Empty, Target = string.Empty };
}
public MyType(Guid id) : this()
{
this.Value.Id = id;
// Fill rest of data through magic
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(this.Value.Id == Guid.Empty)
yield return new ValidationResult("You must fill the property");
}
}
This model shows up in forms (through its own EditorTemplate) as a textbox with a button which allows for selection from a list (the backing data is a Dynamics CRM 2011 Environment, and this model is actually aimed to represent a lookup attribute).
public class MyModel
{
// Many props
[Required] // This one is enforced correctly
public string MyString {get;set;}
[Required] // This one isn't
public MyType MyData {get;set;}
public MyModel() { this.MyData = new MyType(); }
}
The resulting view shows the field (empty, of course). User can only input data by clicking the field and choosing from a list (a jquery dialog takes care of this, and it already works).
The IValidatableObject interface sounds promising but the code doesn't seem to be ever invoked.
In the controller, I'm simply doing
[HttpPost]
public ActionResult MyAction(FormCollection data)
{
if (!ModelState.IsValid) return View();
// magic: handle data
}
What am I missing ? I probably misunderstood the IValidatableObject interface usage ?
Your controller action should take the view model as parameter instead of weakly typed FormCollection which has absolutely no relation to your model (and its validation rules):
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// magic: handle model
}
Now the default model binder is going to be invoked in order to bind the view model from the request and evaluate any validation logic you might have in this model.
How do you expect from your code, ASP.NET MVC, to ever know that you are working with this MyModel class? You absolutely never used it in your POST action, so you cannot expect to have any validation on it.
Once you start using view models you should forget about weakly typed collections such as FormCollection and start working with those view models.

ViewModel loses data on post

I'm trying to create a complex ViewModel that has both an Index Page and Create Page of Company Notes all within the Details Page of a Company, and would like some guidance as to whether I'm doing this properly.
My problem at the moment is that when I create a new Company Note, it doesn't have any information in the object beyond the EditorFor fields I include in my cshtml - it loses all the data in the ViewModel.
I have a Company model and CompanyController, and in my Details action, I populate all the notes that are relevant to the company, and a form to allow users to add a new note.
My Company and CompanyNotes model are very simple:
public class Company
{
public int CompanyID { get; set; }
// bunch of fields related to the company
public virtual ICollection<CompanyNote> CompanyNotes { get; set; }
}
public class CompanyNote
{
public int CompanyNoteID { get; set; }
public DateTime Date { get; set; }
public string Note { get; set; }
public Company Company { get; set; }
}
I have a ViewModel that looks like this:
public class CompanyViewModel
{
public Company Company { get; set; }
// List of all notes associated with this company
public IEnumerable<CompanyNote> CompanyNotes { get; set; }
// A CompanyNote object to allow me to create a new note:
public CompanyNote CompanyNote { get; set; }
}
This is my Details action, which populates the company record, gets a list of related notes, and displays a create form with a new, empty object:
public ActionResult Details(int id = 0)
{
var viewModel = new CompanyViewModel();
viewModel.Company = db.Companies.Find(id);
if (viewModel.Company == null)
{
return HttpNotFound();
}
viewModel.CompanyNotes = (from a in db.CompanyNotes
where a.Company.CompanyID.Equals(id)
select a).OrderBy(x => x.Date);
viewModel.CompanyNote = new CompanyNote
{
Date = System.DateTime.Now,
Company = viewModel.Company
};
return View(viewModel);
}
This is my CreateNote action in my CompanyController. (Should I split this out into a separate partial view? What would be the benefit?)
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateNote(CompanyViewModel companyViewModel)
{
CompanyNote companyNote = companyViewModel.CompanyNote;
if (ModelState.IsValid)
{
db.CompanyNotes.Add(companyNote);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(companyViewModel);
}
Finally, here's a simplified version of detail.cshtml:
#model Project.ViewModels.CompanyViewModel
// My company detail display is here, removed for sake of berevity
#using (Html.BeginForm("CreateNote", "Company"))
{
#Html.EditorFor(model => model.CompanyNote.Date)
#Html.TextAreaFor(model => model.CompanyNote.Note})
<input type="submit" value="Create" />
}
When I post, my CreateNote action has a companyViewModel that is basically empty, with the exception of companyViewModel.CompanyNote.Date and companyViewModel.CompanyNote.Note, which are the fields in my form - all the other data in the ViewModel is null, so I'm not sure how to even include a reference back to the parent company.
Am I even on the right path here?
Thanks,
Robbie
When I post, my CreateNote action has a companyViewModel that is
basically empty, with the exception of
companyViewModel.CompanyNote.Date and
companyViewModel.CompanyNote.Note, which are the fields in my form -
all the other data in the ViewModel is null, so I'm not sure how to
even include a reference back to the parent company.
That's perfectly normal behavior. Only information that is included in your form as input fields is sent to the server when you submit the form and this is the only information you could ever hope the model binder be able to retrieve.
If you need the CompanyNotes collection in your HttpPost action simply query your backend, the same way you did in your GET action. You could do this by passing the company ID as a hidden field:
#Html.HiddenFor(model => model.Company.CompanyID)
So the idea is to only include as input fields in your form information that the user is supposed to somehow modify. For all the other information, well, you've already have it in your backend so all you have to do is hit it to get it.
Contrary to classic WebForms, there's no longer any notion of ViewState in ASP.NET MVC. It is much closer to the stateless nature of the HTTP protocol.

Categories