ASP.Net MVC - Passing data between Views - c#

I'm currently trying to create an XML based website which has access to a feed URL. The XML feed is queried by adding url parameters to the current URL, and so I am using a form with GET method to add parameters to the URL.
I currently have a property search form which will search for properties within the feed by adding xml parameters to the url like so:
/Sales/?minprice=300000&maxprice=500000
This works perfectly and the correct results are shown to the user. However, if I was to use a filter form which filtered these properties by highest price for example, the feed parameters would be removed when the filter form is submitted. The new URL after the filter would be for example:
/Sales/?priceSort=descending
As you can see, the minprice and maxprice fields have been removed completely, leaving me with unwanted properties.
Currently, to combat this I am using sessions to store the URLs for each page and then combining them to make 1 url. I understand that using sessions within MVC based applications isn't exactly recommended.
So, I'm really just wondering if there is a better way to store the url's rather than using sessions?
Any help would be much appreciated.
Thanks in advance.
SOME CODE SNIPPETS OF THE SITE:
Model and ViewModel
public class ResultsViewModel
{
public PropertyResult[] Property { get; set; }
}
public class PropertyResult
{
public int Count { get; set; }
public int Pid { get; set; }
public int RentalPeriod { get; set; }
public string Price { get; set; }
public string Address { get; set; }
public string NameNumber { get; set; }
public string SA1 { get; set; }
public string SA2 { get; set; }
public string Town { get; set; }
public string City { get; set; }
public string County { get; set; }
public string Postcode { get; set; }
public string LocationCode { get; set; }
public string PriceText { get; set; }
public string Primary1 { get; set; }
public string Secondary1 { get; set; }
public string Secondary2 { get; set; }
public string Description { get; set; }
public string Period { get; set; }
public int Bedrooms { get; set; }
public int Receptions { get; set; }
public int Bathrooms { get; set; }
public int Garages { get; set; }
public int Gardens { get; set; }
public bool Featured { get; set; }
public int Views { get; set; }
}
Controller
try
{
var xml = XElement.Load(resultsFeed);
var query = (from props in xml.Descendants("property")
select new PropertyResult
{
// property id
Pid = Convert.ToInt32(props.Attribute("id").Value),
// Rooms Count
Bedrooms = Convert.ToInt32(props.Attribute("bedrooms").Value),
Receptions = Convert.ToInt32(props.Attribute("receptions").Value),
Bathrooms = Convert.ToInt32(props.Attribute("bathrooms").Value),
Gardens = Convert.ToInt32(props.Attribute("gardens").Value),
Garages = Convert.ToInt32(props.Attribute("garages").Value),
// 1 = sales prop, 4 = lettings prop
RentalPeriod = Convert.ToInt32(props.Attribute("rentalperiod").Value),
Period = props.Attribute("period").Value,
// address
Address = props.Element("useAddress").Value,
NameNumber = props.Element("num").Value,
SA1 = props.Element("sa1").Value,
SA2 = props.Element("sa2").Value,
Town = props.Element("town").Value,
City = props.Element("city").Value,
County = props.Element("county").Value,
Postcode = props.Element("postcode").Value,
// location code
LocationCode = props.Element("locationcodes").Value,
Featured = Convert.ToBoolean(props.Attribute("featured").Value),
// description
Description = props.Element("summaryDescription").Value,
// price
Price = props.Attribute("price").Value,
PriceText = props.Element("pricetext").Value,
// images
Primary1 = "http://lb.dezrez.com/Imaging/PictureResizer.ASP?Position=1&AgentId=" + eaid + "&BranchId="+ bid + "&width=1000&Category=Primary&PropertyId=",
Secondary1 = "http://www.dezrez.com/estate-agent-software/ImageResizeHandler.do?&photoID=2&AgentID=1239&BranchID=1976&Width=1000&PropertyId=",
Secondary2 = "http://www.dezrez.com/estate-agent-software/ImageResizeHandler.do?&photoID=3&AgentID=1239&BranchID=1976&Width=1000&PropertyId=",
}).ToArray();
View
I'm currently accessing each node like so:
#Model.Property[i].Gardens

In MVC you need to pass all the needed parameters (or rely in some store as Session, Cache, Db).
So, when sorting, you are just sending the column and order... in this case, you need to post also the filter values.
The correct way to do this, is having a ViewModel with all the filters and sorting parameters... and when you return from filtering or sorting, you can render the current filters.
So, besides filling the filter inputs with the current filters, you should craft the links to take into account all the parameters. For instance: when ordering, you pass also current filters... or if you change the filters you should maintain sortorder passing it on post.
Some code:
Your ViewModel:
public class SalesFilter{
public int? MinPrice {get; set;}
public int? MaxPrice {get; set;}
public int? IdTypeOfSale {get; set;}
...
...
...
public IEnumerable<Sale> FilteredValues {get; set;}
//SelectLists if you need that your filters being DropDownLists
public SelectList TypesOfSales {get; set;}
}
Your Controller:
public ActionResult Sales(){
var model = new SalesFilter();
model.FilteredValues = db.YourSales.Where(/*your init conditions*/);
//set other initial values in your ViewModel
return View(model);
}
[HttpPost]
public ActionResult Sales(SalesFilter filters){
model.FilteredValues = db.YourSales.Where(/*use the conditions of your filter */);
model.TypesOfSales = new SelectList(db.TypesOfSales, "idType", "Name", filter.IdTypeOfSale);
return View(model);
}

Consider using a Domain Model (all your business data, etc.) and separate View Models and extension methods that will transform your domain model to a specific view model and vice versa. Decoupling your business model from your view model using the transform indirection gives you the opportunity to use simple, easy to use view models that fit your view.

Related

Persisting unbound viewmodel data when posting

I am currently in the process of getting accustomed to MVC, having come from ASP.Net.
So far I have found ways to achieve what I want to do, but with this one I am getting a "This cannot be the easiest way" moment.
Scenario:
I am migrating a quoting application to MVC that has an existing database so my model classes are auto-generated. I have created a viewmodel class for each controller action that needs to display data to the user.
The edit quote viewmodel looks like this:
public class QuoteEdit_ViewModel
{
public SelectList DelLocations { get; set; }
public int QuoteID { get; set; }
public string QuoteNo { get; set; }
public string EnquiryNo { get; set; }
public string SalesPerson { get; set; }
public string Exceptions { get; set; }
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
[Required]
[Display(Name = "Equipment Overview")]
public string EquipmentOverview { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
[Required]
public int? Validity { get; set; }
[Required]
[Display(Name = "Minimum Delivery Weeks")]
public int? DeliveryMin { get; set; }
[Required]
[Display(Name = "Maximum Delivery Weeks")]
public int? DeliveryMax { get; set; }
[Required]
[Display(Name = "Delivery Location")]
public int? DelLocationID { get; set; }
public List<Constants.QPT> PackTypes { get; set; }
public List<Constants.QE> Equipments { get; set; }
public List<Constants.QEEx> Extras { get; set; }
}
The lists at the bottom contain equipment lines etc that are junction tables in the database.
Currently I can edit this and post the data back to the database and it works perfectly.
The part that seems messy is the following, specifically the part after the if:
public ActionResult Save(QuoteEdit_ViewModel VM)
{
Quote a = DAL.DB.Quotes.Where(x => x.QuoteID == VM.QuoteID).Single();
TryUpdateModel(a);
if (ModelState.IsValid)
{
DAL.DB.SaveChanges();
return RedirectToAction("Dashboard", "Home");
}
VM.DelLocations = DAL.GetDeliveryLocationDropdown();
var QData = DAL.GetQuoteEditVM(VM.QuoteID);
VM.QuoteNo = QData.QuoteNo;
VM.EnquiryNo = QData.EnquiryNo;
VM.SalesPerson = QData.SalesPerson;
VM.PackTypes = QData.PackTypes;
VM.Equipments = QData.Equipments;
VM.Extras = QData.Extras;
VM.Created = QData.Created;
VM.CreatedBy = QData.CreatedBy;
VM.Modified = QData.Modified;
VM.ModifiedBy = QData.ModifiedBy;
return View("Edit", VM);
}
Currently I need to reload the entire viewmodel and repopulate any fields that were not bound in the view as their values are lost during the POST.
I have read in other posts that you can use hiddenfor, but can this be used for Lists as well?
Also is this the correct way to approach this or am I completely missing the point of MVC?
Do not use HiddenFor. You're going about the correct way. The only change I would make is factoring out your common code into another method(s) that both the GET and POST actions can utilize.
private void PopulateQuoteEditViewModel(QuoteEdit_ViewModel model)
{
mode.DelLocations = DAL.GetDeliveryLocationDropdown();
var QData = DAL.GetQuoteEditVM(model.QuoteID);
model.QuoteNo = QData.QuoteNo;
model.EnquiryNo = QData.EnquiryNo;
model.SalesPerson = QData.SalesPerson;
model.PackTypes = QData.PackTypes;
model.Equipments = QData.Equipments;
model.Extras = QData.Extras;
model.Created = QData.Created;
model.CreatedBy = QData.CreatedBy;
model.Modified = QData.Modified;
model.ModifiedBy = QData.ModifiedBy;
}
Then:
public ActionResult QuoteEdit()
{
var model = new QuoteEdit_ViewModel();
PopulateQuoteEditViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult QuoteEdit(QuoteEdit_ViewModel model)
{
if (ModelState.IsValid)
{
...
}
PopulateQuoteEditViewModel(model);
return View(model);
}
Other Comments
Don't use TryUpdateModel. It's not meant to be used the way you are here. The correct approach is to map over the posted values from your view model. You can either do this manually, or utilize a library like AutoMapper. Either way, you don't want to just willy-nilly overwrite anything on your database entity based on raw posted data as you're doing.
You should never post the ID of the entity you're editing, but rather rely on obtaining it from the URL via a route param. If the URL is changed to a different ID, you are literally editing a different thing, and you can add object-level permissions and such to control who can edit what. However, the ID that's posted can be manipulated, and if you aren't careful (as you aren't being here), then a user can tamper with the ID to mess with objects they potentially shouldn't be editing.

ASP.Net MVC forms/model binding and delete records?

I have a model that I'm loading into a table within a form. The records are retrieved from an Oracle DB using EF6 and loaded into the model.
I also want the user to be able to select records to delete from the database via a checkbox in each row in the form.
The function to retrieve the Attendees:
public List<WebinarAttendeesList> getAttendees(string webinarKey)
{
string connectionString = "Password=password;User Id=user;Data Source=Oracle";
List<WebinarAttendeesList> r = null;
using (webinarAttendeesListDbContext context = new webinarAttendeesListDbContext(connectionString))
{
var result = from w in context.WebinarAttendeesList
where w.webinarKey == webinarKey
orderby w.FirstPollCount, w.SecondPollCount
select w;
r = result.ToList();
}
return r;
}
Here is the model:
[Table("WEBINARATTENDEESLIST")]
public class WebinarAttendeesList {
[Key, Column("WAL_ID")]
public int wa_id { get; set; }
[Column("WAL_CLI_RID")]
public int ParticipantID { get; set; }
[Column("WAL_FULLNAME")]
public string FullName { get; set; }
[Column("WAL_EMAIL")]
public string Email { get; set; }
[Column("WAL_JOINTIME")]
public string JoinTime { get; set; }
[Column("WAL_TIMEINSESSION")]
public string TimeInSession { get; set; }
[Column("WAL_LEAVETIME")]
public string LeaveTime { get; set; }
[Column("WAL_FIRSTPOLLCOUNT")]
public int FirstPollCount { get; set; }
[Column("WAL_SECONDPOLLCOUNT")]
public int SecondPollCount { get; set; }
[Column("WAL_ATTENDEDWEBINAR")]
public int AttendedWebinar { get; set; }
[Column("WAL_MAKEUP")]
public int Makeup { get; set; }
[Column("WAL_COMMENTS")]
public string Comments { get; set; }
[Column("WAL_REGISTRANTKEY")]
public string RegistrantKey { get; set; }
[Column("WAL_WEBINARKEY")]
public string webinarKey { get; set; }
}
When the form is submitted, I am passing the model to a function to store the records in EF6.
public ActionResult PostAttendees(ICollection<WebinarAttendeesList> attendees)
{
foreach (WebinarAttendeesList attendee in attendees)
{
UpdateAttendee(attendee);
}
}
How would I edit the model to allow this delete the records that are selected and update the ones that don't have the checkbox selected?
If I put an int delete property on the model that has no Column attribute I get this exception:
ORA-00904: "Extent1"."delete": invalid identifier
I found this tutorial but I'm NOT using any helpers in the creation of the form and do not have any ViewModels and it also doesn't explain how to handle doing different things to the different records based on the checkbox: http://johnatten.com/2014/01/05/asp-net-mvc-display-an-html-table-with-checkboxes-to-select-row-items/
Is there a better way to do this?
Yes. All models properties in EF are suppose to be column. You should use NotMapped attribute if you don't want property to be treated as a 'column' in database.

View Model implementation - less fields than in entities

I have two entities: Person and Quote (in one to many relationship)
Person:
public class Person
{
public int PersonID { get; set; }
[Required]
[StringLength(20]
public string Name { get; set; }
[StringLength(30]
public string Relation { get; set; }
public byte[] Image { get; set; }
[StringLength(50)]
public string ImageMimeType { get; set; }
public virtual ICollection<Quote> Quotes { get; set; }
}
Quote:
public class Quote
{
public int QuoteID { get; set; }
public int PersonID { get; set; }
[Required]
[StringLength(200)]
public string QuoteName { get; set; }
[StringLength(400)]
public string Context { get; set; }
public DateTime? Date { get; set; }
public virtual Person Person { get; set; }
}
I want to make a ViewModel for displaying quotes in short format - I need just a few properties - Person Name, QuoteName and Person Image. I could do something casual like they're showing in every ASP.NET MVC tutorial:
public class QuoteViewModel
{
public IEnumerable<Quote> Quotes { get; set; }
}
Is there a better way rather than creating IEnumerable with type of Quote and loading all properties?
How about creating QuoteShort model and making QuoteViewModel as IEnumerable<QuoteShort> QuotesShort.
In controller I would map every 3 fields from repository to QuoteShort and add it to QuotesShort IEnumerable (even though I don't know how to persist them to QuotesShort IEnumerable )
Some examples appreciated.
You can make a QuoteShort ViewModel with just the few properties you need, and then have your view expect IEnumerable<QuoteShort> as its model. You don't necessarily have to wrap that up in another container.
If you have this:
public class QuoteShort{
public Person Person {get;set;}
public string Name {get; set;}
// etc
}
You can do this in the controller:
var quotes = //however you get your list of quotes
var model = (from q in quotes select new QuoteShort
{ Person = q.Person, Name = q.Name /*etc*/ }).ToList();
return View(model);
What about something like
public class QuotesShortViewModel
{
public IEnumerable<QuoteShortViewModel> QuotesShort { get; set; }
}
public class QuoteShortViewModel
{
// ... the properties you need
}
Create a View that receives a QuotesShortViewModel and iterates through the list, rendering the short quotes as it pleases you.
AutoMapper is useful to map between Models and ViewModels in your controllers.

EF5 and Lamba/Linq - how only Include some columns of related tables

I have a method in my repository to retrieve All records for Items
public IQueryable<Item> GetAll()
{
//The following causes a circular reference if you attempt to serialize it via an API call.
IQueryable<Item> items = context.Items.Include(c => c.UserProfile).Include(c => c.UserProfile1).AsQueryable();
return items;
}
This causes issues with Kendo Grid and serialization because of how I am including the foreign tables User Profile twice to be able to get the full name of the user whom created and modified the Item record.
Instead of Include(c => c.UserProfile) is there a way to only include the UserProfile.FullName column?
Today I am handling this in my ViewModel and creating a new subclass (this example is for Locations, not Items):
public class LocationsListViewModel
{
public IEnumerable<LocationsGrid> Locations { get; set; }
public IEnumerable<Facility> Facilities { get; set; }
public IEnumerable<string> AreaOptions { get; set; }
public int LocationCount { get; set; }
public class LocationsGrid
{
public int Id { get; set; }
public string DisplayLocation { get; set; }
public string Area { get; set; }
public string Zone { get; set; }
public string Aisle { get; set; }
public string Bay { get; set; }
public string Level { get; set; }
public string Position { get; set; }
public string Barcode { get; set; }
}
}
and then having to populate that in my Tasks or App Services layer (sits between controller and repository) like this:
viewModel.Locations = from l in locations.ToList()
select new LocationsListViewModel.LocationsGrid
{
Id = l.Id,
DisplayLocation = l.DisplayLocation,
Area = l.Area,
Zone = l.Zone,
Aisle = l.Aisle,
Bay = l.Bay,
Level = l.Level,
Position = l.Position,
Barcode = l.BarcodeValue
};
This seems like a lot of extra code and maintenance for each entity going forward. I'm sure there is a more efficient way to do this.
I typically use a Data-Transfer Object (basically just a class that has the exact data you're looking for, then returning objects of that type from your data-access method.
public IQueryable<ItemSummary> GetAll()
{
IQueryable<ItemSummary> items = context.Items
.Select(c => new ItemSummary {
FirstProfileName = c.UserProfile.FullName,
SecondProfileName = c.UserProfile1.FullName,
ScalarProp1 = c.ScalarProp1,
...
})
.AsQueryable();
return items;
}
I'm not sure if that will work the way you want it to, since I'm not familiar with Kendo Grid and such, but it may be useful.

How should I handle lookups in my ViewModel?

My database table for buildings stores the building type as a code. In a separate lookup table the description for that code is stored.
How should I design my ViewModel and where will I need to make the call to get the associated description value?
I sort of can see one option. I want to know if there is a better option.
BuildingViewModel
{
public string BuildingTypeCode { get;set;}
...other properties
}
Then in my view
code...
<p>#MyService.GetDescription(Model.BuildingTypeCode)</p>
...code
Am I incorrect in the way I am thinking? if I do the above I create a dependency in my View to the service?
Update 1
Working through some of the solutions offered. I seem to run into another issue. I can't access the constructor of each building directly...
public ViewResult Show(string ParcelId)
{
var result = _service.GetProperty(ParcelId);
var AltOwners = _service.GetAltOwners(ParcelId);
var Buildings = _service.GetBuildings(ParcelId);
ParcelDetailViewModel ViewModel = new ParcelDetailViewModel();
ViewModel.AltOwnership = new List<OwnerShipViewModel>();
ViewModel.Buildings = new List<BuildingViewModel>();
AutoMapper.Mapper.Map(result, ViewModel);
AutoMapper.Mapper.Map<IEnumerable<AltOwnership>, IEnumerable<OwnerShipViewModel>>(AltOwners,ViewModel.AltOwnership);
AutoMapper.Mapper.Map<IEnumerable<Building>, IEnumerable<BuildingViewModel>>(Buildings, ViewModel.Buildings);
ViewModel.Pool = _service.HasPool(ParcelId);
ViewModel.Homestead = _service.IsHomestead(ParcelId);
return View(ViewModel);
}
public class ParcelDetailViewModel
{
public IEnumerable<OwnerShipViewModel> AltOwnership { get; set; }
//public IEnumerable<ValueViewModel> Values { get; set; }
public IEnumerable<BuildingViewModel> Buildings { get; set; }
//public IEnumerable<TransferViewModel> Transfers { get; set; }
//public IEnumerable<SiteAddressViewModel> SiteAddresses { get; set; }
public string ParcelID { get; set; }
//public string ParcelDescription { get; set; }
//public int LandArea { get; set; }
//public string Incorporation { get; set; }
//public string SubdivisionCode {get;set;}
public string UseCode { get; set; }
//public string SecTwpRge { get; set; }
//public string Census { get; set; }
//public string Zoning { get; set; }
public Boolean Homestead {get;set;}
//public int TotalBuildingArea { get; set; }
//public int TotalLivingArea { get; set; }
//public int LivingUnits { get; set; }
//public int Beds { get; set; }
//public decimal Baths { get; set; }
public short Pool { get; set; }
//public int YearBuilt { get; set; }
}
My understanding is that the view model is meant for display ready data. I think the real problem here is putting model dependent logic into the view.
You can do your service lookup but keep that code in the controller. The view model should be considered display ready (save for some formatting).
class BuildingViewModel
{
public string BuildingTypeCode { get;set;}
...other properties
}
and then do the lookup before you render:
public ActionResult Building()
{
var typeCode = // get from original source?
var model = new BuildingViewModel
{
BuildingTypeCode = MyService.GetDescription(typeCode)
};
return View("Building", model);
}
Having come from a long line of JSP custom tags I dread having any code hidden in the view layout. IMO, that layer should be as dumb as possible.
I would recommend having a helper that does that, or a DisplayTemplate
public class ViewHelpers
{
public static string GetDescription(string code)
{
MyService.GetDescription(Model.BuildingTypeCode);
}
}
or
#ModelType string
#Html.DisplayFor("",MyService.GetDescription(Model.BuildingTypeCode));
More info on templates: http://www.headcrash.us/blog/2011/09/custom-display-and-editor-templates-with-asp-net-mvc-3-razor/
Both of these approaches introduce a dependency on your service but you can test/change them in one single place, instead of the whole application (plus the usage looks cleaner).

Categories