【问题标题】:Survey Controller POST method problems ASP.NET MVCSurvey Controller POST 方法问题 ASP.NET MVC
【发布时间】:2012-06-15 23:32:33
【问题描述】:

我正在尝试创建一个 Controller 类来处理我将在未来创建的所有可预见的调查。目前我有一个“调查”表,其中包含以下字段:IdSurveyNameActive。在“主”调查索引页面上,我列出了在该表中找到的每个调查名称。每个 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”指定的类型(模型)的新实例,并且能够将该模型传递给具有相同名称的视图。

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

这一切都很好而且花花公子。我坚持的部分是尝试将 POST 方法传回的表单保存到正确的相应数据库表中。由于我不提前知道控制器将返回哪种模型,所以我不仅不知道如何告诉它要保存哪种模型,而且也不知道将它保存到哪里,因为我可以不要做一些荒谬的事情:

    //
    //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>();
        }
    }
}

【问题讨论】:

  • (与问题有点无关)有一个 asp.net mvc 调查操作系统应用程序,所有操作 ajax surveymaster.codeplex.com
  • 我更新了我的答案,你可以试一试.. 我最初的答案有一些错误
  • 它可能与 特定 问题无关,但它是一个很好的选择,我肯定会考虑。我一直在寻找一个好的(免费)调查引擎,但还没有遇到过。谢谢你:)
  • 很好奇,如何动态加载视图?修改后的视图引擎,它也使用“surveyname”参数来找到正确的视图?

标签: asp.net-mvc survey


【解决方案1】:

你可以试试这样的,

更新:

内置的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 之间的差异

【讨论】:

  • 当您说“// 将surveyObj 保存到数据库”时,我该怎么做呢?我知道的唯一方法是获取数据库、相应的表并在其上调用 save()。前任。 _db.Articles.Save();或 _db.SummerPicnic.Save();或 _db.PartyAttendance.Save();我如何动态地编写一个语句来做到这一点?或者我只是没有遵循你的逻辑,这不是你这样做的方式:/
  • @cbergman 一种选择是使用反射并在 _db 中找到特定类型(SummerPicnic、PartyAttendance)并调用 Save 方法。如果你需要,我会更新代码。
  • 那个对象_db是EF还是L2SQL创建的?
  • 非常感谢您的好意先生。嗯,我确实相信EF。至少在创建控制器时,我一直在选择“具有读/写操作和视图的控制器,使用 Entity Framework”;)
  • 实体框架有一个名为 AddObject 的通用方法,它以类型名称和对象作为参数,因此您可以使用它并避免反射(请参阅更新的答案)
【解决方案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); 时,我遇到了“null”,所以我继续通过反射检索类型。

我现在遇到的唯一麻烦就在这里:

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

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

更新
我完全忽略的一件事是将模型从视图传递给控制器​​......哦,麻烦了。我目前有一个 ActionLink 替换了普通的“提交”按钮,因为我不确定如何将创建正确的调查模型实例所需的字符串传递给控制器​​:

    <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 正在为我进行绑定(将表单值注入到模型值上)之后,为我的模型值设置了这个固定的空值。所以现在我的视图提交了一个简单的:

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

回到弄清楚如何将“IntranetDb.test”截断为“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,而是一个名为“tests”的实体集,并带有一个's'表示复数。结果我根本不需要截断任何东西,我只需要指向正确的对象: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 命名约定。

【讨论】:

  • @ThiemNguyen 好答案,嗯?如果每个 OP 都做同样的事情,那么没有人有兴趣回答堆栈溢出
  • 这不是一个坏行为。请看这个链接:meta.stackexchange.com/questions/17845/…
  • @Mark: 天哪,我刚刚又看了一遍你的回答,还没有投赞成票??我以为我之前已经投票了,对不起我的错误。也为您的回答 +1 ;)
  • 哦,我做错了吗?我刚刚标记了这篇文章,这样人们就可以更容易地确定我的结果。如果它会有所作为,我可以修复它:/谢谢蒂姆 :)
【解决方案3】:

我看到的主要问题是将模型绑定到TakeSurvey POST 方法。如果您希望此方法应处理不同类型的调查模型,并且 MVC 应在调用操作之前绑定到此模型,我相信您可以在所有此类通用模型上拥有一个包装模型类,例如 SurveyModel 并使用自定义模型绑定器绑定到这些模型。

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

然后编写一个自定义 mobel binder 来绑定这个模型。从请求表单字段中,我们可以看到发布了哪种类型的调查模型,然后相应地获取所有字段并初始化 SurveyModel 类。如果 SummerPicnicSurvey 已发布,则将使用此类设置 SurveyModel 类,并且 PartyAttendanceSurvey 将为空。 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();
    }

您可以继承并使用 .net as 通过检查进行类型转换并使用模型,而不是封装其他调查的 SurveyModel。

话虽如此,我认为对每个模型使用不同的方法并没有什么坏处。这将使您能够很好地对代码进行单元测试。如果其他情况太多,维护起来不健康。或者您可以将通用模型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();
   }

您添加的任何新的调查模型类型都必须实现 SaveChanges 或 Save 方法,这将调用正确的 dbcontext 方法。控制器操作只会在传递给它的通用“SurveyModel”引用上调用 Save。因此,该操作将对修改关闭,但对修改开放。开闭设计原则。

【讨论】:

  • 我还没有在我的 ASP.NET MVC 阅读中创建我自己的自定义模型绑定器;我可能需要检查一下。我绝对可以用一堆 if--else-if 语句或单独的 ActionMethods 来强制每次检查,但这会破坏我真正想要的目的,这是一个相当精简的目标控制器可以动态检测它正在传递的模型并将表单保存到适当的表中。如果我只有 2 或 3 次调查,这可能还好,但几年后当这个数字增长到 100+ 时,继续更新控制器会有点乏味:)
  • 去掉 if-else ai 是更好的设计。正如我之前所说,您可以对各种调查模型使用继承,每种特定类型的调查都派生自通用 SurveyModel。然后将 SaveChanges 和 dbcontext 封装在这个 SurveyModel 中。您添加的任何新的调查模型类型都必须实现 SaveChanges 或 Save 方法,这将调用正确的 dbcontext 方法。控制器操作只会在传递给它的通用“SurveyModel”引用上调用 Save。因此,该操作将对修改关闭,但对修改开放。开闭设计原则。
  • 编辑了上面的回答帖子来演示扩展设计。您可以通过自己的方式实现,以及您希望如何继承以及您希望封装保存更改的类。这个想法是通过移动不确定的代码来应用扩展设计,如您在问题中所说的`_db。 + typeof(调查) + .Add(调查);'到经常变化的实体。当将来有人在您的项目中添加新调查时,他必须实施通用调查以及模型绑定器中的一些修改。控制器没有变化。
  • 你的方法绝对是一个可行的选择。如果我无法让系统自己找出保存表单的位置,我可能会依赖它。谢谢=)
猜你喜欢
  • 1970-01-01
  • 2011-01-04
  • 2013-02-08
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
  • 1970-01-01
  • 2011-07-05
  • 2018-05-31
相关资源
最近更新 更多