So I have this master/detail setup with a WebGrid. All is well until I try and use RenderPage to display the detail when a record is clicked:
<div class="innerbox">
#{
if(gdEligibility.HasSelection){
#RenderPage("~/Views/Eligibility/EligibilityPolicyDetailView.cshtml",
new { Customer = gdEligibility.SelectedRow })
}
}
</div>
Everything works, if I put a break point I can step through the detail view's cshtml file no problem, no errors. But nothing is ever rendered between the outer div's. Ever. Why doesn't RenderPage return anything? I even tried adding .ToHtmlString() on the end of the line but still nothing.
The detail cshtml:
#{ foreach(TravelInsurance.Models.Policy p in Page.Customer.Policies){
<fieldset>
<legend>Policy</legend>
<div class="display-label">Policy Number</div>
<div class="display-field">
#Html.DisplayFor(model => p.PolicyNumber)
</div>
<div class="display-label">Premium</div>
<div class="display-field">
#Html.DisplayFor(model => p.Premium)
</div>
</fieldset>
}}
This question suggests that RenderPage will use the parent model...
Maybe try using
#{Html.RenderPartial("EligibilityPolicyDetailView", new { Customer = gdEligibility.SelectedRow });}
or
#Html.Partial("EligibilityPolicyDetailView" ,new { Customer = gdEligibility.SelectedRow })
Pl. try again by removing the attribute "ajaxUpdateContainerId" in the grid definition and it should work.
Related
I have a partial view like this (simplified):
#model Portal.Models.LoginModel
<div class="login-container k-block">
<section id="login-form" class="">
#using (Html.BeginForm(actionName, controllerName, new { ReturnUrl = ViewBag.ReturnUrl }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset id="login-form-list-items">
<ol>
<li>
#Html.LabelFor(m => m.CardNumber)
#Html.TextBoxFor(m => m.CardNumber, new { #class="k-textbox"})
<div class="k-error-colored">
#Html.ValidationMessageFor(m => m.CardNumber)
</div>
</li>
<li>
#Html.LabelFor(m => m.Pin)
#Html.PasswordFor(m => m.Pin, new { #class="k-textbox"})
<div class="k-error-colored">
#Html.ValidationMessageFor(m => m.Pin)
</div>
</li>
<input id="login-input-submit" class="k-button" type="submit" value="Enter" />
</fieldset>
</div>
And in my login view I call this partial view like:
#model Portal.Models.LoginModel
#Html.Partial("_LoginFormNoRegistration", Model, new ViewDataDictionary { { "actionName", "Login" }, { "controllerName", "Account" } })
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The problem is that when the login method in the controller adds an error like:
public ActionResult Login(LoginModel model, string returnUrl)
{
//...
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
The message is not show in the validation summary... I don't understand why... What could be the problem? Some javascript library missing?
Update
I also found that the form generated as the novalidate attribute set:
<form action="/" method="post" novalidate="novalidate">
//...
</form>
I don't know why.
I found the problem.
I was passing a new ViewData in the RenderPartial which was overriding the ViewData of the parent view, so the model state was lost, as explained here: Pass Additional ViewData to an ASP.NET MVC 4 Partial View While Propagating ModelState Errors.
Changing the main view to:
#model Portal.Models.LoginModel
#{
ViewData.Add("actionName", "Login");
ViewData.Add("controllerName", "Account");
Html.RenderPartial("_LoginFormNoRegistration", Model, ViewData);
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Did the trick!
Also, if you want to show a general error message for the model in the validationsummary, be sure to add the error with an empty string as key:
ModelState.AddModelError("error", "The user name or password provided is incorrect."); - doesn't work
ModelState.AddModelError("", "The user name or password provided is incorrect."); - works
Remove the true argument in #Html.ValidationSummary()
It could be a few different things off the top of my head. First off you may not be including the required JavaScript. You may not need all of these but i include these in almost all of my layout views.
<script src="#Url.Content("~/Scripts/jquery-1.8.3.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
Also, could you show the code for your partial view? If you are referencing values that are inside a child class in your model the validation can act a little wonky sometimes.
Lastly, this sounds silly but as you did not post the code for your login model make sure you are using the proper data annotations for the values that you want the validation to show up for.
I have a View where the User can leave a feedback comment in regards to the content video that is playing on the page. Using the following code I am able to manually enter the UserID and ContentID and it saves to the database without problem.
#using (Html.BeginForm("Screencast", "Media", FormMethod.Post, new { id = "form", enctype = "multipart/form-data" }))
{
<div class="row">
<div class="six columns">
<div class="row">
<div class="six columns">
#Html.LabelFor(c => c.FeedbackComment.UserID)
#Html.TextBoxFor(c => c.FeedbackComment.UserID)
</div>
<div class="six columns">
#Html.LabelFor(c => c.FeedbackComment.ContentID)
#Html.TextBoxFor(c => c.FeedbackComment.ContentID)
</div>
<div class="row">
<div class="twelve columns">
#Html.LabelFor(c => c.FeedbackComment.FeedbackString)
#Html.TextAreaFor(c => c.FeedbackComment.FeedbackString)
</div>
</div>
</div>
</div>
</div>
<input type="submit" value="Submit button" class="medium button bottom20"/>
}
However, when the user is on the page before the HTTP post I actually have the related variables in my Model called:
Model.User.UserID
Model.SelectedItem.ContentID
I want to pass these in as hidden fields but when I try to do either:
#HtmlHiddenFor(c => c.FeedbackComment.UserID, #Model.User.UserID)
#HtmlHiddenFor(c => c.FeedbackComment.ContentID, #Model.SelectedItem.ContentID)
or
#HtmlHidden("UserID",#Model.User.UserID)
#HtmlHidden("ContentID",#Model.User.UserID)
Then these values are returned null despite the values being populated before the post. I read about a workaround of putting the input tags in manually but when I did this the #Using.HtmlBeginForm was returning an error of not being set to an instance of an object
Can someone please help me to understand how I can pass these values to the same page using the values I have in the model prior to the post.
Given the following view model (partial):
public class YourViewModel
{
public User User { get; set; }
public SelectedItem SelectedItem { get; set; }
}
You can bind the these properties to a hidden form element. When you post back these properties will still contain their values.
#Html.HiddenFor(x => x.User.UserID)
#Html.HiddenFor(x => x.SelectedItem.ContentID)
Your action method:
public ActionResult YourActionMethod(YourViewModel viewModel)
{
// When you debug here you will see that the values are retained
}
I hope this helps.
I'm trying to filter a list of entities and update the partial view on the page with the filtered data. The partial view is returning the correct model with the filtered data, but is not being rendered inside the parent page. Instead it is being rendered in "body" element of an empty HTML page. I've found many topics on this but even though I appear to be following their directions, I'm still having no luck. A fresh set of eyes from the community here may be a huge help.
#model PennLighting.ViewModels.LinecardViewModel
#{
ViewBag.Title = "Linecard";
}
<div class="linecard-head">
#using (Ajax.BeginForm("Index",
new AjaxOptions
{
UpdateTargetId = "linecard"
}))
{
#Html.EditorFor(model => model.Categories)
<div class="buttons">
<input type="submit" name="btnFilter" value="Filter" />
<input type="submit" name="btnShowAll" value="Show All" />
</div>
}
</div>
<div id="linecard">
#Html.Partial("Linecard")
</div>
#section Scripts
{
#Scripts.Render("~/bundles/jqueryval")
}
public ActionResult Index()
{
var viewModel = new LinecardViewModel();
viewModel.Categories = db.Categories
.OrderBy(c => c.Name).ToList();
viewModel.Manufacturers = db.Manufacturers
.OrderBy(m => m.Name).ToList();
return View(viewModel);
}
public ActionResult Index(string btnFilter, string[] selectedCategories)
{
var viewModel = new LinecardViewModel();
var selectedMfrs = new List<Manufacturer>();
if (btnFilter != null && selectedCategories != null)
{
var categoryIds = selectedCategories.Select(c => int.Parse(c)).ToArray();
if (categoryIds != null)
{
selectedMfrs = db.Manufacturers
.Where(m => m.Categories.Any(c => categoryIds.Contains(c.ID)))
.OrderBy(m => m.Name).ToList();
}
}
else
selectedMfrs = db.Manufacturers.OrderBy(m => m.Name).ToList();
viewModel.Manufacturers = selectedMfrs;
return PartialView("Linecard", viewModel);
}
<!DOCTYPE html>
<html>
<head>
<title>#ViewBag.Title</title>
#Styles.Render("~/Content/themes/base/css", "~/Content/css")
</head>
<body>
<div id="container" class="round-bottom">
<div id="header">
<div id="header-left">
<div id="logo">
<a href="#Url.Content("~/")">
<img src="#Url.Content("~/Content/Images/logo.png")" alt="Penn Lighting Associates" /></a>
</div>
</div>
<div id="header-right">
<ul class="nav">
<li>#Html.ActionLink("Home", "Index", "Home")</li>
<li>#Html.ActionLink("About", "Index", "About")</li>
<li>#Html.ActionLink("Linecard", "Index", "Linecard")</li>
<li>#Html.ActionLink("Events", "Index", "Events")</li>
<li>#Html.ActionLink("Gallery", "Index", "Gallery")</li>
<li>#Html.ActionLink("Contact", "Index", "Contact")</li>
<li><a href="http://oasis.pennlighting.com:81/OASIS/desk/index.jsp" target="_blank">
Customer Login</a></li>
</ul>
</div>
</div>
<div id="main">
#RenderBody()
</div>
</div>
<div id="footer">
<p>
Copyright © 2008 Penn Lighting Associates</p>
</div>
#Scripts.Render("~/bundles/jquery")
#RenderSection("scripts",false)
</body>
</html>
So what am I missing? Thanks!
You cannot have 2 actions on the same controller with the same name accessible on the same HTTP verb. You might want to decorate your Index contorller action that is invoked with an AJAX call and returns a partial with the [HttpPost] attribute:
[HttpPost]
public ActionResult Index(string btnFilter, string[] selectedCategories)
{
...
}
Without seeing more of your solution, it's a bit fuzzy, but I believe you want to still return the Index and pass the model data into the Partial in your view. The way you are doing it would return only the partial view, which is why you're getting those results.
So in the filtered index:
return View(viewModel)
And in the index view, pass the data to the partial, which I assume without seeing has the right model association to display.
UPDATE
If you're looking to dynamically pull a subset of data and leave the rest untouched, then do an AJAX POST with the filter information to the action specified for the partial view. Take the data results and place them in the Linecard div.
There are many ways to send the data (bundle by JSON, serialize form, individual data points). Here are some examples:
http://brandonatkinson.blogspot.com/2011/01/using-jquery-and-aspnet-mvc-to-submit.html
MVC ajax json post to controller action method
The problem was that my jqueryval bundle was missing the jquery.unobtrusive-ajax.js file. My code works as is once that was included.
I have the following index view:
#model BoringStore.ViewModels.ProductIndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Produkte</h2>
<div id='addProduct'>
#{ Html.RenderPartial("Create", new BoringStore.Models.Product()); }
</div>
<div id='productList'>
#{ Html.RenderPartial("ProductListControl", Model.Products); }
</div>
The "productList" is just a list of all products.
The addProduct renders my Create View:
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<div id="dialog-confirm" title="Produkt hinzufügen" style="display:none">
#using (Ajax.BeginForm("Index_AddItem", new AjaxOptions { UpdateTargetId = "productList" }))
{
#Html.Raw(DateTime.Now.ToString());
<div>
#Html.LabelFor(model => model.Name)
#Html.EditorFor(model => model.Name)
</div>
<br />
<div>
#Html.LabelFor(model => model.Price)
#Html.EditorFor(model => model.Price)
</div>
<br /><br />
<div>
<input type="submit" value="Produkt hinzufügen" />
</div>
}
When submitting the form, the Index_AddItem-method in my controller is called. Unfortunately the form always calls the method twice. :(
Can someone help me out?
Don't include scripts inside your partial views! They should go the your "main" views or your _layout.cshtml.
Your problem is that you have included the jquery.unobtrusive-ajax.min.js twice in your page. Once in your Create partial and once somewhere else. Because if you include that script multiple times it will subscribe on the submit event multiple times so you will get multiple submit with a single click.
So make sure that you have include that script only once in a page. So move the jquery.unobtrusive-ajax.min.js reference into your index view.
insure that you dont have jquery.unobtrusive-ajax.min.js file duplicated in the layout
as mentioned above, and you can check this using browser Inspectors
Another problem which may cause this error that i have encountered is resting a form using jquery in the ajax request as following
$("form").trigger("reset"); //just remove this line
I'm working with MVC 3 / Razor for the first time and it seems odd that all the examples and VS scaffolds for create and edit views all have separate HTML views for these concepts.
There is really not much difference between many Create/Edit forms so I was wondering why I can't find examples of people using a single Update form that can be used by both Create and Edit actions.
I have gotten an Update.cshtml view working but was wondering about how it talks to the Edit or Create action method on the controller.
My questions are:
Anyone have a quick answer to talking to the controller, or
Anyone know of a tutorial showing good practice for working this way, or
Is there some good reason for keeping the Create/Edit views separate when the HTML is often the same.
Cheers Dave
This (kind!) of question is asked before: ASP.NET MVC - using the same form to both create and edit
Basically you can create a partial view and include it on your Create and Edit view.
Scott Guthrie has a nice post about Partial Views.
(I've read about this somewhere, but can't find it, I'll update this post when I do find it)
Be mindful that answers to your question should also be driven by business need (and roles). The scaffolding does provide separate functionality, which in some cases is the preferred implementation.
CREATE and EDIT functionality is often pretty much identical from a technical (programming) perspective. This can lead a technical person to think that the functionality should be combined in order to implement a more efficient technical solution. However, any technical implementation must be in response to business need, which might require separation (e.g. by business role) of these concerns.
For example, a business may require that the role which CREATEs business objects is not the same one as EDITs them. In this case, the implemented web pages may not be seen by the same roles (and people) at all.
If you implement CREATE and EDIT using common functionality but the business need is for role separation, you must still implement "role checking" before rendering the required view/partial view/etc. In such cases, separate views can be a preferred implementation.
I do this. I don't know if it's best practice but it can be nice. There are some situations where a completely separate add/edit view could be useful though. Also, if you're using ViewModels then as far as I can tell you're stuck using the same ViewModel for both add and edit. In theory they should both have their own ViewModels.
Here's how this looks for me:
AddVideo.cshtml
#model Multimedia.MediaVideoViewModel
#{
Layout = "~/Views/Shared/LiveSubLayout.cshtml";
}
#section AdditionalHeadContent {
}
<div class="page-header">
<h1>Add a new video</h1>
</div>
<div id="add-video" class="row-fluid">
#Html.Partial("_VideoForm", Model, new ViewDataDictionary { { "ActionKeyword", "Add" } })
</div>
EditVideo.cshtml
#model Multimedia.MediaVideoViewModel
#{
Layout = "~/Views/Shared/LiveSubLayout.cshtml";
}
#section AdditionalHeadContent {
}
#if (ViewBag.Success)
{
<div class="alert alert-success">
<button class="close" data-dismiss="alert">×</button>
<h3><strong>Video saved!</strong></h3><br/>
<div class="btn-group">
Preview this video
#Html.ActionLink("Add Another Video", "AddVideo", "Multimedia", new { Model.Id }, new { #class = "btn" })
#Html.ActionLink("View all media", "Index", "Multimedia", null, new { #class = "btn" })
</div>
<p>or continue editing below...</p>
</div>
}
<div class="page-header">
<h1>Edit video <small>#Model.Title</small></h1>
</div>
<div id="edit-video" class="row-fluid">
#Html.Partial("_VideoForm", Model, new ViewDataDictionary { { "ActionKeyword", "Edit" } })
</div>
_VideoForm.cshtml (partial)
#model Multimedia.MediaVideoViewModel
#{
string actionKeyword = ViewData["ActionKeyword"].ToString();
}
<div class="span6">
#using (Html.BeginForm("editvideo", "multimedia"))
{
<label class="control-label" id="embed-url">Paste video URL here:</label>
<div class="control-group">
#Html.TextBoxFor(model => model.EmbedUrl, new { #class = "span12", id = "video-url", placeholder = "ex: http://www.youtube.com/watch?v=PoAGasPLh30" })
<button class="btn disabled" id="get-video" title="Tooltip">Get Video</button>
</div>
<div class="video-meta">
<h3>Video Information</h3>
<label class="control-label">Title:</label>
<div class="control-group">
#Html.TextBoxFor(model => model.Title, new { #class = "span12", id = "video-title" })
#Html.ValidationMessageFor(model => model.Title, "A title is required", new { #class = "label label-important" })
</div>
<label class="control-label">Description:</label>
<div class="control-group">
#Html.TextAreaFor(model => model.Description, new { #class = "span12", id = "video-description" })
</div>
<h3>Categories</h3>
<div id="tag-search" class="well">
<label class="control-label">Search tags:</label>
<div class="controls"><input type="text" class="typeahead" /></div>
#if (Model != null)
{
foreach (var category in Model.Tags)
{
#Html.Partial("_TagFragment", category)
}
}
</div>
<hr />
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ThumbnailUrl, new { id = "thumb-url" })
<input type="submit" id="video-submit" name="video-submit" class="btn-large btn-primary" value="#actionKeyword video" />
</div>
}
</div>
I edited these down a bit so something might be missing but this should give you the general idea.
here's how i do it, it's not always the best practice (it depends on the case)
1/ combine the controller actions for create and edit
public PartialViewResult Creedit(string id = null)
{
if (id == null)
{
// Create new record (this is the view in Create mode)
return PartialView();
}
else
{
// Edit record (view in Edit mode)
Client x = db.ClientSet.Find(id);
if (x == null) { return PartialView("_error"); }
// ...
return PartialView(x);
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Creedit(Client x)
{
if (x.id == null)
{
// insert new record
}
else
{
// update record
}
}
2/ combine the edit and create views into one view i called Creedit
// if you need to display something unique to a create view
// just check if the Model is null
#if(Model==null){
}
so i have 1 view and 2 actions (1 post and 1 get) instead of 2 views and 4 action.
Look into MVC scaffolding in nuget as well, when it generates the view files it does so explicitly creating a creatandedit partial and having the create page and edit page use that partial.