creating a partial view with a form post that planning on using in several places - asp.net-mvc-4

How can I create a partial view with a form post that I plan on using in several places?
The partial view will have a form that creates an entry in data storage and displays the persisted data underneath this form.
So after submitting the form I ll see my entry in a grid like structure under the form without switching the parent view.
If the model is not valid the error will be shown also. The trick here is, how do I stay in my current page without creating an action
In the controller of each view that shows the partial view?
I will be using this partial view in say 10 different parent views.
Below, i provide some of the codes that will help community to make sense the question exactly.
How should i configure my code to achieve my goal.
Thanks
This is the partial view sample
#model ViewModels.CommentViewModel
#using (Html.BeginForm("Index", "Comment", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary()
<div class="form-group">
<label class="control-label col-md-2">Please Type Your Name</label>
<div class="col-md-10">
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<input id="addComment" type="submit" value="Add" />
</div>
}
#foreach (var item in Model.Comments)
{
<p>
#item.Name
</p>
}
Here is Controller
public PartialViewResult Index(int id)
{
var model = new CommentViewModel() { Comments= db.Comments.Where(x=> x.NewsId == id && x.isApproved== true )};
return PartialView("_Comments", model);
}
[HttpPost]
public PartialViewResult Comment(int id, CommentViewModel model)
{
if (ModelState.IsValid)
{
var comment = new Comment()
{
Name = model.Name,
Title = model.Title,
CommentContent = model.Content,
Email = model.Email,
CreationDate = DateTime.Now,
RefId = Guid.NewGuid(),
isApproved = false,
NewsId = id
};
db.Comments.Add(comment);
db.SaveChanges();
return PartialView();
}
return PartialView();
}

If you want to do things like submit a form and retrieve updated data without reloading the page, then you're talking about AJAX. The fact that this is a partial view is meaningless in that context. It doesn't matter how many different views this partial view will be rendered in, you just need one action in one controller that can respond to an AJAX request. Then, you'll just need to do something like the following with JavaScript that can be included via an external file in whatever views need this form:
$('#MyPartialViewForm').on('submit', function (e) {
// prevents form from submitting standard way, causing page refresh
e.preventDefault();
$.post('/url/that/handles/form', $(this).serialize(), function (results) {
// results will be a rendered partial with the data here,
// which you can use to replace the content of a div or
// something on your page.
$('#DivWhereSubmittedDataIsDisplayed').html(results);
});
});
Then, in your action that responds to the AJAX request:
[HttpPost]
public ActionResult ActionForAjaxForm(FormModel model)
{
// do something like save the posted model
return PartialView("_PartialViewThatRendersData", model);
}

Related

Razor Pages - Return Error on Duplicate Name

I'm working on a Razor Pages form that takes in a string to create a new customer in a SQL Server Database. I want to make it work so that if the string that is the customer already exists, a prompt comes up that says "This Customer Already Exists". Just to be safe for data integrity.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
int customerCheck = -1; //No Customer ID is -1
try
{
using (var context = new DataWarehouseContext())
{
customerCheck = context.Customer //Tries to grab a Customer with this name
.Where(a => a.Name == Customer.name)
.Select(b => b.CustomerId)
.FirstOrDefault();
}
}
catch (Exception)
{
}
if(customerCheck == -1)
{
_context.Customer.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("/Customer/List");
}
else
{
return Page();
}
}
This is the code I have so far in my backend. What happens is that when a user tries to create a new customer, the backend of the page tries to see if it can grab a customer ID that correlates to this name. If it can, then the value of customerCheck is not -1, therefore some error should get printed out.
I don't know what methods can be used to do this, so any help would be great!
I found a solution, and it wasn't hard to implement. When a duplicate customer was found in the backend, I create a ModelState.AddModelError object and fill it with a key and a description of the error. Next, in the frontend, I put it within an H3 tag to print it out like so:
Backend OnPost() Code
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
int customerCheck = 0; //No Customer ID is 0
try
{
using (var context = new DataWarehouseContext())
{
customerCheck = context.Customer //Tries to grab a Customer with this name
.Where(a => a.Name == Customer.name)
.Select(b => b.CustomerId)
.FirstOrDefault();
}
}
catch (Exception)
{
}
if(customerCheck == 0)
{
_context.Customer.Add(Customer);
await _context.SaveChangesAsync();
return RedirectToPage("/Customer/List");
}
else
{
ModelState.AddModelError("DuplicateCustomer", "This Customer Already Exists");
return Page();
}
}
So on the frontend, it gets implemented like this:
<h3 align="center" style="color:yellowgreen">#Html.ValidationMessage("DuplicateCustomer")</h3>
When return Page(); is hit, the page is reloaded and the DuplicateCustomer Error appears.
At first, glad to hear you have found a solution.
Besides, I think you could also use the Remote Validation to check whether the Customer is exist or not. Check the following sample code:
Remote validation in ASP.NET (Core) relies on Unobtrusive AJAX, so you will need to install that first. The easiest way to do this is via LibMan. Right click on the lib folder in wwwroot, choose Add ยป Client-side Library, and then choose jsdelivr as the source, and type in jquery-ajax-unobtrusive, click the "Install" button to install the package.
In the CreateCustomer.cshtml.cs page, add a Email property and use the PageRemote attribute, then, add a handler method to perform the validation.
public class CreateCustomerModel : PageModel
{
private readonly IRepository _repository;
public CreateCustomerModel(IRepository repository)
{
_repository = repository;
}
[PageRemote(ErrorMessage = "Email Address already exists", AdditionalFields = "__RequestVerificationToken", HttpMethod = "post",PageHandler = "CheckEmail")]
[BindProperty]
public string Email { get; set; }
public void OnGet()
{
}
public IActionResult OnPost()
{
if (ModelState.IsValid)
{
//insert data into database.
}
return Page();
}
#pragma warning disable MVC1001 // Filters cannot be applied to page handler methods.
[ValidateAntiForgeryToken]
#pragma warning restore MVC1001 // Filters cannot be applied to page handler methods.
public JsonResult OnPostCheckEmail()
{
//query database and check whether the email is exist or not.
var existingEmails = _repository.GetCustomers().Select(c => c.Email.ToLower()).ToList();
var valid = !existingEmails.Contains(Email.ToLower());
return new JsonResult(valid);
}
In the CreateCustomer.cshtml razor page, add JQuery reference and add a form to enter the values.
#page
#model RazorSample.Pages.CreateCustomerModel
#{
}
<div class="row">
<div class="col-md-4">
<form method="post" asp-antiforgery="true">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
#* add other fields *#
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
#section scripts{
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<partial name="_ValidationScriptsPartial" />
<script src="~/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js"></script>
}
After submit the button, the result as below: if the email is exist, it will show the prompt:
[Note] In the above sample, we are adding the properties in the PageModel (instead of nested model), and use it to validate the field. Because, if using nested object, we might meet the 400 Bad Request result. The 400 error is related to the AntiForgeryToken, if you meet this error, try to ignore validate the AntiForgeryToken or custom add the __RequestVerificationToken token at the body or header, check this link.
More detail information about Remote Validation in Razor Pages, check the following articles:
Remote Validation in Razor Pages
Improved Remote Validation in Razor Pages

ViewBags using #Html.Action to render form on partial view

View:
<p>Parent ViewData: #ViewData["Test"]</p>
#Html.Action("MemberSignup","MemberSignupSurface")
PartialView:
<p>PartialView ViewData: #ViewData["Test"]</p>
#using (Html.BeginUmbracoForm<MemberSignupSurfaceController>
("MemberSignupSubmit", "MemberSignupSurfaceController",FormMethod.Post))
{
<!-- Some form controls -->
<input type="submit" value="Signup" />
}
Controller:
public class MemberSignupSurfaceController : SurfaceController
{
public ActionResult MemberSignup()
{
ViewData["Test"] = "From MemberSignup";
// Do database stuff and create model from that
return PartialView("MemberSignupView", model);
}
[HttpPost]
public ActionResult MemberSignupSubmit(MemberViewModel model)
{
ViewData["Test"] = "From MemberSignupSubmit";
if (ModelState.IsValid)
{
// Redirect to success using TempData
}
else
{
return CurrentUmbracoPage();
}
}
}
When my page load MemberSignup is called and the page shows
Parent ViewData:
PartialView ViewData: From MemberSignup
Then when i submit the form on the partial view with invalid input so it won't validate and it calls CurrentUmbracoPage() in the action MemberSignupSubmit
I get the following:
Parent ViewData: From MemberSignupSubmit
PartialView ViewData: From MemberSignup
If i use #Html.Partial to render my partial view both viewbags shows the same value set from the submit action.
I've tried TempDatabut it is not working either. Is there really no way to pass anything back to the partial view after i return from the submit action when using #Html.Action to render a partial view form.
The overall problem I am trying to solve is to popluate a dropdown in my form with values from the database. Using #Html.Partial don't allow me to do this but have a working viewbag.
I did this to render a dynamic dropdown list with values from a database. Maybe it will help someone.
It is a music player which needs a dynamic db populated menu to list the playlists
I made a base controller which all other controllers inherit from. In that base class, I have a PlaylistPopupMenu action which gets the list of playlists from a db.
public PartialViewResult PlaylistPopupMenu()
{
try
{
return PartialView("_PlaylistPopupMenu", db.GetPlaylists(1).ToList());
}
catch (Exception)
{
throw;
}
}
Then I have a _PlaylistPopupMenu partial view as follows:
#model List<OneMusic.Models.GetPlaylists_Result>
#if (Model.Count > 0)
{
<li style="height:2px" class="divider"></li>
foreach (var item in Model)
{
<li style="height:30px">#Html.DisplayFor(p => item.Name)
#Html.ActionLink(item.Name, "AddSong", "Playlist", new { playlistId = #item.PlaylistId, songId = 1 }, "")
</li>
}
}
this renders the dynamic parts of the menu (ie the playlists)
Finally the main page has this to build the dynamic part of the menu:
<ul class="dropdown-menu" style="margin-top:10px"><p class="text-primary" style="margin-left:18px; margin-top:6px">Add To</p>
<!-- other static menu items here-->
<li style="margin-top:-60px; height:0px">#Html.Action("PlaylistPopupMenu")</li>
</ul>

Using Ajax.BeginForm with MVC 4 - adding to my model collection asynchronously isn't working

I am trying to make a small football site where the user can create a new team and then asynchronously in another div it shows all the teams the user has created. So basically a team is created then added to the list of teams. All of this is in the model.
Now, I would like to do this asynchronously because its a nice to have but it's not working in my code. I am either missing something or it's not possible with what I am doing.
Controller
public ActionResult TeamManagement()
{
modelTeamSelect modelTeamSelect = new modelTeamSelect();
return View(modelTeamSelect);
}
[HttpPost]
public ActionResult TeamManagement(string btnSubmit, modelTeamSelect modelTeamSelect)
{
switch (btnSubmit)
{
case "Add Team":
// For now - add to collection but not use DAL
modelTeamSelect.teams.Add(modelTeamSelect.team);
//modelTeamSelect.team.TeamName = string.Empty;
break;
}
return View(modelTeamSelect);
}
View
#model Website.Models.modelTeamSelect
#{
ViewBag.Title = "Football App";
}
#section featured {
}
#using (Ajax.BeginForm(new AjaxOptions
{
HttpMethod = "POST",
Url = "Home/TeamManagement",
OnComplete = "teamAdded()"
}))
{
<div id="divTeams" style="float:left">
<h3>Create a new team:</h3>
#Html.LabelFor(m => m.team.TeamName)
#Html.TextBoxFor(m => m.team.TeamName)
<input type="submit" value="Add Team" name="btnSubmit" />
</div>
<div id="divCreatedTeams" style="float:left">
<h3>Your created teams:</h3>
#if (Model.teams.Count > 0)
{
for (int i = 0; i < Model.teams.Count; i++)
{
#Html.TextBoxFor(m => m.teams[i].TeamName)
}
}
</div>
<div id="divLeagues">
</div>
}
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Website.Models
{
public class modelTeamSelect
{
public modelTeamSelect()
{
teams = new List<modelTeam>();
team = new modelTeam();
}
public List<modelTeam> teams { get; set; }
public modelTeam team { get; set; }
}
}
I have the right javascript references being used in the project as I recently fixed this.
Why isn't my UI changing to reflect new contents of list?
I dont get the idea of passing the submit button string to the Action. But in order to pass a ViewModel to the Action I think you have to write your own model binder. If you want you can try getting the models seperately in the action and combining them in the Action
public ActionResult TeamManagement(List<modelTeam> teams, modelTeam team)
and combine them in the action in the viewModel.
Just a sugestion If you want to retrieve them async with ajax what I do is return partial view (i think better in your case) or json

MVC form Post deserialization is incomplete for complex model object

Using MVC 4 Forms, I have a model that always contains four children in a List<T> property. The view displays the model correctly with each of the four child models rendered with a Razor partial view. The problem is that when I submit/post, the model deserializes with a null value for the child list.
Model:
public class MyModel
{
public int SomeValue { get; set; }
public List<ChildModel> Children { get; set; }
...
}
View:
#model MyProject.Models.MyModel
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.SomeValue)
#Html.Partial("ChildPartial", Model.Children[0])
#Html.Partial("ChildPartial", Model.Children[1])
#Html.Partial("ChildPartial", Model.Children[2])
#Html.Partial("ChildPartial", Model.Children[3])
<input type="submit" value="Save" />
}
Controller:
public class MyController : Controller
{
public ActionResult Index()
{
MyModel model = new MyModel();
model.Children = new List<ChildModel>();
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
//model.Children is null here
//do stuff
...
return RedirectToAction("Index", "SomeOtherController");
}
}
The ChildPartial views are each rendering correctly, and I am entering values into the controls, but they are not deserialized into the List<ChildModel>. I can only get the root level properties of MyModel to deserialize in the Post method.
I have tried adding UpdateModel(model); to the beginning of the controller Post method but no luck there. Any ideas?
Edit
ChildModel.cs:
public class ChildModel
{
public String Name { get; set; }
public double Ratio { get; set; }
...
}
ChildPartial.cshtml:
#model MyProject.Models.ChildModel
<div>
<div>
<div>
<span>#Model.Name</span>
</div>
<div>
#Html.LabelFor(m => m.Ratio)
#Html.TextBoxFor(m => m.Ratio, new { autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Ratio)
</div>
</div>
...
</div>
I would first recommend you reading about the specific syntax that the default model binder expects and the naming convention when binding to collections: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Once you compare the names of your input fields with the ones explained in this blog post you will pretty quickly understand why your code doesn't work. You are simply not following the standard naming convention.
In order to fix this I would recommend you using editor templates. So in your main view put the following:
#model MyProject.Models.MyModel
#using (Html.BeginForm())
{
#Html.LabelFor(model => model.SomeValue)
#Html.EditorFor(model => model.Children)
<input type="submit" value="Save" />
}
Then move your ChildPartial.cshtml to ~/Views/Shared/EditorTemplates/ChildModel.cshtml. Notice that the name of the template and the location is extremely important. Make sure you have followed it. And put this inside:
#model MyProject.Models.ChildModel
<div>
<div>
<div>
<span>#Model.Name</span>
</div>
<div>
#Html.LabelFor(m => m.Ratio)
#Html.TextBoxFor(m => m.Ratio, new { autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Ratio)
</div>
</div>
...
</div>
Alright, now run your project, inspect the generated HTML and more specifically the names of the input fields compare them with your initial version and compare them to the blog post I have initially linked to in my answer and you will understand everything about how model binding to collections works in ASP.NET MVC.
Remark: in your child template you don't have a corresponding input field for the Name property of your ChildModel. So don't be surprised if it is null in your controller. You simply never send a value to it when the form is submitted. If you want this to happen you could include it as a hidden field in your editor template:
#Html.HiddenFor(m => m.Name)

Submit multiple buttons on same view

I need to have multiple buttons on same view, but for some reason it doesn't work. I did already looked at the same questions but I haven't got a solution.
How do I know which button is performed in the view?
View:
<%# Page Title="" Language="C#" MasterPageFile="~/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<LIASWeb.Models.Publication>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>NewPublication</h2>
<% using (Html.BeginForm())
{ %>
<div>
<fieldset>
<p>
<label for="Name">Naam:</label>
<%: Html.TextBoxFor(m => Model.nmPublication) %>
</p>
<p>
<%: Html.TextBox("Search", "type naam")%>
<input type="submit" name="btnSearch" value="Zoeken" />
</p>
<p>
<input type="submit" name="btnSave" value="Opslaan" />
</p>
</fieldset>
</div>
<% } %>
</asp:Content>
Controller:
public class PublicationController : Controller
{
private Repository repository = null;
public PublicationController()
{
repository = new Repository();
}
public ActionResult NewPublication(String button)
{
if (button == "btnSave")
// perform save action
if (button == "btnSearch")
// perform save action
return View();
}
public ActionResult Index()
{
IEnumerable<Publication> model = repository.Publications;
return View(model);
}
Routings:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Publication", action = "Index", id = UrlParameter.Optional }
);
}
You should use one submit button in one form.
Different form for different controller method it's best way IMHO
There are really many solutions for this problem:
Option 1-
Have something similar to this (I am not checking if the code is correct, so bear with me):
#using (Html.BeginForm("Controller", "Action1", FormMethod.Post, new { id="MyForm1"}))
{
<button type="submit" id="btn1">Bt1</button>
}
#using (Html.BeginForm("Controller", "Action2", FormMethod.Post, new { id="MyForm2"}))
{
<button type="submit" id="btn2">Bt2</button>
}
and now you are pointing to 2 different actions where u can clearly program them, and you can still return them with a View("SameViewForBothForms") or with a redirect to "MainView"
Option 2- Only one form with 2 buttons > NOT submit type, but simple button, where you will have a Javascript function to CHANGE the value of an hidden field "buttonName" (in the JS function you change this value).
Option 3-
Any kind of mixes of multiple or single forms are possible....
Try this solution:
public ActionResult NewPublication(string btnSearch)
{
if (!string.IsNullOrEmpty(btnSearch))
{
//btnSearch was clicked
}
else
{
//btnSave was clicked
}
}
Check this thread as well
Hope it helps.
This is something I have done before for wizard-like views and it is possible but I don't think that your approach will work.
Instead, I suggest trying the solution that I used which was based on this post
You could also just not use form elements for the submission and submit the form using jQuery / Javascript too.
you can identify your button from there name tag like below, You need to check like this in your controller
if (Request.Form["btnSearch"] != null)
{
//Write your code here
}
else if (Request.Form["btnSave"] != null)
{
//Write your code here
}