Quantcast
Channel: ASP.NET MVC / Web API / Web Pages
Viewing all 7925 articles
Browse latest View live

Updated Wiki: Attribute routing in MVC

$
0
0
WORK IN PROGRESS

Attribute routing in ASP.NET MVC

One of the limitations of ASP.NET MVC's routing system is that it requires you to configure routes on a global route table. This has several consequences:
  • It forces you to encode action-specific information like parameter names in a global route table.
  • Routes registered globally can conflict with other routes and end up matching actions they aren't supposed to match.
  • The information about what URI to use to call into a controller is kept in a completely different file from the controller itself. A developer has to look at both the controller and the global route table in configuration to understand how to call into the controller.
An attribute-based approach solves all these problems by allowing you to configure how an action would be accessed right on the action itself. For most cases, this should improve usability and make Web applications simpler to build and maintain.

Usage

  1. Annotate the action with one of our HTTP verb attributes (HttpGet/HttpPost/HttpPut/HttpDelete/HttpPatch/HttpHead/HttpOptions/HttpRoute), passing in the route template in the constructor.
  2. Call the RouteCollection.MapMvcAttributeRoutes() extension method when configuring routes.
This call will use the default controller factory to locate all available actions and controllers, and retrieve route-defining attributes. It will then use these attributes to create routes and add these to the server's route collection.

This design allows attribute-based routing to compose well with the existing routing system since you can call MapMvcAttributeRoutes and still define regular routes using MapRoute. Here's an example:
routes.MapMvcAttributeRoutes();
routes.MapRoute(
  name: "Default",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
In most cases, MapMvcAttributeRoutes will be called first so that attribute routes are registered before the global routes (and therefore get a chance to supersede global routes).

Defining simple routes

publicclass HomeController : Controller
{
    [HttpGet("")]
    public ActionResult Index() { ... }

    [HttpGet("about")]
    public ActionResult About() { ... }

    [HttpGet("contact-us")]
    public ActionResult ContactUs() { ... }
}

Using route parameters

Route parameters can be specified within the route template.
publicclass GreetingsController : Controller
{
    [HttpGet("say/hello/to/{name}")]
    public ActionResult SayHelloTo(string Name) { ... }
}

Optional parameters and default values

You can specify that a parameter is optional by adding a question mark to the parameter, that is:
[HttpGet("countries/{name?}")]
public ActionResult GetCountry(string name = "USA") { }
Currently, a default value must be specified on the optional parameter for action selection to succeed, but we can investigate lifting that restriction. (Please let us know if this is important.)

Default values can be specified in a similar way:
[HttpGet("countries/{name=USA}")]
public ActionResult GetCountry(string name) { }
The optional parameter '?' and the default values must appear after inline constraints in the parameter definition.

Defining constraints over route parameters

Route constraints can be applied to particular parameters in the route template itself. Here's an example:
[HttpGet("people/{id:int}")]
public ActionResult Get(int id) { }
This action will only match if id in the route can be converted to an integer. This allows other routes to get selected in more general cases.

The following default inline constraints are defined:


Constraint Key Description Example
bool Matches a Boolean value {x:bool}
datetime Matches a DateTime value {x:datetime}
decimal Matches a Decimal value {x:decimal}
double Matches a 64-bit floating point value {x:double}
float Matches a 32-bit floating point value {x:float}
guid Matches a GUID value {x:guid}
int Matches a 32-bit integer value {x:int}
long Matches a 64-bit integer value {x:long}
minlength Matches a string with the specified minimum length {x:minlength(4)}
maxlength Matches a string with the specified maximum length {x:maxlength(8)}
length Matches a string with the specified length or within the specified range of lengths {x:length(6)}, {x:length(4,8)}
min Matches an integer with the specified minimum value {x:min(100)}
max Matches an integer with the specified maximum value {x:max(200)}
range Matches an integer within the specified range of values {x:range(100,200)}
alpha Matches English uppercase or lowercase alphabet characters {x:alpha}
regex Matches the specified regular expression {x:regex(^\d{3}-\d{3}-\d{4}$)}

Inline constraints can have arguments specified in parentheses - this is used by several of the built-in constraints. Inline constraints can also be chained with a colon used as a separator like this:
[HttpGet("people/{id:int:min(0)}")]
public ActionResult GetPerson(int id) { }
Inline constraints must appear before the optional parameter '?' and default values

The constraint resolution is extensible. See below for details.

Overloading

publicclass UsersController : Controller
{
    [HttpGet("show-user({id:int})", RouteName = "GetUserById")]
    public ActionResult Show(int id) { ... }

    [HttpGet("show-user({username})", RouteName = "GetUserByUsername")]
    public ActionResult Show(string username) { ... }
}

Specifying other (or no) HTTP methods on a route

publicclass UsersController : Controller
{
    [HttpPost("new-user")]
    public ActionResult NewUser(string name, int age) { ... }

    [HttpRoute("any-method-would-work")]
    public ActionResult WorksWithAnyMethod() { ... }
}

Specifying multiple ways to access an action

publicclass ProductsController : Controller
{
    [HttpGet("/p-{productId}")]
    [HttpGet("/p/{title}/{productId}")]
    public ActionResult NewUser(string productId, string title) { ... }
}

The experience for getting started with attribute-based routing will look something like this:

Optional parameters and default values

You can specify that a parameter is optional by adding a question mark to the parameter, that is:
[HttpGet("countries/{name?}")]
public Country GetCountry(string name = "USA") { }
Currently, a default value must be specified on the optional parameter for action selection to succeed, but we can investigate lifting that restriction. (Please let us know if this is important.)

Default values can be specified in a similar way:
[HttpGet("countries/{name=USA}")]
public Country GetCountry(string name) { }
The optional parameter '?' and the default values must appear after inline constraints in the parameter definition.

Inline Constraints

Route constraints can be applied to particular parameters in the route template itself. Here's an example:
[HttpGet("people/{id:int}")]
public Person Get(int id) { }
This action will only match if id in the route can be converted to an integer. This allows other routes to get selected in more general cases.

The following default inline constraints are defined:

Constraint Key Description Example
bool Matches a Boolean value {x:bool}
datetime Matches a DateTime value {x:datetime}
decimal Matches a Decimal value {x:decimal}
double Matches a 64-bit floating point value {x:double}
float Matches a 32-bit floating point value {x:float}
guid Matches a GUID value {x:guid}
int Matches a 32-bit integer value {x:int}
long Matches a 64-bit integer value {x:long}
minlength Matches a string with the specified minimum length {x:minlength(4)}
maxlength Matches a string with the specified maximum length {x:maxlength(8)}
length Matches a string with the specified length or within the specified range of lengths {x:length(6)}, {x:length(4,8)}
min Matches an integer with the specified minimum value {x:min(100)}
max Matches an integer with the specified maximum value {x:max(200)}
range Matches an integer within the specified range of values {x:range(100,200)}
alpha Matches English uppercase or lowercase alphabet characters {x:alpha}
regex Matches the specified regular expression {x:regex(^\d{3}-\d{3}-\d{4}$)}

Inline constraints can have arguments specified in parentheses - this is used by several of the built-in constraints. Inline constraints can also be chained with a colon used as a separator like this:
[HttpGet("people/{id:int:min(0)}")]
public Person Get(int id) { }
Inline constraints must appear before the optional parameter '?' and default values, and the constraint resolution is extensible. See below for details.

Route prefixes

Frequently you’ll want to specify a common prefix for an entire controller. For example:

publicclass CustomersController : ApiController
{
    [HttpGet("customers")]
    public IEnumerable<Customer> Get() { }
    [HttpGet("customers/{id}")]
    public Customer Get(int id) { }
    [HttpPost("customers")]
    publicvoid Post(Customer customer) { }
}

The [RoutePrefix] attribute lets you define a common prefix for an entire controller. So the previous controller definition is simplified as:

[RoutePrefix("customers")]
publicclass CustomersController : ApiController
{
    public IEnumerable<Customer> Get() { }
    [HttpGet("{id}")]
    public Customer Get(int id) { }
    publicvoid Post(Customer customer) { }
}

When MapHttpAttributeRoutes gets called, it goes through all the prefixes and adds a route for every action. If the action has no route provider attribute, the route prefix itself gets added. If the action does have a route provider attribute, the two templates get concatenated. Multiple route prefixes will each register their own routes for each action. So if you have two route prefixes and three actions on a controller, you would have six routes generated.

Optional parameters, default values, and inline constraints can all be applied to route prefixes as well.

Naming

Web API's routing system requires every route to have a distinct name. Route names are useful for generating links by allowing you to identify the route you want to use. You can choose to define the route name right on the attribute itself:
[HttpGet("customers/{id}", RouteName = "GetCustomerById")]

In the absence of a specified route name, Web API will generate a default route name. If there is only one attribute route for the action name on a particular controller, the route name will take the form "ControllerName.ActionName". If there are multiple attributes with the same action name on that controller, a suffix gets added to differentiate between the routes: "Customer.Get1", "Customer.Get2".

Ordering

There is an Order property on the [RoutePrefix] attribute and a RouteOrder on the HTTP verb attributes that allows you to customize the order in which the routes are evaluated. The default order is 0, and routes with a smaller order get evaluated first. Negative numbers can be used to evaluate before the default and positive numbers can be used to evaluate after the default. In addition, a default ordering is used to order routes that don't have an order specified.

Here's how the ordering works:
  1. Compare routes by prefix order. If a prefix order is smaller, it goes earlier into the route collection. If the prefix order is the same, keep going.
  2. Compare routes by the RouteOrder on the HTTP verb attribute. If an order is smaller, it goes earlier into the route collection. If the order is the same, keep going.
  3. Go through the parsed route segment by segment. Apply the following ordering for determining which route goes first:
    1. Literal segments
    2. Constrained parameter segments
    3. Unconstrained parameter segments
    4. Constrained wildcard parameter segments
    5. Unconstrained wildcard parameter segments
  4. If no ordering can be determined up to this point, use an OrdinalIgnoreCase string comparison of the two route templates to ensure that the ordering is stable and won't change if the order of the actions and attributes changes.

Extensibility

There are two main extensibility points that are built in - the HttpRouteBuilder and the IInlineConstraintResolver interface. HttpRouteBuilder is the class that takes a tokenized route template and creates an IHttpRoute for it. It exposes the following virtuals:
publicclass HttpRouteBuilder
{
    publicvirtual IHttpRoute BuildHttpRoute(string routeTemplate, IEnumerable<HttpMethod> httpMethods, string controllerName, string actionName);
    publicvirtual IHttpRoute BuildHttpRoute(HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, string routeTemplate);
}
You can pass in a custom HttpRouteBuilder to the MapHttpAttributeRoutes method:
config.MapHttpAttributeRoutes(new MyRouteBuilder());
Extending the route builder allows you to add or remove constraints, add a per-route message handler, and return a custom instance of IHttpRoute among other things.

The other extensibility point is IInlineConstraintResolver. This interface is responsible for taking an inline constraint and manufacturing an IHttpRouteConstraint for that parameter:
publicinterface IInlineConstraintResolver
{
    IHttpRouteConstraint ResolveConstraint(string inlineConstraint);
}
The inline constraint resolver is an argument to HttpRouteBuilder's constructor, so you could call the following:
config.MapHttpAttributeRoutes(new HttpRouteBuilder(new MyConstraintResolver()));

The default constraint resolver uses a dictionary based approach of mapping constraint keys to a particular constraint type. It contains logic to create an instance of the type based on the constraint arguments. It exposes the dictionary publicly so that you can add custom constraints without having to implement IInlineConstraintResolver. Here's an example:
DefaultInlineConstraintResolver constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("phonenumber", typeof(PhoneNumberRouteConstraint));
config.MapHttpAttributeRoutes(new HttpRouteBuilder(constraintResolver));

Updated Wiki: Attribute routing in MVC

$
0
0
WORK IN PROGRESS

Attribute routing in ASP.NET MVC

One of the limitations of ASP.NET MVC's routing system is that it requires you to configure routes on a global route table. This has several consequences:
  • It forces you to encode action-specific information like parameter names in a global route table.
  • Routes registered globally can conflict with other routes and end up matching actions they aren't supposed to match.
  • The information about what URI to use to call into a controller is kept in a completely different file from the controller itself. A developer has to look at both the controller and the global route table in configuration to understand how to call into the controller.
An attribute-based approach solves all these problems by allowing you to configure how an action would be accessed right on the action itself. For most cases, this should improve usability and make Web applications simpler to build and maintain.

Usage

  1. Annotate the action with one of our HTTP verb attributes (HttpGet/HttpPost/HttpPut/HttpDelete/HttpPatch/HttpHead/HttpOptions/HttpRoute), passing in the route template in the constructor.
  2. Call the RouteCollection.MapMvcAttributeRoutes() extension method when configuring routes.
This call will use the default controller factory to locate all available actions and controllers, and retrieve route-defining attributes. It will then use these attributes to create routes and add these to the server's route collection.

This design allows attribute-based routing to compose well with the existing routing system since you can call MapMvcAttributeRoutes and still define regular routes using MapRoute. Here's an example:
routes.MapMvcAttributeRoutes();
routes.MapRoute(
  name: "Default",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
In most cases, MapMvcAttributeRoutes will be called first so that attribute routes are registered before the global routes (and therefore get a chance to supersede global routes).

Defining simple routes

publicclass HomeController : Controller
{
    [HttpGet("")]
    public ActionResult Index() { ... }

    [HttpGet("about")]
    public ActionResult About() { ... }

    [HttpGet("contact-us")]
    public ActionResult ContactUs() { ... }
}

Using route parameters

Route parameters can be specified within the route template.
publicclass GreetingsController : Controller
{
    [HttpGet("say/hello/to/{name}")]
    public ActionResult SayHelloTo(string Name) { ... }
}

Optional parameters and default values

You can specify that a parameter is optional by adding a question mark to the parameter, that is:
[HttpGet("countries/{name?}")]
public ActionResult GetCountry(string name = "USA") { }
Currently, a default value must be specified on the optional parameter for action selection to succeed, but we can investigate lifting that restriction. (Please let us know if this is important.)

Default values can be specified in a similar way:
[HttpGet("countries/{name=USA}")]
public ActionResult GetCountry(string name) { }
The optional parameter '?' and the default values must appear after inline constraints in the parameter definition.

Defining constraints over route parameters

Route constraints can be applied to particular parameters in the route template itself. Here's an example:
[HttpGet("people/{id:int}")]
public ActionResult Get(int id) { }
This action will only match if id in the route can be converted to an integer. This allows other routes to get selected in more general cases.

The following default inline constraints are defined:

Constraint Key Description Example
bool Matches a Boolean value {x:bool}
datetime Matches a DateTime value {x:datetime}
decimal Matches a Decimal value {x:decimal}
double Matches a 64-bit floating point value {x:double}
float Matches a 32-bit floating point value {x:float}
guid Matches a GUID value {x:guid}
int Matches a 32-bit integer value {x:int}
long Matches a 64-bit integer value {x:long}
minlength Matches a string with the specified minimum length {x:minlength(4)}
maxlength Matches a string with the specified maximum length {x:maxlength(8)}
length Matches a string with the specified length or within the specified range of lengths {x:length(6)}, {x:length(4,8)}
min Matches an integer with the specified minimum value {x:min(100)}
max Matches an integer with the specified maximum value {x:max(200)}
range Matches an integer within the specified range of values {x:range(100,200)}
alpha Matches English uppercase or lowercase alphabet characters {x:alpha}
regex Matches the specified regular expression {x:regex(^\d{3}-\d{3}-\d{4}$)}

Inline constraints can have arguments specified in parentheses - this is used by several of the built-in constraints. Inline constraints can also be chained with a colon used as a separator like this:
[HttpGet("people/{id:int:min(0)}")]
public ActionResult GetPerson(int id) { }
Inline constraints must appear before the optional parameter '?' and default values

The constraint resolution is extensible. See below for details.

Overloading

publicclass UsersController : Controller
{
    [HttpGet("show-user({id:int})", RouteName = "GetUserById")]
    public ActionResult Show(int id) { ... }

    [HttpGet("show-user({username})", RouteName = "GetUserByUsername")]
    public ActionResult Show(string username) { ... }
}

Specifying other (or no) HTTP methods on a route

publicclass UsersController : Controller
{
    [HttpPost("new-user")]
    public ActionResult NewUser(string name, int age) { ... }

    [HttpRoute("any-method-would-work")]
    public ActionResult WorksWithAnyMethod() { ... }
}

Specifying multiple ways to access an action

publicclass ProductsController : Controller
{
    [HttpGet("/p-{productId}")]
    [HttpGet("/p/{title}/{productId}")]
    public ActionResult NewUser(string productId, string title) { ... }
}

The experience for getting started with attribute-based routing will look something like this:

Route prefixes

It is possible to prefix all routes within a given controller. For example:
[RoutePrefix("users")]
publicclass UsersController : Controller
{
    [HttpGet("")]
    public AsyncResult List() { ... }

    [HttpGet("name/{name}/id/{id}")]
    public AsyncResult Get(int id, string name) { ... }
}
This will generate the following two routes:
  1. users
  2. users/name/{name}/id/{id}

Multiple RoutePrefix attributes can be specified, resulting with the Cartesian product of prefixes and routes.

For example:
[RoutePrefix("profiles")]
[RoutePrefix("users")]
publicclass UsersController : Controller
{
    [HttpGet("")]
    public AsyncResult List() { ... }

    [HttpGet("name/{name}/id/{id}")]
    public AsyncResult Get(int id, string name) { ... }
}
This will generate the following two routes:
  1. users
  2. users/name/{name}/id/{id}
  3. profiles
  4. profiles/name/{name}/id/{id}

Named routes

Route names are useful for generating links by allowing you to identify the route you want to use. You can choose to define the route name right on the attribute itself:
[HttpGet("customers/{id}", RouteName = "GetCustomerById")]

In the absence of a specified route name, MVC will generate a default route name, based on the given template and HTTP method.
For example, in the case of HttpGet("customer/{id}"), the generated name would be "Get_customer/{id}".
Since there should never be two routes with the exact same template and HTTP method specification, name collisions would not normally occur. In the case they do, it is always possible to explicitly set the RouteName on a any route.

Ordering

There is an Order property on the [RoutePrefix] attribute and a RouteOrder on the HTTP verb attributes that allows you to customize the order in which the routes are evaluated. The default order is 0, and routes with a smaller order get evaluated first. Negative numbers can be used to evaluate before the default and positive numbers can be used to evaluate after the default. In addition, a default ordering is used to order routes that don't have an order specified.

Here's how the ordering works:
  1. Compare routes by prefix order. If a prefix order is smaller, it goes earlier into the route collection. If the prefix order is the same, keep going.
  2. Compare routes by the RouteOrder on the HTTP verb attribute. If an order is smaller, it goes earlier into the route collection. If the order is the same, keep going.
  3. Go through the parsed route segment by segment. Apply the following ordering for determining which route goes first:
    1. Literal segments
    2. Constrained parameter segments
    3. Unconstrained parameter segments
    4. Constrained wildcard parameter segments
    5. Unconstrained wildcard parameter segments
  4. If no ordering can be determined up to this point, use an OrdinalIgnoreCase string comparison of the two route templates to ensure that the ordering is stable and won't change if the order of the actions and attributes changes.

Extensibility

There are two main extensibility points that are built in - the RouteBuilder and the IInlineConstraintResolver interface. RouteBuilder is the class that takes a tokenized route template and creates a DirectRoute for it. It exposes the following virtuals:
publicclass HttpRouteBuilder
{
    publicvirtual IHttpRoute BuildHttpRoute(string routeTemplate, IEnumerable<HttpMethod> httpMethods, string controllerName, string actionName);
    publicvirtual IHttpRoute BuildHttpRoute(HttpRouteValueDictionary defaults, HttpRouteValueDictionary constraints, string routeTemplate);
}
You can pass in a custom RouteBuilder to the MapMvcAttributeRoutes method:
config.MapHttpAttributeRoutes(new MyRouteBuilder());
Extending the route builder allows you to add or remove constraints, add a per-route message handler, and return a custom instance of IHttpRoute among other things.

The other extensibility point is IInlineConstraintResolver. This interface is responsible for taking an inline constraint and manufacturing an IHttpRouteConstraint for that parameter:
publicinterface IInlineConstraintResolver
{
    IHttpRouteConstraint ResolveConstraint(string inlineConstraint);
}
The inline constraint resolver is an argument to HttpRouteBuilder's constructor, so you could call the following:
config.MapHttpAttributeRoutes(new HttpRouteBuilder(new MyConstraintResolver()));

The default constraint resolver uses a dictionary based approach of mapping constraint keys to a particular constraint type. It contains logic to create an instance of the type based on the constraint arguments. It exposes the dictionary publicly so that you can add custom constraints without having to implement IInlineConstraintResolver. Here's an example:
DefaultInlineConstraintResolver constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("phonenumber", typeof(PhoneNumberRouteConstraint));
config.MapHttpAttributeRoutes(new HttpRouteBuilder(constraintResolver));

Source code checked in, #bd007387c45bf3f7f1b3b9ced7b476e601190b58

$
0
0
Issues 802 and 1078 - Making EntitySetLinkBuilderAnnotation public so it can be constructed outside of an EntitySetConfiguration - Making EntitySelfLinks public - Creating a base class for Delta<> with ExpectedType and EntityType properties. - Responding to code review feedback, in particular reverting changes to TypeHelper.cs and adding missing periods.

Edited Issue: Support external creation of IEdmModel [802]

$
0
0
Currently for an IEdmModel to be used with the ODataMediaTypeFormatters, it MUST be built using an ODataModelBuilder. This is because the formatters require an annotation on IEdmEntitySet using a class called EntitySetLinkBuilderAnnotation, and this class is internal.

However there are clearly scenarios where people want to create there own IEdmModel. For example from:
- DbContext
- IDataServiceMetadataProvider

I see a layered solution.

First make EntitySetLinkBuilderAnnotation public, and give it a constructor that doesn't require an EntitySetConfiguration (you don't necessarily have one of those).
Second make the Formatter annotate the model if an EntitySetLinkBuilderAnnotation is not found, this way if a service follows conventions (most do) then you don't need to annotate the IEdmModel you build at all.

The first part has already been completed with this [commit](https://aspnetwebstack.codeplex.com/SourceControl/changeset/bd007387c45bf3f7f1b3b9ced7b476e601190b58)


Closed Feature: Expose ExpectedType and ActualType from IDelta [1078]

$
0
0
When looking at a delta internally we know the actual type and expected type, but this code is internal.
It is very useful, and is needed to create a bridge between WCF Data Services and Web API and is locked away.

I talked to Eilon about this and he was okay with making this change for RTM.
Comments: This has been addressed by this [commit](https://aspnetwebstack.codeplex.com/SourceControl/changeset/bd007387c45bf3f7f1b3b9ced7b476e601190b58) NOTE: the actual solution was to make Delta<> inherit from a new abstract base class called Delta, and put the properties on their (EntityType for actual Type and ExpectedType).

Reviewed: v4.0 RTM (六月 12, 2013)

$
0
0
Rated 4 Stars (out of 5) - this is very good!

New Post: Web API OData 5.0 alpha ETA?

$
0
0
Hi Michael,

Web API's attribute routing is available in the nightly builds that are published to MyGet. We have info on our wiki on how to grab the nightly builds: https://aspnetwebstack.codeplex.com/wikipage?title=Use%20Nightly%20Builds

And there's also the spec page for Web API attribute routing here: https://aspnetwebstack.codeplex.com/wikipage?title=Attribute%20routing%20in%20Web%20API&referringTitle=Specs

When we release an official preview build we will publish that to the regular NuGet.org feed.

Thanks,
Eilon

Closed Issue: Dynamic suffixes for Display Modes and Views location caching [615]

$
0
0
By implementing IDisplayMode you can transform a virtual path pretty much in any way you want. The problem is in Views location caching, which uses the DisplayModeId to cache a location (AppendDisplayModeToCacheKey). This means the transformed path must always be the same and cannot vary depending on the HttpContext.

One use case would be a Display Mode for localization. Based on the user's browser settings, and by setting uiCulture="auto" on Web.config, you could have a display mode that transforms a path like ~/Views/Home/Index.cshtml to ~/Views/Home/Index.es.cshtml. CanHandleContext would always return true.

What I would do is add a new string property to DisplayInfo and use that in the cache key, and fallback to using DisplayModeId if null.
Comments: Hi - We have reviewed this suggestion and we feel that this feature is not well-aligned with Display Modes, which are designed to be static and finite in nature.

Created Unassigned: Exception inside PushStreamContent won't be caught by ExceptionFilterAttribute? [1089]

$
0
0
Hi,

I'm using PushStreamContent in asp.net web api to stream data to the client, I have derived from ExceptionFilterAttribute to capture all the exceptions and return standard HttpError to client. However,
I found that exception thrown inside PushStreamContent won't be caught by ExceptionFilterAttribute, it will return a "text/html" error message to client.
Below is the sample code:

response.Content = new PushStreamContent(
async (outputStream, httpContent, transportContext) =>
{
try
{
// do something and throw exception here

}
catch (Exception ex)
{
throw;
}
finally
{
outputStream.Close();
}
});

New Post: How to remove prefix from ModelState keys?

$
0
0
Is there a way to remove prefixes from ModelState keys? Consider a scenario, where you have an additional validation in your business layer classes, inside which you don't know about ModelState key prefixes.. you just add another model error by property name .AddModelError("Password", "Too strong.") expecting validation reporting logic work as expected down the road, but it won't since some keys will be prefixed and some not.
public object Post(User user)
{
    if (/* userName is taken */)
    {
        ModelState.AddModelError("UserName", "This username already exists.");
    }

    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Also error messages returned to a client shouldn't contain those prefixes! Currently Web Api returns model state errors like this:
{
    "message": "Your request is invalid.",
    "modelState":
    {
        "user.Password": "Your password is too strong."
    }
}
I need them look more like this instead (without ModelState key prefixes, elso replace "modelState" with "errors"):
{
    "message": "Your request is invalid.",
    "errors":
    {
        "password": "Your password is too strong."
    }
}
Ho do I do this? Please advise.

Commented Unassigned: Exception inside PushStreamContent won't be caught by ExceptionFilterAttribute? [1089]

$
0
0
Hi,

I'm using PushStreamContent in asp.net web api to stream data to the client, I have derived from ExceptionFilterAttribute to capture all the exceptions and return standard HttpError to client. However,
I found that exception thrown inside PushStreamContent won't be caught by ExceptionFilterAttribute, it will return a "text/html" error message to client.
Below is the sample code:

response.Content = new PushStreamContent(
async (outputStream, httpContent, transportContext) =>
{
try
{
// do something and throw exception here

}
catch (Exception ex)
{
throw;
}
finally
{
outputStream.Close();
}
});
Comments: This is an expected behavior I think. The PushStreamContent's delegate is invoked way below in the stack (at hosting layers), so any exception thrown at this layer wouldn't be caught in exception filters.

Edited Issue: Tracing should trace warning not error for HttpResponseException [949]

$
0
0
The default trace writers do not currently special-case HttpResponseException, and therefore trace at TraceLevel.Error. This is confusing, because it means simple user error in the URI will generate an error trace. This makes it hard to use the tracing layer to report errors. The System.Diagnostic, ETW, and Memory trace writers all try to correct for this to convert it back into a warning.

Recommend the default tracing logic should trace at TraceLevel.Warn whenever the exception is HttpResponseException.

Edited Issue: Asynchronous child actions not supported [601]

$
0
0
Attempting to use a child action that's marked as "async" in its method declaration results in the exception "HttpServerUtility.Execute blocked while waiting for an asynchronous operation to complete.".

Currently MVC 4 does not support asynchronous child actions, nor does it provide any graceful way of handling the request synchronously other than having to write a synchronous version of each action.

So far I've had to resort to hacks (http://stackoverflow.com/questions/13166180/force-synchronous-execution-of-asynchronous-action-in-asp-net-mvc-4/13210277#13210277) in order to force a blocking call.

A better solution might be to create a custom MvcHandler implementation that does this although MVC does not exactly make this easy with so many of the classes used in the current child action pipeline being internal/sealed.

Of course the ideal solution would be to have async child actions supported, but it would be nice to have a clean workaround until this is done.

There is also a request open on Uservoice for this, please vote. http://aspnet.uservoice.com/forums/41201-asp-net-mvc/suggestions/3233329-support-asynchronous-child-actions.

Code to reproduce:

public class HomeController : Controller
{
//
// GET: /Home/

public ActionResult Index()
{
return View();
}

[ChildActionOnly]
public async Task<ActionResult> Widget()
{
await Task.Delay(1000);
return Content("widget");
}

}

View:

@{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@Html.Action("widget")

Edited Feature: Global error handler for Web API [1001]

$
0
0
If my Web API application is going to return an error to a client, I want to know about it.

I want to set up something like ELMAH to notify me when the problem occurs, and I want full details of the error, including exception stack trace, logged on the server.

In MVC, the __Application_Error__ event is how you achive this, but in Web Api this event often isn't triggered because exceptions are converted into **HttpResponseMessage**s higher in the call stack, and because some error conditions create a **HttpResponseMessage** directly without using an exception. One source of this is __HttpControllerDispatcher.SendAsync__, which catches all exceptions and turns them into error responses, discarding the exception information.

Here are some examples of exceptions that I am interested in handling:

* Exceptions thrown from action methods and action filters
* Exceptions thrown when creating a controller instance
* Exceptions due to routing errors, bad requests, authentication problems, and invalid OData queries
* Exceptions thrown when an IQueryable returned by an action method fails to execute
* Exceptions thrown by custom message handlers and formatters

When I started using Web API, I was surprised when my __Application_Error__ handler did not pick up an action method exception. I added a global __ExceptionFilterAttribute__ to take care of that, but then found several other categories of errors that could be returned to the client and still leave silence in the server logs.

In a production environment, relying on clients to report errors and having no error information available on the server will make diagnosing problems reactive and difficult.

Please provide a global error handling event for Web API, so that error details can be logged and notifications sent. The event should provide access to unhandled exceptions and non-success **HttpResponseException**s and **HttpResponseMessage**s. In the case of **HttpError** objects created from an exception, the exception details should be retained for the error handling event.

Edited Issue: CORS message handler should return 404 in case route doesn't match [985]

$
0
0
This scenario effects WebApi-on-Owin's _Soft_ 404 Not Found responses.

In SelfHost/OwinHost, route matching normally happens at HttpRoutingDispatcher, but when CORS is enabled, we add a message handler where we do route matching for pre-flight requests. This message handler runs before the HttpRoutingDispatcher.

Currently CORS handler(or specifically AttributeBasedPolicyProviderFactory.cs) returns a 500 Internal Server Error. We need to modify it to return a 404 Not Found response.

AttributeBasedPolicyProviderFactory.cs:
```
private static HttpActionDescriptor SelectAction(HttpRequestMessage request)
{
HttpConfiguration config = request.GetConfiguration();
if (config == null)
{
throw new InvalidOperationException(SRResources.NoConfiguration);
}

IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
{
routeData = config.Routes.GetRouteData(request);
if (routeData == null)
{
throw new InvalidOperationException(SRResources.NoRouteData);
}
request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
}
```

Edited Feature: Add overloads taking a CancellationToken parameter [983]

$
0
0
Currently some of TAP-based methods in the aspnetwebstack libraries does not have overloads with _CancellationToken_ parameter. For instance following classes:

_System.Net.Http_: HttpContent and successors.
```
HttpContent: ReadAsStringAsync, ReadAsByteArrayAsync, ReadAsStreamAsync, etc.
```
_System.Net.Http.Formatting_: HttpContent extensions.
```
HttpContentMessageExtensions: ReadAsHttpRequestMessageAsync
HttpContentMultipartExtensions: ReadAsMultipartAsync
HttpContentFormDataExtensions: ReadAsFormDataAsync
HttpContentMessageExtensions: ReadAsHttpRequestMessageAsync
MultipartStreamProvider: ExecutePostProcessingAsync
```
_System.Net.Http.Formatting_: MediaTypeFormatter and successors.
```
MediaTypeFormatter: ReadFromStreamAsync, WriteToStreamAsync
BufferedMediaTypeFormatter
FormUrlEncodedMediaTypeFormatter
JsonMediaTypeFormatter
XmlMediaTypeFormatter
```
_System.Web.Http_: Model binder which breaks cancellable pipeline.
```
FormatterParameterBinding: ReadContentAsync
```
Methods of above classes potentially may be executed for a long time period. In addition if we're trying to create custom _MediaTypeFormatter_ we unable to cancel _ReadFromStreamAsync_ and _WriteToStreamAsync_ methods.

I suppose that it will be great if such methods will contain an overloads with a _CancellationToken_ parameter, which should be properly integrated into Web API message processing pipeline.

Code sample how it can be refactored: https://aspnetwebstack.codeplex.com/SourceControl/network/forks/dtretyakov/CancellationToken/contribution/4472

P.S. Discussions looks dead - no answers for over 2 weeks: http://aspnetwebstack.codeplex.com/discussions/438601

Edited Issue: Action selection requires a default value on a parameter even when the parameter is marked as optional [966]

$
0
0
This is especially an issue for attribute routing:

[HttpGet("{id?}")]
public string Get(string id) { }

doesn't work unless you add a default value

[HttpGet("{id?}")]
public string Get(string id = null) { }

Edited Issue: [CORS] Trace level of preflight request rejected should be Warning instead of Information. [965]

$
0
0
__Issue__
Today if a preflight is rejected. 400 is returned and following is the tracing output
```
iisexpress.exe Information: 0 : [2013-04-02T23:39:57.9893186Z] Level=Info, Kind=Begin, Category='System.Web.Http.Cors', Id=00000000-0000-0000-0000-000000000000, Operation=CorsEngine.EvaluatePolicy
iisexpress.exe Information: 0 : [2013-04-02T23:39:57.9993268Z] Level=Info, Kind=End, Category='System.Web.Http.Cors', Id=00000000-0000-0000-0000-000000000000, Message='CorsResult returned: 'IsValid: False, AllowCredentials: False, PreflightMaxAge: null, AllowOrigin: http://localhost:28793, AllowExposedHeaders: {}, AllowHeaders: {}, AllowMethods: {GET}, ErrorMessages: {The collection of headers 'dataserviceversion,accept,origin' is not allowed.}'', Operation=CorsEngine.EvaluatePolicy
```
Note that the level is Information. Though it is a customer failure, it is still worth to bump the level to warning so user can filter the trace records but still keep failed CORS request records.

Contrast with how we dealing with 404 in tracing. We set the level to warning when controller selection is failed.
```
iisexpress.exe Warning: 0 : Message='UserMessage='No HTTP resource was found that matches the request URI 'http://localhost:27032/api/values'.', MessageDetail='No type was found that matches the controller named 'values'.'', Operation=DefaultHttpControllerSelector.SelectController, Status=404 (NotFound), Exception=System.Web.Http.HttpResponseException: Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.
at System.Web.Http.Dispatcher.DefaultHttpControllerSelector.SelectController(HttpRequestMessage request)
at System.Web.Http.Tracing.Tracers.HttpControllerSelectorTracer.<>c__DisplayClass3.<System.Web.Http.Dispatcher.IHttpControllerSelector.SelectController>b__1()
at System.Web.Http.Tracing.ITraceWriterExtensions.TraceBeginEnd(ITraceWriter traceWriter, HttpRequestMessage request, String category, TraceLevel level, String operatorName, String operationName, Action`1 beginTrace, Action execute, Action`1 endTrace, Action`1 errorTrace)
```

In a simplified tracing model:
When the http status are 1xx to 3xx: the level should be information
When the http status are 4xx, the issues are caused by customer, level should be warning
When the http status are 5xx, the issues are server, level should be error

Edited Issue: Expose ability convert FilterClause to Linq Expression [950]

$
0
0
We can parse a filter to a FilterClause but there is no easy way to convert that FilterClause to an Expression;

ex: var filterClause = Microsoft.Data.OData.Query.ODataUriParser.ParseFilter(filter, model, entityType);

it would be nice if we can then call filterClause.ToLinqExpression();

Edited Feature: Provide a way to specify validation settings on MultipartFormDataStreamProvider/MultipartFileStreamProvider, like to limit file size, number of files etc. [921]

$
0
0
Currently a user cannot specify what is the maximum size of a single file that is allowed or combined size of multiple files that are uploaded or restrict the number of files that are uploaded upfront. By upfront I mean that, currently a user would do the following to read a multipart form upload request:

MultipartFormDataStreamProvider provider = await request.Content.ReadAsMultipartAsync<MultipartFormDataStreamProvider>(new MultipartFormDataStreamProvider(@"C:\UploadedFiles"));

After the above statement is executed, it causes all the files to be read and are stored in the UploadedFiles directory.

Only later can the user can go through each file to do his validation. It would be nice if we provide a way for users to specify validation settings regarding this.
Viewing all 7925 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>