Checking to see if ViewBag has a property or not, to conditionally inject JavaScript - asp.net-mvc-3

Consider this simple controller:
Porduct product = new Product(){
// Creating a product object;
};
try
{
productManager.SaveProduct(product);
return RedirectToAction("List");
}
catch (Exception ex)
{
ViewBag.ErrorMessage = ex.Message;
return View("Create", product);
}
Now, in my Create view, I want to check ViewBag object, to see if it has Error property or not. If it has the error property, I need to inject some JavaScript into the page, to show the error message to my user.
I created an extension method to check this:
public static bool Has (this object obj, string propertyName)
{
Type type = obj.GetType();
return type.GetProperty(propertyName) != null;
}
Then, in the Create view, I wrote this line of code:
#if (ViewBag.Has("Error"))
{
// Injecting JavaScript here
}
However, I get this error:
Cannot perform runtime binding on a null reference
Any idea?

#if (ViewBag.Error!=null)
{
// Injecting JavaScript here
}

Your code doesnt work because ViewBag is a dynamic object not a 'real' type.
the following code should work:
public static bool Has (this object obj, string propertyName)
{
var dynamic = obj as DynamicObject;
if(dynamic == null) return false;
return dynamic.GetDynamicMemberNames().Contains(propertyName);
}

Instead of using the ViewBag, use ViewData so you can check for the of the item you are storing. The ViewData object is used as Dictionary of objects that you can reference by key, it is not a dynamic as the ViewBag.
// Set the [ViewData][1] in the controller
ViewData["hideSearchForm"] = true;
// Use the condition in the view
if(Convert.ToBoolean(ViewData["hideSearchForm"])
hideSearchForm();

I would avoid ViewBag here completely.
See my thoughts here on this:
http://completedevelopment.blogspot.com/2011/12/stop-using-viewbag-in-most-places.html
The alternative would be to throw a custom error and catch it. how do you know if the database is down, or if its a business logic save error? in the example above you just catch a single exception, generally there is a better way to catch each exception type, and then a general exception handler for the truly unhandled exceptions such as the built in custom error pages or using ELMAH.
So above, I would instead
ModelState.AddModelError()
You can then look at these errors (assuming you arent jsut going to use the built in validation) via
How do I access the ModelState from within my View (aspx page)?
So please carefully consider displaying a message when you catch 'any' exception.

You can use ViewData.ContainsKey("yourkey").
In controller:
ViewBag.IsExist = true;
In view:
if(ViewData.ContainsKey("IsExist")) {...}

I need to test this but:
#if (ViewBag.ABoolParam ?? false)
{
//do stuff
}
I think will give you either the value of the ViewBag property, or return a default value if missing.

Related

Vibe.d basic form validation

I have a post create method:
void gönderiyiOluştur(HTTPServerRequest istek, HTTPServerResponse yanıt)
{
render!("gönderiler/oluştur.dt")(yanıt);
}
and a post store method like this:
void gönderiyiKaydet(HTTPServerRequest istek, HTTPServerResponse yanıt)
{
auto başlık = istek.form["baslik"];
auto içerik = istek.form["icerik"];
bool yayınla = false;
if (başlık.length > 0)
{
Gönderi gönderi = Gönderi(başlık, içerik);
gönderi.kaydet();
yanıt.redirect("/");
}
else
{
yanıt.redirect("/gönderiler/oluştur");
}
}
I'd like to make basic form validation. For example if input fields are empty it redirects to previous page.
I suppose I should pass some error message to the create method like baslik field should not be empty etc..
But since I am quite new to framework I shouldn't figure out. Are there any facilities does the framework offer for form validation.
Basic form validation is easy when you use the web framework from vibe.d. The basic steps are:
Create a class Gönderiyi and put your kaydet method inside this class:
class Shipment {
#method(HTTPMethod.POST)
void kaydet() { ... }
}
Define a method inside the class which should be called in case a validations fails. This method should display the error message:
void getError(string _error = null, HTTPServerResponse res) { ... }
Annotate the kaydet method with the #errorDisplay attribute to connect the method with the error function:
class Shipment {
#method(HTTPMethod.POST)
#errorDisplay!getError
void kaydet() { ... }
void getError(string _error = null, HTTPServerResponse res) { ... }
}
Now do the validation inside the kaydet method and throw an exception in case of an error. The getError method is then called automatically. You can take advantage of parameter binding and conversion, too. When the D parameter name is the same as the name of an HTML input value, then this value is bind to the D parameter. Automatic type conversion takes place (e.g. to int) and can lead to exceptions, which are then handled in the getError method, too.
As last step you need to register your class with the web framework:
auto router = new URLRouter;
router.registerWebInterface(new Gönderiyi);
You should have a look at the documentation of errorDisplay and at the web framework example from vibe.d, too.

MVC 3 when is the Controller.ViewData.ModelState is populated

Currently I am reading the MVC 3 source code to try to find when is the Controller's ModelState is set.
From the code I can see from Controller.cs that ModelState property was delegated to its ViewData's ModelStata property, like the code below:
public ModelStateDictionary ModelState {
get {
return ViewData.ModelState;
}
}
However I can only see the ViewData.ModelState seems only populated through ValidateModel() method in Controller.cs, like the code below:
protected internal void ValidateModel(object model, string prefix) {
if (!TryValidateModel(model, prefix)) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentCulture,
MvcResources.Controller_Validate_ValidationFailed,
model.GetType().FullName
)
);
}
}
in above code TryValidateModel() method would indirectly populate the Controller.ModelState like code below:
foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(metadata, ControllerContext).Validate(null)) {
ModelState.AddModelError(DefaultModelBinder.CreateSubPropertyName(prefix, validationResult.MemberName), validationResult.Message);
}
However I searched through all the source code and did not find any place that calls ValidateModel() method, and also from the access modifier, this method is protected, I wonder how this method is called by MVC 3 framework during the request processing, or ValidateModel() is only supposed to be called by user in the inherited controller class.

Extension methods cannot be dynamically dispatched error - how do I solve this?

Can't find the proper solution to this problem.
I am using [Serializable] (MVC3 Futures) in order to have a "wizard" with separate views. Here is the code in my controller to serialize:
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null) //Form was posted containing serialized data
{
myData = (MyViewModel)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
Further along in my controller I do something like this (just a snippet - code goes through wizard with next and back button strings):
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("Details");
else if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Submitted");
else
return View(myData);
}
In my .cshtml view, I have this:
#using (Html.BeginFormAntiForgeryPost())
{
#Html.Hidden("myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed))
...
#Html.TextBoxFor(m => model.Step.EMail)
...
}
Because I am using dynamics, I have to use a variable instead in the view:
var model = (MyViewModel) Model.myData;
in order to do the #Html.TextBoxFor above. And herein lies my probelm, because if I do #model MyViewModel instead, then I can't do model.Step.EMail. But because of dynamics, the #Html.Hidden won't work and I get the following error:
Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper'
has no applicable method named 'Hidden' but appears to have an
extension method by that name. Extension methods cannot be dynamically
dispatched. Consider casting the dynamic arguments or calling the
extension method without the extension method syntax.
I can switch to some other way of doing this without [Serializable], but then I have to convert a LOT of code. Is there any way to make this work?
The extension method is not identifying the method because, the data type does not match.
Try cast as object.
#Html.Hidden("myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed) as Object)
or
#Html.Hidden("myData", (Object)new MvcSerializer().Serialize(Model, SerializationMode.Signed))
It will works.
You can call
#(InputExtensions.Hidden(Html, "myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed)))
instead of #Html.Hidden(...)
It is calling the extension method without the extension method syntax.

MVC3 Custom error pages gives blank result

Using the blog posted here and a topic here on SO i've created a controller which should handle all my error pages.
In my Global.asax.cs I've got the following piece of code:
protected void Application_Error()
{
var exception = Server.GetLastError();
var httpException = exception as HttpException;
var routeData = new RouteData();
Response.Clear();
Server.ClearError();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "General";
routeData.Values["exception"] = exception;
Response.StatusCode = 500;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (Response.StatusCode)
{
case 403:
routeData.Values["action"] = "Http403";
break;
case 404:
routeData.Values["action"] = "Http404";
break;
}
}
// Avoid IIS7 getting in the middle
Response.TrySkipIisCustomErrors = true;
IController errorsController = new ErrorController();
HttpContextWrapper wrapper = new HttpContextWrapper(Context);
var rc = new RequestContext(wrapper, routeData);
errorsController.Execute(rc);
}
My ErrorController looks like this:
public class ErrorController : BaseController
{
/// <summary>
/// Returns a default view for not having access.
/// </summary>
public ActionResult Unauthorized()
{
BaseModel viewModel = new BaseModel
{
LoginModel = new LogonModel(),
ProfessionsTopX = GetTopXProfessions()
};
return View(viewModel);
}
public ActionResult General(Exception exception)
{
return View("Exception", exception);
}
public ActionResult Http404()
{
//This line works
//return Content("Not found", "text/plain");
//This line presents a blank page
return View("404","_Layout");
}
public ActionResult Http403()
{
return View("403", "_Layout");
}
}
And my Razor View only contains the piece of html below;
#{
ViewBag.Title = "404";
}
<h2>404</h2>
This is a 404 page!
When I use the Return Content i'm getting a plain textoutput telling me i'm looking at a 404-page. However, I want the 404 page to fit the rest of my design, so I want to use my own Views. However as soon as I use Return View I'm getting a blank page. I expect to be missing something very obvious, but I don't see it.
I was having the same problem, and finally found the solution that worked for me. The breakthrough came when I placed a breakpoint on the errorsController.Execute(rc); line, and used 'step into' until I came across this exception:
The view 'Detail' or its master was not found or no view engine supports the
searched locations. The following locations were searched:
~/Views/Errors/Detail.aspx
~/Views/Errors/Detail.ascx
~/Views/Shared/Detail.aspx
~/Views/Shared/Detail.ascx
~/Views/Errors/Detail.cshtml
~/Views/Errors/Detail.vbhtml
~/Views/Shared/Detail.cshtml
~/Views/Shared/Detail.vbhtml
The exception was being swallowed, I assume because it was happening inside the Application_Error method and because I had set Response.TrySkipIisCustomErrors = true.
After seeing this error, I quickly realized my problem was simply one of mismatched names: My controller is actually named ErrorController with no 's', not ErrorsController. The problem for me was that I had set routeData.Values["controller"] = "Errors";, which is wrong. Switching it to routeData.Values["controller"] = "Error"; fixed the problem.
Note that you won't catch the error right away, because you directly instantiate the controller, and it won't compile if you have that part spelled wrong. But inside the controller, calling View() will look for the view using the RouteData instance we constructed and passed to the RequestContext object. So if the controller name is spelled wrong there, MVC doesn't know where to look for the view, and since IIS custom errors are skipped, it fails silently.
Long story short: Check your controller and view names. I assume something similar would happen if you have the controller name correct, but the file name of the view doesn't match.
Please check it out this. It is a best way to implement exception handling in mvc.
I have implemented same exception handling but I am facing some issue but still you can refer this.

TryUpdateModel causing error from unit test cases (Asp.net mvc)

I have on post action in controller. Code is as given below
[HttpPost]
public ActionResult Create(Int64 id, FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (TryUpdateModel(data))
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
// TODO: update of the model has failed, look at the error and pass back to the view
if (!ModelState.IsValid)
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}
I wrote test case for this as below
[TestMethod]
public void CreateTest_for_post_data()
{
var collection = GetApplicantDataOnly();
_controller.ValueProvider = collection.ToValueProvider();
var actual = _controller.Create(0, collection);
Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult));
}
When I debug this single test case, test case passed because condition
if (TryUpdateModel(data)) return true and its goes to if condition.
But when I debug test cases from entire solution this test case failed because it goes to else condition of " if (TryUpdateModel(data))".
I dont know why..
Please help...
Thanks
I've experienced a similar problem which will solve your issue provided you don't need to use a FormCollection.
I haven't used TryUpdateModel ever since the day I learned of the Auto-Binding feature. Auto-binding, in a nutshell pretty much does the work TryUpdateModel would do for you, that is, it'll set a model object according to the values found in the FormCollection as well as attempting to validate the model. And it does this automatically. All you'll have to do is place a parameter in the ActionMethod and it will automatically have it's properties filled with the values found in the FormCollection. Your Action signature will then turn into this:
public ActionResult Create(Int64 id, SomeModel data)
Now you don't need to call TryUpdateModel at all. You still need to check if the ModelState is valid to decide whether or not to redirect or return a view.
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
This won't throw an exception in your unit tests so that's one problem solved. However, there's another problem now. If you run your application using the above code, it'll work just fine. You model will be validated upon entering the Action and the correct ActionResult, either a redirect or a view, will be returned. However, when you try to unit test both paths, you'll find the model will always return the redirect, even when the model is invalid.
The problem is that when unit testing, the model isn't being validated at all. And since the ModelState is by default valid, ModelState.IsValid will always return true in your unit tests and thus will always return a redirect even when the model is invalid.
The solution: call TryValidateModel instead of ModelState.IsValid. This will force your unit test to validate the model. One problem with this approach is that means the model will be validated once in your unit tests and twice in your application. Which means any errors discovered will be recorded twice in your application. Which means if you use the ValidationSummary helper method in your view, your gonna see some duplicated messages listed.
If this is too much to bear, you can clear the ModelState before calling TryValidateModel. There are some problems with doing so because your gonna lose some useful data, such as the attempted value if you clear the ModelState so you could instead just clear the errors recorded in the ModelState. You can do so by digging deep into the ModelState and clearing every error stored in every item like so:
protected void ClearModelStateErrors()
{
foreach (var modelState in ModelState.Values)
modelState.Errors.Clear();
}
I've placed the code in a method so it can be reused by all Actions. I also added the protected keyword to hint that this might be a useful method to place in a BaseController that all your controllers derive from so that they all have access to this method.
Final Solution:
[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
ClearModelStateErrors();
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data, id);
return View("Create", data);
}
}
NOTE: I realize I didn't shed any light on the root issue. That's because I don't completely understand the root issue. If you notice the failing unit test, it fails because a ArgumentNullException was thrown because the ControllerContext is null and it is passed to a method which throws an exception if the ControllerContext is null. (Curse the MVC team with their damned defensive programming).
If you attempt to mock the ControllerContext, you'll still get an exception, this time a NullReferenceException. Funnily enough, the stack trace for the exception shows that both exceptions occur at the same method, or should I say constructor, located at System.Web.Mvc.ChildActionValueProvider. I don't have a copy of the source code handy so I have no idea what is causing the exception and I've yet to find a better solution than the one I offered above. I personally don't like my solution because I'm changing the way I code my application for the benefit of my unit tests but there doesn't seem to be a better alternative. I bet the real solution will involve mocking some other object but I just don't know what.
Also, before anyone gets any smart ideas, mocking the ValueProvider is not a solution. It'll stop exceptions but your unit tests won't be validating your model and your ModelState will always report that the model is valid even when it isn't.
You might want to clean up your code a bit:
[HttpPost]
public ActionResult Create(int id, FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (!ModelState.IsValid)
{
if (id != 0)
{
Helper.ShowLeftColumn(data, id);
}
return View("Create", data);
}
if (TryUpdateModel(data))
{
return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
}
return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}
Don't use Int64, just use int.
As for your failing test, I would expect your test to fail all the time since TryUpdateModel will return false. As your running the code from a unit test, the controller context for a controller is not available, thus TryUpdateModel will fail.
You need to somehow fake/mock TryUpdateModel so that it does not actually run properly. Instead you "fake" it to return true. Here's some links to help:
How do I Unit Test Actions without Mocking that use UpdateModel?
The above SO answer shows an example using RhinoMocks which is a free mocking framework.
Or this:
http://www.codecapers.com/post/ASPNET-MVC-Unit-Testing-UpdateModel-and-TryUpdateModel.aspx
Debug your tests and check the modelstate errors collection, all the errors that the tryupdatemodel encountered should be there.

Resources