I have seen similar questions here regarding my question but I am unable to find a suitable solution for my purpose. So I started working with the MVC and had to create a simple wizard. I created the Parent View model as,
public class WizardSteps
{
public int CurrentStepIndex { get; set; }
public WizardStep1 Step1 { get; set; }
public WizardStep2 Step2 { get; set; }
public WizardStep3 Step3 { get; set; }
public WizardSteps()
{
CurrentStepIndex = 1;
Step1 = new WizardStep1();
Step2 = new WizardStep2();
Step3 = new WizardStep3();
}
}
Now I have created three partial views for each of my wizard step. For every view I used the parent model,
#model eFormation_MVC.Models.WizardSteps
For each of the fields on each of the Partial Views I used controls like this inside forms,
#Html.LabelFor(m => m.Step1.FirstName, new { #class = "col-sm-3 control-label" })
#Html.LabelFor(m => m.Step2.Address, new { #class = "col-sm-3 control-label" })
This is brief code for just giving the idea what I have done so far. I have different controller methods for each of the Partial View. I pass the Parent Model WizardSteps to the controller and pass it back to next Partial View. SO for all Partial View single model is used which has child model. Now when I post the model from Second Partial View then all the values in WizardSteps.Step1 become null. I also know that this is the actual behavior but my question is how to persist the values. What can be the proper way of doing this so that the values for every Step is not lost from the Parent model.
Edit:
Each partial view has its own form and I am sending the parent model WizardSteps to the controller on form submit of each partial view. The problem is that when I submit Partial View for Step 2 then the values for Step 1 in the WizardSteps becomes null. I know this happens because there are no fields for Step 1 on Step 2 partial view and controller think values as null and does not persists it.
On some related question it is suggested to Serialize the models and store it on the View and send it back to controller and de-serialize it. Some has use Sessions or TempData for storing the models but I want something which is done with models. There is a question for using MVCWizard at MVCWizard.Wizard - Using Complex Models but the answer to that question is so vague that I am unable to grasp it. I want something similar to the MVCWizard with the use of Merge functionality but couldn't find any example of doing so. Need help with this.
Related
Can someone help me understand partial views, forms and posting in ASP.NET Core Razor.
I have a Search.cshtml partial view located in "~/Client/Search" :
#model Web.Pages.Client.SearchModel
#using (Html.BeginForm())
{
<div>
#Html.RadioButtonFor(x => x.searchType, (int)ApplicationCore.Interfaces.SearchType.mobile, new { Name = "SearchType" }) Mobile
#Html.RadioButtonFor(x => x.searchType, (int)ApplicationCore.Interfaces.SearchType.phone, new { Name = "SearchType" }) Phone
#Html.RadioButtonFor(x => x.searchType, (int)ApplicationCore.Interfaces.SearchType.email, new { Name = "SearchType" }) Email
</div>
#Html.TextBoxFor(x => x.searchFilter)
<input type="submit" value="Search"/>
}
With code page Search.cshtml.cs :
public class SearchModel : PageModel
{
public SearchType searchType { get; set; }
public string searchFilter { get; set; }
private readonly IClientService _clientService;
private readonly Infrastructure.Data.DBContext _context;
public SearchModel(Infrastructure.Data.DBContext context, IClientService clientService)
{
_context = context;
_clientService = clientService;
searchFilter = string.Empty;
searchType = SearchType.mobile;
}
public async Task<IActionResult> OnPostAsync()
{
return RedirectToPage("./Index");
}
}
If I load the "~/Client/Search" Partial View directly it loads and on post it correctly fires the OnPosAsync() action.
However if the "~/Client/Search" Partial View is rendered from the "~/Session/CheckIn" parent View :
#await Html.PartialAsync("~/Client/Search", Model._searchModel)
The OnPostAsync() within the "~/Client/Search" Partial View no longer fires.
I have tried all sorts of combinations to define "action", "controller" within the Html.BeginForm in the Partial View, however I can never get the OnPostAsync() within the Partial View to fire.
Any pointers? Read a lot of articles and forum posts however there are no clear descriptions or walkthroughs to help me understand this and get the Partial View action method firing on postback from parent View.
This is why Razor Pages are a blight: they obfuscate logic, allowing people to build stuff without ever actually understanding how any of it works. </rant>
There's nothing special about a partial view. It's just a view like any other view. What makes it "partial" is the context in which it's used, i.e. injecting it into the rendering of a view. As such, Razor Pages lets you add a code-behind, because it's just a view, and any view can have a Razor Pages code-behind. However, when used like a partial, that code-behind is not actually utilized, and there's your problem.
Also, you need to bear in mind that the whole concept of partial views only exists server-side. Once the response has been returned, all you have is just an HTML document. The browser couldn't care less whether you used one partial view, 100 partial views or no partial views to create the response server-side. As such, using a partial doesn't somehow magically buy you the ability to just work with a single section of your page, such that when you post, only that section is changed. For that, you need AJAX. Otherwise, doing a post, whether from a "partial view" or not, will cause the entire view to be changed in the browser window.
In other words, you need something server-side that will respond to that post request by returning a new full view, not just your partial, or you need to make the request client-side via AJAX, and return just your partial view. However, then, you're responsible for replacing whatever HTML should be replaced with that response, yourself.
Just need to know if this is possible to do or what exactly is standard practice in MVC as this is my first large scale MVC application.
So I've got a form I want the user to be able to edit and on that form page to pull up all the necessary data I need I'm bringing in a viewmodel,
public class Tier2IssueFormViewModel
{
public Tier2IssueDTO Tier2Issue { get; set; }
public IEnumerable<SelectListItem> VersionList { get; set; }
public IEnumerable<SelectListItem> BugList { get; set; }
public IEnumerable<SelectListItem> IssueStatusList { get; set; }
}
Then once I've finished collecting the form data from the user using things like,
#Html.TextBoxFor(m => m.Tier2Issue.Tier2Notes, new { #class = "form-control"})
#Html.DropDownListFor(m => m.Tier2Issue.FishbowlVersion, Model.VersionList, "Select Application Version")
#Html.HiddenFor(m => m.Tier2Issue.ID)
I want to post back to this action with the following signature for my model to bind to,
[HttpPost]
[Route("Issues/{id}/Edit")]
public ActionResult EditIssue(Tier2IssueDTO model)
{
...
// Update DB with the DTO model
...
}
But so far nothing really gets bound to this object. I thought the model binder might be smart enough to pair the two but I'm guessing this logic is incorrect. So I'm currently doing a workaround by using this,
[HttpPost]
[Route("Issues/{id}/Edit")]
public ActionResult EditIssue(Tier2IssueFormViewModel model)
{
...
// Get Tier2IssueDTO values from viewmodel
// Update DB with the DTO model
...
}
I mean it works, but it seems odd to me that you would model bind to a view model. Is this standard practice or is there a way to bind to an object contained within the viewmodel directly?
This will not work because the input text box names are differnt from the model inside your action, ex: the text box will have a name Tier2Issue.Tier2Notes while the model parameter in your action is expecting a property name Tier2Notes only without the Tier2Issue prefix.
You can overcome this issue by either making the model the same as the action parameter or give an explicit name and value to the text box, ex:
#Html.TextBox("Tier2Notes",Model.Tier2Issue.Tier2Notes, new { #class = "form-control"})
This should make it work
You have the right of it. It often seems pretty repetitive to have a viewmodel, dto and entity that all seem to have the same properties, but they all do different jobs an usually end up diverging a bit. A dto could act as a viewmodel, but it's a square peg in a round hole. If you're not using automapper to map these objects to one an other (this may be opinion baesed but it's broadly shared) - then use automapper to save you mindless keystrokes.
I was originally developing a project for WPF, using MVVM, which had the benefit of allowing me to populate a list of views that I wanted available. Each view had a "Next" button that would progress to the next view in the list.
However, now I am trying to do the same in ASP.NET MVC. This is my first time using MVC, but I have an XML file, from which I need to generate this UI. These views, which are chosen from the script, also have components in them that are dynamic -- sometimes ViewA might need 3 "input views" nested in it, sometimes it might need 1.
I was achieving that before with ListBox, ItemsSource, and DataTemplate. So my question is this: how can I dynamically populate which views to display, and (more importantly) how can I dynamically fill those views with x number of control A, and y number of control B?
First off, a high-level overview of the project structure...
YourProjectName
Controllers
ProductController.cs
Models
ProductViewModel.cs
Views
_ProductPartial.cshtml
ListProducts.cshtml
ProductViewModel.cs
public class ProductViewModel
{
public string Name { get; set; }
public string Description { get; set; }
}
ProductController.cs
public class ProductController : Controller
{
public ActionResult Index()
{
// Create your model (this could be anything...)
var model = new List<ProductViewModel>
{
new ProductViewModel { Name = "Apple", Description = "A red apple" },
new ProductViewModel { Name = "Orange", Description = "An orange orange" }
};
// Return the main view and your model
return View("ListProducts", model);
}
}
_ProductPartial.cshtml
#model YourProjectName.Models.ProductViewModel
<h1>#Model.Name</h1>
<p>#Model.Description</p>
ListProducts.cshtml
#model System.Collections.Generic.List<YourProjectname.Models.ProductViewModel>
#foreach (var product in Model)
{
Html.Partial("_ProductPartial", product)
}
Summary
Now if you request that controller action (localhost/Product/Index or whatever it ends up being for you), the controller will create the model, render the parent view, and the parent view will render as many of the product partial views as necessary depending on the product model collection we defined in the controller. Views and partial views don't require models, but I imagine you will be using a model class of some sort to help you determine what/where/how many partial views to render in your parent views. This is about as basic as it gets but it should get you started in using partial views.
I just successfully set up a many-to-many relationship between BlogPosts and Topics in Entity Framework code first approach. So there are a list of topics ("CSS", "HTML", "ASP.NET") that a BlogPost can have many of and vice versa. So currently I had EF create 3 tables, the middle table being the id of both the BlogPost and the Topic itself.
Now I am in the Razor view of my homepage.
#model MvcBlog.Models.MyModel
#foreach (var post in Model.Posts)
{
<div class="blogpost">
<h2>#Html.DisplayFor(modelItem => post.Title)</h2>
<div class="post_info">#Html.DisplayFor(modelItem => post.DateCreated)<span class="right">Blog</span></div>
<p>#Html.Raw(post.Content)</p>
<div class="post_close">
<span class="left">
***********************
</span>
<span class="right"><img src="Content/images/comment.jpg" alt="" /> 0 comments</span>
</div>
</div>
}
All of the above works just fine, but I want to replace the * with the topics associated with this particular post. I can't seem to figure this out. Do I have to pass the model differently from the controller? Currently I am passing the entire DB to this page as it will be using various info from different tables. I am just lost on this one. Any help would be really appreciated! (Obviously I want to do something similar with comments)
Thanks!
No, no, no, do NOT pass the entire database to the view. You need to be abstracting your view data from your database. Create a view model containing just the data you need for this view in the format best suited for the view to consume it. Use your controller or model code, depending on whether you believe in fat or thin controllers, to transform the data from the database into the view model. The whole point of MVC is separation of concerns and if you let your data model leak into the view code, you will be losing this basic idea and lose the benefits of loose coupling between the various layers.
To get you started on the recommended course of action. Your view model will be a normal class:
public class PostViewModel
{
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public List<Topic> Topics { get; set; }
public List<Comment> Comments { get; set; }
}
In your controller, you populate what you need for the view
public ActionResult Index()
{
// assuming entity framework
List<PostViewModel> posts = (from p in context.Set<Post>()
select new PostViewModel {
Title = p.Title,
DateCreated = p.DateCreated,
Topics = p.Topics
}).ToList();
return View(posts);
}
And in your view
#model List<PostViewModel>
#foreach(Post post in Model)
{
#Html.DisplayFor(m=>m.Title)
#foreach(Topic topic in post.Topics)
{
}
}
Do I have to pass the model differently from the controller?
Yes. Make a model specifically for the needs of your view (a view model). Fill in the correct object graphs there (which blog goes to which topic). Then pass that instantiated object into the view and reference those objects in your view.
I have a view model as such:
public class MyViewModel
{
public MyObject myObject{ get; set; }
public List<MyList> myList{ get; set; }
}
I have a view with a form strongly typed to MyViewModel
This view allows you to enter values for the properties of MyObject, as well as create a list of MyList objects. The List part works fine although I thought that would be the more difficult of the two.
Assuming MyObject has a property Description I create a textbox to enter the value as such:
#Html.EditorFor(x => x.myObject.Description);
The text box renders with an id of MyObject_Description...The problem is when I post this to my controller action, MyObject does not get bound at all(althought the list items do as they recieve the appropriate IDs of "MyViewModel_MyList[guid].myListValue")
What am I doing wrong here??
EDIT: more info
The first line of the view is:
#model MyApp.ViewModels.MyViewModel
And the Action method:
[HttpPost]
public ActionResult Create(MyViewModel myViewModel)
{
}
I am passing a new MyViewModel into the partial view to begin...
public ActionResult Create()
{
MyViewModel model = new MyViewModel();
return PartialView(model);
}
EDIT 2
Ok When I render my partial view that contains the forms I call :
#{Html.RenderAction("Create", "MyController");}
this is called from within a View of type #model IEnumerable<MyApp.Models.MyObject>
(this view displays a list of currently existing MyOjects, and at the bottom the partial is rendered to allow the user to add another MyObject to the DB)
If you are not already doing so, try creating a editor template (e.g., Views->Shared->EditorTemplates) called MyObject.cshtml. Move your partial view content to this view and then call
#Html.Editor("myObject").
from your parent partial view.
Change your ViewModel to have the Description directly
public class MyViewModel
{
public string Description { get; set; }
public List<MyList> myList{ get; set; }
}
then bind accordingly
#Html.EditorFor(x => x.Description);
I would expect the top line of your view to look something like this:
<%# Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<MyViewModel>" %>
This tells the view that the model it is supplied is of type MyViewModel (a la <T> style).
I don't think the out of the box model binding knows how to bind to complex objects. You're probably going to have to write up some sort of custom model binder.
I'm afraid it's not something I've done since MVC1 so I'm a bit hesitant to give you any sample code because the mechanism may well have changed completely since then. A quick google did turn up this article http://www.learnxpress.com/asp-net-mvc-hosting-6-tips-for-asp-net-mvc-model-binding-2.html and this article http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html.
Edit: I've just seen this answer which might help Retrieving data from view, should I use model binder?