asp mvc fine grained view permission - asp.net-mvc

I am build ASP MVC 4 web application and I need to create fine-grained permission on my views.
It means I need to show some actions to some users depend on their role, the data type request and other authorization rules.
On the controller side I put Authorize attribute where appropriate and create fine-grained code like so:
public ActionResult Index() {
List<Survey> surveys;
if (MyUser.IsSuperUser) {
surveys = surveyRep.AllSurveys.ToList();
}
else {
surveys = surveyRep.VisibleSurveys.ToList();
}
return View(surveys);
}
Because I'm building multi-tenant application any user who is not super user see only the "visible" surveys. Super user always see everything.
The problem now is how to create the same thing on the view side, without duplicating logic (DRY).
Currently when a user that is not super user and has only one tenant link to it I use this razor view logic:
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Survey</legend>
#if (!MyUser.IsSingleTenant) {
<div class="editor-label">
Tenant
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.TenantID, ...
#Html.ValidationMessageFor(model => model.TenantID)
</div>
}
MyUser return the currently logged in use and IsSingleTenant indicate if she only link to single tenant.
I'm afraid this will make my views messy with a lot of "if-then" logic.
How do other people solve this?
Thank you,
Ido.

There's nothing wrong with this approach. You could just put those sections in partial views to make it more readable:
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Survey</legend>
#if (!MyUser.IsSingleTenant) {
#Html.Partial("Tenant")
}
...
</fieldset>
}

Related

Second model in same view not posting to controller

With the help of several SO questions, I've figured out how to use two models on the same view, using the tuple form. At the top of my file is this:
#using Project.Models;
#{
ViewBag.Title = "Details";
Layout = "~/Views/Shared/_Layout.cshtml";
#model Tuple<Foo, Bar>
}
For the Foo stuff, it uses jQuery like this:
#Html.DisplayFor(tuple => tuple.Item1.ID)
and works fine. However, for my second model, it isn't displaying info, but is a submission form. Currently, this is what I have:
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "createFoo", #action = "/api/Foo" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="editor-field">
#Html.TextAreaFor(tuple => tuple.Item2.Text)
#Html.ValidationMessageFor(tuple => tuple.Item2.Text)<br />
</div>
<input type="submit" value="Post Response" />
}
Mind you, this is mostly copy paste from other views since I'm new to MVC and it worked fine with other forms. For my FooController, I have this:
public void Post([FromBody] Foo foo)
{
Foo existingFoo = this.fooRepository.GetFoo(foo.ID);
if (existingFoo != null)
{
// throw error
}
else
{
System.Diagnostics.Debug.WriteLine("MESSAGE POSTING: " + foo.Text);
}
}
When submitting from the view, the received foo isn't null (checked in other tests), but the foo.text is empty. I've tried lots of different inputs and such, but I'm just so unfamiliar with the #Html.* functions and ASP.net-MVC in general that I'm not sure where I could be going wrong.
If you need to see my repository code let me know, but I doubt it'd have an effect on this. Thanks in advance for any help.
There are 2 issues I can see with your code above:
The way the Html helper will output your fields and how that feeds into your api post
Not having your ID in the controller.
1: Outputting a field like this (#Html.TextAreaFor(tuple => tuple.Item2.Text)) will give the field the name "Item2.Text". This is an issue as that is what gets passed on the post. You will get form data with Item2.Text:dfsdsgdfg. This won't match when your API controller tries to bind. So, try outputting the text field with a set name:
#Html.TextArea("Text", #Model.Item2.Text)
2: Your Id field is not in the form... thus it won't be sent. Try using a hidden field:
#Html.Hidden("ID", #Model.Item1.ID)
Also, just a clarification, this (#Html.DisplayFor(tuple => tuple.Item1.ID)) is not jQuery.

Placing MVC 4 login control on Home/index page

First off I am new to MVC, I was a webforms guy...
I want to show my login control on my home/index page, but no matter what I do I run in to errors. I have tried various techniques and have gotten many errors with each technique, so I wont list them all here.
Right now I have it set that if Request.IsAuthenticated then show the username else show the login form. But the form is obviously looking at the home controller and nothing happens when I submit the login.
Any advice would be much appreciated. I have been dabbling for days on this.
Thanks :)
Here is the Code:
'#{
ViewBag.Title = "MyApp";
}
<p>Code for main Index Page here</p>
#model Application.Models.LoginModel
#{
ViewBag.Title = "Log in";
}
#if (Request.IsAuthenticated) {
<text>
Hello, #Html.ActionLink(User.Identity.Name, "Manage", "Account", routeValues: null, htmlAttributes: new { #class = "username", title = "Manage" })!
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" })) {
#Html.AntiForgeryToken()
Log off
}
</text>
} else {
<section id="loginForm">
<h2>Use a local account to log in.</h2>
#using (Html.BeginForm(new { ReturnUrl = "RedirectToAction" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Log in Form</legend>
<ol>
<li>
#Html.LabelFor(m => m.UserName)
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
</li>
<li>
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
#Html.ValidationMessageFor(m => m.Password)
</li>
<li>
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(m => m.RememberMe, new { #class = "checkbox" })
</li>
</ol>
<input type="submit" value="Log in" />
</fieldset>
<p>
#Html.ActionLink("Register", "Register", "Account") if you don't have an account.
</p>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
</section>
}
'
you have to modify your form like this:
#using (Html.BeginForm("Login","Account",FormMethod.Post, new { ReturnUrl = ViewBag.ReturnUrl }))
the key is the "Login","Account" part, that tells the form to post the data to the login action of the account controller instead of the home controller. I fought with the same thing for a few hours then figured it out.
if the data does not post to the account controller (the controller that actually validates the username and password and logs the user in) it will never try to log them in.
reason is :
by default in the routeconfig.cs class sends any links or forms that do not specify an action and controller send all stuff to the Index action of the Home controller.
Hope this helps
You should use a Child Action:
In your controller:
[ChildActionOnly]
public ActionResult LoginForm()
{
return View("_LoginFormPartialViewHere");
}
Then in your homepage view:
#{ Html.RenderAction("LoginForm"); }
In your partial view for your login form, you can strongly-type the view for your login view model, but make sure to specify a different post action for your form than the default "postback" model. This action will handle the login and only the login (so not your homepage action).
Your problem here is that you are attempting to write code without actually understanding what it does. Without understanding what it does, you are simply left randomly changing things hoping to find something that works. This is a very poor way to write software.
Your problem is rooted in the fact that you don't understand that MVC is merely generating HTML, and without understanding what that HTML is supposed to be doing, you have little hope of randomly figuring this out.
First, MVC has no concept of a "login control". They're just HTML form fields, and they sit within an HTML form element. Those form fields are posted to your controller using standard HTML, which means you have to ensure your form action method is correct, and that the action url is correct.
Secondly, when those form fields are posted, you have to have an action method that will receive the post. If that action method is not the same as the action method used in the GET, then you will have to tell the BeginForm() helper where to post to.
In MVC, you can't think like Webforms and just assume everything gets done for you. You have to do everything yourself, and make sure every link, every action, every selector, every bit of javascript, etc.. is all correct. This is more work than WebForms, but it's also more powerful and more efficient.

Disable Required Validation Specific Field in the View ASP.NET MVC 4

if someone could give me some hint I would appreciate.
I'm searching for a while, and I even found a post I thought it would solve my problem, but it didn't.
Disable Required validation attribute under certain circumstances
Basically I have a simple User.cs model where I have username, FirstName, LastName and SignupDate
All have the required annotation and I would like to solve this without erasing the Required tag.
After I generate the view, I erase in the view the html code for the SignupDate:
<div class="editor-label">
#Html.LabelFor(model => model.SignupDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.SignupDate)
#Html.ValidationMessageFor(model => model.SignupDate)
</div>
When I click submit it does not work.
Also if I do the suggested in the other post
<div class="editor-label">
#Html.LabelFor(model => model.SignupDate)
</div>
<div class="editor-field">
#Html.TexBoxFor(model => model.SignupDate, new { data_val = false })
</div>
If I leave it as blank also does not work..
Any suggestions? Thanks!!
You can disable client validations on the view and remove the errors on the modelstate for those entities you don't want to validate the value.
In my case I wanted to change a Password only if the user typed one. Using Html.HiddenFor was not a good approach due to sends the password to the client every time, and password shouldn't be sent.
What I did was to disable the client validations on the view
#model MyProject.Models.ExistingModelWithRequiredFields
#{
ViewBag.Title = "Edit";
Html.EnableClientValidation(false);
}
That allows me to submit the form even with empty values. Please note that all client validations are ignored, however server validations still run, so you need to clear those you don't need to be executed. In order to do this, go to the action in the controller and remove the errors for each property you need to
public ActionResult Edit(ExistingModelWithRequiredFields updatedModel)
{
var valueToClean = ModelState["RequiredPropertyName"];
valueToClean.Errors.Clear();
if(ModelState.IsValid)
{
...
//Optionally you could run validations again
if(TryValidateModel(updatedModel)
{
...
}
...
}
...
}
I think this should solve it, assuming model.SignupDate holds a value:
<%: Html.HiddenFor(model => model.SignupDate) %>

MVC4 Razor Looses UserID if Unbound

I'm creating some user profile edit forms in MVC4 at the moment and for testing I was rendering the UserId property into a readonly textbox on the form like this:
<li>
#Html.LabelFor(model => model.UserId)
#Html.TextBoxFor(model => model.UserId, new { #readonly="readonly"})
</li>
As I'm nearing completion of the edit form I removed this textbox as it's just using up real estate. Once I had done this the model sent back to the controller when saving had the integer default value of 0 and then the Entity Framework blows up as it cannot update any rows. So I added this to the form:
<li>
#Html.HiddenFor(model => model.UserId, new { #readonly="readonly"})
</li>
Is this a safe move? Should I be using the ViewBag for things like this? On the profile details page I render an edit button like this:
#Html.ActionLink("Edit", "Edit", new { id=Model.UserId })
Meaning that the UserId is rendered in the link. Is this safe and secure or do I need to rethink how I move the models and ids around the UI?
TIA,
Is this a safe move?
This will do the job of sending the id to the server. Just get rid of the readonly="readonly" attribute which makes very little sense for a hidden input.
Should I be using the ViewBag for things like this?
This doesn't change anything in terms of security. Any user could still put whatever id he wants. Whether you are using a hidden field or an ActionLink you are still sending the id as plain text to the server and anyone could forge a request and put whatever id he wants. So if you site uses some form of authentication you must absolutely check on the server side that the id that you received actually is a resource that belongs to the currently authenticated user before attempting to perform any actions on it. Otherwise some authenticated user could supply the id of a resource that belongs to another user and be able to update it. Of course that's just a hypothetical scenario, it's not clear at all if this is your case and whether this id needs to be secured.
If UserId is sensitive, then there are other options
Keep UserId server side only with Session state (if your architecture allows for Session)
Put it in an encrypted cookie. Note as per Darin, that these can be compromised.
If it isn't sensitive, then your HiddenFor is fine - post it back with the rest of the form.
Don't put it in your ActionLink Querystring unless this is part of your route (i.e. /Controller/Action/id)
I would strongly suggest using ValueInjecter. Here is a code snippet doing the same thing
[HttpGet]
public new ActionResult Profile()
{
var model = new ProfileModel();
model.InjectFrom<UnflatLoopValueInjection>(this.GetCurrentUser());
return View(model);
}
[HttpPost]
public new ActionResult Profile(ProfileModel model)
{
if (ModelState.IsValid)
{
this.GetCurrentUser().InjectFrom<UnflatLoopValueInjection>(model);
try
{
_userService.SaveOrUpdate(this.GetCurrentUser());
TempData["Success"] = "User was successfully updated.";
return RedirectToAction("Profile");
}
catch (Exception)
{
ModelState.AddModelError("Exception", "Unexpected error");
}
}
return View(model);
}
And here is the view...
#using (Html.BeginForm("Profile", "Account", FormMethod.Post, new { #class = "form-horizontal" }))
{
#Html.ValidationSummary(true, "Unable to update profile. Please correct the errors and try again.", new { #class = "alert alert-block alert-error" })
#Html.EditorForModel()
<div class="form-actions">
<input type="submit" value="Update" class="btn btn-primary" />
</div>
}

MVC - Multiple Strong Type object ...inside one view

After so many years using ASP.Net, I’m still trying to figure out how to achieve the same results using MVC.
I have a materpage with a control that is strongly type to something. When I navigate to a view of a different strongly type model ...and click on the button to execute something, I get "The model item passed into the dictionary is of type Site.Models.RegisterModel', but this dictionary requires a model item of type Site.Models.LogOnModel'".
For the sake of this example, we can take the Default MVC app that is provided with VS 2010, let’s imagine I want to change the “LogonUserControl.ascx” so that it either tells me the logged user (as it works currently) OR allow me to login from there, showing me the text boxes for username and password (therefore in this case from the home page).
So I take the control and strongly type it as:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Gioby.Models.LogOnModel>" %>
<%
if (Request.IsAuthenticated) {
%>
Welcome <b><%: Page.User.Identity.Name %></b>
[ <%: Html.ActionLink("Log Off", "LogOff", "Account")%> ]
<%
}
else {
%>
<% using (Html.BeginForm()) { %>
<div id="logon">
<div class="editor-label">
<%: Html.LabelFor(m => m.UserName)%>
<%: Html.TextBoxFor(m => m.UserName)%>
<%: Html.ValidationMessageFor(m => m.UserName, "*") %>
<%: Html.LabelFor(m => m.Password)%>
<%: Html.PasswordFor(m => m.Password)%>
<%: Html.ValidationMessageFor(m => m.Password, "*") %>
<input type="submit" value="Log On" />
</div>
<div class="editor-label">
<%: Html.ActionLink("Register here", "Register", "Account")%>
<%: Html.CheckBoxFor(m => m.RememberMe, new { #class = "pad-left" })%>
<%: Html.LabelFor(m => m.RememberMe) %>
</div>
</div>
<% } %>
<%
}
%>
Then on the HomeController, I add a procedure as:
[HttpPost]
public ActionResult Index(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
// ==>> Check Login against your DB
// Now check if param returnUrl is empty
if (!String.IsNullOrEmpty(returnUrl))
return Redirect(returnUrl);
return RedirectToAction("Index", "Home");
}
// If we got this far, something failed, redisplay form
return View(model);
}
I tested it from the home page … it works !!!
BUT when I navigate to the “Register” view (remember that the “LogonUserControl.ascx” is located inside the “MasterPage”, therefore visible from the Register view).
So when I click on the Register button, I get the error:
The model item passed into the dictionary is of type Site.Models.RegisterModel', but this dictionary requires a model item of type Site.Models.LogOnModel'.
QUESTION:
Does that mean that I will never be able to different pieces together into one view?
Let’s say I want to write an eCommerce site and on the home page I want to see “Most used Tags”, “Most bought products”, “Product of the Month”, “List of Categories” …all within the same view and each one with his own HTTP POST action.
If this is possible using MVC?
If I'm understanding the problem correctly, you have two Views that use the same MasterPage, but which are strongly typed against different ViewModels. The master page is able to include a Partial View that is also strongly typed, as long as its expected ViewModel is the same as that of the parent view. However, if you're using a view with a different ViewModel type, it doesn't know what to do.
Consider the following:
<% Html.RenderPartial("LogOn") %>
The above code implicitly includes the model data for the current view being rendered. It's exactly the same as if you had said:
<% Html.RenderPartial("LogOn", Model) %>
So this will only work if Model is a LogOnModel. Remember that the MasterPage is really a part of whatever View inherits it, so even if you're putting this in the MasterPage, it's as if you'd put the same code in every view that inherits it. So if your View's Model is not the same as the PartialView's Model, this won't work. One alternative is to use inheritance to ensure that every ViewModel will include all the information required by the Master Page. This approach is described in detail here.
But that approach means that you have to always use a factory to produce your view model, and every view model has to be somewhat aware of which master page it will use. In our product, we can use a different master page on the same view depending on what mode the user is viewing the site in, so it doesn't make sense to tie the ViewModel to that of the Master Page. We accomplish what you're describing using the RenderAction method, which allows you to render an entire controller action as if it were just a part of the larger view. Some of the advantages of this approach are discussed here.
So now you can have your MasterPage include whatever little partial views you want, but you separate the logic for building the ViewModel of each of these Views into an individual controller action that's responsible for that particular Partial View:
<% Html.RenderAction("LogOnBox") %>
The Action:
public ActionResult LogOnBox()
{
LogOnModel model = GetLogOnModel();
return PartialView("LogOnUserControl", model);
}
Now, regardless of what model your current view uses, your Master Page can include “Most used Tags”, “Most bought products”, “Product of the Month”, “List of Categories”, etc. Better still, these portions of the page can leverage output caching so they don't have to be regenerated with every page load if they don't change very often.

Resources