Remote attribute in asp.net mvc – in some situations restricts our model - asp.net-mvc

I’ve got an unexpected situation when using Remote Attribute in ASP.NET MVC3.
The model type I used:
using System;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
namespace dTweets.Models
{
// at first time, user should create his account with unique username
// as in twitter.com, user do
public class UserMetadata
{
[HiddenInput]
internal int Identity { get; set; }
[Remote("IsUserExist", "Account")] // at any HttpPost, username should
// be unique – not appropriate if
// updating/editing this model later
[Required(ErrorMessage = "username should be unique")]
public string UserName { get; set; } // user cannot change it, later
[DataType(DataType.Password)]
public string Password { get; set; } // user can also change password, later
[DataType(DataType.MultilineText)]
public string About { get; set; } // Optional field – user can edit it later
}
[MetadataType(typeof(UserMetadata))]
[Bind(Include="UserName, Password, About")]
public partial class User
{
}
}
Remote attribute validates user unique name at account creation time. But when later user wants to update/change his account, Remote attribute did not allow to update model if keeping user unique name the same one.
This is not appropriate result because rarely user changes their unique user name. They just change other fields like About field or password etc.
[Note: at account creation time, I want to check user unique name so I used Remote attribute here, but at later time when updating user account I no longer need Remote attribute]
I must remove Remote attribute for updating this model later.
I want to update/change this model without changing user unique name (remote attribute is applied to this unique name).

one way to do this is to send ID value of this record in AdditionalFields named parameter like
[Remote("IsUserExist", "Account",AdditionalFields = "Identity")]
and then you can check for uniqueness across all rows except the ones that belong to current user. and don't forget to change signature of IsUserEsists action result to receive Identity like
public ActionResutl IsUserExists(string UserName, int Identity)
{
}

Can't you just change server side validation method to something like:
public ActionResult IsUserExists(string userName)
{
if (!UserService.UserNameExists(userName) || (CurrentUser.UserName == userName))
{
return "Yeah. Is it valid.";
}
}
You have current user, because he is logged in. As long as user can only edit his data, this will work.

This is one place where buddy metadata falls short.
Edit/Add scenarios require their own view models. One size fits all scenario validation attributes only work in very trivial business CRUD apps. Add and Edit actions happen in totally different contexts and are only transiently related. This concept is very similar to the DDD bounded context idea.

Related

Custom Validation attribute is not firing when the model property is called

I have made a custom attribute which checks the password complexity, the issue is that it called when i first run the code, after that if i change the complexity it does not register that attribute even if the session is refreshed.
i am calling it on property named password
in UserModel.
[ComplexPassword()]
public String Password { get; set; }
The custom attribute is here.
public class ComplexPassword : RegularExpressionAttribute
{
public ComplexPassword()
: base(GetRegex())
{
T = Localizer.CaptionInstance;
}
private Localizer.CaptionDelegate T { get; set; }
Some Logic here...
}
the Password property is used in the changed password form which is using user model. i think view code is not necessary to show. Can anyone guide how to fire it at every at every call of Password property.
I believe the Data Annotations get cached on a model. So if you are doing something behind the scenes so that GetRegex() changes depending on something you do in the app, it might not be reflected in any new validation attempts on the model. You might need to create your own MetadataProvider that cusotmizes the behavior of DataAnnotationsModelMetadataProvider

Information about form sender - ASP.NET MVC

I would like to create a group in my app. In model that group has a field Admin_ID to specify who is the owner. How to include that information in a form? With hidden field? I don't think this would be safe enough solution... Any other ideas?
In model that group has a field Admin_ID to specify who is the owner.
Then apply authentication (see the tutorial for your MVC version) so you know which user is logged in, use authorization to restrict access to admins only and in the controller assign the creating user's ID to the model when it is created:
[Authorize(Roles="Admin")]
public ActionResult Create(SomeModel modelToCreate)
{
// get user, depends on how your project is set up...
var user = GetLoggedOnUser();
modelToCreate.Admin_ID = user.ID;
}
If hidden field is not enough for security - don't include it there, but have a separate action that only does operation on your Admin group.
Sorry, can't give more details, as your question does not contain enough information.
You can always save the Admin_ID in the Session.
To better understand:
public class Group
{
public int A { get; set; }
public string B { get; set; }
public string C { get; set; }
public string ID_Admin { get; set; }
}
where ID_Admin should be automatically generated when someone who is logged in want to submit that form (he need to fill only A, B and C fields). So let's say currently logged user has ID 42, and he want to submit that form (without any knowledge of that additional field of course) - and I want to include that information about his ID in final model submission.

How to save claims used in a MVC site using Thinktecture IdentityModel

I am working on a MVC web site with Claims Authentication and also using Thinktecture IdentityModel.
Each user has a set of CLAIMS: City, Username, Email, Roles, Birthdate.
I have 3 tables in the database: USERS, USERS_PROFILES and USERS_CLAIMS.
USERS contains the columns Username and Email;
USERS_PROFILES contains the columns City and Birthdate.
I am saving these values in these tables ... And I save the Roles in the USERS_CLAIMS table.
When I load a user I fill the ClaimsIdentity with the data from these tables.
THE QUESTION IS:
When I create a user should I, for example, save the email both in USERS table and in USERS_CLAIMS table?
It seems data replication but I am not sure ... Or should I save in only one of them?
What is your approach to this? And what kind of claims, beside roles, do you have that don't fit in a USERS or USERS_PROFILE table?
Thank You,
Miguel
As you're using MVC (Model View Controller), why don't you use Models ?
Your ClaimsIdentity is a model, just create a ClaimsIdentityModel.cs in your Model folder, and use it like this :
public class ClaimsIdentityModel
{
public string Username { get; set; }
public string Email { get; set; }
public string City { get; set; }
public Datetime Birthdate{ get; set; }
}
Then, when you load your user, just fill a ClaimsIdentityModel. And you're done.
As name of each table suggest, the values shall represent those only.
Users are identified as by usernames. Email is one of the claim done by the User.
Roles are also claims, so as City.
If you are creating profile table and adding city in profile, it shall be avoided to add it in Claims table. But you need to handle requirement of City as claim by queries Profile table and providing value as claim as needed.
ClaimsIdentity is System.Security.Principal.GenericIdentity type that is used to identify the user in claim based approach.
(Making Model will be helpful in the View)

how to populate data in controller and avoid ModelState errors

Maybe that's not exactly the solution i need, but this is what i want to do:
i have a company registration form, and each company needs an administrative user. an administrative user may manage multiple companies, so in the company registration form, you can choose an existing user from a dropdown.
the company view model looks something like this:
public class CompanyViewModel {
[Required]
public string Name { get; set; }
// other properties...
public UserViewModel Administrator { get; set; }
public IEnumerable<UserViewModel> AvailableUsers { get; set; }
}
and the user view model looks like this:
public class UserViewModel {
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
// other properties...
}
in the company registration view:
<div><input type="radiobutton" name="chooseuser" id="existing"/>Choose an Existing User:</div.
<div>#Html.DropDownListFor(m => m.Administrator.Id, Model.AvailableUsers.Select(u => new SelectListItem { Text = string.Format("{0} - {1} {2}", u.UserName, u.FirstName, u.LastName), Value = u.Id.ToString() }), "<Choose existing user>", new { id = "existingusers" })
</div>
<div><input type="radiobutton" name="chooseuser" id="createnew"/>Create a new User:</div>
<div><label>Username:</label> #Html.EditorFor(m => m.Administrator.UserName)</div>
Through javascript, based on radio button selection, the dropdown list is disabled and the new user form shown, or the new user form is hidden and the dropdown list is enabled.
The problem is in the controller Save action after you press save, ModelState.IsValid is false if an existing user is chosen and no data is filled in on the form. If the user chooses to enter a new user, validation succeeds.
What is the best way to handle this?
One option is to load all data for all users into data structures in javascript, and when the value changes on the existing user dropdown, the hidden "create new" form fields can be populated. But this seems lame since passwords would be sitting the html in plain text. i can get fancier and use ajax for a "create new" form and populate a user id on the original form once the new user is saved, but i'd like to keep it all in one form if possible.
Seems liked i'd ideally be able to load the existing user data from the db and populate the model state in the controller Save action, but writing this code manually (even using reflection) seems sloppy. It would be nice if there was a built in method to do this.
Any ideas?
Thanks.
That's a typical scenario which perfectly illustrates the limitations of declarative validation (a.k.a Data Annotations). In order to handle it you could write a custom validation attribute which will be applied to the CompanyViewModel instead of individual properties and will allow you to perform the validation logic based on which radio button the user choose (btw you will need a property on your view model which will represent the radio button selection). The problem with model validators is that you might have some hard time handling the error highlighting.
That's one of the reasons why I use FluentValidation.NET instead of Data Annotations. This allows me to have the validation logic away from the model and done in an imperative way. It also allows me to have conditional validators which apply based on the values of some properties on the view model (in this case this would be the radio button selection).
You may want to consider a custom Modelbinder.
Here's some sample code from my site - this is part of a checkout page for a shopping cart - the user can enter an address but for US StateCd is sent and for non US StateOrProvince is sent. So we look at the country and remove any model errors for the other property that doesn't apply.
I think this is very similar to what you're describing (that you have two scenarios that need different rules but you want to use the same model).
The important code here is bindingContext.ModelState.Remove(...)which removes the model state and allows IsValid to return true.
public class AddressModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
// get the address to validate
var address = (Address)bindingContext.Model;
// remove statecd for non-us
if (address.IsUSA)
{
address.StateOrProvince = string.IsNullOrEmpty(address.StateCd) ? null : CountryCache.GetStateName(address.StateCd);
bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateOrProvince");
}
else
{
address.StateCd = null;
bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateCd");
}
// update country
address.Country = CountryCache.GetCountry(address.CountryCode, true).Name;
// validate US zipcode
if (address.CountryCode == "US")
{
if (new Regex(#"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).Match(address.ZipOrPostal ?? "").Success == false)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".ZipOrPostal", "The value " + address.ZipOrPostal + " is not a valid zipcode");
}
}
// all other modelbinding attributes such as [Required] will be processed as normal
}
}
Note: You need to register this modelbinder in global.asax. The modelbinding component is smart enough to let you create differnt model binders for any part of your model if it contains different objects.
ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder();
Hope this helps. I think this applies to your situation.

ASP.NET MVC validation of uniqueness

Rails has a very convenient uniqueness validation.
ASP.NET MVC doesn't.
I need to make sure that the e-mail address a user has entered hasn't been registered by anyone yet.
I can see only one way of doing this kind of validation: create a new data context object in the UniqueAttribute class.
But I'm afraid that wasting memory on a new data context object just for one validation is dangerous.
Am I wrong? Is there a better way to do that?
Update
This is what I got so far
public class UniqueEmailAttribute : ValidationAttribute {
public override bool IsValid(object value) {
DataContext db = new DataContext();
var userWithTheSameEmail = db.Users.SingleOrDefault(
u => u.Email == (string)value);
return userWithTheSameEmail == null;
}
}
// Usage
[UniqueEmail(ErrorMessage="This e-mail is already registered")]
public string Email { get; set; }
There are two problems.
It would be good to have just one UniqueAttribute class, not separate classes for e-mails, usernames etc. How can I do that?
Creating a new data context every time you need to validate a single attribute.
SOLUTION
So in the end I created a unique constraint on the table and now I just have to intercept SqlException in Users repository. Works great and is probably more efficient than searching for the same node in the whole table. Thanks!
Mvc 3 Relaease candidate has new New Validation Attributes as a remotevalidation -where you can register a method for validation on clientside(jquery).
see below example-
RemoteAttribute
The new RemoteAttribute validation attribute takes advantage of the jQuery Validation plug-in's remote validator, which enables client-side validation to call a method on the server that performs the actual validation logic.
In the following example, the UserName property has the RemoteAttribute applied. When editing this property in an Edit view, client validation will call an action named UserNameAvailable on the UsersController class in order to validate this field.
public class User {
[Remote("UserNameAvailable", "Users")]
public string UserName { get; set; }
}
The following example shows the corresponding controller.
public class UsersController {
public bool UserNameAvailable(string username) {
return !MyRepository.UserNameExists(username);
}
}
Mvc 3
UPDATE
public bool UserNameAvailable(string Propertyname)
{
if (Request.QueryString[0]= "UserName")
{
//validate username
}
elseif (Request.QueryString[0]= "Email")
{
//Validate Email
}
}
ASP.Net does have a feature that can automatically check the uniqueness of a user's email address when a user registers. It is the ASP.Net Membership service and you can use it to do what you want even if you don't use all of the features of it.
If you are not using the full Membership feature in your MVC application, then all you need to do is use
Membership.FindUsersByEmail(emailYouAreLookingFor);
If any values come back, you know that the address is not unique. If you ARE using the Membership service to create users, then the Membership service will check AUTOMATICALLY and return a code to you if the user's email address is not unique.
The Membership service sits in the System.Web.Security area so you would need a
using System.Web.Security;
reference in your controller.
Here is an example
MembershipCreateStatus createStatus = MembershipService.CreateUser(UserName, Password, Email);
if (createStatus == MembershipCreateStatus.DuplicateEmail)
{
//do something here
}
else
{
//do something here
}
I hope this helps!
The right way to make a generic remote unique validator in MVC can be found in this MVC forum. by counsellorben. It's based on my MVC unique remote validator article http://msdn.microsoft.com/en-us/library/gg508808(VS.98).aspx
A foolproof way of doing this is to create a validation attribute that would query the database for the email address. It would certainly add latency.
An alternative would be to create a unique constraint on the table and intercept SqlException.

Resources