ASP.NET MVC Create on a Master-Detail Relationship - asp.net-mvc

I am displaying an list of Items for a given Order. When a user clicks Add Item I redirect to the Item / Create page. This page collects that necessary input but also needs to know the order id that the item belongs to. What is the appropriate way to pass the OrderID to the Item / Create so that it survives the form post when the newly created item is saved.
I've played with TempData and writing the id out on the detail page via Html.Encode(). It gets me part of the way there in that the id shows up on the item form but the value is lost when the form submits and posts. I suppose because its not part of the formcollection. I am guessing my workaround is not the best way and would like to know if anyone can point out the proper way to do this in asp.net mvc.

I do this by creating a new route for the Item controller that includes the OrderId. It doesn't make sense to have an Item without an Order, so the OrderId is required using the constraints parameter.
routes.MapRoute(
"OrderItems",
"Item/{action}/{orderId}/{id}",
new { controller = "Item" },
new { orderId = #"d+" }
);
So the url would look like http://<sitename>/Item/Create/8, where 8 is the OrderId for which to create an item. Something similar could be done for Delete action routes with http://<sitename>/Item/Delete/8/5, where 8 is the OrderId and 5 is the ItemId.
Your Action methods would look like this:
public ActionResult Create(int orderId)
public ActionResult Delete(int orderId, int id)
You could also set it up so that the urls looked like http://<sitename>/Order/8/Item/Create and http://<sitename>/Order/8/Item/Delete/5 if that seems to more clearly show what's going on.
Then the route would look like this:
routes.MapRoute(
"OrderItems",
"Order/{orderId}/Item/{action}/{id}",
new { controller = "Item" },
new { orderId = #"d+" }
);

I've used this sequence (sorry if there are mistakes, I took this from a working example and modified it for your question):
1) In the Order.Details view (assume Model is of type Order):
...
<%= Html.ActionLink("Create New Item", "Create", "OrderItem", new { orderId = Model.ID }, null)%>
...
2) In the OrderItem.Create action:
public ActionResult Create(int orderId)
{
ViewData["orderId"] = orderId;
return View();
}
3) In the OrderItem.Create view:
...
<% using (Html.BeginForm(new { orderId = ViewData["orderId"] }))
...
4) In the OrderItem.Create POST action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(int orderId)
{
// omitted code to create item, associated with orderId
return RedirectToAction("Details", "Order", new { orderId = orderId });
}
If anyone can think of how to improve on this, or of a better way altogether, please chime in, I'm sort of new to this myself so I'd like to improve.

To round-trip a field that's not part of the normal data entry, I generally use a hidden field in the view, like this:
<%= Html.Hidden("OrderID", Model.OrderID) %>
It looks like a form field, acts like a form field, but the user cannot see it. Make sure you push the correct OrderID into your model from the controller.

Related

Custom MVC Routing

Okie, I am trying to finish a product dispkay for a client, in my code I have this
#foreach (var item in Model)
{
<div class="itemcontainer">
<p class="button">#Html.ActionLink(item.Category, item.Category) (#item.Count)</p>
</div>
}
Which gives me the link (URL) of Products/Categories, now what do I need to get to my ultimate goal of (for example) Products/Braceletsss. Do I have to write a custom route, if so can someone show me an example, I'm still trying to get my head around this.
**EDIT*
I can provide more code if it's needed :)
[HttpGet, Route("products/{categoryName}")]
public IActionResult GetProductsByCategoryName(string categoryName) {
... code to retrieve products by category name
The above is one way to do it, the way that I prefer at least. When you access the route /products/nine-millimeter-handguns, then in your action, the categoryName variable will have the value nine-millimeter-handguns. You can then use that string value to look up all of the products in that category and return them to the client.
The other way to do it is in your global route config in Startup.cs. If you do it this way, you don't need the [Route] attribute on the action method:
public void Configure(IApplicationBuilder app) {
...
app.UseMvc(routes => {
routes.MapRoute(null, "products/{categoryName}", new {
controller = "Products", action = "GetProductsByCategoryName"
});
});
}
I prefer the former attribute approach because it keeps the routes closer to the controllers & actions that they map to. But both will accomplish the same thing.
In order to render a link to this route from a view, you would pass in the categoryName to the ActionLink html helper method:
#Html.ActionLink(item.Category, item.Category, new {
categoryName = "nine-millimeter-handguns"
})

MVC Routing Issue - refreshing same page

I have an Edit page and once the form is submitted I'm refreshing the page instead of redirecting the user to the Index page. To do so I'm saving the ID of the item in a temp variable and then use it to redirect the user to the edit page using the temp variable ID. Something like this:
[HttpGet]
public ActionResult Edit(Guid id)
{
TempData["CategoryID"] = id;
Category c = new CategoriesBL().GetCategory(id);
return View(c);
}
[HttpPost]
public ActionResult Edit(Category c)
{
new CategoriesBL().UpdateCategory(c);
return RedirectToAction("Edit", (Guid)TempData["CategoryID"]);
}
That's working fine. However I have two methods in a different form on the same page and whenever I submit either of these two methods the redirection is not working and I'm getting an exception.
One of the methods that's not working:
[HttpPost]
public ActionResult AddNewThumbnail()
{
List<byte[]> thumbs = new List<byte[]>();
for (int i = 0; i < Request.Files.Count; i++)
{
thumbs.Add(ConvertToByteArray(Request.Files[i].InputStream));
}
new CategoriesBL().AddCategoryThumbnail(thumbs, (Guid)TempData["CategoryID"]);
return RedirectToAction("Edit", (Guid)TempData["CategoryID"]);
}
Exception:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Guid'....
I think it's an issue with routing but the fact is that the same implementation is used and it's working on one form and not the other. I'm not sure whether I'm doing something wrong or if there's any better way to do this.
Note: I have debugged the code several times and the ID I'm passing to the method does have a value in it. However when the page reloads the URL has no ID present.
Debugging
The problem seems to be due to the different forms I'm using. The first form I'm just editing text and it is like so:
#using (Html.BeginForm()) {
// ....
}
In the second form I'm saving and uploading images so the form has to be different
#using (Html.BeginForm("AddNewThumbnail", "Category", FormMethod.Post, new { enctype = "multipart/form-data" })) {
// ....
}
Somehow when I changed the form to the 'normal' one everything worked. But of course I can't use it as I want to save images from this form.
pass the value from your view. Something like this
[HttpPost]
public ActionResult Edit(Category c, FormCollection f)
{
Guid categoryID = (Guid)f["catergoryID"];
new CategoriesBL().UpdateCategory(c);
return RedirectToAction("Edit", catergoryID);
}
In your first example, you have initialisation:
TempData["CategoryID"] = id;
in GET method. So, you have to init your (Guid)TempData["CategoryID"] before you try to access it here:
return RedirectToAction("Edit", (Guid)TempData["CategoryID"]);

What's the proper way to get values for a dropdown from a table?

Lets say I have a a table called... Person, I guess, and it has the columns Name, Email, AgeGroupId.
AgeGroupId is an int, which relates to the table AgeGroup.
Agegroup only has two columns: Id, and AgeGroupName.
Now, In my view, which is a page to edit a 'person', I want a dropdown box that has EVERY AgeGroupName as the text, and the Id as the value.
This way, later on, I can add a new 'age group' into my age group table, and all my drop down boxes will update. Or is there a better way I should be doing this?
I'm currently passing in my model, and doing this:
#Html.DropDownListFor(model => model.AgeGroupId, #agegroups)
and at the top of the view, i'm making a SelectList with hardcoded values. I don't want them hard-coded in, I don't think? What would I do instead of #agegroup to get the list from the AgeGroups table?
Thanks.
The proper way is using ViewModels. Basically you'll have to create a viewmodel for your view and not pass the db result directly to your view. Something like:
public class PersonViewModel
{
public Person Person {get ; set;}
public Dictionary<int, string> AgeGroups { get; set; }
public PersonViewModel() {}
public PersonViewModel(int personId)
{
var ctx = new Context();
this.Person = ctx.Persons.SingleOrDefault(p => p.Id == personId);
foreach(var ageGroup in ctx.AgeGroups)
{
this.AgeGroups.Add(ageGroup.Id, ageGroup.AgeGroupName);
}
}
Then your controller method will look like this:
public ActionResult Add(PersonViewModel vm)
{
var ctx = new Context();
if(ModelState.IsValid)
{
ctx.Persons.Add(vm.Person);
return View("Index");
}
return View(vm);
}
And in your view, simply:
#Html.DropDownListFor(model => model.Person.AgeGroupId,
new SelectList(model.AgeGroups, "Id", "AgeGroupName"))
Of course, your view's model is now PersonViewModel.
Update
seems like ASP.NET MVC 3 Tools update adds drop down for relations by default. More info here.
The correct way seems to be from Kamyars edit, on the msdn blog here:
http://blogs.msdn.com/b/joecar/archive/2011/04/15/asp-net-mvc-3-tools-update.aspx

How to dynamically display a list of both name and value of a String/boolean pair on MVC in ASP.NET MVC

I need to dynamically display a list of both name and value of string/boolean pair on MVC view (*.cshtml) based on user selection. Specifically, both name and value of a string and boolean pair are different in each list. There are more one list that user can select. For example:
FruitName: Apple (string:string)
IsRipen: true (string:boolean)
BookName: C#
IsSold: false
One list type is defined as one report type. A list can be retrieved from report programmatically.
Possible Solution 1
Since the data type of name and value in the list are fixed (string, boolean), one idea is to build a collection as a MVC model, and pass that model to MVC razor view. The question is that how to display the name on the view,
#Html.LabelFor(model => model.Names[0]) //how to display it as 'Fruit Name'
Possible Solution 2
In ASP.NET web form, there is user control whihch can be loaded dynamically. There is partial view in ASP.NET MVC. Can the partial view do what I want? Or is there better solution.
I am new to MVC, any ideal or example would be very much appreicated.
If I understand you correctly, what you want to do is create a Partial View and call it using an action in your controller.
First, do something like this in your controller
// partial
public ActionResult report(string reportName)
{
return View(reportModel.Name);
}
Then, make sure there is a partial view that shares the name of your report.
You can then call the partial view like this:
#{Html.RenderAction("report", "Home", new{ reportName="report" });}
The line above will render the partial view Report.cshtml into the parent view or master layout.
edit
Ok. so every report has a list of name value pairs right?
Assuming that, we can write an action that passes that list to your view.
public ActionResult DisplayPairs()
{
NameValueCollection pairs = new System.Collections.Specialized.NameValueCollection();
pairs.Add("Name", "Value");
pairs.Add("Name2", "Value2");
pairs.Add("Name3", "Value3");
pairs.Add("Name4", "Value4");
return View(pairs);
}
Then we have the DisplayPairs View:
#model System.Collections.Specialized.NameValueCollection
#{
ViewBag.Title = "DisplayPairs";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>DisplayPairs</h2>
<table>
#foreach(string key in Model.AllKeys){
<tr><th>#key</th><td>#Model[key]</td></tr>
}
</table>
Which displays:
Name Value
Name2 Value2
Name3 Value3
Name4 Value4
I hope this helps
Why don't you just create a wrapper class that contains all the data you need?
public class ReportViewModel
{
IEnumerable<KeyValuePair<object, object>> Items { get; set; }
public ReportViewModel()
{ Items = new List<KeyValuePair<object, object>>() }
}
You can then create your model like so:
var model = new ReportViewModel();
model.Items.Add("BookName", "C#");
model.Items.Add("IsSold", false);
return View(model);
In your view, you just iterate over the KeyValuePairs, and print the key and value:
<ul>
#foreach(var kvp in Model.Items)
{
<li>#kvp.Key: #kvp.Value</li>
}
</ul>
(Excuse me if my razor syntax is buggy - I've not worked very much with it as of yet...)
Also, you might have to add calls to ToSting() if you have odd types of objects in your list. I think the framework does that for you if it needs to, but I'm not sure...

ASP.NET MVC - drop down list selection - partial views and model binding

I'm fairly new to ASP.NET MVC and am trying to work out the best way to do this. It's probably simple but I just want to do things correctly so I thought I'd ask.
Lets say I have a model that is this:
Task - Id, Description, AssignedStaffMember
StaffMember - Id, FirstName, LastName
and in my view I want to create a new task. I make a strongly typed Razor view, and can use EditorFor to create textboxes for Description but what about AssignedStaffMember?
I want a drop down list of all current staff and have the option of selecting one, then this gets submitted to an action method which is
NewTask(string description, StaffMember assignedStaffMember)
either that or I could have an int for staffId instead of the StaffMember object and look it up in the action method.
What is the best way to do this? I need to go to the database to get the list off staff, so here's what I thought:
Make a partial view for the listing of staff drop down, which will be used a few times and use #Html.Action("ListStaff", "Staff") to call it. The action method then has
public ActionResult ListStaff()
{
IEnumerable<StaffMember> model = _serviceLayer.GetAllStaff();
return PartialView(model);
}
However I'm not sure on how this will work with model binding, my understanding is that it has to have the correct name for the form to submit it, I'd need to pass the name to the partial view to put on the element I guess?
Instead of having it call a controller to get the staff, make a ViewModel that contains my Task and a IEnumerable possibleStaff collection. possibly send this information to a partial view.
a Html Helper ?
EditorFor could somehow be used?
which one (or is there more) would be best? and how would I do the model binding?
Here is one way to do this. Create a TaskDetailsViewModel
public class TaskDetailsViewModel
{
public TaskDetailsViewModel()
{
this.Task = new Task();
this.StaffMembers = new List<StaffMember>();
}
public Task Task { get; set; }
public IEnumerable<StaffMember> StaffMembers { get; set; }
}
In Controller
public ActionResult Edit(int id)
{
var task = taskRepository.GetTaskByID(id);
var taskDetailsViewModel = new TaskDetailsViewModel();
// Populate taskDetailsViewModel from task and staff
return View(taskDetailsViewModel);
}
[HttpPost]
public ActionResult Edit(TaskDetailsViewModel taskDetailsViewModel)
{
if (ModelState.IsValid)
{
taskRepository.Save(taskDetailsViewModel.Task);
}
else
{
// Show Error
}
return View(taskDetailsViewModel);
}
In View (bound strongly to TaskDetailsViewModel)
#Html.DropDownListFor(model => model.Task.AssignedStaffMember, new SelectList(Model.StaffMembers, "ID", "FirstName", Model.Task.AssignedStaffMember))
#Html.ValidationMessageFor(model => model.Task.AssignedStaffMember)

Resources