405 when using AttributeRouting.PUTAttribute unless I also include HttpPutAttribute - asp.net-web-api

We have an MVC project that I am attempting to update to include WebApi. In order to get the required routes we are using AttributeRouting. All the calls seem to be routing correctly except for [PUT] which returns a 405. I have simplified the controller and actions and still receive the error with the [PUT] unless I include [HttpPut] also. Not sure what I am missing.
[RoutePrefix("api/Sites")]
public class SitesController : BaseApiController
{
[POST("")]
public bool CreateSite(SiteSignupArgs args)
{
...
}
[GET("Statuses")]
public IList<SiteAuditViewModel> GetStatuses()
{
...
}
[PUT("Statuses/{siteId}")]
[HttpPut] // This is required or 405 is returned
public HttpResponseMessage UpdateStatus(string siteId, UpdateStatusArgs args)
{
...
}
[DELETE("Statuses/{siteId}")]
public HttpResponseMessage Delete(string siteId)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Version 3.5.6 of AttributeRouting.Core, AttributeRouting.Core.Http, AttributeRouting.Core.Web, AttributeRouting.WebApi
MVC4
WebDAV is not installed.

What you are seeing is an expected behavior. Action Selector in Web API by default assumes the action to be of verb POST if the action name does not have a prefix with verbs like "Get", "Post", "Put", "Delete" etc.
Now it isn't working even if you have specified [PUT("Statuses/{siteId}")] attribute explicitly because, Action selector looks for attributes from System.Web.Http namespace like HttpGetAttribute, HttpPostAttribute, HttpPutAttribute etc.
Since AttributeRouting's PUTAttribute isn't of the above types, Action selector doesn't consider it and still thinks it to be the default one, which is POST. So your workaround of having HttpPut attribute is correct.

Related

Serve static file for all sub-routes

I build an MVC Core application with single-page-clients.
I have configured some routes for /api/... which works well. Additionally I want to serve static files for some routes. e.g.:
For all sub-routes of /Home/ I want to receive /Home/index.html
For all sub-routes of /App/ I want to receive /App/index.html
I added app.UseStaticFiles() to Configure() so I can access /Home/index.html but it does not work for any other sub-route.
What is missing?
I changed my routing-system to attribute routing. Among others I added a HomeController:
[Route("")]
public class HomeController : Controller
{
[Route("")]
public IActionResult Index()
{
return View(); // The Home-page
}
[Route("Error")]
public IActionResult Error()
{
// show an error page
return Content(Activity.Current?.Id?.ToString() ?? HttpContext.TraceIdentifier.ToString());
}
[Route("{client}/{*tail}")]
[Produces("text/html")]
public IActionResult ClientApp(string client, string tail)
{
// show a client app
try
{
return new ContentResult()
{
Content = System.IO.File.ReadAllText($"./wwwroot/{client}/index.html"),
ContentType = "text/html"
};
}
catch
{
return RedirectToAction("/Error");
}
}
}
My client apps have had an index.html file inside its own folder (client routing-part) inside of wwwroot. When a request tries to access /something/... the route of ClientApp matches with something as the client-app folder name and the index.html is sent to the client. There is no redirect and the url stays the same.
It causes no problem with static files if you add UseStaticFiles before AddMvc in Startup:
app.UseStaticFiles();
app.UseMvc();
Tested in ASP.NET MVC Core 2.0.

JayData incorrect behaviour with WebApi v2 Odata during entity update the Patch endpoint get invoked

While trying to update the entity, JayData triggers the PatchEntity method on the WepAPI backend. I find this as an invalid behaviour as for the UpdateEntity should be invoked.
The add and delete entity functionality works OK. On the backend I have a controller that inherits from EntitySetController<>
public class BaseODataController<TService, TEntity, TEntityDto, TIdentityType> : EntitySetController<TEntityDto, TIdentityType>
{
//.....
protected override TEntityDto UpdateEntity(TIdentityType key, TEntityDto update)
{
// is not getting called
_service.Update(update);
return base.UpdateEntity(key, update);
}
protected override TEntityDto PatchEntity(TIdentityType key, Delta<TEntityDto> patch)
{
// gets called
return base.PatchEntity(key, patch);
}
//.....
}
Here is the code that gets called on the clientside:
vm.updateRole = function(r) {
return $data.initService('/odata/$metadata').then(function (context) {
r.Name = "NewUpdateRole";
context.Role.update(r);
r.entityState = $data.EntityState.Modified;
context.saveChanges().then(function(result) {
debugger;
});
});
Am I missing something here?
JayData sends the MERGE or PATCH requests based on the dataServiceVersion property of the odata provider configuration.
$data.initService('/odata/$metadata', {dataServiceVersion: '3.0'})
2.0 causes MERGE and 3.0 causes PATCH requests to align with the WCF Data Services OData implementation.
If this behavior does not meet the WebAPI OData requirements, there is a second customization option to determine the kind of the request:
$data.initService('/odata/$metadata', {UpdateMethod: 'PATCH'})
You can make a try to modify the PATCH to UPDATE HTTP Verb

Restricting auto Help Page contents when using Attribute Routing in Web API 2

I'm currently implementing a Web API using Web API 2's attribute routing (http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2). I am also using the Help Pages module in order to automatically generate documentation from XML comments (http://www.asp.net/web-api/overview/creating-web-apis/creating-api-help-pages).
For this API I am providing support for optional return format extensions, so that every API method has a pair of routes defined on it like so:
[HttpGet]
[Route("Path/Foo")]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFoo()
{
// Some API function.
}
This allows a user to hit any of these and get a result:
www.example.com/api/Controller/Path/Foo
www.example.com/api/Controller/Path/Foo.json
www.example.com/api/Controller/Path/Foo.xml
My issue is that when Help Pages uses MapHttpAttributeRoutes() to generate documentation, it is picking up both routes for each method. So right now I see help for:
api/Controller/Foo
api/Controller/Foo.{ext}
But I want to only see:
api/Controller/Foo.{ext}
I would prefer to hide the non-extension route on each method, so that every method only shows a single Help Page entry.
Has anyone else tried something similar? Is there a work around that I am missing?
My question would be is that, would consumers of your api figure out easily that the {ext} is optional?...personally, I would prefer the default behavior...but anyways following are some workarounds that I can think of:
A quick and dirty workaround. Split the DoFoo into 2 actions like DoFoo() and DoFooWithExt maybe. Notice that I am using an attribute called ApiExplorerSettings, which is for HelpPage purposes. Example below:
[HttpGet]
[Route("Path/Foo")]
[ApiExplorerSettings(IgnoreApi=true)]
public HttpResponseMessage DoFoo()
{
return DoFooHelper();
}
[HttpGet]
[Route("Path/Foo.{ext}")]
public HttpResponseMessage DoFooWithExt()
{
return DoFooHelper();
}
private HttpResponseMessage DoFooHelper()
{
//do something
}
Create a custom ApiExplorer (which HelpPage feature uses internally) and check for specific routes like the following and can decide whether to show the action or not for that particular route.
// update the config with this custom implementation
config.Services.Replace(typeof(IApiExplorer), new CustomApiExplorer(config));
public class CustomApiExplorer : ApiExplorer
{
public CustomApiExplorer(HttpConfiguration config) : base(config)
{
}
public override bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route)
{
if (route.RouteTemplate.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))
{
return false;
}
return base.ShouldExploreAction(actionVariableValue, actionDescriptor, route);
}
}
Get list of all ApiDescription from the default ApiExplorer and then filter out the descriptions which you do not like. Example:
Configuration.Services.GetApiExplorer().ApiDescriptions.Where((apiDesc) => !apiDesc.RelativePath.EndsWith("Path/Foo", StringComparison.OrdinalIgnoreCase))

HttpRoutes - how do they work?

I´m struggling with URLs for ajax-reader/JSON. Each time I think I understand it, it seems that I haven´t.
Please, can anybody explain the logic behind this???
I got this Controller:
public class ServiceController : DnnApiController
{
[AllowAnonymous]
[HttpGet]
public HttpResponseMessage GetAllItems(int moduleId)
{
MyProjectController controller = new MyProjectController();
IEnumerable<ItemInfo> items = controller.GetAllItems(moduleId);
return Request.CreateResponse(HttpStatusCode.OK, items);
}
}
I got this Routemapper:
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("MyProject",
"default",
"{controller}/{action}",
new[] { "MyCompany.MyProject.Services" });
}
}
At what URL can I read the data with $.ajax() and what is the URL showing me the data in a browser?
Thanx in Advance!
Asle :)
This is how I do it (Note: this will only work with DNN6.2 and above);
In the View.ascx.cs add
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
ServicesFramework.Instance.RequestAjaxScriptSupport();
ServicesFramework.Instance.RequestAjaxAntiForgerySupport();
jQuery.RequestDnnPluginsRegistration();
}
This ensures that jquery and the required DNN ajax plugins are added.
Initiate the services framework jquery plugin in the View.ascx like this inside javascript script tags (S.O. wouldn't allow me to include them)
var modId = <%=ModuleId %>;
var sf = $.ServicesFramework(modId);
Now in a separate javascript file or in the view.ascx control add the ajax function
function getAllItems(){
$.ajax({
type:"GET",
url:sf.getServiceRoot("MyProject")+"Service/GetAllItems",
beforeSend:sf.setModuleHeaders,
data:{moduleId:modId},
cache:false
}).done(function(data){
alert("Success!");
}).fail(function(){
alert("Crashed!");
}).always(function(){
//something you want done whether passed or failed
//like hide progress bar, ajax spinner etc.
});
}
The DNN jquery plugin will build the url which will look similar to this (Note: 142 is just for illustration purpose and will be replace with actual module id)
/DesktopModules/MyProject/API/Service/GetAllItems?moduleId=142
The URL will be something like
/desktopmodules/SlidePresentation/API/SlidePresetnation.ashx/ListOfSlides
I have examples at
https://slidepresentation.codeplex.com/SourceControl/latest
but they were for DNN6, they might require a few updates due to the API changes for DNN 7
you can see a DNN7 module that has a service layer at https://dnnsimplearticle.codeplex.com/SourceControl/latest#cs/services/

Asp.Net MVC : Execution of the child request failed. Please examine the InnerException for more information

I'm recieving the following error message,
A public action method 'RenderMenu'
was not found on controller
'Web.Controllers.SiteController'.
However this action DOES exist and the controller does exist (As it work everywhere on the site) I looked at the inner exception.
Execution of the child request failed.
Please examine the InnerException for
more information.
(This is the inner exception...)
Stack Trace
at
System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.Wrap[TResult](Func`1
func) at
System.Web.HttpServerUtility.ExecuteInternal(IHttpHandler
handler, TextWriter writer, Boolean
preserveForm, Boolean setPreviousPage,
VirtualPath path, VirtualPath
filePath, String physPath, Exception
error, String queryStringOverride)
Now, we have a website set-up with a dynamic menu system so we are using RenderAction() on a generic controller to build this menu system up.
<% Html.RenderAction("RenderMenu", "Site"); %>
This call is made from the MasterPage and it works fine until there was a validation error like so,
[HttpPost]
public ActionResult Register(UserModel UserToAdd)
{
if(!ModelState.IsValid)
{
return View(UserToAdd);
}
//Run some validation
if (_UserService.DoesEmailExist(UserToAdd.EMail))
{
TempData["error"] = "Email Address Already in use!";
return View(UserToAdd);
}
//Add the user
TempData["info"] = "User Added - " + UserO.ID;
return View("Success");
}
It works fine when there this is a new user, but if someone enters an email that already exist we get the above error. THis RenderAction Method works all over the site (This is the first form we have added)
Any suggestions?
Fixed:
The RenderAction() Method is below
[HttpGet]
public ActionResult RenderMenu()
{
//Do Stuff
}
Removing the HttpGet Attribute has resolved the issue.
public ActionResult RenderMenu()
{
//Do Stuff
}
Would love to know why?
This is because your parent request is an [HttpPost], and the child request operates in the same verb as the parent. If your method is marked as [HttpGet], it will not respond to [HttpPost] requests. Hitting the action directly through your browser works because that is a GET. Hitting the action as a child action in the context of a POST will not work.

Resources