Having some odd behavior I'd love some feed back on as I can't seem to find any details on this sort of thing anywhere...
Setup:
Using ASP.NET MVC 4 and VS 2010, I have created a corporate directory application with a search page and a details page that displays a corporate tree based on the search selection.
The search page is very basic and using ajax.beginform it returns a list of people back based on the criteria selected. After clicking on the person you get a "tree" showing that individual, their supervisor and any subordinates.
Problem
The user searches for someone, clicks on their result, sees the tree. When they click the back button (with or without performing any other action on the tree view) they are returned to the search screen. The criteria they entered is there but the results are not (as I would expect). If they click search or hit the enter key, the user then receives a 404 page not found error...
What I've Tried
From the things I've read, I've been focusing on possible solutions revolving around the cache, I've tried expiring the page or setting the page for no-cache but that has not seemed to resolve the issue.
CODE
Search View:
#using (Ajax.BeginForm("PersonList", "Home", new AjaxOptions { UpdateTargetId = "results", InsertionMode=InsertionMode.Replace }))
{
#Html.LabelFor(model => model.LastName)
#Html.TextBoxFor(model => model.LastName)<br />
#Html.LabelFor(model => model.FirstName)
#Html.TextBoxFor(model => model.FirstName)<br />
<input type="submit" value="Search" />
<div id="results">
</div>
}
Block that gets plopped in for the search results. This is a partial view that is reused for the tree view as well. That is why you'll see that the link at the top of the block is in a conditional. If the link is to be used from the search page, it's an html link that forces a full page change. If you're already on the tree view though, navigation is done via ajax. The 404 error I'm receiving occurs without navigating at all on the tree view page:
<div id="p#{ Html.DisplayFor(model => model.Id); }" class="personCard">
<img class="personImg" id="img#{ Html.DisplayFor(model => model.Id); }" src="#Url.Content("~/Photos")/#Html.DisplayFor(model => model.Id)" />
<div>
#if(ViewBag.IsForSearch == null) {
#Ajax.ActionLink(this.Model.FullName, "PersonHierarchy", new { id = this.Model.Id }, new AjaxOptions { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "tree" })
} else {
#Html.ActionLink(this.Model.FullName, "Person", new { id = this.Model.Id })
}
#if(this.Model.TeamSize > 0) {
#: (
#Html.DisplayFor(model => model.TeamSize)
#:)
}
<br />
#Html.DisplayFor(model => model.Title)<br />
#Html.DisplayFor(model => model.MailLocationCode), #Html.DisplayFor(model => model.WorkNumber) #Html.DisplayFor(model => model.ExtensionForDisplay)<br />
#if (this.Model.CellNumber.Trim() != string.Empty)
{
#:Cell:
#Html.DisplayFor(model => model.CellNumber)
#:<br />
}
#Html.DisplayFor(model => model.EmailAddress)
</div>
</div>
Response from Server:
Server Error in '/' Application.
The resource cannot be found. Description: HTTP 404. The resource you
are looking for (or one of its dependencies) could have been removed,
had its name changed, or is temporarily unavailable. Please review
the following URL and make sure that it is spelled correctly.
Requested URL: /
-------------------------------------------------------------------------------- Version Information: Microsoft .NET Framework Version:4.0.30319;
ASP.NET Version:4.0.30319.272
EDIT:
This appears to be specific to IE 9 (in all modes). This behavior does not happen in Chrome and the search form resubmits perfectly after using the back button...
OK...I seem to have found the answer:
To recap, specifically in IE 9 when using ASP.NET MVC Ajax, using the back button to navigate back to a page that has a ajax form on it and trying to submit that form results in a 404 error.
I initially looked into various cache solutions to try to force IE to realize that the page needed to be "reloaded" (even though Chrome and other browsers had no issue with making the form work again when using the back button). This apparently was the correct path to go down but unfortunately I didn't get far enough. The solution ends up being setting the OutputCache for the controller action (or to the entire controller) to:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
Adding that annotation to the action in the controller or to the controller class as a whole resolved the problem...thanks IE...
For further information on this, I refer to my source:
http://dougwilsonsa.wordpress.com/2011/04/29/disabling-ie9-ajax-response-caching-asp-net-mvc-3-jquery/
Thanks to everyone who tried to help.
Have you tried using developer tools [F12] , Fiddler, Firebug, etc to see what the request is that is returning the 404 error message? By determining what request is returning the 404 you can probably figure out why the request is incorrect.
Related
I'm currently working on a website in asp.net MVC. I have one view to edit my model but some item in this model can only be modified by admins.
So I removed some editorfor() for the basic users and everything works fine...
Until I saw that : if basic user edit the model, the fields which are hidden for them are modified to a default value not the previous one modified by an admin.
Did someone know how can I saved the previous values even they are not in an editorfor() ?
Code :
#Html.EditorFor(model => model.name)
#if (ViewBag.Role == "Admin")
{
#Html.EditorFor(model => model.age)
// If admin is connected, he can edit age => no problem
}
// if a user modify name but don't touch age because it's for admin only, the model age will get 0
Thanks in advance
#Html.HiddenFor(m = m.age)
Simple Answer for my stupid question, I have tried it on first but it didn't work but now it did so there is the solution. Thanks to Stephen Muecke.
#Html.EditorFor(model => model.name)
#if (ViewBag.Role == "Admin")
{
#Html.EditorFor(model => model.age)
// If admin is connected, he can edit age => no problem
}
If we take your example then age is only for admin. so when basic user will edit this page that time condition will not satisfied and #Html.EditorFor(model => model.age) will be not rendered for basic user. So when you post the form that time age's value will not be pass to controller because age is not rendered in form. so it will save default value which you set.
Solution is you have to use hidden field out side the if condition. so value will be keep as it is controller -> View -> controller.
My problem is that the backend server (written in grails) is automatically converting my request URL to be a different URL. Specifically, it is changing it from /UXChallengeAwards/processSelectedNotifications to /UXChallengeAwards/index.
--
In a template gsp file, I have defined a button that makes a jQuery ajax call when clicked on:
<button class="blue-link"
onclick="jQuery.ajax({type:'POST',
data:jQuery(this).parents('.multiSelectForm').serialize(),
url: '/ici/UXChallengeAwards/processSelectedNotifications/${challenge.id}',
success:function(data,textStatus){},
error:function(xhr,textStatus,errorThrown){}
})" >
The method UXChallengeAwardsController.processSelectedNotifications exists. It performs some work and then redirects to another action in the controller. In fact, this used to work. But somehow in the process of adding a second button I made a change which seems to have broken things.
When the button is now clicked, the request URL gets switched to /ici/UXChallengeAwards/index and a 404 is returned because index does not exist as an action in this controller.
I've googled, and the most common answer for when this happens is that a controller must return some results for the view. But I've seen plenty of examples of redirects in controllers, and I do not see what I am doing wrong. (I did try variants of rendering results, but with no success.)
Here is what my controller action looks like:
def processSelectedNotifications = {
def challenge
def checkboxes = params.list('selectCheckbox');
for (checkbox in checkboxes) {
// the checkbox contains the id of a ChallangeAward that should be published
ChallengeAwards challengeAwards = ChallengeAwards.get(checkbox.toInteger())
if (challengeAwards) {
// grab a challenge for use in the redirect, they are all the same
challenge=challengeAwards.challenge
publish(challengeAwards)
}
}
if (challenge) {
redirect action: 'challengeAwardsRemote', id: challenge.id
return
}
// render a failure message if we got here
render messageNS(code:"UX.ChallengeAwards.Publish.failure")
}
I would really appreciate any insights into what might be wrong, or how to go about tackling this issue. I've checked my UrlMappings, and this is the rule that should handle this controller/method request:
"/$controller/$action?/$id?"{ constraints {} }
Thank you very much!
I'm going to go ahead and answer my own question, in case it is helpful for other newbies.
It turns out that I was not getting an automatic redirect. Rather, I had an error in the button setup code, so that grails was using its default link behavior. (Which is to go to the controller that matches the view, and if no action is specified, use the index method.)
The code above was originally created using a remoteSubmit tag, but I found that the generated code did not support handling multiple forms on a single page very well. So, I copied that generated code and then tweaked it to handle the multiple forms. However, I wanted the styling to match up with what was already in place on the page, so I switched it to be a button. That's when things went awry.
Eventually, I ended up specifying an onClick function for the button, and then writing the ajax submit code in javascript. Which turned out to be much simpler.
Here is what the button specification ended up looking like:
<button type="submit" id="notifications" class="blue-link" >
<i class="fa fa-envelope-o"></i>
<g:messageNS
code="UX.DiscussionBoard.ChallengeAward.Button.notify" />
</button>
And the associated JavaScript:
jQuery(document).ready(function() {
var clkBtn = "";
jQuery('button[type="submit"]').click(function(evt) {
clkBtn = evt.target.id;
});
jQuery('.multiSelectForm').submit(function() {
var url = '/ici/UXChallengeAwards/processSelectedNotifications';
if (clkBtn == 'deletes') {
url ='/ici/UXChallengeAwards/processSelectedDeletes';
}
var errorTarget = jQuery(this).parents().find('.recipientMessage').val();
var requestData = jQuery(this).parents('.multiSelectForm').serialize();
var options = {
data : requestData,
type : 'POST',
url : url,
target : '#awardsTab',
error : function(data) {
jQuery('#' + errorTarget).html(data.responseText).show();
},
success : function(data) {
console.log("in success");
}
};
jQuery(this).ajaxSubmit(options);
return false;
});
I'm developing a ASP.NET MVC3 website for the 1st time. On my development machine everything works fine.
I bin deployed everything on our test machine. After some missing DLL issues the website seemed to work on the following url:
http://localhost/Test%20Website
But when I clicked the following link (which is created by an inline javascript because I'm using Infragistics Grid which is irrelevant for the question):
<img src=\"../Resources/Edit.png\" align=\"left \">
I get HTTP Error 404.0 - Not Found, which is logical, because the Physical Path is: C:\inetpub\wwwroot\Patient\Edit\537
While the physical Path should be: C:\inetpub\wwwroot\Test Website\Views\Patient\Edit\537 (atleast... I think... don't understand how MVC routing works exactly)
The links which are made by using the ActionLink HTML helper and such work fine by the way.
So this works:
#Html.ActionLink("About", "About", "Home")
And this works:
#Html.Partial("Search", new SearchModel())
So, my question is, how do you solve these issues?
p.s.: All the images in resources don't work either. sigh
UPDATE after an answer
I'm sorry, apparantly it is important to note that this takes place in a javascript function as a string. That's because I'm using the FormatterFunction from Infragistic's JQuery grid. Ok, this is what I've got so far:
"function(val) {return '<img src=\"../Resources/Edit.png\" align=\"left\">'; }"
The name 'val' does not exist in the current context, which is logical. But I don't know how to solve it, 'cause of my limitted knowledge of javascript/Razor/etc... Could you please help? Val in this case is by the way the value of the column it's bound to. In this case the ID of patient.
Ego4eg asked more code
It's Infragistics JQuery grid. This grid has a FormatterFunction which has a string as parameter. This string needs to be a javascript function. To give you an idea, this looks like:
#(Html.Infragistics()
.Grid(Model)
.ID("grid1")
.AutoGenerateColumns(false)
.Columns(column => {
//column.For(p => p.ID).FormatterFunction("function(val) {return '<img src=\"../Resources/Edit.png\" align=\"left\">'; }").Width("25px").HeaderText(" ");
column.For(p => p.ID).FormatterFunction("function(val) {return '<img src=\"../Resources/Edit.png\" align=\"left\">'; }").Width("25px").HeaderText(" ");
column.For(p => p.ID).FormatterFunction("function(val) {return '<img src=\"../Resources/add.png\" align=\"left\">'; }").Width("25px").HeaderText(" ");
column.For(p => p.FullName).DataType("string").HeaderText("Naam").Width("250px");
column.For(p => p.Address).DataType("string").HeaderText("Adres").Width("400px");
column.For(p => p.BSN).DataType("string").HeaderText("BSN").Width("85px");
column.For(p => p.DateOfBirthAsString).DataType("string").HeaderText("Geboortedatum").Width("85px");
column.For(p => p.GeneralPractitionerName).DataType("string").HeaderText("Huisarts");
})
Hope this helps.
It would be better to use Url.Content like:
<a href=\"#Url.Content("~/Patient/Edit/" + val)\">
Try this:
<a href ="Url.Action("Edit", "Patient", new { id = val })" />
I have a blog that has a sidebar with a partial view in it that enables users to sign up for my e-mail newsfeed. What I'm trying to do is returning the user to the page they came from after posting some data, and displaying any validation or return messages in the form's partial view.
The problem is that my partial view opens in a new window (without the lay-out). How can I fix this so it returns to my blog, with the return data in de sidebar?
This is my view:
#using Blog.Models.Entities
#model Subscriber
<header>
<h2>Subscribe</h2>
</header>
<p>Subscribe to my e-mail newsfeed.</p>
#using (Html.BeginForm("Form", "Subscription"))
{
<div class="editor-label">#Html.LabelFor(subscriber => subscriber.Email)</div>
<div class="editor-field ">#Html.EditorFor(subscriber => subscriber.Email)</div>
#Html.ValidationMessageFor(subscriber => subscriber.Email)
<input type="submit" value="Subscribe" />
<p>#ViewBag.Result</p>
}
And the relevant pieces of controller that are processing the data:
public ActionResult Form()
{
return PartialView("_Form");
}
[HttpPost]
public ActionResult Form(Subscriber subscriber)
{
if (ModelState.IsValid)
{
Subscriber foundSubscriber = _repository.Subscribers.Where(s => s.Email.Equals(subscriber.Email)).FirstOrDefault();
if (foundSubscriber != null)
{
ModelState.AddModelError("Email", "This e-mail address has already been added.");
return PartialView("_Form", subscriber);
}
_repository.SaveSubscriber(subscriber);
ViewBag.Result = "Succesfully subscribed to the newsletter.";
return PartialView("_Form");
}
ModelState.AddModelError("Email", "Please provide a valid e-mail address.");
return PartialView("_Form", subscriber);
}
When submitting a form, the browser sends an HTTP Post request to the server. The browser then displays the Response's payload. Your post Controller Action is returning a PartialView, which the browser is happily rendering (even though it doesn't have the html, head, or body tags necessary to make it truly valid HTML).
It sounds like you want the browser to keep most of your page loaded and rendered, post the form, then take the resulting HTML and only replace a portion of the loaded page. Simply put, the browser isn't smart enough to do that.
What you probably want to do is something like this:
User fills in some form data and clicks save/submit/go/whatever.
However, you don't want the browser to submit the form, because it won't preserve most of the current page the way you want.
Instead, you want the "submit" button to call some local javascript.
That local JS should bundle up the user-entered form data, craft a POST with that data as the payload, and submit the POST using Ajax. This will keep the current page loaded, while the ajax request hits your Controller Action
You controller action stays the way it is, and returns a partial view.
Your JS function that launched the Ajax call must also define a "success" function which will get called when the operation completes.
Within that success function, your javascript will grab the HTML from the response, and use it to replace the area of the page that held the original form.
I highly recommend jQuery - it will make it MUCH easier to craft the Ajax request, handle the success callback, and replace just a section of the currently-loaded page with the result. My understanding is that MS's 'unobtrubsive javascript' may also help implement this, however I don't have any direct experience with it.
Obviously, all of this will only work if the browser has javascript enabled.
I finally found the solution to the problem. I implemented it with AJAX and ended up with the following code:
_Index.cshtml
<header>
<h2>Subscribe</h2>
</header>
<p>Subscribe to my e-mail newsfeed.</p>
<div id="subscription-form">
#{Html.RenderPartial("_Form");}
</div>
_Form.cshtml
#using Blog.Models.Entities
#model Subscriber
#{
AjaxOptions ajaxOptions = new AjaxOptions
{
LoadingElementId = "loading",
LoadingElementDuration = 2000,
HttpMethod = "Post",
UpdateTargetId = "subscription-form"
};
}
<div id="loading" style="display: none;">
<p>Processing request...</p>
</div>
#using (Ajax.BeginForm("Index", "Subscription", ajaxOptions))
{
<div class="editor-label">#Html.LabelFor(subscriber => subscriber.Email)</div>
<div class="editor-field ">#Html.EditorFor(subscriber => subscriber.Email)</div>
#Html.ValidationMessageFor(subscriber => subscriber.Email)
<input type="submit" value="Subscribe" />
}
_Succes.cshtml
#using Blog.Models.Entities
#model Subscriber
<p id="subscription-result">#ViewBag.Result</p>
And the following controller action methods:
public ActionResult Index()
{
return PartialView("_Index");
}
[HttpPost]
public PartialViewResult Index(Subscriber subscriber)
{
if (ModelState.IsValid)
{
Subscriber foundSubscriber = _repository.Subscribers.Where(s => s.Email.Equals(subscriber.Email)).FirstOrDefault();
if (foundSubscriber != null)
{
ModelState.AddModelError("Email", "This e-mail address has already been added.");
return PartialView("_Form", subscriber);
}
_repository.SaveSubscriber(subscriber);
ViewBag.Result = "Succesfully subscribed to the newsletter.";
return PartialView("_Succes", subscriber);
}
ModelState.AddModelError("Email", "Please provide a valid e-mail address.");
return PartialView("_Form", subscriber);
}
I hope this will help anyone trying to achieve the same in the future. BTW, I found the solution on this blog: http://xhalent.wordpress.com/2011/02/05/using-unobtrusive-ajax-forms-in-asp-net-mvc3/.
I have created a new MVC2 project using the ASP.NET MVC2 Web Application, which gives me the default web site. I have added a simple area in which I had a Home Controller and an Index view. That cuased the first problem with the compiler giving "Multiple types were found that match the Controller name 'Home'". I changed Home to Main and it compiled.
I added a new tab to reference the Index view in my area, opened the website and started clicking the tabs. When I visited the Area index page, I couldn't go back to the Home or About page without changing the menu, as follows:
<ul id="menu">
<li><%= Html.ActionLink("SampleArea.Main", "Index", "Main", new { area = "SampleArea" }, null)%></li>
<li><%= Html.ActionLink("Home", "Index", "Home", new { area = "" }, null)%></li>
<li><%= Html.ActionLink("About", "About", "Home", new { area = "" }, null)%></li>
</ul>
I could then cycle through the tabs correctly. I then changed the code in the LogOff view in the Account controller, as follows:
public ActionResult LogOff()
{
FormsService.SignOut();
//return RedirectToAction("Index", "Home");
return RedirectToAction(Request.UrlReferrer.AbsolutePath.ToString());
}
I am using UrlReferrer.AbsolutePath to return to the calling page if the User logs off. If the calling page happens to be the View in SampleArea, .AbsolutePath contains "/SampleArea". This is because the controller and view are the defaults, and so they are not included. As it continues, I get the following error message:
The resource cannot be found.
Description: HTTP 404. The resource
you are looking for (or one of its
dependencies) could have been removed,
had its name changed, or is
temporarily unavailable. Please
review the following URL and make sure
that it is spelled correctly.
Requested URL: /Account/SampleArea
My understanding is that /Account has been added because that is the controller it was in when LogOff was executed. It thinks /SampleArea is an action and therefore added the current controller to complete the route.
Using UrlReferrer.AbsolutePath, is there any way I can specify SampleArea as an area, or is there something else I can do to return to the correct page?
New Addition
This is even stranger than I thought. I opened the website I am currently developing and changed the return statement in view LogOut to return using AbsolutePath. A breakpoint reveals it contains "/Club/PrivacyPolicy". However, I get the same error message with the following difference:
Requested URL: /Login/Club/PrivacyPolicy
Why on earth should it prefix it with /Login which is a View, rather than /Account which is a Controller? In fact, why should it prefix it with anything at all? /Club/PrivacyPolicy
is a valid route in Global.asax.cs.
I have finally figured out how to return to the page you were on when you triggered the LogOn or LogOut request. I have used the following piece of code.
Html.ActionLink("Member LogOn", "LogOn", "Account", new { area = "", returnUrl = HttpContext.Current.Request.RawUrl }, null)
This generates
<a href='/LogIn/LogOn?returnUrl=%2FContactUs'>Member LogOn</a>
for example.
In my HttpPost ActionResult LogOn, I then have
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
Sometimes I find I just need HttpContext.Request.RawUrl.
I'm not quite sure why it has generated /LogIn/LogOn instead of /Account/LogOn, but it works as expected.