Custom validate attribute on query parameter web api - asp.net-web-api

I have the following controller which takes guids in string format as query parameters.
I wanted to verify they were a valid guid before executing the method but I'm not sure how to fire my custom ValidationAttribute:
controller.cs
public async Task<Profile> GetProfile([ValidGuid] string id)
{
...
}
ValidGuidAttribute.cs
internal class ValidGuidAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
try
{
Guid.Parse(value.ToString());
return true;
}
catch (Exception e)
{
throw new InvalidIdException($"{value.ToString()} is an invalid id");
}
}
}
Any help pointing out how to fire my IsValid method would be appreciated.

You should use a route constraint https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#route-constraints
[Route("getprofile/{id:guid}")]
public async Task<Profile> GetProfile(string id)
{
...
}

Related

How to call Redirect() inside a private method, that is called by a Controller Action?

THE GOAL: invoke a Redirect from inside a private method, that's called by an executing Action.
SETUP: we have this private method, inside a Controller, and we need to redirect on any raised error:
[EDIT: please note - the first example here is the problem to be solved (if it is possible) for this post]
private void CleanLoginData(ref object logindata)
{
try
{
logindata.Trim(); <---fake example of what it might do
}
catch (Exception e)
{
CustomError ce = ErrorFactory.BuildCustomRedirectError(e)
Redirect(ce.UrlToErrorPage);
}
}
this method is called by an Action inside the same Controller, in the context of a PostRedirectGet op:
[HttpPost]
public async Task<IActionResult> LogIn(object loginData)
{
CleanLoginData(ref loginData);
await DbAgent.LoginAsync(loginData);
Redirect("~/loggedin");
}
but this fails. MVC ignores the Redirect inside CleanLoginData and still calls 'DbAgent.LoginAsync()'.
also, this one fails as well, if we move the try/catch from inside the method to outside:
public async Task<IActionResult> LogIn(object loginData)
{
try
{
CleanLoginData(loginData);
}
catch (Exception e)
{
CustomError ce = ErrorFactory.BuildCustomRedirecdtError(e)
Redirect(ce.CustomRedirectLink);
}
await DbAgent.LoginAsync(loginData);
Redirect("~/loggedin");
}
MVC still calls DbAgent.LoginAsync(). this surprised me. is there any way to fire a Redirect inside a private method that's called by an Action?
Here is a demo to show redirect on the raised error:
public async Task<IActionResult> Log()
{
string s = "s";
try
{
CleanData(s);
}
catch (Exception e)
{
return Redirect("https://www.google.com/");
}
return View("Index");
}
private void CleanData(string s)
{
var a = Int32.Parse(s);
}
result:
If you want to redirect in private method,It may be like this:
private IActionResult CleanData(string s)
{
try
{
xxxxx
}
catch (Exception e)
{
return Redirect("https://www.google.com/");
}
return View("xxx");
}
public IActionResult Log()
{
string s = "s";
CleanData(s);
return View("Index");
}
But then it will redirect in CleanData,whatever it has an error.
You can't redirect from a void type method but there is a pretty simple solution exist.
set return type of method to bool and return false from catch statement otherwise true and then in controller check if it return false redirect
private bool CleanLoginData(ref object logindata)
{
try
{
logindata.Trim(); <---fake example of what it might do
}
catch (Exception e)
{
return false; // if it throw error
}
return true; // if it does not throw error
}
Now in controller check if your function return true or false. if false then redirect
public async Task<IActionResult> LogIn(object loginData)
{
bool succeed = CleanLoginData(loginData);
if(!succeed)
{
return Redirect("~/loggedin");
}
}

aspnet web api how to validate using modelstate in multipart form data?

I am new for using asp net web api.
I want to create UploadDocument feature in my web api.
so, I create this way.
From Client
api.post('vendor/UploadDocument', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
Controller
public class VendorController : ApiController {
[HttpPost]
public HttpResponseMessage UploadDocument()
{
try
{
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
var request = HttpContext.Current.Request;
var model = new UploadDocumentViewModel();
model.field1 = request.Form["field1"];
model.field2 = request.Form["field2"];
model.Document = request.Files["Document"];
if (ModelState.IsValid)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
else //ModelState is not valid
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
catch (Exception exception)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}
}
Model
public class UploadDocumentViewModel
{
[Required]
public string field1 { get; set; }
[Required]
public int field2 { get; set; }
[Required]
public HttpPostedFile Document { get; set; }
}
My problem is, in controller ModelState always empty.
I have tried to add code
Validate(model);
if (ModelState.IsValid)...
but it didn't work too.
can someone have any idea for validating model data annotation in multipart form data ?
try clear model state before validate
ModelState.Clear();
this.Validate(model);
if (ModelState.IsValid) {
}
Check this answer : Custom DataAnnotation
Your method should look like this:
public class VendorController : ApiController {
[HttpPost]
public IHttpActionResult UploadDocument(UploadDocumentViewModel viewModel)
{
try
{
if (!Request.Content.IsMimeMultipartContent())
return StatusCode(HttpStatusCode.UnsupportedMediaType);
if (viewNodel == null)
return BadRequest("Model is empty");
var field1 = viewModel.field1;
var field2 = viewModel.field2;
var documents = viewModel.document;
if (ModelState.IsValid)
{
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
catch (Exception exception)
{
return InternalServerError(exception);
}
}
}
I prefer to passing some of those validations in action filters, to make your methods cleaner (try/catch, modelstate).
If you will have some problems with model binding, you can implement your custom model binder.

ModelState to check all parameters in Web Api

This is my action the ModelState checks only the bookId parameter. The other one even if it is null, no error is raised.
Is there any way to make it check the ModelState of all parameters?
[HttpPut]
[Route("{bookId}")]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
if (!this.service.ExistsBook(bookId))
{
return this.NotFound();
}
if (!this.ModelState.IsValid)
{
return this.StatusCode(HttpStatusCode.BadRequest);
}
this.service.EditBook(bookId, model);
return this.Ok();
}
You could define an ActionFilterAttribute that protects you from null arguments:
public class CheckModelForNullAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsValue(null))
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "The argument cannot be null");
}
}
Then use this:
[HttpPut]
[Route("{bookId}")]
[CheckModelForNull]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
// model canĀ“t be null now
...
I wrote a custom filter so DataAnnotations works with parameters also.
Here is the filter.
public class ModelValidationFilter : FilterAttribute, IActionFilter, IFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
if (parameters.Any())
{
var validationParams = parameters.Where(x => x.GetCustomAttributes<ValidationAttribute>().Any());
if (validationParams.Any())
{
foreach (var item in validationParams)
{
var val = actionContext.ActionArguments[item.ParameterName];
foreach (var attr in item.GetCustomAttributes<ValidationAttribute>())
{
if (!attr.IsValid(val))
{
actionContext.ModelState.AddModelError(item.ParameterName, attr.FormatErrorMessage(item.ParameterName));
}
}
}
}
if (!actionContext.ModelState.IsValid)
{
return Task.FromResult(actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState));
}
}
return continuation();
}
}
Usage (I have'nt tested completely.):
add it to global filters.
config.Filters.Add(new ModelValidationFilter());
public Student Post([Required] addStudentDTO)
{
//Your logic
}
public Student Patch([FromBody,Required] addStudentDTO, [Required,EmailAddress]string emailAddress])
{
//Your logic
}

Web API ReadFromStreamAsync not executed

I have a very dumb issue related to a custom formatter.
public class RequestHeaderJsonFormatter : MediaTypeFormatter
{
public RequestHeaderJsonFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
}
public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
return base.ReadFromStreamAsync(type, readStream, content, formatterLogger);
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
}
I register the formatter in Global.asax as it follows:
protected void Application_Start()
{
GlobalConfiguration.Configuration.Formatters.Insert(0, new RequestHeaderJsonFormatter());
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}
My issue is that ReadFromStreamAsync never gets called when executing the controller action.
public class HomeController : ApiController
{
public HttpResponseMessage GetString(string param)
{
var response = Request.CreateResponse(HttpStatusCode.OK, "ererrer");
return response;
}
}
What am I doing wrong...I can't figure it out. Any help would be much appreciated.
By default 'string' type action parameters are expected to be read from Uri, unless [FromBody] attribute is used to decorate it, in which case the formatters come into picture.
public HttpResponseMessage GetString(*[FromBody]*string param)

How to return Faults in JSON from AJAX Enabled WCF Service?

I have an AJAX-enabled WCF service (with enableWebScript in the behavior) that has a ValidationFault which I created.
Here's the service:
[ServiceContract]
public interface ICoreWCF
{
/// <summary>
/// Saves the Customer.
/// </summary>
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[FaultContract(typeof(ValidationFault))]
void Customer_Save(Customer customer);
}
Here's the fault:
[DataContract]
public class ValidationFault
{
[DataMember(Name = "success")]
public bool Success { get; set; }
[DataMember(Name = "msg")]
public string ValidationMessage { get; set; }
[DataMember(Name = "errors")]
public Dictionary<string, string> Errors { get; set; }
}
I would like to send this fault back to the client javascript.
The problem is that my custom fault's DataMembers are ignored and a general exception is returned.
How can I send the errors collection to the client?
I already tried writing my own IErrorHandler similar to this, such that it uses Exception Handling Application Block to convert an exception to a fault, and then the IErrorHandler serializes the resulting fault. But it appears that the JsonErrorHandler of the WebScriptingEnablingBehavior is not dealing well with the resulting Message object.
Thanks.
If you have implemented IErrorHandler and associated it to service using using custom behavior inherited from WebHttpBehavior as sighted by link then perhaps you should try adding default request/response format etc. For example,
private class CustomWebScriptBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
// clear current error handlers
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add our error handler
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(
new ErrorHandler(true));
}
private WebMessageFormat _requestFormat;
private WebMessageFormat _responseFormat;
public CustomWebScriptBehavior()
{
_requestFormat = _responseFormat = WebMessageFormat.Json;
}
public override bool AutomaticFormatSelectionEnabled
{
get { return false; }
set { throw new NotSupportedException(); }
}
public override WebMessageBodyStyle DefaultBodyStyle
{
get { return WebMessageBodyStyle.WrappedRequest; }
set { throw new NotSupportedException(); }
}
public override WebMessageFormat DefaultOutgoingRequestFormat
{
get { return _requestFormat; }
set { _requestFormat = value; }
}
public override WebMessageFormat DefaultOutgoingResponseFormat
{
get { return _responseFormat; }
set { _responseFormat = value; }
}
}
This will eliminate the need to specify WebInvoke attribute for each method.
in webinvoke you can add RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json
try it

Resources