I have an application developed in ASP.NET Core MVC with a set of controllers for normal view responses and Web API.
I am trying to figure a correct way to wrap all Web API responses with a consistent class.
My first question is what would be a correct approach to wrap the responses coming from Web API controllers. Since I have two controller types, I would need to distinguish between them as the responses should only be wrapped for API controller, and not view controllers.
As I understand there are two choices a middleware or an action filter.
At first I thought the middleware would be the best choice, but then I realized that I would still need to figure out what kind of request is being processed which would add more potential headache with maintenance?
Then I looked at an action filter and it seems that it would be a better choice to plugin the wrapping handling.
For example an action filter can be added to a base controller just for Web API and not controllers handling the views.
So the question is whether the action filters are best approach to achieve this?
I would recommend you to look at result filters for this. Result filters run after a controller action has produced a result, and it allows you to look at that result and then perform some action.
For example, when you return View in a controller action, then what gets returned is a ViewResult which makes it easy to identify results that would cause a Razor view to be rendered.
Within an API controller, you would usually return a ActionResult<T>, some model object directly, or an ObjectResult. The former two will be automatically converted into an ObjectResult as the action gets executed. So you can just look for ObjectResults and then change the result to have some wrapper object instead. This would look something like this:
public class WrapApiResponseResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result;
if (result is ObjectResult)
{
// wrap the inner object
var newValue = new WrapperObject(result.Value);
// replace the result
context.Result = new ObjectResult(newValue)
{
// copy the status code
StatusCode = result.StatusCode,
};
}
}
public void OnResultExecuted(ResultExecutedContext context)
{ }
}
Related
I am using the DevExtreme ASP.NET Data helpers
https://github.com/DevExpress/DevExtreme.AspNet.Data/blob/master/net/Sample/DataSourceLoadOptions.cs to easily show some data in front end grid from an ASP.NET Core 3.1 WebAPI.
Basically, the library offers a bunch of helpers for interpreting the data from the DevExtreme dxDataGrid
requests when it works in server-side / remote mode (ie. see remoteOperations) with Entity Framework through the DataSourceLoader class.
It works pretty well with the requests made by the dxDataGrid itself, except that there is one use-case that bothers me a bit when I expose the endpoint through a Swagger and a random user sends the wrong request model (aka DataSourceLoadOptions), that is that most fields inherited from the parent class DataSourceLoadOptionsBase are not filled properly which like shown in the code below will result in an exception... exception which will then trigger a 500 HTTP code return from the API resource.
It basically happens when DataSourceLoadOptionsParser involved in the binding process failed to deserialize properly one of the request model properties.
[ModelBinder(BinderType = typeof(DataSourceLoadOptionsBinder))]
public class DataSourceLoadOptions : DataSourceLoadOptionsBase
{
}
public class DataSourceLoadOptionsBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var loadOptions = new DataSourceLoadOptions();
DataSourceLoadOptionsParser.Parse(loadOptions, key => bindingContext.ValueProvider.GetValue(key).FirstOrDefault());
bindingContext.Result = ModelBindingResult.Success(loadOptions);
return Task.CompletedTask;
}
}
Instead of a 500 error, I would rather like to return a 400 error indicating what field(s) is/are not suitable to perform that particular request.
But I am not too sure how I can achieve that, since the error prevents the ASP.NET Core binding process to go further (Custom Model Binding in ASP.NET Core).
I am using .Net Core 2.2 as my underlying framework here, so this information might not be inkeeping with how things are currently done (if so please, I welcome the feedback on how to put this right).
I have an Api App that initializes multiple OData contexts that happen to have some crossover in entity set names and thus controller names.
In this case I have a "Core" OData context + model and a "Members" context + model.
the net result is that both of these OData models contains a Users entity set with controllers looking like this ...
[ODataRoutePrefix("Members/User")]
public class UserController : MembersEntityODataController<Members.User> { }
[ODataRoutePrefix("Core/User")]
public class UserController : CoreEntityODataController<Core.User> { }
... they essentially do the same job but refer to entities stored in the same table but in different databases.
I can't seem to for the life of me figure out how to initialize / declare these controllers such that the routing actually works and instead all requests for both ~/Core/User and ~/Members/User result in a members user controller being passed on to handle the request.
My understanding is that this is the exact scenario that the ODataRoutePrefix attribute was built to solve (amongst others) and it doesn't seem to help here.
to make the process of adding new OData models to my API easier I wrapped up the model construction in my own model building but the net result is a core call to use odata during startup which looks like this ...
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.MapRoute("default", "{controller=Home}/{action=Get}");
});
var builders = new ODataModelBuilder[] {
new MembersModelBuilder(),
new CoreModelBuilder(),
new B2BModelBuilder(),
new SearchModelBuilder()
};
foreach (var builder in builders)
{
var model = builder.Build();
app.UseOData(model.Context + "Api", builder.GetType().Name.Replace("ModelBuilder", ""), model.EDMModel);
}
Do I need to do something special here to make routing work as intended?
It does seem that this "type of scenario" has been considered before as I'm seeing things like this ...
https://github.com/OData/WebApi/issues/1494
... which talks about API versions ... my case isn't quite this, but it's close enough that I figure the same parts of the framework logic should apply (somewhat).
#xuzhg talks about the solution being to apply the ODataRoute Attribute on the Actions ...
[ODataRoute("Sensors", RouteName = "ODataRouteV1")]
public IActionResult Get()
[ODataRoute("Sensors", RouteName = "ODataRouteV2")]
public IActionResult Get()
... I need to do presumably be able to do the same thing at the controller level but alas this attribute cannot be used on classes only methods.
Obviously i'm sourcing my understanding from this ...
https://learn.microsoft.com/en-us/odata/webapi/attribute-routing
... which talks about using the ODataRoutePrefix attribute to apply context to the routing engine for when a controller should be selected.
Have I hit an edge case here ?
I am making an ASP.net core 2.0 Web API and have a method in my controller called
[HttpPost("Create")]
public void Create()
{
// var d = employee;
}
Does the HttpPost act as the same the [Route] attribute or do I need both?
For Asp.Net Core Web API you do not need both.
It acts the same as Route and is the advised approach. When using MVC with views you use Route and Http{Verb} together.
Documentation states...
When building a REST API, it's rare that you will want to use [Route(...)] on an action method. It's better to use the more specific Http*Verb*Attributes to be precise about what your API supports. Clients of REST APIs are expected to know what paths and HTTP verbs map to specific logical operations.
Reference Routing to Controller Actions in ASP.NET Core
I would also advise having your actions return IActionResult to allow the proper HTTP Verb to be returned from the action.
[HttpPost("Create")]
public IActionResult Create() {
// var d = employee;
return Ok();
}
void actions always return 200 OK unless an exception is thrown. This limits the possible response from the action.
I want to be able to take over the 404 response from web api (iis) when the resource does not exist.
I have done my research and came across only one solution that made this work, but i am not sure how "safe" it is since the "routeTemplate" is just {*url}
This post is kinda to ask for help and explanation.
My App uses MVC and WebAPI... would this template affect MVC as well?
Is there a way to add "api" with {*url} in the template? (to make sure only requests with ".../api/..." get affected)
config.Routes.MapHttpRoute("Error404", "{*url}", new { controller = "Error", action = "Handle404" });
Has anyone come across a cleaner way of doing this and handling 404 in web api?
EDIT 1
The above code DOES affect my MVC routes.
How can i add "api" to "{*url}"?... if tried many different ways and no dice.
Had the exact same issue. After some research and trial and error, I was able to find a working solution.
I went with a RoutePrefix solution and I tried to implement something similar to how the MVC controllers used HandleUnknownAction in a base controller.
With the help from this post: .NET WebAPI Attribute Routing and inheritance to allow for route inheritance, I created a base controller for my web APIs with a HandleUnknownAction method like this:
public abstract class WebApiControllerBase : ApiController {
[Route("{*actionName}")]
[AcceptVerbs("GET", "POST", "PUT", "DELETE")]//Include what ever methods you want to handle
[AllowAnonymous]//So I can use it on authenticated controllers
[ApiExplorerSettings(IgnoreApi = true)]//To hide this method from helpers
public virtual HttpResponseMessage HandleUnknownAction(string actionName) {
var status = HttpStatusCode.NotFound;
//This is custom code to create my response content
//....
var message = status.ToString().FormatCamelCase();
var content = DependencyService
.Get<IResponseEnvelopeFactory>()
.CreateWithOnlyMetadata(status, message);
//....
return Request.CreateResponse(status, content);
}
}
If you don't want to go down the inheritance path, you can always put the method directly into the controller you want to apply the functionality on.
This allows me to use route prefixes that handle custom not found messages that pertain to specific controllers as I have both back-office and public public facing APIs.
If a URL does not apply to an ApiController the default Error controller will handle the not found as usual.
I currently have a MVC 3 Web Application with around 50 Views.
Is it somehow possible to provide some kind of API next to my Views so everything still works fine and dandy?
My kinda perfect world idea:
Server is running and every request gets to its view. The user can decide in his get request if he wants the raw data in lets say JSON. Additional to the raw data also the Structure/Names of the Viewmodel will be parsed so the Modelbinder can do his stuff.
or another approach might be to somehow deactivate my Views to have only the API available (again with raw data and structure).
Is this even possible (the modelbinder is kinda a big concern) and maybe even without much effort/trouble to do?
If you don't want to do everything all over again with WebAPI, you can implement some ActionFilterAttribute to change the ActionResult based on some querystring f.i.
Something like this:
public class ResultSwitcherAttribute: ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Request["data"] != null && filterContext.HttpContext.Request["data"] == "json")
{
filterContext.Result = new JsonResult
{
Data = (filterContext.Result as ViewResult).Model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
Then, you'll register that either in application_start or per controller/action like this:
[ResultSwitcherAttribute]
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new TestModel()
{
Web = "http://www.mywebpage.com", Name = "Yngve"
});
}
}
When you now access your url with ?data=json you'll get a JSON-representation of the Model, instead of the view.
EDIT: Code sample updated
Consider adding an ASP.NET Web API to your app. It's extremely simple to implement, entailing little more than adding an API controller with methods corresponding to your REST actions. I don't know how one would go about adding this to an MVC 3 app, but I have a recipe for MVC 4 Internet Applications in this stackoverflow question. So, if you're fine with upgrading your project so that it is Web API capable, perhaps to MVC 4, I think it'd represent a good alternative.
To learn ASP.NET Web API, this tutorial is a good place to start.