View or Hide a dropdownlist in my razor view using helper method - asp.net-mvc-3

I have an object named Visit and i defined the following helper method (“CanBeEdited”) to specify if users can edit the object Status property or not:-
public partial class Visit
{
public bool CanBeEdited(string username)
{return (((DoctorID != null) && (DoctorID.ToUpper().Equals(username.ToUpper()))) && (StatusID == 5)); } }}
Then i have specified to show or hide certain dropdownlist on my Edit view depending on weather the CanBeEdited helper method returns true or false (if it returns true then the user can view and edit the Status dropdownlist, and if it returns false then the view will render an #Html.HiddenFor representing the old status value).
My edit view which includes the helper method looks as following:-
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Visit</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Note)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Note)
#Html.ValidationMessageFor(model => model.Note)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DoctorID)
</div>
<div class="editor-field">
#Html.DropDownList("DoctorID", String.Empty)
#Html.ValidationMessageFor(model => model.DoctorID)
</div>
#{
if (Model.CanBeEdited(Context.User.Identity.Name))
{
<div class="editor-label">
#Html.LabelFor(model => model.StatusID)
</div>
<div class="editor-field">
#Html.DropDownList("StatusID", String.Empty)
#Html.ValidationMessageFor(model => model.StatusID)
</div>
}
else
{
#Html.HiddenFor(model => model.StatusID)}
}
<p>
#Html.HiddenFor(model => model.VisitTypeID)
#Html.HiddenFor(model => model.CreatedBy)
#Html.HiddenFor(model => model.Date)
#Html.HiddenFor(model => model.VisitID)
#Html.HiddenFor(model => model.PatientID)
#Html.HiddenFor(model => model.timestamp)
<input type="submit" value="Create" />
</p>
</fieldset>
}
To be honest it is the first time i implement such as case,, so is my approach sound valid ???,, or it have some weaknesses i am unaware of ??. As i need to implemented similar cases all around my web application...
Baring in mind that i am also checking for the CanBeEdited on the action methods..
Thanks in advance for any help.
Updated:-
My post action method look as follow:-
[HttpPost]
public ActionResult Edit(Visit visit)
{
if (!(visit.Editable(User.Identity.Name)))
{
return View("NotFound");
}
try
{
if (ModelState.IsValid)
{
repository.UpdateVisit(visit);
repository.Save();
return RedirectToAction("Index");
}
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var clientValues = (Visit)entry.Entity;
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
// patient.timestamp = databaseValues.timestamp;
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
ViewBag.DoctorID = new SelectList(Membership.GetAllUsers(), "Username", "Username", visit.DoctorID);
ViewBag.StatusID = new SelectList(db.VisitStatus, "StatusID", "Description", visit.StatusID);
ViewBag.VisitTypeID = new SelectList(db.VisitTypes, "VisitTypeID", "Description", visit.VisitTypeID);
return View(visit);
}

I don't feel adding that in the View is a good idea. I would like to have My ViewModel to hold a property of boolean type to determine that it is editable or not. The value of that you can set in your controller after checking the relevant permissions.
public class ProductViewModel
{
public bool IsEditable { set;get;}
//other relevant properties
}
and controller action
public ActionResult GetProduct()
{
ProductViewModel objVM=new ProductViewModel();
objVm.IsEditable=CheckPermissions();
}
private bool CheckPermissions()
{
//Check the conditions and return true or false;
}
So view will be clean like ths
#if (Model.IsEditable)
{
//Markup for editable region
}

IMHO, it sounds valid enough.
UPDATE: removed irrelevant commentary, and edited to indicate a primary concern.
Now, taking a closer look, especially with the controller action, I strongly recommend that you eliminate the hidden fields (except the one that you need to re-load the record from your back end).
A savvy user can tamper with the hidden form data (all the form data) and your controller action will happily send it all back to the server.
In reality, you should post back only the fields that are permitted to be changed, rehydrate the record from the back end, and transfer the "editable" fields to the fresh copy. This also comes closer to addressing concurrent edit and stale record issues.

Related

MVC 5 Conditional Validation Option?

I'm developing an MVC 5 web application. Within a particular View I need to validate a ViewModel, however, I need some of the validation only to occur depending on the users inpupt.
For example, I have a ViewModel
public class TimeEntryViewModel
{
public int proposalID { get; set; }
public int proposalCode { get; set; }
public int nonchargeCode { get; set; }
public SelectList UserProposals { get; set; }
public SelectList TimeEntryClientCodes { get; set; }
public SelectList TimeEntryNonChargeCodes { get; set; }
}
This ViewModel is passed to a View which looks like this
<div class="form-group">
#Html.LabelFor(m => m.proposalID, "Proposal")
#Html.DropDownListFor(m => m.proposalID, Model.UserProposals, "No Proposal", new { #class = "form-control"})
#Html.ValidationMessageFor(m => m.proposalID)
</div>
<div id="ClientCodes" class="form-group" style="display:none">
#Html.LabelFor(m => m.proposalCode, "Client")
#Html.DropDownListFor(m => m.proposalCode, Model.TimeEntryClientCodes, "Select", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.proposalCode)
</div>
<div id="NonChargeCodes" class="form-group">
#Html.LabelFor(m => m.nonchargeCode, "Non Charge")
#Html.DropDownListFor(m => m.nonchargeCode, Model.TimeEntryNonChargeCodes, "Select", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.nonchargeCode)
</div>
If the user selects 'No Proposal' from the first drop down list, then the drop down list 'nonchargeCode' appears and I need to validate so that the user selects an option from it.
However, if the user selects another option from the first down drop list, then the drop down list 'nonchargeCode' will disappear and another drop down called 'proposalCode' will appear. I then want to validate to ensure the user selects an option from this drop down, but not the 'nonchargeCode' (which will be hidden).
In an MVC 4 application I previously coded, I used http://fluentvalidation.codeplex.com/ to help with this scenario.
I'm just wondering if anyone else had used anything else to overcome this problem of conditional validation? If so, I'd be keen to hear.
Thanks again.
You can use conditional validation in jQuery and in fluentvalidation.
You can use a jQuery selector on the validation, something like this.
I'm not sure about the HTML element names.
$( "#myform" ).validate({ rules: {
proposalCode: {
required: "#proposalCode:visible"
} }
Check out jQuery Dependency expression for more information.
In FluentValidation validation (Server side only) you can use the 'When' expression.
RuleFor(r => r.proposalCode).NotNull().When(e => // Check selected value);
Check out the documentation here
I think this should get you started.

MVC3 - list information used for dropdownlist is null after post

I wish to show a DropDownList in a view and therfore include in my model (ExampleAddSetupDto) sent to a view a list of entries to populate the dropdownlist. That works fine, but if I have a validation error and redisplay the view with in incoming model my list is now null.
My Action is given below (note: the problem occurs if ModelState.IsValid fails). Also the Action method second parameter may look odd as I am using Autofac to inject the right service into the method).
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Add(ExampleAddSetupDto add, IServiceAddCommit<IExampleAddSetupDto, IExampleAddCommitDto> service)
{
if (ModelState.IsValid)
{
var response = service.Create(add);
if (response.IsValid)
{
TempData["message"] = "You successfully added a new Example Entry";
return View("AddSuccess", response);
}
//else errors, so copy the errors over to the ModelState
response.CopyErrorsToModelState(ModelState, add);
}
// Some validation error, so redisplay same view
return View(add);
}
My model looks like this:
public class ExampleAddSetupDto : IExampleAddSetupDto
{
[StringLength(50, MinimumLength = 2)]
public string Name { get; set; }
public int Option1Id { get; set; }
public int Option2Id { get; set; }
//-----------------------------
//now the properties for the drop down lists
public IList<Option1> PosibleEntriesForOption1 { get; set; }
public IList<Option2> PosibleEntriesForOption2 { get; set; }
}
My View is:
#model ServiceLayer.Example.DTOs.ExampleAddSetupDto
#{
ViewBag.Title = "Add";
}
<h2>Add</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Add an Example item</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-field">
#Html.Label("Option1")
#Html.DropDownListFor(model => model.Option1Id, new SelectList(Model.PosibleEntriesForOption1, "Option1Id", "OptionText"))
#Html.ValidationMessageFor(model => model.Option1Id)
</div>
<div class="editor-field">
#Html.Label("Option2")
#Html.DropDownListFor(model => model.Option2Id, new SelectList(Model.PosibleEntriesForOption2, "Option2Id", "OptionText"))
#Html.ValidationMessageFor(model => model.Option2Id)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
I understand that I need to return the Model.PosibleEntriesForOption in with the form. I tried using the Html.HiddenFor helper in the view to return the list, i.e.
#Html.HiddenFor(model => model.PosibleEntriesForOption1)
but this throws the error
'The value 'System.Collections.Generic.List`1[DataClasses.ExampleClasses.Option1]' is invalid.'.
Clearly I am missing something here and I would appreciate some advice on how to return the lists so that redisplaying the model won't cause an error.
If you are forced to persist the entire list between the two requests, for whatever reason, the best way I see to do this would be to use:
TempData["EnterUniqueKeyHere"] = PossibleEntriesForOption1;
to store it, and then:
PossibleEntriesForOption1 = TempData["EnterUniqueKeyHere"] as IList<Option1>;
to retrieve it.
Note that anything stored in TempData will be removed automatically after a single request.
If the Validation is failing, you need to load up the the dropdownlist with the List of values. Other wise, it will fail.
I believe, when you load up your view initially, it executes HttpGet Method. In HttpGet method you must be binding the Dropdownlist
When you submit page, it executes httpPost method, if all is well, it will submit. If validation fails, it will execute, HTTPPost method, but it cannot find any binding for dropdown.
So try this : In your case
if (ModelState.IsValid)
{
var response = service.Create(add);
if (response.IsValid)
{
TempData["message"] = "You successfully added a new Example Entry";
return View("AddSuccess", response);
}
//else errors, so copy the errors over to the ModelState
response.CopyErrorsToModelState(ModelState, add);
}
else //if validation fails, you need to reload the dropdown and display your view.
{
// populate your dropdown again
// You can add errors list into ModelState.
ViewData.ModelState.AddModelError("What is the error", "Error Message, "What needs to be done by user, to get it work");
return view(add)
}
On the get action for add, you will be creating the model with appropriate values for these 2 properties - PosibleEntriesForOption1 & PosibleEntriesForOption2
Since these are set properly & available on the view, the dropdown gets rendered correctly on the get.
Now on a POST, when validation fails, you have to set those properties again.
if (ModelState.IsValid)
{
// Do something
}
// before you redisplay the same view
// set the properties PosibleEntriesForOption1 & PosibleEntriesForOption2
// Some validation error, so redisplay same view
return View(add);
The TempData technique from Dan Nixon works once but if the validation fails again, the TempData entry is null. I guess I'll have to reload my lists too.

How to keep the same data when return to the view?

How to keep the same data when return to the view?
I tried to put return the form to the view, but it did not work.
Is there any good and simple way to do this?
[HttpPost]
public ActionResult Register(FormCollection form)
{
string name = form["Name"].Trim();
if (string.IsNullOrEmpty(name))
{
TempData["TempData"] = "Please provide your name ";
return View(form);
}
string email = form["Email"].Trim();
var isEmail = Regex.IsMatch(email, #"(\w+)#(\w+)\.(\w+)");
if (!isEmail)
{
TempData["TempData"] = "Sorry, your email is not correct.";
return View(form);
}
//do some things
}
Not sure why you would be using FormCollection in the post but maybe you come from a WebForms background. In MVC you should use ViewModels for the transport of your data to and from the Views.
By default the Register method in an MVC 3 app uses a ViewModel in the Register View. You should simply post it back. In fact, the default app has that already created for you if you didn't know as part of the Internet template.
The standard pattern is to have a ViewModel that represents your data that you will use in your View. For example, in your case:
public class RegisterViewModel {
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
}
Your controller the should contain 2 actions, a Get and a Post. The Get renders the View and is ready for the user to enter data. upon submitting the View the Post action is then called. The View sends the ViewModel to the action and the method then takes action to validate and save the data.
If there is a validation error with the data, it's very simple to return the ViewModel back to the View and display the error messages.
Here is the Get action:
public ActionResult Register() {
var model = new RegisterViewModel();
return View(model);
}
And here is the Post action:
[HttpPost]
public ActionResult Register(RegisterViewModel model) {
if(ModelState.IsValid) { // this validates the data, if something was required, etc...
// save the data here
}
return View(model); // else, if the model had validation errors, this will re-render the same view with the original data
}
Your view would look something like this
#model RegisterViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Name) <br />
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Email) <br />
#Html.ValidationMessageFor(model => model.Email)
</div>
}
Using other strategies to capture and save data in an MVC app is absolutely possible, it's a very extensible framework. But there is a specific pattern that makes MVC what it is and working against that pattern can sometimes prove difficult. For a beginner it is best to understand the preferred patterns and strategies first and then once understood very well, you can then adopt some of your own custom strategies to meet your needs. By then you should understand the system well enough to know what you need to change and where.
Happy coding!!

Problem with Edit View using ViewModel

I have a complex object that I want to use in an edit view. To simplify things I have created a ViewModel and have successfully created the edit view page, and everything renders correctly. When I hit save, everything falls apart.
The ViewModel is as follows:
public class ClosureEditViewModel
{
public Model.Closure Closure { get; set; }
public Model.School School { get; set; }
public Model.ClosureDetail CurrentDetails { get; set; }
}
Some of the View is as follows:
<div class="display-label">School</div>
<div class="display-field">
#Html.DisplayFor(model => model.Closure.School.Name)
</div>
<div class="display-label">Closed</div>
<div class="display-field">
#Html.DisplayFor(model => model.Closure.Logged)
</div>
....
<div class="editor-label">
#Html.LabelFor(model => model.CurrentDetails.DateOpening, "Date Opening (dd/mm/yyyy)")
</div>
<div class="editor-field">
#Html.TextBox("DateOpening", Model.CurrentDetails.DateOpening.ToString("dd/MM/yyyy"))
#Html.ValidationMessageFor(model => model.CurrentDetails.DateOpening)
</div>
....
<tr>
<td>
#Html.CheckBoxFor(model => model.CurrentDetails.Nursery, (Model.School.Nursery ? null : new { #disabled = "disabled" }))
</td>
The important parts of the controller are as follows:
public ActionResult Edit(int id)
{
Data.IClosureReasonRepository reasonRepository = new Data.SqlServer.Repositories.ClosureReasonRepository(UnitOfWork);
IEnumerable<Model.ClosureReason> reasons = reasonRepository.GetAll();
Model.Closure closure = ClosureRepository.GetClosure(id);
Model.ClosureDetail currentDetail = closure.ClosureDetails.Last();
ViewModels.ClosureEditViewModel editClosure = new ViewModels.ClosureEditViewModel() { Closure = closure, School = closure.School, CurrentDetails = closure.ClosureDetails.Last() };
ViewBag.ReasonId = new SelectList(reasons, "Id", "Name", currentDetail.ReasonId);
return View(editClosure);
}
[HttpPost]
public ActionResult Edit(ViewModels.ClosureEditViewModel newDetail)
{
//if (ModelState.IsValid)
//{
//}
Data.IClosureReasonRepository reasonRepository = new Data.SqlServer.Repositories.ClosureReasonRepository(UnitOfWork);
IEnumerable<Model.ClosureReason> reasons = reasonRepository.GetAll();
ViewBag.ReasonId = new SelectList(reasons, "Id", "Name", newDetail.CurrentDetails.ReasonId);
return View(newDetail);
}
When I hit save the following message appears:
Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 94: </td>
Line 95: <td>
Line 96: #Html.CheckBoxFor(model => model.CurrentDetails.P1, (Model.School.P1 ? null : new { #disabled = "disabled" }))
Line 97: </td>
Line 98: <td>
I just can't figure out why it is having problems with the School property but neither of the other two.
James :-)
It seems that Model.School is null when you render the view once again in the POST action. Make sure that it isn't null because in your view you don't have a single input field bound to the School property => this property will be null inside your POST controller action.
[HttpPost]
public ActionResult Edit(ClosureEditViewModel viewModel)
{
... some operations
// Make sure that viewModel.School is not null
// Remember that the checkbox is bound to CurrentDetails.P1 so
// when you post to this action there is nothing that will initialize
// the School property => you should do whatever you did in your GET
// action in order to initialize this property before returning the view
return View(viewModel);
}

ASP.NET MVC3 edit action not updating records in database

I have an edit action configured that is not updating records but not throwing any exceptions. The update appears to have worked but the changes are not reflected in the database.
I am using EF and MVC3, i have an interface that defines save customer
public interface ICustomerRepository
{
//allows a sequence of customers to be displayed
IQueryable<Customer> Customers { get; }
//saves edits to customer records
void SaveCustomer(Customer customer);
}
Then my implementation of this
public void SaveCustomer(Customer customer)
{
if (customer.CustomerId == 0)
{
context.Customers.Add(customer);
}
context.SaveChanges();
}
Then in the controller my get and post actions
public ViewResult Edit(int customerId)
{
Customer customer = repository.Customers.FirstOrDefault(c => c.CustomerId == customerId);
return View(customer);
}
[HttpPost]
public ActionResult Edit(Customer customer)
{
if (ModelState.IsValid)
{
repository.SaveCustomer(customer);
TempData["message"] = string.Format("{0} has been saved", customer.CustomerName);
return RedirectToAction("Index");
}
else
{
//there is something wrong with the data values
return View(customer);
}
}
Then in my view i have
#model CustomerOrders.Domain.Entities.Customer
#{
ViewBag.Title = "Admin: Edit" + #Model.CustomerName;
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h1>Edit #Model.CustomerName</h1>
using (Html.BeginForm("Edit", "Admin"))
{
<div class="left-column">
<div class="editor">#Html.EditorFor(model => model.CustomerId)</div>
<div class="label-for">#Html.LabelFor(model => model.CustomerName)</div>
<div class="editor">#Html.EditorFor(model => model.CustomerName)</div>
#Html.ValidationMessageFor(model => model.CustomerName)
</div>
<div class="middle-column">
<div class="label-for">#Html.LabelFor(model => model.PrimaryContactName)</div>
<div class="editor">#Html.EditorFor(model => model.PrimaryContactName)</div>
<div class="label-for">#Html.LabelFor(model => model.PrimaryContactNo)</div>
<div class="editor">#Html.EditorFor(model => model.PrimaryContactNo)</div>
</div>
<div class="right-column">
<input type="submit" value="Save" />
#Html.ActionLink("Cancel and return to list", "Index")
</div>
}
I also have a create method wired up to the same edit action and view which works fine. Not sure where i am going wrong, im new to MVC3 and not sure if my edit implementation is correct?
The simple answer is because your working in a stateless environment, unless your using self tracking entities, you need to Attach the object to EF's graph.
I've had problems with edit in the past though. So i ended up going to the DB to fetch the object first, then merging in the changes i need, then doing Save.
You need to seperate your Create/Edit actions out. Simply checking if the ID > 0 to deem an edit is not enough, be more explicit.
So to sum up:
Have one action method for new objects. Use context.AddObject in this scenario.
Have another action method for modifying objects. Go get the object from the repository, merge in your changes (left to right, or auto mapper, or TryUpdateModel), and do context.SaveChanges.
Overall, it's a pain. Many developers (myself included) have gone through what you have.

Resources