On an Web API controller I have the following:
if (!ModelState.IsValid)
return BadRequest();
BadRequest: https://msdn.microsoft.com/en-us/library/system.web.http.apicontroller_methods%28v=vs.118%29.aspx
I would like to create a custom error message to pass errors as json.
Something like:
if (!ModelState.IsValid)
return ModelHasErrors(errors);
How can I create a custom error response?
The simplest way is
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ModelState);
I am used to use special object, that represents error response. I can add my specific (translated) message, status code, etc.
public class ErrorModel
{
public ErrorModel(HttpStatusCode statusCode, string message)
{
StatusCode = (int)statusCode;
Message = message;
ValidationErrors = new Dictionary<string, ModelErrorCollection>();
}
public ErrorModel(HttpStatusCode statusCode)
{
StatusCode = (int)statusCode;
ValidationErrors = new Dictionary<string, ModelErrorCollection>();
}
public string Message { get; set; }
public int StatusCode { get; set; }
public Dictionary<string, ModelErrorCollection> ValidationErrors { get; set; }
public Exception Exception { get; set; }
}
Then I have extension for CreateCustomResponse
public static class ApiExtensions
{
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string errorMessage)
{
var errorMessageModel = new ErrorModel(statusCode, errorMessage);
return request.CreateResponse(statusCode, errorMessageModel);
}
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request, HttpStatusCode statusCode, Exception exception, string errorMessage = "")
{
if (string.IsNullOrEmpty(errorMessage) && exception != null)
{
errorMessage = exception.Message;
}
var errorMessageModel = new ErrorModel(statusCode, errorMessage)
{
Exception = exception
};
return request.CreateResponse(statusCode, errorMessageModel);
}
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request,
HttpStatusCode statusCode, ModelStateDictionary modelState, string errorMessage = "")
{
if (string.IsNullOrEmpty(errorMessage))
{
errorMessage = ApiValidationMessages.GeneralModelIsNotValid;
}
var errorMessageModel = new ErrorModel(statusCode, errorMessage);
foreach (var error in modelState.Where(x => x.Value.Errors != null && x.Value.Errors.Any()))
{
errorMessageModel.ValidationErrors.Add(error.Key.Replace("model.", ""), error.Value.Errors);
}
return request.CreateResponse(statusCode, errorMessageModel);
}
}
And finally in my controllers I just call:
return Request.CreateCustomResponse(HttpStatusCode.NotFound, ApiHttpResultMessages.NotFound);
You can find inspiration in my CodePlex project Web API Design: https://webapidesign.codeplex.com/
You can return directly whichever object you want, and it will be serialized as JSON. It can even be an anonymous class object created with new { }
On The client side you have to check if you've received a regurlar response, or the error object, which can be easyly donde by checking the existence of some property of your custom error object.
You could return a HttpResponseMessage using an object containing your error messages (in this example errors) as content:
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent(typeof(ErrorClass), errors, new JsonMediaTypeFormatter())
};
More information about how to return action results can be found here.
You can obviously reuse the above code by creating a method and call it like in your example: ModelHasErrors(errors).
However, if you often find yourself returning the same response, a nice option would be to create an custom exception filter which would return the same response whenever ModelState.IsValid is false.
Related
When performing inline edit in a jquery/jqgrid table, I am seeing the ID (PK) and the column values come across to my public IActionResult Edit URL with a 200 response. However I am then encountering an error where my DBContext does not exist, however it's the same dbcontext I used to retrieve the data. Also, not able to return a string message? The code up until my Edit works, what am I doing wrong?
namespace MyProject.Controllers
{
public class CheckRequestController : Controller
{
private readonly MyDBContext _context;
public CheckRequestController(MyDBContext context)
{
_context = context;
}
public IActionResult Index()
{
return View();
}
public IActionResult LoadData()
{
try
{
var fileData = (from tempFile in _context.CheckRequest
select tempFile);
var data = fileData.ToList();
return Json(data);
}
catch (Exception)
{
throw;
}
}
// I'm trying to run an update to the row in SQL Server. I see my ID and the values come across 200 status but can't update.. Also can't return string from IAction
// Also can't return string from IActionResult or ActionResult. How to return message if failure
public IActionResult Edit(CheckRequest checkrequests)
{
try
{
if (ModelState.IsValid)
{
using (MyDBContext db = new MyDBContext())
{
db.Entry(checkrequests).State = EntityState.Modified;
db.SaveChanges();
}
}
}
catch (Exception ex)
{
//msg = "Error occured:" + ex.Message;
var msg = new string[] { "Error occured:" + ex.Message };
}
return msg;
}
}
}
I removed the readonly and was able to trace the server and see the request coming across; can't believe my eyes didn't catch that. Still curious how to capture and return an error message that's useful though on the return in the IActionResult. Right now I changed it to return Ok() so it's not really useful if an error is to occur.
I am trying to execute custom asyncCodeActivity in UIPath. Added the package, passing all data, however UIPath just hangs when it reaches custom activity and does not throw any exceptions/or stops. I tried to create Class Library using CodeActivity and AsyncCodeActivity - my activity should make several APICalls but I get result it just stops when it reaches my custom activity and does not go to the next one. Is there any example how to create async custom activity for UIPath? My class library worked ok when I tried to test it outside of UIpath. Will appreciate any help.
My class library using CodeActivity:
public class AddInvoice : CodeActivity
{
[Category("Input")]
[RequiredArgument]
public InArgument<string> PickupZip { get; set; }
[Category("Output")]
[RequiredArgument]
public OutArgument<String> Output { get; set; }
public async Task<string> ApiTest(CodeActivityContext context)
{
try
{
var origin = await GoogleAPIWrapper.GetAddressByZip(PickupZip.Get(context));
string PickupAddress;
string DeliveryAddress;
var inv = new IList();
if (origin.StatusId >= 0)
{
invoice.PickupCity = origin.Locality;
invoice.PickupState = origin.AdminLevel1;
}
else
{
invoice.PickupCity = null;
invoice.PickupState = null;
}
var tkn = token.Get(context);
var client = new HttpClient();
HttpClientHandler handler = new HttpClientHandler();
client = new HttpClient(handler, false);
client.BaseAddress = new Uri("http://test.test.com/");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tkn);
StringContent content = new StringContent(JsonConvert.SerializeObject(inv), Encoding.UTF8, "application/json");
var response = await client.PostAsync("api/insert/", content);
var resultContent = response.StatusCode;
Output.Set(context, resultContent.ToString());
}
catch (Exception e)
{
Output.Set(context, e.ToString());
}
return "ok";
}
protected override void Execute(CodeActivityContext context)
{
try
{
string result = ApiTest(context).GetAwaiter().GetResult();
}
catch (Exception e)
{
Output.Set(context, e.ToString());
}
}
public class IList
{
public string PickupState { get; set; }
public string PickupCity { get; set; }
}
}
Classes that derive from CodeActivity are synchronous by default. Since UiPath is based on Windows Workflow, deriving from an AsyncCodeActivity class should work.
You didn't ask explicitly for it, but since you're essentially calling a web service, have a look at the Web Activities package, the HTTP Request in particular. This also comes with JSON deserialization. You can find more information about web service integration here, for example (disclaimer: I am the author).
I am using .Net framework 4.6.1 and Swashbuckle version 5.3.2 in my WebApi project. Swagger UI is not giving an option to send the input as a request body to my POST Api which uses a custom model binder.
- Model Used :
[ModelBinder(typeof(FieldValueModelBinder))]
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public string City { get; set; }
}
- API Post method used:
[HttpPost]
// POST: api/Employee
public HttpResponseMessage Post([ModelBinder(typeof(FieldValueModelBinder))]Employee emp)
{
if (!ModelState.IsValid)
return Request.CreateResponse(HttpStatusCode.BadRequest, "Please provide valid input");
else
//Add Employee logic here
return Request.CreateResponse(HttpStatusCode.OK, "Employee added sucessfully");
}
- Model Binder used :
public class FieldValueModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
/// <summary>
/// Store received data in API in KeyValuePair
/// </summary>
private List<KeyValuePair<string, string>> kvps;
/// <summary>
/// Storing error while binding data in Model class
/// </summary>
private Dictionary<string, string> dictionaryErrors = new Dictionary<string, string>();
/// <summary>
/// Implementing Base method and binding received data in API to its respected property in Model class
/// </summary>
/// <param name="actionContext">Http Action Context</param>
/// <param name="bindingContext">Model Binding Context</param>
/// <returns>True if no error while binding. False if any error occurs during model binding</returns>
public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
{
try
{
var bodyString = actionContext.Request.Content.ReadAsStringAsync().Result;
if (actionContext.Request.Method.Method.ToUpper().Equals("GET"))
{
var uriContext = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query);
if (uriContext.HasKeys())
{
this.kvps = uriContext.AllKeys.ToDictionary(k => k, k => uriContext[k]).ToList<KeyValuePair<string, string>>();
}
}
else if (!string.IsNullOrEmpty(bodyString))
{
this.kvps = this.ConvertToKvps(bodyString);
}
else
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Please provide valid input data.");
return false;
}
}
catch (Exception ex)
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Please provide data in a valid format.");
return false;
}
// Initiate primary object
var obj = Activator.CreateInstance(bindingContext.ModelType);
try
{
this.SetPropertyValues(obj);
}
catch (Exception ex)
{
if (this.dictionaryErrors.Any())
{
foreach (KeyValuePair<string, string> keyValuePair in this.dictionaryErrors)
{
bindingContext.ModelState.AddModelError(keyValuePair.Key, keyValuePair.Value);
}
}
else
{
bindingContext.ModelState.AddModelError("Internal Error", ex.Message);
}
this.dictionaryErrors.Clear();
return false;
}
// Assign completed Mapped object to Model
bindingContext.Model = obj;
return true;
}
I am facing below issues:
When we use ‘ModelBinder’ in our post method, Swagger UI is
displaying this screen where the input parameter are posted in a
query string and CustomModelBinder is invoked and tries to read
request body to perform model binding and validation and gets null in
this case.
Public HttpResponseMessage Post([ModelBinder(typeof(FieldValueModelBinder))]Employee emp)
When we use ‘FromBody’ in our post method, Swagger UI displays this
screen where we can send the input in a request body, but in this
case CustomModelBinder is not invoked and we are not able to perform
modelbinding and validation.
public HttpResponseMessage Post([FromBody]Employee emp)
When we try using both ‘modelbinder’ and ‘frombody’, Swagger UI takes
the input as a query and we get the below response:
Tried with Postman, the API works fine and we are able to pass the input in request body and get the proper output. The custom model binding also works and populates the error message in case of invalid model state and we can then use those messages to send in the response.
What needs to be changed to invoke the custom model binder from Swagger UI while posting input data to API in request body. Please Suggest.
You can do that with an IDocumentFilter here is the code:
private class ApplyDocumentVendorExtensions : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
{
if (swaggerDoc != null)
{
foreach (var path in swaggerDoc.paths)
{
if (path.Value.post != null && path.Value.post.parameters != null )
{
var parameters = path.Value.post.parameters;
if (parameters.Count == 3 && parameters[0].name.StartsWith("emp"))
{
path.Value.post.parameters = EmployeeBodyParam;
}
}
}
}
}
private IList<Parameter> EmployeeBodyParam
{
get
{
return new List<Parameter>
{
new Parameter {
name = "emp",
#in = "body",
required = true,
schema = new Schema {
#ref = "#/definitions/Employee"
}
}
};
}
}
}
We have a rest interface which all works fine, but some customers wanted the ability to upload xml files via a portal.
I parse the file upload and then create the respective controller that would have been used if the data had been sent in the correct format via the rest interface.
Everything works, except an error message is returned from the nested controller to my ajax, ONLY if i return Ok(); which I find really weird. It must be quite late in the pipeline that something goes wrong, as I can't seem to catch any exception at all.
I've commented some code below with some example controllers to show what I mean.
public class FileController : ApiController
{
[HttpPost]
public async Task<IHttpActionResult> UploadFile()
{
// if I return Ok(); from here it works fine.. so it's not the ajax
// Get the file details
var someParam = blah;
var someData = blah;
try
{
var restController = new RestController();
return await restController.SomeMethod(someParam, someData);
}
catch(Exception ex)
{
// No error is ever caught here
return new HttpExceptionResult(HttpStatusCode.InternalServerError, "Error uploading file");
}
}
}
public class RestController : ApiController
{
[HttpPost]
public async Task<IHttpActionResult> SomeMethod(someParam, someData)
{
// This returns fine
return new HttpExceptionResult(HttpStatusCode.409, "Something already exists");
// this fails -> returns okay, but my ajax receives a 500 internal server error
return Ok();
}
}
public class HttpExceptionResult : IHttpActionResult
{
private readonly HttpStatusCode _statusCode;
private readonly string _message;
public HttpExceptionResult(HttpStatusCode statusCode, string message)
{
_statusCode = statusCode;
_message = message;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(_statusCode);
response.Content = new StringContent(_message);
return Task.FromResult(response);
}
}
I'm sure there's a few things I could try to fix it straight off the bat (using my own class to return the 200 code), but i'd like to understand why this is happening internally. Does anyone have any ideas?
Revisited this issue again to see if I can get around it, I only managed to find one way so far.
The trick appears to be passing the current ControllerContext to the new Controller.
public class FileController : ApiController
{
[HttpPost]
public async Task<IHttpActionResult> UploadFile()
{
var someParam = blah;
var someData = blah;
try
{
var restController = new RestController()
{
ControllerContext = this.ControllerContext
};
// This now works
return await restController.SomeMethod(someParam, someData);
}
catch(Exception ex)
{
return new HttpExceptionResult(HttpStatusCode.InternalServerError, "Error uploading file");
}
}
}
Hope this helps anyone with a similar problem
Don't want to over-complicate the issue, but I think I need to post all the code that's hooked into this error.
Using MvcMailer and introduced a separate Send mechanism (for use with Orchard CMS' own EMail).
The MvcMailer Code:
1) AskUsMailer.cs:
public class AskUsMailer : MailerBase, IAskUsMailer
{
public AskUsMailer()
: base()
{
//MasterName = "_Layout";
}
public virtual MvcMailMessage EMailAskUs(AskUsViewModel model)
{
var mailMessage = new MvcMailMessage { Subject = "Ask Us" };
ViewData.Model = model;
this.PopulateBody(mailMessage, viewName: "EMailAskUs");
return mailMessage;
}
}
2) IAskUsMailer.cs:
public interface IAskUsMailer : IDependency
{
MvcMailMessage EMailAskUs(AskUsViewModel model);
}
3) AskUsController.cs: (GETTING NULL REFERENCE ERROR BELOW)
[Themed]
public ActionResult Submitted()
{
//This is the new call (see new code below):
//Note: Debugging steps through eMailMessagingService,
//then shows the null reference error when continuing to
//SendAskUs
eMailMessagingService.SendAskUs(askUsData);
//Below is normal MvcMailer call:
//AskUsMailer.EMailAskUs(askUsData).Send();
return View(askUsData);
}
Note: askUsData is defined in a separate block in the controller:
private AskUsViewModel askUsData;
protected override void OnActionExecuting(ActionExecutingContext
filterContext)
{
var serialized = Request.Form["askUsData"];
if (serialized != null) //Form was posted containing serialized data
{
askUsData = (AskUsViewModel)new MvcSerializer().
Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(askUsData);
}
else
askUsData = (AskUsViewModel)TempData["askUsData"] ??
new AskUsViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext
filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["askUsData"] = askUsData;
}
I did not know how to get my EMailMessagingService.cs (see below) call into the controller, so in a separate block in the controller I did this:
private IEMailMessagingService eMailMessagingService;
public AskUsController(IEMailMessagingService eMailMessagingService)
{
this.eMailMessagingService = eMailMessagingService;
}
I think this is part of my problem.
Now, the new code trying to hook into Orchard's EMail:
1) EMailMessagingServices.cs:
public class EMailMessagingService : IMessageManager
{
private IAskUsMailer askUsMailer;
private IOrchardServices orchardServices;
public EMailMessagingService(IAskUsMailer askUsMailer,
IOrchardServices orchardServices)
{
this.orchardServices = orchardServices;
this.askUsMailer = askUsMailer;
this.Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
public void SendAskUs(AskUsViewModel model)
{
var messageAskUs = this.askUsMailer.EMailAskUs(model);
messageAskUs.To.Add("email#email.com");
//Don't need the following (setting up e-mails to send a copy anyway)
//messageAskUs.Bcc.Add(AdminEmail);
//messageAskUs.Subject = "blabla";
Send(messageAskUs);
}
....
}
The EMailMessagingService.cs also contains the Send method:
private void Send(MailMessage messageAskUs)
{
var smtpSettings = orchardServices.WorkContext.
CurrentSite.As<SmtpSettingsPart>();
// can't process emails if the Smtp settings have not yet been set
if (smtpSettings == null || !smtpSettings.IsValid())
{
Logger.Error("The SMTP Settings have not been set up.");
return;
}
using (var smtpClient = new SmtpClient(smtpSettings.Host,
smtpSettings.Port))
{
smtpClient.UseDefaultCredentials =
!smtpSettings.RequireCredentials;
if (!smtpClient.UseDefaultCredentials &&
!String.IsNullOrWhiteSpace(smtpSettings.UserName))
{
smtpClient.Credentials = new NetworkCredential
(smtpSettings.UserName, smtpSettings.Password);
}
if (messageAskUs.To.Count == 0)
{
Logger.Error("Recipient is missing an email address");
return;
}
smtpClient.EnableSsl = smtpSettings.EnableSsl;
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
messageAskUs.From = new MailAddress(smtpSettings.Address);
messageAskUs.IsBodyHtml = messageAskUs.Body != null &&
messageAskUs.Body.Contains("<") &&
messageAskUs.Body.Contains(">");
try
{
smtpClient.Send(messageAskUs);
Logger.Debug("Message sent to {0} with subject: {1}",
messageAskUs.To[0].Address, messageAskUs.Subject);
}
catch (Exception e)
{
Logger.Error(e, "An unexpected error while sending
a message to {0} with subject: {1}",
messageAskUs.To[0].Address, messageAskUs.Subject);
}
}
}
Now, in EMailMessagingService.cs I was getting an error that things weren't being implemented, so I auto-generated the following (don't know if this is part of my error):
public void Send(Orchard.ContentManagement.Records.ContentItemRecord recipient, string type, string service, System.Collections.Generic.Dictionary<string, string> properties = null)
{
throw new NotImplementedException();
}
public void Send(System.Collections.Generic.IEnumerable<Orchard.ContentManagement.Records.ContentItemRecord> recipients, string type, string service, System.Collections.Generic.Dictionary<string, string> properties = null)
{
throw new NotImplementedException();
}
public void Send(System.Collections.Generic.IEnumerable<string> recipientAddresses, string type, string service, System.Collections.Generic.Dictionary<string, string> properties = null)
{
throw new NotImplementedException();
}
public bool HasChannels()
{
throw new NotImplementedException();
}
public System.Collections.Generic.IEnumerable<string> GetAvailableChannelServices()
{
throw new NotImplementedException();
}
2) IEMailMessagingServices.cs
public interface IEMailMessagingService
{
MailMessage SendAskUs(AskUsViewModel model);
}
MvcMailer works fine without this addition (outside of Orchard), but I am trying to get everything working within Orchard.
I just cannot figure out what I am doing wrong. Any thoughts?
Sorry for excessive code.
IEmailMessaginService does not implement IDependency, so it can't be found by Orchard as a dependency. That's why it's null.