0

I'm calling three Models (Unit, Site, Work_Type) in my view model called UnitAdminViewModel. I need to set one field as required from the Unit Model. Since I'm using Database First approach, I cannot modify the Unit Model directly since this gets autogenerated. How can I successfully add:

[Required(ErrorMessage = "Group is required")] public string GroupName { get; set; }

to my view model UnitAdminViewModel?

public class UnitAdminViewModel
{
    public Unit Unit { get; set; }
    public List<Site> Site { get; set; }
    public IEnumerable<Work_Type> Work_Type { get; set; }
}

In the Unit Model, I want to set the field GroupName as [Required]

public partial class Unit
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Unit()
    {
        this.Staffs = new HashSet<Staff>();
    }

    public int UnitID { get; set; }
    public string UnitCode { get; set; }
    public string UnitName { get; set; }
    public string GroupName { get; set; }
    public byte IncentiveUnit { get; set; }
    public bool CallCenter { get; set; }
    public bool CDWUnit { get; set; }
    public string CDWSite { get; set; }
    public Nullable<int> SiteID { get; set; }
    public Nullable<int> DivisionID { get; set; }
    public bool WFCUnit { get; set; }
    public bool QAMonitored { get; set; }
    public bool NICEMonitored { get; set; }
    public string ListPrefix { get; set; }
    public string TSHSource { get; set; }
    public string StatsSource { get; set; }
    public string DialerSource { get; set; }
    public Nullable<int> CostCenterID { get; set; }
    public int WaterfallView { get; set; }
    public bool Locked { get; set; }
    public string Platform { get; set; }
    public Nullable<int> Supplier { get; set; }
    public string Work_Type { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Staff> Staffs { get; set; }
}

Update


I tried going off @Izzy example. I feel like i'm closer, but the [Required] still doesn't seem to trigger a validation error when I submit a form without populating that field. @Izzy, is there something I might be missing?

View Model

public class UnitAdminViewModel
{
    public Unit Unit { get; set; }
    public List<Site> Site { get; set; }
    public IEnumerable<Work_Type> Work_Type { get; set; }

}

UnitMetaData class

[MetadataType(typeof(UnitMetaData))]
    public partial class Unit
    {

    }

    public class UnitMetaData {
        [Required(ErrorMessage = "Group is required")]
        public string GroupName { get; set; }

        [Required(ErrorMessage = "UnitName is required")]
        public string UnitName { get; set; }

        public string CDWSite { get; set; }

        public string Platform { get; set; }

        public Nullable<int> Supplier { get; set; }

        public string Work_Type { get; set; }
}

VIEW

    @model WebReportingToolDAL.Models.ViewModels.UnitAdminViewModel

@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Create</h2>

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>Unit</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.UnitName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Unit.UnitName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Unit.UnitName, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.GroupName, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Unit.GroupName, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Unit.GroupName, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.CDWSite, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.CDWSite, new SelectList(Model.Site, "SiteName", "SiteName"), new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.Platform, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.Platform, new List<SelectListItem> { new SelectListItem { Text = "PSCC", Value = "PSCC" }, new SelectListItem { Text = "RC", Value = "RC" } }, new { @class = "form-control" }) 
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.Supplier, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.Supplier, new List<SelectListItem> { new SelectListItem { Text = "0", Value = "0" }, new SelectListItem { Text = "1", Value = "1" } }, new { @class = "form-control" }) 
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Unit.Work_Type, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.Unit.Work_Type,new SelectList(Model.Work_Type, "Name", "Name"),new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</div>
}

Controller

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "UnitID,UnitCode,UnitName,GroupName,IncentiveUnit,CallCenter,CDWUnit,CDWSite,SiteID,DivisionID,WFCUnit,QAMonitored,NICEMonitored,ListPrefix,TSHSource,StatsSource,DialerSource,CostCenterID,WaterfallView,Locked,Platform,Supplier,Work_Type")] Unit unit)
    {
        if (ModelState.IsValid)
        {
            unit.UnitCode = "XX";
            unit.IncentiveUnit = 1;
            unit.CallCenter = true;
            unit.CDWUnit = true;
            unit.DivisionID = 2;
            unit.WFCUnit = false;
            unit.QAMonitored = false;
            unit.NICEMonitored = true;
            unit.ListPrefix = null;
            unit.TSHSource = null;
            unit.StatsSource = null;
            unit.DialerSource = null;
            unit.CostCenterID = 3;
            unit.WaterfallView = 1;
            unit.Locked = false;

            var siteId = (from s in db.Sites
                         where s.SiteName.ToLower().Equals(unit.CDWSite.ToLower())
                         select s.SiteID).First();

            unit.SiteID = siteId;

            db.Units.Add(unit);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(unit);
    }
GRU119
  • 1,028
  • 1
  • 14
  • 31
  • To me, it looks like you are using Entity, and this is really where other ORM's like Dapper would shine. With Entity, you have to copy the properties of the object to your "Model" and add the [Required] data annotation there. And when saving it back to your database, copy back to your POCO and save it. With Dapper, you can simply create a POCO class that is modeled from your database table, and add your data annotation right to it. – Kevin B Burns May 04 '17 at 13:12
  • Don't you want/need a UnitViewModel? Because that would be the right and logical place to add the attribute. – H H May 04 '17 at 13:48
  • @KevinBBurns - all those benefits apply to EF with code-first as well. This is just a consequence of database-first. – H H May 04 '17 at 13:49

2 Answers2

4

When using Database first approach you'll realise that the class is marked as partial So what you can do is make use of MetadataType attribute to achieve what you're after.

So go ahead and create a file and name it e.g. UnitMetaData. Your code should look something like:

public class UnitMetaData
{
    [Required(ErrorMessage = "Group is required")]
    public string GroupName { get; set; }
    //more properties
}

Your Unit class is partial so you can create it another file and use MetadataType as:

[MetadataType(typeof(UnitMetaData))]
public partial class Unit
{
}

More about MetadataType here

partial definition:

It is possible to split the definition of a class or a struct, an interface or a method over two or more source files. Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled.

source

Please Note: Ensure the namespace is same as the generated Unit class, otherwise it will not work

Izzy
  • 6,740
  • 7
  • 40
  • 84
  • Thanks for the detailed, @Izzy. I'm using your response now to see if I can get this to work. One question, when you have //more properties listed in your UnitMetaData class example. Are you saying I need to add the rest of my properties? Or can I just add GroupName if that's the only one I need required? – GRU119 May 04 '17 at 13:38
  • I see your second note about splitting definition of a class. So i'm assuming this is a yes then? – GRU119 May 04 '17 at 13:40
  • @GRU119 You're welcome! you do not have to add all the properties just the ones that require `DataAnnotations`. Also do have a look at @Chris answer he makes a very valid point. – Izzy May 04 '17 at 13:43
  • I tried your @Izzy approach but still seem to have issues. I updated my original question...thoughts? – GRU119 May 04 '17 at 17:34
  • @GRU119 can you add the code from your view please and also I'm assuming you're using jQuery for client side validation? – Izzy May 04 '17 at 17:50
  • I added the code from my view, @Izzy. So the reason why i'm trying to get [Required] to work correctly, so i can validate my form server side vs client side. I have no validation client side. From my understanding, if the [Required] works as expected, I would not need to have client side validation. – GRU119 May 04 '17 at 18:07
  • @GRU119 I'm sure you're going to show a message or something to the client when the validation checks don't pass. Also in the POST action I'm guessing you've got `if(ModelState.IsValid){}`? – Izzy May 04 '17 at 18:12
  • I also posted my controller, but yes I have `if(ModelState.IsValid){}` The message that should be displayed would be coming from the ErrorMessage that is in my UnitMetaData i'm assuming – GRU119 May 04 '17 at 18:16
  • @GRU119 In your model you need to use `Unit` class not `UnitMetaData`. Also look [here](http://stackoverflow.com/questions/19734608/required-attribute-not-working-in-asp-net-mvc) it's a good guide on how to handle your required fields – Izzy May 04 '17 at 18:23
  • I updated my model to use Unit (Orignal question updated) but still not luck. When I fill out the form fully, i'm able to submit the form successfully. When I fill out the form, but leave GroupName blank, I get the following error: _The model item passed into the dictionary is of type 'WebReportingToolDAL.Models.Unit', but this dictionary requires a model item of type 'WebReportingToolDAL.Models.ViewModels.UnitAdminViewModel'._ – GRU119 May 04 '17 at 18:42
  • I appreciate the help, @Izzy. Sorry this is getting lengthy...Would Client Side be a better approach? – GRU119 May 04 '17 at 18:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143468/discussion-between-izzy-and-gru119). – Izzy May 05 '17 at 07:25
2

You can use a real view model, for one. Simply wrapping a bunch of entities in a class is missing the point of what view models are for. Your view models should only contain the properties that should be displayed/edited and it should hold the business logic for your view, such as the fact that GroupName is required (when it apparently isn't at the database level).

That means creating something like:

public class UnitViewModel
{
    // other properties you want to edit

    [Required]
    public string GroupName { get; set; }
}

Then, you use this rather than Unit in your view, and map the posted properties from UnitViewModel onto your Unit instance.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • you made me curious about this field at the database level. I checked. GroupName cannot be null in the database. With that being said, it is required at the database level. Since it's required at the Database level, do you know why EF does not honor this? You would think the auto generated model would have the [Required] annotations already there? The whole point of this question is...when I create a new record on a web form, I want the validation saying "Group Name is required" if they don't fill it out when submitting. Do I even need the [Required] annotation to do this? – GRU119 May 04 '17 at 13:58