Best asp.net-mvc-3 questions in June 2012

ASP.NET MVC - Database entities or ViewModels?

19 votes

I am currently working on an ASP.NET MVC project.

Some developers on the team want to bind the auto-generated database entities directly to the Views.

Other developers want to create tailor-made ViewModel's and bind those to the Views.

Objectively, what are the pros and cons of both approaches?

(By "database entities" I am referring to the auto generated classes that an ORM framework generates, such as LINQ to SQL, Entity Framework or LLBLGen).

Definitely use view models in your views, and use something like AutoMapper to create view models from entities easily.

Cons:

  1. Sometimes it feels like you are duplicating code, specifically, when the view model and the entity have the exact same properties

Pros:

  1. You often need to represent an object in a simpler format (often called flattening), but you need full fidelity on the server side. This allows you to transition between the two without mucking up your domain model with presentation cruft.
  2. Aggregate roots often have a lots of value objects and additional entities that are irrelevant to a specific view, and omitting them in a view model makes it easier to work with.
  3. Your entities will have lots of two way references that are sensible in terms of an API, but create pure hell when serializing them for JSON, XML, etc. View models will eliminate these circular references.
  4. You may often use the same entity but in different ways for different views. Trying to balance both needs on one type can create a huge mess.

AntiForgeryToken deprecated in ASP.Net MVC 4 RC

6 votes

I just installed ASP.Net MVC 4 RC to replace ASP.Net MVC 4 beta. When trying to run an existing application I'm getting an error message that AntiForgeryToken has been deprecated. Here's my code:

using (Html.BeginForm("", "", FormMethod.Post, new { id = "MonthElectionForm" }))
{
    @Html.AntiForgeryToken("AddEditMonthElection")
}

---- UPDATE ---

ASP.Net MVC 4 RC has made the Salt property obsolete for ValidateAntiForgeryToken attribute and AntiForgeryToken html helper. So, now my code looks like this:

controller:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public JsonResult CreateCompany(CompanyDataEntryViewModel modelData)
       {...}

form:

@using (Html.BeginForm("", "", FormMethod.Post, new { id = "CreateCompanyDataEntryForm" }))
{
    @Html.AntiForgeryToken()
...
}

Looking at generated HTML, AntiForgeryToken still generates a hidden field and provides an encrypted value. My action still works too. But I've lost the ability to designate a key to use in the encryption process. I'm not too sure how the process works, but before I can tell I was setting the salt value on the action and on the form. The values had to match in order for the action to accept the post. So, how do you set the salt value now? I think it has something to do with AntiForgeryConfig AdditionalDataProvider but I cannot find anything googling on how to use AntiForgeryConfig AdditionalDataProvider. Please help.

Thanks

Setting the salt parameter is unnecessary and didn't provide any additional protection, so we removed support for it.

Please see my response at How to choose a salt value for ValidateAntiForgeryToken for more information.

MVC3 Ajax.BeginForm OnSuccess Doesn't Run in Firefox

6 votes

FINAL EDIT:

After following the answer from Darin Dimitrov, I have found that the problem ended up being that the AJAX call to the Controller's method UpdateForm() was returning an empty string. This was a modification that I found necessary some time ago after experiencing a different problem. Passing an empty string was causing Firefox's parser to choke (while Chrome and IE didn't care, apparently) so I replaced the empty string with an empty div.

Edit:

Thanks to Darin Dimitrov's suggestions below, I have found that the reason I was having trouble is due to an error being thrown whenever the form in question is being submitted.

JQuery Error

The error reads "Node cannot be inserted at the specified point in the heirarchy". This is thrown each and every time the form is submitted. I noticed in the POST data that it seems to think this is an XMLHttpRequest. Is that the cause (the AJAX request in question is just returning HTML)? Here is the POST data from Firebug:

POST Data 1

POST Data 2

POST Data 3

This error reads "XML Parsing Error -- No Element Found".

FYI - the HTML being returned is always an empty string...


I have an MVC3 application running on IIS7. In one of my views, I have a form being built using a Microsoft HTML helper function:

@using (Ajax.BeginForm("UpdateForm", new AjaxOptions { UpdateTargetId = "TargetDiv", InsertionMode = InsertionMode.InsertAfter, OnSuccess = "ClearTextBox" }))
{
    @Html.TextArea("txtInput", new { id = "txtInput", cols = "20", rows = "5", wrap = "virtual" })
    <input id="send" class="button" type="submit" value="Send"/><br />
} 

This generates the following HTML when the Controller provides this view:

<form action="/RootName/ControllerName/UpdateForm" data-ajax="true" data-ajax-mode="after" data-ajax-success="ClearTextBox" data-ajax-update="#TargetDiv" id="form0" method="post">
     <textarea cols="20" id="txtInput" name="txtInput" rows="5" wrap="virtual"></textarea>    
     <input id="send" class="button" type="submit" value="Send"><br>
</form>

What I'm basically trying to do here is take the text inside the TextArea called txtInput and append it to the end of the Div called TargetDiv whenever the Send button above is clicked and clear out the text from txtInput after the appending is complete by means of the ClearTextBox() method (Javascript). The append always works in every browser; and when I run in Internet Explorer or Chrome, the clearing of the text works just fine. However, Firefox doesn't seem to want to call the ClearTextBox() method.

Is Firefox not compatible with this data-ajax-success option in the form signature?


Things I've Tried

I found this guy: Ajax.BeginForm doesn't call onSuccess

The solution is to add this script:

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>

I am calling this script:

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>

...but I tried swapping it out just in case. No joy.

I was asked to try changing the method call to include parentheses by some folks in the C# chat room so that the HTML came out like this:

<form action="/WebChat/TMWC/UpdateForm" data-ajax="true" data-ajax-mode="after" data-ajax-success="ClearTextBox()" data-ajax-update="#chatText" id="form0" method="post">
     <textarea cols="20" id="txtInput" name="txtInput" rows="5" wrap="virtual"></textarea>    
     <input id="send" class="button" type="submit" value="Send"><br>
</form>

But that didn't help.

The folks in C# Chat also suggested I replace the Javascript call with an alert - something like this:

<form action="/WebChat/TMWC/UpdateForm" data-ajax="true" data-ajax-mode="after" data-ajax-success="alert('yo!')" data-ajax-update="#chatText" id="form0" method="post">
     <textarea cols="20" id="txtInput" name="txtInput" rows="5" wrap="virtual"></textarea>    
     <input id="send" class="button" type="submit" value="Send"><br>
</form>

While Chrome pops the message box, Firefox does not!

Status no repro in a newly created ASP.NET MVC 3 application.

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult UpdateForm()
    {
        return Content(DateTime.Now.ToLongTimeString());
    }
}

View (~/Views/Home/Index.cshtml):

<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
<script type="text/javascript">
    function ClearTextBox() {
        $('textarea').val(''); 
    }
</script>

<form action="/Home/UpdateForm" data-ajax="true" data-ajax-mode="after" data-ajax-success="ClearTextBox" data-ajax-update="#TargetDiv" id="form0" method="post">
     <textarea cols="20" id="txtInput" name="txtInput" rows="5" wrap="virtual"></textarea>    
     <input id="send" class="button" type="submit" value="Send"><br>
</form>

<div id="TargetDiv"></div>

Works perfectly fine in Chrome, FF and IE.

Also you might want to ensure that the Content-Type response HTTP header matches the actual response that you are sending. For example I have seen so many people send the application/json response header with some invalid JSON in the response body which produces the more sensitive parsers to choke.

Model object incomplete when try to update

5 votes

I have the following action methods:

public ActionResult ProfileSettings()
        {
            Context con = new Context();
            ProfileSettingsViewModel model = new ProfileSettingsViewModel();
            model.Cities = con.Cities.ToList();
            model.Countries = con.Countries.ToList();
            model.UserProfile = con.Users.Find(Membership.GetUser().ProviderUserKey);
            return View(model); // Here model is full with all needed data
        }

        [HttpPost]
        public ActionResult ProfileSettings(ProfileSettingsViewModel model)
        {
            // Passed model is not good
            Context con = new Context();

            con.Entry(model.UserProfile).State = EntityState.Modified;
            con.SaveChanges();

            return RedirectToAction("Index", "Home");
        }

@using (Html.BeginForm("ProfileSettings", "User", FormMethod.Post, new { id = "submitProfile" }))
        {
            <li>
                <label>
                    First Name</label>
                @Html.TextBoxFor(a => a.UserProfile.FirstName)
            </li>
            <li>
                <label>
                    Last Name</label>
                @Html.TextBoxFor(a => a.UserProfile.LastName)
            </li>
...
<input type="submit" value="Save" />
...

When I hit submit received model in POST method is incomplete. It contains FirstName, LastName etc. But UserID is null. So I can't update object. What am I doing wrong here?

MVC reconstructs your model only based on what's coming in the request. In your particular case, you are only submitting the FirstName and the LastName, because those are the only @Html.TextBoxFor() calls included in your View. MVC models don't behave like ViewState, it isn't stored anywhere.

You also don't want to include your entire Entity in your view-model. If all you need is the ID then that should be all you include. Then you'd load your entity again from your DAL, update the properties that need to be altered, and then save your changes.

ASP.NET MVC 3 Structure - Go to view in another project

4 votes

I've got the following project setup

Project A (main)

  1. Business
  2. Data
  3. View (asp.net mvc 3 project)

Project N

  1. Business
  2. Data
  3. View (asp.net mvc 3 project)

How can I call from Project A the View in Project N and from N back to A. Essentially what I'm trying to do is package each Project N to have its own individual MVC as it comes from different sources and plug it in to the main project and then just have it navigate to the correct view.

Can this be done? Or is there a better way to do this?

I do sugest another approach if possible. if I understood correctly, those projects are somehow ike plugins but they are not standalone applications.Also they now about each others so they are coupled. It's, let's say tricky, but I would use only 1 asp.net mvc project (the web ui). All the UI bits which belong to other projects I'd make them helpers (pretty much widgets). This means, that each project contains only the helpers which will be used to construct a view.

I think it's a bit of an architectural problem if you only want to keep the views in each project just for the sake of hosting them in a different assembly. Going the widgets' way it might seem mkore work, but I think you gain the most control and the separation level you want. The only thing is you don't have full Views defined, but why you would want to have full Views (partials, layouts) in separate places if they will be used in one place only?!

Now, if each project is indeed a plugin, independent of other plugins, then going with compiled views is the best way. But if Project B knows about the view of Project N, then I think the above solution is more suitable. That or the whole app is too over engineered. Separation is good when it doesn't create a whole new jungle to navigate it.

MVC Route Segment with a Question Mark?

4 votes

In this article by Sam Saffron, he mentions that Stack Overflow has a route that looks like this:

questions/{id}/{title?} 

Is that a typo? What does that question mark do?

From http://maproutes.codeplex.com/:

[Url("store/{category?}")]
public ActionResult Products(string category)
{
    return View();
}

'?' sign at the end of {category?} parameter means that it's optional. UrlParameter.Optional will be a default value for it.