Is there anyway in a MVC strongly typed View to find out which ActionResult in the controller passed the Model? I do not want to add extra fields in the Model at this stage or add anything in my querystring? Is it even possible?
Sorry this may be a noob question. Any help is appreciated.
Never mind mumbling about validation of the principles of the MVC Architecture ;)
Place this code in your view:
string action = #ViewContext.Controller.ValueProvider.GetValue("action").RawValue.ToString();
In the view you can acess the Context, then you can retrieve the action/controller from the current request like this:
#{
string controller = RequestContext.RouteData.Values["controller"].ToString();
string action = RequestContext.RouteData.Values["action"].ToString();
}
Then you can use it at the view to make some conditional
#if(action.Equals("some name"))
{
//Do stuff here
}
The Action should tell the View. Instead of adding extra fields to your model you could wrap it in a ViewModel:
public class MyViewModel
{
public MyModel InnerModel {get; set; }
public string ActionName {get; set; }
}
Or use ViewBag:
ViewBag.ActionName = "MyActionName";
Related
I have a ViewModel that I would like to use to populate the QueryString, almost the opposite of the binding that MVC does out of the box. So for the model that looks like this:
public class SearchViewModel
{
public string Keywords { get; set; }
// more properties here
}
I would hope to be able to do something like this:
string querystring = AspMagicMethods.GetQueryStringFromViewModel(searchViewModel);
// querystring == ?keywords=booyah&...
Obviously I could go through each property and create the string myself, but I was wondering if there's anything built into the framework that might be of assistance.
You can use following method of MVC to do so from any controller action.
var myModel = new SearchViewModel{Keywords ="test"};
RedirectToAction("actionName", "controllerName", myModel)
Every page in my site should have some of the same data, similar to how in SO every page displays info about the current user at the top of the page. The way I implemented this was to have a base controller class that all my controllers derive from. In that base controller's constructor I put my model in the ViewBag, and then my layout page uses that.
I'm running into problems with this because my layouts aren't strongly typed. For example, I have to construct new HtmlHelpers in the layout page:
#{var layoutHtml = new HtmlHelper<LayoutModel>(Html.ViewContext, Html.ViewDataContainer); }
#* OK, now we can use the html helper... *#
#layoutHtml.TextAreaFor(model => model.Feedback)
I really don't want to have to have my models subclass from a layout model, since that would force each action to fill out the shared model data individually, but manually creating HtmlHelpers also seems like a bad idea.
Any thoughts?
I might be wrong, but shouldn't you use partial views for this?
Your solution looks way too complicated.
A base controller is a great way to start. I would also introduce a base viewmodel. The base viewmodel would contain user specific information.
public abstract class BaseController : Controller
{
public string CurrentAccountName
{
get
{
return (HttpContext.User == null) ? null : HttpContext.User.Identity.Name;
}
}
public T CreateViewModel<T>() where T : BaseViewModel, new()
{
T viewModel = new T
{
UserName = CurrentAccountName
};
return viewModel;
}
}
public abstract class BaseViewModel
{
public string UserName { get; set; }
}
Then on each request you would populate your viewmodel with user specific information and whatever information is required for the view. MyViewModel is just a viewmodel that inherits from BaseViewModel.
public class MyController : BaseController
{
public ActionResult Index()
{
MyViewModel viewModel = CreateViewModel<MyViewModel>();
return View(viewModel);
}
}
In the master view I would pass in the BaseViewModel and the View I would pass in the inherited MyViewModel.
Now you have access to your user information in your master view and can pass it to a partial or render it directly to the page.
you can define multiple Layouts which you can use in your appropiate views! Just include them like so:
#{
Layout = "~/Views/Shared/_MySubLayout.cshtml";
}
i believe you can use RenderAction to solve this problem. Because information this action will display is common on all pages, you can put it in BaseController and call it from your site master. it will compute its own model and return that model to partial view which can be strongly typed and you don't have to instantiate htmlHelper the way you are doing now.
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)
I want a way to separate the loading of reference data into a view model from the controller. At the moment I have a view model with a property for the selected value and the reference data:
public IEnumerable<SelectListItem> DayTypes { get; set; }
public int DayTypeId { get; set; }
and the data is populated from the relevant repository in the controller action:
model.DayTypes = _dayTypeRepository.GetAll().ToSelectList(d => d.Description, d => d.Identifier.ToString());
I would like to change this because it pollutes the controller with lots of repositories and code that is not core to its concerns. All of these dependencies make unit testing the controller a pain.
One possible approach to solving this would be to make the view model class do the loading which would require a custom model binder to instantiate them using the IoC container to provide the repository dependency. Is this a good option?
Another approach that I think would be good is hinted at in CodeCampServer but is incomplete and commented out involving attributes on the field in the view model:
[SelectListProvided(typeof(AllDaysSelectListProvider))]
public IEnumerable<SelectListItem> DayTypes { get; set; }
however I am struggling to figure out how this could be implemented in a way that would not require some major replumbing of the MVC framework.
How do you solve this problem?
EDIT: I want to keep with strongly typed views and avoid stuffing the data into view data.
FURTHER EDIT: I would also like a solution that is ideally model independent, by which I mean if the same reference data is needed by multiple view models this can be achieved with a single piece of code. Matt's approach is interesting but is tightly coupled to the view model.
I would use a service layer which would return me a POCO object that I would map to a view model. So my controller action would look like this:
public ActionResult Index(int id)
{
var model = _service.GetModel(id);
var viewModel = Mapper.Map<Model, ViewModel>(model);
return View();
}
I also like using action filters to avoid the mapping code all over again so:
[AutoMap(typeof(Model), typeof(ViewModel))]
public ActionResult Index(int id)
{
var model = _service.GetModel(id);
return View(model);
}
This way only the service talks with the CRUD repositories and the controller talks to the service and the mapping layer.
You could write a new ActionFilter that you can decorate an action method with; this action filter will load the reference data into the viewdata, which you can access from your view.
There is more on action filters here.
EDIT: Based on the users comments, this now includes a strongly typed option.
Firstly, you need to create the SharedViewModel to contain the shared data.
public class SharedViewModel
{
public List<string> Days { get; set; }
public List<string> Months { get; set; }
public List<string> Years { get; set; }
}
Next, we create the view model to be used by the Index view, which uses this shared view model.
public class HomeViewModel
{
public string ViewName { get; set; }
public SharedViewModel SharedViewModel { get; set; }
}
The next step is important, it implements an action filter called SharedData(), which will apply the shared data.
public class SharedDataActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var currentModel = ((HomeViewModel) filterContext.Controller.ViewData.Model);
currentModel.SharedViewModel = new SharedViewModel
{
Days = new List<string> {"Mon"},
Months = new List<string> {"Jan"},
Years = new List<string> {"2011"}
};
base.OnActionExecuted(filterContext);
}
}
At the moment, it just applies the whole shared data, but you can added parameters into the method to be selective.
When the action has been executed, this method takes the current model and adds the shared data.
Here is the controller action.
[SharedDataActionFilter]
public ActionResult Index()
{
return View("Index", new HomeViewModel { ViewName = "HomePage" });
}
You can access the data like any other strongly typed view, and the shared data wont affect the data already in the model (in this case "ViewName"). You can also use action filters across controllers, and globally across the site with mvc 3.
Hope this helps, Matt.
I'm new to ASP.NET MVC so this could have an obvious answer. Right now I have a form in my view with a lot of input controls, so I have an action that looks like this:
public ActionResult MyAction(string formItemOne, int? formItemTwo, etc...)
It has like a dozen parameters, which is pretty ugly. I'm trying to change it to this:
public ActionResult MyAction(FormCollection formItems)
and then parse the items dynamically. But when I change to a FormCollection, the form items no longer "automagically" remember their values through postbacks. Why would changing to a FormCollection change this behavior? Anything simple I can do to get it working automagically again?
Thanks for the help,
~ Justin
Another solution is to use models instead of manipulating the raw values. Like this:
class MyModel
{
public string ItemOne { get; set; }
public int? ItemTwo { get; set; }
}
Then use this code:
public ActionResult MyAction(MyModel model)
{
// Do things with model.
return this.View(model);
}
In your view:
<%# Page Inherits="System.Web.Mvc.ViewPage<MyModel>" %>
<%= Html.TextBox("ItemOne", Model.ItemOne) %>
<%= Html.TextBox("ItemTwo", Model.ItemTwo) %>
To replace your big list of parameters with a single one, use a view model. If after the POST you return this model to your view, then your view will remember the values posted.
A view model is simply an class with your action parameters as public properties. For example, you could do something like this, replacing:
public ActionResult MyAction(string formItemOne, int? formItemTwo, etc...)
with
public ActionResult MyAction(FormItems formItems)
{
//your code...
return View(formItems);
}
where FormItems is
public class FormItems
{
public property string formItemOne {get; set;}
public property int? formItemTwo {get; set;}
}
You may see a complete example in Stephen Walter's post ASP.NET MVC Tip #50 – Create View Models.
Maybe because they aren't magically inserted into the ModelState dictionary anymore. Try inserting them there.
If you use UpdateModel() or TryUpdateModel() I think the values are gonna be persisted.