In my controller i have an ID to an object in a database, and multiple actions needs this id to access the object. This ID is fetched in the Index action and used to populate the view model. The way I get the ID is through an argument, which EPiServer (out CMS) passes the Index action, so I cant really get it anywhere else.
My question is, is there any way to make this id accessible to my actions without the use a hidden field etc on the client side?
Id like to avoid sending extra data as much as possible, and it isnt really a good solution.
I'm not sure what you mean by "it isn't really a good solution," since using hidden fields is potentially the cleanest method of persisting data across multiple posts. Bear in mind that HTTP is - and should be treated as - a stateless protocol. A few other options to consider, however:
Cookies
Add the value to a cookie in your controller and add it to the Response object. Then, pick it up and read it as and when you need it using Request.Cookies.
/* Setting */
Response.Cookies["id"].Value = "myId";
/* Getting */
var id = Request.Cookies["id"] != null
? Request.Cookies["id"].Value
: String.Empty;
Pros: Persist across multiple requests without having to do anything in the requests in between.
Cons: Easily switched off client-side.
Session state
Add the value to session state using the Session collection.
/* Setting */
Session["id"] = "myId";
/* Getting */
var myId = Session["id"] != null ? Session["id"].ToString() : String.Empty;
Pros: Easy to use, persist across multiple requests
Cons: Can get very messy if the user has multiple tabs open, uses the 'Back' button, etc. Often unpredictable.
Hidden fields
Add the value to a hidden field and pick this value back up on the next POST.
/* View (assuming string model property 'Id') */
#Html.BeginForm("MyAction", "MyController")
{
#Html.HiddenFor(m => m.Id)
<input type="submit" value="Submit" />
}
/* Controller */
public ActionResult MyAction(MyModel model)
{
var id = model.Id;
}
Pros: No footprint. Clean, simple, stateless and efficient.
Cons: No good for persisting across GET requests. Only persists across one POST at a time.
Query string
Add the values as query string parameters on redirects.
/* View */
#Html.ActionLink("Go!", "MyAction", new { identifier = "myId" })
/* Controller */
public ActionResult MyAction(string identifier)
{
var id = identifier ?? String.Empty;
}
Pros: Unlike hidden fields, can be used for GET requests.
Cons: Requires you to implement a method of persisting the query string across all requests for which you need the values.
What's best?
Personally, I'd say that - in your case - using cookies is probably the best option. If you want to persist across multiple requests without having to handle the value in every single request (and without the horribleness of using Session), cookies are likely to be the simplest and neatest approach.
I usually go for hidden input in form for a scenario like this but if you are unwilling, You can use Cookies or Sessions.
Related
From what I've read, I'm supposed to be using ViewModels to populate my views in MVC, rather than the model directly. This should allow me to pass not just the contents of the model, but also other information such as login state, etc. to the view instead of using ViewBag or ViewData. I've followed the tutorials and I've had both a model and a viewmodel successfully sent to the view. The original problem I had was that I needed a paginated view, which is simple to do when passing a model alone, but becomes difficult when passing a viewmodel.
With a model of
public class Instructor {
public string forename { get; set; }
public string surname { get; set; }
}
and a viewmodel of
public class InstructorVM {
public Instructor Instructors { get; set; }
public string LoggedIn { get; set; }
}
I can create a paginated list of the instructors using the pure model Instructor but I can't pass InstructorVM to the view and paginate it as there are other properties that aren't required in the pagination LoggedIn cause issues. If I pass InstructorVM.Instructors to the view, I get the pagination, but don't get the LoggedIn and as this is just the model, I may has well have passed that through directly.
An alternative that was suggested was to convert/expand the viewmodel into a list or somesuch which would produce an object like this that gets passed to the view
instructor.forename = "dave", instructor.surname = "smith", LoggedIn="Hello brian"
instructor.forename = "alan", instructor.surname = "jones", LoggedIn="Hello brian"
instructor.forename = "paul", instructor.surname = "barns", LoggedIn="Hello brian"
where the LoggedIn value is repeated in every row and then retrieved in the row using Model[0].LoggedIn
Obviously, this problem is caused because you can only pass one object back from a method, either Instructor, InstructorVM, List<InstructorVM>, etc.
I'm trying to find out the best option to give me pagination (on part of the returned object) from a viewmodel while not replicating everything else in the viewmodel.
One suggestion was to use a JavaScript framework like React/Angular to break up the page into a more MVVM way of doing things, the problem with that being that despite looking for suggestions and reading 1001 "Best JS framework" lists via Google, they all assume I have already learned all of the frameworks and can thus pick the most suitable one from the options available.
When all I want to do is show a string and a paginated list from a viewmodel on a view. At this point I don't care how, I don't care if I have to learn a JS framework or if I can do it just using MVC core, but can someone tell me how to do this thing I could do quite simply in ASP.NET? If it's "use a JS framework" which one?
Thanks
I'm not exactly sure what the difficulty is here, as pagination and using a view model aren't factors that play on one another. Pagination is all about selecting a subset of items from a data store, which happens entirely in your initial query. For example, whereas you might originally have done something like:
var widgets = db.Widgets.ToList();
Instead you would do something like:
var widgets = db.Widgets.Skip((pageNumber - 1) * itemsPerPage).Take(itemsPerPage).ToList();
Using a view model is just a layer on top of this, where you then just map the queried data, no matter what it is onto instances of your view model:
var widgetViewModels = widgets.Select(w => new WidgetViewModel
{
...
});
If you're using a library like PagedList or similar, this behavior may not be immediately obvious, since the default implementation depends on having access to the queryset (in order to do the skip/take logic for you). However, PagedList, for example has StaticPagedList which allows you to create an IPagedList instance with an existing dataset:
var pagedWidgets = new StaticPagedList<WidgetViewModel>(widgetViewModels, pageNumber, itemsPerPage, totalItems);
There, the only part you'd be missing is totalItems, which is going to require an additional count query on the unfiltered queryset.
If you're using a different library, there should be some sort of similar functionality available. You'll just need to confer with the documentation.
Beginner question : I've worked through the Try Meteor tutorial. I've got fields in my HTML doc, backed by helper functions that reference collections, and BOOM --> the fields are updated when the data changes in the DB.
With the "Hide completed" checkbox, I've also seen data-binding to a session variable. The state of the checkbox is stored in the Session object by an event handler and BOOM --> the list view is updated "automatically" by its helper when this value changes. It seems a little odd to be assigning to a session object in a single page application.
Through all this, my js assigns nothing in global scope, I've created no objects, and I've mostly seen just pipeline code, getting values from one spot to another. The little conditional logic is sprayed about wherever it is needed.
THE QUESTION... Now I want to construct a model of my business data in javascript, modelling my business rules, and then bind html fields to this model. For example, I want to model a user, giving it an isVeryBusy property, and a rule that sets isVeryBusy=true if noTasks > 5. I want the property and the rule to be isolated in a "pure" business object, away from helpers, events, and the meteor user object. I want these business objects available everywhere, so I could make a restriction, say, to not assign tasks to users who are very busy, enforced on the server. I might also want a display rule to only display the first 100 chars of other peoples tasks if a user isVeryBusy. Where is the right place to create this user object, and how do I bind to it from my HTML?
You can (and probably should) use any package which allows you to attach a Schema to your models.
Have a look at:
https://github.com/aldeed/meteor-collection2
https://github.com/aldeed/meteor-simple-schema
By using a schema you can define fields, which are calculated based on other fields, see the autoValue property: https://github.com/aldeed/meteor-collection2#autovalue
Then you can do something like this:
// Schema definition of User
{
...,
isVeryBusy: {
type: Boolean,
autoValue: function() {
return this.tasks.length > 5;
}
},
...
}
For all your basic questions, I can strongly recommend to read the DiscoverMeteor Book (https://www.discovermeteor.com/). You can read it in like 1-2 days and it will explain all those basic questions in a really comprehensible way.
Best Regards,
There is a very good package to implement the solution you are looking for. It is created by David Burles and it's called "meteor-collection-helper". Here it the atmosphere link:
You should check the link to see the examples presented there but according to the description you could implement some of the functionality you mentioned like this:
// Define the collections
Clients = new Mongo.Collection('clients');
Tasks = new Mongo.Collection('tasks');
// Define the Clients collection helpers
Clients.helpers({
isVeryBusy: function(){
return this.tasks.length > 5;
}
});
// Now we can call it either on the client or on the server
if (Meteor.isClient){
var client = Clients.findOne({_id: 123});
if ( client.isVeryBusy() ) runSomeCode();
}
// Of course you can use them inside a Meteor Method.
Meteor.methods({
addTaskToClient: function(id, task){
var client = Clients.findOne({_id: id});
if (!client.isVeryBusy()){
task._client = id;
Tasks.insert(task, function(err, _id){
Clients.update({_id: client._id}, { $addToSet: { tasks: _id } });
});
}
}
});
// You can also refer to other collections inside the helpers
Tasks.helpers({
client: function(){
return Clients.findOne({_id: this._client});
}
});
You can see that inside the helper the context is the document transformed with all the methods you provided. Since Collections are ussually available to both the client and the server, you can access this functionality everywhere.
I hope this helps.
I am new to MVC and EF and I have a question.
I have built a site with models views controllers etc.
On an edit view for a Case (pretty big model so I won't post it here) I have a FK to a Customer model using CustomerID. When a user selects a customer id from a drop down list, I would like to display CustomerName, CustomerPhone etc after the selection of the ID. I think I might need to do a post back for this to work?
Also, do I need to Include the related entities as part of the initial data "get"? I have read some detail on that but I dont fully understand how that needs to work.
Please let me know if I should post more info. Thanks!
Here is my ActionResult for Edit
public ActionResult Edit(int id)
{
Cases cases = db.Cases.Find(id);
//related data needs to loaded to show related data fields
//include related data entities
var v = db.Cases.Include("Customers");
ViewBag.TechnicianID = new SelectList(db.Technicians, "TechnicianID", "LastName", cases.TechnicianID);
ViewBag.BranchID = new SelectList(db.Branches, "BranchID", "BranchName", cases.BranchID);
ViewBag.EngineModelID = new SelectList(db.EngineModels, "EngineModelID", "EngineModelName", cases.EngineModelID);
ViewBag.CaseCategoryID = new SelectList(db.CaseCategories, "CaseCategoryID", "CategoryName",cases.CaseCategoryID);
ViewBag.Qualified = new SelectList(new[] { "YES", "NO", "PARTIALLY" });
ViewBag.CaseStatus = new SelectList(new[] { "OPEN/IN PROCESS", "CLOSED" });
return View(cases);
}
The line
var v = db.Cases.Include("Customers")
is what I am trying to use to load related customer data and then show in my edit view like this:
#Html.EditorFor(model => model.Customer.CustomerName)
Well it depends on what you are trying to do. You could include a model which holds all the required data and send it with every call on that page (initial empty ofcourse)
Once you selected the customer, do post-back and send the customerId to your application and return the same page with the desired model.
You could do that via AJAX too.
Also, do I need to Include the related entities as part of the initial data "get"?
Not sure if I understand what you are trying to say. You mean that you think you would have to send all customer's data down to the page and select the related data on client side?
My place of work currently maintains a website for several customers which is written using classic asp. Each customer requires specific parts of the website to be written specifically to them.
For example, customer A requires an Address to be input, displayed and stored in the following format:
Address Line 1
Address Line 2
Address Line 3
Address Line 4
Address Line 5
Postcode
whereas customer B requires the Address to be input, displayed and stored as:
Street
Town
City
Postcode
and so forth...
Therefore, my place of work took the path of storing the data as xml in the database and using xsl (of which I currently know little) to transform the data to html.
So if we require information from the user via a html form, the xml is transformed using xsl. The user then enters the information and submits the data via the form. An asp page is then used to validate the data. This asp page is specific to the xsl page used to display the form. Therefore, we are now in a postion where for each customer we have many xsl pages and many customer specific asp pages (where much of the code is duplicated).
I have been asked to move the site over to asp.net mvc3 and to remove much of the duplication and was wondering what would be the best way to cater for this customer specific field functionality. My preference would be to keep the data stored as xml as the database layer is accessed using com components which I would like to reuse without changing.
I have read that I could keep the xsl pages and develop an xslt view engine to display the html. However, I am not sure how I would validate the data when the user submits the form?
What would be the best way to display customer specific fields if I was to remove the xsl completely? Or would I have to have customer specific views and view models?
Any thoughts would be much appreciated.
If you really want to use MVC's built in validation / model functionality I think your best bet would be to use the XmlSerializer or use DataContracts to develop something that serializes to and from your XML (once its retrieved from the COM objects, so you don't need to re-code those), then you can use those classes as Models for MVC and use the standard data annotations for taking advantage of the richer MVC model functionality and skip the XSL step entirely.
To couple this with a custom specific view, what I typically do is override the default view engine to have one that actually will try names that are more specific to the customer/object and then fallback to a general one.
This view engine would allow you to pass a view to pass a view name (ie. FallbackViewEngine.BuildViewName("General", "Customer Name") and it would look for "General.Customer Name.cshtml" first and then "General.cshtml" as a fallback. This way you can actually use customer specific views in your view folder.
public class FallbackViewEngine : RazorViewEngine
{
const string NameSeparator = "==";
const string FileSeparator = ".";
public static string BuildViewName(string root, params string[] fallbackList)
{
if (string.IsNullOrWhiteSpace(root)) throw new ArgumentNullException("root");
if (fallbackList == null) throw new ArgumentNullException("fallbackList");
var sb = new StringBuilder(root);
foreach (var s in fallbackList)
{
if (string.IsNullOrWhiteSpace(s)) continue;
sb.Append(NameSeparator);
sb.Append(s);
}
return sb.ToString();
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (string.IsNullOrWhiteSpace(viewName)) throw new ArgumentNullException("viewName");
var names = viewName.Split(new string[] {NameSeparator}, StringSplitOptions.None);
var searched = new List<string>();
//iterate from specific to general
for (var i = names.Length; i >= 1; i--)
{
var result = base.FindView(controllerContext, string.Join(FileSeparator, names, 0, i), masterName, useCache);
if (result.View != null)
{
return result;
}
else
{
searched.AddRange(result.SearchedLocations);
}
}
return new ViewEngineResult(searched);
}
}
MVC best practices state that the model should handle input/data validation. Let's say that we have a model that creates new user accounts, with the following fields and constraints:
Username - not null, not already in DB
Password - not null, alphanumeric only
E-mail - not null, not already in DB, valid e-mail format
We have an AccountModel with a CreateNewUser() function:
component
{
public void function CreateNewUser(string username, string password, string email)
{
// create account
}
}
Then we have a controller that processes a form post and tells the model to create the account:
component
{
public void function NewUser()
{
var username = event.getValue("username");
var password = event.getValue("password");
var email = event.getValue("email");
var accountModel = new AccountModel();
accountModel.CreateNewUser(username, password, email);
event.addResult("UserCreated");
}
Now I want to add validation. If the user fails to provide input for all three fields, the application should show the user three validation error messages. This is easy enough to do in the controller:
// assumes that ValidateInput() is a function on the controller that returns an array
var validationErrors = ValidateInput(username, password, email);
// if there were any validation errors, return a ValidationError result
if (len(validationErrors)
{
event.setValue("validationerrors", validationErrors);
event.addResult("ValidationError");
}
else
{
event.addResult("UserCreated");
}
And the view will pull the validationerrors variable and display the error messages.
However, this logic is supposed to reside in the model. How do I do this? I can think of two ways:
Method 1: Move ValidateInput() from the controller to the model. Now the controller has to call ValidateInput() first before CreateNewUser(). If the programmer forgets to do this, CreateNewUser() will throw a validation exception. The downside to this is that now every data operation that requires validation will need an if/else block.
Method 2: Forego having to call ValidateInput() and just call CreateNewUser() directly. If there were any validation errors, an exception will be thrown and it will contain the error message array. This method would work in C#, but it looks like ColdFusion does not support returning of data with the exception, only an exception message and type. Also, every data operation will require a try/catch block.
Method 3: ??
How would you handle validation in this case? Is method 1 what most people do when it comes to validation in the model? Or do people typically use method 2? Or is there a method 3 that I'm not thinking of?
I don't think you should couple the validation of the user's data entry to the creation of the user's account: they are too different things.
If you couple the two together, it means you're basically doing form validation every time you create an account, which doesn't seem right to me.
I see form validation as a UI concern, more than a concern of the objects that might be ultimately created from that data. Obviously your createNewUser() method has its own business rules (which will probably closely mirror that of the validation for a create-new-user form, but they are still separate concerns).
It is possibly a bit unorthodox, but I will put a validateUserData() (or something) method in my User CFC which the form-validation model then calls to help with the validation. This means the business rules for a user are in the same place, and can be called separately. Thereafter, createNewUser() works on a garbage-in/garbage-out principle.
Sidenote: createNewUser() is a bit of a tautological name, innit? What other sort of user would you be creating other than a new one? Also, if it's in your Account.cfc; is it a new user or a new account that's being created? If an account and a user are not synonymous and an account might exist without a user (or vice-versa), perhaps you ought to have a User.cfc too. Then again, this code you've given us could simply be for the purposes of illustration, and you've got all this covered.