Avoid null model when no data is posted in Web API - c#

This question is similar to what I want to achieve:
Avoiding null model in ASP.Net Web API when no posted properties match the model
But it's gone un-answered.
I have a route which takes a model that is a GET:
[HttpGet, Route("accounts")]
public AccountListResult Post(AccountListRequest loginRequest)
{
return accountService.GetAccounts(loginRequest);
}
The model is populated with additional data from an action filter.
In this case all that needs to be known is the UserId, which the action filter add's to the model based on cookie/header into passed in with the request.
I want to use all the default model binding in WebAPI but I want to avoid null objects.
I don't believe model binding solves my problem.
How do I replace the behaviour of Web API model binding so that instead of Null I receive a new instance when no parameters are passed in
This is closer to what I want to do except its per type which is tedious.

EDIT: Since the question is for Web API, I am posting the Web API solution also below.
You can do this as below in an Action Filter. The below code works only if your model contains default constructor.
Web API Implementation:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
foreach (var parameter in parameters)
{
object value = null;
if (actionContext.ActionArguments.ContainsKey(parameter.ParameterName))
value = actionContext.ActionArguments[parameter.ParameterName];
if (value != null)
continue;
value = CreateInstance(parameter.ParameterType);
actionContext.ActionArguments[parameter.ParameterName] = value;
}
base.OnActionExecuting(actionContext);
}
protected virtual object CreateInstance(Type type)
{
// Check for existence of default constructor using reflection if needed
// and if performance is not a constraint.
// The below line will fail if the model does not contain a default constructor.
return Activator.CreateInstance(type);
}
MVC Implementation:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var parameters = filterContext.ActionDescriptor.GetParameters();
foreach (var parameter in parameters)
{
if (filterContext.ActionParameters.ContainsKey(parameter.ParameterName))
{
object value = filterContext.ActionParameters[parameter.ParameterName];
if (value == null)
{
// The below line will fail if the model does not contain a default constructor.
value = Activator.CreateInstance(parameter.ParameterType);
filterContext.ActionParameters[parameter.ParameterName] = value;
}
}
}
base.OnActionExecuting(filterContext);
}

#Sarathy's solution works, but doesn't trigger model validation on objects it creates. This can cause situations where an empty model was passed in to an action but ModelState.IsValid still evaluates true.
For my own purposes, it was necessary to trigger re-validation of the model in the event that an empty model object was created:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
foreach (var parameter in parameters)
{
object value = null;
if (actionContext.ActionArguments.ContainsKey(parameter.ParameterName))
value = actionContext.ActionArguments[parameter.ParameterName];
if (value != null)
continue;
value = Activator.CreateInstance(parameter.ParameterType);
actionContext.ActionArguments[parameter.ParameterName] = value;
var bodyModelValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator();
var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider();
bodyModelValidator.Validate(value, value.GetType(), metadataProvider, actionContext, string.Empty);
}
base.OnActionExecuting(actionContext);
}

Related

c# MVC + Reflexion: Exposing assembly name

So I have Web app project that refers class library.
Like so:
MyWebApp
DatatableLibrary
----- MyDataTableClass.cs
In my view, I'm making an ajax call that got exposed MyDataTableClass AssemblyQualifiedName. for ex.:
DatatableLibrary.MyDataTableClass, DatatableLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Why? So I can in controller do this trick:
public JsonResult DatatableRequest(string className){ Type.GetType(className); //more stuff + response}
I feel that there is good risk of exposing valuable informations in class and assembly name in View, am I right? Is there any trick or workaround here?
About the security, I can only think about 2 options:
[Strong] Have the current user / current session of who is calling the the controller DatatableRequest and check if that user/caller has the permissions to access that assembly.
[Weak] Pass a token to the call, and return a DULL object if the token isn't correct. By DULL I mean an object which is empty or doesn't do anything, or if you want, you can even return a NULL
But, why do you need to do this?
I would choose the first option and implement a filter so I can reuse it in different places if needed.
like this:
Filter
public class ValidateAssemblyAttribute : ActionFilterAttribute
{
public string classNameField = "className";
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get the field through the helper
string className = filterContext.GetValue<string>(classNameField);
// User 2 doesn't have access to xpto.dll
if (Membership.CurrentUserId == 2 && className == "xpto.dll")
AuthorizationHelpers.NoPermissions(filterContext);
base.OnActionExecuting(filterContext); // Execute base
}
}
Helpers
public static class AuthorizationHelpers
{
public static T GetValue<T>(this ActionExecutingContext filterContext, string Field)
{
if (Field != null)
{
// Get the value provider for the given field name
var value = filterContext.Controller.ValueProvider.GetValue(Field);
// If the value provider has an attemped value (value), proceed
if (value != null && !string.IsNullOrEmpty(value.AttemptedValue))
{
// Get the underlying type of the generic parameter and return the true type: [Nullable<
Type underType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
// Get the TypeConvert to change string (AttemptedValue) to `T`.
TypeConverter conv = TypeDescriptor.GetConverter(underType);
// Create an auxiliar variable that will contain the result.
T result = default(T);
// See if the converted can convert the AttemptedValue to `T`
if (conv.IsValid(value.AttemptedValue))
result = (T)conv.ConvertFrom(value.AttemptedValue); // Convert the value
return result;
}
}
// Return the default of the generic type if the field name doesn't exist.
return default(T);
}
public static void NoPermissions(ActionExecutingContext filterContext)
{
// Override the result so it returns another action (No permissions action)
filterContext.Result = new RedirectResult("~/Error/NoPermissions");
}
}
Usage
[ValidateAssembly]
public JsonResult DatatableRequest(string className)
{
Type.GetType(className);
//more stuff + response
}

Autofac "action injection" with ASP.NET MVC model binding

I'm using Autofac with ASP.NET MVC and am wondering if my view model setup in the web project is a good approach or not. In the past I only used constructer injection at the Controller level but thought it would be interesting to see if everything could be injected via Autofac.
Let's say I have a view model that looks this:
public class CollegeViewModel : BaseViewModel
{
private CollegeModel _model { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public CollegeViewModel(ICsnCache cache, CollegeModel model)
{
this._cache = cache;
this._model = model;
}
public void Populate() { /* TODO: */ }
public void Update() { /* TODO: */ }
}
Cache is to access code sets (i.e. for drop down lists)
The model has methods that are called from the view model (e.g. Save())
This is where things get a bit strange. I found myself having to create a custom action invoker which derives from System.Web.Mvc.Async.AsyncControllerActionInvoker. Autofac has one of these already Autofac.Integration.Mvc.ExtensibleActionInvoker. The problem I found with the one built into Autofac is that it stopped the default model binding. i.e. it succeeded in injecting my dependencies but then the rest of the view model properties were empty even though the POST had valid form data.
This is a combination of Autofac and ASP.NET MVC code (taken out some validation and comments for readability):
public class CustomExtensibleActionInvoker : System.Web.Mvc.Async.AsyncControllerActionInvoker
{
protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
object value = null;
try
{
value = base.GetParameterValue(controllerContext, parameterDescriptor);
}
catch (MissingMethodException) { }
if (value == null)
{
// We got nothing from the default model binding, so try to resolve it.
var context = Autofac.Integration.Mvc.AutofacDependencyResolver.Current.RequestLifetimeScope;
value = context.ResolveOptional(parameterDescriptor.ParameterType);
// This is the part I added, which is from the ASP.NET MVC source code
ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => value, parameterDescriptor.ParameterType),
ModelName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
PropertyFilter = GetPropertyFilter(parameterDescriptor),
ValueProvider = controllerContext.Controller.ValueProvider,
};
value = System.Web.Mvc.ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
}
return value;
}
private Predicate<string> GetPropertyFilter(ParameterDescriptor parameterDescriptor)
{
ParameterBindingInfo bindingInfo = parameterDescriptor.BindingInfo;
return propertyName => IsPropertyAllowed(propertyName, bindingInfo.Include, bindingInfo.Exclude);
}
private bool IsPropertyAllowed(string propertyName, ICollection<string> includeProperties, ICollection<string> excludeProperties)
{
bool includeProperty = (includeProperties == null) || (includeProperties.Count == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
return includeProperty && !excludeProperty;
}
}
I realise I'm always using the default model binder, but that could be enhanced if I ever needed other model binders.
Could it be a bug in Autofac (the fact model binding doesn't happen but the DI works) or am I misusing the framework?
Note that this does work, but since it's early days I'm worried I might be adding complexity and maybe should just handle some dependency injection myself.
Edit (tweaked Ruskin's code to suit my app):
public class MyCustomModelBinder : DefaultModelBinder
{
/// <summary>
/// If the model type is registered in our Autofac configuration,
/// use that, otherwise let MVC create the model as per usual
/// </summary>
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var item = DependencyResolver.Current.GetService(modelType);
if (item != null)
{
return item;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
Global.asax.cs:
protected void Application_Start()
{
// removed other code for readability
System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new MyCustomModelBinder();
}
To answer your question (I think that's your question):
The problem I found with the one built into Autofac is that it stopped the default model binding. i.e. it succeeded in injecting my dependencies but then the rest of the view model properties were empty even though the POST had valid form data.
I don't think it's a bug in Autofac, I believe the model resolution happens well before the MVC has bound it's properties into the view model, so when are you expecting the properties to be present in the view model?
I had the exact same issue: have a a read of this question
Edit:
This is your autofac registration where builder is your ContainerBuilder...
var types = LoadMyViewModels(); // Do some reflection to get all your viewmodels
foreach (var type in types)
{
Type modelBinderType = typeof(MyCustomModelBinder<>);
Type[] typeArgs = { modelType };
Type genType = modelBinderType.MakeGenericType(typeArgs);
object viewmodel = Activator.CreateInstance(genType);
ModelBinders.Binders.Add(modelType, (IModelBinder)viewmodel);
var registeredType = builder.RegisterType(modelType).AsSelf();
}
CustomModelBinder
[ModelBinderType]
public class MyCustomModelBinder<T> : DefaultModelBinder where T : class
{
[NotNull]
protected override object CreateModel([NotNull] ControllerContext controllerContext, [NotNull] ModelBindingContext bindingContext, [NotNull] Type modelType)
{
var item = DependencyResolver.Current.GetService<T>();
if (item != null)
{
return item;
}
throw new ArgumentException(string.Format("model type {0} is not registered", modelType.Name));
}
}

JSON.Net deserializer does not generate exception when passing an invalid value for an object

I have the following structure:
public class SampleEntity{
public string Name { get; set; }
public OtherEntity Relation { get; set; }
}
public class OtherEntity {
public string Name { get; set; }
}
When i make a call to update an object in my web.api with the following request body:
"{'Name':'Nome', 'Relation':''}"
The deserializer fills the object with null value, but i think the correct action is throw an exception like 'invalid value for field Relation' and i can return a status code 400.
I tried to make a custom converter to do this, but i'm not happy with the solution and i am quite concerned with the performance of this.
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
var #object = JObject.Load(reader);
var target = Activator.CreateInstance(objectType);
var objectProperties = target.GetType().GetProperties().Where(x => x.PropertyType.IsPrimitive == false && x.PropertyType != typeof(string));
foreach (var prop in objectProperties)
{
var value = #object[prop.Name];
if (value != null && value.ToString() == string.Empty)
throw new Exception();
}
serializer.Populate(#object.CreateReader(), target);
return target;
}
return reader.Value;
}
The Web API does not automatically return an error to the client when validation fails. It is up to the controller action to check the model state and respond appropriately. While #loop's answer will work, you may want to consider another option whereby you won't even have to enter your controller's action method.
To do this, you can create an action filter to check the model state before the controller action is even invoked.
For example:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
Note that you will STILL need to decorate your model with attributes that describe the validation rules. This would be similar to what #loop has suggested.
If model validation fails, this filter returns an HTTP response that contains the validation errors. In that case, the controller action is not invoked.
To apply this filter to all Web API controllers, add an instance of the filter to the HttpConfiguration.Filters collection during configuration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
Another option is to set the filter as an attribute on individual controllers or controller actions:
public class EntitiesController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(SampleEntity entity)
{
// ...
}
}
For a more detailed explanation, take a look at this article. To learn about the various model annotations that you can use to define validation rules, e.g. [Required], etc., have a look at this MSDN page.

Asp.net webapi enum parameter with default value

I have a controller
[HttpGet]
[RoutePrefix("api/products/{productId}")]
public HttpResponseMessage Products(int productId,TypeEnum ptype=TypeEnum.Clothes)
{
if(!Enum.IsDefined(typeOf(TypeEnum),ptype))
//throw bad request exception
else
//continue processing
}
Myenum is declared as
public TypeEnum
{
Clothes,
Toys,
Electronics
}
Currently if,some garbage value is passed it is getting converted into default value.
What I want to do is if i call the controller as api/products/1 then the ptype should be assigned default value i.e clothes. If I call the controller as api/products/1?pType=somegarbagevalue then the controller should throw bad request exception. How can I achieve this?
Defining all your enum parameters as strings and then parsing them everywhere means you have to do this on every single action and you will need to come up with a consistent approach such that all parsing errors conform.
This is a parameter binding issue and should not be dealt with in the controller layer, it should be taken care of in the pipeline. One way to do this is to create a custom filter and add it to your config.
public class ModelStateValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = <your standardised error response>
}
}
}
And in your global.asax.cs
...
GlobalConfiguration.Configure(WebApiConfig.Register);
...
public class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
config.Filters.Add(new ModelStateValidationAttribute());
...
}
}
If you're having trouble with the model state, it's type is a ModelStateDictionary and you simply iterate over it and then it's Errors property contains all the model binding issues. e.g.
modelState = actionContext.ModelState;
modelState.ForEach(x =>
{
var state = x.Value;
if (state.Errors.Any())
{
foreach (var error in state.Errors)
{
<work your magic>
}
}
});
You have to do with string and use TryParse() to convert string to Enum value.
public HttpResponseMessage Products(int productId,string ptype="Clothes")
{
TypeEnum category = TypeEnum.Clothes;
if(!Enum.TryParse(ptype, true, out category))
//throw bad request exception if you want. but it is fine to pass-through as default Cloathes value.
else
//continue processing
}
It may look naive but the benefit of this approach is to allow ptype parameter to whatever string and to perform process without exception when ptype fails to bind the value.
This type of validation should be handled in pipeline not in controller.
public abstract class ETagMatchAttribute : ParameterBindingAttribute
{
private ETagMatch _match;
public ETagMatchAttribute(ETagMatch match)
{
_match = match;
}
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
if (parameter.ParameterType == typeof(ETag))
{
return new ETagParameterBinding(parameter, _match);
}
return parameter.BindAsError("Wrong parameter type");
}
}
something like this. refer to MSDN link for detailed explanation

Prevent MVC Action method from executing if a parameter is null

I've thought of a few ways of doing this but I want to get the community's view. I have a feeling that the answer is cringeworthily simple - I'm not afraid to look stupid (my kids took that fear away from me long ago!)
I'm writing an XML REST web service using MVC2. All XML Types that consumers of the web service will receive and send are governed by simple but extensive XSD, and these parameters will be bound from xml in the request body via a custom default model binder and value provider.
I have a goodly amount of controllers, each with a goodly amount of action methods (not excessive - just 'goodly' ;) ) - and in nearly every case these action methods are going to be accepting model types that are all reference types.
In practically every case it's going to be an error for the caller not to provide these parameter values, and as such a standard error message such as "The parameter {name} type:{ns:type} is required" can be sent back.
What I want to do is to be able to validate parameters are not null before an action method is executed; and then to return an ActionResult that represents the Error to the client (for this I already have an XMLResult type) without the action method itself having to validdate the parameters itself.
So, instead of:
public ActionResult ActionMethod(RefType model)
{
if(model == null)
return new Xml(new Error("'model' must be provided"));
}
Something like:
public ActionResult ActionMethod([NotNull]RefType model)
{
//model now guaranteed not to be null.
}
I know this is exactly the kind of cross-cutting that can be achieved in MVC.
It seems to me that either a base controller override of OnActionExecuting or a custom ActionFilter is the most likely way of doing this.
I'd also like to be able to extend the system so that it automatically picks up XML schema validation errors (added to ModelState during binding by a custom value provider) thus preventing the action method from continuing if any of the parameter values can't be loaded correctly because the XML request is badly formed.
Here's the implementation that I've come up with (while waiting for any better ideas :) )
It's a generic approach and I think is pretty scalable - allowing for hopefully a similar kind of depth to parameter validation as you get with model validation at the same time as providing the error auto-respond functionality (when model state contains one or more errors) that I was looking for.
I hope this isn't too much code for an SO answer(!); I had a load of documentation comments in there that I've taken out to keep it shorter.
So, in my scenario I have two types of model error that, if they occur, should block execution of the action method:
Failed schema validation of the XML from which a parameter value will be constructed
Missing (null) parameter value
Schema validation is currently performed during model binding, and automatically adds model errors to the ModelState - so that's great. So I need a way to perform the auto-null check.
In the end I created two classes to wrap up the validation:
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public abstract class ValidateParameterAttribute : Attribute
{
private bool _continueValidation = false;
public bool ContinueValidation
{ get { return _continueValidation; } set { _continueValidation = value; } }
private int _order = -1;
public int Order { get { return _order; } set { _order = value; } }
public abstract bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value);
public abstract ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value);
public virtual ModelError GetModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
if (!Validate(context, parameter, value))
return CreateModelError(context, parameter, value);
return null;
}
}
[AttributeUsage(AttributeTargets.Parameter,
AllowMultiple = false, Inherited = false)]
public class RequiredParameterAttribute : ValidateParameterAttribute
{
private object _missing = null;
public object MissingValue
{ get { return _missing; } set { _missing = value; } }
public virtual object GetMissingValue
(ControllerContext context, ParameterDescriptor parameter)
{
//using a virtual method so that a missing value could be selected based
//on the current controller's state.
return MissingValue;
}
public override bool Validate
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return !object.Equals(value, GetMissingValue(context, parameter));
}
public override ModelError CreateModelError
(ControllerContext context, ParameterDescriptor parameter, object value)
{
return new ModelError(
string.Format("Parameter {0} is required", parameter.ParameterName));
}
}
With this I can then do this:
public void ActionMethod([RequiredParameter]MyModel p1){ /* code here */ }
But this on its own doesn't do anything of course, so now we need something to actually trigger the validation, to get the model errors and add them to model state.
Enter the ParameterValidationAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public class ParameterValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var paramDescriptors = filterContext.ActionDescriptor.GetParameters();
if (paramDescriptors == null || paramDescriptors.Length == 0)
return;
var parameters = filterContext.ActionParameters;
object paramvalue = null;
ModelStateDictionary modelState
= filterContext.Controller.ViewData.ModelState;
ModelState paramState = null;
ModelError modelError = null;
foreach (var paramDescriptor in paramDescriptors)
{
paramState = modelState[paramDescriptor.ParameterName];
//fetch the parameter value, if this fails we simply end up with null
parameters.TryGetValue(paramDescriptor.ParameterName, out paramvalue);
foreach (var validator in paramDescriptor.GetCustomAttributes
(typeof(ValidateParameterAttribute), false)
.Cast<ValidateParameterAttribute>().OrderBy(a => a.Order)
)
{
modelError =
validator.GetModelError(filterContext, paramDescriptor, paramvalue);
if(modelError!=null)
{
//create model state for this parameter if not already present
if (paramState == null)
modelState[paramDescriptor.ParameterName] =
paramState = new ModelState();
paramState.Errors.Add(modelError);
//break if no more validation should be performed
if (validator.ContinueValidation == false)
break;
}
}
}
base.OnActionExecuting(filterContext);
}
}
Whew! Nearly there now...
So, now we can do this:
[ParameterValidation]
public ActionResult([RequiredParameter]MyModel p1)
{
//ViewData.ModelState["p1"] will now contain an error if null when called
}
To complete the puzzle we need something that can investigate the model errors and automatically respond if there are any. This is the least tidy of the classes (I hate the name and the parameter type used) and I'll probably change it in my project, but it works so I'll post it anyway:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = false)]
public abstract class RespondWithModelErrorsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ModelStateDictionary modelState =
filterContext.Controller.ViewData.ModelState;
if (modelState.Any(kvp => kvp.Value.Errors.Count > 0))
filterContext.Result = CreateResult(filterContext,
modelState.Where(kvp => kvp.Value.Errors.Count > 0));
base.OnActionExecuting(filterContext);
}
public abstract ActionResult CreateResult(
ActionExecutingContext filterContext,
IEnumerable<KeyValuePair<string, ModelState>> modelStateWithErrors);
}
In my application I have an XmlResult that takes a Model instance and serializes to the response using either DataContractSerializer or XmlSerializer - so I've then created RespondWithXmlModelErrorsAttribute that inherits from this last type to formulate one of those with the model as an Errors class that simply contains each of the model errors as strings. The Response Code is also automatically set to 400 Bad Request.
Thus, now I can do this:
[ParameterValidation]
[RespondWithXmlModelErrors(Order = int.MaxValue)]
public ActionResult([RequiredParameter]MyModel p1)
{
//now if p1 is null, the method won't even be called.
}
In the case of web pages this last stage won't necessarily be required, since model errors are typically included in a re-rendering of page that sent the data in the first place, and the existing MVC approach suits this fine.
But for web services (either XML or JSON) being able to offload error reporting to something else makes writing the actual action method a lot easier - and much more expressive, I feel.
Well you could add constraints using regular expressions to individual route values. Then, if these constraints are not upheld, the action method will not be hit:
routes.MapRoute ("SomeWebService", "service/{userId}",
new { controller = "Service", action = "UserService" },
new { userId = #"\d+" });
Alternatively you could create custom constraints to validate route values together as a pack. This would probably be a better strategy for you. Have a look here: Creating a Custom Route Constraint

Categories

Resources