ASP.NET MVC partial view and form action name - asp.net-mvc

How do I create a partial view that has a form with assigned id?
I got as far as:
using (Html.BeginForm(?action?,"Candidate",FormMethod.Post,new {id="blah"}))
Partial view is used for both Create and Edit so first parameter ?action? will be different. I can't figure out what value of ?action? supposed to be.
UPDATE:
I guess I was not clear enough with the question. What I ended up doing is splitting Request.RawUrl to get controller name and action name:
string[] actionUrlParts = ViewContext.HttpContext.Request.RawUrl.Split('/');
using (Html.BeginForm(actionUrlParts.Length >= 2? actionUrlParts[2] : "",
actionUrlParts.Length >= 1 ? actionUrlParts[1] : "", FormMethod.Post, new { id = "blah" }))
Kind of ugly but it works. Is there a better way to get an action name inside the partial view?

Pass in the action to be performed via ViewData.
In your action that renders the view, create a ViewData item for the postback action. In your form reference this ViewData item to fill in the action parameter. Alternatively, you can create a view-only model that includes the action and the actual model as properties and reference it from there.
Example using ViewData:
using (Html.BeginForm( (string)ViewData["PostBackAction"], "Candidate", ...
Rendering actions:
public ActionResult Create()
{
ViewData["PostBackAction"] = "New";
...
}
public ActionResult Edit( int id )
{
ViewData["PostBackAction'] = "Update";
...
}
Example using Model
public class UpdateModel
{
public string Action {get; set;}
public Candidate CandidateModel { get; set; }
}
using (Html.BeginForm( Model.Action, "Candidate", ...
Rendering actions:
public ActionResult Create()
{
var model = new UpdateModel { Action = "New" };
...
return View(model);
}
public ActionResult Edit( int id )
{
var model = new UpdateModel { Action = "Update" };
model.CandidateModel = ...find corresponding model from id...
return View(model);
}
EDIT: Based on your comment, if you feel that this should be done in the view (though I disagree), you could try some logic based off the ViewContext.RouteData
<%
var action = "Create";
if (this.ViewContext.RouteData.Values["action"] == "Edit")
{
action = "Update";
}
using (Html.BeginForm( action, "Candidate", ...
{
%>

Pass nulls as action and controller. Extension will use just current action and current controller
using (Html.BeginForm(null, null, FormMethod.Post, new { id="Model" }))
Action generated for form will be the same as parent view of this partial view.
It generates
<form action="/Orders/Edit/1" id="Model" method="post">
for url http://localhost:1214/Orders/Edit/1
... and this
<form action="/Orders/Create" id="Model" method="post">
for url http://localhost:1214/Orders/Create

<% html.RenderPartial("MyUserControl", Model.ID) %>

Related

How to get main controller and action names from partial view action

I have a controller:
public class LanguageController : Controller
{
[HttpGet]
public ActionResult Index()
{
// populate viewModel from database
return PartialView(viewModel)
}
[HttpPost]
public ActionResult Index(string language)
{
LanguageCookie.Write(Response, language);
return RedirectToAction(ACTION, CONTROLLER, new {culture = language});
}
}
and its partial view:
#model MyModel
#using (Html.BeginForm("Index", "Language"))
{
#Html.DropDownList(
Model.SelectedLanguageShortName,
Model.AllLanguages
)
<input type="submit" value="Select" />
}
which I render in _Layout.cshtml:
<div>
#Html.Action("Index", "Language")
</div>
Please let me know how can I get ACTION/CONTROLLER names of main (not partial) controller, from my LanguageController was called. I need this information on the postback where I set cookie and want to redirect user on the same page but with prefered language.
I have found this example:
var rd = ControllerContext.ParentActionViewContext.RouteData;
var currentAction = rd.GetRequiredString("action");
var currentController = rd.GetRequiredString("controller");
But ControllerContext.ParentActionViewContext is null in the postback. I am able to get what I need in the view but it is ugly:
#Html.Hidden("Controller", HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString());
#Html.Hidden("Action", HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString());
How to get the same information in the controller scope?
When Index(string language) is processed ParentActionViewContext is null because this is another request to server and it doesn't know anything about previous request that invoked child action.
Instead of storing control and action in hidden field you can store the whole address and invoke Redirect:
#model MyModel
#using (Html.BeginForm("Index", "Language", new { redirectUrl = Request.Url }))
{
#Html.DropDownList(
Model.SelectedLanguageShortName,
Model.AllLanguages
)
<input type="submit" value="Select" />
}
and then
[HttpPost]
public ActionResult Index(string language, string redirectTo)
{
LanguageCookie.Write(Response, language);
return Redirect(redirectTo);
}
Another way is to save CONTROLER and ACTION in TempData, but in this way you can have problem if somebody open multiple pages of your site.
Third solution is to invoke that method with Ajax and when response arrive reload the page with javascript.

Passing only one object of a ViewModel to controller

I have a Home ViewModel class which contains others class:
public class HomeVM
{
public ProductSearchRequest ProductSearchRequest { get; set; }
//Other class
//Other class
//Other class
}
My home/Index view has #model HomeVM, and there I have a search text input:
#using (Html.BeginForm("Search", "Product"))
{
#Html.TextBoxFor(m => m.ProductSearchRequest.SearchText)
<input type="submit" value="Search" />
}
But in my Product/Search I need to receive only ProductSearchRequest because there is other pages that uses other ViewModel but contains ProductSearchRequest.
I'm trying this:
public ActionResult Search(ProductSearchRequest request)
{
var response = new ProductSearchResponse
{
SearchText = request.SearchText,
Products = GetProductsByName(request.SearchText)
};
return View(response);
}
but it doesn't work.. request.SearchText is always null..
How can I do this?
This is just a stab in the dark, but your action, try calling the parameter 'ProductSearchRequest'
Or how about putting your whole form in a view for that action I.e. 'Search' that takes the 'ProductSearchRequest' as model?
The problem you are having is because TextBoxFor() will create an html input using a naming convention that is expected to be bound to the same type of model HomeVM as the original action.
You can try using the simple TextBox() helper method like this:
#Html.TextBox("SearchText", Model.ProductSearchRequest.SearchText)
To avoid using a string value, you can make a separate form partial that takes the ProductSearchRequest object as the model, and call:
#Html.RenderPartial("SearchForm",Model.ProductSearchRequest)
Now you can use the TextBoxFor() method like this in the partial:
#Html.TextBoxFor(m => m.SearchRequest)

Partial view displaying error

I created a drop down list in a partial view and I am trying to render that on my aspx page. I am getting an error:
{"Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."}
This is my aspx page where I am using the ascx control:
<td>
<% Html.RenderAction("getFilterdData");%>
</td>
My ascx control looks like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewPage<IEnumerable<assist>>" %>
<%=Html.DropDownList("Assists", (SelectList)ViewData["Assists"], "--Select One--")%>
and my controller code is like this:
public ActionResult getFilterdData()
{
scorerep sc = new scorerep();
ViewData["Assists"] = new SelectList(sc.FilterData(), "assist_a","");
return View();
}
Why am I getting this error and how can I fix it?
It is difficult to help without seeing the entire exception stacktrace. Here are a few tips:
Make sure that your partial Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<assist>>" and not Inherits="System.Web.Mvc.ViewPage<IEnumerable<assist>>". You are using an ASCX partial and inheriting from System.Web.Mvc.ViewPage which is wrong.
Make sure that your partial view is called exactly the same as the controller action: getFilterdData.ascx (I see a typo here)
Make sure that the Assist class contains a property called assist_a as that's what you are using when rendering the dropdown
Make sure there is no exception being thrown inside the getFilterdData controller action while you are fetching the data.
Here's a working example:
Model:
public class Assist
{
public string Id { get; set; }
public string Value { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult GetFilteredData()
{
// TODO: replace with your repository logic
ViewData["Assists"] = new SelectList(new[] {
new Assist { Id = "1", Value = "Assist 1" },
new Assist { Id = "2", Value = "Assist 2" },
new Assist { Id = "3", Value = "Assist 3" },
}, "Id", "Value");
return View();
}
}
View (~/Views/Home/Index.aspx):
<% Html.RenderAction("GetFilteredData"); %>
Partial: (~/Views/Home/GetFilteredData.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Assist>>" %>
<%= Html.DropDownList("Assists", (SelectList)ViewData["Assists"], "--Select One--") %>

ASP.Net MVC2 Custom Templates Loading via Ajax and Model Updating

I have a view model with a collection of other objects in it.
public ParentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Child { get; set; }
}
public ChildViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
}
In one of my views I pass in a ParentViewModel as the model, and then use
<%: Html.EditorFor(x => x) %>
Which display a form for the Id and Name properties.
When the user clicks a button I call an action via Ajax to load in a partial view which takes a collection of Child:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Child>>" %>
<%: Html.EditorFor(x => x) %>
which then uses the custom template Child to display a form for each Child passed in.
The problem I'm having is that the form created by the Child custom template does not use the naming conventions used by the DefaultModelBinder.
ie the field name is (when loaded by Ajax):
[0].FirstName
instead of:
Child[0].FirstName
So the Edit action in my controller:
[HttpPost]
public virtual ActionResult Edit(int id, FormCollection formValues)
{
ParentViewModel parent = new ParentViewModel();
UpdateModel(parent);
return View(parent);
}
to recreate a ParentViewModel from the submitted form does not work.
I'm wondering what the best way to accomplish loading in Custom Templates via Ajax and then being able to use UpdateModel is.
Couple of things to start with is that you need to remember the default ModelBinder is recursive and it will try and work out what it needs to do ... so quite clever. The other thing to remember is you don't need to use the html helpers, actual html works fine as well :-)
So, first with the Model, nothing different here ..
public class ParentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Child { get; set; }
}
public class ChildViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
}
Parent partial view - this takes an instance of the ParentViewModel
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ParentViewModel>" %>
<h2>Parent</h2>
<%: Html.TextBox("parent.Name", Model.Name) %>
<%: Html.Hidden("parent.Id", Model.Id) %>
<% foreach (ChildViewModel childViewModel in Model.Child)
{
Html.RenderPartial("Child", childViewModel);
}
%>
Child partial view - this takes a single instance of the ChildViewModel
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ChildViewModel>" %>
<h3>Child</h3>
<%: Html.Hidden("parent.Child.index", Model.Id) %>
<%: Html.Hidden(string.Format("parent.Child[{0}].Id", Model.Id), Model.Id)%>
<%: Html.TextBox(string.Format("parent.Child[{0}].FirstName", Model.Id), Model.FirstName) %>
Something to note at this point is that the index value is what is used for working out the unique record in the list. This does not need to be incremental value.
So, how do you call this? Well in the Index action which is going to display the data it needs to be passed in. I have setup some demo data and returned it in the ViewData dictionary to the index view.
So controller action ...
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewData["Parent"] = GetData();
return View();
}
private ParentViewModel GetData()
{
var result = new ParentViewModel
{
Id = 1,
Name = "Parent name",
Child = new List<ChildViewModel>
{
new ChildViewModel {Id = 2, FirstName = "first child"},
new ChildViewModel {Id = 3, FirstName = "second child"}
}
};
return result;
}
In the real world you would call a data service etc.
And finally the contents of the Index view:
<form action="<%: Url.Action("Edit") %>" method="post">
<% if (ViewData["Parent"] != null) { %>
<%
Html.RenderPartial("Parent", ViewData["Parent"]); %>
<% } %>
<input type="submit" />
</form>
Saving
So now we have the data displayed how do we get it back into an action? Well this is something which the default model binder will do for you on simple data types in relatively complex formations. So you can setup the basic format of the action which you want to post to as:
[HttpPost]
public ActionResult Edit(ParentViewModel parent)
{
}
This will give you the updated details with the original ids (from the hidden fields) so you can update/edit as required.
New children through Ajax
You mentioned in your question loading in custom templates via ajax, do you mean how to give the user an option of adding in another child without postback?
If so, you do something like this ...
Add action - Need an action which will return a new ChildViewModel
[HttpPost]
public ActionResult Add()
{
var result = new ChildViewModel();
result.Id = 4;
result.FirstName = "** to update **";
return View("Child", result);
}
I've given it an id for easy of demo purposes.
You then need a way of calling the code, so the only view you need to update is the main Index view. This will include the javascript to get the action result, the link to call the code and a target HTML tag for the html to be appended to. Also don't forget to add your reference to jQuery in the master page or at the top of the view.
Index view - updated!
<script type="text/javascript">
function add() {
$.ajax(
{
type: "POST",
url: "<%: Url.Action("Add", "Home") %>",
success: function(result) {
$('#newchild').after(result);
},
error: function(req, status, error) {
}
});
}
</script>
<form action="<%: Url.Action("Edit") %>" method="post">
<% if (ViewData["Parent"] != null) { %>
<%
Html.RenderPartial("Parent", ViewData["Parent"]); %>
<% } %>
<div id="newchild"></div>
<br /><br />
<input type="submit" /> add child
</form>
This will call the add action, and append the response when it returns to the newChild div above the submit button.
I hope the long post is useful.
Enjoy :-)
Hmm... i personally would recommend to use a JSON result, instead of a HTML result, that you fiddle in the page...
makes the system cleaner. and your postback working ;-)
I found another way to accomplish this which works in my particular situation.
Instead of loading in a partial via via Ajax that is strongly typed to a child collection like:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Child>>" %>
I created a strongly typed view to the parent type and then called EditorFor on the list like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Parent>" %>
<%: Html.EditorFor(x => x.ChildList) %>
This then calls a Custom Display Template and the result is that all the HTML elements get named correctly and the Default Model binder can put everything back together.

How to get the ID from URL?

I am using asp.net MVC.
I have edit page url like {controller}/Edit/2
so on the view page how can I get the ID from this URL?
I'm gonna put a link to redirect to some page with sending above ID.
EDIT
Like
<%=Html.ActionLink("name", "Action", "Controller", new{ ID = ? } ) %>
you can get it from the RouteData object
<%=Html.ViewContext.RouteData.Values["id"].ToString() %>
Put the ID in the ViewData in your action method, then your view can access the value from the ViewData.
Controller: ViewData["ID"] = id;
View: <%=Html.ActionLink("name", "Action", "Controller", new{ ID = (int)ViewData["ID"]} ) %>
...in view you get info in the model to show data so you have id in model then you can get id and pass id easy
call Model in View :
#model IEnumerable<NewsWebsite.Models.Blog>
foreach (var item in Model)
{
//your code
#Html.ActionLink("Edit", "Edit", new { id = item.BlogID })
}
Or
you can get the id from URL Like This:
Cotroller:
public ActionResult Index(int id)
{
ViewBag.ID = id;
//Your Code......
return View(...);
}
View:
#{
ViewBag.Title = "Index";
var ID = ViewBag.ID;
}
Now you have an ID in the variable
I know of two ways: You can create your link and use the "ViewData" property to pass the link to your view or you can stronglyType the ViewPage.
Here is a link on strongly typing the view.

Resources