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.
Related
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.
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.
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
Here's the situation. I've got a single action filter that I'm using in two different controllers. The action filter is defined as:
public class ValidSubmissionAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller;
var session = filterContext.HttpContext.Session;
var isValid = controller.TempData["IsValid"];
if (isValid == null || !(bool)isValid)
{
SharedUtilities.LogOutUser(session, controller.ViewData.ModelState);
filterContext.Result = SharedUtilities.GetThankYouRedirect();
}
}
}
When I invoke the Attribute in one controller, like this:
[HttpPost]
public ActionResult DoSomething(string button, Model data)
{
try
{
if (ModelState.IsValid)
{
TempData["IsValid"] =
Request.Form["ValidRequest"] == Session.SessionID;
Session["VerifyDoingSomethingData"] = data;
return RedirectToAction("VerifyDoingSomething");
}
}
catch (Exception ex)
{
}
}
[ValidSubmission]
public ActionResult VerifyDoingSomething()
{
ViewData.Model = Session["VerifyDoingSomethingData"];
return View("VerifyDoingSomething");
}
it functions as expected. However, when I call it from a different controller, like this:
[HttpPost]
public ActionResult Index(string button, Model data)
{
try
{
if (ModelState.IsValid)
{
TempData["IsValid"] =
Request.Form["ValidRequest"] == Session.SessionID;
Session["ViewModel"] = data;
return RedirectToAction("VerifyCancellation");
}
}
catch (Exception ex)
{
}
}
[ValidSubmission]
public ActionResult VerifyCancellation()
{
ViewData.Model = Session["ViewModel"];
return View("VerifyCancellation");
}
the attribute doesn't run at all. My breakpoint in the OnActionExecuting method doesn't get hit.
If I had to guess, I'd say there was some difference in the controllers or in the action methods, but they appear to be functionally similar. Any insights? Why would I be seeing such different behavior?
Aaaaand, I'm a schmuck.
Turns out there's a completely different execution path that I'd forgotten about. That path didn't have the TempData information to use in the ValidSubmisionAttribute. Everything is functioning correctly now.
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.