I have an ASP.NET MVC3 application with an action filter attribute applied to a controller:
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Headers.Add(
"MyFilterAttribute", "entered" );
}
public override void OnResultExecuted(
ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Headers.Add(
"MyFilterAttribute", "exited" );
}
}
[MyFilter]
public class MyController : Controller
{
public ActionResult MyAction()
{
return new EmptyResult();
}
}
MVC routing maps /MyPath/MyAction to the controller-action pair above.
and the client code invokes https://my.company.com/MyPath/MyAction and dumps the response headers.
Initially it works - I see that the response received on the client contains two MyFilterAttribute headers as expected.
Then I add a location element to web.config:
<configuration>
// lots of stuff, then
<location path="MyPath">
<system.webServer>
<security>
<access sslFlags="SslNegotiateCert"/>
</security>
</system.webServer>
</location>
</configuration>
and once I redeploy with these changes the response headers no longer contain the two MyFilterAttribute items.
Adding location to web.config is the only change. Once I remove it the old expected behavior is back.
It looks like adding a location elements somehow breaks MVC attributes.
What might be causing this behavior?
sslFlags="SslNegotiateCert" requests that IIS opens a mutually verified channel which is not the default behavior. I looked into IIS logs and it's HTTP 403.16 (client certificate untrusted) all the time. Because the client certificate is self-signed IIS doesn't trust it and so fails to open a mutually verified channel.
Either I have to not use SslNegotiateCert (and then the client certificate is not passed to application code) or I need a certificate which IIS trusts (this answer explains how that can be achieved).
This error is very common, and I tried all of the solutions and non of them worked. I have disabled WebDAV publishing in control panel and added this to my web config file:
<handlers>
<remove name="WebDAV"/>
</handlers>
<modules runAllManagedModulesForAllRequests="true">
<remove name="WebDAVModule"/>
</modules>
The error still persists. This is the controller:
static readonly IProductRepository repository = new ProductRepository();
public Product Put(Product p)
{
return repository.Add(p);
}
Method implementation:
public Product Add(Product item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
item.Id = _nextId++;
products.Add(item);
return item;
}
And this is where the exception is thrown:
client.BaseAddress = new Uri("http://localhost:5106/");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.PostAsJsonAsync("api/products", product);//405 exception
Any suggestions?
You are POSTing from the client:
await client.PostAsJsonAsync("api/products", product);
not PUTing.
Your Web API method accepts only PUT requests.
So:
await client.PutAsJsonAsync("api/products", product);
I had the same exception. My problem was that I had used:
using System.Web.Mvc; // Wrong namespace for HttpGet attribute !!!!!!!!!
[HttpGet]
public string Blah()
{
return "blah";
}
SHOULD BE
using System.Web.Http; // Correct namespace for HttpGet attribute !!!!!!!!!
[HttpGet]
public string Blah()
{
return "blah";
}
My problem turned out to be Attribute Routing in WebAPI. I created a custom route, and it treated it like a GET instead of WebAPI discovering it was a POST
[Route("")]
[HttpPost] //I added this attribute explicitly, and it worked
public void Post(ProductModel data)
{
...
}
I knew it had to be something silly (that consumes your entire day)
I tried many thing to get DELETE method work (I was getting 405 method not allowed web api) , and finally I added [Route("api/scan/{id}")] to my controller and was work fine.
hope this post help some one.
// DELETE api/Scan/5
[Route("api/scan/{id}")]
[ResponseType(typeof(Scan))]
public IHttpActionResult DeleteScan(int id)
{
Scan scan = db.Scans.Find(id);
if (scan == null)
{
return NotFound();
}
db.Scans.Remove(scan);
db.SaveChanges();
return Ok(scan);
}
This error can also occur when you try to connect to http while the server is on https.
It was a bit confusing because my get-requests were OK, the problem was only present with post-requests.
Chrome often times tries to do an OPTIONS call before doing a post. It does this to make sure the CORS headers are in order. It can be problematic if you are not handling the OPTIONS call in your API controller.
public void Options() { }
I'm late to this party but as nothing above was either viable or working in most cases, here is how this was finally resolved for me.
On the server the site/service was hosted on, a feature was required!
HTTP ACTIVATION!!!
Server Manager > Manage > Add Roles and Features > next next next till you get to Features > Under .NET (each version) tick HTTP Activation.
Also note there is one hidden under >net > WCF Services.
This then worked instantly!
That was melting my brain
I was getting the 405 on my GET call, and the problem turned out that I named the parameter in the GET server-side method Get(int formId), and I needed to change the route, or rename it Get(int id).
You can also get the 405 error if say your method is expecting a parameter and you are not passing it.
This does NOT work ( 405 error)
HTML View/Javascript
$.ajax({
url: '/api/News',
//.....
Web Api:
public HttpResponseMessage GetNews(int id)
Thus if the method signature is like the above then you must do:
HTML View/Javascript
$.ajax({
url: '/api/News/5',
//.....
If you have a route like
[Route("nuclearreactors/{reactorId}")]
You need to use the exact same parameter name in the method e.g.
public ReactorModel GetReactor(reactorId)
{
...
}
If you do not pass the exact same parameter you may get the error "405 method not allowed" because the route will not match the request and WebApi will hit a different controller method with different allowed HTTP method.
This does not answer your specific question, but when I had the same problem I ended up here and I figured that more people might do the same.
The problem I had was that I had indeliberately declared my Get method as static. I missed this an entire forenoon, and it caused no warnings from attributes or similar.
Incorrect:
public class EchoController : ApiController
{
public static string Get()
{
return string.Empty;
}
}
Correct:
public class EchoController : ApiController
{
public string Get()
{
return string.Empty;
}
}
Here is one solution:
<handlers accessPolicy="Read, Script">
<remove name="WebDAV" />
</handlers>
learn.microsoft.com solution article
and remove WebDAV from modules
<remove name="WebDAVModule" />
[HttpPost] is unnecessary!
[Route("")]
public void Post(ProductModel data)
{
...
}
I could NOT solve this. I had CORS enabled and working as long as the POST returned void (ASP.NET 4.0 - WEBAPI 1). When I tried to return a HttpResponseMessage, I started getting the HTTP 405 response.
Based on Llad's response above, I took a look at my own references.
I had the attribute [System.Web.Mvc.HttpPost] listed above my POST method.
I changed this to use:
[System.Web.Http.HttpPostAttribute]
[HttpOptions]
public HttpResponseMessage Post(object json)
{
...
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
}
This fixed my woes. I hope this helps someone else.
For the sake of completeness, I had the following in my web.config:
<httpProtocol>
<customHeaders>
<clear />
<add name="Access-Control-Expose-Headers " value="WWW-Authenticate"/>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, PATCH, DELETE" />
<add name="Access-Control-Allow-Headers" value="accept, authorization, Content-Type" />
<remove name="X-Powered-By" />
</customHeaders>
</httpProtocol>
Old question but none of the answers worked for me.
This article solved my problem by adding the following lines to web.config:
<system.webServer>
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule" />
</modules>
</system.webServer>
In my case I had a physical folder in the project with the same name as the WebAPI route (ex. sandbox) and only the POST request was intercepted by the static files handler in IIS (obviously).
Getting a misleading 405 error instead of the more expected 404, was the reason it took me long to troubleshoot.
Not easy to fall-into this, but possible. Hope it helps someone.
Make sure your controller inherits from Controller class.
It might even be crazier that stuff would work locally even without that.
For my part my POST handler was of this form:
[HttpPost("{routeParam}")]
public async Task<ActionResult> PostActuality ([FromRoute] int routeParam, [FromBody] PostData data)
I figured out that I had to swap the arguments, that is to say the body data first then the route parameter, as this:
[HttpPost("{routeParam}")]
public async Task<ActionResult> PostActuality ([FromBody] PostData data, [FromRoute] int routeParam)
check in your project .csproj file and change
<IISUrl>http://localhost:PORT/</IISUrl>
to your website url like this
<IISUrl>http://example.com:applicationName/</IISUrl>
Another possible issue which causes the same behavior is the default parameters in the routing. In my case the controller was located and instantiated correctly, but the POST was blocked because of default Get action specified:
config.Routes.MapHttpRoute(
name: "GetAllRoute",
routeTemplate: "api/{controller}.{ext}"/*,
defaults: new { action = "Get" }*/ // this was causing the issue
);
I was having exactly the same problem. I looked for two hours what was wrong with no luck until I realize my POST method was private instead of public .
Funny now seeing that error message is kind of generic. Hope it helps!
We had a similar issue. We were trying to GET from:
[RoutePrefix("api/car")]
public class CarController: ApiController{
[HTTPGet]
[Route("")]
public virtual async Task<ActionResult> GetAll(){
}
}
So we would .GET("/api/car") and this would throw a 405 error.
The Fix:
The CarController.cs file was in the directory /api/car so when we were requesting this api endpoint, IIS would send back an error because it looked like we were trying to access a virtual directory that we were not allowed to.
Option 1: change / rename the directory the controller is in
Option 2: change the route prefix to something that doesn't match the virtual directory.
In my case, the 405 error only showed up in production server, and not on my dev machine.
I found that the problem was due to the fact that I simply "manually" transferred the contents of the locally published folder from my local machine to the online production server.
So, the FIX for me was to simply delete all the online files on the prod server, and then use the "Publish" option on Visual Studio to publish directly from my local machine to the prod server via FTP.
I don't know exactly why this changed something, because it seems to me the files were the same, but this thing fixed the problem and I hope it could help someone else too.
Another possible cause can be to do with Session State config in IIS causing a redirect which appends "?AspxAutoDetectCookieSupport=1" to the URL. In my case I was performing a POST but the redirect was being performed as a GET by the HttpClient.
The solution I found was to add the following to my web.config:
<system.web>
<sessionState cookieless="UseCookies" />
</system.web>
Function names make it complicated for c# sometimes. Change name of the function, it will works. Like ProductPut instead of PutProduct or Put.
public Product ProductPut(Product p)
{
return repository.Add(p);
}
I need to rewrite the URL using ASP.NET with code behind as C#. My Application contains the following URL...
http://www.mywebsite.com/Products.aspx?id=1&pg=1
However, I need to rewrite the URL in such a way that the user gets the same contents of the above URL when the user types the following URL...
http://www.mywebsite.com/CategoryName/ProductName/1
Can any of you guys help me with the complete necessary code how to do it?
I mean the web.config, Global.asax, etc...
If you have IIS7, the best option would be to use IIS Url Rewrite Module.
There might be a couple of options I could suggest:
One
You could look at setting up an HttpHandlerFactory.
I have written one myself: http://mvcsnippets.blogspot.co.uk/2012/10/custom-cms-using-httphandlerfactory.html
here is the basic gist:
namespace Web.Helpers {
public class HttpCMSHandlerFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url,string pathTranslated)
{
string pageName = Path.GetFileNameWithoutExtension(context.Request.PhysicalPath);
//on Server.Transfer the context is kept, so this just overrides the existing value.
if (context.Items.Contains("PageName"))
{
context.Items["PageName"] = pageName; } else { context.Items.Add("PageName", pageName); }
FileInfo fi = new FileInfo(context.Request.MapPath(context.Request.CurrentExecutionFilePath));
//if File is not physical
if (fi.Exists == false)
{
//return page to CMS handler the context containing "PageName" is passed on to this page, which then calls to the database to return the copy.
return PageParser.GetCompiledPageInstance(url, context.Server.MapPath("~/CMSPage.aspx"), context);
}
else
{
// Returns real page.
return PageParser.GetCompiledPageInstance(context.Request.CurrentExecutionFilePath, fi.FullName, context);
}
}
}
The behaviour I was trying to handle was that if there might be CMS content and I didnt want to have to create a page each time I needed information served, but if a physical page exists that should be returned.
For you, you might want to accept the URL, break it down to the component parts.
so http://www.mywebsite.com/CategoryName/ProductName/1 becomes:
context.Items["Categoryname"] = valueFromUrlasCategoryName;
context.Items["Productname"] = valueFromUrlasProductName;
context.Items["Id"] = valueFromUrlasId (or pg);
return PageParser.GetCompiledPageInstance(url, context.Server.MapPath("~/ControlPage.aspx"), context);
Then on your control page you can intercept these values from the context passed in and interrogate the data as needed.
In the web.config you point a reference towards your HttpHandlerFactory
<httphandlers>
<add path="*.aspx" type="Web.Helpers.HttpCMSHandlerFactory, Web.helpers" verb="*"/>
</httphandlers>
In your case you could set the path as "." to capture all traffic. this would mean you would have to add handling for images and scripts.
you will also need to make sure you add a wildcard to your IIS for extensionless pages.
There are plenty of articles about HttpHandlerFactories on the web, and might explain better.
Two
The sort of thing you are after is part of MVC, could you look at changing your UI to use that?
Well, I know it's IIS which is supposed to invoke it. Anyway; I have a Sharepoint solution which is supposed to return a special string when files with particular extensions are clicked on document libraries.
In the corresponding web.config file I have following to run this HTTP Handler:
<system.webServer>
<handlers>
...
<add name="MyFileHandler" path="*.bar" verb="*" type="Foo.Example.MyHandler, Foo.Example, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3b53a24010893ac2" resourceType="File" />
...
</handlers>
</system.webServer>
And the HttpHandler class is something like this:
namespace Foo.Example
{
public class MyHandler : IHttpHandler
{
public MyHandler(){} //For breakpoint
public void ProcessRequest(HttpContext context)
{
//Do stuff and write to response.
}
public bool IsReusable
{
get { return false; }
}
}
}
When I try to open a file with '.bar' extension on Sharepoint, it returns 404. What I do in ProcessRequest is not relevant because when I debug the handler, I can see that the handler's constructor is invoked but not the 'ProcessRequest'. Besides the debugger I have also put debug lines(File.AppendAll), again only the constructor gets invoked according to the debug output.
IIS 7.5.7600
Sharepoint 2010 Foundation
Turns out
resourceType="File"
on handler tag in web.config was the problem. Either remove it or set it as "Unspecified".
That is already mentioned here which, unfortunately, I failed to spot before.
The only thing I can think of is to try moving your handler to be really the first one.
Otherwise it could be better to actually integrate with SharePoint instead of trying to override its behavior. In this case you probably should post separate question for what you want to achieve.
I need to redirect a user to a different page if they visit a certain set of pages in a web. There is one condition: I'm not allowed to touch the IIS. I have googled around and found a thing called the custom HttpHandler for WSS 3.0. It sounded like something that I can use to capture the URL the user enters (the most malicious way to get to a page that should really be redirected) and redirect them to another page/web. But having not have the chance to use it yet, I was wondering am I on the right track by using a Custom HttpHandler to redirect a user to a different page in Sharepoint using C#?
Many thanks.
HttpHandlers are used by IIS to "handle" different document types, for instance you have separate .asmx handle, .aspx handler, .ascx handler, etc.
Look into SPUtility.Redirect
You can use the SPUtility.Redirect method whenever you want to direct the user to a different page. For example, you might create a landing page that determines the user's role membership, and based on that information you can redirect them to an appropriate page. Or, based on the contents of a query string issued by the user's browser, you might redirect them to a page that can process the query string, such as the Search Center results page.
Alternatively, you can look into Using Disposable Windows SharePoint Services Objects.
--EDIT--
This is in response to your comment; you are thinking in the right direction
Create a custom HttpHandler; Example 1, Example 2
Whenever a user requests a url, your custom HttpHandler is going to process that.
Redirect() if you like the url, else otherwise.
But for your Custom HttpHandler to work, it should be called first - so in your config file you will have to provide the value in path. Usually an extension is added here. But you can replace that with a * to work for all request. I believe that would work.
<httpHandlers>
<add verb="*" path="*.aspx" type="MyNameSpace.MyHttpHandler, MyAssemblyName" />
</httpHandlers>
--EDIT--
This is in response to your comment. Assuming that you have "access" to pages so that you can write javascript in it, you can use the javascript in following way.
<script language=JavaScript>
function RedirectIfUnAuthorized()
{
//Get the location of the current page.
var currentUrl = new String( document.location.href )
//See if it belongs to the urls you are looking for; redirect if so.
if (currentUrl == "http://www.thisUrl.com/page1.aspx") {Response.Redirect("http://www.GOOGLE.com")}
if (currentUrl == "http://www.thisUrl.com/page2.aspx") {Response.Redirect("http://www.BING.com")}
if (currentUrl == "http://www.thisUrl.com/page3.aspx") {Response.Redirect("http://www.someother.com")}
}
</script>
You may call the above javascript in the page's OnLoad event.
Are you allowed to deploy code on the server? Or is that touching IIS too?
(SharePoint makes changes to web.config too. If you're allowed to deploy code, so could you. If you don't tell your admins they probably wouldnt even notice.)
You can 'deploy' your web.config changes through an SPWebConfigModification and deploy any web.config redirections or httphandlers that way.
HTTP handlers are the end point
objects in ASP.NET pipeline and an
HTTP Handler essentially processes the
request and produces the response. For
example an ASP.NET Page is an HTTP
Handler.
HTTP Modules are objects which also
participate the pipeline but they work
before and after the HTTP Handler does
its job, and produce additional
services within the pipeline (for
example associating session within a
request before HTTP handler executes,
and saving the session state after
HTTP handler has done its job, is
basically done by an HTTP module,
SessionStateModule)
In your case, HTTPModule will require to redirect the another web page.
using System;
using System.Web;
using System.Web.UI;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data;
namespace CustomHttpModule
{
public class HttpModuleImplementation : IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
}
public void Init(HttpApplication context)
{
if (context == null)
throw new ArgumentNullException("Context == null");
context.AuthorizeRequest += new EventHandler(this.ProcessRequestHandler);
}
#endregion
private void DummpRequest(object sender, EventArgs e)
{
}
//first check that user.identity record exist in database
//If not then forward user to User registration page
private void ProcessRequestHandler(object sender, EventArgs e)
{
try
{
HttpApplication context = (HttpApplication)sender;
string strAbsoluteUri = context.Request.Url.AbsoluteUri.ToLower();
//check if request is accessing aspx page
if (strAbsoluteUri.Substring(strAbsoluteUri.Length - 5, 5).Contains(".aspx"))
{
string userName = context.User.Identity.Name;
//replace Test Module with DB call to validate user data
if (!CheckUserInDb(userName))
{
if (!strAbsoluteUri.Contains("mypage.aspx"))
redirectToRegistrationPage(context);
}
}
}
catch (Exception ex)
{
}
}
private void redirectToRegistrationPage(HttpApplication context)
{
context.Response.Redirect("http://" + context.Request.ServerVariables["HTTP_HOST"].ToString() + "Regpage.aspx", false);
}
private bool CheckUserInDb(string userName)
{
return true;
}
}
}
In SharePoint virtual directory web.config file you have to enter the following entry under section httpModules :
<add name="CustomHttpModule" type="CustomHttpModule.HttpModuleImplementation, CustomHttpModule" />