I have a database with a one-to-many relationship that I'm having a difficult time modeling in ASP.NET MVC. For the sake of simplicity, let's say table A represents office buildings, table B represents employees and table C creates the one-to-many relation between employees and buildings to indicate which employees have access to a particular building.
Employee
EmployeeId - int
FirstName - string
LastName - string
Office
OfficeId - int
Location - string
EmployeeOffice
EmployeeId - int
OfficeId - int
When new employees come on board, I'd like to assign them to any office buildings they would be able to access. To do this, the UI calls for check boxes for each individual office building. Checking the boxes grants the user access to the related office building.
[ ] - Office 1
[ ] - Office 2
[ ] - Office 3
[ ] - Office 4
My concern is, offices should be dynamic. In other words, an office (or offices) can come or go at any time.
The model is actually more complicated than what I have depicted. As such, I have a CreateEmployeeViewModel, which contains properties (with annotations) as follows:
public class CreateEmployeeViewModel
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public IDictionary Offices
{
get;
private set;
}
public CreateEmployeeViewModel(IDictionary offices)
{
Offices = offices;
}
}
My view resembles the following
<div>
<%: Html.LabelFor(model => model.FirstName) %>
</div>
<div>
<%: Html.TextBoxFor(model => model.FirstName) %>
<%: Html.ValidationMessageFor(model => model.FirstName) %>
</div>
<div>
<%: Html.LabelFor(model => model.LastName) %>
</div>
<div>
<%: Html.TextBoxFor(model => model.LastName) %>
<%: Html.ValidationMessageFor(model => model.LastName) %>
</div>
<!-- This is really ugly, so I welcome any suggested updates -->
<% foreach (KeyValuePair<int, string> office in Model.Offices) { %>
<label for="Office<%: office.Key %>"><%: office.Value %></label>
<input id="Office<%: office.Key %>" type="checkbox" value="<%: office.Key %>" />
<% } %>
When I click the submit button for the form, I expect to get back the strongly typed view model, in addition to the check boxes so that I know which offices to assign users to. I created my action method like the following:
public ActionResult Create(CreateUserViewModel user, FormCollection collection)
The reason for the additional FormCollection object is I was hoping I could get form values in addition to the ones found on the view model (e.g., the check boxes). Unfortunately, however, the collection contains only information for the properties found in my view model.
What is the right way to handle forms with this design in ASP.NET MVC 2.0?
You need to add the "name" attribute to your checkbox input elements - otherwise they will not show up in the FormCollection.
Related
I have build my entities via database-first, since I have to use an existing db.
Now, let's say I've got the entity customer and country and I want to edit the customer.
My View should contain something like First Name, Last Name and Country which is a DropdownList from all countries in the entity.
For that I created a CustomerViewModel which uses these two entities. But I dont' know if all of this is right, since some things don't work.
First the code of my CustomerViewModel:
public class CustomerViewModel
{
public CUSTOMER customer { get; set; }
public COUNTRY country { get; set; }
public IEnumerable<SelectListItem> countryList { get; set; }
MyEFEntities db = new MyEFEntities();
public CustomerViewModel(int id)
{
IEnumerable<CUSTOMER> customerList = from c in db.CUSTOMER
where c.CUSTOMERNO== id
select c;
customer = customerList.First();
var countryListTemp = new List<String>();
var countryListQry = from s in db.COUNTRY
select s.COUNTRY_ABBREVIATION;
countryListTemp.AddRange(countryListQry);
countryList = new SelectList(countryListTemp);
}
}
Then the CustomerController.cs:
public ViewResult CustomerData(int id = 0)
{
// if (id == 0) { ... }
var viewModel = new CustomerViewModel(id);
return View(viewModel);
}
[HttpPost]
public ActionResult CustomerData(CustomerViewModel model)
{
db.Entry(model.customer).State = System.Data.EntityState.Modified;
db.SaveChanges();
return View(model);
}
And last but not least the CustomerData.cshtml:
#model MyApp.ViewModels.CustomerViewModel
#{
ViewBag.Title = "Customer";
}
<h2>
Customer</h2>
#using (Html.BeginForm("CustomerData", "Customer"))
{
#Html.ValidationSummary(true)
<div id="tabs">
<ul>
<li>Data</li>
<li>Hobbies</li>
<li>Stuff</li>
</ul>
<div id="data">
<div class="editor-label">
#Html.Encode("Country:")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.country.COUNTRY_ABBREVIATION, Model.countryList, Model.customer.ADDRESS.COUNTRY.COUNTRY_ABBREVIATION)
</div>
<div class="editor-label">
#Html.Encode("Last name:")
#Html.TextBox("lastName", #Model.customer.ADDRESS.NAME)
</div>
<div class="editor-label">
#Html.Encode("First Name:")
#Html.TextBox("firstName", #Model.customer.ADDRESS.FIRSTNAME)
</div>
</div>
<div id="hobbies">
<p>
Hobbies
</p>
</div>
<div id="stuff">
<p>
Stuff
</p>
</div>
</div>
<p>
<input type="submit" value="Save" />
</p>
}
Viewing works great. I get to the URL http://localhost:12345/Customer/CustomerData/4711 and can see the current values for that customer.
Now, I'm missing some stuff.
The Save-button doesn't work. If I click it, I got an error message, that no parameterless constructor was found for this object. But I want to pass the ViewModel back to the controller. How to overload the Html.BeginForm() method?!
How to I store the changed values from the customer? Is is done by editing the text-fields or do I have to use Html.TextboxFor() instead of Html.Textbox? This is so complicated for a beginner. I'm not into the LINQ style at all.
The Dropdownlist doesn't work as supposed to. The country the customer has already is twice in it. It seems, that the third parameter does not preselct an item but add a default one. Would Html.Dropdownlist be better?
This design is bad, I'm afraid. Never do DB access from the model, that is what the Controller is for. The model should just hold data that the Controller feeds to it.
If you factor out DB access, or any type of logic, from your model, you will find that your system becomes much easier to set up and maintain, and it probably even solves the problems you mention.
I'm new to ASP.Net MVC and have a question regarding a master detail input form.
My database has a Child Table with foreign key relationships to the Physician, Immunization and Parent Table. I've used Linq to SQL to create my model.
I've created the controller and view for Child. The user will come to the form and submit everything all at once - a Child, their Physician info, one or many Parents and one or many Vaccinations.
I'm not sure how to approach this. Do I need a controller for Vaccinations, Parents etc?
My pre MVC app simply grabbed everything from the web form and populated all the
I typically have one controller that supports several views (add, edit, delete, etc.). I read from my database into a model that has fields for each piece of data that you want in the view. You then pass the model to the view so it can render it.
After the form is submitted, you'll get a model back from it as a parameter to the controller. You then update the database as needed.
Here's some code. I didn't try compiling it so your mileage may vary.
public class ParentModel
{
// Parent fields
public string FirstName { get; set; }
public string LastName { get; set; }
IList<ChildModel> Children { get; set; }
}
public class ChildModel
{
// Child fields
public int Age { get; set; }
}
public ActionResult MyAction()
{
// Grab a model with the children property filled out
ParentModel myModel = GetFromDatabase();
return View("MyView", myModel);
}
The view (abbreviated):
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<ParentModel>" %>
.
.
.
<% using (Html.BeginForm("MyAction", "MyController", FormMethod.Post)) %>
<% {%>
<%= Html.ValidationSummary(true) %>
<table>
<tr valign="top">
<td>
<%:Html.LabelFor(a => a.FirstName)%>:
</td>
<td>
<%:Html.EditorFor(a => a.FirstName)%>
<%:Html.ValidationMessageFor(a => a.FirstName)%>
</td>
</tr>
<tr valign="top">
<td>
<%:Html.LabelFor(a => a.LastName)%>:
</td>
<td>
<%:Html.EditorFor(a => a.LastName)%>
<%:Html.ValidationMessageFor(a => a.LastName)%>
</td>
</tr>
</table>
.
.
.
<%} %>
You only need the master controllers. The trick for editing list of thinks (the child objects) is explained in this haacked post
Basically you need to follow this conventions and MVC will fill an array of child object in the post method:
<% for (int i = 0; i < 3; i++) { %>
<%: Html.TextBoxFor(m => m[i].Title) %>
<%: Html.TextBoxFor(m => m[i].Author) %>
<%: Html.TextBoxFor(m => m[i].DatePublished) %>
<% } %>
I am trying out ASP.Net MVC2 by building a small sample website which, amongst other features provides the user with a 'Contact Us' page. The idea is to allow a user to enter their name, email address, message subject and message. To send the message the user clicks on an ActionLink. This is the view:
<% Html.BeginForm(); %>
<div>
<%: Html.Label("Name")%>
<br />
<%: Html.TextBox("txtName", "",new { style = "width:100%" })%>
<br />
<%: Html.Label("Email address")%>
<br />
<%: Html.TextBox("txtEmail", "", new { style = "width:100%" })%>
<br />
<%: Html.Label("Subject")%>
<br />
<%: Html.TextBox("txtSubject", "", new { style = "width:100%" })%>
<br />
<%: Html.Label("Message")%>
<br />
<%: Html.TextBox("txtMessage", "", new { style = "width:100%" })%>
</div>
<div style='text-align: right;'>
<%:
Html.ActionLink("Send", "SentEmail", new { name = Html.g, sender = "txtEmail", subject = "txtSubject", message="txtMessage" })
%>
</div>
<% Html.EndForm(); %>
The idea is once the ActionLink has been clicked a method in the controller is called into which the username, email address, subject and message will be passed. This is the method in the controller:
public ActionResult SentEmail(string name, string sender, string subject, string message)
{
//Send email here and then display message contents to user.
ViewData["Name"] = name;
ViewData["Message"] = message;
ViewData["ThankyouMessage"] = "Thank you for contacting us. We will be in touch as soon as possible.";
return View();
}
However... when I click the link the values which are passed into the method are null. I have tried creating a route to do this but it doesn't work either. Should I be using another method?
Thank you,
Morris
Actually to achieve what you want to is easier than in your sample. Never heard about Model classes, Model Binder and strong typed views? Here thery are
Model class
public class ContactUsModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Subject { get; set; }
public string Message { get; set; }
}
Then in your controller you should have two action: the first that show the form with default values and the second that receive the form with the data placed by the user. These two actions maps exactly to the HttpGet and HttPost verbs.
[HttpGet]
public virtual ActionResult ContactUs() {
ContactUsModel model = new ContactUsModel();
return View(model);
}
[HttpPost]
public virtual ActionResult ContactUs( ContactUsModel model ) {
//e.g. Save the contact request to database
}
To use this your view shal be strong typed to the ContactUsModel class
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactUsModel>" %>
<% using (Html.BeginForm()) { %>
<div>
<%: Html.LabelFor(model => model.Name) %><br />
<%: Html.TextBoxFor(model => model.Name, new { style = "width:100%" })%>
</div>
<div>
<%: Html.LabelFor(model => model.Email) %><br />
<%: Html.TextBoxFor(model => model.EMail, new { style = "width:100%" })%>
</div>
<div>
<%: Html.LabelFor(model => model.Subject) %><br />
<%: Html.TextBoxFor(model => model.Subject, new { style = "width:100%" })%>
</div>
<div>
<%: Html.LabelFor(model => model.Message) %><br />
<%: Html.TextAreaFor(model => model.Message, new { style = "width:100%" })%>
</div>
<div>
<input type="submit" value="Save" />
</div>
<% } %>
the magic of everything this is called ModelBinder. Please read more and more about MVC here.
The action link isn't going to trigger a http post nor will it pass in the values of your form fields, just a http get and not passing through any form data - ideally you'd use an input submit button to post the data. What is certain is that it is good practise that any request that causes creating/updating of data should be done via a http post.
Submit button would just be like.
<input type="submit" value="Send" />
You then have several ways of accessing the form data firstly you could use a FormCollection to access the data
[HttpPost]
public ActionResult SendEmail(FormCollection collection)
{
string email = collection["txtEmail"];
...
}
Secondly you could use the method parameters and rely on model binding, but you must make sure field names match the parameter name so
<%: Html.TextBox("txtEmail", "", new { style = "width:100%" })%>
...would require...
[HttpPost]
public ActionResult SendEmail(string txtEmail)
{
...
}
If this form isn't being posted to the same action thats return the view then you'd also need to change your begin form tag, ideal you should use 'using' with it as well. So you'd get:
<% using (Html.BeginForm("SendEmail", "<controller-name>"))
{ %>
.... form fields in here ...
<input type="submit" value="Send" />
<% } %>
If the button isn't suitable for your design you could use something like:
<input type="image" src="<%: Url.Content("~/Content/images/myimage.gif") %>" value="Send" />
This would have the same effect. To trigger a post from an a tag though you'd need to look at using javascript, I can't remember the exact syntax but off hand I think if you used jquery you'd be looking at something like: (form a single form page only)
Send
But then you create a dependency on javascript where as really you should try have your site degrade gracefully so it can be used by visitors with javascript disabled. There are perhaps more advanced way of achieving this whilst meeting design requirements but that can get heavily into client side code which is probably outside of what you want for your sample.
I have a hard time in asp.net MVC2 trying to get the checked values of different checkbox.
Here is my view
<div id="RoleSelection">
<ul>
<% foreach (var roles in Model.Roles)
{
%>
<li>
<input type="checkbox" name="roles" value="<%: roles %>" /> <%: roles %>
</li>
<%
}
%>
</ul>
</div>
My model:
[LocalizedDisplayName("Role", NameResourceType = typeof(UserResources))]
public string Role { get; set; }
public IEnumerable<string> Roles { get; set; }
So basically here I'm trying to figure out how to get all the checked checkbox from my form!
Thank you
Use the Name attribute instead of the id attribute. The id must be unique amongst all element.
The name attribute in your case will allow you to regroup the multiple checkbox into a single group.
<input type="checkbox" name="roles" value="<%: roles %>" /> <%: roles %>
Hi in my db there is a field "date_start" type integer.
This is the part of form for this field
<div class="editor-label">
<%: Html.LabelFor(model => model.date_start) %>
</div>
<div class="editor-field">
<%: Html.TextBoxFor(model => model.date_start, new { #class = "calendar" })%>
<%: Html.ValidationMessageFor(model => model.date_start) %>
</div>
In the form i want that the field's format is date type.
and memorize it in int type after calling a my function for the convertion.
How can i manage it?
thanks
Okay. I'm assuming you have a Linq-generated class, which we'll call LGC, and in that class is a property of type int called date_start.
To start, wrap LGC in a another class:
public class LGCPageForm
{
public LGC BaseModel { get; set; }
public DateTime date_start_as_date { get; set; }
}
Set the LGCPageForm class to be your Page's Model class. That'll require some refactoring, but I think it's your best option. Now, in your form, you'll have something like:
<div class="editor-label">
<%: Html.LabelFor(model => model.date_start_as_date) %>
<%: Html.TextBoxFor(model => model.date_start_as_date, new { #class = "calendar" })%>
<%: Html.ValidationMessageFor(model => model.date_start_as_date) %>
</div>
Then, capturing your postback. You'll definitely want to implement validation via DataAnnotations, but this should get you started in the right direction:
[HttpPost]
public ActionResult SubmitWeirdDate(LGCPageForm form)
{
//I'm not sure if this is the conversion you want?
form.LGC.date_start = form.LGC.date_start_as_date.Ticks;
}
There's the UNIX timestamp for this, which is basically an integer (and can be stored as such). I have no clue how to implement this in ASP.NET though. Check if there's a class named TimeStamp which can take an integer upon construct (or later on), then look for a form element that takes a TimeStamp.
That said, I haven't got any experience at all in ASP.NET.