MVC2 Add Collection of Object to Another Object - asp.net-mvc-2

Not sure if this has been asked before, but I have an object (Restaurant) and I have a details view of that restaurant. On that details view, I want to have a list of cuisines that the restaurant offers. I want to also have a dropdownlist of the available cuisines and have the ability to click an "Add" button and it adds that cuisine to the Restaurant. I have a RestaurantCuisine table (using Entity Framework) that has a foreign key of the ID of the cuisine from a Cuisine table that has a primary key of ID.
So, now, my question, how do I do this? I sortof understand the concept behind the Create view and then the Create view post, but in this case, I'm not posting back the Restaurant object. So, how do I get the restaurant ID and the Cuisine ID so that I can add that to the restaurant cuisine collection?
Ok, so, now after investigating more, I believe I have asked the wrong question. My actual issue, is that I have a View that displays a Restaurant's details and I have a Details function in my controller for it. This works fine. The next step I want to do is have a dropdownlist with available Cuisines that this restaurant offers and have an 'Add' button next to it. And if you click on the 'Add' button, it adds the value of the item in the dropdownlist to the collection of cuisines setup in the Restaurant object.
Is this "easily" possible? I'm beginning to lose my faith in MVC2 :(
Ok, last try here. Let me ask this, does anyone know how to have a dropdownlist (I have now got this created) and have an "Add" button next to it, and get the selected value from that dropdownlist?

For those of you attempting to use VB.Net with MVC, I'm saying a prayer for you. It's rough. There are rarely any examples, and some syntax is not available that IS available in C#. So, as far as the answer to my original question. It seems as though it was my inexperience that caused the instant flare of the question.
After analying my problem, I figured out a way around it. First of all, let me clarify what I was attempting to do then explain what I did. The goal was to have an object (Restaurant in my case). And I wanted to have a list of properties (cuisines in my case) displayed on the Details view that I could assign to that object (Restaurant). The properties were cuisines that I had setup in a Cuisines table (CuisineId, Name, Description) and when you add a cuisine to a restaurant, it writes a record in another table RestaurantCuisine (RestaurantCuisineId, RestaurantId, CuisineId). So, getting the list of cuisines was the first task and have them display in a dropdownlist. That was done by creating a SelectList of cuisines in the Details view function by creating a CuisineRepository and calling a function that gets a list of all cuisines:
Dim cuiss As New CuisineRepository()
ViewData("Cuisines") = New SelectList(cuiss.FindAllCuisines().ToList(), "CuisineId", "Name")
Once you have this setup as a SelectList, it's easy to display that on the view by doing the following:
<h3>Cuisines:</h3>
<br />
<%= Html.DropDownList("Cuisines")%>
So, the problem after this was the real problem. I wanted to have an ActionLink that would be bound to the value of the DropDownList. I was unsuccessful in this attempt. But luckily, my jQuery is not dusty so I performed a little magic by doing the following:
<script type="text/Javascript">
$(document).ready(function () {
$('#Cuisines').change(function (e) {
$('#SelectedCuisine').val($('#Cuisines').val());
setHref($(this).val());
});
setHref($('#Cuisines').val());
});
function setHref(val) {
if (val) {
$("#addCuisine").attr('href', '/Restaurant/AddCuisine/' + $('#RestaurantId').val() + '?cuisineId=' + val);
}
}
</script>
<h3>Cuisines:</h3>
<br />
<%= Html.DropDownList("Cuisines")%>
<a id="addCuisine" href="">Add</a>
So, as you can see, I simply modify the href of the anchor as the user changes the selected Cuisine. Then, in my controller, I have a function setup to add a cuisine and to remove a cuisine from a restaurant:
Function AddCuisine(ByVal id As Guid, ByVal cuisineId As String) As ActionResult
Try
If ModelState.IsValid Then
'Dim selcuisInp As HtmlInputHidden = ViewData("SelectedCuisine")
Dim selectedCuisineId As Guid = New Guid(cuisineId)
Dim rc As New RestaurantCuisine
rc.RestaurantCuisineId = Guid.NewGuid
rc.RestaurantId = id
rc.CuisineId = selectedCuisineId
'rc.CuisineId = New Guid(selList.SelectedValue.ToString)
rc.CreatedDate = DateTime.Now()
'rc.CreatedBy =
db.AddToRestaurantCuisines(rc)
db.SaveChanges()
End If
Return RedirectToAction("Details", New With {.id = id})
Catch e As Exception
Dim innerE As String = e.InnerException.ToString
Return RedirectToAction("Details", New With {.id = id})
End Try
End Function
Function DeleteRestaurantCuisine(ByVal id As Guid) As ActionResult
Dim rc = (From rcs In db.RestaurantCuisines
Where rcs.RestaurantCuisineId = id
Select rcs).Single()
Dim rid As Guid = rc.RestaurantId
Try
db.RestaurantCuisines.DeleteObject(rc)
db.SaveChanges()
Return RedirectToAction("Details", New With {.id = rid})
Catch ex As Exception
Return RedirectToAction("Details", New With {.id = rid})
End Try
End Function
Notice the RedirectToAction. I had to pass the id of the object (Restaurant) that the Details view required and couldn't find the syntax anywhere. Finally, after a while of searching, found this site that the guy has a few examples of controllers and luckily he gives examples of both C# and VB. To add routeValues to the RedirectToAction, you declare a new list, and use an inline with and add your values with a period (.) preceding them:
Return RedirectToAction("Details", New With {.id = rid})
I hope this helps someone. It sure made the difference in my decision to use MVC. I am 100% sold even though I know I'm going to run into many roadblocks along the way.

Related

MVC how do i populate dropdownlist without using a model

I am using mvc 4, i have a form in a view (not binded to a model), its using standard html elements.
I want to populate a dropdownlist from a list value (i.e return from controller action)
also based on the selection of a value from the first dropdownlist i want to populate a second dropdownlist
can someone please guide
for the first dropdownlist you loop thru all the available options and add in <option> tags inside <select> for the second dropdownlist you need to either make bunch drop downs and hide/show them, or you create one big list and remove invalid entries base on the first list's selection. You would definitely need to do javascript for 2nd list.
If you don't want to use a Model (you should though), you will have to add the items to ViewData
I'll stub something out for you and you can complete the rest.
Inside your controller, create a list object of what you need. If you are using EntityFrameWork this will look familiar.
var list = context.Table.ToList();
List<System.Web.Mvc.SelectListItem> ddlItems = new List<System.Web.Mvc.SelectListItem>();
foreach (var item in list)
{
ddlItems.Add(new SelectListItem(){ Text = item.Text, Value = item.Value.ToString()});
}
ViewData["DDLItems"] = ddlItems;
#Html.DropDownListFor(x => x.LeagueId,
new SelectList((System.Collections.IEnumerable)ViewData["DDLItems"], "Value", "Text")
, "--Select League--", new { id = "league" })
You can define your second dropdownlist with just a placeholder until the cascade effect happens.
#Html.DropDownListFor(x => x.DivisionId, Enumerable.Empty<SelectListItem>(),
"--Select Division--", new { id = "ddlDivision" })
Your going to need to use JQuery and fire and event when the dropdown changes, and then use Ajax to make a call back to the controller. Theres 2348239 examples online about making Ajax calls, know how to do that because it's done all the time in MVC.
I'll let you figure that part out. One hint, inside the Ajax call you can pass data to the controller. something like this data: { leagueId: value } where value is the value of the dropdownlist you want to cascade off of. leagueId must match type and name of the parameter your controller will expect.
Return a Json object from your controller like so...
public JsonResult GetDivisions(int leagueId)
{
var division = //similar to before, fill a list.
return Json(divisions, JsonRequestBehavior.AllowGet);
}
And then in the success function of your Ajax call, you will populate the Second dropdownlist.
success: function (data) {
$.each(data, function (index, item)
$('#ddlDivision')
.append($('<option></option>')
.val(item.Value)
.html(item.Text))
item.Value and item.Text can be anything, just as long as the Json you return as the properties of Text and Value
IE...
var divisions = (from x in context.Division
select new
{
Text = league + " " + x.Region,
Value = x.DivisionId
}).ToList();

how do you get a variable from a post address in zendframework 1

I am still new to Zend Framework and confused about a few concepts.
I have built a POST form and attached a unique Id to the URL at the end of the form. I now want to collect that Id when the form is submitted but I am unclear how to do that
I will show you want I have done:
Below is the function that renders the form from my controller page to the view. You will note that I have fed into the parameter, for the form, a return Action address with the ID
$action = "{$this->view->baseUrl()}/sample-manager/process-price/{$sampleId}";
$this->view->Form = $model= $this->_model->createForm($action);
The function to receive the post is below. However, I want to collect the Id that should have come back with the post return values, but I have no idea where to find it or how to attach it.
public function processPriceAction()
{
$this->requirePost();
if($this->_model->processTieredPriceForm($this->view->form, $this->getRequest()->getPost()))
{
$this->_helper->FlashMessenger('Changes saved');
return $this->_redirect("/product-ecommerce/{$this->_model->getProduct()->id}");
}
else
{
return $this->render('index');
}
}
In summary, when a post is returned, does the return address come with the post in Zend Framework?
Could you not supply the id into the construction of the form and assign it to a hidden element? For example, in your controller:
$action = "{$this->view->baseUrl()}/sample-manager/process-price";
$this->view->Form = $model= $this->_model->createForm($action, $sampleId);
In your form model (not provided so best guess here):
$sampleId = new Zend_Form_Element_Hidden('sampleId');
$sampleId->setValue($sampleId);
$form->addElement($sampleId);
Then once the form is posted, you should be able to get the sample id in your controller in the standard way:
$sampleId = $this->getParam('sampleId');
The answer depends a bit on how your routing is setup. If you're using the default setup, after the action name the default route allows for key/value pairs of additional data. So, you might have more luck with a URL like this:
{$this->view->baseUrl()}/sample-manager/process-price/id/{$sampleId}
That'll put your sampleId in a named parameter called 'id', which you can access in your controller action with $this->_getParam('id').

ASP.NET MVC2 - passing a parameter to a controller

This is for a project I'm doing in the university. We're using ASP.NET MVC2 to build a mini-prototype of a website just to show what we can do.
Now, I have a controller called ItemController.cs, a view Item/Index.aspx, a model Item.cs and a ViewModel ViewItems.cs. I use the ViewModel to pass information to the view from the controller. My question is - how do I call a controller method with parameters? Currently, I use
Html.ActionLink("View Event Items", "Index", "Item")
which points to ItemController's Index() method. Say I want it to take an int parameter (Index(int eventId)) How would I write the ActionLink to pass the parameter to it?
Also, if I have any errors in how I think this stuff works, please feel free to point them out.
You'll need to use the routevalues object (or RouteValuesDictionary) to pass values to your action.
In your example it would look like this:
Html.ActionLink("View Event Items", "Index", "Item", new { eventId = 1}, new {})
...where 2 is your event id. The second empty object (new {}) is for html attributes. Nate's answer is close, but I don't think there is an overload that takes a routevalues object as the second parameter.
I think that
Html.ActionLink("View Event Items", new { controller = "Item", action = "Index", id = 5 })
should get you in the vicinity of where you're looking to go.

MVC Areas and routing

I'd like to have an area called "Products", where I can use routes such as
http://localhost/products/foo
http://localhost/products/bar
I would like to have the views and other assets organized into a folder structure like
/areas/products/views/foo/index.aspx
/areas/products/views/bar/index.aspx
I'd like to keep images, etc specifically related to each product (foo, bar) in their respective /area/products/views/(foo|bar)/ folder.
I also do not want to have to add a controller action for each product.
If I declare a route like
context.MapRoute(
"products-show-product"
, "Products/{id}"
, new { controller = "Products", action = "Index", id=UrlParameter.Optional }
);
and request the url
http://localhost/products/foo
then ProductsController.Index() is called, as I would expect. However, since the view "foo" is not in the views/products or views/shared folder, it isn't being found.
How can I do this so that I can keep each product's pages in a separate folder?
I don't have a concrete answer to your question since I am not sure about my understanding of it. However I have a general feeling for the direction for the solution.
When one starts to change locations of views, the corresponding methods that find those views also need to change. A simple approach would be to override the FindView and FindPartialView methods.
A simple demo. I created an Area called Blog, a Blog controller with an Index method. In my case I user the controller action as the SubFolder but I am sure that this can be extended to your case for each product folder. I assume that the product will be a request argument. Area http://www.freeimagehosting.net/uploads/85b5306402.gif
The basic idea is to interrogate the controllercontext for the controller, area, action and id and modify the what the default viewengine looks for. The default locations for area views looks like "~/Areas/{2}/Views/{1}/{0}.aspx", so we can basically inject values for the view name and in this case ActionName/Index. The view location will end up being ~/Area/Blog/Views/Blog/Index/Index.aspx.
This is just a rough outline, of the code that can be used. The string comparisons can definitely be updated to more robust methods. As it stands this method will work for the entire app as expected, except for the case when a request is made to the Blog area for the Index action.
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (controllerContext.RouteData.DataTokens ["Area"] == "Blog" )
{
if (String.Compare(controllerContext.RouteData.Values ["Action"].ToString(),"Index",true) == 0)
{
var viewLocation = String.Format("{0}/{1}", controllerContext.RouteData.Values["Action"].ToString(), viewName);
return base.FindView(controllerContext, viewLocation , masterName, useCache);
}
}
return base.FindView(controllerContext, viewName, masterName, useCache);
}

ADO.NET Mapping From SQLDataReader to Domain Object?

I have a very simple mapping function called "BuildEntity" that does the usual boring "left/right" coding required to dump my reader data into my domain object. (shown below) My question is this - If I don't bring back every column in this mapping as is, I get the "System.IndexOutOfRangeException" exception and wanted to know if ado.net had anything to correct this so I don't need to bring back every column with each call into SQL ...
What I'm really looking for is something like "IsValidColumn" so I can keep this 1 mapping function throughout my DataAccess class with all the left/right mappings defined - and have it work even when a sproc doesn't return every column listed ...
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim product As Product
While reader.Read()
product = New Product()
product.ID = Convert.ToInt32(reader("ProductID"))
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
Also check out this extension method I wrote for use on data commands:
public static void Fill<T>(this IDbCommand cmd,
IList<T> list, Func<IDataReader, T> rowConverter)
{
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
list.Add(rowConverter(rdr));
}
}
}
You can use it like this:
cmd.Fill(products, r => r.GetProduct());
Where "products" is the IList<Product> you want to populate, and "GetProduct" contains the logic to create a Product instance from a data reader. It won't help with this specific problem of not having all the fields present, but if you're doing a lot of old-fashioned ADO.NET like this it can be quite handy.
Although connection.GetSchema("Tables") does return meta data about the tables in your database, it won't return everything in your sproc if you define any custom columns.
For example, if you throw in some random ad-hoc column like *SELECT ProductName,'Testing' As ProductTestName FROM dbo.Products" you won't see 'ProductTestName' as a column because it's not in the Schema of the Products table. To solve this, and ask for every column available in the returned data, leverage a method on the SqlDataReader object "GetSchemaTable()"
If I add this to the existing code sample you listed in your original question, you will notice just after the reader is declared I add a data table to capture the meta data from the reader itself. Next I loop through this meta data and add each column to another table that I use in the left-right code to check if each column exists.
Updated Source Code
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product While reader.Read()
product = New Product()
If Not colNames.Columns("ProductID") Is Nothing Then
product.ID = Convert.ToInt32(reader("ProductID"))
End If
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
This is a hack to be honest, as you should return every column to hydrate your object correctly. But I thought to include this reader method as it would actually grab all the columns, even if they are not defined in your table schema.
This approach to mapping your relational data into your domain model might cause some issues when you get into a lazy loading scenario.
Why not just have each sproc return complete column set, using null, -1, or acceptable values where you don't have the data. Avoids having to catch IndexOutOfRangeException or re-writing everything in LinqToSql.
Use the GetSchemaTable() method to retrieve the metadata of the DataReader. The DataTable that is returned can be used to check if a specific column is present or not.
Why don't you use LinqToSql - everything you need is done automatically. For the sake of being general you can use any other ORM tool for .NET
If you don't want to use an ORM you can also use reflection for things like this (though in this case because ProductID is not named the same on both sides, you couldn't do it in the simplistic fashion demonstrated here):
List Provider in C#
I would call reader.GetOrdinal for each field name before starting the while loop. Unfortunately GetOrdinal throws an IndexOutOfRangeException if the field doesn't exist, so it won't be very performant.
You could probably store the results in a Dictionary<string, int> and use its ContainsKey method to determine if the field was supplied.
I ended up writing my own, but this mapper is pretty good (and simple): https://code.google.com/p/dapper-dot-net/