Appropriate pattern for setting request object properties from POST request with MVC3? - asp.net-mvc-3

With incoming POST requests to my MVC3 application, I want to validate the incoming request parameters. If an invalid parameter exists, an exception is thrown.
Given the following object:
public class ActionRequest
{
public string ActionRequestPassword { get; set; }
public bool EnableNewsfeedAppPool { get; set; }
}
With incoming post requests, I want to initialize the object with the appropriate properties via:
public class NewsfeedAppPoolController : Controller
{
[ActionName("EnableAppPool"), AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[NoCache]
public ActionResult EnableAppPool(FormCollection formCollection)
{
Models.ActionRequest actionRequest = ValidatePOSTRequest(formCollection);
// do things with actionRequest
return null;
}
private Models.ActionRequest ValidatePOSTRequest(FormCollection formCollection)
{
try
{
Type actionRequestType = typeof(Models.ActionRequest);
System.Reflection.PropertyInfo propertyInfo = null;
object systemActivatorObject = Activator.CreateInstance(actionRequestType);
foreach (var key in formCollection.AllKeys)
{
propertyInfo = typeof(Models.ActionRequest).GetProperty(key);
Type t = propertyInfo.PropertyType; // t will be System.String
if (t.Name == "Int32")
{
actionRequestType.GetProperty(key).SetValue(systemActivatorObject, Convert.ToInt32(formCollection[key]), null);
}
else
{
actionRequestType.GetProperty(key).SetValue(systemActivatorObject, formCollection[key], null);
}
}
return (Models.ActionRequest)systemActivatorObject;
}
catch (Exception ex)
{
throw ex;
}
}
}
I would like to know if there can be any improvements made to this, or recommendations of how else to accomplish this in an efficient manner.
Thanks.

ASP.Net MVC already does all of this for you.
Just add a Models.ActionRequest actionRequest parameter to your action.
If you want to add additional validation logic, use System.ComponentModel.DataAnnotations.

Simply use the default model binder which will take care of instantiating and binding the ActionRequest from the request parameters:
public class NewsfeedAppPoolController : Controller
{
[ActionName("EnableAppPool"), AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
[NoCache]
public ActionResult EnableAppPool(ActionRequest actionRequest)
{
// do things with actionRequest
return null;
}
}

The appropriate pattern is,
[HttpPost]
public ActionResult Save(Employee employee)
{
if(ModelState.IsValid)
{
db.Save(employee);
RedirectToAction("Index");
}
return View();
}
Notes:
The employee instance is automatically created and populated by the default model binder from the values available in the request(form, querystrings, routedata and more)
When the default model binder binds the values to the model it also does the validation and store all the errors in the ModelState dictionary, so by checking the ModelState.IsValid you can know that whether the validation is succeeded or not.
To know more about model binding refer this.
To know more about model validation refer this.

Related

passing parameter from custom filter to web api controller

I am working on a web application in which we are using web-api and oAuth2.
I had stored my UserId in front-end but now for security reason I am storing my UserId in backend against the token generated from oAuth2.
So I have around 800 api's in my application all of them are POST api's and the data is passing in those api's like below
Type 1
[HttpPost]
[Authorize]
[ActionName("GetList")]
[Filters.AuthorizeLoginApi()]
public List<BusinessEntities.Admin.Users> GetList(Dictionary<string, string> Parameters)
{
try
{
if (Parameters != null)
{
BusinessLayer.IAdmin.IUsers a = (BusinessLayer.IAdmin.IUsers)DALFinder.GetInstance(typeof(BusinessLayer.IAdmin.IUsers));
return a.GetList(Convert.ToString(Parameters["LoginText"]), Convert.ToString(Parameters["Name"])
, Convert.ToString(Parameters["Email"]), Convert.ToInt32(Parameters["UserTypeId"]), Convert.ToString(Parameters["IsActive"])
, Convert.ToInt32(Parameters["UserId"])); /*(LoginText, Name, Email, UserTypeId, IsActive, UserId);*/
}
else
{
return new List<BusinessEntities.Admin.Users>();
}
}
catch (Exception ex)
{
Utils.Logger.Instance.LogException(ex);
return new List<BusinessEntities.Admin.Users>();
}
}
In the above code I have a Dictionary parameter in which I am storing my userId
Type 2
[HttpPost]
[Authorize]
[ActionName("Delete")]
[Filters.AuthorizeLoginApi()]
public SPResponse Delete(BusinessEntities.Admin.Users item)
{
SPResponse response = new SPResponse();
try
{
//item.ModifiedByUserId is my UserId
BusinessLayer.IAdmin.IUsers a = (BusinessLayer.IAdmin.IUsers)DALFinder.GetInstance(typeof(BusinessLayer.IAdmin.IUsers));
response = a.Delete(item);
}
catch (Exception ex)
{
response.ReturnMessage = ex.Message;
}
return response;
}
I am doing custom validation in each and every api calls like below
public class AuthorizeLoginApi : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
//Code to Get userId from database
//int UserId = data coming from db
//pass the above UserId Parameter into every apis as UserId/ModifiedByUserId
}
}
Now I want to Pass UserId/ModifiedByUserId from OnActionExecuting filter method into my respective API's
How can I achieve this

Custom AuthorizeAttribute on WebApi OData EntitySetController

I created custom authorize attribute to handle my custom permissions on WebAPI odata controller inherited from EntitySetController, here is the code for my attribute
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class RequirePermissionsAttribute : System.Web.Http.AuthorizeAttribute
{
public Permissions[] Permissions { get; set; }
public RequirePermissionsAttribute()
{ }
public RequirePermissionsAttribute(params Permissions[] permissions)
{
this.Permissions = permissions;
}
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
// Custom authorization logic
}
Now I try to add this attribute on Get() method, it get invoked
public class ItemsController : EntitySetController<Item, Guid>
{
[EnableQuery(MaxExpansionDepth = 5)]
[RequirePermissionsAttribute(Permissions.ViewAll)]
public override IQueryable<Item> Get()
{
//Code go here
}
}
But when I add the same attribute on CreateEntity() it never get invoked
[RequirePermissionsAttribute(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
}
Any help appreciated
You should use your attribute as RequirePermissions without the tailed "Attribute" word, So change your code to be like this
[RequirePermissions(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
}
Islam
From the WebAPI source codes, the internal virtual function CreateEntity() is called in POST request. Here's the source codes in EntitySetController:
public virtual HttpResponseMessage Post([FromBody] TEntity entity)
{
TEntity createdEntity = CreateEntity(entity);
TKey entityKey = GetKey(entity);
return EntitySetControllerHelpers.PostResponse(this, createdEntity, entityKey);
}
I use your sample codes and send a POST request, the CreateEntity() can be invoked as:
POST ~/odata/Items
Content-type: application/json
{"Id":"9daf653f-212c-42e3-80a4-4778e445c092"}
However, if you want to get the correct response, you should override GetKey() because GetKey() is called after CreateEntity() in the Post() method. The same information is also mentioned in the remarks of CreateEntity() as below:
Sample Test
I create the following two functions in ItemsController:
protected override Guid GetKey(Item entity)
{
return entity.Id;
}
[RequirePermissionsAttribute(Permissions.Add)]
protected override Item CreateEntity(Item item)
{
// Create item
return item;
}
And send the same POST request mentioned above, I can get the following response:
HTTP/1.1 201 Created
Cache-Control: no-cache
.....
Content-Length: 124
{
"odata.metadata":"http://localhost:47794/odata/$metadata#Items/#Element","Id":"9daf653f-212c-42e3-80a4-4778e445c092"
}
Hope it can help you. Thanks.

Web API Validation for Model Bound in GET request

I have created a custom Model Binder to read the data from the URI in a specific format
public ResponseObject Get([FromUri(BinderType = typeof(CustomModelBinder)]ProductFilter product
{...}
public class ProductFilter
{
[Required(ErrorMessage = #"Name is required")]
public string Name { get; set; }
}
public class CustomModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
//Code to convert the uri parameters to object
return true;
}
}
In the above example, i need the name to be passed from the client before executing the Action.
But, I am unable to run the in-built validations on the Product class using this?
Any ideas?
I wrote in a custom action filter and I registered this action filter in the GlobalConfiguration for all the services. The action filter hooks on to onActionExecuting, looks for the validation in the bound arguments.
bool isValid;
foreach (var item in actionContext.ActionArguments)
{
var parameterValue = item.Value;
var innerContext = new ValidationContext(parameterValue);
if(parameterValue != null)
{
var innerContext = new ValidationContext(parameterValue);
isValid = Validator.TryValidateObject(parameterValue, innerContext, results, true);
}
}
//If not valid, throw a HttpResponseException
if(!isValid)
throw new HttpResponseException(HttpStatusCode.BadRequest);
else
base.onActionExecuting(actionContext);
With more tuning, the exact validation message can be retrieved from the validation context and sent as the response message.
I was also able to extend this to having validation attributes on the parameters themselves, thereby giving more flexibility to my Api

Breeze webapi controller 'cannot support querying' over DbSet error

I have setup very similar to breeze sample application which comes in sample nuget. This is the code of my api controller:
[JsonFormatter, ODataActionFilter]
public class WorkOrdersController : ApiController
{
readonly EFContextProvider<WorkOrdersContext> _contextProvider =
new EFContextProvider<WorkOrdersContext>();
public WorkOrdersController()
{
// I was thinking this may be the cause of the issue
this._contextProvider.Context.Configuration.ProxyCreationEnabled = false;
}
[HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<WorkOrder> WorkOrders()
{
return _contextProvider.Context.WorkOrders;
}
}
The problem I'm having is when I try to perform query over WorkOrders action, I get 500 - Internal server error, and this is the payload of response:
{"$id":"1","$type":"System.Web.Http.HttpError, System.Web.Http","Message":"An error has occurred.","ExceptionMessage":"The action 'WorkOrders' on controller 'WorkOrders' with return type 'System.Collections.Generic.List`1[[WorkOrders.Domain.Models.WorkOrder, WorkOrders.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' cannot support querying. Ensure the type of the returned content is IEnumerable, IQueryable, or a generic form of either interface.","ExceptionType":"System.InvalidOperationException","StackTrace":" at System.Web.Http.QueryableAttribute.ValidateReturnType(Type responseContentType, HttpActionDescriptor actionDescriptor)\r\n at System.Web.Http.QueryableAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)\r\n 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)\r\n at System.Web.Http.Tracing.Tracers.ActionFilterAttributeTracer.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)\r\n at System.Web.Http.Filters.ActionFilterAttribute.<>c__DisplayClass2.<System.Web.Http.Filters.IActionFilter.ExecuteActionFilterAsync>b__0(HttpResponseMessage response)\r\n at System.Threading.Tasks.TaskHelpersExtensions.<>c__DisplayClass41`2.<Then>b__40(Task`1 t)\r\n at System.Threading.Tasks.TaskHelpersExtensions.ThenImpl[TTask,TOuterResult](TTask task, Func`2 continuation, CancellationToken cancellationToken, Boolean runSynchronously)"}
WorkOrders is Dbset on context:
public DbSet<WorkOrder> WorkOrders { get; set; }
I have also tried explicitly casting it to IQueryable, with no change:
[HttpGet]
public IQueryable<WorkOrder> WorkOrders()
{
return (IQueryable<WorkOrder>)_contextProvider.Context.WorkOrders;
}
The only thing that works for me is:
[HttpGet]
public IEnumerable WorkOrders()
{
return _contextProvider.Context.WorkOrders.AsEnumerable();
}
This, however causes another problem for me on the client side, described in this question.
Just a guess here, but can you try using the latest breeze (v 0.82.1) with the following attribute.
[BreezeController]
instead of these
[JsonFormatter, ODataActionFilter]
The new [BreezeController] attribute is a complete replacement for the other two and avoids some other issues.

How to return HTTP status code form Custom Model Binder

I have a custom model binder which pulls an implementation of an interface from a MEF container. It is implemented as follows:
public class PetViewModelBinder : DefaultModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var petId = bindingContext.ValueProvider.GetValue("id");
var container = controllerContext.HttpContext.Application[MvcApplication.PLUGINS] as CompositionContainer;
Lazy<IPet, IPetMetadata> pet = null;
try
{
pet = container.GetExport(typeof(IPet), petId);
var petVM = new Models.PetViewModel(pet);
bindingContext.ModelMetadata.Model = petVM;
return base.BindModel(controllerContext, bindingContext);
}
catch (Exception)
{
throw;
}
finally
{
container.ReleaseExport(pet);
}
}
This works splendidly when MEF has an Export of petId... but returns http status 500 (server error) when an Export does not exist. Error message obfuscation requirements dictate http status 403 (forbidden) should be returned.
What can be done to trap the error, change the response status, and either not return content, or re-route the Action to handle this condition?
If you want to return a particular http status code you should do that from a controller or action filter.
One way to do this is to return null from your model binder and handle that in your controller. This is a bit coarse however so you won't be able to distinguish between different errors.
Another way to do it would be to throw a specific exception and handle that in your (global) error handling. A customized HandleError action filter could do this:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public int StatusCode { get; set; }
public override void OnException( ExceptionContext filterContext )
{
base.OnException( filterContext );
if ( StatusCode > 0 )
{
filterContext.HttpContext.Response.StatusCode = StatusCode;
}
}
}
In your controller, decorate the action with this attribute:
[CustomHandleError( ExceptionType = typeof (NotAllowedException), View = "~/Views/Shared/Error.cshtml",
StatusCode = 403 )]
public ActionResult Index( FancyModel model )
{
return View( model );
}
Finally, in your model binder throw a NotAllowedException, which is a custom exception type you'll also need to define.
Note that this will only work on your development setup if you have enabled custom errors in your web.config file.

Resources