Partial view does not return any data.When i check with debug tool on PartialView page(_ContentBlock.cshtml), model seem to null.
Controller
public ActionResult Module()
{
int RouteDataValue = default(int);
if (RouteData.Values["id"] != null)
{
RouteDataValue = int.Parse(RouteData.Values["id"].ToString());
}
using (Models.ContentModel db = new Models.ContentModel())
{
var Query = from n in db.PageModule
join m in db.Module on n.ModuleId equals m.ModuleId
where n.PageId == RouteDataValue
select m.PhysicalPath;
return PartialView(Query.Single()); //returns PartialView such as ~/Modules/Content/_ContentBlock.cshtml
}
}
public PartialViewResult ContentBlock()
{
using (Models.ContentModel db = new Models.ContentModel())
{
return PartialView("~/Modules/Content/_ContentBlock.cshtml", db.ContentBlock.Where(n => n.PageId == 2).Single());
}
}
Page.cshtml
#Html.Action("Module")
_ContentBlock.cshtml
#model IEnumerable<Models.ContentBlock>
#foreach (var item in Model)
{
#Html.DisplayFor(n => item.Content)
}
You seem to have used the Html.Partial helper instead of Html.Action. So you were basically only rendering the partial without ever hitting the controller action that is supposed to populate and the model to the partial.
Your page Page.cshtml is calling the partial view action Module using:
#Html.Action("Module")
The action called Module is being executed. In that action, your query results in a path to your view, such as:
"~/Modules/Content/_ContentBlock.cshtml"
That action is returning the single result of that query using:
return PartialView(Query.Single());
What this is doing is passing the name of the view to the PartialView method to return which view is going to be used to display data from the action. In addition, no model data is included in this return.
That's where your problem is. When you return the path to the partial view, you are simply telling the MVC system what view to use to display the data from Module. It won't actually call another partial view. That's not how it works. So your model is null because, you didn't pass any data in your PartialView(...) call.
You have another action called ContentBlock. But that action is not being called because nothing is calling it.
Edit:
Another problem you have is that _ContentBlock.cshtml uses a model of IEnumerable<ContentBlock>, but you're only passing it a .Single() from your ContentBlock action.
Related
Can I return a view in PartialViewResult() like the following?:
public PartialViewResult EditAdminProfile_Post(int Id, FormCollection formCollection)
{
//if (Session["AdminID"] != null)
//{
Admin admin = new Admin();
admin = db.Admins.Single(m => m.ID == Id);
admin.Name = formCollection[0];
admin.Gender = formCollection[1];
admin.Email = formCollection[2];
admin.ContactNumber = formCollection[3];
admin.AboutMe = formCollection[4];
if (ModelState.IsValid)
{
db.Entry(admin).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
}
return PartialView("AdminProfile");
AdminProfile is not a partial view.
Yes you can. It's not advised though, since your _layout is ignored (which means css or script files defined in the layout, so your styles and functionality may behave differently as you'd expect). Even though you can make it work, you're looking at code smell.
Are you looking to reuse AdminProfile as both a partial and normal view?
Create a partial view AdminProfilePartial with reusable content.
In your AdminProfile view, call AdminProfilePartial inside it (even if that's all you use)
Then when you return the full view:
return View("AdminProfile");
Otherwise partial view:
return PartialView("AdminProfilePartial");
The problem is that you don't pass the model to you partial view, that's why when you call it you get a Null reference
Update your code with this line:
return PartialView("AdminProfile", admin);
PS Instead of using FormCollection it's better to use default model binder and accept the Admin model in your action method
I'm using MVC 5, and have a page where I will list out payments for a contract. there are times when there are no payments associated with a contract. my view brings in the model to list:
#model List<MyProject.Models.PaymentViewModel>
the Action Method on the Payment view looks like:
[HttpGet]
public ActionResult Payment(int id)
{
var payments = contractController.GetPaymentsForActiveContract(id);
return View(payments);
}
Now GetPaymentsForActiveContract returns a List and this works fine when I use
#Html.Partial("partialviews/_Payments")
on my view. but what i'm trying to do now is have another action method get called, which will return either a view which will display a table of payments, or another one which will write out there are none if no payments exists.
I have that action defined as:
public ActionResult GetPayments(List<PaymentViewModel> payments)
{
if (payments.Count > 0)
{
return PartialView("partialviews/_Payments", payments);
}
return PartialView("partialviews/_NoPayments");
}
I'm not sure how to pass the Model into this Action from my view. I'm looking through the #Html methods, but can't seem to find anything which would allow me to call the GetPayments method passing in the model and having it return either Partial View.
If you want to call controller action from view, you can use
#Html.Action()
method and pass payments list as route value
#{
var paymentsList = new List<PaymentViewModel> {new PaymentViewModel(), new PaymentViewModel()};
}
#Html.Action("GetPayments", new { payments = paymentsList })
In ASP.NET MVC 5, does every Controller Action have to return a View with the same name as the Controller?
Here's my project. Have a webpage which contains a button to upload an image to a database. When the webpage is loaded, I want it to display a list of all the images that have already been uploaded. So, the Index (default) Action for this Controller loads the images from the database, and returns the Index View, which in turn displays the list of images:
public ActionResult Index()
{
// Load the images from the database
var images = GetImages();
return View(images);
}
On that same webpage, there is a button which allows the user to upload an image to the database. That button calls the Upload Action, which uploads the file based upon the "file" and "folder" arguments that are passed, and then finally returns the Index View again:
[HttpPost]
public ActionResult Upload(HttpPostedFileBase file, string folder)
{
// Upload the file from the specified folder
// ...
// ...
// ...
return Index();
}
However, when a user clicks on this upload button, the following error message is displayed:
The view 'Upload' or its master was not found or no view engine supports the searched locations
But I am not trying to render a View called "Upload" - I am trying to render the view called "Index", which is why I have the line return Index();.
Any help on where I'm going wrong?
Answer
Although Vitaliy and Nathan A provided adequate answers, I wanted to explain why your initial approach doesn't work because it's a great question and doesn't seem to make sense.
To get our answer we have to look at the ASP.NET MVC source code.
Before we get to that let's walk through your code.
The user visits (or POSTS to) /Controller/Upload
We do some logic and then return Index()
Index() is a method that returns its own view with its own model
MVC fails to find 'Upload' view and throws an exception
What went wrong?
Firstly know that Index() is being called and returned successfully. The model object is also being passed to the view (if one is found).
When you return Index(), it is returning View() which is an inherited method from the Controller class which returns a ViewResult.
A ViewResult inherits from ViewResultBase.
When a ViewResult is being returned it calls ExecuteResult().
Taking a look at the source code for ExecuteResult():
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (string.IsNullOrEmpty(this.ViewName))
{
this.ViewName = context.RouteData.GetRequiredString("action");
}
ViewEngineResult viewEngineResult = null;
if (this.View == null)
{
viewEngineResult = this.FindView(context);
this.View = viewEngineResult.View;
}
TextWriter output = context.HttpContext.Response.Output;
ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output);
this.View.Render(viewContext, output);
if (viewEngineResult != null)
{
viewEngineResult.ViewEngine.ReleaseView(context, this.View);
}
}
The key here is context.RouteData.GetRequiredString("action"). This code gets the action name so that a view can be found and rendered. Note that it is using a ControllerContext.
Because of this, your action is actually set when the Upload() method is first called. If you step through your Index() method you will see that calling context.RouteData.GetRequiredString("action") will return the string "Upload".
This is because, within the context of the user request, the action is in fact Upload (that's the page they requested).
Fun fact
If you return Index() and that method happens to alter the ViewBag (ViewData) then the ViewData will be altered regardless of what is rendered.
If your Upload() does this:
ViewBag.Test = "Upload method";
And you return Index() and your Index() does this:
ViewBag.Test = "Index method";
Then the value of Test will be "Index method".
Look up the documentation on the View method. It has several arguments you can provide, one of them being a string of the name of the view, but you always use the View() method if you want to return a view.
However, if you don't want to use the default View name (being the name of the action method), simply use a string to specify a new name like so:
public ActionResult Upload(HttpPostedFileBase file, string folder)
{
return View("Index");
}
you can do either:
return RedirectToAction("Index");
or:
return View("Index");
Just a side-note, you're not constrained to show views from the View folder for the controller name. You can do
public ActionResult something()
{
return View("../OtherView/somethingElse");
}
i want to show a message in asp.net mvc. for this, i create a partial view. name of this partial view is _feedback. in body of this partial view i write this codes.
#model MyProject.SharedTools.OperationStatus
#if (Model != null)
{
if (Model.IsSuccess)
{
#:Model.Message;
}
else
{
#:Model.Message;
}
}
i put this code in _layout file:
#Html.Partial("_feedback")
and when i want to see a message from controller, using this code:
operationStatus = _provinceRepository.Save();
if (operationStatus.IsSuccess)
{
TempData["OperationStatus"] = operationStatus;
return RedirectToAction("Index");
}
but i give this error:
The model item passed into the dictionary is of type 'MyProject.Models.ProvinceModel', but this dictionary requires a model item of type 'MyProject.SharedTools.OperationStatus'.
Make sure that you have passed the correct model that your partial is expecting:
#Html.Partial("_feedback", Model.SomePropertyOfTypeOperationStatus)
If you do not specify a model as second argument to the Html.Partial helper, then it will automatically pass the model of the current view (which in your case is of type MyProject.Models.ProvinceModel) and that's why you are getting the error : your partial expects a model of type MyProject.SharedTools.OperationStatus.
Also it is not quite clear where you are using the TempData value that you stored in your controller inside your partial. Maybe it should be something like this:
#model MyProject.SharedTools.OperationStatus
#if (Model != null)
{
#TempData["OperationStatus"]
}
or didn't you just mean to display directly the value you stored in TempData in your partial without using a model?
#TempData["OperationStatus"]
I'm just learning MVC3 now and this is really confusing me.
I have a ViewModel that contains some child ViewModels. Each of the ChildViewModels get rendered with a different Partial View, and when submitting execute a different action on the Controller. All the ChildViewModels should perform some custom validation on their data, and if successful it should move on to the next page. If the validation fails, it should simply return to the ParentView and display the errors.
[HandleError]
public class MyController: Controller
{
public ActionResult Index()
{
var viewModel = new ParentViewModel();
return View("ParentView", viewModel);
}
[HttpPost]
public ActionResult ChildViewModelB_Action(ChildViewModelB viewModel)
{
if (ModelState.IsValid)
{
return View("ChildViewModelB_Page2", viewModel);
}
else
{
// I'm having trouble returning to the ParentView and
// simply displaying the ChildViewModel's errors, however
// discovered that creating a new copy of the VM and displaying
// the ParentView again shows the existing data and any errors
// But why??
var vm = new ParentViewModel();
return View("ParentView", vm);
}
}
}
For example,
The page loads with 3 options.
User selects option B and fills out a form.
Upon submit, the child ViewModel B gets validated and fails.
Page returns to ParentView, with ChildB all filled out, however ChildB errors are now also showing.
Why does creating a new copy of the ParentViewModel display the ParentView with the same data as the original ParentViewModel?
And is there a different way I should be returning to the ParentView after doing server-side validation?
You need to clear the modelstate if you intend to modify values in your POST action
else
{
ModelState.Clear();
var vm = new ParentViewModel();
return View("ParentView", vm);
}
The reason for that is because Html helper such as TextBoxFor will first look in the modelstate when binding their values and after that in the model. And since the modelstate already contains the POSTed values, that's what's used => the model is ignored. This is by design.
This being said the correct thing to do in your case is to simply redirect to the GET action which already blanks the model and respect the Redirect-After-Post pattern:
else
{
return RedirectToAction("Index");
}
Why does creating a new copy of the ParentViewModel display the
ParentView with the same data as the original ParentViewModel?
Because the values of the fields are retrieved from the POSTed form and not from the model. That makes sense right? We don't want the user to show a form filled with different values from what they submitted.