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

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.)

Related

Passing HTML string to my controller action method

I am using a rich text editor to type formatted text, as shown below:
I can get the HTML formatted text, which would look like this:
<p>This is my rich HTML Text</p>
Now I want to pass this HTML formatted text to my controller and my controller would put the text in an email and send it to the receiver.
The problem is HTML string is considered unsafe, so in order to pass it to my controller, I need to add [ValidateInput(false)] attribute to my Action method, like below:
[ValidateInput(false)] // <-- not able to hit the action method without this
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<JsonResult> Contact(string message)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
return Json(new { Authorize = "false" });
}
// email message to receiver
}
And this is the Ajax method which contacts the controller:
$('#contactBtn').click(function () {
var form = $('#__AjaxAntiForgeryForm');
var token = $('input[name="__RequestVerificationToken"]', form).val();
var message = quill.root.innerHTML; // <-- HTML formatted message
$.ajax({
url: "/Communication/Contact",
data: { __RequestVerificationToken: token, message: message },
dataType: 'json',
type: "POST"
});
});
So the above code works, but I am not sure if this is the right thing to do? Is there any security issue with the above code? Is there any encoding that I need to do on the HTML?
Actually ValidateInput attribute is related to XSS (Cross Site Security) issue.
XSS(Cross Site Security) is a security attack where the attacker injects malicious code while doing data entry. This code can be a javascript, vbscript or any other scripting code. Once the code is injected in end user’s browser. This code can run and gain access to cookies, sessions, local files and so on.
Now the good news is that XSS is by default prevented in ASP.NET MVC. So if any one tries to post JavaScript or HTML code with input he lands with the following error.
A potentially dangerous Request.Form value was detected from the client.....
But in real life there are scenarios where HTML has to be allowed, like HTML editors. So for those kind of scenarios we decorate our action method with ValidateInput attribute as follows:
[ValidateInput(false)]
public async Task<JsonResult> Contact(string message)
{
}
But there is problem of doing this. We are allowing HTML and script on the complete action which can be dangerous. Suppose the form we are posting have five input text fields, now the all five text fields can contain HTML and scripts.
Instead this Microsoft article suggest:
For ASP.NET MVC 3 or greater applications, when you need to post HTML back to your model, don’t use ValidateInput(false) to turn off Request Validation. Simply add [AllowHtml] to your model property, like so:
public class BlogEntry
{
public int UserId {get;set;}
[AllowHtml]
public string BlogText {get;set;}
}
So bottom line is that ValidateInput allows scripts and HTML to be posted on the whole action level while AllowHTML is on a more granular level.
For more details you can read ASP.NET Security - Securing Your ASP.NET Applications
Using [ValidateInput(false)] on the action method is not a good approach, as there could be other input parameters that don't get validated... using [AllowHtml] works if we are passing in a Model...
For this scenario, we could do what is explained in this this tutorial:
My solution is based on the tutorial above, except I have added sanitization logic to model binder, which means we allow the HTML input, but use HTMLSanitizer to sanitize the input.
Defined a custom model binder:
public class AllowHtmlBinder: IModelBinder
{
// use HtmlSanitizer to remove unsafe HTML/JS from input
private HtmlSanitizer _htmlSanitizer = new HtmlSanitizer();
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var name = bindingContext.ModelName;
var unvalidatedInputMessage = request.Unvalidated[name]; // get the unvalidated input
var sanitizedMessage = _htmlSanitizer.Sanitize(unvalidatedInputMessage); // removed script or any XSS thread from user input
return sanitizedMessage;
}
}
And used it on the specific parameter:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<JsonResult> Contact([ModelBinder(typeof(AllowHtmlBinder))] string message)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
return Json(new { Authorize = "false" });
}
// email message to receiver
}

How to serialize ZF2 form object to JSON?

I'm using zf2 form object on the server and ajax code on the client to implement my registration form.
I post the form values in the ajax request, no problem, and the form gets them fine with
$form->setData($request->getPost());
After I validate the form and perform the registration on the server, I want to send the form back to the client, especially if there are errors, so I can show them to the user.
I'm looking for a standard way using zend or any plugin to serialise the form object into JSON format, so I can send it in the response to the AJAX call.
Any idea?
Well what you can do is run the validation on your form and after that you will return your form within a new JsonModel.
Here is a little example of how to handle your controller:
class RegistrationController extends AbstractActionController
{
public function RegisterAction()
{
$form = new RegisterForm();
$form->setInputFilter(new RegisterInputFilter());
if ($this->getRequest()->isPost()) {
$form->setData($this->getRequest()->getPost());
if($form->isValid()) {
// Handle your registration as the form is valid!
// return to some path after registration is complete.
// Show user he registered succesfully, etc. ;)
}
// Checks if the request is from JavaScript
if($this->getRequest()->isXmlHttpRequest()) {
return new JsonModel(array('registerForm' => $form));
}
}
return new ViewModel(array('registerForm' => $form));
}
}
Notice that the form object is holding all the invalid inputs including its message after validation.
I would take another approach just to completely render the ViewModel again so you can display the validation message much easier. On the side you could add Client Side (Javascript) validation as it's much more user-friendly, but that is just some fancy shizzle I would do ;) In case of rendering the ViewModel:
use Zend\View\Renderer\PhpRenderer;
if($this->getRequest()->isXmlHttpRequest()) {
$renderer = new PhpRenderer;
$registerViewModel = new ViewMOdel();
$registerViewModel->setTemplate('view/register.phtml');
return new JsonModel(array('registerViewModel' => $renderer->render($registerViewModel));
}
Note that not setting a template to your viewModel will result in ZF2 getting the default of the action (view/moduleName/registration/register.phtml) you are in! So in your case you don't need to use PhpRenderrer::setTemplate(). But I just hand it to you so you can change it if you are using any other file.
So now you will receive Json from our controller, in your javascript. Retrieve the new ViewModel from Json and remove the old ViewModel and replace it with the new. By removing the old, you also remove any Javascript that is bound to any element within the viewModel, so you might set the events on your body within your javascript or have it on your attributes in Form/RegistrationForm.
Hope this pushes you in the right direction.

handle direct url with history api in asp.mvc

I have my site like http://mysite.com/ and on the index page i have search box and for the result i am using jqgrid. When user click row in the jqgrid row I am taking data from cells and do ajax call to server and fetch json data and once data arrived I hide the search box and jqgrid and show another div which is I kept for result. In short, user will be on the same page just div's hide/show.
Now I have seen history api and used pushState and popstate so my url becomes in the addressbar like http://mysite.com/controller/action/para1/para2 (here para1 and para2 are the parameters i am passing to action). Everything is ok so far.
Now the problem is if I copy this URL "http://mysite.com/controller/action/para1/para2" and if I open this with let's say different browser and hit enter it display just json data. So, I am confused that how to handle when user directly use that url in controller.
I was thinking to check in the controller action if the request is AJAX then return json data otherwise full page, is that right approach? OR something on the client side we have so that it load the same way as earlier.
Thanks
if I copy this URL "http://mysite.com/controller/action/para1/para2" and if I open this with let's say different browser and hit enter it display just json data.
it only display json data because its ajax call only so how to deal with that so that it also display the same page even user directly access the url.
I think what you're looking for is Request.IsAjaxRequest():
public class FooController : Controller {
public ActionResult GetFoo(int id) {
var model = _fooService.Get(id);
if (Request.IsAjaxRequest())
return PartialView("_Foo", model);
return View("Foo", model);
}
}
Note: It's recommended to use WebAPI controllers to handle only json data. So if the user got there by typing the url the mvc controller will handle it and if you need to get the json data for that view you could call the webapi controller.
Use a separate controller or action method for AJAX and for Views. The View controller should match the URL. The Ajax controller should be the less "pretty" URL since it's behind the scenes.
You need to set up a routing definition in global.asax (MVC 3) or App_Start/RouteConfig.cs (MVC 4) to handle the parameters if you haven't already done that.
routes.MapRoute(
"MyRoute",
"MyUrlController/{action}/{para1}/{para2}",
new
{
action = "Index",
para1 = UrlParameter.Optional,
para2 = UrlParameter.Optional
}
);
Then in the View controller:
public ActionResult Index(string para1 = "Default Value", string para2 = "Default Value")
{
// ...Handle parameters...
return View("_MyView", viewModel);
}
Returning a View object type is the key. The History API URL doesn't get it's data from the same AJAX source controller which returns a PartialViewResult.

MVC & ajax: Should I create more views?

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");

How to prevent duplicate form submission in ASP.NET MVC 3?

I have a razor view that renders a html form and it posts to the server.
If the form values are right then it gets saved to database.
After insertion, I redirect to another view where user can make further changes.
Right now the user can hit browser back button and resubmit the form to create another record in db.
How do I prevent duplicate submission in my MVC app?
One solution is to put a hidden "token" field on the form that's generated randomly when the form loads. When you see that token come back on creation store it somewhere temporarily (in session if you're using sessions for example). If you see the same one again, you can assume the same form was submitted twice quickly together.
Create a cookie to represent that particular page when it succeeds. If it is replayed with the cookie (which the browser would now send over with every request) you know not to allow the new attempt.
Redirect the user to another HttpGet action after handling the post request.
So that when the user refreshes the browser the post action will not be called again.
return RedirectToAction("YourActionMethod");
Although client side validation is possible, it is not secure enough.
I am not sure if this method applies to MVC 3, but what i did is implement a ActionFilterAttribute
here is the implementation:
public class PreventFrequentCallsAttribute : ActionFilterAttribute
{
public int DelayRequest = 5;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.HttpContext.Request;
var cache = filterContext.HttpContext.Cache;
var originationInfo = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress;
originationInfo += request.UserAgent;
var targetInfo = request.RawUrl + request.QueryString;
var hashValue = string.Join("", MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(originationInfo + targetInfo)).Select(s => s.ToString("x2")));
if (cache[hashValue] != null)
{
filterContext.Controller.ViewData.ModelState.AddModelError("ExcessiveRequests", "Excessive Request Attempts Detected.");
}
else
{
cache.Add(hashValue, originationInfo, null, DateTime.Now.AddSeconds(DelayRequest), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
}
base.OnActionExecuting(filterContext);
}
}
later, in the target controller, just add this attribute:
[PreventFrequentCalls(3)]
public PartialViewResult LogOn(LogOnViewModel model)

Resources