views:

53

answers:

5

Hi, I am developing a site that needs to have a small form repeated in a lot of pages. So I decided to create a user control (partial view) that has this form.

This form posts to an action, so I have:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SampleProject.DoSomethingActionVM>" %>
<%= using(Html.BeginForm("DoSomethingAction", "Home")){ %>

    //Form stuff

<% } %>

So the action is

public ActionResult DoSomethingAction(DoSomethingActionVM model){
   if(!ModelState.IsValid){
         return View(); //HERE IS THE PROBLEM
   }

   //Do something
}

So, the problem comes when I want to validate the form. If the form is not valid, I dont know where to return as this user control is being used in many views... :( I tried to use a hidden field in the user control that says what View to use... but this was a bad idea as I am using strongly typed views and I dont know what model to send.

What is the best way to validate forms that are inside user controls (partial views)?

Thank you all in advance. I will thank you even more if you can give me quick ideas as I need it for tomorrow work :)

Juan

A: 

Why not create a Service layer that creates the validation for you instead of the user control? See my blog for some more info.

Basically in your Service layer you create MetaData that attaches to the Class Object. I have a LINQ to SQL Class called User, and my UserMetaData adds Validation Functionality to the class.

<MetadataType(GetType(UserMetaData))> _ 
Partial Public Class User 
    Public Property UserRegion As String 
    Public LastSeen As DateTime 
End Class 


Public Class UserMetaData 

    <DisplayName("name")> _ 
    <Required(ErrorMessage:="Username is required.")> _ 
    <StringLength(30, ErrorMessage:="Username cannot exceed 30 characters.")> _ 
    <RegularExpression("^\w{3,30}$", ErrorMessage:="Not a valid username.")> _ 
    Public Property UserName As String 

    <DisplayName("email")> _ 
    <StringLength(50, ErrorMessage:="Email Address cannot exceed 50 characters.")> _ 
    <RegularExpression("^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$", ErrorMessage:="Not a valid email address.")> _ 
    Public Property Email As String 

    <DisplayName("website")> _ 
    <StringLength(256, ErrorMessage:="Web Address cannot exceed 256 characters.")> _ 
    <RegularExpression("^http(s?)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$", ErrorMessage:="Not a valid website address.")> _ 
    Public Property WebSite As String 

    <DisplayName("about")> _ 
    <StringLength(2000, ErrorMessage:="Profile cannot exceed 2000 characters.")> _ 
    Public Property About As String 

    <DisplayName("region")> _ 
    <Required(ErrorMessage:="Region is required.")> _ 
    Public Property UserRegion As Integer 

    <DisplayName("birthdate")> _ 
    <DisplayFormat(ApplyFormatInEditMode:=True, ConvertEmptyStringToNull:=True, DataFormatString:="{0:MM/dd/yyyy}")> _ 
    Public Property BirthDate As DateTime 

End Class 

Then your validation is as easy as passing the User object to the View and then using the following for validation

    <%  
        Html.EnableClientValidation()  
        Using Html.BeginForm("Edit", "Users")  
    %>  
      <%-- Lot's more crap in here--%>  
           <tr>  
                <td>  
                    <%: Html.LabelFor(Function(model) model.UserName)%></td>  
                <td class="full-width">  
                    <%: Html.TextBoxFor(Function(model) model.UserName) %>  
                    <%: Html.ValidationMessage("UserName", "*")%>  
                </td>  
            </tr>  
      <%-- Lot's more crap in here--%>  
    <% End Using%>  

<script src="../../Assets/Scripts/MicrosoftAjax.js" type="text/javascript"></script> 
<script src="../../Assets/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script> 
<script src="../../Assets/Scripts/MicrosoftMvcValidation.js" type="text/javascript"></script> 

EDIT:

Upon reading your "answer", I think I better understand your question. Here's what I think your best option will be.

  1. You need to create a custom ViewModal for the fields you want to send to the View. In this ViewModal you want to create validation as I did above.
  2. Then you need to create A view to send the ViewModel to and you need to structure your controller as follows

In your controller you send info to the View

Function Email() As ActionResult
    ''# do stuff to populate your viewmodel
    Return View(customViewModel)
End Function

<AcceptVerbs(HttpVerbs.Post)> _
Function Email(ByVal customViewModel as MyCustomViewModel)
    If ModelState.IsValid Then
        ''# do your valid stuff
    Else
        Return View(customViewModel)
    End If
End Function
rockinthesixstring
I edited my answer.
rockinthesixstring
I edited my answer explaining a bit better (I hope) the problem. Thanks for everything ;)
Juan
A: 

I think that you missunderstood my problem... I want to encapsulate some behavior. For example, I want to have a form in several pages that has an email field and a submit button. When the user hits the submit button, I make some validation (server side) and if everything is ok I send an email to that address.

As I have to include this form in several pages, I would like to have a user control that has a form that posts to certain action in a controller that sends the email. The thing is... how do I handle validations? If the validation fails (ModelState.IsValid = false), which View does the action return? It has no idea...

I think that his is quite a common problem... I suppose that it has some nice solution that I cant figure it out...

EDIT: I give another example because I think that I might be not explaining well the problem. Example: You want a search control on several pages (maybe all the site), that pages also have information, so they require some model data. This search control would have a textbox and a "search" button, but you want to validate on the server that the textbox is not empty. So if the validation is ok, you redirect the user to the search results page... but if the validation fails, you want to display the same page from where it came with a red asterix at the right of the textbox, but with the same data that the page had.

So this control has inside a form that goes to a certain controller and a certain action. But it is always the same, no matter the page that includes the control. For example it could be controller=Home, action=Search. The problem that I have is that this action receives a SearchVM with an attribute "string Query" that I decorated with the "Required" data annotation. So the first thing that I do is to check if ModelState.IsValid. If it is not valid, what do I do???? Because I cannot do return View(model) because there is no view associated with this action, remember that the action "Search" is used by the control in every page that includes it. And I cant return the view where I came from because I dont have the model data that the view requires to display.

I hope that now I explain myself a bit better.

Thanks again

Juan

Juan
Please add comments and don't answer your own question with explanations.
rockinthesixstring
if the ModelState fails, you return the same view with the same object. `Return View(SomeObject)`
rockinthesixstring
I edited my answer.
rockinthesixstring
A: 

You could put the <form> outside the user control in the calling page.

Darin Dimitrov
I edited my answer explaining a bit better (I hope) the problem. (look at the last message). Thanks
Juan
A: 

Your idea to pass some metadata to the action looks nice. You want to use the view but I would take the calling url.

This way you dont have to know view and model but can just redirect to the calling url. You will loose your ModelState.Errors this way, but you can solve this issues using TempData directly or the ModelStateToTempdata attribute of MVCContrib.

Malcolm Frexner
A: 

I found the solution... Its to use ajax for posting the form inside the control, and have a javascript function that handles the response. So, in the action you chack if ModelState.IsValid, if its not valid you get the modelstate errors and put them in a JSON object and return it. If everything its ok, I needed to redirect to another page, so I return a JSON object with a redirect url.

This is the url of what give the basics to solve the problem: http://www.hightech.ir/SeeSharp/aspnet-validation-with-ajax-and-json

thanks to all for the responses! Juan

Juan