I have created one API controller and implement ActionFilterAttribute and am trying to get result as a class object.
public class AuthorizationAttribute : ActionFilterAttribute
{
ErrorInformation objError = new ErrorInformation();
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (string.IsNullOrEmpty(token))
{
objError.ErrorMessage = "Missing Request Token";
objError.StatusCode = Convert.ToInt16(HttpStatusCode.ExpectationFailed);
objError.ErrorType = "";
objError.ErrorCode = "E:101";
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Request Token"),
};
// Here i want to pass my above class object 'objError' in 'actionContext.Response'
// it is possible to pass 'objError' as a result ?**
return;
}
}
}
After some research i have successfully resolved this issue as below.
public class AuthorizationAttribute : ActionFilterAttribute
{
ErrorInformation objError = new ErrorInformation();
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (string.IsNullOrEmpty(token))
{
objError.ErrorMessage = "Missing Request Token";
objError.StatusCode = Convert.ToInt16(HttpStatusCode.ExpectationFailed);
objError.ErrorType = "";
objError.ErrorCode = "E:101";
//actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
// {
// Content = new StringContent("Missing Request Token"),
//};
// For pass class object as a response
var request = actionContext.Request;
actionContext.Response = request.CreateResponse<ErrorInformation>(HttpStatusCode.BadRequest, objError);
return;
}
}
}
Related
Is it possible to return custom error messages to client from Asp.Net Core 3.1 Web Api? I've tried a few different things to set the "ReasonPhrase" with no luck. I have tried using StatusCode:
return StatusCode(406, "Employee already exists");
I tried to return using HttpResponseMessage:
HttpResponseMessage msg = new HttpResponseMessage();
msg.StatusCode = HttpStatusCode.NotAcceptable;
msg.ReasonPhrase = "Employee alredy exists";
return (IActionResult)msg;
I am trying to return a message to the client calling the method that the employee already exists:
public async Task<IActionResult> CreateEmployee([FromBody] EmployeeImport Employee)
{
var exists = await employeeService.CheckForExistingEmployee(Employee);
if (exists > 0)
{
//return StatusCode(406, "Employee already exists");
HttpResponseMessage msg = new HttpResponseMessage();
msg.StatusCode = HttpStatusCode.NotAcceptable;
msg.ReasonPhrase = "Employee already exists";
return (IActionResult)msg;
}
}
This is the code in the client:
public async Task<ActionResult>AddEmployee(EmployeeImport employee)
{
var message = await CommonClient.AddEmployee(employee);
return Json(message.ReasonPhrase, JsonRequestBehavior.AllowGet);
}
public async Task<HttpResponseMessage> AddEmployee(EmployeeImport employee)
{
var param = Newtonsoft.Json.JsonConvert.SerializeObject(employee);
HttpContent contentPost = new StringContent(param, System.Text.Encoding.UTF8, "application/json");
var response = await PerformPostAsync("entity/NewEmployee", contentPost);
return response;
}
protected async Task<HttpResponseMessage> PerformPostAsync(string requestUri, HttpContent c)
{
_webApiClient = new HttpClient { BaseAddress = _baseAddress };
_webApiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
var webApiResponse = await _webApiClient.PostAsync(requestUri, c);
return webApiResponse;
}
To do this, you can create a Custom Error class that implements the IActionResult interface as follows:
public class CustomError : IActionResult
{
private readonly HttpStatusCode _status;
private readonly string _errorMessage;
public CustomError(HttpStatusCode status, string errorMessage)
{
_status = status;
_errorMessage = errorMessage;
}
public async Task ExecuteResultAsync(ActionContext context)
{
var objectResult = new ObjectResult(new
{
errorMessage = _errorMessage
})
{
StatusCode = (int)_status,
};
context.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = _errorMessage;
await objectResult.ExecuteResultAsync(context);
}
}
And use the following form :
[HttpGet]
public IActionResult GetEmployee()
{
return new CustomError(HttpStatusCode.NotFound, "The employee was not found");
}
Try changing
return (IActionResult)msg;
to
return Task.FromResult(BadRequest(msg) as IActionResult);
In my API I have a Create method in my controller that accepts all of the models fields, but in the method I'm excluding the ID field since on a create it's generated. But in Swagger it's showing the following.
Is there a way for it not to show the following part?
"id": 0
Is a viewmodel how I should go about this?
I tried the following, but can't get it to work.
public class PartVM
{
public string Name { get; set; }
}
public interface IPartService
{
Task<Part> CreatePart(PartVM part);
Task<IEnumerable<Part>> GetParts();
Task<Part> GetPart(int partId);
}
public class PartService : IPartService
{
private readonly AppDbContext _appDbContext;
public PartService(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
public async Task<Part> CreatePart(PartVM part)
{
var _part = new Part()
{
Name = part.Name
};
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}
}
Here's my controller.
[Route("api/[controller]")]
[ApiController]
public class PartsController : ControllerBase
{
private readonly IPartService _partService;
public PartsController(IPartService partService)
{
_partService = partService;
}
[HttpPost]
public async Task<ActionResult<Part>> CreatePart(PartVM part)
{
try
{
if (part == null)
return BadRequest();
var _part = new Part()
{
Name = part.Name
};
var createdPart = await _partService.CreatePart(_part);
return CreatedAtAction(nameof(GetPart),
new { id = createdPart.Id}, createdPart);
}
catch (Exception /*ex*/)
{
return StatusCode(StatusCodes.Status500InternalServerError, "Error creating new record in the database");
}
}
I'm getting a build error saying "CS1503 Argument 1: cannot convert from 'MusicManager.Shared.Part' to 'MusicManager.Server.Data.ViewModels.PartVM'".
It's refering to "_part" in this line "var createdPart = await _partService.CreatePart(_part);".
Any help is appreciated, thank you!
you have a CreatePart method which receives a PartVM model, but you are sending a Part Model to it
change your method to this :
public async Task<Part> CreatePart(Part part)
{
var result = await _appDbContext.Parts.AddAsync(_part);
await _appDbContext.SaveChangesAsync();
return result.Entity;
}
I have wired up FluentValidation as per instructions, and when debuging test I can see that model is invalid based on the test setup, but exception is not thrown, but rather method on the controller is being executed. This is on 3.1 with EndPoint routing enabled. Is there anything else one needs to do to get this to work and throw. What happens is that validation obviously runs; it shows as ModelState invalid and correct InstallmentId is invalid, but it keeps processing in Controller instead of throwing exception.
services.AddMvc(
options =>
{
options.EnableEndpointRouting = true;
//// options.Filters.Add<ExceptionFilter>();
//// options.Filters.Add<CustomerRequestFilter>();
})
.AddFluentValidation(
config =>
{
config.RegisterValidatorsFromAssemblyContaining<Startup>();
})
Command and Validator
public class ProcessManualPayment
{
public class Command
: CustomerRequest<Result?>
{
public Guid PaymentPlanId { get; set; }
public Guid InstallmentId { get; set; }
public Guid PaymentCardId { get; set; }
}
public class Validator : AbstractValidator<Command>
{
public Validator()
{
this.RuleFor(x => x.CustomerId)
.IsValidGuid();
this.RuleFor(x => x.PaymentPlanId)
.IsValidGuid();
this.RuleFor(x => x.InstallmentId)
.IsValidGuid();
this.RuleFor(x => x.PaymentCardId)
.IsValidGuid();
}
}
Controller
[Authorize]
[HttpPost]
[Route("payments")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> ProcessManualPayment(
[FromBody]
ProcessManualPayment.Command command)
{
Test
[Fact]
public async Task When_Command_Has_Invalid_Payload_Should_Fail()
{
var client = this.factory.CreateClient();
// Arrange
var validCmd = new ProcessManualPayment.Command()
{
CustomerId = Guid.NewGuid(),
PaymentPlanId = Guid.NewGuid(),
InstallmentId = Guid.NewGuid(),
PaymentCardId = Guid.NewGuid(),
};
var validCmdJson = JsonConvert.SerializeObject(validCmd, Formatting.None);
var jObject = JObject.Parse(validCmdJson);
jObject["installmentId"] = "asdf";
var payload = jObject.ToString(Formatting.None);
// Act
var content = new StringContent(payload, Encoding.UTF8, MediaTypeNames.Application.Json);
var response = await client.PostAsync(MakePaymentUrl, content);
var returned = await response.Content.ReadAsStringAsync();
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
[Fact]
public async Task When_Payload_Is_Null_Should_Fail()
{
// Arrange
var client = this.factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(MakePaymentUrl, null);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
}
GuidValidator
public class GuidValidator : PropertyValidator
{
public GuidValidator()
: base("'{PropertyName}' value {AttemptedValue} is not a valid Guid.")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
context.MessageFormatter.AppendArgument("AttemptedValue", context.PropertyValue ?? "'null'");
if (context.PropertyValue == null)
{
return false;
}
Guid.TryParse(context.PropertyValue.ToString(), out var value);
return IsValid(value);
}
private static bool IsValid(Guid? value) =>
value.HasValue
&& !value.Equals(Guid.Empty);
}
Mystery solved, I was missing [ApiController] attribute on the controller.
I am using the FluentValidation library to auto-validate models which is working fine - however - there is a requirement to set an error code using the WithErrorCode() method in the validator (AbstractValidator<T>). This works fine as well, the problem is then retrieving that code from an ASP.NET MVC Core Action Filter defined as such:
public class ActionModelValidationAttribute : ActionFilterAttribute
{
readonly ILogger<ActionModelValidationAttribute> log;
public ActionModelValidationAttribute (ILogger<ActionModelValidationAttribute> log) => this.log = log;
public override void OnActionExecuting (ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var routeName = context.RouteData.Values["action"] ?? "unknown";
log.LogDebug($"model validation failed for {routeName}");
var errors = context.ModelState.Values.Where(state => state.Errors.Count > 0)
.SelectMany(errs => errs.Errors)
.Select(e => new BaseErrorResponse(){
Code = 404, // <<-- this is where I would like the code from WithErrorCode()
Details = e.Exception?.Message ?? "",
Message = e.ErrorMessage,
Field = "field"
}).ToList();
var response = new ValidationErrorResponseModel()
{
Message = "Bad Request",
Errors = errors
};
context.Result = new JsonResult(response)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
}
}
The type of errs is Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry
The type of e is Microsoft.AspNetCore.Mvc.ModelBinding.ModelError
Here is my Validator:
public class ViewModelValidator : AbstractValidator<ViewModel>
{
public ViewModelValidator() {
RuleFor(m => m.DistributorId)
.NotNull().WithErrorCode("910000")
.NotEmpty().WithErrorCode("910001");
}
}
It doesn't appear that the FluentValidation lib can handle this on it's own. A workaround is to implement the IValidatorInterceptor interface on the AbstractValidator<T> concrete implementation. Memory cache can be used to store the unique request id which then makes it possible to retrieve the id from cache from within the action filter. A ValidationResult object will be returned which has all of the rich validation information.
Code example(s) follow:
public abstract class BaseModelValidator<T> : AbstractValidator<T>, IValidatorInterceptor
{
protected readonly IMemoryCache cache;
protected readonly ILogger<BaseModelValidator<T>> log;
protected string RequestId { get; set; }
public BaseModelValidator(IMemoryCache cache, ILogger<BaseModelValidator<T>> log)
{
this.cache = cache;
this.log = log;
}
public virtual ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext)
{
RequestId = controllerContext.HttpContext.TraceIdentifier;
return validationContext;
}
public virtual ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result)
{
cache.Set(RequestId, result, TimeSpan.FromMinutes(1));
return result;
}
}
Global Action Filter:
public class ActionModelValidationAttribute : ActionFilterAttribute
{
readonly ILogger<ActionModelValidationAttribute> log;
readonly IMemoryCache cache;
public ActionModelValidationAttribute(IMemoryCache cache, ILogger<ActionModelValidationAttribute> log)
{
this.log = log;
this.cache = cache;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var key = context.HttpContext.TraceIdentifier;
cache.TryGetValue<ValidationResult>(key, out var result);
if (result == null) ReturnError(context, key); // impl ReturnError however you like
cache.Remove(key);
var count = result.Errors.Count();
var controllerName = context.RouteData.Values["Controller"] ?? "unknown";
var routeName = context.RouteData.Values["Action"] ?? "unknown";
var response = result.AsBaseResponse();
log.LogDebug($"Model validation failed. {count} errors in model for {controllerName}.{routeName}");
context.Result = new JsonResult(response)
{
StatusCode = (int)HttpStatusCode.BadRequest
};
}
}
}
I want to set constraint to activity to prevent adding it to some other activities.
I have problem with GetParentChain I think. I did everything like in msdn samples:
I have three activities: MyActivity, SqlNativeActivity and SqlActivity. This classes look like:
SqlNativeActivity:
public sealed class SqlNativeActivity : BaseNativeActivity
{
public Activity Activity { get; set; }
protected override void Execute(NativeActivityContext context)
{
}
}
public abstract class BaseNativeActivity : NativeActivity
{
protected ActivityConstraintsProvider ActivityConstraintsProvider;
protected abstract override void Execute(NativeActivityContext context);
}
SqlActivity:
public sealed class SqlActivity : BaseActivity
{
public Activity Activity { get; set; }
}
public abstract class BaseActivity : Activity
{
protected ActivityConstraintsProvider ActivityConstraintsProvider;
}
MyActivity:
public sealed class MyActivity : BaseActivity
{
public MyActivity()
{
ActivityConstraintsProvider = new ActivityConstraintsProvider();
ActivityConstraintsProvider.AddNotAcceptedParentActivity(typeof(SqlActivity));
ActivityConstraintsProvider.AddNotAcceptedParentActivity(typeof(SqlNativeActivity));
base.Constraints.Add(ActivityConstraintsProvider.CheckParent());
}
}
And I wrote ActivityConstraintsProvider in which I define List with not accepted parent types.
ActivityConstraintsProvider:
public class ActivityConstraintsProvider
{
private List<Type> _notAcceptedParentActivity;
public void AddNotAcceptedParentActivity(Type type)
{
if (_notAcceptedParentActivity == null)
_notAcceptedParentActivity = new List<Type>();
_notAcceptedParentActivity.Add(type);
}
public Constraint CheckParent()
{
var element = new DelegateInArgument<Activity>();
var context = new DelegateInArgument<ValidationContext>();
var result = new Variable<bool>();
var parent = new DelegateInArgument<Activity>();
var con = new Constraint<Activity>
{
Body = new ActivityAction<Activity, ValidationContext>
{
Argument1 = element,
Argument2 = context,
Handler = new Sequence
{
Variables =
{
result
},
Activities =
{
new ForEach<Activity>
{
Values = new GetParentChain
{
ValidationContext = context
},
Body = new ActivityAction<Activity>
{
Argument = parent,
Handler = new If()
{
Condition = new InArgument<bool>((env) => _notAcceptedParentActivity.Contains(parent.Get(env).GetType())),
Then = new Assign<bool>
{
Value = true,
To = result
},
}
}
},
new AssertValidation
{
Assertion = new InArgument<bool> { Expression = new Not<bool, bool> { Operand = result } },
Message = new InArgument<string> ("Decide can't be in Sql"),
}
}
}
}
};
return con;
}
}
And finally Main:
class Program
{
static void Main()
{
ValidationResults results;
Activity wf3 = new SqlActivity
{
Activity = new Sequence()
{
Activities =
{
new MyActivity
{
}
}
}
};
results = ActivityValidationServices.Validate(wf3);
Console.WriteLine("WF3 (SqlActivity):");
PrintResults(results);
//----------------------------------------------------------------
Activity wf4 = new SqlNativeActivity
{
Activity = new Sequence()
{
Activities =
{
new MyActivity
{
}
}
}
};
results = ActivityValidationServices.Validate(wf4);
Console.WriteLine("WF4 (SqlNativeActivity):");
PrintResults(results);
//----------------------------------------------------------------
}
static void PrintResults(ValidationResults results)
{
Console.WriteLine();
if (results.Errors.Count == 0 && results.Warnings.Count == 0)
{
Console.WriteLine(" No warnings or errors");
}
else
{
foreach (ValidationError error in results.Errors)
{
Console.WriteLine(" Error: " + error.Message);
}
foreach (ValidationError warning in results.Warnings)
{
Console.WriteLine(" Warning: " + warning.Message);
}
}
Console.WriteLine();
}
}
And the problem is that if my sql activity is inherites from System.Activities.NativeActivity (SqlNativeActivity) constraints are working very well, but if I define constraints and parent is activity inherites from System.Activities.Activity or System.Activities.CodeActivity constraints validation is not working at all.
Anybody can help me with my problem?
Thank you in advance :)
if you create a custom activity (inheriting from System.Activities.CodeActivity), your validation should be done at CacheMetaData:
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
//Validate here
base.CacheMetadata(metadata);
}