Redirect within partial view in MVC - c#

I have kind of a weird scenario. I'm creating a site using the ASP.NET MVC Framework that contains a profile page which has a user's picture, info, etc. I have a view called Profile which loads partial views using the Ajax action link into a div. Here is an example:
#Ajax.ActionLink("Basic Info", "Index", "BasicInfo",
new {id=Model.UserName},new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "content",
HttpMethod = "GET"
})
The Index action of BasicInfo just shows the user's basic information. I want to have an edit link in that partial view that when pressed loads another action method, Edit where I can edit the values (another partial view). I have a couple of questions:
1) I didn't make Profile a layout even though it would be similar to a master page in ASP.NET because it would need a controller. Is there a way to create a layout that has its own controller?
2) How do I do a redirect within a partial view without doing a postback i.e. updating the div holding the partial view that was previously called by Ajax to an edit view?
3) I hope this all makes sense. I would see a profile with the person's basic information and I can press edit within that view and the edit view of that basic information controller is loaded into the div without doing a postback. What would be the best way to accomplish this?

You will find this a it easier using the jQuery ajax methods rather than the Ajax.ActionLink() and Ajax.BeginForm() methods. In the main view
<button type="button" class="details" data-id="Model.UserName">View details</button>
<div id="content"></div>
var detailsUrl = '#Url.Action("Details", "User")';
var editUrl = '#Url.Action("Edit", "User")';
// Display the details view
$('.details').click(function() {
$.get(detailsUrl, { id: $(this.data('id') }, function(response) {
$('#content').html(response);
});
});
// Display the edit view
$('#content').on('click', '#edit', function() {
$.get(editUrl, { id: $(this.data('id') }, function(response) {
$('#content').html(response);
});
});
// Post the edit form and replace with the updated details view
$('#content').on('click', '#save', function() {
var id = $(this).data('id');
var data = $(this).closest('form').serialize();
$.post(editUrl, data, function(response) {
if (response) {
$.get(detailsUrl, { id: id }, function() {
$('#content').html(response);
});
} else {
// Oops
}
}).fail(function (result) {
// Oops
});
});
The above assumes a UserController with the following methods
public PartialViewResult Details(int ID) // or string?
{
// Get the user model based on the ID
return PartialView("_Details", model);
}
public PartialViewResult Edit(int ID) // or string?
{
// Get the user model based on the ID
return PartialView("_Edit", model);
}
public JsonResult Edit(UserModel model) // replace with the name of your model
{
// Save the model
return Json(true); // indicate success
}
where the partial views are
_Details.cshtml
#model UserModel
.... // display properties of the model
<button type="button" id="edit" data-id="Model.UserName">Edit</button>
_Edit.cshtml
#model UserModel
<form>
.... // controls for properties of the model
<button type="button" id="save" data-id="Model.UserName">Save</button>
</form>

I might be misunderstanding things.
I think you're trying to flip a display view of a part of a page for an edit view of that part of the page.
I'll keep things general, because there's not enough code to refer to directly.
You should register javascript event handlers against the various clicks that can take place (in a jquery closure in a separate file is my personal preference). Those handlers should request whichever actions (returning partial views) are required.
e.g. when someone clicks the edit link, the handler calls the /GetEditStuff action, gets the partial view, and on success, clears out the previous content of the parent div and replaces it with the partial view.

Related

jQuery post to controller and redirect to ASP.NET MVC view

I have some checkboxes and a button (not in a form).
When the button is clicked, I have some jQuery and I am creating a post model which contains the values of the checked boxes and posting to a controller.
The controller then creates view models and I want to redirect the user to the correct view, passing the view model in to the view.
jQuery:
$.ajax({
url: AppSettings.baseUrl + "BOM/getMultiBOM",
type: 'POST',
data: JSON.stringify(data)
});
Controller:
[HttpPost]
public ActionResult getMultiBOM(multiBOMPostModel multiBomsPostModel)
{
BOM bom = null;
foreach (int value in multiBomsPostModel.bomsArray)
{
bom = db.BOMs.Find(value);
}
BOMViewModel viewModel = getViewModel(bom, null);
return RedirectToAction("OpenMultiBOM", new { viewModel = viewModel, bom = bom });
}
public ActionResult OpenMultiBOM(BOMViewModel viewModel, BOM bom)
{
viewModel.projectModel = new ProjectViewModel
{
project = bom.Project
};
return View(viewModel);
}
It is probably a bit of a mess.
I think the jQuery is necessary to pass the checkbox values to the controller.
When I use RedirectToAction to the method which then returns the view, the model is not being passed through, presumably as it is sending the model as a query string.
The view model is not simple and contains lists, IEnumerables, and nested models.
Can anyone help with the most efficient way to redirect/return the view while passing the view model?
Answer: I kept the ajax to post my checkbox values to the controller
$.ajax({
url: AppSettings.baseUrl + "BOM/getMultiBOM",
type: 'POST',
data: JSON.stringify(dataArr),
}).done(function (result) {
location.href = "/BOM/OpenMultiBOM";
});
In my controller, I assigned the posted values to a postModel and then stored them in TempData. The key here was to return a Json value which would then allow the redirect on the client side to take place.
public ActionResult getMultiBOM(multiBOMPostModel multiBOMPostModel)
{
TempData["BOMs"] = multiBOMPostModel;
return Json("success");
}
I then had another HttpGet method which would load after the page is redirected by the Ajax result and cast the TempData to an object.
[HttpGet]
public ActionResult OpenMultiBOM(int? BomMarkupMessage = null)
{
var bomIds = TempData["BOMs"] as multiBOMPostModel;
}
I would persist the viewmodel server side, perhaps in a session variable, or perhaps as a TempData (TempData typically only lives until the next request), and pass a key for the session variable to the second controller in the case of session variable, or use the TempData directly in your view in the case of TempData. This would avoid passing the whole object back and forth multiple times.
So the way that i have done this before is to have an empty div in DOM.
<div id="partialViewContent">
<!-- Content will be loaded later. -->
</div>
If you have a default view, you'll need to set it to load from URI using the below snippet.
$("#partialViewContent").load("Controller/Action",
function (response, status) {
if (status !== "success") {
console.log("An error has occured when attempting to load partial view.");
}
});
When you post to your controller action via JQUERY, have the action return a partial view with the model. (Assume model is relevant to each partial view).
Then when your ajax is complete, replace the content in partialViewContent with the POST response.

Not sure how to properly use a partial view

I read a lot of questions here on stacoverflow, but it is still not clear to me how should I use a partialview within a main view with an action method. What's probably wrong is my aproach in general. With what I have so far I am not sure how to continue with my code.
I will start with the main view :
#{
ViewBag.Title = "getRolesByYear";
}
</script>
<script type="text/javascript">
getRolesForYear(parseInt(#DateTime.Now.Year));
$(function () {
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
//console.log(year);
function getRolesForYear(year) {
console.log(year);
$.ajax({
type: "POST",
url: '#Url.Action("getRolesByYear", "WorkRoles")',
dataType: "json",
data: {
year: year
},
success: successFunc,
error: errorFunc
});
function successFunc(data, status) {
console.log('x');
}
}
function errorFunc() {
alert('error');
}
}
</script>
<div id = "years" class="btn-group btn-group-justified timeline">
#DateTime.Now.Year
#DateTime.Now.AddYears(-1).Year
#DateTime.Now.AddYears(-2).Year
</div>
<div id"partial"></div>
In this view I have three buttons with different year for each button. On page load or on button click I make an ajax call to an action method with the an int year as a parameter.
This is a simplified version of my action method :
public ActionResult getRolesByYear(int year)
{
// a couple of queries here
var list = list of RoleViewModel objects;
return PartialView(list);
And here is the partialView :
#model IEnumerable<eksp.Models.RoleViewModel>
#foreach (var item in Model)
{
<div class="jumbotron">
<h2>item.Role.RoleName</h2>
<h1> item.Role.RoleDescription</h1>
<p class="lead">Focus start : item.Role.FocusStart</p>
<p>Focus end : item.Role.FocusStart </p>
</div>
}
Obviously, a lot of thins aren't clear to me. How can I use this partial view with the action method i have and the main view? Do I need a separate method for the partial view? Any tips?
Your ajax call will invoke the action method which returns the partial view result (markup generated by executing the partial view). I guess you simply need to use the response of the ajax call to update your DOM.
If you want to update the content of the div with id partial with the response, you can do that in the success event handler of your ajax call.
success : function(response)
{
$("#partial").html(response);
}
I would also recommend to call the getRolesForYear method on document ready event.
$(function () {
getRolesForYear(parseInt(#DateTime.Now.Year));
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
Also, If your main view is also the result of action method getRolesByYear, you probably want to return the partial view result only on the ajax call, the other calls,you can return the partial view
public ActionResult getRolesByYear(int year)
{
var list = list of RoleViewModel objects;
if(Request.IsAjaxRequest())
{
return PartialView(list);
}
else
{
return View(list);
}
}
Here the same action method handles the request for main view and ajax call. It returns the same view for ajax call, but using PartialView call, so layout will be ignored. But If you have a specific view you want to return, you can do that as well.
if(Request.IsAjaxRequest())
{
return PartialView("_yearlyRoles",list);
}
One of the reasons I like using partial views for rendering data via Ajax calls. For example if I want to start searching in order to avoid the server call i Just use an ajax call to the controller which returns the search results through a partial view. In your example yoy need to load the results in partial div.
function successFunc(data, status) {
$("#partial").html(data);
}

ASP.NET MVC - How to call void controller method without leaving the view?

Question background:
I am implementing some basic 'shopping cart' logic to an MVC app. Currently when I click a link - denoted as 'Add To Cart' on the screen shot below this calls to an 'AddToCart' method in the 'ProductController' as shown:
Product.cshtml code:
#Html.ActionLink("Add To Cart", "AddToCart")
'AddToCart' method in the ProductController:
public void AddToCart()
{
//Logic to add item to the cart.
}
The issue:
Not an issue as such but currently when I click the 'Add To Cart' button on the ActionLink on the ProductDetail.cshtml view the page calls the 'AddToCart' method on the ProductController and gives a blank view on the page - as shown below. I want the view to stay on 'ProductDetail.cshtml' and just call the 'AddToCart' method, how do I do this?
Basically #Html.ActionLink() or <a></a> tag uses get request to locate the page. Hence whenever you clicked it, you request to your AddToCart action method in ProductController and if that action method returns null or void so a blank or empty page is shown as you experienced (because or #Html.ActionLink() get request by Default).
So if you want to add your value to cart then call AddToCart method using ajax i.e:
HTML:
#Html.ActionLink("Add To Cart", "AddToCart", null, new { id="myLink"})
Jquery or Javascript:
$("#myLink").click(function(e){
e.preventDefault();
$.ajax({
url:$(this).attr("href"), // comma here instead of semicolon
success: function(){
alert("Value Added"); // or any other indication if you want to show
}
});
});
'AddToCart' method in the ProductController:
public void AddToCart()
{
//Logic to add item to the cart.
}
Now this time when the call goes to AddToCart method it goes by using ajax hence the whole page will not redirect or change, but its an asynchronous call which execute the AddToCart action method in your ProductController and the current page will remains same. Hence the product will also added to cart and page will not change to blank.
Hope this helps.
The answer of Syed Muhammad Zeeshan is what you are looking for, however you may return an EmptyResult.
public ActionResult AddToCart()
{
//Logic to add item to the cart.
return new EmptyResult();
}
According to this it has no impact on your code ASP.Net MVC Controller Actions that return void
But maybe sometime you want to return data and then you could do something like this:
if (a)
{
return JSon(data);
}
else
{
return new EmptyResult();
}
As many people mentioned here you will need to use AJAX if your using asp.net MVC to hit a controller POST function without having to leave your view.
A good use case for this is if you want to upload a file without refreshing the page and save that on the server.
All of the
return new EmptyResult();
Wont work, they will still redirect you.
Here is how you do it, in your view have the follow form as an example:
<form enctype="multipart/form-data" id="my-form">
<p>
The CSV you want to upload:
</p>
<input type="file" class="file-upload" name="FileUpload" />
</div>
<div>
<button type="submit" class="btn btn-default" name="Submit" value="Upload">Upload</button>
</div>
</form>
Then in the JavaScript side you need to add this to your view with within Script tags.
$("#my-form").on('submit', function (event) {
event.preventDefault();
// create form data
var formData = new FormData();
//grab the file that was provided by the user
var file = $('.file-upload')[0].files[0];
// Loop through each of the selected files.
formData.append('file', file);
if (file) {
// Perform the ajax post
$.ajax({
url: '/YourController/UploadCsv',
data: formData,
processData: false,
contentType: false,
type: 'POST',
success: function (data) {
alert(data);
}
});
}
});
Your controller might look something like this to process this type of file:
[HttpPost]
public void UploadCsv()
{
var listOfObjects = new List<ObjectModel>();
var FileUpload = Request.Files[0]; //Uploaded file
//check we have a file
if (FileUpload.ContentLength > 0)
{
//Workout our file path
string fileName = Path.GetFileName(FileUpload.FileName);
string path = Path.Combine(Server.MapPath("~/App_Data/"), fileName);
//Try and upload
try
{
//save the file
FileUpload.SaveAs(path);
var sr = new StreamReader(FileUpload.InputStream);
string csvData = sr.ReadToEnd();
foreach (string r in csvData.Split('\n').Skip(1))
{
var row = r;
if (!string.IsNullOrEmpty(row))
{
//do something with your data
var dataArray = row.Split(',');
}
}
}
catch (Exception ex)
{
//Catch errors
//log an error
}
}
else
{
//log an error
}
}
There are many ways to accomplish what you want, but some of them require a lot more advanced knowledge of things like JavaScript than you seem aware of.
When you write ASP.NET MVC applications, you are required to have more intimate knowledge of how browsers interact with the web server. This happens over a protocol called HTTP. It's a simple protocol on the surface, but it has many subtle details that you need to understand to successfully write ASP.NET MVC apps. You also need to know more about Html, CSS, and JavaScript.
In your case, you are creating an anchor tag (<a href="..."/>), which when click upon, instructs the browser to navigate to the url in the href. That is why you get a different page.
If you don't want that, there are a number of ways change your application. The first would be, instead of using an ActionLink, you instead simply have a form and post values back to your current controller. And call your "add to cart" code from your post action method.
Another way would be have your AddToCart method look at the referrer header (again, part of that more subtle knowledge of http) and redirect back to that page after it has processed its work.
Yet another way would be to use Ajax, as suggested by Syed, in which data is sent to your controller asynchronously by the browser. This requires that you learn more about JavaScript.
Another option is to use an embedded iframe and have your "add to cart" be it's own page within that iframe. I wouldn't necessarily suggest that approach, but it's a possibility.
Controller should return ActionResult. In this case a redirect to the caller page.
using System.Web.Mvc;
using System.Web.Mvc.Html;
public ActionResult Index()
{
HtmlHelper helper = new HtmlHelper(new ViewContext(ControllerContext, new WebFormView(ControllerContext, "Index"), new ViewDataDictionary(), new TempDataDictionary(), new System.IO.StringWriter()), new ViewPage());
helper.RenderAction("Index2");
return View();
}
public void Index2(/*your arg*/)
{
//your code
}
I was struggling with this and couldn't get it working with ajax.
Eventually got a working solution by making my controller method return type ActionResult rather than void and returning a RedirectToAction() and inputting the action relating to the view I wanted to remain on when calling the controller method.
public ActionResult Method()
{
// logic
return RedirectToAction("ActionName");
}

Display Success message on the same page when submit

I'm using Html.Beginform in view page and get the parameters using FormCollection to the controller i want to return the Success message on the same ViewPage as a result.i'm using following code,
public string InsertDetails(FormCollection collection)
{
string result = "Record Inserted Successfully!";
return result;
}
It shows the success message on the new page.How can i resolve this? what i have to return to get the Success message on the same page?
Personally, I'd pop the result string into the ViewBag.
public ActionResult InsertDetails(FormCollection collection)
{
//DO LOGIC TO INSERT DETAILS
ViewBag.result = "Record Inserted Successfully!";
return View();
}
Then on the web page:
<p>#ViewBag.result</p>
I have following Options.
1. Use Ajax Begin Form with AjaxOptions like below
#using (Ajax.BeginForm("ActionName", "ControllerName", new { area = "AreaName" }, new
AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "alert('Success');" //This will execute once the Ajax call is finished.
}, null))
{
<input type="submit" name="nameSubmit" value="Submit" />
}
2. Use JQuery to Manually Setup the XHR Request
$.ajax({
url: "#Url.Action("ActionName", "ControllerName", new { area = "AreaName" });",
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify({param : Value})
})
.done(function () { alert('Success');}) //This will execute when you request is completed.
.fail(function () { })
My Suggestions
There are following disadvantages while using the FormCollection
Point - 1
In case FormCollection is being used...It will be mandatory to Type Cast the Primitive Type Values un-necessarily because while getting the entry of specific Index of the System.Collections.Specialized.NameValueCollection, value being returned is of type String. This situation will not come in case of Strongly Typed View-Models.
Issue - 2
When you submit the form and goes to Post Action Method, and View-Model as Parameter exists in the Action method, you have the provision to send back the Posted Values to you View. Otherwise, write the code again to send back via TempData/ViewData/ViewBag
Point - 3
We have Data Annotations that can be implemented in View Model or Custom Validations.
ASP.Net MVC simplifies model validatons using Data Annotation. Data Annotations are attributes thyat are applied over properties. We can create custom validation Attribute by inheriting the built-in Validation Attribute class.
Point - 4
Example you have the following HTML
<input type="text" name="textBox1" value="harsha" customAttr1 = "MyValue" />
Question : How can we access the value of customAttr1 from the above eg from inside the controller
Answer : When a form get posted only the name and value of elements are posted back to the server. You can also use Hidden Fields to post the Attributes to Post Action method.
Alternatives : Use a bit of jQuery to get the custom attribute values, and post that along with the form values to action method
Another option is to rather put what you got in your custom attributes in hidden controls
That's the reason, I would always prefer to use View-Models
we can do it on Form inside view
#using (Ajax.BeginForm("Action", "Controller", new AjaxOptions { HttpMethod = "POST", OnSuccess = "Showmessage" }))
[HttpPost]
public ActionResult Test(TestViewModel model)
{
return Json(new {isok=true, message="Your Message" });
}
function Showmessage(data)
{
$('#Element').html('Successfully Submitted');
}

ASP.NET MVC design issue

I have a main view with 2 partial views (a gridview and a callback panel)
This main view receives a IEnumerable<Model> and the panel just receives a Model.
When the view is initially called, the partial view of the panel is filled with an empty model so it is empty.
But I want to re render the panel once I click Edit in my GridView.
How can I achieve this?
I currently have an #Html.ActionLink in the Edit button but its not working since it will create a new view instead of re render the partial view of the panel.
Any clues?
EDIT:
this is my Edit:
Html.ActionLink("Edit", "EditConfig", new { id = DataBinder.Eval(c.DataItem, "QueueMonitorConfigurationsID") })
Function that the edit link calls:
[HttpGet]
public ActionResult EditConfig(int id)
{
StorageConfigurationModel resultForPanel = new StorageConfigurationModel { };
IEnumerable<StorageConfigurationModel> configList = (IEnumerable<StorageConfigurationModel>)Session["ConfigurationList"];
foreach (StorageConfigurationModel configModel in configList)
{
if (configModel.QueueMonitorConfigurationsID == id)
{
resultForPanel = configModel;
break;
}
}
return PartialView("cbpnlNewUpdateConfigs", resultForPanel);
}
Main view containing the partial views:
#model IEnumerable<BPM.Website.Models.StorageConfigurationModel>
#Html.Partial("gvConfigurations", Model)
#Html.Partial("cbpnlNewUpdateConfigs", new BPM.Website.Models.StorageConfigurationModel { QueueMonitorConfigurationsID = -1 })
I currently have an #Html.ActionLink in the Edit button
You could use AJAX. For example you could replace this Html.ActionLink with an Ajax.ActionLink and include the jquery.js and jquery.unobtrusive-ajax.js scripts (in that order) to make it act as an AJAX call. For example:
#Ajax.ActionLink(
"click to edit record",
"Edit",
new { id = item.Id },
new AjaxOptions { UpdateTargetId = "editContainer" }
)
When the link is clicked the Edit controller action will be invoked using an AJAX request and the id of the current item will be passed as parameter. When this AJAX request completes a DOM element with id="editContainer" will be updated with the results of this AJAX call. So your Edit controller action should return a partial view containing the record to be edited.
Solved with an Ajax POST
$.ajax({
type: "POST",
url: url,
data: data,
success: success,
dataType: dataType
});

Categories

Resources