0

I have a controller Edit which does an insert and update at the same action. In there I do a check against Active Directory. The applictaion should show a validation error if insert fails due to a mismatch of the username entered by the user and checked agaisnt Active Directory. Since multiple entries happen in the action then Insert should be stopped for that particular user even if the later usernames are valid.

Problem: My code currently does use the ModelState check but it doesn't work for validation message to be passed into VIEW, because model did not actually change it's state in this case.

Question: How can the server side check be done before DB insert for a particular Model property (username in this case)?

Assumption: Should the username check method return the username itself and that can then be passed to the view to display error message?

Limitations: Couldn't use the Annotation on the model IsRequired because it is not a mandatory field. USer should be able to edit the current list of user and save even if username text box is empty. Only when a username is entered in the text box and insert happens then the validation needed. Can't do a client side check against Active Directory since it would be heavy. This is not a filed validation and rather check that bad data doesn't get through to the DB.

Model:

public class User
{
    [Key]
    public string username { get; set; } 

    [Key]
    public string role { get; set; }

}

View:

@model List<Project.ViewModels.UserViewModel>
@using (@Html.BeginForm())
{
<table class="table">
    <tr>
        <th>User</th>
        @for (int i = 0; i < Model[0].Roles.Count; i++)
        {
            <th>
                @Model[0].Roles[i].RoleName
            </th>
        }
    </tr>
    @for (int i = 0; i < Model.Count; i++)
    {
        <tr>
            <td>                
                @if (Model[i].UserName == null)
                {
                    @Html.EditorFor(m=> m[i].UserName)
                    @Html.ValidationMessageFor(m=> m[i].UserName)
                    if (!ViewData.ModelState.IsValid)
                    {
                        <span class="field-validation-error">
                            @ViewData.ModelState["UserName"].Errors[0].ErrorMessage
                        </span>
                    }
                }
                else
                {
                    @Html.HiddenFor(m => m[i].UserName)
                    @Model[i].UserName
                }
            </td>
            @for (int j = 0; j < Model[i].Roles.Count; j++)
            {
                <td>
                    @Html.HiddenFor(m => m[i].Roles[j].RoleName)
                    @Html.CheckBoxFor(m => m[i].Roles[j].IsSelected)
                </td>
            }
        </tr>
    }    
</table>

<div class="form-actions">
    <button id="SubmitUserRoles" type="submit" class="btn btn-success submit" value="Save">Save</button>
</div>
}

<script>
    $("#SubmitUserRoles").click(function () {
        $.ajax({
            url: '@Url.Action("Edit", "Users")',
            type: 'POST',
            cache: false,
            data: JSON.stringify($('form').serialize()),
            success: function (data) {
                window.location.href = data
            }, error: function (xhr, ajaxOptions, error) {
                console.log(xhr.status);
                console.log("Error: " + xhr.responseText);
            }
        });
    });
});

Controller:

[HttpPost]
    public ActionResult Edit(List<UserViewModel> model)
    {
        var level = model[0].Level;
        var location = model[0].Location;

        for (int i = 0; i < model.Count; i++)
        {
            if (model[i].UserName != null)
            {
                var uName = model[i].UserName;
                for (int j = 0; j < model[i].Roles.Count; j++)
                {

                    var uRole = model[i].Roles[j].RoleName;
                    var uRoleSelected = model[i].Roles[j].IsSelected;

                    var userWithSpecificRole = db.Users.FirstOrDefault(m => m.username == uName && m.role == uRole);

                    if (uRoleSelected && userWithSpecificRole == null)
                    {
                        if (DoesUserExist(uName))
                        {
                            if (ModelState.IsValid) 
                            { 
                                db.Users.Add(new User
                                {
                                   username = uName,
                                   role = uRole,
                                });                             
                                db.SaveChanges();
                            }
                        }
                        else
                            ModelState.AddModelError("UserName", "Username does not exist!");
                    }
                }
            }
        }
        return Json(Url.Action("Index", "Users", new {level,location}));
    }

The method that checks the username against Active Directory is as follows:

    private bool DoesUserExist(string username)
    {
        PrincipalContext domain = new PrincipalContext(ContextType.Domain, "CompanyDomain", "DC=CompanyDomain,dc=com");

        UserPrincipal foundUser = UserPrincipal.FindByIdentity(domain, IdentityType.SamAccountName, username);

        return foundUser != null;
    }
PineCone
  • 2,193
  • 12
  • 37
  • 78
  • Bit unclear what your actually wanting to validate. But first remove `if (!ViewData.ModelState.IsValid) { ... }` in the view (that make no sense - you already have the correct `@Html.ValidationMessageFor(m=> m[i].UserName)` in the view. And delete your script - your wanting to redirect when successful so do not use ajax - make a normal submit –  Nov 03 '16 at 09:26
  • I'm assuming your view is similar to the image in your [previous question](http://stackoverflow.com/questions/40131014/c-sharp-mvc-persisting-data-from-view-to-controller-and-then-via-single-object)? But why do you have a textbox to edit the user name but allow it to be blank? And the code in you controller is only saving data is `UserName` has a value, so why are you not making it `[Required]`? (hard to understand what your trying to achieve. –  Nov 03 '16 at 09:29
  • @StephenMuecke Yes, related to previous post. The view would show existing users and three empty text boxes each time to enter new user if needed. This text box is not to edit but to enter new username and roles for new users.Do you mean I can use `[Required]` in this case? – PineCone Nov 03 '16 at 09:37
  • That is not the way to handle this (refer the answers [here](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) and [here](http://stackoverflow.com/questions/29837547/set-class-validation-for-dynamic-textbox-in-a-table/29838689#29838689) for how to dynamically add new collections items - which you do as needed by clicking an 'add' button. not by including a lot of 'blank' rows –  Nov 03 '16 at 09:40
  • But why are you allowing the user to enter UserNames instead of just getting all the users from AD and creating a row for each one? –  Nov 03 '16 at 09:41
  • I know bad design bad decisions but I am afarid this is the way I have to get the app working initially.This is the requirement.Can't do much for now :(. Would really appreciate if you can help me on the serverside validation. Also need to use AJAX because I have to stay on the same page. – PineCone Nov 03 '16 at 09:49
  • What do you mean _I have to stay on the same page_ - the code in the success callback is `window.location.href = data` which redirects - it does not stay on the same page. Sorry, but nothing your doing here makes sense –  Nov 03 '16 at 09:57
  • Yes redirecting to the `Index` `Àction` which uses the same `View`. This way I meant I am on the same page. – PineCone Nov 03 '16 at 10:04
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/127258/discussion-between-stephen-muecke-and-sharlene). –  Nov 03 '16 at 10:06

0 Answers0