MVC & ajax: Should I create more views? - asp.net-mvc-3

I'm developing an MVC3 application with EF and I wanted to make the UI fluent using jQuery ajax, the user will be able to navigate through the url, if he knows it or maybe he might receive a link pointing to a particular route, but, once the page is fully loaded it needs to be fluent, so I came up with one idea and I would like to discuss it here before I make the changes to the solution.
Here is what I came up with:
TestController.cs (Methods code has been omitted for simplicity)
public ActionResult Index() { ... }
public ActionResult Create() { ... }
[HttpPost]
public ActionResult Create(Test test) { ... }
public ActionResult Update(int testID) { ... }
[HttpPost]
public ActionResult Update(Test test) { ... }
public ActionResult Delete(int testID) { ... }
[HttpPost]
public ActionResult Delete(Test test) { ... }
So far it looks like most controllers. My views are as follows:
Views\Test\List.cshtml
Views\Test\Create.cshtml
Views\Test\Details.cshtml
Views\Test\Delete.cshtml
Now since I wanted to do it async: I've changed my List view so I could add, modify and remove from the list, so far is working like a charm. Plus, the user could still be able to navigate through the application using the url's, note that every link inside the application will perform an ajax request to do the actual work, there are no Route/Action links.
By now the application is working as expected, but now I came across something: there are views that I need to be ActionResult and PartialViewResult, that is because the user could type in the url: "/Admin/Test", which should return the full page, or could click on an anchor which will load only the content of the "/Admin/Test" and display it. To avoid the famous page inside page errors I wrote a function to send the request, and when the request arrives it selects only what I need, avoiding then the page inside page, and to duplicate views, but, the response is the whole page which, I don't need to say, it's not the best option, but since the application will be used by lan I didn't care too much about the payload of the response, but then I needed to write javascript code inside the views, so my solution was like null because using the jQuery selector to get only what I need the javascript wasn't there.
As for my new solution to solve my last solution:
I thought I might leave the original view as is, and create another view appending the word "Partial" after the original name, creating another method in the controller with the same naming convention, plus adding the new Route to my Route Table.
To wrap things up, what I need is the following:
- If the user types in "/Test" the response should be the entire page, loaded like the old days, screens flashing white and such.
- But if the user clicks the Test link in the navigation bar, the response should be async and refreshing only the content of my layout.
Any ideas? thoughts? suggestions?

In your actionmethod you can have
if (Request.IsAjaxRequest())
return PartialView("_somePartialView");
else
return PartialView("_someOtherPartialView");

Related

How to apply single page application functionality on parts of asp.NET MVC3 projects?

So I'm creating a asp.NET MVC3 application and want to apply single page application functionality to parts of the application. I think the easiest way to explain is with an example:
The app consists of an admin area and a public area and is built using ordinary link-structure. I want to convert the admin area to an single page application reusing view and models from the existing application. Is it possible to do this and in that case how?
You have to face two main problems, which makes the difference between SPA and standard application:
Links: In standard application, each link redirects you to a different page.
Forms: When a form is been submitted, a request is been issued with the HTTP method you've specified in the post (usually POST) and it contains in the payload the data the user has entered.
In order to solve that problems, you have to take action both in client-side and server-side.
For explaining propose, lets take the following code:
HomeController.cs:
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
public ActionResult Contact() {
return View(new ContactUsViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactUsViewModel model) {
if (ModelState.IsValid) {
/* Send mail / Save in DB etc. */
return Redirect("Index");
}
return View(model);
}
}
Index.cshtml:
<p>This is a simple page.</p>
<p>#Html.ActionLink("Click here to contact us", "Contact")
Client-Side:
We should fix up linking between pages, as well as forms submittions.
Links: You can wire up an event in JS (jQuery if you'd like) that'll observe for each link click in the areas you'd like to apply on SPA - then, instead of redirecting the user - you'll load the content via AJAX.
For instance, take a look at this sample:
$("a").click(function(e) {
e.preventDefault(); // Disable standard redirecting
var href = $(e.currentTarget).attr("href");
$.get(href, function(responseText) {
$("#main-content-wrapper").html(responseText);
});
});
Forms: Just like in the approch we've used for links, we can wire up an observer to the form submit event in JS and then transfer the data using AJAX.
For instance:
$("form").submit(function(e) {
e.preventDefault(); // Disable standard submittion
var data = $(e.currentTarget).serialize(); // Serializing the form data
var method = $(e.currentTarget).attr("method");
if (typeof (method) == "undefined") { method = "POST"; }
$.ajax({
method: $(e.currentTarget).attr("method"),
parameters: data,
statusCodes: {
404: function() { /* Handle it somehow */ }
403: function() { /* Handle it... */ }
200: function(response) {
/* Since we've done a form submittion, usurally if we're getting standard OK (200) status code - we've transffered a data - such as JSON data - indicating if the request success or we got errors etc. The code you're writing here depends on how your current application works. */
},
});
});
Server-Side:
Since you don't wish to break your current application logic - you have to still be able to use standard ASP.NET MVC methods - such as View(), Redirect() and so on.
In this case, I recommend to create your own custom base Controller class - which will override ASP.NET basic implementation.
For instance, this is a starting point:
public class MyController : System.Web.Mvc.Controller {
public override View(string viewName) {
if (Request.IsAjaxRequest()) {
return PartialView(viewName); // If this is an AJAX request, we must return a PartialView.
}
return base.View(viewName);
}
}
Few things you have to keep in mind:
You have to somehow distinguish between standard requests and AJAX requests - the way I've used with Request.IsAjaxRequest() is a great way to do so.
Many times when you're handling a form, In the form submittion action, after you finish with the form logic, you're using Redirect() to redirect the user to another page. As you may have guessed, you can't take this approch when developing SPA. However, I can think of few solutions for this problem:
You can create a status handler in the JS code so when redirecting is been issued by the server - you can load the content via AJAX / display a message and so on.
You can override Redirect() and add a specific logic to perform in case of redirection when the request was done by AJAX - for instance, you can request from ASP.NET to perform the action that you're going to be transfered into and then return its content etc.
You can decide that although its an SPA app - when a redirect was issued - you allows the server to perform this redirection.
As you can see - there're many approches you can take, and they depends on the way you've developed your site, how you wish it to work and what is the basic rules you're defining (e.g. "No redirection is permitted never - even after submitting a form", "After form submittion - always in case that the operation success - I'm displaying a message or performing other JS action. Because of that, I can override Redirect() and if this is an AJAX request I can return a JSON object." etc.)

Why does RedirectToAction have no effect

When the method with the barcode parameter is called, RedirectToAction has absolutely no effect. I can see that it does indeed return to that route in the URL, but the model is not refreshed and it displays stale data.
In the SQL Server profiler I can see that the call isn't being made to pull back the new data.
How can I force this to happen?
Incidently, the call to: public ActionResult SRScanItem(string barcode) is itself the result of a RedirectToAction from another controller.
I have handful of calls to RedirectToAction("SRPickCollectionItems") elsewhere in the same controller and these all work fine.
Any idea what may be causing this?
public ActionResult SRPickCollectionItems()
{
IEnumerable<ISRPickingItemSummary> items =
SRPickingItemsViewModel.
GetDisplayableChunk(ApplicationState.CollectionId.ToString(),
ApplicationState.AssistantNumber);
return View(items);
}
public ActionResult SRScanItem(string barcode)
{
DataLayer.Instance.AddStockroomFoundItem(barcode, ApplicationState.CollectionId, ApplicationState.AssistantNumber);
return RedirectToAction("SRPickCollectionItems");
}
Maybe the redirect is going to a cached page.
Could you try and decorate SRPickCollectionItems with
[OutputCache(Duration = 0)]
You don't seem to returning to a post method, only a get so it is not obvious how the method with the Redirect is getting called unless you are send the barcode as a query string parameter in a get call through an actionlink or link tag.
Normally you would have two methods named SRPickCollectionItems. The Get method (which you already have) loads the view and the post method (that you are missing) processes the postback. The post method would be thus...
[HttpPost]
public ActionResult SRPickCollectionItemsstring barcode){
DataLayer.Instance.AddStockroomFoundItem(barcode, ApplicationState.CollectionId, ApplicationState.AssistantNumber);
return RedirectToAction("SRPickCollectionItems");
}

View and controller in asp.net mvc3 - controller should match a view one to one?

I have a very typical situation in any application, where i have the following functionality:
create new record
edit existing record
so other irrelevant actions
IMO, creating and editing should be served by the same view, but different actions. But it appears that I have to have the action name match the view name....would you use partial views for this? I would rather not complicate this scenario - which is very simple and appears in virtually every web app.
Action can return a view with a diferent name this way:
public ActionResult OneName()
{
return View("OtherName");
}
If you don't specify the view name (View("") then the view will be the view with the action name
Partial views are an excellent answer. I'd suggest you look at how the MvcScaffold NuGet package does it. See here or get the package in Visual Studio.
I'd simply use the same action altogether and use the ID to determine if this is a new record or updating an existing one:
/Forum/Post/Edit/0 create a new record
/Forum/Post/Edit/10457 update a record with ID 10457
However, since you insist on using different actions, why not simply create 2 actions, both returning the same view?
public class PostController : Controller
{
public ActionResult Create(Post post)
{
// work your magic...
return View("Edit", post);
}
public ActionResult Update(Post post)
{
// work your magic...
return View("Edit", post);
}
}
If this doesn't work in your scenario, you're pretty much left with partial views.

Is there a simple way to pass data into an MVC3 app?

[Edit] To try to clarify:
I have a view that needs to be launched from an external application. The application requires string data to be passed from an external application (the data is free text and too long to pass as a query parameter), So I would like to launch the MVC application with a POST request. The view that is launched also needs to post data back to itself in order to submit the data it collects for storage in a database. So I end up with a View with two HttpPost flagged methods in my controller (MVC throws an error that there are ambiguous Create methods).
So in the code below Create() would be posted to from the external application. Create(FormCollection collection) would be posted to when a displayed View is submitted.
//POST: /Application/Create
[HttpPost]
public ActionResult Create()
{
MyModel model = new MyModel();
//Parse External Data to model from Request.InputStream
return View(Model);
}
//POST: /Application/Create
[HttpPost]
public ActionResult Create(FormCollection collection)
{
//Save form collection data to database
return RedirectToAction("Index");
}
So long story short, how can I post data to an MVC application to launch a view, without getting an error for an ambiguous call.
Thanks.
in the first case when the post method comes in from the outside:
return View("ConfirmCreate", model)
Then create an action method named ConfirmCreate. After ConfirmCreate is called the second time you will redirect back to Index as you have.
So I was able to do this by changing the POST call to load the application to a PUT To avoid have duplicate post endpoints), then sending the PUT from an ajax call in another application and replacing the current document with the returned html from the successful ajax call. Thanks for the suggestions.

MVC3 Routing Issues - How to re-use a View for all Controller Methods?

I'm trying to implement a common controller in MVC3 to return various JSON feeds, example -
public class AjaxController : Controller
{
public ActionResult Feed1()
{
ViewBag.Json = LogicFacade.GetFeed1Json();
return View();
}
public ActionResult Feed2()
{
ViewBag.Json = LogicFacade.GetFeed2Json();
return View();
}
}
This class has 30+ methods in it, the problem is this requires implementing an IDENTICAL View for each of the Controller's methods (sigh) that writes out ViewBag.Json.
I'm assuming this is a routing issue but I'm struggling with that. The following didn't work -
Tried setting ViewBag.Json then using RedirectToAction() but that seems to reset ViewBag.Json.
Note JsonResult is not appropriate for my needs, I'm using a different JSON serialiser.
So the objective here is to maintain one View file but keep this class with seperate methods that are called by routing, and not a crappy switch statement implementation.
Any help appreciated.
Use the same view and just specify the name. You can store in the controller's view folder, if only used by one controller, or in the Shared view folder if used by more than one.
return View("SharedJsonView");
Another, perhaps better, solution would be to create your own result -- maybe deriving from JsonResult, maybe directly from ActionResult -- that creates the JSON response that you need. Look at the source code for JsonResult on http://www.codeplex.com/aspnet for ideas on how to do it.

Resources