Having read In ASP.NET MVC3, how should one render multiple PartialViews backed by multiple Models?, and some others, I still don't know the best way to go about validating multiple partial-view--forms--models on a page.
I don't doubt that whatever's compelling me to think of them as triples right now is the problem. All I ask is that the "why's" be comments and the answers be answers.
I have a view, "Editor", that does multiple render-actions that prefill some forms:
#{
Html.RenderAction("EditName", new { name = Url.RequestContext.RouteData.Values["ID"]});
}
#{
//returns EditColor partial view prefilled with this widget's color's properties
Html.RenderAction("EditColor", new { name = Url.RequestContext.RouteData.Values["ID"]});
}
So I have an EditColorFormModel that has something like (since I don't want extravagant color names)...
[StringLength(8)]
public String Name
{
get;
set;
}
And I have a POST action to accept the EditColorForm submit with something like...
if (!ModelState.IsValid)
{
return View("Editor", EditColorFormModel);
}
But if so, I get stack overflow. I guess that's because this model hits that RenderAction to start the loop. I tried RedirectToAction("Editor", EditColorFormModel), but the validation messages don't get to the summary helper under EditColor partial view, which I'm assuming is because the model does not hit the RenderAction after all. So does it, or doesn't it, and what should I do?
My own answer is that I didn't ask the right question, and that I need a complete isolated example of the problem. I left the model out because there's no logic there, it's got some properties with validation attributes applied.
Controller:
[ChildActionOnly]
public PartialViewResult ColorEditor(Int32 WidgetId)
{
return PartialView(new Models.WidgetColorModel() {
Id = WidgetId
//prefill whatever else on form
});
}
[ChildActionOnly]
public PartialViewResult DimensionEditor(Int32 WidgetId)
{
//same for dim
}
[HttpPost]
public ActionResult ColorEditor(Models.WidgetColorModel m)
{
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
ModelState.AddModelError("", "Something not Kosher-colored in Denmark");
return View("Editor", m);
}
}
[HttpPost]
public ActionResult DimensionEditor(Models.WidgetDimensionModel m)
{
//same for dim
}
View:
<body>
<div>
#{
Html.RenderAction("ColorEditor", new { WidgetId = Context.Request.Params["WidgetId"]});
Html.RenderAction("DimensionEditor", new { WidgetId = Context.Request.Params["WidgetId"]});
}
</div>
</body>
Partial Views (DimensionEditor is structured exactly the same):
#model MvcApplication2.Models.WidgetColorModel
#using (Html.BeginForm("ColorEditor","Home", FormMethod.Post))
{
#Html.ValidationSummary()
<span>Specialized color editor for Widget #Model.Id</span>
#Html.EditorForModel()
<input type=submit name= "ColSubmit" value="Sumbit" />
}
Top-level view does RenderAction
ChildAction controller executes
Top-level view does RenderAction for the second editor
ChildAction controller executes
I fill in a bad value on one of the forms and submit
HttpPost action method picks it up, adds error message, and tries to render the top-level view with the same model
Top-Level view does RenderAction
HttpPost action method picks it up again (in the words of Rick Perry, "...")
And the result is a stack overflow exception because the View("Editor") reenters the Editor(Model) action method with RenderAction which warns about merging the RouteValues.
My question, of course, is what to change/insert, based on what, and where (in the controller, stupid) to prevent the overflow.
Related
I have an ASP.NET Core 5 web application and would like to know what strategy to use for partial vs. normal views.
So, for example, when a user navigates in the web browser to /bars, I would like to load the page with the Bar list (along with the menu, headers, footer, and other shared layout elements).
However, if the user clicks on the Bars menu, I would like to reload only the main container (without the menu, headers, footer, etc.).
In the Controller, should I create an Action for each page with a PartialAction, like this:
public async Task<IActionResult> Index()
{
return View(await _repository.ListAsync<T>());
}
public async Task<IActionResult> IndexPartial()
{
return PartialView(await _repository.ListAsync<T>());
}
And then call the AJAX on the menu with /Bar/IndexPartial, leaving the main action for the normal view? Related, should I create separate views for each action?
and call the ajax on the menu with "/bar/IndexPartial", leaving the main action for the normal view?
(should I, by the way, create separate views for each action?)
Yes, Index action for the normal Index view. Create a new partial view called "_IndexPartial.cshtml" for content that may change in the main view. Here you can just put the main container to your partial view. When click the button, use ajax to request IndexPartial to get the returned partial view html content, then replace it in the main view.
A simple example for understanding:
Index.cshtml:
#model List<Book>
#{
ViewData["Title"] = "Home Page";
}
<label>BookList:</label>
<div id="bookpartial">
#foreach(var book in Model)
{
<div>
#book.Name
</div>
}
</div>
<button id="update">Update</button>
#section scripts{
<script>
$("#update").click(function () {
$.ajax({
method: 'get',
url: 'Home/IndexPartial',
success: function (result) {
$('#bookpartial').empty();
$('#bookpartial').html(result);
}
})
})
</script>
}
_IndexPartial.cshtml:
#model List<Book>
#foreach (var book in Model)
{
<div>
#book.Name
</div>
}
Controller:
public IActionResult Index()
{
var books = new List<Book>
{
new Book{ BookId = 1, Name = "BookA"},
new Book{ BookId = 2, Name = "BookB"},
new Book{ BookId = 3, Name = "BookC"},
};
return View(books);
}
public IActionResult IndexPartial()
{
var newbooks = new List<Book>
{
new Book{ BookId = 4, Name = "BookD"},
new Book{ BookId = 5, Name = "BookE"},
new Book{ BookId = 6, Name = "BookF"},
};
return PartialView("_IndexPartial", newbooks);
}
Result:
This is an interesting question! Your general approach of exposing an action for each page and returning a PartialViewResult for the inner contents of that page makes good sense to me.
If the only difference between the full view and the partial view is the common elements, however, I'd be looking into ways of centralizing the entry point views—if not the entry point actions themselves—as otherwise they're going to get really repetitive.
Below, I’ll scaffold the basic strategy I’d consider for handling the main entry point.
Note: Given the nature of the question, I’m going to assume you’re comfortable implementing the AJAX portion, and instead focus exclusively on the server-side architecture, which I believe to be at the heart of your question. Obviously, the existing answers address the client-side implementation, if that’s important.
View Models
To begin, you might establish a general view model that handles the overall page content. This might include properties for e.g. your navigation items. But it would also contain the name of the (partial) view as well as the view model for that view, as you’ll likely want these in order to pre-render the inner content:
public class PageViewModel
{
…
public string View { get; set; }
public object ViewModel { get; set; }
}
You'd then have individual view models for each page, such as:
public class BarListViewModel
{
public Collection<Bar> Bars { get; } = new();
}
Controller
Now, in your controller, you’ll introduce a method that establishes a BarListViewModel, so that you don’t need to repeat your logic between both your entry point as well as the actions which return a partial view:
[NonAction]
public BarListViewModel GetBarListViewModel()
{
var viewModel = new BarListViewModel()
{
Bars = …
};
return viewModel;
}
With that, you can now have an action that delivers your Bars page, similar to what you proposed:
public IActionResult Bars() => PartialView(GetBarListViewModel());
But, instead of having a different Index action for each partial, you could instead have something like the following
public IActionResult Index(Page page = "Home")
{
var viewModel = new PageViewModel
{
View = page.ToString(),
ViewModel = page switch
{
"Bars" => GetBarListViewModel(),
_ => GetDefaultViewModel()
}
};
return View("Index", viewModel);
}
This provides a single “dispatch” for wiring up any entry point, without needing to create a new action or view for each one.
View
Finally, in your Index.cshtml, you'd have something like the following to dynamically inject the correct partial view based on the view model:
#model PageViewModel
…
<main>
#await Html.PartialAsync(Model.View, Model.ViewModel)
</main>
…
That would load an e.g. Bars.cshtml which would contain the logic for looping through the BarListViewModel.Bars property.
Note: This assumes you want to prerender your partial view on the server side. Obviously, if you want the initial load to occur via AJAX, you wouldn’t need to pass the nested BarListViewModel, or call PartialAsync().
Routing
Presumably, the above would be paired with a route configuration that would allow the page to be passed in as a route parameter—possibly even baking in a default Controller and Action for your entry point:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{page?}",
new { controller = "Home", action = "Index" }
);
});
Note: Obviously, you’ll need to be careful not to lock out your AJAX calls or other entry points with an overly greedy route. The above is just intended as a basic demonstration of how to capture the page route parameter.
Conclusion
This is similar to the approach proposed by #User1997, but it allows their proposed Index.cshtml and implied Index() action to be reused for all of your entry points. And, of course, if you had multiple controllers that followed this pattern, this could even be generalized as a base controller.
You should create two views and one controller for each view.
One parital view: _IndexPartial.cshtml and a normal view: Index.cshtml.
Partial view renders a portion of view content so you should include to the normal view "Index.cshtml" when the page is rendered for the first time.In this way you remove code rewriting.
Everytime you want to change the content make a request to the ParitalView Controller and add the response(partial view) to the html element that displays the partial view.
#model List<T>
#{
ViewData["Title"] = "Test";
}
<label>Partial Content</label>
<div id="partialContent">
#await Html.PartialAsync("_IndexPartial.cshtml", Model)
</div>
<button id="UpdateContent">Update</button>
#section scripts{
<script>
$("#UpdateContent").click(function () {
$.ajax({
method: 'get',
url: 'Home/IndexPartial',
success: function (response) {
$('#partialContent').html(response);
}
})
})
</script>
}
I am fairly new to MVC and had a question about a form I am creating. The page has a form at the top and a grid at the bottom. As people enter data into the form and click the button, the form data is added to the grid below.
My plan is to use a BeginForm and send the form to an HttpPost controller method for processing and then bounce back to the view. Currently, I am using this for the form on the view:
#using (Html.BeginForm("AddRefund", "Refund", FormMethod.Post))
In the controller, I have this:
[HttpPost]
public ActionResult AddRefund(RefundModel refund)
{
if (ModelState.IsValid)
{
(etc...)
My problem is that the "refund" object in controller always arrives from the view empty. From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view. I don't understand why this is, however, as it would seem very valuable to be able to send a populated viewmodel from the view to a controller.
Also, how would you guys handle the code for this problem? How would you collect all of these form submissions from the user, present them to the user in the grid below the form, and then ultimately submit the page and insert all of the items in the grid into the database?
edit: here is my view
#model RefundsProject.Models.RefundModel
#using (Html.BeginForm("AddRefund", "Refund", FormMethod.Post))
{
(all of the form elements are here)
<input id="button-add" type="submit" value=" Add Refund to List " />
}
Eventually, there will be another button at the very bottom of the view that will submit all of the items the user entered into the grid to the database.
From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view.
This is completely the opposite of the way ASP.Net MVC was designed. ASP.Net comes with default ModelBinders that are used to Bind data from a Form, Querystring, Ajax (Json and XML) to a strongly typed object for a Controller Method.
My problem is that the "refund" object in controller always arrives from the view empty.
This is most likely due to a lack of knowledge or a misunderstand of how model binders work.
Also, how would you guys handle the code for this problem?
I would Ajax Post the RefundModel back to the controller to validate the refund. If it is valid, then dynamically create fields in the form that will eventually model bind back to an IEnumerable/List on a new method that will then verify all the refunds, one at a time (to validate the data again).
Here is an Extremely broken down example (probably needs some work, but the important parts are there):
Classes:
public class AddRefundsViewModel
{
public RefundModel Refund { get; set; }
}
public class RefundModel
{
public string Reason { get; set; }
public Decimal Amount { get; set; }
}
Methods:
public ActionResult AddRefunds()
{
var model = new AddRefundsViewModel()
model.Refund = new RefundModel();
return this.View(model);
}
[HttpPost]
public ActionResult ValidateRefund(AddRefundsViewModel model)
{
var result = new { isValid = modelState.IsValid };
return this.Json(result);
}
[HttpPost]
public ActionResult ValidateRefunds(IEnumerable<RefundModel> model)
{
var isRefundsValid = true;
foreach (var refund in model)
{
isRefundsValid = TryValidateModel(refund);
if (!isRefundsValid )
break;
}
if (isRefundsValid)
{
}
else
{
// either someone hacked the form or
// logic for refunds changed.
}
}
Views:
#model AddRefundsViewModel
// assuming RefundController
#using (Html.BeginForm("Refund", "ValidateRefunds", FormMethod.Post))
{
#html.EditFor(m => m.Refund.Reason)
#html.EditFor(m => m.Refund.Amount)
<input type="button" id="addRefundButton" name="addRefundButton" value="add"/>
<input type="submit" id="submitRefundButton" name="submitRefundButton" value="submit all"/>
}
<!-- jquery -->
$(document).ready(function()
{
$('#addRefundButton').on('click', function()
{
$.ajax({
url: '/Refund/ValidateRefund',
data: $("addRefundForm").serialize(),
success: function(result)
{
if (result.isValid)
{
// create new hidden imput elements, and grid
$("addRefundForm")[0].reset();
}
else
{
// Refund isn't valid
}
}
});
});
});
From my research, it seems that the model reference in the controller is just there to provide model structure, and NOT to receive the actual model from the view. I don't understand why this is, however, as it would seem very valuable to be able to send a populated viewmodel from the view to a controller.
Your a bit wrong. There is a difference between ViewModel and Domain Model. View Model is a class that you use to process the logic between views and your domain (business).
Then there is Domain Model (in .net) this is usually some data container objects (POCO). This is anemic. Based on DDD there is a little difference.
So what is the best practive?
It is always good to use a ViewModel object to transfer data between your views and controller.
Then in controller you can use a mapper (automapper or valueinjecter) to transform them.
Now you have your domain object that you can process.
Using ViewModels to pass data both up and down between controllers and views is completely acceptable.
To help with your model coming up empty issue, inputs, such as <input id="FirstName" type="text" /> need to have name attributes for the MVC model binder to map what you posted into your RefundModel object. In your View code you shared, you only showed a submit button, so it is unclear if your other elements you expect to get mapped have names or not.
To fix my above example of an input tag, you would do <input id="FirstName" name="FirstName" type="text" /> or use a Razor helper: #Html.TextBoxFor(m => m.FirstName)
So right now if I want to change what partial view I display on a page based of off configuration I do the follow:
Config Item:
<add key="InstanceOwner" value="companyName" />
Call to render partial view:
<div id="" class="sidebarBox side-box3">
Html.RenderAction("ProductFeature", "Dashboard");
</div>
Controller ActionResult:
[OutputCache(Duration = 1)]
[ValidateInput(false)]
public ActionResult ProductFeature(string viewName = InstnaceOwner+"ProductFeature")
{
return PartialView(viewName);
}
I will name the partial views using a naming convention that is companyName-ProductFeature, the companies name will be variable. Doing it this way feels wrong and inefficient. I'm still really new to .NET MVC though and just want to know what the best approach to this is,Thanks!
the plan is to just change the value of the configuration key if I'm
having to change which instance (which customer) the app is being used
for
Based on that comment then it could just be as simple as reading the config value and use that to determine which view to show. You don't need to accept any parameters for the controller method.
public ActionResult ProductFeature()
{
var prefix = ConfigurationManager.AppSettings["InstanceOwner"];
return PartialView(prefix+"ProductFeature");
}
What version of MVC are you using?
If i was you, i will get all the data soon as i enter the page controller (e.g. HomeController)
public ActionResult Index()
{
var model = new SomeViewModel();
model.ProductFeature = InstnaceOwner + "ProductFeature"
return View(model);
}
Then in the View, (index.cshtml)
#Html.Partial("_ProductFeature", Model.ProductFeature)
and in the partial View (_ProductFeature.cshtml)
#model string
<span>Product Feature is #model</span>
Hope this answers your question
Is it possible from a Controller to show a view, and then dependant on what that user selects in dropDownList - render another different view back in the original calling controller? Kind of a "daisy-chaining" effect.
The thinking behind this - is a user selecting a vehicle type - (associated with an ID number) in a view, back in the Controller dependant on what was chosen will render another view immediately displaying HTML according to the vehicle type they chose e.g. an HTML page for car or a boat or aeroplane etc...
If this is possbile can someone point me to a code examaple?
Actual Database Model below - but it is for documents, not vehicles!
check the method paremetares of your action method and return different views baed on that . Something like this.
public ActionResult GetInfo(string id,string vehicleTypId)
{
if(String.IsNullOrEmpty(vehicleTypeId))
{
var vehicle=GetVehicleType(vehicleTypId);
return View("ShowSpecificVehicle",vehicle) ;
}
var genericVehicle=GetVehicle(id);
return View(genericVehicle);
}
EDIT : Saying so, I seriously think you should keep those in 2 seperate Action methods. That makes your code clean and better readable. You may move the common functionality to a function and call if from bothe the action methods id needed. So i would do it in this way
Assuming you have a ViewModel for the first page( displays all vehicletypes)
public class VehicleTypesViewModel
{
//other relevant properties
public IEnumerable Types { set;get;}
public int SelectedTypeId { set;get;}
}
Your GET request for the initial view will be handled by this action result.It gets all the Vehicle types and return that to your view in the ViewModels Types property.
public ActionResult VehicleTypes()
{
VehicleTypesViewModel objVM=new VehicleTypesViewModel();
objVM.Types=dbContext.VehicleTypes.ToList();
return View(objVM);
}
and in your View called VehicleTypes.cshtml,
#model VehicleTypesViewModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(Model.SelectedTypeId,new SelectList(Model.Types,"Text",Value"),"Select")
<input type="submit" value="Go" />
}
Another Action method to handle the form post. You have the selected type id here and you can get the specific details here and return a different view
[HttpPost]
public ActionResult VehicleTypes(VehicleTypesViewModel model)
{
// you have the selected Id in model.SelectedTypeId property
var specificVehicle=dbContext.Vehicles.Where(x=>x.TypeId=model.SelectedTypeId);
return View("SpecificDetails",specificVehicle);
}
Alternatively you can do a Get request for the specific vehicle using RedirecToAction method. I would prefer this approach as it sticks with the PRG pattern.
[HttpPost]
public ActionResult VehicleTypes(VehicleTypesViewModel model)
{
int typeId=model.SelectedTypeId;
return RedirectToAction("GetVehicle",new {#id=typeId});
}
public ActionResult GetVehicle(int id)
{
var specificVehicle=dbContext.Vehicles.Where(x=>x.TypeIdid);
return View(specificVehicle);
}
With Javascript : You can do a get call to the new view from your javascript also. without the HTTPpost to controller. You should add some javascript in your initial view for that
#model VehicleTypesViewModel
//Include jQuery library reference here
#Html.DropDownListFor(Model.SelectedTypeId,new SelectList(Model.Types,"Text",Value"),"Select")
<script type="text/javascript">
$(function(){
$("#SelectedTypeId").change(){
window.location.href="#Url.Action("GetVehicle","Yourcontroller")"+"/"+$(this).attr("id");
});
});
</script>
I think to get a better user experience create a partial view, and load that partial view in a div in the same page via an ajax call.
public ActionResult GetVehicalInfo(string id, string vehicleType)
{
var vehicle = GetVehicleType(id, vehicleTypId);
return PartialView("vehicle);
}
I have an HttpPost and HttpGet version of the action method Rate() :
http://pastebin.com/embed_js.php?i=6x0kTdK0
public ActionResult Rate(User user, Classified classified)
{
var model = new RatingModel
{
CurrentUser = user,
RatedClassified = classified,
};
return View(model);
}
[HttpPost]
public ActionResult Rate(RatingModel model)
{
model.RatedClassified.AddRating(model.CurrentUser, model.Rating);
return RedirectToAction("List");
}
The view that HttpGet Rate() returns:
#model WebUI.Models.RatingModel
#{
ViewBag.Title = "Rate";
}
Rate #Model.RatedClassified.Title
#using(Html.BeginForm("Rate","Classified", FormMethod.Post))
{
for (int i = 1; i < 6; i++)
{
Model.Rating = i;
<input type="submit" value="#i" model="#Model"></input>
}
}
I'm trying to find out to send a Model through the Form to the Post method, and my thinking was that the value "model" in the submit button's tag would be the parameter to do so, however null is being passed through if i breakpoint inside of the Post method. The for loop is trying to create 5 buttons to send the proper rating.
Thanks
Them model binding works on the name attribute as #Ragesh suggested you need to specify the name attributes matching the RatingModel properties in the view. Also note that the submit button values dont get posted to the server, there are hacks through which you can achieve that, one way is to include a hidden field.
Also in your provided code the loop runs six times and at the end Model.Rating will be equal to 5 always... what are you trying to achieve?. Say for example you have a model like
public class MyRating{
public string foo{get;set;}
}
in your view
#using(Html.BeginForm("Rate","Classified", FormMethod.Post))
#Html.TextBoxFor(x=>x.foo) //use html helpers to render the markup
<input type="submit" value="Submit"/>
}
now your controller will look like
[HttpPost]
public ActionResult Rate(MyRating model)
{
model.foo // will have what ever you supplied in the view
//return RedirectToAction("List");
}
hope you will get the idea
I think there are two things you need to fix:
The input tag needs a name attribute
The name attribute should be set to model.Rating