MVC5 How to do a post from partialview? - asp.net-mvc

My partialview:
#model Alina_2017.Models.DropDownModel
<h2>Groepen</h2>
<div>
<div>
#using (Html.BeginForm("SelectGroup", "~/Controllers/WerkvormController"))
{
#Html.DropDownListFor(x => x.selectedItem, new SelectList(ViewBag.groepen, "id", "Naam"), "Select", new { #class = "form-control" })
<input type="submit" id="zoekgroep" value="Zoeken" />
}
</div>
</div>
My main view:
#model Alina_2017.Models.WerkvormModel
#{
ViewBag.Title = "Index";
}
#Html.Partial("~/Views/DropDown/Groepen.cshtml")
//More irrelevant html
My controller:
public ActionResult Index()
{
ViewBag.groep1 = convertWerkvorm(db.Werkvormens.Where(f => f.GroepenWerkvormID == 1).ToList());
ViewBag.groep2 = convertWerkvorm(db.Werkvormens.Where(f => f.GroepenWerkvormID == 2).ToList());
ViewBag.groep3 = convertWerkvorm(db.Werkvormens.Where(f => f.GroepenWerkvormID == 3).ToList());
setViewBags();
return View();
}
[HttpPost]
public ActionResult SelectGroup(DropDownModel model)
{
// the value is received in the controller.
var selectedItem = model.selectedItem;
Debug.WriteLine(selectedItem);
return View("Index");
}
I'm getting a HTTP Error 404.0 - Not Found. Is it possible to call an action from a different controller? The reason it's in a partial view is because I'm using two different models + I'll be using the partialview in multiple other views (at least once I get it to work).

Your controller's name is wrong.
Replace
#using (Html.BeginForm("SelectGroup", "~/Controllers/WerkvormController"))
with
#using (Html.BeginForm("SelectGroup", "Werkvorm"))
You can verify the actual post URL if you view your source in browser, or check network tab in the browser's development tools.

The second argument to the BeginForm() method is simply the name of the controller, not its file:
#using (Html.BeginForm("SelectGroup", "Werkvorm"))
{
}
You can post to any server-side action from anywhere. There's no limit based on how the view is rendered because once everything is rendered it's all just client-side markup no matter where it came from.
As a learning exercise, examine the actual rendered markup in your browser's debugging tools and see the URLs created for the forms. Regardless of how the partial views are arranged, which controller returned the view, what the models are, etc... It's all just HTML in the end. You can even manually write a simple .html file with a form on it which successfully posts to a server-side ASP.NET MVC action.

Related

How to load partial razor page into razor view

I am trying to replace my view components with razor pages but it seems that it's not possible to load a partial razor page because a model is expected to be passed yet it is my understanding that the model for a razor page should be declared in the OnGetAsync method. Here is my code...
Razor Page
#page "{id:int}"
#model _BackgroundModel
<form method="POST">
<div>Name: <input asp-for="Description" /></div>
<input type="submit" />
</form>
Razor Page Code-Behind
public class _BackgroundModel : PageModel
{
private readonly IDataClient _dataClient;
public _BackgroundModel(IDataClient dataClient)
{
_dataClient = dataClient;
}
[BindProperty]
public BackgroundDataModel Background { get; set; }
public async Task OnGetAsync(int id)
{
Background = await _dataClient.GetBackground(id);
}
public async Task OnPostAsync()
{
if (ModelState.IsValid)
{
await _dataClient.PostBackground(Background);
}
}
}
Razor View
<div class="tab-pane fade" id="client-background-tab">
<div class="row">
<div class="col-sm-12">
#await Html.PartialAsync("/Pages/Client/_Background.cshtml", new { id = 1 })
</div>
</div>
</div>
Page Load Error
InvalidOperationException: The model item passed into the
ViewDataDictionary is of type '<>f__AnonymousType0`1[System.Int32]',
but this ViewDataDictionary instance requires a model item of type
'WebApp.Pages.Client._BackgroundModel'
In this example (as per MS recommended approach in their docs) the model is set inside the OnGetAsync method which should be run when the page is requested. I have also tried #await Html.RenderPartialAsync("/Pages/Client/_Background.cshtml", new { id = 1 }) but the same error result.
How can I load the razor page into my existing view?
Microsoft confirmed this cannot be achieved and therefore razor pages cannot be used as a replacement for view components.
See the comments of their docs...
MS docs
#RickAndMSFT moderator15 hours ago
#OjM You can redirect to the page, or you can make the core view >code into a partial and call it from both.
Pages are not a replacement for partials or View Components.

Using webforms in MVC

I am learning MVC, following THIS tutorial. (link will take you directly to where i'm stuck). so far I have learnt, there's a controller for every view. Now i have to take input from user through web entry form as mentioned in tutorial. In my project, i have a controller named Default1 and i can run it as localhost:xyz/Default1/Index. it runs perfect.
Then i created a new Controller, named Default2 and bound it to some view to display some data, and it worked perfect as localhost:xyz/Default2/Displaycustomer. the customer information was static (hard coded). and controller is as:
public ViewResult DisplayCustomers()
{
Customer cobj = new Customer();
cobj.Code = "12";
cobj.Name = "Zeeshan";
cobj.Amount = 7000;
return View("DisplayCustomers",cobj);
}
Now i have to take input from User, regarding cutomer iformation, using html page as mentioned in tutorial. so i tried adding a new webform under view folder, and and modified my controller as:
[HttpPost]
public ViewResult DisplayCustomers()
{
Customer cobj = new Customer();
cobj.Code = Request.Form["Id"].ToString();
cobj.Name = Request.Form["Name"].ToString();
cobj.Amount = Convert.ToDouble(Request.Form["Amount"].ToString());
return View("DisplayCustomers",cobj);
}
My Question is: How can i make my project stared, so that it takes input first, and then displays it, using above controller? Did i add the webform at right location? What would be the link to run it? i tried localhost:xyz/Default2/entryform etc. but failed.
(in my entryform.aspx, i have mentioned form action="DisplayCustomer" )
It sounds like what you're missing is an action to just display the form. In otherwords, you just need an action to display a form. That form's POST action should reference your controller's DisplayCustomers action.
So in your controller code:
public class CustomerController : Controller
{
[HttpGet]
public ViewResult New()
{
return View("NewCustomer"); //Our view that contains the new customer form.
}
// Add your code for displaying customers below
}
And in your view, you have code like this
#using(Html.BeginForm("DisplayCustomers", "Customer")) {
<!-- Add your form controls here -->
}
Notice that I'm using the version of the BeginForm helper that specifies the action method and controller to call. This will write the form tag to post back to your DisplayCustomers action. Here is the equivalent HTML:
<form method="POST" action="/Customer/DisplayCustomers">
You would then access your form using the URL http://test.server/Customer/New.
This may not be the best example in the world...but this will at least get you rolling..
url would be:localhost:1234/Home/Customer
the controller
public ActionResult Customer()
{
return View();
}
[HttpPost]
public ActionResult Customer(FormCollection frm)
{
var name = frm["name"].ToString();
var address = frm["address"].ToString();
ViewBag.Name = name;
ViewBag.Address = address;
return View();
}
The view
<div>
#using (Html.BeginForm())
{
<input type="text" name="name" id="name" />
<input type="text" name="address" id="address"/>
<input type="submit" name="submit" value="submit" />
<input type="text" name="namedisplay" value='#ViewBag.Name'/>
<input type="text" name="addressdisplay" value='#ViewBag.Address'/>
}
</div>

ViewBag.Title vs. Page.Title in ASP.NET MVC 3

Why sometimes #ViewBag.Title contains the right page title while #Page.Title is null? We are debugging our View/Layout code and we noticed this difference.
Thanks.
When your are using asp.Net MVC you have a few tools you can use to get data to your page.
The model you send to the view.
The ViewBag.
TempData.
Cookies.
Session.
Each one of theese has their own use cases
The example we will be using is a basic
List and update View
For a collection of companies
The Model
The model Should be used whenever you have a defined dataset being sent to a view and should contain the primary data for the page and usally a model is specific to a page or controller.
The ViewBag
The ViewBag should be used whenever you need to send general data to a page that is not defined on the model or data that is used across your view tree.
Temp Data
Temp Data is as the name states storage for temporary data this should be used in cases where you are unsure if the data will reach its destination or when you want to pass data between actions without adding parameters.
Cookies
Cookies are like Global variables the host data across your entire application.
Session
Sessions like cookies are global variables but differ in their lifetime sessions can be used for things like Storing form data between requests dont use session to do this rather use something like http://garlicjs.org/
Lets look at the example
We have the following ViewModel
public class Company
{
public int? id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
The layout page looks like this
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
//This looks for the Title property on the ViewBag and inserts it in the title tags
<title>#ViewBag.Title</title>
</head>
<body>
#Html.Action("Header", "PageElements")
#RenderBody()
</body>
</html>
The Header Action on the PageElements Contoller looks like this
public PartialViewResult Header()
{
ViewBag.CurrentLink = HttpContext.Current.Request.Cookies["CurrentLink"];
return PartialView();
}
The Header Partial View looks like this
<header>
<ul>
<li class="#(ViewBag.CurrentLink == "Home" ? "Current" : "")">
#Html.ActionLink("Home", "Index", "Home")
</li>
<li class="#(ViewBag.CurrentLink == "Companies" ? "Current" : "")">
#Html.ActionLink("Companies", "Index", "Companies")
</li>
</ul>
</header>
The controller action for update looks like this
public ViewResult Update(int id)
{
//Gets a company from the database
var model = db.GetCompany(id);
//Sets The Title property on the ViewBag to Update : CompanyName
ViewBag.Title = String.Format("Update : {0}", model.Name);
//Sends the company to the view
return View(model);
}
The update view looks like this
#model Application.Models.Company
#{
Layout = "_layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.HiddenFor(model => Model.id)
#Html.LabelFor(model => Model.Name)
#Html.TextBoxFor(model => Model.Name)
#Html.LabelFor(model => Model.Description)
#Html.TextAreaFor(model => Model.Description)
<button>Submit</button>
}
The Post Action for Update looks like this
[HttpPost]
public ActionResult Update(Company model)
{
//Attempts to update the model
if (model.Update())
{
//Set the message to update succeeded
TempData["Message"] = String.Format("{0} Successfully updated");
}
else
{
//Set the message to update failed
TempData["Message"] = String.Format("{0} filed to update");
}
return RedirectToAction("Index");
}
The Controller action for companies looks like this
public ViewResult Index()
{
//Index is the entrypoint for companies so we set the currentlink to companies while we are in companies
HttpContext.Current.Request.Cookies["CurrentLink"] = "Companies";
//Gets the success or failure message from the temp data
ViewBag.Message = TempData["Message"];
//Sets The Title property on the ViewBag to List of companies
ViewBag.Title = "List of companies";
var model = db.GetAllCompanies();
return View(model);
}
The view for the list looks like this
#model IEnumrable<Application.Models.Company>
#{
Layout = "_layout.cshtml";
}
<span>#ViewBag.Message</span>
<ul>
#foreach (var company in Model)
{
<li>#company.Name : #company.Description #Html.AnchorLink("Edit", "Update", new { company.id}) </li>
}
</ul>
Lets discuss how the flow of this application works
The Index Action gets triggered on companies.
We set the current link to companies in a Cookie
Then we set the Title in the ViewBag
We check the TempData for messages and put them into the ViewBag
Then we put the collection of companies into a Model and send it to the page.
Now razor kicks in and starts renders the page first
Then the layout renders and gets the Title value from the ViewBag
Then the header partial view renders and gets the currentlink value from a Cookie
We click the update button for a company
The update action runs
Gets the company from a database and sends it to the view in a Model
We change the title to update in the ViewBag
The page renders first and puts the company data on the form, from the Model
Then the layout renders and Gets the title value from the ViewBag
Then the header renders and gets the currentlink from a cookie (the value is still companies we did not have to change it)
Then we submit the form and add a message to the TempData
now we are back at the company list and we can get the success or failure message from the TempData
We used the Model to send Specific data to the view.
We used the ViewBag to send General data to the view and the layout.
We used TempData to pass data between actions without using parameters.
We used Cookies to Store data that wouldn't change for a while.
The correct is using ViewBag.Title, because it is on the context of your ViewBag property of your View while Page.Title is something that comes from asp.net webforms. That is the reason why Page.Title is null, there is no context for it.

Create reusable form contact component in asp.net mvc

I'm newbie at asp.net mvc, I'm trying to develop a reusable contact form component for asp.net mvc .
I have tryed to do it creating a PartialView with a form, I don't know if this is the better approach, for create a reusable component
My PartialView is
#model MyModel.ContactData
#using (Html.BeginForm("ContactForm", "ContactForm", FormMethod.Post)) {
<fieldset>
<p>
#Html.LabelFor(model => model.MailAddress)
#Html.EditorFor(model => model.MailAddress)
</p>
<p>
#Html.LabelFor(model => model.Message)
#Html.TextAreaFor(model => model.Message)
</p>
<input type="submit" value="Save" />
</fieldset>
}
The problems start with the controller, the controller is a specific controller for only this partialView.
public class ContactFormController : Controller
{
[HttpPost]
public ActionResult ContactForm(ContactData contactData)
{
if (ModelState.IsValid)
{
return PartialView("MessageSend");
}
return PartialView();
}
}
My problem is in case of some of the required fields are empty, in that case the controller returns only the partial view, not returning the partival view inside its parent context. I have tryed calling the PartialView from parent View as #Html.Partial, #Html.RenderAction, #Html.RenderPartial and the same occurs.
How can I return the partial view Inside it's parent Context? I have tried with
return View(ParentViewsName, contactData) but I dislike it because on form submitting it changes the url on address bar from /Contact to /ContactForm/ContactForm.
Perphaps I'm trying to create a reusable component with a wrong approach? It's better to update with ajax only the PartialView? Alternatives?
Thanks
From my understanding, you wish to display status message which is a partial view after user successfully submits the form. I think tempdata will be apt for this kind of situation.
public class ContactFormController : Controller
{
[HttpPost]
public ActionResult ContactForm(ContactData contactData)
{
if (ModelState.IsValid)
{
TempData["success"] = true;
return RedirectToAction("parentpage");
}
return View(contactData);
}
}
In parent page, check whether TempData["success"] is null and display the partial view "MessageSend".
Finally as Sundeep explains I have done this with ajax like this example Partial ASP.NET MVC View submit.
Thanks for your help.

MVC 3 StackOverflowException w/ #Html.Action()

I've looked over a bunch of other reports of this, but mine seems to be behaving a bit differently. I am returning PartialViewResults for my child actions, so that's not the source of the recursion. Here's a dumbed down version of what I have.
// The Controller
[ChildActionOnly]
public ActionResult _EditBillingInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditBillingInfo(EditBillingInfoViewModel model)
{
// Update billing informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditBillingInfo"
};
return View("EditProfile", profileModel);
}
[ChildActionOnly]
public ActionResult _EditUserInfo()
{
// Generate model
return PartialView(model);
}
[HttpPost]
public ActionResult _EditUserInfo(EditUserInfoViewModel model)
{
// Update user informatoin
var profileModel = new EditProfileViewModel()
{
PartialToLoad = "_EditUserInfo"
};
return View("EditProfile", profileModel);
}
public ActionResult EditProfile(EditProfileViewModel model)
{
if (String.IsNullOrEmpty(model.PartialToLoad))
{
model.PartialToLoad = "_EditUserInfo";
}
return View(model);
}
// EditProfile View
#model UPLEX.Web.ViewModels.EditProfileViewModel
#{
ViewBag.Title = "Edit Profile";
Layout = "~/Views/Shared/_LoggedInLayout.cshtml";
}
<div>
<h2>Edit Profile</h2>
<ul>
<li class="up one"><span>#Ajax.ActionLink("Account Information", "_EditUserInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
<li class="up two"><span>#Ajax.ActionLink("Billing Information", "_EditBillingInfo",
new AjaxOptions { UpdateTargetId = "EditProfileDiv", LoadingElementId = "LoadingImage" })</span></li>
</ul>
<img alt="Loading Image" id="LoadingImage" style="display: none;" src="../../Content/Images/Misc/ajax-loader.gif" />
<div id="EditProfileDiv">
#Html.Action(Model.PartialToLoad)
</div>
</div>
The partial views are both forms for updating either the user information or billing information.
I debugged through this and found what is happening, but cannot figure out why. When a user browses to EditProfile, it load up with the _EditUserInfo partial and the form is there for editing. When you change some info and submit the form it hangs and you get a StackOverflowException in the EditProfile view on the call to #Html.Action(). What happens is on the initial visit to EditProfile, the #Html.Action calls the HttpGet version of _EditUserInfo. You make some changes to the user info and click submit. Once the information is updated the EditProfile view is returned again, but this time #Html.Action calls the HttpPost version of _EditUserInfo which updates the user information again, returns the EditProfile view again and the #Html.Action calls the HttpPost version of _EditUserInfo... You get where this is going. Why after form submission does it call the post version and not the get version like it did for the initial visit to EditProfile?
Thanks for any help!
I might be getting this a bit wrong, it's been a long day so, but in EditProfile you set PartialToLoad (if it's empty) to "_EditUserInfo", then in _EditUserInfo you set it again to _EditUserInfo, won't this create a loop that behaves as what you are experiencing?

Resources