What is a way to share a drop down inside a layout for use in all views? - asp.net-mvc-3

I am becoming more familiar with MVC 3 and the RAZOR view engine. I have a question regarding layouts and shared controls on pages.
Let’s say I have a header section defined in my main layout. In that header is a dropdown I need to populate with project names. This dropdown will serve as a context for the entire site and is present on all pages. As an example, if the user selects “Project A” from the drop down, all of the views for the site will be based on “Project A”. Since this dropdown control is rather static and is used by the entire site, where is the best place to put the code to pull all the projects to display in the dropdown? In a Partial View? In a HTML helper? Another thought is, if a user selects a new value, they would be taken to a dashboard or similar page for that newly selected project. I am trying to figure out how to reuse this control on every page in the site without having to keep wiring it up in every possible controller.

You could use a child action along with the Html.Action helper. So you start by defining a view model:
public class ProjectViewModel
{
[DisplayName("Project name")]
public string ProjectId { get; set; }
public IEnumerable<SelectListItem> ProjectNames { get; set; }
}
then a controller:
public class ProjectsController: Controller
{
private readonly IProjectsRepository _repository;
public ProjectsController(IProjectsRepository repository)
{
_repository = repository;
}
public ActionResult Index(string projectId)
{
var projects = _repository.GetProjects();
var model = new ProjectViewModel
{
ProjectId = projectId,
ProjectNames = projects.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Name
})
};
return PartialView(model);
}
}
then the corresponding view (~/views/projects/index.cshtml):
#model ProjectViewModel
#Html.LabelFor(x => x.ProjectId)
#Html.DropDownListFor(
x => x.ProjectId,
Model.ProjectNames,
new {
id = "projects",
data_url = Url.Action("SomeAction", "SomeController")
}
)
Now all that's left is to render this widget inside the _Layout.cshtml:
#Html.Action("Index", "Products", new { projectid = Request["projectId"] })
And now we could put some javascript so that when the user decides to change the selection he is redirected to some other action:
$(function() {
$('#projects').change(function() {
var url = $(this).data('url');
var projectId = encodeURIComponent($(this).val());
window.location.href = url + '?projectid=' + projectId;
});
});
Another possibility is to put the dropdown inside an HTML form:
#model ProjectViewModel
#using (Html.BeginForm("SomeAction", "SomeController", FormMethod.Get))
{
#Html.LabelFor(x => x.ProjectId)
#Html.DropDownListFor(
x => x.ProjectId,
Model.ProjectNames,
new {
id = "projects",
}
)
}
so that inside the javascript we don't have to worry about building urls when the selection changes and simply trigger the containing form submission:
$(function() {
$('#projects').change(function() {
$(this).closest('form').submit();
});
});

We just did a similiar thing on a project.
First, you can't really put it in a section because you have to put that section on every view, you could put it in a partial but you would still have to call it from every view.
Second, you can't really put it in the Layout page because the layout page isn't passed any kind of model. So I created an html helper and referenced that in the layout page. There are lots of tutorials on creating html helpers so I won't put the code here. But essentially in your html helper you can make a database call to get all of your projects. Then you can create a select list using string builder in the html helper and return that to the layout page. We then used jquery to add an on change event to the select list. When the select list changed it loaded a new page. So for example, in your select list the value of each item could be the project id, then on change it redirects them to a page like /Projects/View?id=234 where 234 is your project id.
So things to research. 1. Creating HTML Helpers 2. JQUERY change event.
That should get you in the right direction. Let me know if you need any other help and I can post some code.

Related

Partial view returns the Model with empty fields

In a web application using ASP.NET MVC 3, I pass from the controller a model with initialized properties as parameter to a partial view.
The view displays a dialog with a single textbox and on submit an action in the starting controller is fired (the action takes the same model type as parameter). The problem is that at this point only the property relative to the textbox field has a value, the one inserted by the user, while all the others are null, even if in the view they had a proper value.
How can I do in order to keep the properties from the view to controller once the submit button is clicked?
EDIT (added code):
//---------- This method in the controller call the Partial View and pass the model --------
[HttpPost]
public PartialViewResult GetAddCustomFormerClubDialog()
{
var order = GetCurrentOrder();
//Order has here all properties initialized
var dialogModel = new dialogModel<Order> { Entity = order, ControllerAddEntityActionName = "SelectOrder"};
return PartialView("Dialogs/AddOrder", dialogModel);
}
//----------------- Here the Partial View -----------------------------------
#model FifaTMS.TMS.Presentation.Model.Wizard.WizardDialogModel<Club>
<div>
#using (Ajax.BeginForm(Model.ControllerAddEntityActionName, "Orders", new AjaxOptions { HttpMethod = "POST"}))
{
#Html.LabelFor(a => a.Entity.Name)
#Html.TextBoxFor(a => a.Entity.Name, new { #class = "isrequired", style="width: 250px;" })
}
</div>
//-------- Here the method from the view (in the same controller as the first code portion) -----
[HttpPost]
public JsonResult SelectOrder(dialogModel<Order> OrderModel)
{
var order= OrderModel.Entity;
// But order has only the property Name set (in the view)
...
}
I was able to solve the issue simply by adding an hidden field for each needed property, like:
#Html.HiddenFor(p => p.Entity.OrderId, new { id = "OrderId" })
This is because from the PartialView a new instance of the Model is created and sent to the controller. Therefore only the properties set in the form are taken (in my case the only field was the OrderName related to the TextBox in the PartialView).

How do I insert a partial view in a View at a certain place in the view with MVC3?

I have an MVC3 application that I am implementing pjax into . Everything is working well except what to do on the server side when an address gets loaded that doesn't already have the main view on the client side. My Controller code looks like
public virtual ActionResult Details(Guid id)
{
var partDetail = new PartDetail(id);
var partialView = PartialView("Details", partDetail);
if(Request.Headers["X-PJAX"]!= null)
{
return partialView;
}
var mainView = View("Index");
// Stick Partial View into main view at #update_panel?
return mainView;
}
How can I stick the partial View into the main view so it inserts the partial view in the #update_panel?
Ok, without a major refactor, you could do the following.
(this assumes that you are able to set the #model on index.cshtml to PartDetail()).
in your controller action above, change:
var mainView = View("Index");
to:
var mainView = View("Index", partDetail);
then, inside your index.cshtml, add the following:
<div id="update_panel">#RenderPartial("Details", Model)</div>
As i said, this will ONLY work if the index #model is set to PartDetail(), otherwise, a little refactoring on the model in the index view will be required to include this PartDetail() model. this viewmodel might well look like the following:
public class IndexViewModel
{
ModelForIndex Index{get; set;}
PartDetail Details{get; set;}
}
this refactored viewmodel would be added to the index.cshtml as #model IndexViewModel and consumed by the partial as:
<div id="update_panel">#RenderPartial("Details", Model.Details)</div>
hope this makes sense.

use Razor to fill dropdown with Linq2Sql data

I'm experimenting with ASP.NET MVC3 and want to simply populate a dropdown list with data I get from a LINQ2SQL class, like so
controller (I know, Linq doesn't belong in the controller)
var allUsers = (from u in _userDataContext.Users
select u).ToList();
ViewBag.allUsers = allUsers.ToList();
return View();
view:
<select id="drop_heroes">
#foreach (var u in ViewBag.allUsers)
{
<option value="#u.pk_userid">#u.email</option>
}
</select>
That works fine, but I would like to use Razor #Html.Dropdownlist to create the same dropdown list, but can't find any info to make this work with Linq data.
I know, Linq doesn't belong in the controller
Then why are you using it in a controller? Anyway, at least it's fine that you know it.
Here's an example. As always in an ASP.NET MVC application you start by defining a view model which will represent the data that you need in the view. So in your case you need to display a dropdown so you define a list of users and a selected user id:
public class MyViewModel
{
public string SelectedUserId { get; set; }
public IEnumerable<SelectListItem> Users { get; set; }
}
then you define a controller action which will populate this view model from your repository and handle it to the view:
public ActionResult Index()
{
var model = new MyViewModel
{
Users = _userDataContext.Users.ToList().Select(x => new SelectListItem
{
Value = x.pk_userid.ToString(),
Text = x.email
})
}
return View(model);
}
and finally you will have a view which will be strongly typed to your view model and use HTML helpers to generate the dropdownlist:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedUserId, Model.Users)
<button type="submit">OK</button>
}
Things to notice:
Usage of view models
Usage of a strongly typed view
Usage of strongly typed HTML helpers to generate markup such as form elements and input fields
Getting rid of weakly typed structures such as ViewBag
If you follow these simple rules you will see how much easier your life as an ASP.NET MVC developer will become.

Strongly Typed RadioButtonlist

I want to get some options (say payment method cash, credit card etc.) and bind these to radio buttons. I believe there is no RadioButtonList in MVC 3.
Also, once radios are bound I want to show the previously selected option to the user while editing the answer.
As always you start with a model:
public enum PaiementMethod
{
Cash,
CreditCard,
}
public class MyViewModel
{
public PaiementMethod PaiementMethod { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and finally a view:
#model MyViewModel
#using (Html.BeginForm())
{
<label for="paiement_cash">Cash</label>
#Html.RadioButtonFor(x => x.PaiementMethod, "Cash", new { id = "paiement_cash" })
<label for="paiement_cc">Credit card</label>
#Html.RadioButtonFor(x => x.PaiementMethod, "CreditCard", new { id = "paiement_cc" })
<input type="submit" value="OK" />
}
And if you want some more generic solution which encapsulates this in a helper you may find the following answer helpful.
This is how I like to bind RadioButtonLists. The view model has a collection of my strongly typed objects. For example, maybe PaymentOptions is a code table. Along with the collection is a SelectedPaymentOptionKey (or Selected*Id if you prefix your primary keys with Id). Initially this key will just be default 0, but on postback, it will hold the value of the selected item.
public class PaymentSelectionVM
{
public ICollection<PaymentOption> PaymentOptions { get; set; }
public int SelectedPaymentOptionKey { get; set; }
}
public ViewResult PaymentSelection()
{
var paymentOptions = db.PaymentOptions.ToList();
return View(
new PaymentSelectionVM {
PaymentOptions = paymentOptions,
//This is not required, but shows how to default the selected radiobutton
//Perhaps you have a relationship between a Customer and PaymentOption already,
//SelectedPaymentOptionKey = someCustomer.LastPaymentOptionUsed.PaymentOptionKey
// or maybe just grab the first one(note this would NullReferenceException on empty collection)
//SelectedPaymentOptionKey = paymentOptions.FirstOrDefault().PaymentOptionKey
});
}
Then in the View:
#foreach (var opt in Model.PaymentOptions)
{
#*Any other HTML here that you want for displaying labels or styling*#
#Html.RadioButtonFor(m => m.SelectedPaymentOptionKey, opt.PaymentOptionKey)
}
The m.SelectedPaymentOptionKey serves two purposes. First, it groups the Radio buttons together so that the selection is mutually exclusive(I would encourage you to use something like FireBug to inspect the generated html just for your own understanding. The wonderful thing about MVC is the generated HTML is fairly basic and standard so it shouldn't be hard for you to eventually be able to predict the behavior of your views. There is very little magic going on here.). Second, it will hold the value of the selected item on postback.
And finally in the post handler we have the SelectedPaymentOptionKey available:
[HttpPost]
public ActionResult PaymentSelection(PaymentSelectionVM vm)
{
currentOrder.PaymentOption = db.PaymentOptions.Find(vm.SelectedPaymentOptionKey);
....
}
The advantage of this over using SelectListItems is you have access to more of the object's properties in the case that you are displaying a grid/table and need to display many values of the object. I also like that there are no hard coded strings being passed in the Html helpers as some other approaches have.
The disadvantage is you get radio buttons which all have the same ID, which is not really a good practice. This is easily fixed by changing to this:
#Html.RadioButtonFor(m => m.SelectedPaymentOptionKey, opt.PaymentOptionKey, new { id = "PaymentOptions_" + opt.PaymentOptionKey})
Lastly, validation is a bit quirky with most all of the radio button techniques I've seen. If I really needed it, I would wire some jquery up to populate a hidden SelectedPaymentOptionsKey whenever the radio buttons are clicked, and place the [Required] or other validation on the hidden field.
Another workaround for the validation problem
ASP.NET MVC 3 unobtrusive validation and radio buttons
This looks promising but I haven't had a chance to test it:
http://memoriesdotnet.blogspot.com/2011/11/mvc-3-radiobuttonlist-including.html
You should bind your options to SelectList in ViewModel and set Selected attribute to true for previously selected option

MVC3 Ajax.BeginForm odd behavior

I have a very simple view that has a DropDownListFor and a Button inside an Ajax.BeginForm helper. Clicking the button renders the whole view again inside the div I have set to update including the layout page (I also notice a spike in the cpu when clicking the button multiple times)
Here is the Ajax.BeginForm inside the view:
#using (Ajax.BeginForm("About2", "Home", new AjaxOptions { UpdateTargetId = "property22" }))
{
<div>
<div id="property22">
#Html.DropDownListFor(m => m.SomePropertyToBind, new SelectList(Model.list, "property1", "property2"))
</div>
<button type="submit" id="test">
Click me</button>
</div>
}
Any ideas where I'm going wrong?
I uploaded the whole project if someone has a couple of minutes to take a look at it:
http://www.sendspace.com/file/siu3r31 (free provider so there may be a popup)
Thanks
You are using a wrong overload of the Ajax.BeginForm helper. It should be like this:
#using (Ajax.BeginForm(
"About2",
"Home",
null,
new AjaxOptions { UpdateTargetId = "property22" },
new { #id = "refreshe" }
))
Notice the additional null value I am passing as the routeValues parameter. Also in the example you uploaded you forgot to include the TestView.cshtml view. This being said in order to fix the problem you have two possibilities:
Either return a partial view:
public ActionResult About2()
{
Random randomizer = new Random();
int random = randomizer.Next(1, 1000000000);
ModelTest newModelTest = new ModelTest();
string[] stringList = new string[] { "Red", "Blue", "Green" };
newModelTest.list = from test in stringList
select new ModelTestList
{
property1 = test,
property2 = test
};
newModelTest.SomePropertyToBind = stringList[random % 2];
return PartialView("TestView", newModelTest);
}
or disable the layout in the TestView.cshtml view:
#{
Layout = null;
}
Unfortunately from your explanation above and from the code, I am not sure what you are trying to achieve. However, I think your most worry is about having Ajax working in your view.
In your About2 action method, you are trying to return a complete view which is TestView (in that case, it doesnt exist) and passing it the newModelTest view Model. I would advise changing to return either a PartialView or JsonResult.
For example, changing the return statement of About2 action method to
public ActionResult About2()
{
...
return Json(newModelTest);
}
or changing it to a return type to string and returning "TestResult"
public String About2()
{
...
return "TestResult";
}
or you could change the return statement to return a PartialView
Thanks for your replies.
I just realized that About2 should have returned the "About" view instead of the "TestView". I had tried creating a partial view with the Ajax.BeginForm code but I came across the same problem.
This is my first attempt at Ajax.BeginForm (so far I have always used jquery), and I was under the impression that it works in a similar fashion in the sense that by specifying the target id only the contents of that element will get updated, not that the target will actually get replaced by the whole response object.
Thanks for your help, not only did I get it to work, but I now understand how it should work.
I suspect that what's happening is that you're returning the a complete View (including the layout template) in the Ajax response. Try changing your "Home" controller "About2" action temporarily to the following:
public ContentResult About2() {
return Content("Hello World");
}
I tested this sample Action with your Razor markup and clicking the button properly replaced your dropdown list with "Hello World!".
If this is indeed what's happening, then you'll want to return a View from "About2" without the layout by declaring the following at the top of the View that you're returning.
#{
Layout = null;
}

Resources