I have an ASP.NET MVC 4 app. I'm relatively new to ASP.NET MVC 4. Currently, I'm trying to build a basic Task list app.
public ActionResult Create()
{
var model = new TaskModel();
return View("~/Views/Task.cshtml", model);
}
[HttpPost]
public ActionResult Create(TaskModel model)
{
if (model.TaskName.Length == 0)
{
// Display error message
}
else
{
// Save to database
// Write success message
}
return View("~/Views/Task.cshtml", model);
}
If there is an error, I display an error message on the screen. My problem is, the previously entered values in the view are not shown. The entire view is blank.How do I preserve the values in the view in this case?
Thank you!
I use TempData for this.
public ActionResult Create()
{
var model = new TaskModel();
TempData["task"] = model;
return View("~/Views/Task.cshtml", model);
}
[HttpPost]
public ActionResult Create()
{
var task = (TaskModel)TempData["task"];
UpdateModel(task);
if (model.TaskName.Length == 0)
{
// Display error message
}
else
{
// Save to database
// Write success message
}
TempData["task"] = task;
return View("~/Views/Task.cshtml", model);
}
MVC works different than WebForms, since there is no concept of 'controls', you have to preserve the state yourself. Another option if you don't want to use TempData is to use an LosFormatter to Serialize your controls into a hidden HTML field. This would replicate the functionality of ViewState in ASP.NET WebForms
Related
I am trying to create a custom error code page that displays a message I pass to it in my .NET Core MVC 1.1 application. I setup custom error code pages support in the Startup.cs class file and then created a simple view in a controller that does public IActionResult Example1 => NotFound("Some custom error message"). I expected this message to be pushed to the controller however this is not the case. Calling NotFound() without any parameters hits the error controller but as soon as I pass a message through, the controller is never used and a simple text message is displayed.
I could have sworn I used to do this in the past with classic .NET MVC but it has been awhile.
How can I have custom error code pages that display the proper error. I also need the ability in a controller to return the standard text or JSON response during the error for cases when I expect a JSON response (API actions and such). I am assuming there is a way to do this with a attribute but I have yet to find a way to do either of these tasks.
What you could do is something similar to how the StatusCodePages middleware works. That middleware allows a pipeline re-execution model, to allow handling status code errors through the normal MVC pipeline. So when you return a non-successful status code from MVC, the middleware detects that and then re-executes the whole pipeline for a status code error route. That way, you are able to fully design status code errors. But as Chris Pratt already mentioned, those status codes are typically limited to just their code. There is not really a way to add additional details to it.
But what we could do is create our own error handling implementation on top of that re-execution model. For that, we create a CustomErrorResponseMiddleware which basically checks for CustomErrorResponseException exceptions and then re-executes the middleware pipeline for our error handler.
// Custom exceptions that can be thrown within the middleware
public class CustomErrorResponseException : Exception
{
public int StatusCode { get; set; }
public CustomErrorResponseException(string message, int statusCode)
: base(message)
{
StatusCode = statusCode;
}
}
public class NotFoundResponseException : CustomErrorResponseException
{
public NotFoundResponseException(string message)
: base(message, 404)
{ }
}
// Custom context feature, to store information from the exception
public interface ICustomErrorResponseFeature
{
int StatusCode { get; set; }
string StatusMessage { get; set; }
}
public class CustomErrorResponseFeature : ICustomErrorResponseFeature
{
public int StatusCode { get; set; }
public string StatusMessage { get; set; }
}
// Middleware implementation
public class CustomErrorResponseMiddleware
{
private readonly RequestDelegate _next;
private readonly string _requestPath;
public CustomErrorResponseMiddleware(RequestDelegate next, string requestPath)
{
_next = next;
_requestPath = requestPath;
}
public async Task Invoke(HttpContext context)
{
try
{
// run the pipeline normally
await _next(context);
}
catch (CustomErrorResponseException ex)
{
// store error information to be retrieved in the custom handler
context.Features.Set<ICustomErrorResponseFeature>(new CustomErrorResponseFeature
{
StatusCode = ex.StatusCode,
StatusMessage = ex.Message,
});
// backup original request data
var originalPath = context.Request.Path;
var originalQueryString = context.Request.QueryString;
// set new request data for re-execution
context.Request.Path = _requestPath;
context.Request.QueryString = QueryString.Empty;
try
{
// re-execute middleware pipeline
await _next(context);
}
finally
{
// restore original request data
context.Request.Path = originalPath;
context.Request.QueryString = originalQueryString;
}
}
}
}
Now, all we need to do is hook that up. So we add the middleware within our Startup.Configure, somewhere near the beginning:
app.UseMiddleware<CustomErrorResponseMiddleware>("/custom-error-response");
The /custom-error-response is the route that we are re-executing when a custom response is being requested. This can be a normal MVC controller action:
[Route("/custom-error-response")]
public IActionResult CustomErrorResponse()
{
var customErrorResponseFeature = HttpContext.Features.Get<ICustomErrorResponseFeature>();
var view = View(customErrorResponseFeature);
view.StatusCode = customErrorResponseFeature.StatusCode;
return view;
}
Since this uses MVC, this also needs a view:
#model ICustomErrorResponseFeature
#{
ViewData["Title"] = "Error";
}
<p>There was an error with your request:</p>
<p>#Model.StatusMessage</p>
And that’s basically all. Now, we can just throw our custom error response exceptions from our MVC actions to trigger this:
// generate a 404
throw new NotFoundResponseException("This item could not be found");
// or completely custom
throw new CustomErrorResponseException("This did not work", 400);
Of course, we could also expand this further, but that should be the basic idea.
If you are already using the StatusCodePages middleware, you might think whether all this custom re-execution is really necessary, when you already have exactly that in the StatusCodePages middleware. And well, it is not. We can also just expand on that directly.
For that, we will just add the context features, which we can set at any point during the normal execution. Then, we just return a status code, and let the StatusCodePages middleware run. Inside its handler, we can then look for our feature and use the information there to expand the status code error page:
// Custom context feature
public interface IStatusCodePagesInfoFeature
{
string StatusMessage { get; set; }
}
public class StatusCodePagesInfoFeature : IStatusCodePagesInfoFeature
{
public string StatusMessage { get; set; }
}
// registration of the StatusCodePages middleware inside Startup.Configure
app.UseStatusCodePagesWithReExecute("/error/{0}");
// and the MVC action for that URL
[Route("/error/{code}")]
public IActionResult StatusCode(int code)
{
var statusCodePagesInfoFeature = HttpContext.Features.Get<IStatusCodePagesInfoFeature>();
return View(model: statusCodePagesInfoFeature?.StatusMessage);
}
Inside of the normal controller actions, we can set that feature before returning a status code:
HttpContext.Features.Set<IStatusCodePagesInfoFeature>(new StatusCodePagesInfoFeature
{
StatusMessage = "This item could not be found"
});
return NotFound();
It is too bad you cannot intercept NotFound, Unauthorized, etc. responses in a middleware class.
Okay, option three! You can totally intercept those responses, just not inside of middleware, since these are MVC results and will not leave the MVC pipeline. So you have to intercept them within the MVC filter pipeline. But we could absolutely run a filter, for example a result filter, that modifies the result.
The problem is that we still need a way to pass the information on. We could use a context feature again, but we can also use the MVC object results. So the idea is that we can just do the following in the MVC actions:
return NotFound("The item was not found");
So usually, that string would be the plain text response. But before the result is being executed and the response is being generated, we can run a result filter to modify this and return a view result instead.
public class StatusCodeResultFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// retrieve a typed controller, so we can reuse its data
if (context.Controller is Controller controller)
{
// intercept the NotFoundObjectResult
if (context.Result is NotFoundObjectResult notFoundResult)
{
// set the model, or other view data
controller.ViewData.Model = notFoundResult.Value;
// replace the result by a view result
context.Result = new ViewResult()
{
StatusCode = 404,
ViewName = "Views/Errors/NotFound.cshtml",
ViewData = controller.ViewData,
TempData = controller.TempData,
};
}
// intercept other results here…
}
await next();
}
}
All you need is a view at Views/Errors/NotFound.cshtml now and everything will magically work once you have the filter registered.
You can either register the filter by adding a [TypeFilter(typeof(StatusCodeResultFilter))] attribute to the controller or individual actions, or you can register it globally.
What you want is not possible. When you do something like return NotFound with a message, that message will be included in the response body only if it's left unmolested. When you do something like enable status code pages, the NotFound is simply caught by the middleware, and the request will simply be handed off to your error handling action to ultimately obtain the response. Importantly, that means your original NotFoundResult along with any custom message has been round-filed.
I am new to MVC, have some experience in webforms.
I currently have an ActionResult function in my controller that binds all my SQL data to a model, which then I can then successfully build a dataview from in the view.
My issue is that to have everything work properly, I need to call my ActionResult directly in the controller before rendering the page, and this works just fine:
public ActionResult Index()
{
Bind_grid();
return View();
}
public class My_Table
{
public string info_a { get; set; }
public string info_b { get; set; }
public string mod_id { get; set; }
}
public ActionResult Bind_grid()
{
SqlConnection checkitem = new SqlConnection("Data Source=[IPADDRESS];Persist Security Info=True;User ID=userid;Password=password");
string query = "SELECT id, info_a, info_b FROM [my_db].[dbo].[ref_index]";
SqlCommand execute = new SqlCommand(query, checkitem);
var model = new List<My_Table>();
using (checkitem)
{
checkitem.Open();
SqlDataReader rdr = execute.ExecuteReader();
while (rdr.Read())
{
var table = new My_Table();
table.info_a = rdr["info_a"].ToString();
table.info_b = rdr["info_b"].ToString();
table.mod_id = rdr["id"].ToString();
model.Add(table);
}
checkitem.Close();
}
return View(model);
}
I am wondering how to refresh the model from the view instead of requiring it to be ran before returning the view, I've tried AJAX calls and #HTML.Action calls to Bind_grid, but I can't seem to get the page to refresh the data.
Any suggestions in running Bind_grid from a view to refresh the model?
Currently I try this:
function bind_data() {
$.ajax({
url: '#Url.Action("Bind_grid")',
method: 'POST',
});
#{
int p = 0;
foreach (var d in Model)
{
#:console.log(#d.mod_id);
p++;
}
}
}
And I set a javascript interval for 10 seconds to run the bind_data() function,
remove some IDs from the SQL database between intervals,
but the data written to the console is still the original fetched, not the refreshed data.
If I manually refresh the page (F5), I get a correct update of data.
I'm guessing I need to do some kind of partial postback to refresh the razor data, but am not sure the best way to do it in MVC. Any help is appreciated.
You should be able to use the jquery load method, call in your JavaScript interval
$("#grid").load("bind_grid");
Fixed the issue by doing all processing in the codebehind, changed the Bind_grid()
to JsonResult, returning Json(model.ToArray(), JsonRequestBehavior.AllowGet);
Was able to do successful pulls via ajax request.
I want to add posts to threads in my forum project, but to do so I need to pass a parameter with thread ID, so after creating post it will redirect me back to that specific thread, but the problem is that I have no idea how to pass that parameter...
Here is my Create() code:
// GET: /Posts/Create
public ActionResult Create(int id)
{
ViewBag.ThreadId = new SelectList(db.Albums, "ThreadId", "Title");
ViewBag.IdOfThread = id;
return View();
}
//
// POST: /Posts/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Posts posts)
{
if (ModelState.IsValid)
{
db.Posts.Add(posts);
db.SaveChanges();
return RedirectToAction("Index", new { **id = 5** });
}
ViewBag.ThreadId = new SelectList(db.Albums, "ThreadId", "Title", posts.ThreadId);
//ViewBag.IdOfThread = id;
return View(posts);
}
When I strongly type number id = 5 it works as intended, so how can I make ActionResult Create(Posts posts) see my ViewBoxes from Create View? Or maybe there is some better way to do that without using ViewBoxes?
Through the glory of EF when you add a model to the Entity and call SaveChanges() it will automatically put the ID back into the model.
if (ModelState.IsValid)
{
db.Posts.Add(posts);
db.SaveChanges();
// Replace Id with whatever your auto increment PK is.
return RedirectToAction("Index", new { id = posts.Id });
}
I am trying to add row to table using asp.net mvc kendo ui. But for the first time, it is a success. when do second time, the debugging pointer says "The process or thread has been changed since the last step" and try to add the first insertion values also. Since the table does not allow the duplication of primary key the insertion fails at second time. Please advie.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Insert([DataSourceRequest] DataSourceRequest request, AdminHeaderImage batchModel)
{
if (ModelState.IsValid)
{
using (var Entity = new DealerEntities())
{
Entity.AdminHeaderImages.AddObject(batchModel);
Entity.SaveChanges();
return RedirectToAction("Index");
}
}
else
{
return RedirectToAction("Index");
}
}
public ActionResult Index()
{
using (var Entity = new DealerEntities())
{
var outPut = Entity.AdminHeaderImages.ToList();
return View(outPut);
}
}
It seems your Entity object is shared between requests.
I recommend having an entity object per web request.
This article explains more
http://blogs.microsoft.co.il/blogs/gilf/archive/2010/05/18/how-to-manage-objectcontext-per-request-in-asp-net.aspx
I just used the following code at the end of Insert which would refresh my entity.
"return Json(new [] { product }.ToDataSourceResult(request, ModelState));"
This solved my problem.
I am working with the following code. I have set up the form, models, and controllers necessary to add an item. I know the code works because after entering text into the text field I am redirect to the correct page. My question is: How do I add the text entered by the user into the default database?
public ActionResult TruckAdd()
{
ViewBag.DisplayText = "Add A Truck";
return View();
}
[HttpPost]
public ActionResult TruckAdd(RegisterTruckModel model)
{
if (ModelState.IsValid)
{
// Attempt to register the Truck
try
{
return RedirectToAction("Index", "Dashboard");
}
catch
{
}
}
// If we got this far, something failed, redisplay form
return View(model);
}