Survey Controller POST方法问题ASP.NET MVC

时间:2012-06-15 23:32:33

标签: asp.net-mvc survey

我正在尝试创建一个Controller类来处理我将来最终创建的所有可预见的调查。目前我有一个'调查'表,其中包含以下字段: Id SurveyName Active 。在'主'调查'索引页面上,我列出了该表中找到的每个SurveyName。每个SurveyName都是可单击的,当单击时,页面将SurveyName作为字符串发送到接收控制器操作。所述控制器动作如下所示:

    //
    //GET: /Surveys/TakeSurvey/
    public ActionResult TakeSurvey(string surveyName)
    {
        Assembly thisAssembly = Assembly.GetExecutingAssembly();
        Type typeToCreate = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();

        object newSurvey = Activator.CreateInstance(typeToCreate);

        ViewBag.surveyName = surveyName;

        return View(surveyName, newSurvey);
    }

使用反射我能够创建由传入字符串'surveyName'指定的类型(Model)的新实例,并且能够将该模型传递给具有相同名称的视图。

示例
有人点击“SummerPicnic”,字符串“SummerPicnic”被传递给控制器​​。控制器使用反射创建SummerPicnic类的新实例,并将其传递给具有相同名称的视图。然后,一个人可以为他们的夏季野餐计划填写表格。

这一切都很好,花花公子。我坚持的部分是试图将POST方法传回的表单保存到正确的相应DB表中。由于我事先并不知道控制器会取回什么样的模型,我不仅不知道如何告诉它要保存哪种模型,还要把它保存到哪里,因为我可以做一些荒谬的事情,如:

    //
    //POST: Surveys/TakeSurvey
    [HttpPost]
    public ActionResult TakeSurvey(Model survey)
    {

        if (ModelState.IsValid)
        {
            _db. + typeof(survey) + .Add(survey);
            _db.SaveChanges();
            return RedirectToAction("Index", "Home");
        }

        return View();
    }

有没有办法做到这一点,还是我应该从一个不同的角度来解决这个问题?我的最终目标是让一个控制器协调每一个简单的调查,所以我不必为我最后开展的每一项调查创建一个单独的控制器。

我能想到的另一种解决方案是为每个调查都有一个单独的方法,并在每个调查的视图中定义调用哪个方法。例如,如果我进行了SummerPicnic调查,则提交按钮会调用名为“SummerPicnic”的ActionMethod:

@Ajax.ActionLink("Create", "SummerPicnic", "Surveys", new AjaxOptions { HttpMethod = "POST" })

对PartyAttendance的调查会调用ActionMethod'PartyAttendance'等等。我宁愿不必这样做,但是......

更新1 我打电话的时候:

    _db.Articles.Add(article);
    _db.SaveChanges();

这是_ db

    private IntranetDb _db = new IntranetDb();

哪个是......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace Intranet.Models
{
    public class IntranetDb : DbContext
    {
        public DbSet<Article> Articles { get; set; }
        public DbSet<ScrollingNews> ScrollingNews { get; set; }
        public DbSet<Survey> Surveys { get; set; }
        public DbSet<Surveys.test> tests { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}

3 个答案:

答案 0 :(得分:2)

您可以尝试这样的事情,

<强>更新

内置UpdateModel将使用通用模型,请参阅此post,这样我们就可以完成更多工作。

[HttpPost]
public ActionResult TakeSurvey(FormCollection form, surveyName)
{
  var surveyType = Type.GetType(surveyName);
  var surveyObj = Activator.CreateInstance(surveyType);

  var binder = Binders.GetBinder(surveyType);

  var bindingContext = new ModelBindingContext()
  {
    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => surveyObj, surveyType),
    ModelState = ModelState,
    ValueProvider = form
  };

  binder.BindModel(ControllerContext, bindingContext);

  if (ModelState.IsValid)
  {
    // if "db" derives from ObjectContext then..
    db.AddObject(surveyType, surveyObj);         
    db.SaveChanges();

    // if "db" derives from DbContext then..
    var objCtx = ((IObjectContextAdapter)db).ObjectContext;        
    objCtx.AddObject(surveyType, surveyObj);         
    db.SaveChanges();

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

  return View();
}

检查this两个人是否知道DbContextObjectContext

之间的差异

答案 1 :(得分:2)

我最终得到了Mark代码的略微修改版本:

    [HttpPost]
    public ActionResult TakeSurvey(string surveyName, FormCollection form)
    {
        //var surveyType = Type.GetType(surveyName);
        //var surveyObj = Activator.CreateInstance(surveyType);

        // Get survey type and create new instance of it
        var thisAssembly = Assembly.GetExecutingAssembly();
        var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
        var newSurvey = Activator.CreateInstance(surveyType);

        var binder = Binders.GetBinder(surveyType);

        var bindingContext = new ModelBindingContext()
        {
            ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurvey, surveyType),
            ModelState = ModelState,
            ValueProvider = form
        };

        binder.BindModel(ControllerContext, bindingContext);

        if (ModelState.IsValid)
        {
            var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
            objCtx.AddObject(surveyName, newSurvey);
            _db.SaveChanges();
        return RedirectToAction("Index", "Home");
        }

        return View();
    }

surveyType 设置为 Type.GetType(surveyName); 时,我遇到了 surveyType ,所以我继续前进并通过Reflection检索了Type。 / p>

我遇到的唯一麻烦就是:

        if (ModelState.IsValid)
        {
            var objCtx = ((IObjectContextAdapter)_db).ObjectContext;
            objCtx.AddObject(surveyName, newSurvey);
            _db.SaveChanges();
            return RedirectToAction("Index", "Home");
        }

当它尝试AddObject时,我收到异常“无法找到EntitySet名称'IntranetDb.test'。”我只需要弄清楚前缀'IntranetDb'。希望我会做生意。

<强>更新
我完全忽视的一件事是从视图中将模型传递给控制器​​......哦,麻烦。我目前有一个ActionLink取代了正常的“提交”按钮,因为我不知道如何向控制器传递创建正确的Survey模型实例所需的字符串:

    <p>
        @Ajax.ActionLink("Create", "TakeSurvey", "Surveys", new { surveyName = ViewBag.surveyName }, new AjaxOptions { HttpMethod = "POST" })
        @*<input type="submit" value="Create" />*@
    </p>

因此,一旦我弄清楚如何将'IntranetDb.test'变为'测试',我将解决如何在提交时使调查字段全部为“空”。

更新2
我将提交方法从使用Ajax ActionLink更改为普通提交按钮。在我意识到Mark的bindingContext正在为我做绑定(将表单值注入到Model值)之后,为我的Model值设置了这个固定的空值。所以现在我的View提交了一个简单的:

<input type="submit" value="Submit" />

回过头来弄清楚如何将'IntranetDb.test'截断为'测试'......

得到它
问题在于我的IntranetDb类:

public class IntranetDb : DbContext
{
    public DbSet<Article> Articles { get; set; }
    public DbSet<ScrollingNews> ScrollingNews { get; set; }
    public DbSet<SurveyMaster> SurveyMaster { get; set; }
    public DbSet<Surveys.test> tests { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

objCtx.AddObject(surveyName,newSurveyEntry); 正在IntranetDb类中查找名为“test”的条目(“EntitySet”)。问题在于我没有一个名为“test”的EntitySet,而是一个带有's'的“tests”的名称用于复数。事实证明我根本不需要截断任何东西,我只需要指向正确的对象:P一旦我顺利完成,我就应该做生意了!感谢Mark和Abhijit的帮助! ^ _ ^

结束

    //
    //POST: Surveys/TakeSurvey
    [HttpPost]
    public ActionResult TakeSurvey(string surveyName, FormCollection form)
    {
        //var surveyType = Type.GetType(surveyName);
        //var surveyObj = Activator.CreateInstance(surveyType);

        // Create Survey Type using Reflection
        var thisAssembly = Assembly.GetExecutingAssembly();
        var surveyType = thisAssembly.GetTypes().Where(t => t.Name == surveyName).First();
        var newSurveyEntry = Activator.CreateInstance(surveyType);

        // Set up binder
        var binder = Binders.GetBinder(surveyType);            
        var bindingContext = new ModelBindingContext()
        {
            ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => newSurveyEntry, surveyType),
            ModelState = ModelState,
            ValueProvider = form        // Get values from form
        };

        var objCtx = ((IObjectContextAdapter)_db).ObjectContext;

        // Retrieve EntitySet name for Survey type
        var container = objCtx.MetadataWorkspace.GetEntityContainer(objCtx.DefaultContainerName, DataSpace.CSpace);
        string setName = (from meta in container.BaseEntitySets
                                      where meta.ElementType.Name == surveyName
                                      select meta.Name).First();

        binder.BindModel(ControllerContext, bindingContext);    // bind form values to survey object     

        if (ModelState.IsValid)
        {
            objCtx.AddObject(setName, newSurveyEntry);  // Add survey entry to appropriate EntitySet
            _db.SaveChanges();

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

        return View();
    }

它有点臃肿,但它现在有效。 This post帮助我从Survey对象本身获取EntitySet,因此我不必担心建立某种EntitySet命名约定。

答案 2 :(得分:0)

我看到的主要问题是将模型绑定到TakeSurvey POST方法。如果你想要不同类型的调查模型应该由这个方法处理,MVC应该在调用动作之前绑定到这个模型,我相信你可以在所有这样的通用模型上有一个包装模型类,比如说SurveyModel并使用自定义模型绑定器绑定到这些模型。

public class SurveyModel
{
    public string GetSurveyModelType();
    public SummerPicnicSurvey SummerPicnicSurvey { get; set; }
    public PartyAttendanceSurvey PartyAttendanceSurvey { get; set; }
}

然后编写自定义mobel绑定器来绑定此模型。从请求表单字段中,我们可以看到发布了什么类型的调查模型,然后相应地获取所有字段并初始化SurveyModel类。如果发布了SummerPicnicSurvey,则将使用此类设置类SurveyModel,并且PartyAttendanceSurvey将为null。 Example custom model binder.

从控制器操作TakeSurvey POST方法,您可以像这样更新数据库:

 [HttpPost]
    public ActionResult TakeSurvey(SurveyModel survey)
    {

        if (ModelState.IsValid)
        {
            if(survey.GetSurveyModelType() == "SummerPicnicSurvey")
                _db.UpdateSummerPicnicSurvey(survey.SummerPicnicSurvey);
            else if (survey.GetSurveyModelType() == "PartyAttendanceSurvey")
                _db.UpdateSummerPicnicSurvey(survey.PartyAttendanceSurvey);

            _db.SaveChanges();
            return RedirectToAction("Index", "Home");
        }

        return View();
    }

代替SurveyModel封装其他调查,您可以继承并使用.net as进行类型检查并使用模型。

话虽如此,我认为对每种模型使用不同的方法没有坏处。这将使您能够很好地对代码进行单元测试。太多,否则维持不健康。或者,您可以将通用模型SurveyModel传输到存储库或数据访问层,并让它以多态方式处理它。我更喜欢更小的功能并保持代码清洁。

编辑:继承方式:

    public class SurveyModel
    {      
        public virtual bool Save();        
    }

    public partial class SummerPicnicSurvey : SurveyModel
    {
      public bool Save(SummerPicnicSurvey survey)
      {
         using(var _dbContext = new MyContext())
         {
           _dbContex.SummerPicnicSurveys.Add(survey);
           _dbContex.SaveChanges();
         }
      }
    }

  [HttpPost]
  public ActionResult TakeSurvey(SurveyModel survey)
  {
     if (ModelState.IsValid)
      {
         survey.Save();
           return RedirectToAction("Index", "Home");
       }

      return View();
   }

您添加的任何新的Survey模型类型都必须实现SaveChanges或Save方法,这将调用正确的dbcontext方法。控制器操作只会在传递给它的泛型`SurveyModel'引用上调用Save。因此,该行动将被关闭以进行修改,但可以进行修改。开放式设计原则。