2

I am a beginner programmer and having trouble with the @Html.DropDownListFor helper...

I am using a General Repository and Unit of Work pattern based off of the tutorial here: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

Here is my code for the Repository:

public class GenericRepository<TEntity> where TEntity : class
{
    internal UsersContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(UsersContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    // Delete methods not shown

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
}

Here is my code for my UnitOfWork class:

public class UnitOfWork : IDisposable
{
    private UsersContext context = new UsersContext();
    private GenericRepository<UserProfile> userProfileRepository;
    private GenericRepository<Lead> leadRepository;
    private GenericRepository<UnitedStatesState> unitedStatesStateRepository;

    public GenericRepository<UserProfile> UserProfileRepository
    {
        get
        {

            if (this.userProfileRepository == null) 
            {
                this.userProfileRepository = new GenericRepository<UserProfile(context);
            }
            return userProfileRepository;
        }
    }

    public GenericRepository<Lead> LeadRepository
    {
        get
        {

            if (this.leadRepository == null)
            {
                this.leadRepository = new GenericRepository<Lead>(context);
            }
            return leadRepository;
        }
    }

    public GenericRepository<UnitedStatesState> UnitedStatesStateRepository 
    {
        get
        {

            if (this.unitedStatesStateRepository == null)
            {
                this.unitedStatesStateRepository = new GenericRepository<UnitedStatesState>(context);
            }
            return unitedStatesStateRepository;
        }
    } 

I am trying to use strongly typed views and models in order to pass the selectlist data to the view without using ViewData/ViewBag. From what I understand, the best practice is to do something similar to what I saw here: validate a dropdownlist in asp.net mvc

I tried following that as closely as possible and this is what I came up with

My View Model looks like this:

public class Lead
{
    public int LeadID { get; set; }
    public int CompanyID { get; set; }

    [Required(ErrorMessage = "Please enter state")]
    [Display(Name = "State")]
    [MaxLength(2)]
    public string State { get; set; }

    [Display(Name = "Assigned To")]
    public string AssignedTo { get; set; }

    [Timestamp]
    public Byte[] Timestamp { get; set; }

    public virtual Company Company { get; set; }

    // IEnumerables for Dropdown Lists passed to views
    public IEnumerable<UnitedStatesState> UnitedStatesStates { get; set; }
    public IEnumerable<UserProfile> UserProfiles { get; set; }

    // Objects passed to views
    public Lead lead { get; set; }
}

These IEnumerables for my dropdown lists are then populated in my controller from my database through my repository. The odd part is that I am using these dropdown lists in two different views, Create and Edit. When I use the dropdown lists in the Create view they work perfectly both on the GET and POST ActionResults. When I try and use the same dropdown lists for my Edit view they work for the GET ActionResult (the view loads and the dropdowns work) but when I try to POST them to my Edit ActionResult I get the following error:

{"Value cannot be null.\r\nParameter name: items"} // This is the error as shown in Visual Studio 2012

System.ArgumentNullException: Value cannot be null. Parameter name: items // This is the error shown in Google Chrome

Below is my Lead Controller with the Edit and Create ActionResults:

public class LeadController : Controller
{
    // create instance of Repository Unit of Work
    private UnitOfWork unitOfWork = new UnitOfWork();

    public ActionResult Create()
    {
        // Get the current users profile
        UserProfile userProfile = UserProfile.GetCurrentUserProfile();

        // Creates Dropdown Lists to pass to view
        var model = new Lead
        {
            UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
            UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
        };

        // Return View
        return View(model);
    }

    [HttpPost]
    public ActionResult Create(Lead model)
    {
        try
        {
            if (ModelState.IsValid)
            {
                // Call the current users profile
                UserProfile userProfile = UserProfile.GetCurrentUserProfile(); 

                // Create a new lead and apply all attirbutes that were entered
                Lead lead = new Lead();
                lead.CompanyID = userProfile.CompanyID; 
                lead.State = model.State;
                lead.AssignedTo = model.AssignedTo;

                // Add the lead and save the changes.  Redirect to Lead Index.
                unitOfWork.LeadRepository.Insert(lead); 
                unitOfWork.Save(); 
                return RedirectToAction("Index");
            }
        }
        catch (DataException)
        {
            ModelState.AddModelError("", "Unable to save changes.  Try again and if the problem persists, see your system administrator.");
        }
        // Return view if ModelState is not valid
        return View();
    }

    public ActionResult Edit(int id = 0)
    {
        // Get Users Profile
        UserProfile userProfile = UserProfile.GetCurrentUserProfile();

        // Check to see if Lead Exists
        if (unitOfWork.LeadRepository.GetByID(id) == null)
        {
            return HttpNotFound();
        }

        // Creates Dropdown Lists and Gets current lead values to pass to view
        var model = new Lead
        {
            lead = unitOfWork.LeadRepository.GetByID(id),
            UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
            UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Edit(Lead lead)
    {
        try
        {
            // Update lead if model state is valid
            if (ModelState.IsValid)
            {
                unitOfWork.LeadRepository.Update(lead);
                unitOfWork.Save();
                return RedirectToAction("Index");
            }
        }
        // Catch any concurrency exceptions
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.Single();
            var databaseValues = (Lead)entry.GetDatabaseValues().ToObject();
            var clientValues = (Lead)entry.Entity;

            if (databaseValues.State != clientValues.State)
                ModelState.AddModelError("State", "Current value: "
                    + databaseValues.State);

            if (databaseValues.AssignedTo != clientValues.AssignedTo )
                ModelState.AddModelError("Assigned To ", "Current value: "
                    + databaseValues.AssignedTo );

            ModelState.AddModelError(string.Empty, "The record you attempted to edit "
                + "was modified by another user after you got the original value. The "
                + "edit operation was canceled and the current values in the database "
                + "have been displayed. If you still want to edit this record, click "
                + "the Save button again. Otherwise click the Back to List hyperlink.");
            lead.Timestamp = databaseValues.Timestamp;
        }
        catch (DataException)
        {
            //Log the error (add a variable name after Exception)
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
        }

        // Return View if Model State is not valid
        return View(lead);
    }

The POST Edit ActionResult includes code to catch concurrencies which I created following the tutorial shown here: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application

Below is my view for Create (this works perfectly):

@model SolarToolbase.Models.Lead

@using (Html.BeginForm()) {                
@Html.ValidationSummary(true)
<div>                
    <div>
        @Html.LabelFor(model => model.State)
        @Html.DropDownListFor(model => model.State, new SelectList(Model.UnitedStatesStates, "StateAbbreviation", "UnitedStatesStateName"),"Choose State")<br />
        @Html.ValidationMessageFor(model => model.State)
    </div> 
    <div>
        @Html.LabelFor(model => model.AssignedTo)
        @Html.DropDownListFor(model => model.AssignedTo, new SelectList(Model.UserProfiles, "FullName", "FullName"),"Choose User")<br />
        @Html.ValidationMessageFor(model => model.AssignedTo)
    </div> 
    <p>
        <input type="submit" value="Create" />
    </p>

</div>
}   

Below is my view for Edit(this throws the aforementioned errors when I hit the submit button. I inserted a comment below to show the line that the error is being thrown from):

@model SolarToolbase.Models.Lead

@using (Html.BeginForm()) {               
@Html.ValidationSummary(true) 
@Html.HiddenFor(model => model.lead.LeadID)
@Html.HiddenFor(model => model.lead.Timestamp)

<div>
    <div>
        @Html.LabelFor(model => model.lead.State)
        @Html.DropDownListFor(model => model.lead.State, new SelectList(Model.UnitedStatesStates, "StateAbbreviation", "UnitedStatesStateName"))<br />  // Error thrown from this line
        @Html.ValidationMessageFor(model => model.lead.State)
    </div> 
    <div>
        @Html.LabelFor(model => model.lead.AssignedTo)
        @Html.DropDownListFor(model => model.lead.AssignedTo, new SelectList(Model.UserProfiles, "FullName", "FullName"))<br />
        @Html.ValidationMessageFor(model => model.lead.AssignedTo)
    </div> 
    <p>
        <input type="submit" value="Save" />
    </p>   
</div> 
}

I apologize in advance for posting so much code, I just honestly don't know where this error is coming from and I've beat my head against the wall trying to figure it out for about 4 hours now. Free virtual high fives and good karma for anyone that can help.

Thanks!

4

1 に答える 1

0

とアクションのPOST両方の場合、エラーがあるか無効であると、例外をキャッチし、モデルバインダーによって作成および設定された、構築されたビューモデルでデフォルトを返します。CreateEditModelStateViewLead

ただし、Edit POSTアクションでは、エラー状態が発生した場合は、leadオブジェクトをに戻します。およびプロパティは、に再入力されないことに注意してViewください。アクションにそれらを入力しますが、アクションでもそれを行う必要があります。ビューに送信するモデルが適切な形状であり、予想されるすべてのメンバーが設定されていることに注意する必要があります。Model.UnitedStatesStatesUserProfilesPOSTGETPOST

また、ビューモデルが。Leadというプロパティを持つタイプであることに注意してくださいlead。それはそこにあるコードの臭いです。独自のクラスのインスタンスへの参照を持つビューモデルクラスはありません。それはすでにあなたに混乱を引き起こしています。Lead明示的にLeadViewModelする必要があり、ビューに出入りするときに必要なすべてのプロパティと値を、プロパティなしで保持する必要がありますlead

ビューでは、たとえばEditのようにモデルのプロパティを参照していますが、ビューではのように親レベルのプロパティを参照しています。しかし、ビューでは、代わりに使用している場合は、このパターンを廃止して、子プロパティをまったく持たずに、ビューが現在実行していることを実行します。たとえば、すべてのプロパティと両方のビューで実行します。model.lead.StateCreatemodel.StateEditSelectListItemsModel.UnitedStatesStatesModel.lead.UnitedStatesStates.Createleadmodel.State

したがって、モデルをビューに渡すときは常に、コレクションのプロパティが入力されていることを確認してください。

[HttpPost]
public ActionResult Edit(Lead lead)
{
    try
    {
        // Update lead if model state is valid
        if (ModelState.IsValid)
        {
            unitOfWork.LeadRepository.Update(lead);
            unitOfWork.Save();
            return RedirectToAction("Index");
        }
    }
    // Catch any concurrency exceptions
    catch (DbUpdateConcurrencyException ex)
    {
        var entry = ex.Entries.Single();
        var databaseValues = (Lead)entry.GetDatabaseValues().ToObject();
        var clientValues = (Lead)entry.Entity;

        if (databaseValues.State != clientValues.State)
            ModelState.AddModelError("State", "Current value: "
                + databaseValues.State);

        if (databaseValues.AssignedTo != clientValues.AssignedTo )
            ModelState.AddModelError("Assigned To ", "Current value: "
                + databaseValues.AssignedTo );

        ModelState.AddModelError(string.Empty, "The record you attempted to edit "
            + "was modified by another user after you got the original value. The "
            + "edit operation was canceled and the current values in the database "
            + "have been displayed. If you still want to edit this record, click "
            + "the Save button again. Otherwise click the Back to List hyperlink.");
        lead.Timestamp = databaseValues.Timestamp;
    }
    catch (DataException)
    {
        //Log the error (add a variable name after Exception)
        ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
    }

    // Return View if Model State is not valid
    /////////// CHANGES HERE
    lead.UnitedStatesStates = unitOfWork.UnitedStatesStateRepository.Get(u => u.StateAbbreviation != null),
    lead.UserProfiles = unitOfWork.UserProfileRepository.Get(u => u.CompanyID == userProfile.CompanyID)
    return View(lead); // pass the model to the view for Create and Edit POST actions when there's an error
}

両方のPOSTアクションでそれを行います。エラーが発生した場合、ビューは、入力されたモデルを使用したアクションによってインスタンス化されます。また、ビューを変更して、Editビューと同じように機能し、ビューモデルのプロパティCreateを使用しないようにします。おそらく、これでビュー内のnull参照例外が処理されます。Leadlead

于 2013-05-16T21:48:00.810 に答える