With the new methods implemented, it's time to create the
scaffolding for the CRUD forms. The first task will be to create
an edit form. Open the BreweriesController and
locate the Edit methods that were generated by
the Add Controller wizard.
In the HTTP GET override of Edit, modify it as
shown below. This action will retrieve the
Brewery and pass it to the view as the model.
Note the change from an int id parameter to a string id parameter.
public ActionResult Edit(string id) { var brewery = BreweryRepository.Get(id).Item1; return View(brewery); }
Update the Edit method that handles POSTs as
shown below. Validation and error handling are intentionally being
omitted for brevity.
[HttpPost] public ActionResult Edit(string id, Brewery brewery) { BreweryRepository.Update(brewery); return RedirectToAction("Index"); }
The edit form will be created using scaffolding, as was the case with the listing page. Right click on the “Breweries” folder in the “Views” directory and click Add -> View. Name the view “Edit” and strongly type it to a Brewery with Edit scaffolding.
Rebuild the application and return to the brewery listing page. Click on an “Edit” link and you should see the edit form loaded with the details for that brewery. Edit some values on the form and click save. You should see your changes persisted on the listing page.
The Details action looks very much like
Edit. Get the Brewery and
provide it as the model for the view.
public ActionResult Details(string id) { var brewery = BreweryRepository.Get(id).Item1; return View(brewery); }
Create a scaffolding form for Details using the
same process as was used with Edit.
Rebuild and return to the list page. Click on a "Details" link. You should see a page listing the data for that brewery.
The Create and Edit actions
of the BreweriesController are quite similar,
save for the fact that Create's GET method doesn't provide a model
to the view. Again, error handling and validation are being
omitted for brevity's sake.
public ActionResult Create() { return View(); } [HttpPost] public ActionResult Create(Brewery brewery) { BreweryRepository.Create(brewery); return RedirectToAction("Index"); }
Go through the scaffolding process again to add a create view for
the Create action. Rebuild and click the
“Create New” link on the list page to test the new form.
Breweries (for now) are sorted by key and limited to 50, so you
might not see yours in the list. If you want to verify your
create action worked, use brewery name that starts with a numeric
value (e.g., 123 Brewery).
Another reason you wouldn't see your new brewery appear in the list of breweries is that the view is set to allow stale (eventually consistent) results. In other words, the incremental update to the “all” index would be performed after the query. If you refresh, you should see your brewery in the list.
Allowing the breweries to be sorted by key is convenient, since the key is based on the breweries name. However, if case-sensitivity is important in sorting or the key creation strategy changes, then explicitly sorting on the brewery's name is a better idea. To that end, creating a new view indexed on the Brewery name is the right approach.
The new map function will look similar to the “all” map function, but will add tests on “doc.name” and will emit the doc.name as the key.
function(doc, meta) { if (doc.type == "brewery" && doc.name) { emit(doc.name, null); } }
If you are using the web console to manage your design documents,
save the map function above as “by_name” in the
“breweries” design document. If you are using the Model
Views framework, add an attribute to the Name
property of Brewery>. Then compile and run
your application.
Adding the CouchbaseViewKey attribute will
create the view above. The first argument is the name of the
view. The second is the name of the JSON document property to
emit as key.
[CouchbaseViewKey("by_name", “name”)] public string Name { get; set; }
The next step is to replace the GetAll call with a call to the new view. First, add a protected method in RepositoryBase that returns a typed view instance, set with the design doc for that model type. The isProjection flag is set when the type of T does not properties of the JSON to properties of the class. It must be used with explicit JSON.NET mappings.
protected IView<T> GetView(string name, bool isProjection = false) { return _Client.GetView<T>(_designDoc, name, ! isProjection); }
Then in BreweryRepository, implement
GetAllByName as shown below. This new method
simply returns the view, optionally allowing a limit and stale
results.
public IEnumerable<Brewery> GetAllByName(int limit = 0, bool allowStale = false) { var view = GetView("by_name"); if (limit > 0) view.Limit(limit); if (! allowStale) view.Stale(StaleMode.False); return view; }
Next, modify the BreweriesController so that
the Index action calls the new
GetAllByName method.
public ActionResult Index() { var breweries = BreweryRepository.GetAllByName(50); return View(breweries); }
Compile and run your application. The list page might be ordered a little differently as the sample database did scrub some keys of punctuation and other non-word or non-digit characters. Also now (because of the stale setting), if you create a new Brewery, it should appear after a redirect and should not require a refresh.
Note that it is still possible that the view didn't consider the new Brewery when it was executed with state set to false. If the document hadn't persisted to disk before the index was updated, it wouldn't have been included.
If that level of consistency is important to your application, you
should use an overload of ExecuteStore that
includes durability requirements. See the documentation on
ExecuteStore for more information.
The last piece required to complete the CRUD functionality for
breweries is to implement the delete form.
Update the Delete actions in
BreweriesController as shown below.
public ActionResult Delete(string id) { var brewery = BreweryRepository.Get(id).Item1; return View(brewery); } [HttpPost] public ActionResult Delete(string id, Brewery brewery) { BreweryRepository.Delete(id); return RedirectToAction("Index"); }
To create the delete form, simply go through the Add View process
again and choose scaffolding for delete (don't forget to choose
the Brewery model).