I have a VB.NET mvc view form that has 3 parameters and 2 buttons and I not sure how to design my page.
Button 1 is essentially a GET - suppose to run a sql query and return a dataset (multiple tables).
Button 2 is essentially a PUT - suppose to run a sql update statement (multiple updates).
CustomModel includes my 3 parameters as Properties and I have included the Required Attribute on all.
How do I design so that the Required attributes are respected.
I.e. my ViewInfo method is being reached even if the parameter inputs are blank.
Do I declare ViewInfo with the HttpGet and my UpdateInfo with HttpPut attribute. Do I need the parameter declarations in my ViewInfo method?
Is my #Html.BeginForm(...) declaration correct?
VB.NET Model class:
Public Class CustomModel
<Required(ErrorMessage:="Parameter 1 is required"),
Display(Name:="Parameter 1:")>
Public Property Parameter1 As String
<Required(ErrorMessage:="Parameter 2 is required"),
Display(Name:="Parameter 2:")>
Public Property Parameter2 As String
<Required(ErrorMessage:="Parameter 3 is required"),
Display(Name:="Parameter 3:")>
Public Property Parameter3 As Integer
Public Sub New(Parameter1 As String, Parameter2 As String, Parameter3 As Integer)
Me.Parameter1 = Parameter1
Me.Parameter2 = Parameter2
Me.Parameter3 = Parameter3
End Sub
Public Property Info As DataSet = New DataSet
End Class
VB.NET Controller:
Function Index() As ActionResult
ViewBag.ShowView = False
Return View()
End Function
Function ViewInfo(Parameter1 As String, Parameter2 As String, Parameter3 As Integer)
Dim result As New CustomModel(Parameter1, Parameter2, Parameter3)
If ModelState.IsValid Then
result.Info = GetInfo(Parameter1, Parameter2, Parameter3)
End If
ViewBag.ShowView = True
Return View("Index", result)
End Function
Function UpdateInfo(Parameter1 As String, Parameter2 As String, Parameter3 As Integer)
Dim result As New CustomModel(Parameter1, Parameter2, Parameter3)
If ModelState.IsValid Then
result.Info = UpdateInfo(Parameter1, Parameter2, Parameter3)
End If
ViewBag.ShowView = True
Return View("Index", result)
End Function
HTML View
#ModelType CustomModel
...
#Using (Html.BeginForm("ViewInfo", "Home", Nothing, FormMethod.Post))
...
#Html.LabelFor(...)
#Html.TextBoxFor(...)
...
<button type=button>View</button>
<button type=submit>Update</button>
End Using
Output:
#If ViewBag.Show Then
// Multiple WebGrid calls ???
End If
I found a solution to get me on my way finally.
I did not realise the default layout page behind the scenes does not include the validation javascript file so I had to add to my specific view file:
#Section scripts
#Scripts.Render("~/bundles/jqueryval")
End Section
Is there something you have to do so that this file is automatically brought in or is what I have done is correct you have to manually include it in either the layout or the specific view where you need it?
Related
I'm using custom validation requiredconditional that inherits from RequiredAttribute to conditionally require certain fields. This works beautifully everywhere except in once case and I cannot figure out what is happening. It is calling the IsValid method twice for one property in the model (the client side validation works perfectly) The model has 2 properties using this attribute but only one has the issue. At first I thought it was because one of my conditions was a checkbox and it was posting back both the checked value and the hidden value but I tried using radio buttons instead and even a hidden value but had the same results in all cases. Here is my view (simplified for testing):
#ModelType List(Of eLADNETBusiness.AdditionalInterest)
#Code
ViewData("Title") = "Bind Coverage Entry"
End Code
<h2>Bind Coverage Entry</h2>
#Using Html.BeginForm()
#Html.AntiForgeryToken()
#Html.Hidden("hullValue", ViewBag.HullValue)
Dim currentCount As Integer = 0
#For count As Integer = 0 To Model.Count - 1
currentCount = count
#<div class="editor-label">
#Html.LabelFor(Function(model) model(currentCount).LienholderAmount)
</div>
#<div class="editor-field">
#Html.EditorFor(Function(model) model(currentCount).LienholderAmount)
#Html.ValidationMessageFor(Function(model) model(currentCount).LienholderAmount)
</div>
#<div>
#Html.EditorFor(Function(model) model(currentCount).Lienholder90Percent)
</div>
Next
#<p>
<input type="submit" value="Continue" />
</p>
End Using
And here is my model (simplified for testing):
<DataContract()> _
Public Class AdditionalInterest
<DataMember()> _
Public Property ID As Integer = 0
<RequiredConditional("Lienholder90Percent", False, ErrorMessage:="Enter Breach of Warranty lienamount or select 90 percent of insured value")> _
<Display(Name:="Lienholder Amount")> _
<DataMember()> _
Public Property LienholderAmount As Nullable(Of Integer)
<DataMember()> _
Public Property Lienholder90Percent As Boolean
End Class
And my requiredconditional attribute:
Imports System.Collections.Generic
Imports System.Linq
Imports System.ComponentModel.DataAnnotations
Imports System.Web.Mvc
Imports System.Collections
Imports System.Text
Public Class RequiredConditional
Inherits RequiredAttribute
Implements IClientValidatable
Private Property PropertyNames() As String()
Private Property DesiredValues() As Object()
Public Sub New(comparePropertyNames As String(), comparePropertyDesiredValues As Object())
PropertyNames = comparePropertyNames
DesiredValues = comparePropertyDesiredValues
End Sub
Public Sub New(comparePropertyNames As String, comparePropertyDesiredValues As Object)
PropertyNames = New String() {comparePropertyNames}
DesiredValues = New String() {comparePropertyDesiredValues}
End Sub
Protected Overrides Function IsValid(value As Object, context As ValidationContext) As ValidationResult
Dim instance As Object = context.ObjectInstance
Dim type As Type = instance.GetType()
Dim propertyvalue As Object
Dim trueConditions As Integer = 0
For count As Integer = 0 To PropertyNames.Count - 1
propertyvalue = type.GetProperty(PropertyNames(count)).GetValue(instance, Nothing)
If Not propertyvalue Is Nothing Then
If DesiredValues.Count >= count + 1 Then
If propertyvalue.ToString() = DesiredValues(count).ToString() Then
trueConditions += 1
End If
End If
End If
Next
'if all conditions are met, validate value
If trueConditions = PropertyNames.Count And trueConditions = DesiredValues.Count Then
Dim result As ValidationResult = MyBase.IsValid(value, context)
Return result
End If
Return ValidationResult.Success
End Function
Public Function GetClientValidationRules(metadata As System.Web.Mvc.ModelMetadata, context As System.Web.Mvc.ControllerContext) As System.Collections.Generic.IEnumerable(Of System.Web.Mvc.ModelClientValidationRule) _
Implements System.Web.Mvc.IClientValidatable.GetClientValidationRules
Dim results As New List(Of ModelClientValidationRule)
Dim rule = New ModelClientValidationRule With {.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), .ValidationType = "requiredif"}
Dim depProp As String = String.Empty
Dim sbProp As New StringBuilder()
Dim sbTarget As New StringBuilder()
Dim helper As New ValidationHelper
For count As Integer = 0 To PropertyNames.Count - 1
Dim prop As String = PropertyNames(count)
depProp = helper.BuildDependentPropertyName(metadata, TryCast(context, ViewContext), prop)
sbProp.AppendFormat("|{0}", depProp)
Dim targetValue As String = String.Empty
If DesiredValues.Count >= count + 1 Then
targetValue = (If(DesiredValues(count), "")).ToString()
End If
If DesiredValues(count).GetType() = GetType(Boolean) Then
targetValue = DesiredValues(count).ToString.ToLower
End If
sbTarget.AppendFormat("|{0}", targetValue)
Next
rule.ValidationParameters.Add("dependentproperty", sbProp.ToString().TrimStart("|"))
rule.ValidationParameters.Add("targetvalue", sbTarget.ToString().TrimStart("|"))
results.Add(rule)
Return results
End Function
End Class
So when I click submit and debug into the requiredconditional attribute, the lienholderamount property hits IsValid twice. The first time it hits, the value of Lienholder90Percent is False even though in the model it is true and was true on the form (and passed client side validation) so fails validation at that point. Then hits again and Lienholder90Percent is True (which is correct) and passes validation. But since it failed on the first one, it still fails and show the error message. You'll notice that the model is a list but for testing purposes, I'm only sending one and still getting the same results. Can't figure out why it is only happening with this one property. Hope it is something easy I just can't see. I've spent all day trying to figure this out. Like I said, I use this attribute quite a bit and works great. Can't find a difference for this case.
Inherit from ValidationAttribute instead of RequiredAttribute.
RequiredAttribute's IsValid is called right after the property is filled, whereas the ValidationAttribute's IsValid is called after the whole model is filled (which would make sense for more complex validation).
I have a mvc 3 vb.net razor App that I need to redirect the view to a different action if the link is not clicked on between a preset range of Month/Day/Year ranges... This is needed to limit registration for a school semester to only be able to be performed between and Open Registration Date and a End Registration Date... I am thinking I can simply put this in the controller function in the form of either a If Statement or a select case and then use redirect based on the condition of the date...... Is there some simple short code to use to perform this test... My variables for the dates are OpenDate and EndDate. I think its probably something close to
Dim OpenDate as date = mm/dd/yy
Dim CloseDate as date = mm/dd/yy
If system.datetime.now.toshortdatestring < OpenDate Then
Return RedirecttoAction ("Too Soon")
ElseIf system.datetime.now.toshortdatestring > CloseDate Then
Return RedirecttoAction ("Too Late")
Else
Return View()
End If
Does this look good or is there a easier way???
I would write a custom authorization attribute:
Public Class MyAuthorizeAttribute
Inherits AuthorizeAttribute
Private ReadOnly _openDate As DateTime
Private ReadOnly _closeDate As DateTime
Public Sub New(openDate As String, closeDate As String)
_openDate = DateTime.ParseExact(openDate, "dd/MM/yyyy", CultureInfo.InvariantCulture)
_closeDate = DateTime.ParseExact(closeDate, "dd/MM/yyyy", CultureInfo.InvariantCulture)
End Sub
Protected Overrides Function AuthorizeCore(httpContext As HttpContextBase) As Boolean
Dim now = DateTime.Now
If now < _openDate Then
httpContext.Items("actionToRedirectTo") = "toosoon"
Return False
End If
If now > _closeDate Then
httpContext.Items("actionToRedirectTo") = "toolate"
Return False
End If
Return True
End Function
Protected Overrides Sub HandleUnauthorizedRequest(filterContext As AuthorizationContext)
Dim values = New RouteValueDictionary(New With { _
Key .action = filterContext.HttpContext.Items("actionToRedirectTo"), _
Key .controller = "somecontroller" _
})
filterContext.Result = New RedirectToRouteResult(values)
End Sub
End Class
and then simply decorate controller actions that need this kind of logic with it:
<AuthorizeRegistration("01/11/2011", "01/12/2011")>
Function Register() As ActionResult
Return View()
End Function
This way you can reuse it on different actions that need this kind of protection.
You want to limit registration for a school semester by date, so the given date for registration should be valid - this is a validation concern so you should implement go for validation with an error message and not redirecting special views.
You can use the Range (System.ComponentModel.DataAnnotations) attribute on your model for your purposes. If you would need more dynamic values for date range validation you should write your own custom validation attribute.
have looked at Phil Haacks project on books at
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
which has been useful, but I have a mix of data types.
I use a modelview so that i can have a mix of objects, in this case: Order (ie order.id, order.date etc), Customer, SoilSamplingOrder and a list of SoilSamplingSubJobs which is like this [0].id, [0].field, [1].id, [1].field etc
Perhaps I should be using ICollection instead of List? I had problems getting UpdateModel to work so I used an extract from collection method. the first 4 method calls : orderRepository.FindOrder(id); etc give the model the original to be edited. but after this point i'm a little lost in how to update the subjobs. I hope i have delineated enough to make sense of the problem.
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Order order = orderRepository.FindOrder(id);
Customer cust = orderRepository.FindCustomer(order.customer_id);
IList<SoilSamplingSubJob> sssj = orderRepository.FindSubOrders(id);
SoilSamplingOrder sso = orderRepository.FindSoilSampleOrder(id);
try
{
UpdateModel(order, collection.ToValueProvider());
UpdateModel(cust, collection.ToValueProvider());
UpdateModel(sso, collection.ToValueProvider());
IList<SoilSamplingSubJob> sssjs = orderRepository.extractSSSJ(collection);
foreach (var sj in sssjs)
UpdateModel(sso, collection.ToValueProvider());
orderRepository.Save();
return RedirectToAction("Details", new { id=order.order_id});
}
catch
{
return View();
}
}
I think you should work on developing a view model that reflects the data that you need to get back and create display/edit templates for that model that renders the view model using Phil Haack's methods for your lists of objects -- in this case, arrays of submodel classes. Let the model binding framework build the returned model (as a parameter) to your action, then reconstitute your domain models from the view model data. Brad Wilson has an excellent series of articles on templating that should be helpful.
I use IModelBinder on my complex classes. You don't need IModelBinder, but it will make your controller post codeblock look much cleaner. I'm using VB at the moment, but my class looks something like this for example:
Public Class CombinedRulesAndXmlRules : Implements IModelBinder
Public Rules As New Rules()
Public XmlRules As New XmlRules()
Public RequiredTemplates As New List(Of RequiredTemplates)
Public SearchCriteria As New List(Of SearchCriteriaList)
Public OptionalTemplates As New List(Of OptionalTemplates)
Public Questions As New List(Of Questions)
Public QATemplates As New List(Of QATemplates)
**Public Answers As New List(Of Answers)**
Now I don't use editor templates in my views, so to have your lists appear in the formcollection you have to add something like this in your view:
#For x As Integer = 0 To Model.Answers.Count - 1
Dim incr As Integer = x
#Html.HiddenFor(Function(model) model.Answers(incr).Answer)
#Html.HiddenFor(Function(model) model.Answers(incr).AnswerId)
#Html.HiddenFor(Function(model) model.Answers(incr).AnswerTemplateTag)
#Html.HiddenFor(Function(model) model.Answers(incr).Tag)
Next
When the view is submitted/posted, the model binder takes over before hitting the first line of code in your mvc post controller method. I then iterate through the actual formcollection and strip out the [#] using regex, because your formcollection will show your list items like this: Answers[0].Answer, Answers[0]AnswerId ,etc.:
For x As Integer = 1 To request.Form.Count - 1
keyname = request.Form.Keys(x)
Debug.Write(keyname)
val = request.Form(x).ToString()
'If keyname contains [#] strip it. it's a list item.
Dim pattern As String = "\[(\d+)\]"
Dim iterpattern As String = "\d+"
Dim rgx As New Regex(pattern)
Dim rgxiter As New Regex(iterpattern)
If Regex.IsMatch(keyname, pattern) Then
Dim match As Match = rgxiter.Match(keyname)
ListIteration = CInt(match.Value)
Dim result As String = rgx.Replace(keyname, "")
keyname = result
End If
The Select Case codeblock is next. So you already know you have a strong typed class in your model, so your select can look like this:
Select Case keyname
Case "Answers.Answer"
'add code here to add to your return list. What you
'get in the post controller is a fully populated class.
I have a class like
Public Class Task
Property DuplexType As Char
Property Name As String
End Class
In my controller I have an action that looks like
<HttpPost()>
Function Edit(ByVal task As Task) As ActionResult
Dim duplexType = task.DuplexType
Dim valid = ModelState.IsValid
Return RedirectToAction("Index")
End Function
In the view, DuplexType = " " (single space) and Name = "Foo". Why doesn't the property DuplexType have a value? If I assign any other character it works fine.
In the controller name = "foo" but DuplexType = " (empty).
Also ModelState.IsValid = false if DuplexType = " ".
Have a look at the Response object and examine the values being populated by the form Post. You will probably see that values are trimmed and therefore lose the space. If possible try to UrlEncode the values.
Where I have had to preserve trailing spaces in my own projects, I have ended up having to put delimiters on the parameter and then strip them off in the action method.
1) I've a Product table with 4 columns: ProductID, Name, Category, and Price. Here's the regular linq to query this table.
public ActionResult Index()
{
private ProductDataContext db = new ProductDataContext();
var products = from p in db.Products
where p.Category == "Soccer"
select new ProductInfo { Name = p.Name, Price = p.Price}
return View(products);
}
Where ProductInfo is just a class that contains 2 properties (Name and Price). The Index page Inherits ViewPage - IEnumerable - ProductInfo. Everything works fine.
2) To dynamicaly execute the above query, I do this:
Public ActionResult Index()
{
var products =
db.Products
.Where("Category = \"Soccer\"")
.Select(/* WHAT SOULD I WRITE HERE TO SELECT NAME & PRICE?*/)
return View(products);
}
I'm using both 'System.Lind.Dynamic' namespace and the DynamicLibrary.cs (downloaded from ScottGu blog).
Here are my questions:
What expression do I use to select only Name and Price?
(Most importantly) How do I retrieve the data in my view? (i.e. What type the ViewPage inherits? ProductInfo?)
===================
EDIT
When I write .Select("new(Name, Price)"), I'm able to pass an object to the ViewData's Model property. Unfortunately, in order to use the Viewdata object, I'm asked to cast the Viewdata to a type. But, I do not know how to determine the type to do the casting.
====================
EDIT
Instead of the ViewData's Model property, I'm using simply the ViewData["products"]. To retrieve the content, I just place a IEnumerable cast before the ViewData, like this:
<% foreach(var item in (IEnumerable)ViewData["products"]){%>
<p><% = Html.Encode(item)%><p>
<%}%>
There are 2 situations:
1) If I select only one column (for instance, Name), everything work fine.
2) If I select more than 1 more columns (Name, Price), I get something like this
{Name=Soccer, Price=19.50}
{Name=Shin Pads, Price=11.59}
Why I just don't get something like
Soccer, 19.50
Shin Pads, 11.59
=================================
EDIT April 02 - 05h47 AM
I've define the GetPropertyValue Method (as your response suggets) as static in a static Class that I called 'HelperClass'. Now, this is the way I try to access the value of Name from my object.
<% = Html.Encode(HelperClass.GetPropertyValue(ViewData["product"], "Name")) %>
I get the following Exception:"Object reference not set to an instance of an object". And, the following line from the inside GetPropertyValue() his highlight.
Line 22: return propInfo.GetValue(obj, null);
Do I need to use new keyword? (where?)
Thanks for helping
Private Sub filter()
Dim coll = db.Products.Where(Function(x) x.Category.Equals("Soccer")) _
.Select(Function(x) GetObject(x.Name, x.Price))
End Sub
Private Function GetObject(ByVal name As String, ByVal price As String) As Object
Return new ProductInfo(name, price)
End Function
1) To generate a new projection type at runtime you can:
.Select("new(Name, Price)")
2) To read values from the object, you need to use reflection:
string name = GetPropertyValue(someObject, "Name");
...
public static object GetPropertyValue(object obj, string propName)
{
System.Reflection.PropertyInfo propInfo = obj.GetType().GetProperty(propName);
return propInfo.GetValue(obj, null);
}