simple MVC form post and url routing question....I hope - asp.net

I have a view called Associations that has a drop down list and a button. When the user selects an option and presses submit, I want them to go to Association/{associationKey}. Association needs to work on get and post.
Currently, with the code below, when the form is posted, it DOES go to Association and displays the correct record, BUT it does not append the associationKey to the url.
So I am getting:
http://localhost/Association
instead of:
http://localhost/Association/202
If I manually navigate to http://localhost/Association/202 everything works perfectly, so get and post are both working fine....I just want the key in the url after a post!
Surely there must be something super simple I am doing wrong. Relevant code below.
Thanks!
ASSOCIATIONS view:
<% Html.BeginForm("Association", "Staff", FormMethod.Post); %>
<%:Html.DropDownList("associationKey", new SelectList(Model.Associations.ToList(), "AssociationKey", "LegalName"))%>
<input type="submit" value="Edit The Selected Record" />
<% Html.EndForm(); %>
STAFF controller:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Association(int associationKey)
{
return View("Association", new AssociationViewModel(associationKey));
}
GLOBAL.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "{action}", new { controller = "Staff", action = "Default" });
routes.MapRoute("Associations", "Associations", new { controller = "Staff", action = "Associations" });
routes.MapRoute("Association", "Association/{associationKey}", new { controller = "Staff", action = "Association" });
}
ASSOCIATION view model:
public class AssociationViewModel
{
public Repository db = new Repository();
public Association Association {get; private set; }
public List TelephoneTypes { get; private set; }
public AssociationViewModel(int associationKey)
{
Association = db.AssociationById(associationKey);
TelephoneTypes = db.ListTelephoneTypes().ToList();
}
}

I think you should separate out your controller actions into a Get action and a POST action like so:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Association(int associationKey)
{
return View("Association", new AssociationViewModel(associationKey));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Association(AssociationViewModel model)
{
return RedirectToAction("Association", new {associationKey= model.associationKey});
}
The MVC framework will automatically bind the selected value from your SelectList to the model (assuming you have a property in the model to hold the selected value). From there you just need to redirect to your GET method passing in the key.

It's doing a post instead of a GET. This puts the value in the form parameters not in the url. You might want to intercept the form submit using javascript and turn it into a GET using location=..
<script type="text/javascript">
$(function() { // requires jQuery
$('form').submit( function() {
// maybe do some validation to ensure a legal value chosen first?
location.href = $(this).attr('action') + '/' + $(this).find('select').value();
return false; // cancel submit
});
});
</script>
<% Html.BeginForm("Association", "Staff", FormMethod.Post); %>
<%:Html.DropDownList("associationKey", new SelectList(Model.Associations.ToList(), "AssociationKey", "LegalName"))%>
<input type="submit" value="Edit The Selected Record" />
<% Html.EndForm(); %>

Related

Is there any way to set ActionName in QueryString in ASP.NET MVC 5?

We all know url in ASP.NET MVC always like example.com/controller/action?param1=value1&param2=value2
I'd like to know is there a way to put action name in query string just like example.com/controller?aciton=index&param1=value1&param2=value2(notice action=index in query string), and make ASP.NET MVC route the url to the corresponding controller and action. The name of the query name can be action or something else.
--- update 28th Sep ---
The actual situation is I have a form, inside the form is a table with radio button each column and some button (create, edit, delete etc.) above the table. These buttons go to different action in same controller.
As a result of search, I've got some solutions:
handle onsubmit via JavaScript and change the action property of form. Answer link
write a "route" method in controller to re-route the request. Answer link ( I think this is not a graceful solution )
write a custom attribute let ASP.NET MVC route to corresponding action base on query. This anwser and this answer ( Very close to my original idea, I am currently using this solution )
I'm the questioner and just several hours after I update the question, I finally figure out the solution. That is write a custom Route.
public class QueryActionRoute : Route
{
public QueryActionRoute(string url, object defaults) : base(url, new RouteValueDictionary(defaults), new MvcRouteHandler()) { }
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
var action = httpContext.Request["action"];
if (routeData != null)
{
if (!string.IsNullOrEmpty(action))
{
routeData.Values["action"] = action;
}
}
else
{
routeData = new RouteData()
{
Values = { {"action", "Index"} }
};
}
return routeData;
}
}
and replace the default route
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(
"Default",
new QueryActionRoute(
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }));
}
}
after that you can access an action via
example.com/home/about?param=value(you can emit action name if the action is "index") like before
or
example.com/home?action=about&param=value.
I's really helpful when you need to specify different buttons to go different actions in form. You can make it in this way:
<form action="#Url.Action("Index")">
<button name="action" value="Create">Create</button>
<button name="action" value="Details">Detail</button>
<button name="action" value="Edit">Edit</button>
<button name="action" value="Delete">Delete</button>
</form>

Partial refresh in ASP using ajax

How do you do a partial refresh of a page using Ajax in cshtml?
As I understand it, Ajax is required. In my scenario, in my index page I have a table where each row has a program (a batch file) and a Run button. Beneath the table I have the a space for the program output. I would like this to populate (I'm happy to wait for the program to finish just now) without having to refresh the rest of the page.
Code is below, but in summary I have one model for the table data, one model for the selected program log/output. The controller for the index page creates both and passes them in to a view model, which is passed to the view. When the Run button is hit an Index overload method in the controller handles the running of the program and 'getting' the output. It also populates the appropriate model in the VM (possibly not ideal and I'm open to suggestions to improve it).
The overloaded method currently returns a PartialViewResult and the output/logging has it's own PartialView (as I'll want to reuse it elsewhere). This is also why it has a separate model. In the PartialView break points are hit, but it doesn't appear on the page in the browser.
I'm using ASP.NET-MVC-4 with Razor.
View (Index.cshtml)
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
#model ViewModels.UpdateTestViewModel
#{ ViewBag.Title = "Update Test"; }
#{
<table>
#* Headers *#
<tr>
<td>Programs</td>
</tr>
#* Data *#
<tr>
<td>#Model.ProgramName</td>
<td style="min-width:75px"><input id="btnRun" type="button" value="Run" /></td>
</tr>
</table>
<div id="log">
#Html.Partial("ScriptLog", Model.Log)
</div>
<script>
$("input[type=button]").on("click", function () {
var NAME = ($(this).parent().siblings(":first")).text();
$.post("/UpdateTest/Run", { input: NAME });
});
</script>
}
Partial View
#model Models.ScriptLog
#if (Model != null && Model.Log.Any(x => !string.IsNullOrWhiteSpace(x)))
{
<fieldset>
<legend>Log</legend>
#foreach (string entry in Model.Log)
{
<p>#entry</p>
}
</fieldset>
}
Script Log
public IEnumerable<string> Log { get { // returns log } }
ViewModel
public class UpdateTestViewModel
{
public string ProgramName { get { return "My Program"; } }
public ScriptLog Log { get { return _log; } }
private readonly ScriptLog _log;
public UpdateTestViewModel(ScriptLog log)
{
_log = log;
}
}
Controller
public ActionResult Index()
{
if (SessionFacade.CurrentUpdateTestLog == null)
{
ScriptLog log = new ScriptLog();
SessionFacade.CurrentUpdateTestLog = log; // Store in Session
}
UpdateTestViewModel vm = new UpdateTestViewModel(SessionFacade.CurrentUpdateTestLog);
return View(vm);
}
[ActionName("Run")]
public PartialViewResult Index(string input)
{
ExecuteScript.ExecuteUpdateTestScript(input); // Run batch file
UpdateTestLog(input); // Get log and update in Session
return PartialView("ScriptLog", SessionFacade.CurrentUpdateTestLog);
}
Since you are making an $.post() you need to decorate your /UpdateTest/Run action with [HttpPost].
You also don't define a success handler so, while you are making the request, you never do anything with it.
$.post("/UpdateTest/Run", { input: NAME })
.done(function(partialResult) {
$("#log").html(partialResult);
})
.fail(function(jqxhr, status, error) {
console.log(jqXhr, status, error);
});
With much help and patience from #Jasen I got a working solution which was to extend the existing Ajax, so that it looks like:
$("input[type=button]").on("click", function () {
var NAME = ($(this).parent().siblings(":first")).text();
$.post("/UpdateTest/Run", { input: NAME })
.done(function (partialResult) {
$("#log").html(partialResult);
})
});
Note I also have added the [HttpPost] attribute in the controller

Dropdownlist returning null viewdata Id in partialview

I am working on a multi-step form which must commit the first step to database before the other steps add up and commit finally after last step. Looking at the options I decided to use sessions(however I will be happy to use any better alternative). I managed to get this to work and later decided to use ajax for the form submission hence the requirement of partialviews. The problem is the dropdowns are returning null viewdata values - ie they are not binding. Can anyone suggest the best way to compose in the controller to make this work. It works fine if I revert to return views instead of return partialviews.
Controller
[AllowAnonymous]
public ActionResult ContactViewDetails()
{
ViewBag.CountryList = new SelectList(db.Countries, "CountryId", "CountryName");
return PartialView("ContactViewDetails");
}
model
public int CountryId{get;set;}
public virtual Country Country { get; set; }
.......and others
Default Page View
<script src="~/Scripts/jquery-2.1.3.min.js"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<div id="divContainer">
#Html.Partial("RegViewDetails")
</div>
PartialView: ContactViewDetails.cs
#{
AjaxOptions options = new AjaxOptions();
options.HttpMethod = "POST";
options.InsertionMode = InsertionMode.Replace;
options.UpdateTargetId = "divContainer";
}
#using (Ajax.BeginForm("ContactViewDetails", "OnlineApplication", options, new { #class = "form-horizontal" }))
{
#Html.DropDownListFor(x => x.CountryId, (IEnumerable<SelectListItem>)ViewBag.CountryList, new { #class = "chooseOption" })
#Html.......others
}
The ContactViewDetails page is the second step in the form succession The first step is RegViewDetails page as you can see in the Default page view. After validation RegViewDetails returns ContactViewDetails Partial
......
return PartialView("ContactViewDetails");

Accessing Route Value of Id inside a Partial View

I am trying to access the Route value of id from inside the PartialView. I have a Post, Comments scenario. And I want my Comments partialview to have access to the PostId.
The Url looks like the following:
Posts/Detail/2
Now, how can I access the value 2 inside the Partial View?
UPDATE:
I changed my CommentsController to the following:
public ActionResult Add(string id,string subject,string name,string body)
{
}
But now when I debug it does not even step into the Add method.
UPDATE 2:
My partial view looks like the following:
<div class="title">Add Comment</div>
#using (Html.BeginForm("Add","Comments",FormMethod.Post))
{
#Html.Label("Subject")
#Html.TextBox("subject")
<br />
#Html.Label("Name")
#Html.TextBox("name")
<br />
#Html.Label("Body")
#Html.TextBox("body")
<input type="submit" value="submit" />
}
And here is the Controller:
public ActionResult Add(string id, string subject, string name, string body)
If I remove id from the above controller action then it works.
You could fetch it from RouteData:
In WebForms:
<%
var id = ViewContext.RouteData.Values["id"];
%>
In Razor:
#{
var id = ViewContext.RouteData.Values["id"];
}
After showing your code it seems that in your form you are not passing the id. So modify it like this:
#using (Html.BeginForm("Add", "Comments", new { id = ViewContext.RouteData.Values["id"] }, FormMethod.Post))
{
...
}
or use a hidden field if you prefer:
#using (Html.BeginForm("Add", "Comments", FormMethod.Post))
{
#Html.Hidden("id", ViewContext.RouteData.Values["id"])
...
}
Now you should get the id in your Add controller action.
Inside of your controller, you could put the route data in the view model that gets sent to the view; the view can then pass along its view model (or portions of it) to the partial view. Your views, including partial views, should ideally only need to rely on data provided by their view models to render.
public ActionResult Add(string id, string subject, string name, string body)
{
return View(
new AddedViewModel {
Id = id,
// etc...
}
);
}

Trouble passing complex data between view and controller in ASP.NET MVC

Here's a simplification of my real models in ASP.NET MVC, that I think will help focus in on the problem:
Let's say I have these two domain objects:
public class ObjectA
{
public ObjectB ObjectB;
}
public class ObjectB
{
}
I also have a view that will allow me to create a new ObjectA and that includes selecting one ObjectB from a list of possible ObjectBs.
I have created a new class to decorate ObjectA with this list of possibilities, this is really my view model I guess.
public class ObjectAViewModel
{
public ObjectA ObjectA { get; private set; }
public SelectList PossibleSelectionsForObjectB { get; private set; }
public ObjectAViewModel(ObjectA objectA, IEnumerable<Location> possibleObjectBs)
{
ObjectA = objectA;
PossibleSelectionsForObjectB = new SelectList(possibleObjectBs, ObjectA.ObjectB);
}
}
Now, what is the best way to construct my view and controller to allow a user to select an ObjectB in the view, and then have the controller save ObjectA with that ObjectB selection (ObjectB already exists and is saved)?
I tried creating a strongly-typed view of type, ObjectAViewModel, and binding a Html.DropDownList to the Model.PossibleSelectionsForObjectB. This is fine, and the I can select the object just fine. But getting it back to the controller is where I am struggling.
Attempted solution 1:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ObjectAViewModel objectAViewModel)
This problem here is that the objectAViewModel.ObjectA.ObjectB property is null. I was thinking the DropDownList which is bound to this property, would update the model when the user selected this in the view, but it's not for some reason.
Attempted solution 2:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ObjectA objectA)
This problem here is that the ObjectA.ObjectB property is null. Again, I thought maybe the DropDownList selection would update this.
I have also tried using the UpdateModel method in each of the above solutions, with no luck. Does anyone have any ideas? I'm guessing I'm missing a binding or something somewhere...
Thanks!
I use code as follows:
[HttpPost]
public ActionResult Create([Bind(Exclude = "Id")]ObjectA objectAToCreate)
{
try
{
Repository.AddObjectA(objectAToCreate);
return RedirectToAction("Details", new { id = objectAToCreate.Id });
}
catch
{
return View();
}
}
With the following code in a Repository (Entity Framework specific):
public void AddObjectA(ObjectA objectAToAdd)
{
objectAToAdd.ObjectB = GetObjectB(objectAToAdd.ObjectB.Id);
_entities.AddToObjectAs(objectAToAdd);
_entities.SaveChanges();
}
public void GetObjectB(int id)
{
return _entities.ObjectBs.FirstOrDefault(m => m.id == id);
}
As per your commments, it is essentially reloading the object from the underlying data service, however I didn't find the need to use the ModelState to access the attempted value.
This is based on a view coded along these lines:
<p>
<%= Html.LabelFor( f => f.ObjectB.Id) %>
<%= Html.DropDownList("ObjectB.Id", new SelectList((IEnumerable)ViewData["ObjectBList"], "Id", "Descriptor"),"") %>
<%= Html.ValidationFor( f => f.ObjectB, "*") %>
</p>
Note that this could be improved to use a strongly typed ViewModel (which I believe you already do) and also to create a custom Editor Template for ObjectB such that the call could be made using:
<%= Html.EditorFor( f => f.ObjectB ) %>
After some more research it doesn't look like this is a case ASP.NET MVC will take care of for me. Perhaps there is a data service binding model I can use (so MVC would automatically grab the appropriate object out of memory, based on what was selected in the dropdown), but for now, I can fix this by handling it in the controller:
Get the selected item from the dropdown using Controller.ModelState
Reload that ObjectB from the underlying data service
Assign that ObjectB to ObjectA.ObjectB
Save ObjectA
So my controller method looks like this now:
Edited based on the comment from LukLed
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ObjectA objectA, string objectBStr)
{
ObjectB objectB = _objBService.Get(objectBStr);
objectA.ObjectB = objectB;
_objAService.Save(objectA);
return RedirectToAction("Details", new { id = objectA.Id });
}

Resources